Complete Nx plugin development toolkit: create custom generators, executors, and extend Nx workspaces with reusable automation
93
94%
Does it follow best practices?
Impact
92%
1.00xAverage score across 5 eval scenarios
Passed
No known issues
Basic generator test:
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Tree, readProjectConfiguration } from '@nx/devkit';
import { libraryGenerator } from './generator';
describe('library generator', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace();
});
it('should create library', async () => {
await libraryGenerator(tree, { name: 'test' });
const config = readProjectConfiguration(tree, 'test');
expect(config.root).toBe('libs/test');
expect(tree.exists('libs/test/src/index.ts')).toBeTruthy();
});
it('should handle directory option', async () => {
await libraryGenerator(tree, {
name: 'test',
directory: 'shared'
});
const config = readProjectConfiguration(tree, 'shared-test');
expect(config.root).toBe('libs/shared/test');
});
});Test before applying:
npx nx g @my-org/my-plugin:library test --dry-runValidate output structure:
npx nx g @my-org/my-plugin:library test --dry-run | grep -E "CREATE|UPDATE"Basic executor test:
import { ExecutorContext } from '@nx/devkit';
import executor from './executor';
import { ExecutorSchema } from './schema';
const mockContext: ExecutorContext = {
root: '/workspace',
cwd: '/workspace',
projectName: 'test-project',
targetName: 'build',
workspace: { version: 2, projects: {} },
projectsConfigurations: {
version: 2,
projects: {
'test-project': {
root: 'apps/test-project'
}
}
},
isVerbose: false
};
describe('Build Executor', () => {
it('should execute successfully', async () => {
const options: ExecutorSchema = {
outputPath: 'dist/test'
};
const result = await executor(options, mockContext);
expect(result.success).toBe(true);
});
it('should fail with invalid options', async () => {
const options: ExecutorSchema = {
outputPath: ''
};
const result = await executor(options, mockContext);
expect(result.success).toBe(false);
});
});Test executor in workspace:
npx nx run my-project:my-targetSkip cache for testing:
npx nx run my-project:my-target --skip-nx-cacheVerify outputs:
npx nx run my-project:build
ls -la dist/apps/my-projectError:
NX Cannot find executor '@my-org/tools:build'
Unable to resolve @my-org/tools:buildCauses:
Solution:
package.json:{
"workspaces": [
"packages/*",
"tools/*"
]
}tsconfig.base.json:{
"compilerOptions": {
"paths": {
"@my-org/tools": ["tools/src/index.ts"]
}
}
}tools/src/index.ts:export * from './executors/build/executor';tools/package.json:{
"main": "./src/index.ts"
}npx nx reset
npx nx run my-project:buildError:
Cannot find generator '@my-org/my-plugin:library'Solution:
generators.json registration:{
"generators": {
"library": {
"factory": "./src/generators/library/generator",
"schema": "./src/generators/library/schema.json"
}
}
}package.json points to registry:{
"generators": "./generators.json"
}export default async function libraryGenerator(tree: Tree, options: Schema) {
// ...
}Error:
Required property 'name' is missingSolution:
{
"required": ["name"]
}{
"properties": {
"name": {
"type": "string",
"$default": {
"$source": "argv",
"index": 0
}
}
}
}{
"properties": {
"name": {
"type": "string",
"x-prompt": "What name would you like to use?"
}
}
}Symptoms:
Solution:
{
"targets": {
"build": {
"outputs": ["{options.outputPath}"]
}
}
}{ success: true }Error:
Cannot write file outside of workspaceSolution:
tree.write() not fs.writeFileSync()npx nx run my-project:build --verbosenpx nx resetnpx nx show project my-projectnpx nx workspace-lintnpx nx graphrg -n "generateFiles|tree.write|ExecutorContext" tools pluginsCreate test workspace:
import { execSync } from 'child_process';
import { rmSync } from 'fs';
describe('plugin e2e', () => {
const testWorkspace = 'test-workspace';
beforeAll(() => {
execSync(`npx create-nx-workspace ${testWorkspace} --preset=empty --no-interactive`);
execSync(`npm install @my-org/my-plugin`, { cwd: testWorkspace });
});
afterAll(() => {
rmSync(testWorkspace, { recursive: true, force: true });
});
it('should generate library', () => {
execSync(`npx nx g @my-org/my-plugin:library test`, {
cwd: testWorkspace,
stdio: 'inherit'
});
// Verify generated files
});
});Measure generator performance:
time npx nx g @my-org/my-plugin:library test --dry-runMeasure executor performance:
time npx nx run my-project:build --skip-nx-cache