How To: Create an Nx Generator

Create custom Nx generators for SEA-Forge™ to scaffold new bounded contexts, services, or components following the spec-first pipeline.


Prerequisites


Quick Start

1. Generate Generator Scaffold

1
nx g @nx/plugin:generator <generator-name> --project=generators

This creates:

1
2
3
4
5
6
generators/
  <generator-name>/
    schema.json      # Input schema
    schema.d.ts      # TypeScript types
    generator.ts     # Implementation
    generator.spec.ts # Tests

2. Define Schema

Edit schema.json to define inputs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
  "$schema": "http://json-schema.org/schema",
  "id": "<generator-name>",
  "title": "<Generator Title>",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "Name of the item to generate",
      "$default": {
        "$source": "argv",
        "index": 0
      }
    },
    "context": {
      "type": "string",
      "description": "Bounded context name"
    }
  },
  "required": ["name", "context"]
}

3. Implement Generator

Edit generator.ts:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import { Tree, formatFiles, installPackagesTask } from '@nx/devkit';
import { Schema } from './schema';

export default async function (tree: Tree, options: Schema) {
  // 1. Validate inputs
  if (!options.name) {
    throw new Error('Name is required');
  }

  // 2. Generate files from templates
  generateFiles(
    tree,
    path.join(__dirname, 'files'),
    `libs/${options.context}`,
    {
      ...options,
      tmpl: '',
    }
  );

  // 3. Update configuration
  await formatFiles(tree);

  return () => {
    installPackagesTask(tree);
  };
}

4. Create Templates

Create template files in files/ directory:

1
2
3
4
files/
  __name__/
    __name__.ts__tmpl__
    README.md__tmpl__

Template content (uses EJS):

1
2
3
4
5
6
// __name__.ts__tmpl__
export class <%= className %> {
  constructor() {
    console.log('<%= name %> initialized');
  }
}

5. Test Generator

1
nx g generators:<generator-name> my-feature --context=semantic-core --dry-run

Example: Bounded Context Generator

See existing generator:

1
2
3
4
5
# View source
cat generators/bounded-context/generator.ts

# Run generator
nx g generators:bounded-context inventory

What it creates:


Best Practices

  1. Schema Validation: Use JSON Schema to validate inputs
  2. Dry Run Support: Always test with --dry-run first
  3. File Templates: Use EJS templates for dynamic content
  4. Format Files: Call formatFiles() to apply Prettier
  5. Update Config: Modify nx.json or project.json as needed
  6. Error Handling: Provide clear error messages

Testing Generators

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// generator.spec.ts
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Tree } from '@nx/devkit';
import generator from './generator';

describe('my-generator', () => {
  let tree: Tree;

  beforeEach(() => {
    tree = createTreeWithEmptyWorkspace();
  });

  it('should generate files', async () => {
    await generator(tree, { name: 'test', context: 'semantic-core' });

    expect(tree.exists('libs/semantic-core/test/test.ts')).toBeTruthy();
  });
});