CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-nx

AI-first build platform designed for monorepo development that provides task orchestration, project graph generation, and CLI tools for managing complex software projects

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

plugins.mddocs/

Plugin System

Comprehensive APIs for creating custom plugins that extend Nx's project detection, dependency analysis, and task configuration capabilities.

Capabilities

Plugin Interface

Core plugin interface for extending Nx functionality.

/**
 * Main plugin interface for extending Nx
 */
interface NxPlugin {
  /** Plugin name */
  name: string;
  /** Create project configurations from config files */
  createNodes?: CreateNodes;
  /** Create additional project dependencies */
  createDependencies?: CreateDependencies;
  /** Process and modify the project graph */
  processProjectGraph?: ProcessProjectGraph;
  /** Generate workspace files and configurations */
  createNodesV2?: CreateNodesV2;
}

/**
 * Function to create project nodes from configuration files
 */
type CreateNodes = (
  configFilePath: string,
  options: any,
  context: CreateNodesContext
) => CreateNodesResult | Promise<CreateNodesResult>;

/**
 * Enhanced version of CreateNodes with batching support
 */
type CreateNodesV2 = (
  configFiles: string[],
  options: any,
  context: CreateNodesContext
) => Promise<CreateNodesResultV2>;

/**
 * Function to create project dependencies
 */
type CreateDependencies = (
  opts: CreateDependenciesContext
) => RawProjectGraphDependency[] | Promise<RawProjectGraphDependency[]>;

/**
 * Function to process the project graph
 */
type ProcessProjectGraph = (
  graph: ProjectGraph,
  context: CreateDependenciesContext
) => ProjectGraph | Promise<ProjectGraph>;

Plugin Context Types

Context objects provided to plugin functions.

interface CreateNodesContext {
  /** Nx workspace configuration */
  nxJsonConfiguration: NxJsonConfiguration;
  /** Workspace root directory */
  workspaceRoot: string;
  /** All configuration files found */
  configFiles: string[];
  /** File map for workspace files */
  fileMap?: ProjectFileMap;
}

interface CreateDependenciesContext {
  /** All workspace files */
  projects: Record<string, ProjectConfiguration>;
  /** Nx workspace configuration */
  nxJsonConfiguration: NxJsonConfiguration;
  /** File map for workspace files */
  fileMap: ProjectFileMap;
  /** File map for external dependencies */
  externalNodes?: Record<string, ProjectGraphExternalNode>;
}

interface CreateNodesResult {
  /** Map of project root to project configuration */
  projects?: Record<string, CreateNodesProjectConfiguration>;
  /** External dependencies discovered */
  externalNodes?: Record<string, ProjectGraphExternalNode>;
}

interface CreateNodesResultV2 {
  /** Map of config file to create nodes result */
  [configFile: string]: CreateNodesResult;
}

interface CreateNodesProjectConfiguration {
  /** Project name */
  name?: string;
  /** Build targets */
  targets?: Record<string, TargetConfiguration>;
  /** Project metadata */
  metadata?: ProjectMetadata;
  /** Project tags */
  tags?: string[];
  /** Named inputs */
  namedInputs?: NamedInputs;
}

Plugin Registration

Functions for loading and registering plugins.

/**
 * Loads Nx plugins from the workspace configuration
 * @param nxJson - Nx workspace configuration
 * @param paths - Additional paths to search for plugins
 * @returns Array of loaded plugins
 */
function loadNxPlugins(
  nxJson: NxJsonConfiguration,
  paths?: string[]
): Promise<LoadedNxPlugin[]>;

/**
 * Checks if an object implements the NxPlugin interface
 * @param plugin - Object to check
 * @returns True if object is an NxPlugin
 */
function isNxPlugin(plugin: any): plugin is NxPlugin;

/**
 * Reads package.json for a plugin package
 * @param pluginName - Name of the plugin package
 * @param paths - Paths to search for the plugin
 * @returns Plugin package.json content
 */
function readPluginPackageJson(
  pluginName: string,
  paths?: string[]
): { packageJson: PackageJson; path: string };

