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

generators-executors.mddocs/

Generators and Executors

Built-in code generation and task execution tools, plus comprehensive APIs for creating custom generators and executors.

Capabilities

Generator Development APIs

Functions and utilities for creating custom code generators.

/**
 * Formats all files in the tree using configured formatters
 * @param tree - Virtual file tree
 * @returns Promise that resolves when formatting is complete
 */
function formatFiles(tree: Tree): Promise<void>;

/**
 * Generates files from templates with variable substitution
 * @param tree - Virtual file tree
 * @param templatePath - Path to template directory
 * @param targetPath - Target directory for generated files
 * @param substitutions - Variables for template substitution
 */
function generateFiles(
  tree: Tree,
  templatePath: string,
  targetPath: string,
  substitutions: any
): void;

/**
 * Adds a callback to install packages after generation
 * @param tree - Virtual file tree
 * @param options - Package installation options
 * @returns Generator callback function
 */
function installPackagesTask(tree: Tree, options?: InstallPackagesTaskOptions): GeneratorCallback;

/**
 * Adds dependencies to package.json
 * @param tree - Virtual file tree
 * @param dependencies - Map of package names to versions
 * @param options - Installation options
 * @returns Generator callback function
 */
function addDependenciesToPackageJson(
  tree: Tree,
  dependencies: Record<string, string>,
  devDependencies?: Record<string, string>,
  options?: InstallPackagesTaskOptions
): GeneratorCallback;

/**
 * Removes dependencies from package.json
 * @param tree - Virtual file tree
 * @param dependencies - Array of package names to remove
 * @param devDependencies - Array of dev dependency names to remove
 * @returns Generator callback function
 */
function removeDependenciesFromPackageJson(
  tree: Tree,
  dependencies: string[],
  devDependencies?: string[]
): GeneratorCallback;

interface InstallPackagesTaskOptions {
  packageManager?: 'npm' | 'yarn' | 'pnpm';
  cwd?: string;
}

/**
 * Generator callback function type
 */
type GeneratorCallback = () => void | Promise<void>;

/**
 * Generator function signature
 */
type Generator<T = any> = (
  tree: Tree,
  schema: T
) => void | GeneratorCallback | Promise<void | GeneratorCallback>;

Generator Utilities

Helper functions for common generator tasks.

/**
 * Converts strings to various naming conventions
 */
interface Names {
  /** Converts to PascalCase for class names */
  className: (name: string) => string;
  /** Converts to camelCase for property names */
  propertyName: (name: string) => string;
  /** Converts to CONSTANT_CASE */
  constantName: (name: string) => string;
  /** Converts to kebab-case for file names */
  fileName: (name: string) => string;
}

/**
 * Naming utility functions
 * @param name - Input string to convert
 * @returns Object with various naming convention functions
 */
function names(name: string): Names;

/**
 * Strips indentation from template strings
 * @param strings - Template string parts
 * @param values - Template interpolation values
 * @returns String with normalized indentation
 */
function stripIndents(strings: TemplateStringsArray, ...values: any[]): string;

/**
 * Gets project configuration with error handling
 * @param tree - Virtual file tree
 * @param projectName - Name of the project
 * @returns Project configuration
 */
function readProjectConfiguration(tree: Tree, projectName: string): ProjectConfiguration;

/**
 * Updates project configuration
 * @param tree - Virtual file tree
 * @param projectName - Name of the project
 * @param config - Updated project configuration
 */
function updateProjectConfiguration(
  tree: Tree,
  projectName: string,
  config: ProjectConfiguration
): void;

Built-in Generators

Nx includes built-in generators for common tasks.

/**
 * Connect workspace to Nx Cloud generator
 * Configures workspace for remote caching and distributed execution
 */
interface ConnectToNxCloudGeneratorOptions {
  /** Skip interactive prompts */
  hideFormatLogs?: boolean;
  /** Installation method */
  installationSource?: string;
}

/**
 * Generator function for connecting to Nx Cloud
 * @param tree - Virtual file tree
 * @param options - Generator options
 * @returns Generator callback for package installation
 */
declare function connectToNxCloudGenerator(
  tree: Tree,
  options: ConnectToNxCloudGeneratorOptions
): GeneratorCallback;

