0
# Generators and Executors
1
2
Built-in code generation and task execution tools, plus comprehensive APIs for creating custom generators and executors.
3
4
## Capabilities
5
6
### Generator Development APIs
7
8
Functions and utilities for creating custom code generators.
9
10
```typescript { .api }
11
/**
12
* Formats all files in the tree using configured formatters
13
* @param tree - Virtual file tree
14
* @returns Promise that resolves when formatting is complete
15
*/
16
function formatFiles(tree: Tree): Promise<void>;
17
18
/**
19
* Generates files from templates with variable substitution
20
* @param tree - Virtual file tree
21
* @param templatePath - Path to template directory
22
* @param targetPath - Target directory for generated files
23
* @param substitutions - Variables for template substitution
24
*/
25
function generateFiles(
26
tree: Tree,
27
templatePath: string,
28
targetPath: string,
29
substitutions: any
30
): void;
31
32
/**
33
* Adds a callback to install packages after generation
34
* @param tree - Virtual file tree
35
* @param options - Package installation options
36
* @returns Generator callback function
37
*/
38
function installPackagesTask(tree: Tree, options?: InstallPackagesTaskOptions): GeneratorCallback;
39
40
/**
41
* Adds dependencies to package.json
42
* @param tree - Virtual file tree
43
* @param dependencies - Map of package names to versions
44
* @param options - Installation options
45
* @returns Generator callback function
46
*/
47
function addDependenciesToPackageJson(
48
tree: Tree,
49
dependencies: Record<string, string>,
50
devDependencies?: Record<string, string>,
51
options?: InstallPackagesTaskOptions
52
): GeneratorCallback;
53
54
/**
55
* Removes dependencies from package.json
56
* @param tree - Virtual file tree
57
* @param dependencies - Array of package names to remove
58
* @param devDependencies - Array of dev dependency names to remove
59
* @returns Generator callback function
60
*/
61
function removeDependenciesFromPackageJson(
62
tree: Tree,
63
dependencies: string[],
64
devDependencies?: string[]
65
): GeneratorCallback;
66
67
interface InstallPackagesTaskOptions {
68
packageManager?: 'npm' | 'yarn' | 'pnpm';
69
cwd?: string;
70
}
71
72
/**
73
* Generator callback function type
74
*/
75
type GeneratorCallback = () => void | Promise<void>;
76
77
/**
78
* Generator function signature
79
*/
80
type Generator<T = any> = (
81
tree: Tree,
82
schema: T
83
) => void | GeneratorCallback | Promise<void | GeneratorCallback>;
84
```
85
86
### Generator Utilities
87
88
Helper functions for common generator tasks.
89
90
```typescript { .api }
91
/**
92
* Converts strings to various naming conventions
93
*/
94
interface Names {
95
/** Converts to PascalCase for class names */
96
className: (name: string) => string;
97
/** Converts to camelCase for property names */
98
propertyName: (name: string) => string;
99
/** Converts to CONSTANT_CASE */
100
constantName: (name: string) => string;
101
/** Converts to kebab-case for file names */
102
fileName: (name: string) => string;
103
}
104
105
/**
106
* Naming utility functions
107
* @param name - Input string to convert
108
* @returns Object with various naming convention functions
109
*/
110
function names(name: string): Names;
111
112
/**
113
* Strips indentation from template strings
114
* @param strings - Template string parts
115
* @param values - Template interpolation values
116
* @returns String with normalized indentation
117
*/
118
function stripIndents(strings: TemplateStringsArray, ...values: any[]): string;
119
120
/**
121
* Gets project configuration with error handling
122
* @param tree - Virtual file tree
123
* @param projectName - Name of the project
124
* @returns Project configuration
125
*/
126
function readProjectConfiguration(tree: Tree, projectName: string): ProjectConfiguration;
127
128
/**
129
* Updates project configuration
130
* @param tree - Virtual file tree
131
* @param projectName - Name of the project
132
* @param config - Updated project configuration
133
*/
134
function updateProjectConfiguration(
135
tree: Tree,
136
projectName: string,
137
config: ProjectConfiguration
138
): void;
139
```
140
141
### Built-in Generators
142
143
Nx includes built-in generators for common tasks.
144
145
```typescript { .api }
146
/**
147
* Connect workspace to Nx Cloud generator
148
* Configures workspace for remote caching and distributed execution
149
*/
150
interface ConnectToNxCloudGeneratorOptions {
151
/** Skip interactive prompts */
152
hideFormatLogs?: boolean;
153
/** Installation method */
154
installationSource?: string;
155
}
156
157
/**
158
* Generator function for connecting to Nx Cloud
159
* @param tree - Virtual file tree
160
* @param options - Generator options
161
* @returns Generator callback for package installation
162
*/
163
declare function connectToNxCloudGenerator(
164
tree: Tree,
165
options: ConnectToNxCloudGeneratorOptions
166
): GeneratorCallback;
167
```
168
169
### Executor Development APIs
170
171
Functions and types for creating custom executors.
172
173
```typescript { .api }
174
/**
175
* Executor function signature for promise-based executors
176
*/
177
type PromiseExecutor<T = any> = (
178
options: T,
179
context: ExecutorContext
180
) => Promise<{ success: boolean; [key: string]: any }>;
181
182
/**
183
* Executor function signature for async iterator executors
184
*/
185
type Executor<T = any> = (
186
options: T,
187
context: ExecutorContext
188
) => Promise<{ success: boolean; [key: string]: any }> |
189
AsyncIterableIterator<{ success: boolean; [key: string]: any }>;
190
191
/**
192
* Converts async iterator executor to promise-based executor
193
* @param executor - Async iterator executor
194
* @returns Promise-based executor
195
*/
196
function convertToPromiseExecutor<T>(
197
executor: Executor<T>
198
): PromiseExecutor<T>;
199
200
interface ExecutorContext {
201
/** Workspace root directory */
202
root: string;
203
/** Current working directory */
204
cwd: string;
205
/** Workspace configuration */
206
workspace: WorkspaceJsonConfiguration;
207
/** Whether verbose logging is enabled */
208
isVerbose: boolean;
209
/** Name of the project being executed */
210
projectName?: string;
211
/** Name of the target being executed */
212
targetName?: string;
213
/** Name of the configuration being used */
214
configurationName?: string;
215
/** Task graph for current execution */
216
taskGraph?: TaskGraph;
217
/** Hash of the current task */
218
hash?: string;
219
}
220
```
221
222
### Built-in Executors
223
224
Nx includes several built-in executors for common tasks.
225
226
```typescript { .api }
227
/**
228
* No-operation executor for testing
229
* Does nothing and always succeeds
230
*/
231
interface NoopExecutorOptions {}
232
233
/**
234
* Run arbitrary shell commands executor
235
*/
236
interface RunCommandsExecutorOptions {
237
/** Command to execute */
238
command?: string;
239
/** Multiple commands to execute */
240
commands?: Array<{
241
command: string;
242
/** Command description */
243
description?: string;
244
/** Override working directory */
245
cwd?: string;
246
/** Environment variables */
247
env?: Record<string, string>;
248
/** Prefix for command output */
249
prefix?: string;
250
/** Background execution */
251
background?: boolean;
252
}>;
253
/** Working directory for commands */
254
cwd?: string;
255
/** Environment variables */
256
env?: Record<string, string>;
257
/** Run commands in parallel */
258
parallel?: boolean;
259
/** Maximum parallel processes */
260
maxParallel?: number;
261
/** Prefix for output */
262
prefix?: string;
263
/** Color output */
264
color?: boolean;
265
/** Read commands from package.json scripts */
266
readyWhen?: string;
267
}
268
269
/**
270
* Run package.json scripts executor
271
*/
272
interface RunScriptExecutorOptions {
273
/** Script name from package.json */
274
script: string;
275
/** Additional arguments to pass to script */
276
args?: string[];
277
/** Working directory */
278
cwd?: string;
279
/** Environment variables */
280
env?: Record<string, string>;
281
}
282
```
283
284
## Usage Examples
285
286
### Creating a Custom Generator
287
288
```typescript
289
import {
290
Tree,
291
formatFiles,
292
generateFiles,
293
installPackagesTask,
294
addDependenciesToPackageJson,
295
readProjectConfiguration,
296
updateProjectConfiguration,
297
names,
298
joinPathFragments,
299
logger
300
} from "nx/src/devkit-exports";
301
302
interface ComponentGeneratorSchema {
303
name: string;
304
project: string;
305
directory?: string;
306
skipTests?: boolean;
307
export?: boolean;
308
}
309
310
export default async function componentGenerator(
311
tree: Tree,
312
options: ComponentGeneratorSchema
313
) {
314
logger.info(`Generating component: ${options.name}`);
315
316
// Read project configuration
317
const projectConfig = readProjectConfiguration(tree, options.project);
318
const projectRoot = projectConfig.root;
319
const sourceRoot = projectConfig.sourceRoot || `${projectRoot}/src`;
320
321
// Determine component directory
322
const componentDir = options.directory
323
? joinPathFragments(sourceRoot, options.directory)
324
: joinPathFragments(sourceRoot, 'components');
325
326
// Generate component files from templates
327
const substitutions = {
328
...options,
329
...names(options.name),
330
template: '', // Remove __template__ from file names
331
skipTests: options.skipTests
332
};
333
334
generateFiles(
335
tree,
336
joinPathFragments(__dirname, 'files'),
337
componentDir,
338
substitutions
339
);
340
341
// Add component to barrel export
342
if (options.export !== false) {
343
const indexPath = joinPathFragments(sourceRoot, 'index.ts');
344
const componentName = names(options.name).className;
345
const relativePath = `./${names(options.name).fileName}`;
346
347
if (tree.exists(indexPath)) {
348
const content = tree.read(indexPath, 'utf-8') || '';
349
const exportStatement = `export * from '${relativePath}';`;
350
351
if (!content.includes(exportStatement)) {
352
tree.write(indexPath, `${content}\n${exportStatement}`);
353
}
354
} else {
355
tree.write(indexPath, `export * from '${relativePath}';`);
356
}
357
}
358
359
// Add dependencies if needed
360
const installTask = addDependenciesToPackageJson(
361
tree,
362
{}, // dependencies
363
{
364
'@types/react': '^18.0.0'
365
} // devDependencies
366
);
367
368
// Format all generated files
369
await formatFiles(tree);
370
371
return installTask;
372
}
373
374
// Schema definition (schema.json)
375
export const schema = {
376
$schema: 'http://json-schema.org/schema',
377
type: 'object',
378
properties: {
379
name: {
380
type: 'string',
381
description: 'The name of the component.'
382
},
383
project: {
384
type: 'string',
385
description: 'The name of the project.',
386
$default: {
387
$source: 'projectName'
388
}
389
},
390
directory: {
391
type: 'string',
392
description: 'The directory at which to create the component file, relative to the project source root.'
393
},
394
skipTests: {
395
type: 'boolean',
396
description: 'Skip creating test files.',
397
default: false
398
},
399
export: {
400
type: 'boolean',
401
description: 'Add the component to the nearest export barrel.',
402
default: true
403
}
404
},
405
required: ['name', 'project'],
406
additionalProperties: false
407
};
408
```
409
410
### Template Files for Generator
411
412
Template directory structure:
413
```
414
files/
415
├── __name@fileName__.component.ts__template__
416
├── __name@fileName__.component.spec.ts__template__ (if !skipTests)
417
└── __name@fileName__.stories.ts__template__
418
```
419
420
Template content (`__name@fileName__.component.ts__template__`):
421
```typescript
422
import React from 'react';
423
424
export interface <%= className %>Props {
425
title?: string;
426
}
427
428
export function <%= className %>(props: <%= className %>Props) {
429
return (
430
<div>
431
<h1>{props.title || '<%= name %>'}</h1>
432
</div>
433
);
434
}
435
436
export default <%= className %>;
437
```
438
439
### Creating a Custom Executor
440
441
```typescript
442
import { ExecutorContext, logger } from "nx/src/devkit-exports";
443
import { spawn } from 'child_process';
444
import { promisify } from 'util';
445
446
interface DockerBuildExecutorOptions {
447
dockerfile?: string;
448
context?: string;
449
tag: string;
450
buildArgs?: Record<string, string>;
451
target?: string;
452
push?: boolean;
453
registry?: string;
454
}
455
456
export default async function dockerBuildExecutor(
457
options: DockerBuildExecutorOptions,
458
context: ExecutorContext
459
): Promise<{ success: boolean }> {
460
logger.info(`Building Docker image for ${context.projectName}`);
461
462
const projectRoot = context.workspace.projects[context.projectName].root;
463
const dockerfile = options.dockerfile || 'Dockerfile';
464
const buildContext = options.context || projectRoot;
465
466
// Build Docker command
467
const dockerArgs = [
468
'build',
469
'-f', dockerfile,
470
'-t', options.tag
471
];
472
473
// Add build args
474
if (options.buildArgs) {
475
for (const [key, value] of Object.entries(options.buildArgs)) {
476
dockerArgs.push('--build-arg', `${key}=${value}`);
477
}
478
}
479
480
// Add target if specified
481
if (options.target) {
482
dockerArgs.push('--target', options.target);
483
}
484
485
// Add build context
486
dockerArgs.push(buildContext);
487
488
try {
489
// Execute docker build
490
await executeCommand('docker', dockerArgs, context.root);
491
logger.info(`✅ Docker image built successfully: ${options.tag}`);
492
493
// Push if requested
494
if (options.push) {
495
const pushTag = options.registry
496
? `${options.registry}/${options.tag}`
497
: options.tag;
498
499
await executeCommand('docker', ['push', pushTag], context.root);
500
logger.info(`✅ Docker image pushed successfully: ${pushTag}`);
501
}
502
503
return { success: true };
504
505
} catch (error) {
506
logger.error(`❌ Docker build failed: ${error.message}`);
507
return { success: false };
508
}
509
}
510
511
function executeCommand(command: string, args: string[], cwd: string): Promise<void> {
512
return new Promise((resolve, reject) => {
513
const child = spawn(command, args, {
514
cwd,
515
stdio: 'inherit'
516
});
517
518
child.on('close', (code) => {
519
if (code === 0) {
520
resolve();
521
} else {
522
reject(new Error(`Command failed with exit code ${code}`));
523
}
524
});
525
526
child.on('error', reject);
527
});
528
}
529
530
// Executor schema (schema.json)
531
export const schema = {
532
$schema: 'http://json-schema.org/schema',
533
type: 'object',
534
properties: {
535
dockerfile: {
536
type: 'string',
537
description: 'Path to the Dockerfile',
538
default: 'Dockerfile'
539
},
540
context: {
541
type: 'string',
542
description: 'Build context directory'
543
},
544
tag: {
545
type: 'string',
546
description: 'Image tag'
547
},
548
buildArgs: {
549
type: 'object',
550
description: 'Build arguments',
551
additionalProperties: { type: 'string' }
552
},
553
target: {
554
type: 'string',
555
description: 'Build target'
556
},
557
push: {
558
type: 'boolean',
559
description: 'Push image after build',
560
default: false
561
},
562
registry: {
563
type: 'string',
564
description: 'Docker registry URL'
565
}
566
},
567
required: ['tag'],
568
additionalProperties: false
569
};
570
```
571
572
### Using Built-in Executors
573
574
```typescript
575
import { runExecutor, ExecutorContext } from "nx/src/devkit-exports";
576
577
// Using run-commands executor
578
async function runCustomCommands(projectName: string) {
579
const context: ExecutorContext = {
580
root: process.cwd(),
581
cwd: process.cwd(),
582
workspace: readWorkspaceConfiguration(),
583
isVerbose: false,
584
projectName
585
};
586
587
const options = {
588
commands: [
589
{
590
command: 'echo "Starting build process..."',
591
description: 'Build start message'
592
},
593
{
594
command: 'npm run build',
595
description: 'Build application',
596
cwd: 'apps/my-app'
597
},
598
{
599
command: 'npm run test',
600
description: 'Run tests',
601
background: false
602
}
603
],
604
parallel: false,
605
color: true
606
};
607
608
const results = await runExecutor(
609
{ project: projectName, target: 'run-commands' },
610
options,
611
context
612
);
613
614
for await (const result of results) {
615
if (!result.success) {
616
throw new Error('Commands execution failed');
617
}
618
}
619
}
620
```
621
622
### Workspace Generator
623
624
```typescript
625
import {
626
Tree,
627
formatFiles,
628
generateFiles,
629
readWorkspaceConfiguration,
630
updateWorkspaceConfiguration,
631
addProjectConfiguration,
632
names,
633
joinPathFragments
634
} from "nx/src/devkit-exports";
635
636
interface LibraryGeneratorSchema {
637
name: string;
638
directory?: string;
639
tags?: string;
640
buildable?: boolean;
641
publishable?: boolean;
642
}
643
644
export default async function libraryGenerator(
645
tree: Tree,
646
options: LibraryGeneratorSchema
647
) {
648
const normalizedOptions = normalizeOptions(tree, options);
649
650
// Add project configuration
651
addProjectConfiguration(tree, normalizedOptions.projectName, {
652
root: normalizedOptions.projectRoot,
653
sourceRoot: `${normalizedOptions.projectRoot}/src`,
654
projectType: 'library',
655
targets: {
656
build: {
657
executor: '@nx/js:tsc',
658
outputs: [`{options.outputPath}`],
659
options: {
660
outputPath: `dist/${normalizedOptions.projectRoot}`,
661
main: `${normalizedOptions.projectRoot}/src/index.ts`,
662
tsConfig: `${normalizedOptions.projectRoot}/tsconfig.lib.json`
663
}
664
},
665
test: {
666
executor: '@nx/jest:jest',
667
outputs: [`{workspaceRoot}/coverage/${normalizedOptions.projectRoot}`],
668
options: {
669
jestConfig: `${normalizedOptions.projectRoot}/jest.config.ts`
670
}
671
}
672
},
673
tags: normalizedOptions.parsedTags
674
});
675
676
// Generate library files
677
generateFiles(
678
tree,
679
joinPathFragments(__dirname, 'files'),
680
normalizedOptions.projectRoot,
681
{
682
...normalizedOptions,
683
template: ''
684
}
685
);
686
687
await formatFiles(tree);
688
}
689
690
function normalizeOptions(tree: Tree, options: LibraryGeneratorSchema) {
691
const name = names(options.name).fileName;
692
const projectDirectory = options.directory
693
? `${names(options.directory).fileName}/${name}`
694
: name;
695
const projectName = projectDirectory.replace(new RegExp('/', 'g'), '-');
696
const projectRoot = `libs/${projectDirectory}`;
697
698
const parsedTags = options.tags
699
? options.tags.split(',').map((s) => s.trim())
700
: [];
701
702
return {
703
...options,
704
projectName,
705
projectRoot,
706
projectDirectory,
707
parsedTags
708
};
709
}
710
```