interface LoadedNxPlugin {
  /** Plugin name */
  name: string;
  /** Plugin implementation */
  plugin: NxPlugin;
  /** Plugin options from nx.json */
  options: any;
}

Project Graph Dependencies

Types for defining project dependencies through plugins.

interface RawProjectGraphDependency {
  /** Source project name */
  source: string;
  /** Target project name */
  target: string;
  /** Type of dependency */
  type: DependencyType;
  /** Source file that created the dependency */
  sourceFile?: string;
}

interface ProjectGraphExternalNode {
  /** Package type (npm, etc.) */
  type: 'npm' | string;
  /** Package name */
  name: string;
  /** Package data */
  data: {
    version: string;
    packageName: string;
    hash?: string;
  };
}

type DependencyType = 
  | 'static'      // Import/require statements
  | 'dynamic'     // Dynamic imports
  | 'implicit'    // Configured dependencies
  | 'direct'      // Direct project dependencies
  | 'indirect';   // Transitive dependencies

Built-in Plugin Utilities

Utilities for common plugin development tasks.

/**
 * Creates a project node from package.json
 * @param packageJsonPath - Path to package.json
 * @param context - Create nodes context
 * @returns Project configuration
 */
function createNodeFromPackageJson(
  packageJsonPath: string,
  context: CreateNodesContext
): CreateNodesProjectConfiguration | null;

/**
 * Parses target configuration from executor strings
 * @param executor - Executor string (e.g., "@nx/webpack:webpack")
 * @returns Parsed executor information
 */
function parseExecutor(executor: string): {
  package: string;
  executor: string;
};

/**
 * Resolves workspace relative paths
 * @param workspaceRoot - Workspace root directory
 * @param path - Path to resolve
 * @returns Resolved absolute path
 */
function resolveWorkspacePath(workspaceRoot: string, path: string): string;

/**
 * Gets project root from configuration file path
 * @param configFilePath - Path to configuration file
 * @param workspaceRoot - Workspace root directory
 * @returns Project root directory
 */
function getProjectRootFromConfigFile(
  configFilePath: string,
  workspaceRoot: string
): string;

Usage Examples

Basic Plugin Implementation

import { 
  NxPlugin, 
  CreateNodes, 
  CreateNodesContext,
  CreateNodesResult,
  TargetConfiguration,
  logger 
} from "nx/src/devkit-exports";

const createNodes: CreateNodes = (
  configFilePath: string,
  options: any,
  context: CreateNodesContext
): CreateNodesResult => {
  logger.info(`Processing config file: ${configFilePath}`);
  
  // Get project root from config file path
  const projectRoot = path.dirname(configFilePath);
  const projectName = path.basename(projectRoot);
  
  // Read configuration file
  const configContent = readFileSync(configFilePath, 'utf-8');
  const config = JSON.parse(configContent);
  
  // Create build target based on configuration
  const buildTarget: TargetConfiguration = {
    executor: '@my-plugin/executor:build',
    options: {
      outputPath: `dist/${projectRoot}`,
      ...config.buildOptions
    }
  };
  
  // Create test target if tests are configured
  const targets: Record<string, TargetConfiguration> = { build: buildTarget };
  
  if (config.testFramework) {
    targets.test = {
      executor: '@my-plugin/executor:test',
      options: {
        testFramework: config.testFramework,
        ...config.testOptions
      }
    };
  }
  
  return {
    projects: {
      [projectRoot]: {
        name: projectName,
        targets,
        tags: config.tags || [],
        metadata: {
          framework: config.framework,
          version: config.version
        }
      }
    }
  };
};

const myPlugin: NxPlugin = {
  name: 'my-custom-plugin',
  createNodes: [
    // Glob pattern to match configuration files
    '**/my-config.json',
    createNodes
  ]
};

export default myPlugin;

Advanced Plugin with Dependencies

import {
  NxPlugin,
  CreateNodes,
  CreateDependencies,
  CreateDependenciesContext,
  RawProjectGraphDependency,
  logger
} from "nx/src/devkit-exports";

