Skip to content

Commit 9478989

Browse files
committed
feat(nx-plugin): add createNodes generator
1 parent f303577 commit 9478989

File tree

17 files changed

+880
-0
lines changed

17 files changed

+880
-0
lines changed

docs/generated/manifests/menus.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6171,6 +6171,14 @@
61716171
"isExternal": false,
61726172
"disableCollapsible": false
61736173
},
6174+
{
6175+
"id": "create-nodes",
6176+
"path": "/reference/core-api/plugin/generators/create-nodes",
6177+
"name": "create-nodes",
6178+
"children": [],
6179+
"isExternal": false,
6180+
"disableCollapsible": false
6181+
},
61746182
{
61756183
"id": "plugin-lint-checks",
61766184
"path": "/reference/core-api/plugin/generators/plugin-lint-checks",

docs/generated/manifests/new-nx-api.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3837,6 +3837,15 @@
38373837
"path": "/reference/core-api/plugin/generators/executor",
38383838
"type": "generator"
38393839
},
3840+
"/reference/core-api/plugin/generators/create-nodes": {
3841+
"description": "Create an Inference Plugin (createNodes) for an Nx Plugin.",
3842+
"file": "generated/packages/plugin/generators/create-nodes.json",
3843+
"hidden": false,
3844+
"name": "create-nodes",
3845+
"originalFilePath": "/packages/plugin/src/generators/create-nodes/schema.json",
3846+
"path": "/reference/core-api/plugin/generators/create-nodes",
3847+
"type": "generator"
3848+
},
38403849
"/reference/core-api/plugin/generators/plugin-lint-checks": {
38413850
"description": "Adds linting configuration to validate common json files for nx plugins.",
38423851
"file": "generated/packages/plugin/generators/plugin-lint-checks.json",

docs/generated/packages-metadata.json

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
{
2+
"name": "create-nodes",
3+
"factory": "./src/generators/create-nodes/create-nodes",
4+
"schema": {
5+
"$schema": "https://json-schema.org/schema",
6+
"cli": "nx",
7+
"$id": "NxPluginCreateNodes",
8+
"title": "Create an Inference Plugin (createNodes) for an Nx Plugin",
9+
"description": "Create an Inference Plugin (createNodes) for an Nx Plugin.",
10+
"type": "object",
11+
"properties": {
12+
"path": {
13+
"type": "string",
14+
"description": "The file path to the plugin. Relative to the current working directory.",
15+
"x-prompt": "What is the plugin file path?",
16+
"$default": { "$source": "argv", "index": 0 },
17+
"x-priority": "important"
18+
},
19+
"name": { "type": "string", "description": "The plugin name." },
20+
"targetName": {
21+
"type": "string",
22+
"description": "The default name for the target created by this plugin.",
23+
"default": "echo",
24+
"x-priority": "important"
25+
},
26+
"configFile": {
27+
"type": "string",
28+
"description": "The configuration file glob pattern to match (e.g., '**/my-config.json').",
29+
"default": "**/.my-plugin-config.json",
30+
"x-priority": "important"
31+
},
32+
"unitTestRunner": {
33+
"type": "string",
34+
"enum": ["jest", "vitest", "none"],
35+
"description": "Test runner to use for unit tests.",
36+
"default": "jest"
37+
},
38+
"skipReadme": {
39+
"type": "boolean",
40+
"default": false,
41+
"description": "Do not create or update README.md with plugin documentation."
42+
},
43+
"skipFormat": {
44+
"type": "boolean",
45+
"description": "Skip formatting files.",
46+
"default": false,
47+
"x-priority": "internal"
48+
}
49+
},
50+
"required": ["path"],
51+
"additionalProperties": false,
52+
"presets": []
53+
},
54+
"description": "Create an Inference Plugin (createNodes) for an Nx Plugin.",
55+
"implementation": "/packages/plugin/src/generators/create-nodes/create-nodes.ts",
56+
"aliases": [],
57+
"hidden": false,
58+
"path": "/packages/plugin/src/generators/create-nodes/schema.json",
59+
"type": "generator"
60+
}

docs/generated/packages/plugin/generators/plugin.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@
9696
"useProjectJson": {
9797
"type": "boolean",
9898
"description": "Use a `project.json` configuration file instead of inlining the Nx configuration in the `package.json` file."
99+
},
100+
"includeCreateNodes": {
101+
"type": "boolean",
102+
"description": "Generate an Inference Plugin (createNodes) example with the plugin.",
103+
"default": true,
104+
"x-priority": "important"
99105
}
100106
},
101107
"required": ["directory"],