Executor Development APIs

Functions and types for creating custom executors.

/**
 * Executor function signature for promise-based executors
 */
type PromiseExecutor<T = any> = (
  options: T,
  context: ExecutorContext
) => Promise<{ success: boolean; [key: string]: any }>;

/**
 * Executor function signature for async iterator executors
 */
type Executor<T = any> = (
  options: T,
  context: ExecutorContext
) => Promise<{ success: boolean; [key: string]: any }> | 
   AsyncIterableIterator<{ success: boolean; [key: string]: any }>;

/**
 * Converts async iterator executor to promise-based executor
 * @param executor - Async iterator executor
 * @returns Promise-based executor
 */
function convertToPromiseExecutor<T>(
  executor: Executor<T>
): PromiseExecutor<T>;

interface ExecutorContext {
  /** Workspace root directory */
  root: string;
  /** Current working directory */
  cwd: string;
  /** Workspace configuration */
  workspace: WorkspaceJsonConfiguration;
  /** Whether verbose logging is enabled */
  isVerbose: boolean;
  /** Name of the project being executed */
  projectName?: string;
  /** Name of the target being executed */
  targetName?: string;
  /** Name of the configuration being used */
  configurationName?: string;
  /** Task graph for current execution */
  taskGraph?: TaskGraph;
  /** Hash of the current task */
  hash?: string;
}

Built-in Executors

Nx includes several built-in executors for common tasks.

/**
 * No-operation executor for testing
 * Does nothing and always succeeds
 */
interface NoopExecutorOptions {}

/**
 * Run arbitrary shell commands executor
 */
interface RunCommandsExecutorOptions {
  /** Command to execute */
  command?: string;
  /** Multiple commands to execute */
  commands?: Array<{
    command: string;
    /** Command description */
    description?: string;
    /** Override working directory */
    cwd?: string;
    /** Environment variables */
    env?: Record<string, string>;
    /** Prefix for command output */
    prefix?: string;
    /** Background execution */
    background?: boolean;
  }>;
  /** Working directory for commands */
  cwd?: string;
  /** Environment variables */
  env?: Record<string, string>;
  /** Run commands in parallel */
  parallel?: boolean;
  /** Maximum parallel processes */
  maxParallel?: number;
  /** Prefix for output */
  prefix?: string;
  /** Color output */
  color?: boolean;
  /** Read commands from package.json scripts */
  readyWhen?: string;
}

/**
 * Run package.json scripts executor
 */
interface RunScriptExecutorOptions {
  /** Script name from package.json */
  script: string;
  /** Additional arguments to pass to script */
  args?: string[];
  /** Working directory */
  cwd?: string;
  /** Environment variables */
  env?: Record<string, string>;
}

Usage Examples

Creating a Custom Generator

import {
  Tree,
  formatFiles,
  generateFiles,
  installPackagesTask,
  addDependenciesToPackageJson,
  readProjectConfiguration,
  updateProjectConfiguration,
  names,
  joinPathFragments,
  logger
} from "nx/src/devkit-exports";

interface ComponentGeneratorSchema {
  name: string;
  project: string;
  directory?: string;
  skipTests?: boolean;
  export?: boolean;
}

export default async function componentGenerator(
  tree: Tree,
  options: ComponentGeneratorSchema
) {
  logger.info(`Generating component: ${options.name}`);
  
  // Read project configuration
  const projectConfig = readProjectConfiguration(tree, options.project);
  const projectRoot = projectConfig.root;
  const sourceRoot = projectConfig.sourceRoot || `${projectRoot}/src`;
  
  // Determine component directory
  const componentDir = options.directory
    ? joinPathFragments(sourceRoot, options.directory)
    : joinPathFragments(sourceRoot, 'components');
    
  // Generate component files from templates
  const substitutions = {
    ...options,
    ...names(options.name),
    template: '', // Remove __template__ from file names
    skipTests: options.skipTests
  };
  
  generateFiles(
    tree,
    joinPathFragments(__dirname, 'files'),
    componentDir,
    substitutions
  );
  
  // Add component to barrel export
  if (options.export !== false) {
    const indexPath = joinPathFragments(sourceRoot, 'index.ts');
    const componentName = names(options.name).className;
    const relativePath = `./${names(options.name).fileName}`;
    
    if (tree.exists(indexPath)) {
      const content = tree.read(indexPath, 'utf-8') || '';
      const exportStatement = `export * from '${relativePath}';`;
      
      if (!content.includes(exportStatement)) {
        tree.write(indexPath, `${content}\n${exportStatement}`);
      }
    } else {
      tree.write(indexPath, `export * from '${relativePath}';`);
    }
  }
  
  // Add dependencies if needed
  const installTask = addDependenciesToPackageJson(
    tree,
    {}, // dependencies
    {
      '@types/react': '^18.0.0'
    } // devDependencies
  );
  
  // Format all generated files
  await formatFiles(tree);
  
  return installTask;
}