const createDependencies: CreateDependencies = (
  context: CreateDependenciesContext
): RawProjectGraphDependency[] => {
  const dependencies: RawProjectGraphDependency[] = [];
  
  // Analyze workspace files to find dependencies
  for (const [projectName, project] of Object.entries(context.projects)) {
    const projectFiles = context.fileMap.projectFileMap[projectName] || [];
    
    // Look for custom import patterns
    for (const file of projectFiles) {
      if (file.file.endsWith('.ts') || file.file.endsWith('.js')) {
        const filePath = path.join(context.workspaceRoot, file.file);
        const content = readFileSync(filePath, 'utf-8');
        
        // Find custom import statements
        const importRegex = /import.*from\s+['"]@workspace\/([^'"]+)['"]/g;
        let match;
        
        while ((match = importRegex.exec(content)) !== null) {
          const targetProject = match[1];
          
          if (context.projects[targetProject]) {
            dependencies.push({
              source: projectName,
              target: targetProject,
              type: 'static',
              sourceFile: file.file
            });
          }
        }
      }
    }
  }
  
  logger.info(`Found ${dependencies.length} custom dependencies`);
  return dependencies;
};

const advancedPlugin: NxPlugin = {
  name: 'advanced-plugin',
  createNodes: ['**/project.config.json', createNodes],
  createDependencies
};

export default advancedPlugin;

Plugin with Project Graph Processing

import {
  NxPlugin,
  ProcessProjectGraph,
  ProjectGraph,
  CreateDependenciesContext,
  logger
} from "nx/src/devkit-exports";

const processProjectGraph: ProcessProjectGraph = (
  graph: ProjectGraph,
  context: CreateDependenciesContext
): ProjectGraph => {
  logger.info('Processing project graph for optimization');
  
  // Add virtual nodes for shared resources
  const modifiedGraph = { ...graph };
  
  // Find projects that share common patterns
  const sharedLibraries = new Set<string>();
  
  for (const [projectName, dependencies] of Object.entries(graph.dependencies)) {
    dependencies.forEach(dep => {
      if (dep.target.startsWith('shared-')) {
        sharedLibraries.add(dep.target);
      }
    });
  }
  
  // Add metadata to shared library nodes
  for (const sharedLib of sharedLibraries) {
    if (modifiedGraph.nodes[sharedLib]) {
      modifiedGraph.nodes[sharedLib].data = {
        ...modifiedGraph.nodes[sharedLib].data,
        tags: [...(modifiedGraph.nodes[sharedLib].data.tags || []), 'shared']
      };
    }
  }
  
  return modifiedGraph;
};

const graphProcessorPlugin: NxPlugin = {
  name: 'graph-processor-plugin',
  processProjectGraph
};

export default graphProcessorPlugin;

Multi-Framework Plugin

import {
  NxPlugin,
  CreateNodes,
  CreateNodesContext,
  CreateNodesResult,
  TargetConfiguration
} from "nx/src/devkit-exports";

interface FrameworkConfig {
  framework: 'react' | 'vue' | 'angular';
  buildTool: 'webpack' | 'vite' | 'rollup';
  features: string[];
}

