Skip to content

Commit b85316d

Browse files
fix(nx-plugin): print process output for CLI command (#1095)
This PR includes: - use `executeProcess` in `cli` executor to log process output This changes are needed to be able to use the plugin in combination with the CI actions on all platforms --------- Co-authored-by: Matěj Chalk <34691111+matejchalk@users.noreply.github.com>
1 parent e76011b commit b85316d

File tree

6 files changed

+145
-56
lines changed

6 files changed

+145
-56
lines changed

e2e/nx-plugin-e2e/tests/executor-cli.e2e.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ describe('executor command', () => {
172172
expect(cleanStdout).toContain(
173173
'nx run my-lib:code-pushup collect --persist.filename=terminal-report',
174174
);
175+
expect(cleanStdout).toContain('Code PushUp CLI');
175176

176177
await expect(
177178
readJsonFile(path.join(cwd, '.reports', 'terminal-report.json')),
Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,29 @@
1-
import { execSync } from 'node:child_process';
21
import { afterEach, expect, vi } from 'vitest';
32
import { executorContext } from '@code-pushup/test-nx-utils';
3+
import * as executeProcessModule from '../../internal/execute-process.js';
44
import runAutorunExecutor from './executor.js';
55
import * as utils from './utils.js';
66

7-
vi.mock('node:child_process', async () => {
8-
const actual = await vi.importActual('node:child_process');
9-
return {
10-
...actual,
11-
execSync: vi.fn(),
12-
};
13-
});
14-
157
describe('runAutorunExecutor', () => {
168
const parseAutorunExecutorOptionsSpy = vi.spyOn(
179
utils,
1810
'parseAutorunExecutorOptions',
1911
);
12+
const executeProcessSpy = vi.spyOn(executeProcessModule, 'executeProcess');
13+
14+
beforeEach(() => {
15+
executeProcessSpy.mockResolvedValue({
16+
code: 0,
17+
stdout: '',
18+
stderr: '',
19+
date: new Date().toISOString(),
20+
duration: 100,
21+
});
22+
});
2023

2124
afterEach(() => {
2225
parseAutorunExecutorOptionsSpy.mockReset();
26+
executeProcessSpy.mockReset();
2327
});
2428

2529
it('should normalize context, parse CLI options and execute command', async () => {
@@ -38,11 +42,15 @@ describe('runAutorunExecutor', () => {
3842
projectConfig: expect.objectContaining({ name: 'utils' }),
3943
}),
4044
);
41-
// eslint-disable-next-line n/no-sync
42-
expect(execSync).toHaveBeenCalledTimes(1);
43-
// eslint-disable-next-line n/no-sync
44-
expect(execSync).toHaveBeenCalledWith(expect.stringContaining('utils'), {
45+
expect(executeProcessSpy).toHaveBeenCalledTimes(1);
46+
expect(executeProcessSpy).toHaveBeenCalledWith({
47+
command: 'npx',
48+
args: expect.arrayContaining(['@code-pushup/cli']),
4549
cwd: process.cwd(),
50+
observer: {
51+
onError: expect.any(Function),
52+
onStdout: expect.any(Function),
53+
},
4654
});
4755
});
4856
});
Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { type ExecutorContext, logger } from '@nx/devkit';
2-
import { execSync } from 'node:child_process';
3-
import { createCliCommand } from '../internal/cli.js';
2+
import { executeProcess } from '../../internal/execute-process.js';
3+
import {
4+
createCliCommandObject,
5+
createCliCommandString,
6+
} from '../internal/cli.js';
47
import { normalizeContext } from '../internal/context.js';
58
import type { AutorunCommandExecutorOptions } from './schema.js';
69
import { mergeExecutorOptions, parseAutorunExecutorOptions } from './utils.js';
@@ -11,7 +14,7 @@ export type ExecutorOutput = {
1114
error?: Error;
1215
};
1316

14-
export default function runAutorunExecutor(
17+
export default async function runAutorunExecutor(
1518
terminalAndExecutorOptions: AutorunCommandExecutorOptions,
1619
context: ExecutorContext,
1720
): Promise<ExecutorOutput> {
@@ -25,9 +28,10 @@ export default function runAutorunExecutor(
2528
normalizedContext,
2629
);
2730
const { dryRun, verbose, command } = mergedOptions;
28-
29-
const commandString = createCliCommand({ command, args: cliArgumentObject });
30-
const commandStringOptions = context.cwd ? { cwd: context.cwd } : {};
31+
const commandString = createCliCommandString({
32+
command,
33+
args: cliArgumentObject,
34+
});
3135
if (verbose) {
3236
logger.info(`Run CLI executor ${command ?? ''}`);
3337
logger.info(`Command: ${commandString}`);
@@ -36,21 +40,21 @@ export default function runAutorunExecutor(
3640
logger.warn(`DryRun execution of: ${commandString}`);
3741
} else {
3842
try {
39-
// @TODO use executeProcess instead of execSync -> non blocking, logs #761
40-
// eslint-disable-next-line n/no-sync
41-
execSync(commandString, commandStringOptions);
43+
await executeProcess({
44+
...createCliCommandObject({ command, args: cliArgumentObject }),
45+
...(context.cwd ? { cwd: context.cwd } : {}),
46+
});
4247
} catch (error) {
4348
logger.error(error);
44-
return Promise.resolve({
49+
return {
4550
success: false,
4651
command: commandString,
4752
error: error as Error,
48-
});
53+
};
4954
}
5055
}
51-
52-
return Promise.resolve({
56+
return {
5357
success: true,
5458
command: commandString,
55-
});
59+
};
5660
}

packages/nx-plugin/src/executors/cli/executor.unit.test.ts

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,17 @@
11
import { logger } from '@nx/devkit';
2-
import { execSync } from 'node:child_process';
32
import { afterAll, afterEach, beforeEach, expect, vi } from 'vitest';
43
import { executorContext } from '@code-pushup/test-nx-utils';
54
import { MEMFS_VOLUME } from '@code-pushup/test-utils';
5+
import * as executeProcessModule from '../../internal/execute-process.js';
66
import runAutorunExecutor from './executor.js';
77

8-
vi.mock('node:child_process', async () => {
9-
const actual = await vi.importActual('node:child_process');
10-
11-
return {
12-
...actual,
13-
execSync: vi.fn((command: string) => {
14-
if (command.includes('THROW_ERROR')) {
15-
throw new Error(command);
16-
}
17-
}),
18-
};
19-
});
20-
218
describe('runAutorunExecutor', () => {
229
const processEnvCP = Object.fromEntries(
2310
Object.entries(process.env).filter(([k]) => k.startsWith('CP_')),
2411
);
2512
const loggerInfoSpy = vi.spyOn(logger, 'info');
2613
const loggerWarnSpy = vi.spyOn(logger, 'warn');
14+
const executeProcessSpy = vi.spyOn(executeProcessModule, 'executeProcess');
2715

2816
/* eslint-disable functional/immutable-data, @typescript-eslint/no-dynamic-delete */
2917
beforeAll(() => {
@@ -34,27 +22,39 @@ describe('runAutorunExecutor', () => {
3422

3523
beforeEach(() => {
3624
vi.unstubAllEnvs();
25+
executeProcessSpy.mockResolvedValue({
26+
code: 0,
27+
stdout: '',
28+
stderr: '',
29+
date: new Date().toISOString(),
30+
duration: 100,
31+
});
3732
});
3833

3934
afterEach(() => {
4035
loggerWarnSpy.mockReset();
4136
loggerInfoSpy.mockReset();
37+
executeProcessSpy.mockReset();
4238
});
4339

4440
afterAll(() => {
4541
Object.entries(processEnvCP).forEach(([k, v]) => (process.env[k] = v));
4642
});
4743
/* eslint-enable functional/immutable-data, @typescript-eslint/no-dynamic-delete */
4844

49-
it('should call execSync with return result', async () => {
45+
it('should call executeProcess with return result', async () => {
5046
const output = await runAutorunExecutor({}, executorContext('utils'));
5147
expect(output.success).toBe(true);
5248
expect(output.command).toMatch('npx @code-pushup/cli');
53-
// eslint-disable-next-line n/no-sync
54-
expect(execSync).toHaveBeenCalledWith(
55-
expect.stringContaining('npx @code-pushup/cli'),
56-
{ cwd: MEMFS_VOLUME },
57-
);
49+
expect(executeProcessSpy).toHaveBeenCalledWith({
50+
command: 'npx',
51+
args: expect.arrayContaining(['@code-pushup/cli']),
52+
cwd: MEMFS_VOLUME,
53+
observer: {
54+
onError: expect.any(Function),
55+
onStdout: expect.any(Function),
56+
},
57+
});
5858
});
5959

6060
it('should normalize context', async () => {
@@ -67,9 +67,14 @@ describe('runAutorunExecutor', () => {
6767
);
6868
expect(output.success).toBe(true);
6969
expect(output.command).toMatch('utils');
70-
// eslint-disable-next-line n/no-sync
71-
expect(execSync).toHaveBeenCalledWith(expect.stringContaining('utils'), {
70+
expect(executeProcessSpy).toHaveBeenCalledWith({
71+
command: 'npx',
72+
args: expect.arrayContaining(['@code-pushup/cli']),
7273
cwd: 'cwd-form-context',
74+
observer: {
75+
onError: expect.any(Function),
76+
onStdout: expect.any(Function),
77+
},
7378
});
7479
});
7580

@@ -114,8 +119,7 @@ describe('runAutorunExecutor', () => {
114119
{ verbose: true },
115120
{ ...executorContext('github-action'), cwd: '<CWD>' },
116121
);
117-
// eslint-disable-next-line n/no-sync
118-
expect(execSync).toHaveBeenCalledTimes(1);
122+
expect(executeProcessSpy).toHaveBeenCalledTimes(1);
119123

120124
expect(output.command).toMatch('--verbose');
121125
expect(loggerWarnSpy).toHaveBeenCalledTimes(0);

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

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
export function createCliCommand(options?: {
1+
import { logger } from '@nx/devkit';
2+
import type { ProcessConfig } from '../../internal/execute-process.js';
3+
4+
export function createCliCommandString(options?: {
25
args?: Record<string, unknown>;
36
command?: string;
47
bin?: string;
@@ -9,6 +12,26 @@ export function createCliCommand(options?: {
912
)}`;
1013
}
1114

15+
export function createCliCommandObject(options?: {
16+
args?: Record<string, unknown>;
17+
command?: string;
18+
bin?: string;
19+
}): ProcessConfig {
20+
const { bin = '@code-pushup/cli', command, args } = options ?? {};
21+
return {
22+
command: 'npx',
23+
args: [bin, ...objectToCliArgs({ _: command ?? [], ...args })],
24+
observer: {
25+
onError: error => {
26+
logger.error(error.message);
27+
},
28+
onStdout: data => {
29+
logger.log(data);
30+
},
31+
},
32+
};
33+
}
34+
1235
type ArgumentValue = number | string | boolean | string[];
1336
export type CliArgsObject<T extends object = Record<string, ArgumentValue>> =
1437
T extends never

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

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { describe, expect, it } from 'vitest';
2-
import { createCliCommand, objectToCliArgs } from './cli.js';
2+
import {
3+
createCliCommandObject,
4+
createCliCommandString,
5+
objectToCliArgs,
6+
} from './cli.js';
37

48
describe('objectToCliArgs', () => {
59
it('should empty params', () => {
@@ -86,14 +90,59 @@ describe('objectToCliArgs', () => {
8690
});
8791
});
8892

89-
describe('createCliCommand', () => {
93+
describe('createCliCommandString', () => {
9094
it('should create command out of object for arguments', () => {
91-
const result = createCliCommand({ args: { verbose: true } });
95+
const result = createCliCommandString({ args: { verbose: true } });
9296
expect(result).toBe('npx @code-pushup/cli --verbose');
9397
});
9498

9599
it('should create command out of object for arguments with positional', () => {
96-
const result = createCliCommand({ args: { _: 'autorun', verbose: true } });
100+
const result = createCliCommandString({
101+
args: { _: 'autorun', verbose: true },
102+
});
97103
expect(result).toBe('npx @code-pushup/cli autorun --verbose');
98104
});
99105
});
106+
107+
describe('createCliCommandObject', () => {
108+
it('should create command out of object for arguments', () => {
109+
expect(createCliCommandObject({ args: { verbose: true } })).toStrictEqual({
110+
args: ['@code-pushup/cli', '--verbose'],
111+
command: 'npx',
112+
observer: {
113+
onError: expect.any(Function),
114+
onStdout: expect.any(Function),
115+
},
116+
});
117+
});
118+
119+
it('should create command out of object for arguments with positional', () => {
120+
expect(
121+
createCliCommandObject({
122+
args: { _: 'autorun', verbose: true },
123+
}),
124+
).toStrictEqual({
125+
args: ['@code-pushup/cli', 'autorun', '--verbose'],
126+
command: 'npx',
127+
observer: {
128+
onError: expect.any(Function),
129+
onStdout: expect.any(Function),
130+
},
131+
});
132+
});
133+
134+
it('should create command out of object for arguments with bin', () => {
135+
expect(
136+
createCliCommandObject({
137+
bin: 'node_modules/@code-pushup/cli/src/bin.js',
138+
}),
139+
).toStrictEqual({
140+
args: ['node_modules/@code-pushup/cli/src/bin.js'],
141+
command: 'npx',
142+
observer: {
143+
onError: expect.any(Function),
144+
onStdout: expect.any(Function),
145+
},
146+
});
147+
});
148+
});

0 commit comments

Comments
 (0)