Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions e2e/nx-plugin-e2e/tests/executor-cli.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ describe('executor command', () => {
expect(cleanStdout).toContain(
'nx run my-lib:code-pushup collect --persist.filename=terminal-report',
);
expect(cleanStdout).toContain('Code PushUp CLI');

await expect(
readJsonFile(path.join(cwd, '.reports', 'terminal-report.json')),
Expand Down
40 changes: 25 additions & 15 deletions packages/nx-plugin/src/executors/cli/executor.int.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
import { execSync } from 'node:child_process';
import { afterEach, expect, vi } from 'vitest';
import { executorContext } from '@code-pushup/test-nx-utils';
import * as executeProcessModule from '../../internal/execute-process.js';
import runAutorunExecutor from './executor.js';
import * as utils from './utils.js';

vi.mock('node:child_process', async () => {
const actual = await vi.importActual('node:child_process');
return {
...actual,
execSync: vi.fn(),
};
});

describe('runAutorunExecutor', () => {
const parseAutorunExecutorOptionsSpy = vi.spyOn(
utils,
'parseAutorunExecutorOptions',
);
const executeProcessSpy = vi.spyOn(executeProcessModule, 'executeProcess');

beforeEach(() => {
executeProcessSpy.mockResolvedValue({
code: 0,
stdout: '',
stderr: '',
date: new Date().toISOString(),
duration: 100,
});
});

afterEach(() => {
parseAutorunExecutorOptionsSpy.mockReset();
executeProcessSpy.mockReset();
});

it('should normalize context, parse CLI options and execute command', async () => {
Expand All @@ -38,11 +42,17 @@ describe('runAutorunExecutor', () => {
projectConfig: expect.objectContaining({ name: 'utils' }),
}),
);
// eslint-disable-next-line n/no-sync
expect(execSync).toHaveBeenCalledTimes(1);
// eslint-disable-next-line n/no-sync
expect(execSync).toHaveBeenCalledWith(expect.stringContaining('utils'), {
cwd: process.cwd(),
});
expect(executeProcessSpy).toHaveBeenCalledTimes(1);
expect(executeProcessSpy).toHaveBeenCalledWith(
expect.objectContaining({
command: 'npx',
args: expect.arrayContaining(['@code-pushup/cli']),
cwd: process.cwd(),
observer: expect.objectContaining({
onError: expect.any(Function),
onStdout: expect.any(Function),
}),
}),
);
});
});
39 changes: 27 additions & 12 deletions packages/nx-plugin/src/executors/cli/executor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { type ExecutorContext, logger } from '@nx/devkit';
import { execSync } from 'node:child_process';
import { createCliCommand } from '../internal/cli.js';
import { executeProcess } from '../../internal/execute-process.js';
import {
createCliCommandObject,
createCliCommandString,
} from '../internal/cli.js';
import { normalizeContext } from '../internal/context.js';
import type { AutorunCommandExecutorOptions } from './schema.js';
import { mergeExecutorOptions, parseAutorunExecutorOptions } from './utils.js';
Expand All @@ -11,7 +14,8 @@ export type ExecutorOutput = {
error?: Error;
};

export default function runAutorunExecutor(
// eslint-disable-next-line max-lines-per-function
export default async function runAutorunExecutor(
terminalAndExecutorOptions: AutorunCommandExecutorOptions,
context: ExecutorContext,
): Promise<ExecutorOutput> {
Expand All @@ -26,8 +30,10 @@ export default function runAutorunExecutor(
);
const { dryRun, verbose, command } = mergedOptions;

const commandString = createCliCommand({ command, args: cliArgumentObject });
const commandStringOptions = context.cwd ? { cwd: context.cwd } : {};
const commandString = createCliCommandString({
command,
args: cliArgumentObject,
});
if (verbose) {
logger.info(`Run CLI executor ${command ?? ''}`);
logger.info(`Command: ${commandString}`);
Expand All @@ -36,21 +42,30 @@ export default function runAutorunExecutor(
logger.warn(`DryRun execution of: ${commandString}`);
} else {
try {
// @TODO use executeProcess instead of execSync -> non blocking, logs #761
// eslint-disable-next-line n/no-sync
execSync(commandString, commandStringOptions);
await executeProcess({
...createCliCommandObject({ command, args: cliArgumentObject }),
observer: {
onError: error => {
logger.error(error.message);
},
onStdout: data => {
process.stdout.write(data);
},
},
...(context.cwd ? { cwd: context.cwd } : {}),
});
} catch (error) {
logger.error(error);
return Promise.resolve({
return {
success: false,
command: commandString,
error: error as Error,
});
};
}
}

return Promise.resolve({
return {
success: true,
command: commandString,
});
};
}
58 changes: 33 additions & 25 deletions packages/nx-plugin/src/executors/cli/executor.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,17 @@
import { logger } from '@nx/devkit';
import { execSync } from 'node:child_process';
import { afterAll, afterEach, beforeEach, expect, vi } from 'vitest';
import { executorContext } from '@code-pushup/test-nx-utils';
import { MEMFS_VOLUME } from '@code-pushup/test-utils';
import * as executeProcessModule from '../../internal/execute-process.js';
import runAutorunExecutor from './executor.js';

vi.mock('node:child_process', async () => {
const actual = await vi.importActual('node:child_process');

return {
...actual,
execSync: vi.fn((command: string) => {
if (command.includes('THROW_ERROR')) {
throw new Error(command);
}
}),
};
});

describe('runAutorunExecutor', () => {
const processEnvCP = Object.fromEntries(
Object.entries(process.env).filter(([k]) => k.startsWith('CP_')),
);
const loggerInfoSpy = vi.spyOn(logger, 'info');
const loggerWarnSpy = vi.spyOn(logger, 'warn');
const executeProcessSpy = vi.spyOn(executeProcessModule, 'executeProcess');

/* eslint-disable functional/immutable-data, @typescript-eslint/no-dynamic-delete */
beforeAll(() => {
Expand All @@ -34,26 +22,40 @@ describe('runAutorunExecutor', () => {

beforeEach(() => {
vi.unstubAllEnvs();
executeProcessSpy.mockResolvedValue({
code: 0,
stdout: '',
stderr: '',
date: new Date().toISOString(),
duration: 100,
});
});

afterEach(() => {
loggerWarnSpy.mockReset();
loggerInfoSpy.mockReset();
executeProcessSpy.mockReset();
});

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

it('should call execSync with return result', async () => {
it('should call executeProcess with return result', async () => {
const output = await runAutorunExecutor({}, executorContext('utils'));
expect(output.success).toBe(true);
expect(output.command).toMatch('npx @code-pushup/cli');
// eslint-disable-next-line n/no-sync
expect(execSync).toHaveBeenCalledWith(
expect.stringContaining('npx @code-pushup/cli'),
{ cwd: MEMFS_VOLUME },
expect(executeProcessSpy).toHaveBeenCalledWith(
expect.objectContaining({
command: 'npx',
args: expect.arrayContaining(['@code-pushup/cli']),
cwd: MEMFS_VOLUME,
observer: expect.objectContaining({
onError: expect.any(Function),
onStdout: expect.any(Function),
}),
}),
);
});

Expand All @@ -67,10 +69,17 @@ describe('runAutorunExecutor', () => {
);
expect(output.success).toBe(true);
expect(output.command).toMatch('utils');
// eslint-disable-next-line n/no-sync
expect(execSync).toHaveBeenCalledWith(expect.stringContaining('utils'), {
cwd: 'cwd-form-context',
});
expect(executeProcessSpy).toHaveBeenCalledWith(
expect.objectContaining({
command: 'npx',
args: expect.arrayContaining(['@code-pushup/cli']),
cwd: 'cwd-form-context',
observer: expect.objectContaining({
onError: expect.any(Function),
onStdout: expect.any(Function),
}),
}),
);
});

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

expect(output.command).toMatch('--verbose');
expect(loggerWarnSpy).toHaveBeenCalledTimes(0);
Expand Down
18 changes: 17 additions & 1 deletion packages/nx-plugin/src/executors/internal/cli.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export function createCliCommand(options?: {
export function createCliCommandString(options?: {
args?: Record<string, unknown>;
command?: string;
bin?: string;
Expand All @@ -9,6 +9,22 @@ export function createCliCommand(options?: {
)}`;
}

export function createCliCommandObject(options?: {
args?: Record<string, unknown>;
command?: string;
bin?: string;
}): {
command: string;
args: string[];
cwd?: string;
} {
const { bin = '@code-pushup/cli', command, args } = options ?? {};
return {
command: 'npx',
args: [bin, ...objectToCliArgs({ _: command ?? [], ...args })],
};
}

type ArgumentValue = number | string | boolean | string[];
export type CliArgsObject<T extends object = Record<string, ArgumentValue>> =
T extends never
Expand Down
10 changes: 6 additions & 4 deletions packages/nx-plugin/src/executors/internal/cli.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import { createCliCommand, objectToCliArgs } from './cli.js';
import { createCliCommandString, objectToCliArgs } from './cli.js';

describe('objectToCliArgs', () => {
it('should empty params', () => {
Expand Down Expand Up @@ -86,14 +86,16 @@ describe('objectToCliArgs', () => {
});
});

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

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