Skip to content

Commit 06b177f

Browse files
authored
feat(nx-plugin): implement bin property in options (#773)
1 parent 3a64434 commit 06b177f

29 files changed

+582
-41
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2+
3+
exports[`nx-plugin > should NOT add config targets dynamically if the project is configured 1`] = `
4+
{
5+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
6+
"implicitDependencies": [],
7+
"name": "my-lib",
8+
"projectType": "library",
9+
"root": "libs/my-lib",
10+
"sourceRoot": "libs/my-lib/src",
11+
"tags": [
12+
"scope:plugin",
13+
],
14+
"targets": {
15+
"code-pushup": {
16+
"configurations": {},
17+
"executor": "@code-pushup/nx-plugin:autorun",
18+
"options": {},
19+
},
20+
},
21+
}
22+
`;
23+
24+
exports[`nx-plugin > should NOT add targets dynamically if plugin is not registered 1`] = `
25+
{
26+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
27+
"implicitDependencies": [],
28+
"name": "my-lib",
29+
"projectType": "library",
30+
"root": "libs/my-lib",
31+
"sourceRoot": "libs/my-lib/src",
32+
"tags": [
33+
"scope:plugin",
34+
],
35+
"targets": {},
36+
}
37+
`;
38+
39+
exports[`nx-plugin > should add configuration target dynamically 1`] = `
40+
{
41+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
42+
"implicitDependencies": [],
43+
"name": "my-lib",
44+
"projectType": "library",
45+
"root": "libs/my-lib",
46+
"sourceRoot": "libs/my-lib/src",
47+
"tags": [
48+
"scope:plugin",
49+
],
50+
"targets": {
51+
"code-pushup--configuration": {
52+
"configurations": {},
53+
"executor": "nx:run-commands",
54+
"options": {
55+
"command": "nx g @code-pushup/nx-plugin:configuration --skipTarget --targetName="code-pushup" --project="my-lib"",
56+
},
57+
},
58+
},
59+
}
60+
`;
61+
62+
exports[`nx-plugin > should add executor target dynamically if the project is configured 1`] = `
63+
{
64+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
65+
"implicitDependencies": [],
66+
"name": "my-lib",
67+
"projectType": "library",
68+
"root": "libs/my-lib",
69+
"sourceRoot": "libs/my-lib/src",
70+
"tags": [
71+
"scope:plugin",
72+
],
73+
"targets": {
74+
"code-pushup": {
75+
"configurations": {},
76+
"executor": "@code-pushup/nx-plugin:autorun",
77+
"options": {},
78+
},
79+
},
80+
}
81+
`;
82+
83+
exports[`nx-plugin > should execute dynamic configuration target 1`] = `
84+
"import type { CoreConfig } from '@code-pushup/models';
85+
86+
// see: https://github.com/code-pushup/cli/blob/main/packages/models/docs/models-reference.md#coreconfig
87+
export default {
88+
plugins: [],
89+
} satisfies CoreConfig;
90+
"
91+
`;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,256 @@
1+
import { Tree } from '@nx/devkit';
2+
import { rm } from 'node:fs/promises';
3+
import { join, relative } from 'node:path';
4+
import { readProjectConfiguration } from 'nx/src/generators/utils/project-configuration';
5+
import { afterEach, expect } from 'vitest';
6+
import { generateCodePushupConfig } from '@code-pushup/nx-plugin';
7+
import {
8+
generateWorkspaceAndProject,
9+
materializeTree,
10+
nxShowProjectJson,
11+
registerPluginInWorkspace,
12+
} from '@code-pushup/test-nx-utils';
13+
import { removeColorCodes } from '@code-pushup/test-utils';
14+
import { executeProcess, readTextFile } from '@code-pushup/utils';
15+
16+
// @TODO replace with default bin after https://github.com/code-pushup/cli/issues/643
17+
export function relativePathToCwd(testDir: string): string {
18+
return relative(join(process.cwd(), testDir), process.cwd());
19+
}
20+
21+
describe('nx-plugin', () => {
22+
let tree: Tree;
23+
const project = 'my-lib';
24+
const projectRoot = join('libs', project);
25+
const baseDir = 'tmp/nx-plugin-e2e/plugin';
26+
27+
beforeEach(async () => {
28+
tree = await generateWorkspaceAndProject(project);
29+
});
30+
31+
afterEach(async () => {
32+
await rm(baseDir, { recursive: true, force: true });
33+
});
34+
35+
it('should add configuration target dynamically', async () => {
36+
const cwd = join(baseDir, 'add-configuration-dynamically');
37+
registerPluginInWorkspace(
38+
tree,
39+
join(relativePathToCwd(cwd), 'dist/packages/nx-plugin'),
40+
);
41+
await materializeTree(tree, cwd);
42+
43+
const { code, projectJson } = await nxShowProjectJson(cwd, project);
44+
expect(code).toBe(0);
45+
46+
expect(projectJson.targets).toStrictEqual({
47+
['code-pushup--configuration']: {
48+
configurations: {}, // @TODO understand why this appears. should not be here
49+
executor: 'nx:run-commands',
50+
options: {
51+
command: `nx g @code-pushup/nx-plugin:configuration --skipTarget --targetName="code-pushup" --project="${project}"`,
52+
},
53+
},
54+
});
55+
56+
expect(projectJson).toMatchSnapshot();
57+
});
58+
59+
it('should execute dynamic configuration target', async () => {
60+
const cwd = join(baseDir, 'execute-dynamic-configuration');
61+
registerPluginInWorkspace(tree, {
62+
plugin: join(relativePathToCwd(cwd), 'dist/packages/nx-plugin'),
63+
options: {
64+
bin: join(relativePathToCwd(cwd), 'dist/packages/nx-plugin'),
65+
},
66+
});
67+
await materializeTree(tree, cwd);
68+
69+
const { code, stdout } = await executeProcess({
70+
command: 'npx',
71+
args: ['nx', 'run', `${project}:code-pushup--configuration`],
72+
cwd,
73+
});
74+
75+
expect(code).toBe(0);
76+
77+
const cleanStdout = removeColorCodes(stdout);
78+
expect(cleanStdout).toContain(
79+
`> NX Successfully ran target code-pushup--configuration for project ${project}`,
80+
);
81+
await expect(
82+
readTextFile(join(cwd, projectRoot, 'code-pushup.config.ts')),
83+
).resolves.toMatchSnapshot();
84+
});
85+
86+
it('should consider plugin option targetName in configuration target', async () => {
87+
const cwd = join(baseDir, 'configuration-option-target-name');
88+
registerPluginInWorkspace(tree, {
89+
plugin: join(relativePathToCwd(cwd), 'dist/packages/nx-plugin'),
90+
options: {
91+
targetName: 'cp',
92+
},
93+
});
94+
await materializeTree(tree, cwd);
95+
96+
const { code, projectJson } = await nxShowProjectJson(cwd, project);
97+
98+
expect(code).toBe(0);
99+
100+
expect(projectJson.targets).toStrictEqual({
101+
['cp--configuration']: expect.any(Object),
102+
});
103+
});
104+
105+
it('should consider plugin option bin in configuration target', async () => {
106+
const cwd = join(baseDir, 'configuration-option-bin');
107+
registerPluginInWorkspace(tree, {
108+
plugin: join(relativePathToCwd(cwd), 'dist/packages/nx-plugin'),
109+
options: {
110+
bin: 'XYZ',
111+
},
112+
});
113+
await materializeTree(tree, cwd);
114+
115+
const { code, projectJson } = await nxShowProjectJson(cwd, project);
116+
117+
expect(code).toBe(0);
118+
119+
expect(projectJson.targets).toStrictEqual({
120+
['code-pushup--configuration']: expect.objectContaining({
121+
options: {
122+
command: `nx g XYZ:configuration --skipTarget --targetName="code-pushup" --project="${project}"`,
123+
},
124+
}),
125+
});
126+
});
127+
128+
it('should NOT add config targets dynamically if the project is configured', async () => {
129+
const cwd = join(baseDir, 'configuration-already-configured');
130+
registerPluginInWorkspace(
131+
tree,
132+
join(relativePathToCwd(cwd), 'dist/packages/nx-plugin'),
133+
);
134+
const { root } = readProjectConfiguration(tree, project);
135+
generateCodePushupConfig(tree, root);
136+
await materializeTree(tree, cwd);
137+
138+
const { code, projectJson } = await nxShowProjectJson(cwd, project);
139+
140+
expect(code).toBe(0);
141+
142+
expect(projectJson.targets).toStrictEqual(
143+
expect.not.objectContaining({
144+
['code-pushup--configuration']: expect.any(Object),
145+
}),
146+
);
147+
expect(projectJson).toMatchSnapshot();
148+
});
149+
150+
it('should add executor target dynamically if the project is configured', async () => {
151+
const cwd = join(baseDir, 'add-executor-dynamically');
152+
registerPluginInWorkspace(
153+
tree,
154+
join(relativePathToCwd(cwd), 'dist/packages/nx-plugin'),
155+
);
156+
const { root } = readProjectConfiguration(tree, project);
157+
generateCodePushupConfig(tree, root);
158+
await materializeTree(tree, cwd);
159+
160+
const { code, projectJson } = await nxShowProjectJson(cwd, project);
161+
expect(code).toBe(0);
162+
163+
expect(projectJson.targets).toStrictEqual({
164+
['code-pushup']: {
165+
configurations: {},
166+
executor: `@code-pushup/nx-plugin:autorun`,
167+
options: {},
168+
},
169+
});
170+
171+
expect(projectJson).toMatchSnapshot();
172+
});
173+
174+
it('should execute dynamic executor target', async () => {
175+
const cwd = join(baseDir, 'execute-dynamic-executor');
176+
const pathRelativeToPackage = relative(join(cwd, 'libs', project), cwd);
177+
registerPluginInWorkspace(tree, {
178+
plugin: join(relativePathToCwd(cwd), 'dist/packages/nx-plugin'),
179+
options: {
180+
bin: join(relativePathToCwd(cwd), 'dist/packages/nx-plugin'),
181+
},
182+
});
183+
const { root } = readProjectConfiguration(tree, project);
184+
generateCodePushupConfig(tree, root, {
185+
fileImports: `import type {CoreConfig} from "${join(
186+
relativePathToCwd(cwd),
187+
pathRelativeToPackage,
188+
'dist/packages/models',
189+
)}";`,
190+
plugins: [
191+
{
192+
fileImports: `import jsPackagesPlugin from "${join(
193+
relativePathToCwd(cwd),
194+
pathRelativeToPackage,
195+
'dist/packages/plugin-js-packages',
196+
)}";`,
197+
// @TODO improve formatObjectToJsString to get rid of the "`" hack
198+
codeStrings: 'await jsPackagesPlugin({packageManager: `npm`})',
199+
},
200+
],
201+
});
202+
await materializeTree(tree, cwd);
203+
204+
const { stdout, stderr } = await executeProcess({
205+
command: 'npx',
206+
args: ['nx', 'run', `${project}:code-pushup -- --dryRun`],
207+
cwd,
208+
});
209+
210+
const cleanStderr = removeColorCodes(stderr);
211+
// @TODO create test environment for working plugin. This here misses package-lock.json to execute correctly
212+
expect(cleanStderr).toContain(
213+
'DryRun execution of: npx @code-pushup/cli autorun',
214+
);
215+
216+
const cleanStdout = removeColorCodes(stdout);
217+
expect(cleanStdout).toContain(
218+
'NX Successfully ran target code-pushup for project my-lib',
219+
);
220+
});
221+
222+
it('should consider plugin option bin in executor target', async () => {
223+
const cwd = join(baseDir, 'configuration-option-bin');
224+
registerPluginInWorkspace(tree, {
225+
plugin: join(relativePathToCwd(cwd), 'dist/packages/nx-plugin'),
226+
options: {
227+
bin: 'XYZ',
228+
},
229+
});
230+
const { root } = readProjectConfiguration(tree, project);
231+
generateCodePushupConfig(tree, root);
232+
await materializeTree(tree, cwd);
233+
234+
const { code, projectJson } = await nxShowProjectJson(cwd, project);
235+
236+
expect(code).toBe(0);
237+
238+
expect(projectJson.targets).toStrictEqual({
239+
['code-pushup']: expect.objectContaining({
240+
executor: 'XYZ:autorun',
241+
}),
242+
});
243+
});
244+
245+
it('should NOT add targets dynamically if plugin is not registered', async () => {
246+
const cwd = join(baseDir, 'plugin-not-registered');
247+
await materializeTree(tree, cwd);
248+
249+
const { code, projectJson } = await nxShowProjectJson(cwd, project);
250+
251+
expect(code).toBe(0);
252+
253+
expect(projectJson.targets).toStrictEqual({});
254+
expect(projectJson).toMatchSnapshot();
255+
});
256+
});

packages/nx-plugin/src/executors/autorun/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@ Show what will be executed without actually executing it:
5656
| ----------------- | --------- | ------------------------------------------------------------------ |
5757
| **projectPrefix** | `string` | prefix for upload.project on non root projects |
5858
| **dryRun** | `boolean` | To debug the executor, dry run the command without real execution. |
59+
| **bin** | `string` | Path to Code PushUp CLI |
5960

6061
For all other options see the [CLI autorun documentation](../../cli/packages/cli/README.md#autorun-command).

packages/nx-plugin/src/executors/autorun/schema.json

+4
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
"type": "boolean",
2020
"description": "Print the commands that would be run, but don't actually run them"
2121
},
22+
"bin": {
23+
"type": "string",
24+
"description": "Path to Code PushUp CLI"
25+
},
2226
"verbose": {
2327
"type": "boolean",
2428
"description": "Print additional logs"

packages/nx-plugin/src/executors/internal/cli.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
export function createCliCommand(
22
command: string,
33
args: Record<string, unknown>,
4+
options?: {
5+
bin: string;
6+
},
47
): string {
5-
return `npx @code-pushup/cli ${command} ${objectToCliArgs(args).join(' ')}`;
8+
const { bin = '@code-pushup/cli' } = options ?? {};
9+
return `npx ${bin} ${command} ${objectToCliArgs(args).join(' ')}`;
610
}
711

812
type ArgumentValue = number | string | boolean | string[];

packages/nx-plugin/src/executors/internal/config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
export function globalConfig(
1111
options: Partial<GlobalExecutorOptions & Record<string, unknown>>,
1212
context: BaseNormalizedExecutorContext,
13-
): Required<GlobalExecutorOptions> {
13+
): GlobalExecutorOptions {
1414
const { projectConfig } = context;
1515
const { root: projectRoot = '' } = projectConfig ?? {};
1616
// For better debugging use `--verbose --no-progress` as default

packages/nx-plugin/src/executors/internal/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export type ProjectExecutorOnlyOptions = {
2020
* CLI types that apply globally for all commands.
2121
*/
2222
export type GlobalExecutorOptions = {
23+
bin?: string;
2324
verbose?: boolean;
2425
progress?: boolean;
2526
config?: string;

0 commit comments

Comments
 (0)