docs/shared/reference/sitemap.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,7 @@
745745
- [migration](/reference/core-api/plugin/generators/migration)
746746
- [generator](/reference/core-api/plugin/generators/generator)
747747
- [executor](/reference/core-api/plugin/generators/executor)
748+
- [create-nodes](/reference/core-api/plugin/generators/create-nodes)
748749
- [plugin-lint-checks](/reference/core-api/plugin/generators/plugin-lint-checks)
749750
- [preset](/reference/core-api/plugin/generators/preset)
750751
- [web](/reference/core-api/web)

packages/plugin/generators.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@
3333
"schema": "./src/generators/executor/schema.json",
3434
"description": "Create an executor for an Nx Plugin."
3535
},
36+
"create-nodes": {
37+
"factory": "./src/generators/create-nodes/create-nodes",
38+
"schema": "./src/generators/create-nodes/schema.json",
39+
"description": "Create an Inference Plugin (createNodes) for an Nx Plugin."
40+
},
3641
"plugin-lint-checks": {
3742
"factory": "./src/generators/lint-checks/generator",
3843
"schema": "./src/generators/lint-checks/schema.json",
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import 'nx/src/internal-testing-utils/mock-project-graph';
2+
3+
import { Tree } from '@nx/devkit';
4+
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
5+
import { pluginGenerator } from '../plugin/plugin';
6+
import { createNodesGenerator } from './create-nodes';
7+
import { setCwd } from '@nx/devkit/internal-testing-utils';
8+
9+
describe('create-nodes generator', () => {
10+
let tree: Tree;
11+
let projectName: string;
12+
13+
beforeEach(async () => {
14+
projectName = 'my-plugin';
15+
tree = createTreeWithEmptyWorkspace();
16+
setCwd('');
17+
await pluginGenerator(tree, {
18+
directory: projectName,
19+
unitTestRunner: 'jest',
20+
linter: 'eslint',
21+
compiler: 'tsc',
22+
includeCreateNodes: false,
23+
});
24+
});
25+
26+
it('should generate files', async () => {
27+
await createNodesGenerator(tree, {
28+
name: 'my-plugin',
29+
path: 'my-plugin/src/plugins/my-plugin/plugin',
30+
unitTestRunner: 'jest',
31+
});
32+
33+
expect(
34+
tree.exists('my-plugin/src/plugins/my-plugin/plugin.ts')
35+
).toBeTruthy();
36+
expect(
37+
tree.exists('my-plugin/src/plugins/my-plugin/plugin.spec.ts')
38+
).toBeTruthy();
39+
});
40+
41+
it('should create README when it does not exist', async () => {
42+
await createNodesGenerator(tree, {
43+
name: 'my-plugin',
44+
path: 'my-plugin/src/plugins/my-plugin/plugin',
45+
unitTestRunner: 'jest',
46+
});
47+
48+
const readmePath = 'my-plugin/README.md';
49+
expect(tree.exists(readmePath)).toBeTruthy();
50+
51+
const readmeContent = tree.read(readmePath, 'utf-8');
52+
expect(readmeContent).toContain('## What is an Inference Plugin?');
53+
expect(readmeContent).toContain('## How This Plugin Works');
54+
});
55+
56+
it('should append to existing README without duplicating content', async () => {
57+
const readmePath = 'my-plugin/README.md';
58+
tree.write(readmePath, '# My Plugin\n\nExisting content');
59+
60+
await createNodesGenerator(tree, {
61+
name: 'my-plugin',
62+
path: 'my-plugin/src/plugins/my-plugin/plugin',
63+
unitTestRunner: 'jest',
64+
});
65+
66+
const readmeContent = tree.read(readmePath, 'utf-8');
67+
expect(readmeContent).toContain('# My Plugin');
68+
expect(readmeContent).toContain('Existing content');
69+
expect(readmeContent).toContain('## What is an Inference Plugin?');
70+
71+
// Run generator again to ensure no duplication
72+
await createNodesGenerator(tree, {
73+
name: 'another-plugin',
74+
path: 'my-plugin/src/plugins/another-plugin/plugin',
75+
unitTestRunner: 'jest',
76+
});
77+
78+
const updatedContent = tree.read(readmePath, 'utf-8');
79+
const matches = updatedContent.match(/## What is an Inference Plugin\?/g);
80+
expect(matches?.length).toBe(1);
81+
});
82+
83+
it('should skip README when skipReadme is true', async () => {
84+
await createNodesGenerator(tree, {
85+
name: 'my-plugin',
86+
path: 'my-plugin/src/plugins/my-plugin/plugin',
87+
unitTestRunner: 'jest',
88+
skipReadme: true,
89+
});
90+
91+
// README exists from plugin generator, but should not contain our inference plugin content
92+
const readmePath = 'my-plugin/README.md';
93+
expect(tree.exists(readmePath)).toBeTruthy();
94+
const readmeContent = tree.read(readmePath, 'utf-8');
95+
expect(readmeContent).not.toContain('## What is an Inference Plugin?');
96+
});
97+
98+
it('should skip test file when unitTestRunner is none', async () => {
99+
await createNodesGenerator(tree, {
100+
name: 'my-plugin',
101+
path: 'my-plugin/src/plugins/my-plugin/plugin',
102+
unitTestRunner: 'none',
103+
});
104+
105+
expect(
106+
tree.exists('my-plugin/src/plugins/my-plugin/plugin.ts')
107+
).toBeTruthy();
108+
expect(
109+
tree.exists('my-plugin/src/plugins/my-plugin/plugin.spec.ts')
110+
).toBeFalsy();
111+
});
112+
113+
it('should use custom targetName', async () => {
114+
await createNodesGenerator(tree, {
115+
name: 'my-plugin',
116+
path: 'my-plugin/src/plugins/my-plugin/plugin',
117+
unitTestRunner: 'jest',
118+
targetName: 'custom-target',
119+
});
120+
121+
const pluginContent = tree.read(
122+
'my-plugin/src/plugins/my-plugin/plugin.ts',
123+
'utf-8'
124+
);
125+
expect(pluginContent).toContain(
126+
"targetName: options.targetName ?? 'custom-target'"
127+
);
128+
expect(pluginContent).toContain("@default 'custom-target'");
129+
});
130+
131+
it('should use custom configFile pattern', async () => {
132+
await createNodesGenerator(tree, {
133+
name: 'my-plugin',
134+
path: 'my-plugin/src/plugins/my-plugin/plugin',
135+
unitTestRunner: 'jest',
136+
configFile: '**/custom.config.json',
137+
});
138+
139+
const pluginContent = tree.read(
140+
'my-plugin/src/plugins/my-plugin/plugin.ts',
141+
'utf-8'
142+
);
143+
expect(pluginContent).toContain("'**/custom.config.json'");
144+
});
145+
146+
it('should handle path with file extension', async () => {
147+
await createNodesGenerator(tree, {
148+
name: 'my-plugin',
149+
path: 'my-plugin/src/plugins/my-plugin/plugin.ts',
150+
unitTestRunner: 'jest',
151+
});
152+
153+
expect(
154+
tree.exists('my-plugin/src/plugins/my-plugin/plugin.ts')
155+
).toBeTruthy();
156+
expect(
157+
tree.exists('my-plugin/src/plugins/my-plugin/plugin.spec.ts')
158+
).toBeTruthy();
159+
});
160+
});

0 commit comments

Comments
 (0)