πŸ”„ Meta-Generators

Generators that create new generators.


Overview

Meta-generators enable engineers to extend the generation infrastructure by creating custom generators with consistent structure.


Generator Types

Type Purpose Target Path
domain DDD entities, value objects libs/<scope>/<name>
service Backend services apps/<name>
component UI components libs/<scope>/ui/<name>
adapter Infrastructure adapters libs/<scope>/infrastructure/<name>
utility Shared utilities libs/shared/<name>
custom Blank template Configurable

Creating a New Generator

Using Just

1
2
3
4
5
6
7
8
# Create a domain generator
just generator-new order --type=domain

# Create a service generator
just generator-new billing --type=service

# Create a custom generator
just generator-new my-thing --type=custom

Manual Nx Command

1
2
3
pnpm exec nx g @nx/plugin:generator my-generator \
  --project=my-generators \
  --directory=src/generators/my-generator

Generator Structure

1
2
3
4
5
6
7
8
9
10
generators/
└── my-generator/
    β”œβ”€β”€ generator.ts       # Main generator logic
    β”œβ”€β”€ schema.json        # Configuration schema
    β”œβ”€β”€ schema.d.ts        # TypeScript types
    └── files/             # Template files
        └── __name__/
            β”œβ”€β”€ src/
            β”‚   └── index.ts__tmpl__
            └── package.json__tmpl__

Schema Definition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "$schema": "http://json-schema.org/schema",
  "id": "MyGenerator",
  "title": "My Generator",
  "type": "object",
  "properties": {
    "name": {
      "type": "string",
      "description": "Name of the generated project",
      "$default": { "$source": "argv", "index": 0 }
    },
    "directory": {
      "type": "string",
      "description": "Directory where the project will be created"
    },
    "tags": {
      "type": "string",
      "description": "Comma-separated tags"
    }
  },
  "required": ["name"]
}

Generator Implementation

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
28
29
30
31
32
33
34
35
36
37
import {
  Tree,
  formatFiles,
  generateFiles,
  names,
  offsetFromRoot,
  joinPathFragments,
} from '@nx/devkit';
import { MyGeneratorSchema } from './schema';

export default async function myGenerator(
  tree: Tree,
  options: MyGeneratorSchema
) {
  const projectRoot = joinPathFragments(
    options.directory ?? 'libs',
    options.name
  );
  
  // Generate files from templates
  generateFiles(
    tree,
    joinPathFragments(__dirname, './files'),
    projectRoot,
    {
      ...options,
      ...names(options.name),
      offsetFromRoot: offsetFromRoot(projectRoot),
      tmpl: '', // For __tmpl__ files
    }
  );
  
  // Update project configuration
  // Add to workspace...
  
  await formatFiles(tree);
}

Template Syntax

Variable Substitution

1
2
3
4
// __name__.ts__tmpl__
export function <%= name %>() {
  return '<%= propertyName %>';
}

Available Variables

Variable Example Input Output
name my-service my-service
className my-service MyService
propertyName my-service myService
constantName my-service MY_SERVICE
fileName my-service my-service

Registering Generators

generators.json

1
2
3
4
5
6
7
8
9
{
  "generators": {
    "my-generator": {
      "factory": "./src/generators/my-generator/generator",
      "schema": "./src/generators/my-generator/schema.json",
      "description": "Creates a new my-thing"
    }
  }
}

Testing Generators

1
2
3
4
5
6
7
8
9
10
11
12
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import myGenerator from './generator';

describe('my-generator', () => {
  it('should create expected files', async () => {
    const tree = createTreeWithEmptyWorkspace();
    
    await myGenerator(tree, { name: 'test' });
    
    expect(tree.exists('libs/test/src/index.ts')).toBeTruthy();
  });
});

Policies

GeneratorTraceability

Every generator must document its SEAβ„’ spec source:

Policy "GeneratorTraceability" per Constraint Obligation priority 10 as:
  forall g in generators:
    exists spec in g.metadata: (spec.id matches /SDS-\d+/)