const createNodes: CreateNodes = (
  configFilePath: string,
  options: any,
  context: CreateNodesContext
): CreateNodesResult => {
  const projectRoot = path.dirname(configFilePath);
  const config: FrameworkConfig = JSON.parse(
    readFileSync(configFilePath, 'utf-8')
  );
  
  // Create framework-specific targets
  const targets: Record<string, TargetConfiguration> = {};
  
  // Build target based on framework and build tool
  switch (config.framework) {
    case 'react':
      targets.build = {
        executor: config.buildTool === 'vite' 
          ? '@nx/vite:build'
          : '@nx/webpack:webpack',
        options: {
          outputPath: `dist/${projectRoot}`,
          main: `${projectRoot}/src/main.tsx`,
          index: `${projectRoot}/src/index.html`
        }
      };
      break;
      
    case 'vue':
      targets.build = {
        executor: '@nx/vite:build',
        options: {
          outputPath: `dist/${projectRoot}`,
          configFile: `${projectRoot}/vite.config.ts`
        }
      };
      break;
      
    case 'angular':
      targets.build = {
        executor: '@angular-devkit/build-angular:browser',
        options: {
          outputPath: `dist/${projectRoot}`,
          index: `${projectRoot}/src/index.html`,
          main: `${projectRoot}/src/main.ts`,
          polyfills: `${projectRoot}/src/polyfills.ts`,
          tsConfig: `${projectRoot}/tsconfig.app.json`
        }
      };
      break;
  }
  
  // Add feature-specific targets
  if (config.features.includes('testing')) {
    targets.test = {
      executor: '@nx/jest:jest',
      options: {
        jestConfig: `${projectRoot}/jest.config.ts`
      }
    };
  }
  
  if (config.features.includes('e2e')) {
    targets.e2e = {
      executor: '@nx/cypress:cypress',
      options: {
        cypressConfig: `${projectRoot}/cypress.config.ts`
      }
    };
  }
  
  if (config.features.includes('storybook')) {
    targets.storybook = {
      executor: '@nx/storybook:storybook',
      options: {
        port: 4400,
        configDir: `${projectRoot}/.storybook`
      }
    };
  }
  
  return {
    projects: {
      [projectRoot]: {
        name: path.basename(projectRoot),
        targets,
        tags: [config.framework, config.buildTool, ...config.features],
        metadata: {
          framework: config.framework,
          buildTool: config.buildTool,
          features: config.features
        }
      }
    }
  };
};

const multiFrameworkPlugin: NxPlugin = {
  name: 'multi-framework-plugin',
  createNodes: ['**/framework.config.json', createNodes]
};

export default multiFrameworkPlugin;

Plugin Configuration in nx.json

{
  "plugins": [
    {
      "plugin": "@my-org/nx-custom-plugin",
      "options": {
        "buildCommand": "custom-build",
        "testPattern": "**/*.test.{js,ts}",
        "outputDir": "custom-dist"
      }
    },
    {
      "plugin": "./tools/local-plugin",
      "options": {
        "localOption": "value"
      }
    }
  ]
}

Plugin Development Best Practices

import { 
  NxPlugin, 
  CreateNodes, 
  logger,
  workspaceRoot 
} from "nx/src/devkit-exports";

// Cache parsed configurations to improve performance
const configCache = new Map<string, any>();

const createNodes: CreateNodes = (configFilePath, options, context) => {
  // Use caching for expensive operations
  if (configCache.has(configFilePath)) {
    const cachedConfig = configCache.get(configFilePath);
    return createProjectFromConfig(cachedConfig, configFilePath, options);
  }
  
  try {
    // Read and parse configuration
    const config = parseConfigFile(configFilePath);
    configCache.set(configFilePath, config);
    
    return createProjectFromConfig(config, configFilePath, options);
    
  } catch (error) {
    logger.warn(`Failed to parse config file ${configFilePath}: ${error.message}`);
    return {}; // Return empty result on error
  }
};

function createProjectFromConfig(config: any, configFilePath: string, options: any) {
  const projectRoot = path.relative(workspaceRoot, path.dirname(configFilePath));
  
  // Validate configuration
  if (!config || typeof config !== 'object') {
    logger.warn(`Invalid configuration in ${configFilePath}`);
    return {};
  }
  
  // Create targets with error handling
  const targets: Record<string, TargetConfiguration> = {};
  
  try {
    if (config.build) {
      targets.build = createBuildTarget(config.build, projectRoot, options);
    }
    
    if (config.test) {
      targets.test = createTestTarget(config.test, projectRoot, options);
    }
  } catch (error) {
    logger.error(`Error creating targets for ${projectRoot}: ${error.message}`);
    return {};
  }
  
  return {
    projects: {
      [projectRoot]: {
        name: config.name || path.basename(projectRoot),
        targets,
        tags: config.tags || [],
        metadata: {
          configFile: configFilePath,
          pluginVersion: '1.0.0'
        }
      }
    }
  };
}

const robustPlugin: NxPlugin = {
  name: 'robust-plugin',
  createNodes: ['**/plugin.config.{json,js,ts}', createNodes]
};

export default robustPlugin;

Install with Tessl CLI

npx tessl i tessl/npm-nx

docs

cli.md

devkit-core.md

devkit-files.md

devkit-tasks.md

generators-executors.md

index.md

plugins.md

tile.json