Skip to content

Nx Generator Generator

Adds an Nx Generator to a TypeScript project, to help you automate repetitive tasks such as scaffolding components or enforcing particular project structures.

You can generate a generator in two ways:

  1. Install the Nx Console VSCode Plugin if you haven't already
  2. Open the Nx Console in VSCode
  3. Click Generate (UI) in the "Common Nx Commands" section
  4. Search for @aws/nx-plugin - ts#nx-generator
  5. Fill in the required parameters
    • Click Generate
    Parameter Type Default Description
    pluginProject Required string - TypeScript project to add the generator to. We recommend creating a ts#project in a top-level 'tools' directory.
    name Required string - Generator name
    description string - A description of your generator
    directory string - The directory within the plugin project's source folder to add the generator to (default: <name>)

    The generator will create the following project files within the given pluginProject:

    • Directorysrc/<name>/
      • schema.json Schema for input to your generator
      • schema.d.ts TypeScript types for your schema
      • generator.ts Stub generator implementation
      • generator.spec.ts Tests for your generator
    • generators.json Nx configuration to define your generators
    • package.json Created or updated to add a “generators” entry
    • tsconfig.json Updated to use CommonJS

    This generator will update the selected pluginProject to use CommonJS, as Nx Generators only support CommonJS at present (refer to this GitHub issue for ESM support).

    Select your local nx-plugin project when running the ts#nx-generator generator, and specify a name and optional directory and description.

    The schema.json file defines the options that your generator accepts. It follows the JSON Schema format with Nx-specific extensions.

    A schema.json file has the following basic structure:

    {
    "$schema": "https://json-schema.org/schema",
    "$id": "YourGeneratorName",
    "title": "Your Generator Title",
    "description": "Description of what your generator does",
    "type": "object",
    "properties": {
    // Your generator options go here
    },
    "required": ["requiredOption1", "requiredOption2"]
    }

    Here’s a simple example with a few basic options:

    {
    "$schema": "https://json-schema.org/schema",
    "$id": "ComponentGenerator",
    "title": "Create a Component",
    "description": "Creates a new React component",
    "type": "object",
    "properties": {
    "name": {
    "type": "string",
    "description": "Component name",
    "x-priority": "important"
    },
    "directory": {
    "type": "string",
    "description": "Directory where the component will be created",
    "default": "src/components"
    },
    "withTests": {
    "type": "boolean",
    "description": "Whether to generate test files",
    "default": true
    }
    },
    "required": ["name"]
    }

    You can customise the prompts displayed when running your generator via the CLI by adding the x-prompt property:

    "name": {
    "type": "string",
    "description": "Component name",
    "x-prompt": "What is the name of your component?"
    }

    For boolean options, you can use a yes/no prompt:

    "withTests": {
    "type": "boolean",
    "description": "Whether to generate test files",
    "x-prompt": "Would you like to generate test files?"
    }

    For options with a fixed set of choices, use enum so that users can select from one of the options.

    "style": {
    "type": "string",
    "description": "The styling approach to use",
    "enum": ["css", "scss", "styled-components", "none"],
    "default": "css"
    }

    A common pattern is to let users select from existing projects in the workspace:

    "project": {
    "type": "string",
    "description": "The project to add the component to",
    "x-prompt": "Which project would you like to add the component to?",
    "x-dropdown": "projects"
    }

    The x-dropdown: "projects" property tells Nx to populate the dropdown with all projects in the workspace.

    You can configure options to be passed as positional arguments when running the generator from the command line:

    "name": {
    "type": "string",
    "description": "Component name",
    "x-priority": "important",
    "$default": {
    "$source": "argv",
    "index": 0
    }
    }

    This allows users to run your generator like nx g your-generator my-component instead of nx g your-generator --name=my-component.

    Use the x-priority property to indicate which options are most important:

    "name": {
    "type": "string",
    "description": "Component name",
    "x-priority": "important"
    }

    Options can have priorities of "important" or "internal". This helps Nx to order properties in the Nx VSCode extension and Nx CLI.

    You can provide default values for options:

    "directory": {
    "type": "string",
    "description": "Directory where the component will be created",
    "default": "src/components"
    }

    For more details on schemas, refer to the Nx Generator Options documentation.

    Along with schema.json, the generator creates a schema.d.ts file that provides TypeScript types for your generator options:

    export interface YourGeneratorSchema {
    name: string;
    directory?: string;
    withTests?: boolean;
    }

    This interface is used in your generator implementation to provide type safety and code completion:

    import { YourGeneratorSchema } from './schema';
    export default async function (tree: Tree, options: YourGeneratorSchema) {
    // TypeScript knows the types of all your options
    const { name, directory = 'src/components', withTests = true } = options;
    // ...
    }

    After creating the new generator as above, you can write your implementation in generator.ts.

    A generator is a function which mutates a virtual filesystem (the Tree), reading and writing files to make the desired changes. Changes from the Tree are only written to disk once the generator finishes executing, unless it is run in “dry-run” mode.

    Here are some common operations you might want to perform in your generator:

    // Read a file
    const content = tree.read('path/to/file.ts', 'utf-8');
    // Write a file
    tree.write('path/to/new-file.ts', 'export const hello = "world";');
    // Check if a file exists
    if (tree.exists('path/to/file.ts')) {
    // Do something
    }

    You can generate files with the generateFiles utility from @nx/devkit. This allows you to define templates in EJS syntax, and substitute variables.

    import { generateFiles, joinPathFragments } from '@nx/devkit';
    // Generate files from templates
    generateFiles(
    tree,
    joinPathFragments(__dirname, 'files'), // Template directory
    'path/to/output', // Output directory
    {
    // Variables to replace in templates
    name: options.name,
    nameCamelCase: camelCase(options.name),
    nameKebabCase: kebabCase(options.name),
    // Add more variables as needed
    },
    );

    TypeScript AST (Abstract Syntax Tree) Manipulation

    Section titled “TypeScript AST (Abstract Syntax Tree) Manipulation”

    You can use the tsAstReplace method exposed by the Nx Plugin for AWS to replace parts of a TypeScript abstract syntax tree.

    import { tsAstReplace } from '@aws/nx-plugin/sdk/utils/ast';
    import * as ts from 'typescript';
    // Example: Increment version number in a file
    tsAstReplace(
    tree,
    'path/to/version.ts',
    'VariableDeclaration:has(Identifier[name="VERSION"]) NumericLiteral',
    (node: ts.NumericLiteral) =>
    ts.factory.createNumericLiteral(Number(node.text) + 1));
    import { addDependenciesToPackageJson } from '@nx/devkit';
    // Add dependencies to package.json
    addDependenciesToPackageJson(
    tree,
    {
    'new-dependency': '^1.0.0',
    },
    {
    'new-dev-dependency': '^2.0.0',
    },
    );
    import { formatFilesInSubtree } from '@aws/nx-plugin/sdk/utils/format';
    // Format all files that were modified
    await formatFilesInSubtree(tree, 'optional/path/to/format');
    import { readJson, updateJson } from '@nx/devkit';
    // Read a JSON file
    const packageJson = readJson(tree, 'package.json');
    // Update a JSON file
    updateJson(tree, 'tsconfig.json', (json) => {
    json.compilerOptions = {
    ...json.compilerOptions,
    strict: true,
    };
    return json;
    });

    Extending a Generator from the Nx Plugin for AWS

    Section titled “Extending a Generator from the Nx Plugin for AWS”

    You can import generators from the Nx Plugin for AWS, and extend or compose them as you wish, for example you might wish to create a generator which builds on top of a TypeScript project:

    import { tsProjectGenerator } from '@aws/nx-plugin/sdk/ts';
    export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
    const callback = await tsProjectGenerator(tree, { ... });
    // Extend the TypeScript project generator here
    // Return the callback to ensure dependencies are installed.
    // You can wrap the callback if you wish to perform additional operations in the generator callback.
    return callback;
    };

    You can use and extend the generators we use for TypeScript clients and hooks in a similar way to the above:

    import { openApiTsClientGenerator } from '@aws/nx-plugin/sdk/open-api';
    export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
    await openApiTsClientGenerator(tree, { ... });
    // Add additional files here
    };

    We also expose a method which allows you to build a data structure that can be used to iterate over operations in an OpenAPI specification and therefore instrument your own code generation, for example:

    import { buildOpenApiCodeGenerationData } from '@aws/nx-plugin/sdk/open-api.js';
    export const myGenerator = async (tree: Tree, schema: MyGeneratorSchema) => {
    const data = await buildOpenApiCodeGenerationData(tree, 'path/to/spec.json');
    generateFiles(
    tree,
    joinPathFragments(__dirname, 'files'), // Template directory
    'path/to/output', // Output directory
    data,
    );
    };

    Which then allows you to write templates such as:

    files/my-operations.ts.template
    export const myOperationNames = [
    <%_ allOperations.forEach((op) => { _%>
    '<%- op.name %>',
    <%_ }); _%>
    ];

    Refer to the codebase on GitHub for more complex example templates.

    You can run your generator in two ways:

    1. Install the Nx Console VSCode Plugin if you haven't already
    2. Open the Nx Console in VSCode
    3. Click Generate (UI) in the "Common Nx Commands" section
    4. Search for @my-project/nx-plugin - my-generator
    5. Fill in the required parameters
      • Click Generate

      Unit tests for generators are straightforward to implement. Here’s a typical pattern:

      import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
      import { yourGenerator } from './generator';
      describe('your generator', () => {
      let tree;
      beforeEach(() => {
      // Create an empty workspace tree
      tree = createTreeWithEmptyWorkspace();
      // Add any files that should already exist in the tree
      tree.write(
      'project.json',
      JSON.stringify({
      name: 'test-project',
      sourceRoot: 'src',
      }),
      );
      tree.write('src/existing-file.ts', 'export const existing = true;');
      });
      it('should generate expected files', async () => {
      // Run the generator
      await yourGenerator(tree, {
      name: 'test',
      // Add other required options
      });
      // Check that files were created
      expect(tree.exists('src/test/file.ts')).toBeTruthy();
      // Check file content
      const content = tree.read('src/test/file.ts', 'utf-8');
      expect(content).toContain('export const test');
      // You can also use snapshots
      expect(tree.read('src/test/file.ts', 'utf-8')).toMatchSnapshot();
      });
      it('should update existing files', async () => {
      // Run the generator
      await yourGenerator(tree, {
      name: 'test',
      // Add other required options
      });
      // Check that existing files were updated
      const content = tree.read('src/existing-file.ts', 'utf-8');
      expect(content).toContain('import { test } from');
      });
      it('should handle errors', async () => {
      // Expect the generator to throw an error in certain conditions
      await expect(
      yourGenerator(tree, {
      name: 'invalid',
      // Add options that should cause an error
      }),
      ).rejects.toThrow('Expected error message');
      });
      });

      Key points for testing generators:

      • Use createTreeWithEmptyWorkspace() to create a virtual file system
      • Set up any prerequisite files before running the generator
      • Test both the creation of new files and updates to existing files
      • Use snapshots for complex file content
      • Test error conditions to ensure your generator fails gracefully

      You can also use ts#nx-generator to scaffold a generator within @aws/nx-plugin.

      When this generator is run in our repository, it’ll generate the following files for you:

      • Directorypackages/nx-plugin/src/<name>/
        • schema.json Schema for input to your generator
        • schema.d.ts TypeScript types for your schema
        • generator.ts Generator implementation
        • generator.spec.ts Tests for your generator
      • Directorydocs/src/content/docs/guides/
        • <name>.mdx Documentation page for your generator
      • packages/nx-plugin/generators.json Updated to include your generator

      You can then start to implement your generator.