Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
348246c
refactor: set env vars if verbose is given
Dec 2, 2025
2dcfb65
refactor: fix int test
Dec 2, 2025
1106a36
refactor: fix lint
Dec 2, 2025
21511d4
refactor: wip
Dec 2, 2025
e120e3b
refactor: wip
Dec 2, 2025
5bab88e
refactor: wip
Dec 2, 2025
bf78e82
refactor: wip
Dec 2, 2025
676b88c
refactor: wip
Dec 2, 2025
5ff3627
refactor: wip
Dec 2, 2025
f270316
refactor: intro evn options to cli executor
Dec 2, 2025
38c07da
Merge branch 'main' into refactor/nx-plugin/fix-verbose-handling
Dec 2, 2025
be53602
refactor: fix unit test
Dec 2, 2025
f3a9231
refactor: wip
Dec 2, 2025
13a6340
refactor: wip
Dec 2, 2025
f5b950b
refactor: wip
Dec 2, 2025
aea384b
refactor: wip
Dec 2, 2025
4e02f79
refactor: wip
Dec 2, 2025
986ce0e
refactor: fix lint
Dec 2, 2025
893b1d3
Merge remote-tracking branch 'origin/main' into fix/nx-plugin/add-env…
Dec 2, 2025
f4b8cdd
Merge branch 'refactor/nx-plugin/fix-verbose-handling' into fix/nx-pl…
Dec 2, 2025
15418df
refactor: wip
Dec 2, 2025
507a9e6
Merge branch 'main' into fix/nx-plugin/add-env-vars-to-executor
Dec 4, 2025
e8033d3
refactor: wip
Dec 4, 2025
8c1e35c
refactor: wip
Dec 4, 2025
20d8c86
Merge branch 'main' into fix/nx-plugin/add-env-vars-to-executor
Dec 4, 2025
983f1f8
refactor: wip
Dec 4, 2025
2eb3abd
refactor: wip
Dec 4, 2025
2787b89
refactor: wip
Dec 4, 2025
e74275b
refactor: wip
Dec 4, 2025
0118a7e
refactor: wip
Dec 4, 2025
093b4e6
refactor: wip
Dec 4, 2025
4a483a6
refactor: wip
Dec 4, 2025
75d080e
Merge branch 'main' into fix/nx-plugin/add-env-vars-to-executor
Dec 5, 2025
e5e8df0
refactor: wip
Dec 5, 2025
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
38 changes: 26 additions & 12 deletions packages/nx-plugin/src/executors/cli/executor.int.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import { afterEach, expect, vi } from 'vitest';
import { afterEach, beforeEach, describe, expect, it, 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 runCliExecutor from './executor.js';
import * as utils from './utils.js';

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

beforeEach(() => {
Expand All @@ -22,22 +19,22 @@ describe('runAutorunExecutor', () => {
});

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

it('should normalize context, parse CLI options and execute command', async () => {
expect(process.env).not.toHaveProperty('CP_VERBOSE', 'true');
const output = await runAutorunExecutor(
const output = await runCliExecutor(
{ verbose: true },
executorContext('utils'),
);
expect(output.success).toBe(true);

expect(parseAutorunExecutorOptionsSpy).toHaveBeenCalledTimes(1);
expect(parseCliExecutorOptionsSpy).toHaveBeenCalledTimes(1);

//is context normalized
expect(parseAutorunExecutorOptionsSpy).toHaveBeenCalledWith(
expect(parseCliExecutorOptionsSpy).toHaveBeenCalledWith(
{ verbose: true },
expect.objectContaining({
projectConfig: expect.objectContaining({ name: 'utils' }),
Expand All @@ -49,7 +46,24 @@ describe('runAutorunExecutor', () => {
args: expect.arrayContaining(['@code-pushup/cli']),
cwd: process.cwd(),
});
});

expect(process.env).toHaveProperty('CP_VERBOSE', 'true');
it('should forward env options to executeProcess', async () => {
const output = await runCliExecutor(
{
verbose: true,
env: { TEST_VALUE: '42' },
},
executorContext('utils'),
);
expect(output.success).toBe(true);
expect(executeProcessSpy).toHaveBeenCalledTimes(1);
expect(executeProcessSpy).toHaveBeenCalledWith(
expect.objectContaining({
env: expect.objectContaining({
TEST_VALUE: '42',
}),
}),
);
});
});
45 changes: 28 additions & 17 deletions packages/nx-plugin/src/executors/cli/executor.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { ExecutorContext } from '@nx/devkit';
import { executeProcess } from '../../internal/execute-process.js';
import { normalizeContext } from '../internal/context.js';
import type { AutorunCommandExecutorOptions } from './schema.js';
import { parseAutorunExecutorOptions } from './utils.js';
import type { CliCommandExecutorOptions } from './schema.js';
import { parseCliExecutorOptions } from './utils.js';

export type ExecutorOutput = {
success: boolean;
Expand All @@ -11,44 +11,55 @@ export type ExecutorOutput = {
};

/* eslint-disable-next-line max-lines-per-function */
export default async function runAutorunExecutor(
terminalAndExecutorOptions: AutorunCommandExecutorOptions,
export default async function runCliExecutor(
terminalAndExecutorOptions: CliCommandExecutorOptions,
context: ExecutorContext,
): Promise<ExecutorOutput> {
const { objectToCliArgs, formatCommandStatus, logger, stringifyError } =
await import('@code-pushup/utils');
const normalizedContext = normalizeContext(context);
const cliArgumentObject = parseAutorunExecutorOptions(
terminalAndExecutorOptions,
normalizedContext,
);
const { command: cliCommand } = terminalAndExecutorOptions;
const { verbose = false, dryRun, bin, ...restArgs } = cliArgumentObject;
const {
command: cliCommand,
verbose = false,
dryRun,
env: executorEnv,
bin,
...restArgs
} = parseCliExecutorOptions(terminalAndExecutorOptions, normalizedContext);

logger.setVerbose(verbose);

const command = bin ? `node` : 'npx';
const positionals = [
const args = [
bin ?? '@code-pushup/cli',
...(cliCommand ? [cliCommand] : []),
...objectToCliArgs(restArgs),
];
const args = [...positionals, ...objectToCliArgs(restArgs)];
const executorEnvVariables = {
...(verbose && { CP_VERBOSE: 'true' }),
};
const commandString = formatCommandStatus([command, ...args].join(' '), {
cwd: context.cwd,
env: executorEnvVariables,
env: {
...executorEnv,
...(verbose && { CP_VERBOSE: 'true' }),
},
});

if (dryRun) {
logger.warn(`DryRun execution of: ${commandString}`);
} else {
try {
logger.debug(`With env vars: ${executorEnvVariables}`);
logger.debug(`With env vars: ${executorEnv}`);
await executeProcess({
command,
args,
...(context.cwd ? { cwd: context.cwd } : {}),
...(executorEnv && Object.keys(executorEnv).length > 0
? {
env: {
...process.env,
...executorEnv,
},
}
: {}),
});
} catch (error) {
logger.error(stringifyError(error));
Expand Down
36 changes: 23 additions & 13 deletions packages/nx-plugin/src/executors/cli/executor.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import { afterAll, afterEach, beforeEach, expect, vi } from 'vitest';
import {
afterAll,
afterEach,
beforeAll,
beforeEach,
describe,
expect,
it,
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';
import runCliExecutor from './executor.js';

describe('runAutorunExecutor', () => {
describe('runCliExecutor', () => {
const processEnvCP = Object.fromEntries(
Object.entries(process.env).filter(([k]) => k.startsWith('CP_')),
);
Expand Down Expand Up @@ -41,7 +50,7 @@ describe('runAutorunExecutor', () => {
});

it('should call executeProcess with return result', async () => {
const output = await runAutorunExecutor({}, executorContext('utils'));
const output = await runCliExecutor({}, executorContext('utils'));
expect(output.success).toBe(true);
expect(output.command).toMatch('npx @code-pushup/cli');
expect(executeProcessSpy).toHaveBeenCalledWith({
Expand All @@ -52,15 +61,16 @@ describe('runAutorunExecutor', () => {
});

it('should normalize context', async () => {
const output = await runAutorunExecutor(
const output = await runCliExecutor(
{},
{
...executorContext('utils'),
cwd: 'cwd-form-context',
},
);
expect(output.success).toBe(true);
expect(output.command).toMatch('utils');
expect(output.command).toMatch('npx @code-pushup/cli');
expect(output.command).toContain('cwd-form-context');
expect(executeProcessSpy).toHaveBeenCalledWith({
command: 'npx',
args: expect.arrayContaining(['@code-pushup/cli']),
Expand All @@ -69,7 +79,7 @@ describe('runAutorunExecutor', () => {
});

it('should process executorOptions', async () => {
const output = await runAutorunExecutor(
const output = await runCliExecutor(
{ output: 'code-pushup.config.json', persist: { filename: 'REPORT' } },
executorContext('testing-utils'),
);
Expand All @@ -79,7 +89,7 @@ describe('runAutorunExecutor', () => {
});

it('should create command from context and options if no api key is set', async () => {
const output = await runAutorunExecutor(
const output = await runCliExecutor(
{ persist: { filename: 'REPORT', format: ['md', 'json'] } },
executorContext('core'),
);
Expand All @@ -91,7 +101,7 @@ describe('runAutorunExecutor', () => {

it('should create command from context, options and arguments if api key is set', async () => {
vi.stubEnv('CP_API_KEY', 'cp_1234567');
const output = await runAutorunExecutor(
const output = await runCliExecutor(
{
persist: { filename: 'REPORT', format: ['md', 'json'] },
upload: { project: 'CLI' },
Expand All @@ -107,7 +117,7 @@ describe('runAutorunExecutor', () => {
});

it('should set env var information if verbose is set', async () => {
const output = await runAutorunExecutor(
const output = await runCliExecutor(
{
verbose: true,
},
Expand All @@ -131,8 +141,8 @@ describe('runAutorunExecutor', () => {
expect(logger.warn).toHaveBeenCalledTimes(0);
});

it('should log env var in dryRun information if verbose is set', async () => {
const output = await runAutorunExecutor(
it('should log CP_VERBOSE env var in dryRun information if verbose is set', async () => {
const output = await runCliExecutor(
{
dryRun: true,
verbose: true,
Expand All @@ -150,7 +160,7 @@ describe('runAutorunExecutor', () => {
});

it('should log command if dryRun is set', async () => {
await runAutorunExecutor({ dryRun: true }, executorContext('utils'));
await runCliExecutor({ dryRun: true }, executorContext('utils'));

expect(logger.command).toHaveBeenCalledTimes(0);
expect(logger.warn).toHaveBeenCalledTimes(1);
Expand Down
9 changes: 8 additions & 1 deletion packages/nx-plugin/src/executors/cli/schema.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/schema",
"$id": "AutorunExecutorOptions",
"$id": "CliExecutorOptions",
"title": "CodePushup CLI autorun executor",
"description": "Executes the @code-pushup/cli autorun command See: https://github.com/code-pushup/cli/blob/main/packages/cli/README.md#autorun-command",
"type": "object",
Expand All @@ -21,6 +21,13 @@
"type": "string",
"description": "Path to Code PushUp CLI"
},
"env": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"description": "Environment variables added to Code PushUp CLI process"
},
"verbose": {
"type": "boolean",
"description": "Print additional logs"
Expand Down
6 changes: 3 additions & 3 deletions packages/nx-plugin/src/executors/cli/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@ import type {

export type PrintConfigOptions = { output?: string };
export type PrintConfigCommandExecutorOptions = PrintConfigOptions;
export type AutorunCommandExecutorOnlyOptions = ProjectExecutorOnlyOptions &
export type CliCommandExecutorOnlyOptions = ProjectExecutorOnlyOptions &
CollectExecutorOnlyOptions &
GeneralExecutorOnlyOptions;

export type AutorunCommandExecutorOptions = Partial<
export type CliCommandExecutorOptions = Partial<
{
upload: Partial<UploadConfig>;
persist: Partial<PersistConfig>;
} & AutorunCommandExecutorOnlyOptions &
} & CliCommandExecutorOnlyOptions &
GlobalExecutorOptions
> &
PrintConfigOptions;
8 changes: 4 additions & 4 deletions packages/nx-plugin/src/executors/cli/utils.int.test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { expect, vi } from 'vitest';
import { describe, expect, it, vi } from 'vitest';
import type { UploadConfig } from '@code-pushup/models';
import { normalizedExecutorContext } from '../../../mock/utils/executor.js';
import * as config from '../internal/config.js';
import { parseAutorunExecutorOptions } from './utils.js';
import { parseCliExecutorOptions } from './utils.js';

describe('parseAutorunExecutorOptions', () => {
describe('parseCliExecutorOptions', () => {
const persistConfigSpy = vi.spyOn(config, 'persistConfig');
const uploadConfigSpy = vi.spyOn(config, 'uploadConfig');
const globalConfigSpy = vi.spyOn(config, 'globalConfig');
const normalizedContext = normalizedExecutorContext('portal');

afterEach(() => {

Check failure on line 13 in packages/nx-plugin/src/executors/cli/utils.int.test.ts

View workflow job for this annotation

GitHub Actions / Standalone mode

<✓> TypeScript | Semantic errors

TS2304: Cannot find name 'afterEach'.
persistConfigSpy.mockReset();
uploadConfigSpy.mockReset();
globalConfigSpy.mockReset();
});

it('should call child config functions with options', () => {
parseAutorunExecutorOptions(
parseCliExecutorOptions(
{
verbose: true,
persist: { filename: 'my-name' },
Expand Down
22 changes: 12 additions & 10 deletions packages/nx-plugin/src/executors/cli/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,21 @@ import {
} from '../internal/config.js';
import type { NormalizedExecutorContext } from '../internal/context.js';
import type {
AutorunCommandExecutorOnlyOptions,
AutorunCommandExecutorOptions,
CliCommandExecutorOnlyOptions,
CliCommandExecutorOptions,
PrintConfigCommandExecutorOptions,
} from './schema.js';

export function parseAutorunExecutorOnlyOptions(
options: Partial<AutorunCommandExecutorOnlyOptions>,
): AutorunCommandExecutorOnlyOptions {
const { projectPrefix, dryRun, onlyPlugins } = options;
export function parseCliExecutorOnlyOptions(
options: Partial<CliCommandExecutorOnlyOptions>,
): CliCommandExecutorOnlyOptions {
const { projectPrefix, dryRun, onlyPlugins, env, bin } = options;
return {
...(projectPrefix && { projectPrefix }),
...(dryRun != null && { dryRun }),
...(onlyPlugins && { onlyPlugins }),
...(env && { env }),
...(bin && { bin }),
};
}

Expand All @@ -30,10 +32,10 @@ export function parsePrintConfigExecutorOptions(
};
}

export function parseAutorunExecutorOptions(
options: Partial<AutorunCommandExecutorOptions>,
export function parseCliExecutorOptions(
options: Partial<CliCommandExecutorOptions>,
normalizedContext: NormalizedExecutorContext,
): AutorunCommandExecutorOptions {
): CliCommandExecutorOptions {
const { projectPrefix, persist, upload, command, output } = options;
const needsUploadParams =
command === 'upload' || command === 'autorun' || command === undefined;
Expand All @@ -44,7 +46,7 @@ export function parseAutorunExecutorOptions(
const hasApiToken = uploadCfg?.apiKey != null;
return {
...parsePrintConfigExecutorOptions(options),
...parseAutorunExecutorOnlyOptions(options),
...parseCliExecutorOnlyOptions(options),
...globalConfig(options, normalizedContext),
...(output ? { output } : {}),
persist: persistConfig({ projectPrefix, ...persist }, normalizedContext),
Expand Down
Loading