// Schema definition (schema.json)
export const schema = {
  $schema: 'http://json-schema.org/schema',
  type: 'object',
  properties: {
    name: {
      type: 'string',
      description: 'The name of the component.'
    },
    project: {
      type: 'string',
      description: 'The name of the project.',
      $default: {
        $source: 'projectName'
      }
    },
    directory: {
      type: 'string',
      description: 'The directory at which to create the component file, relative to the project source root.'
    },
    skipTests: {
      type: 'boolean',
      description: 'Skip creating test files.',
      default: false
    },
    export: {
      type: 'boolean',
      description: 'Add the component to the nearest export barrel.',
      default: true
    }
  },
  required: ['name', 'project'],
  additionalProperties: false
};

Template Files for Generator

Template directory structure:

files/
├── __name@fileName__.component.ts__template__
├── __name@fileName__.component.spec.ts__template__ (if !skipTests)
└── __name@fileName__.stories.ts__template__

Template content (__name@fileName__.component.ts__template__):

import React from 'react';

export interface <%= className %>Props {
  title?: string;
}

export function <%= className %>(props: <%= className %>Props) {
  return (
    <div>
      <h1>{props.title || '<%= name %>'}</h1>
    </div>
  );
}

export default <%= className %>;

Creating a Custom Executor

import { ExecutorContext, logger } from "nx/src/devkit-exports";
import { spawn } from 'child_process';
import { promisify } from 'util';

interface DockerBuildExecutorOptions {
  dockerfile?: string;
  context?: string;
  tag: string;
  buildArgs?: Record<string, string>;
  target?: string;
  push?: boolean;
  registry?: string;
}

export default async function dockerBuildExecutor(
  options: DockerBuildExecutorOptions,
  context: ExecutorContext
): Promise<{ success: boolean }> {
  logger.info(`Building Docker image for ${context.projectName}`);
  
  const projectRoot = context.workspace.projects[context.projectName].root;
  const dockerfile = options.dockerfile || 'Dockerfile';
  const buildContext = options.context || projectRoot;
  
  // Build Docker command
  const dockerArgs = [
    'build',
    '-f', dockerfile,
    '-t', options.tag
  ];
  
  // Add build args
  if (options.buildArgs) {
    for (const [key, value] of Object.entries(options.buildArgs)) {
      dockerArgs.push('--build-arg', `${key}=${value}`);
    }
  }
  
  // Add target if specified
  if (options.target) {
    dockerArgs.push('--target', options.target);
  }
  
  // Add build context
  dockerArgs.push(buildContext);
  
  try {
    // Execute docker build
    await executeCommand('docker', dockerArgs, context.root);
    logger.info(`✅ Docker image built successfully: ${options.tag}`);
    
    // Push if requested
    if (options.push) {
      const pushTag = options.registry 
        ? `${options.registry}/${options.tag}`
        : options.tag;
        
      await executeCommand('docker', ['push', pushTag], context.root);
      logger.info(`✅ Docker image pushed successfully: ${pushTag}`);
    }
    
    return { success: true };
    
  } catch (error) {
    logger.error(`❌ Docker build failed: ${error.message}`);
    return { success: false };
  }
}

function executeCommand(command: string, args: string[], cwd: string): Promise<void> {
  return new Promise((resolve, reject) => {
    const child = spawn(command, args, {
      cwd,
      stdio: 'inherit'
    });
    
    child.on('close', (code) => {
      if (code === 0) {
        resolve();
      } else {
        reject(new Error(`Command failed with exit code ${code}`));
      }
    });
    
    child.on('error', reject);
  });
}

