Skip to content

Commit 104a059

Browse files
committed
fix(nx-plugin): print stdout for CLI command
1 parent 2626edf commit 104a059

File tree

5 files changed

+101
-65
lines changed

5 files changed

+101
-65
lines changed
Lines changed: 25 additions & 15 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,17 @@ 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-
cwd: process.cwd(),
46-
});
45+
expect(executeProcessSpy).toHaveBeenCalledTimes(1);
46+
expect(executeProcessSpy).toHaveBeenCalledWith(
47+
expect.objectContaining({
48+
command: 'npx',
49+
args: expect.arrayContaining(['@code-pushup/cli']),
50+
cwd: process.cwd(),
51+
observer: expect.objectContaining({
52+
onError: expect.any(Function),
53+
onStdout: expect.any(Function),
54+
}),
55+
}),
56+
);
4757
});
4858
});
Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
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';
6-
import {
7-
mergeExecutorOptions,
8-
objectToCliArgs,
9-
parseAutorunExecutorOptions,
10-
} from './utils.js';
9+
import { mergeExecutorOptions, parseAutorunExecutorOptions } from './utils.js';
1110

1211
export type ExecutorOutput = {
1312
success: boolean;
1413
command?: string;
1514
error?: Error;
1615
};
1716

17+
// eslint-disable-next-line max-lines-per-function
1818
export default async function runAutorunExecutor(
1919
terminalAndExecutorOptions: AutorunCommandExecutorOptions,
2020
context: ExecutorContext,
@@ -30,8 +30,10 @@ export default async function runAutorunExecutor(
3030
);
3131
const { dryRun, verbose, command } = mergedOptions;
3232

33-
const commandString = createCliCommand({ command, args: cliArgumentObject });
34-
const commandStringOptions = context.cwd ? { cwd: context.cwd } : {};
33+
const commandString = createCliCommandString({
34+
command,
35+
args: cliArgumentObject,
36+
});
3537
if (verbose) {
3638
logger.info(`Run CLI executor ${command ?? ''}`);
3739
logger.info(`Command: ${commandString}`);
@@ -40,32 +42,30 @@ export default async function runAutorunExecutor(
4042
logger.warn(`DryRun execution of: ${commandString}`);
4143
} else {
4244
try {
43-
const { executeProcess }: typeof import('@code-pushup/utils') =
44-
await import('@code-pushup/utils');
4545
await executeProcess({
46-
command: command,
47-
args: objectToCliArgs(cliArgumentObject),
46+
...createCliCommandObject({ command, args: cliArgumentObject }),
4847
observer: {
49-
error: data => {
50-
process.stderr.write(data);
48+
onError: error => {
49+
logger.error(error.message);
5150
},
52-
next: data => {
51+
onStdout: data => {
5352
process.stdout.write(data);
5453
},
5554
},
55+
...(context.cwd ? { cwd: context.cwd } : {}),
5656
});
5757
} catch (error) {
5858
logger.error(error);
59-
return Promise.resolve({
59+
return {
6060
success: false,
6161
command: commandString,
6262
error: error as Error,
63-
});
63+
};
6464
}
6565
}
6666

67-
return Promise.resolve({
67+
return {
6868
success: true,
6969
command: commandString,
70-
});
70+
};
7171
}

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

Lines changed: 33 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,30 @@
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(() => {
3018
Object.entries(process.env)
3119
.filter(([k]) => k.startsWith('CP_'))
3220
.forEach(([k]) => delete process.env[k]);
21+
executeProcessSpy.mockResolvedValue({
22+
code: 0,
23+
stdout: '',
24+
stderr: '',
25+
date: new Date().toISOString(),
26+
duration: 100,
27+
});
3328
});
3429

3530
beforeEach(() => {
@@ -39,21 +34,28 @@ describe('runAutorunExecutor', () => {
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 },
49+
expect(executeProcessSpy).toHaveBeenCalledWith(
50+
expect.objectContaining({
51+
command: 'npx',
52+
args: expect.arrayContaining(['@code-pushup/cli']),
53+
cwd: MEMFS_VOLUME,
54+
observer: expect.objectContaining({
55+
onError: expect.any(Function),
56+
onStdout: expect.any(Function),
57+
}),
58+
}),
5759
);
5860
});
5961

@@ -67,10 +69,17 @@ describe('runAutorunExecutor', () => {
6769
);
6870
expect(output.success).toBe(true);
6971
expect(output.command).toMatch('utils');
70-
// eslint-disable-next-line n/no-sync
71-
expect(execSync).toHaveBeenCalledWith(expect.stringContaining('utils'), {
72-
cwd: 'cwd-form-context',
73-
});
72+
expect(executeProcessSpy).toHaveBeenCalledWith(
73+
expect.objectContaining({
74+
command: 'npx',
75+
args: expect.arrayContaining(['@code-pushup/cli']),
76+
cwd: 'cwd-form-context',
77+
observer: expect.objectContaining({
78+
onError: expect.any(Function),
79+
onStdout: expect.any(Function),
80+
}),
81+
}),
82+
);
7483
});
7584

7685
it('should process executorOptions', async () => {
@@ -114,8 +123,7 @@ describe('runAutorunExecutor', () => {
114123
{ verbose: true },
115124
{ ...executorContext('github-action'), cwd: '<CWD>' },
116125
);
117-
// eslint-disable-next-line n/no-sync
118-
expect(execSync).toHaveBeenCalledTimes(1);
126+
expect(executeProcessSpy).toHaveBeenCalledTimes(1);
119127

120128
expect(output.command).toMatch('--verbose');
121129
expect(loggerWarnSpy).toHaveBeenCalledTimes(0);

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export function createCliCommand(options?: {
1+
export function createCliCommandString(options?: {
22
args?: Record<string, unknown>;
33
command?: string;
44
bin?: string;
@@ -9,6 +9,22 @@ export function createCliCommand(options?: {
99
)}`;
1010
}
1111

12+
export function createCliCommandObject(options?: {
13+
args?: Record<string, unknown>;
14+
command?: string;
15+
bin?: string;
16+
}): {
17+
command: string;
18+
args: string[];
19+
cwd?: string;
20+
} {
21+
const { bin = '@code-pushup/cli', command, args } = options ?? {};
22+
return {
23+
command: 'npx',
24+
args: [bin, ...objectToCliArgs({ _: command ?? [], ...args })],
25+
};
26+
}
27+
1228
type ArgumentValue = number | string | boolean | string[];
1329
export type CliArgsObject<T extends object = Record<string, ArgumentValue>> =
1430
T extends never

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

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

44
describe('objectToCliArgs', () => {
55
it('should empty params', () => {
@@ -86,14 +86,16 @@ describe('objectToCliArgs', () => {
8686
});
8787
});
8888

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

9595
it('should create command out of object for arguments with positional', () => {
96-
const result = createCliCommand({ args: { _: 'autorun', verbose: true } });
96+
const result = createCliCommandString({
97+
args: { _: 'autorun', verbose: true },
98+
});
9799
expect(result).toBe('npx @code-pushup/cli autorun --verbose');
98100
});
99101
});

0 commit comments

Comments
 (0)