or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cli.mddevkit-core.mddevkit-files.mddevkit-tasks.mdgenerators-executors.mdindex.mdplugins.md

generators-executors.mddocs/

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

```