// Executor schema (schema.json)
export const schema = {
  $schema: 'http://json-schema.org/schema',
  type: 'object',
  properties: {
    dockerfile: {
      type: 'string',
      description: 'Path to the Dockerfile',
      default: 'Dockerfile'
    },
    context: {
      type: 'string',
      description: 'Build context directory'
    },
    tag: {
      type: 'string',
      description: 'Image tag'
    },
    buildArgs: {
      type: 'object',
      description: 'Build arguments',
      additionalProperties: { type: 'string' }
    },
    target: {
      type: 'string',
      description: 'Build target'
    },
    push: {
      type: 'boolean',
      description: 'Push image after build',
      default: false
    },
    registry: {
      type: 'string',
      description: 'Docker registry URL'
    }
  },
  required: ['tag'],
  additionalProperties: false
};

Using Built-in Executors

import { runExecutor, ExecutorContext } from "nx/src/devkit-exports";

// Using run-commands executor
async function runCustomCommands(projectName: string) {
  const context: ExecutorContext = {
    root: process.cwd(),
    cwd: process.cwd(),
    workspace: readWorkspaceConfiguration(),
    isVerbose: false,
    projectName
  };
  
  const options = {
    commands: [
      {
        command: 'echo "Starting build process..."',
        description: 'Build start message'
      },
      {
        command: 'npm run build',
        description: 'Build application',
        cwd: 'apps/my-app'
      },
      {
        command: 'npm run test',
        description: 'Run tests',
        background: false
      }
    ],
    parallel: false,
    color: true
  };
  
  const results = await runExecutor(
    { project: projectName, target: 'run-commands' },
    options,
    context
  );
  
  for await (const result of results) {
    if (!result.success) {
      throw new Error('Commands execution failed');
    }
  }
}

Workspace Generator

import { 
  Tree,
  formatFiles,
  generateFiles,
  readWorkspaceConfiguration,
  updateWorkspaceConfiguration,
  addProjectConfiguration,
  names,
  joinPathFragments
} from "nx/src/devkit-exports";

interface LibraryGeneratorSchema {
  name: string;
  directory?: string;
  tags?: string;
  buildable?: boolean;
  publishable?: boolean;
}

export default async function libraryGenerator(
  tree: Tree,
  options: LibraryGeneratorSchema
) {
  const normalizedOptions = normalizeOptions(tree, options);
  
  // Add project configuration
  addProjectConfiguration(tree, normalizedOptions.projectName, {
    root: normalizedOptions.projectRoot,
    sourceRoot: `${normalizedOptions.projectRoot}/src`,
    projectType: 'library',
    targets: {
      build: {
        executor: '@nx/js:tsc',
        outputs: [`{options.outputPath}`],
        options: {
          outputPath: `dist/${normalizedOptions.projectRoot}`,
          main: `${normalizedOptions.projectRoot}/src/index.ts`,
          tsConfig: `${normalizedOptions.projectRoot}/tsconfig.lib.json`
        }
      },
      test: {
        executor: '@nx/jest:jest',
        outputs: [`{workspaceRoot}/coverage/${normalizedOptions.projectRoot}`],
        options: {
          jestConfig: `${normalizedOptions.projectRoot}/jest.config.ts`
        }
      }
    },
    tags: normalizedOptions.parsedTags
  });
  
  // Generate library files
  generateFiles(
    tree,
    joinPathFragments(__dirname, 'files'),
    normalizedOptions.projectRoot,
    {
      ...normalizedOptions,
      template: ''
    }
  );
  
  await formatFiles(tree);
}

function normalizeOptions(tree: Tree, options: LibraryGeneratorSchema) {
  const name = names(options.name).fileName;
  const projectDirectory = options.directory 
    ? `${names(options.directory).fileName}/${name}`
    : name;
  const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');
  const projectRoot = `libs/${projectDirectory}`;
  
  const parsedTags = options.tags
    ? options.tags.split(',').map((s) => s.trim())
    : [];
    
  return {
    ...options,
    projectName,
    projectRoot,
    projectDirectory,
    parsedTags
  };
}

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