Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
28 changes: 27 additions & 1 deletion e2e/cli-e2e/tests/print-config.e2e.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cp } from 'node:fs/promises';
import { cp, readFile } from 'node:fs/promises';
import path from 'node:path';
import { beforeAll, expect } from 'vitest';
import { nxTargetProject } from '@code-pushup/test-nx-utils';
Expand Down Expand Up @@ -72,4 +72,30 @@ describe('CLI print-config', () => {
);
},
);

it('should print config to output file', async () => {
const { code, stdout } = await executeProcess({
command: 'npx',
args: ['@code-pushup/cli', 'print-config', '--output=config.json'],
cwd: testFileDummySetup,
});

expect(code).toBe(0);

const output = await readFile(
path.join(testFileDummySetup, 'config.json'),
'utf8',
);
expect(JSON.parse(output)).toEqual(
expect.objectContaining({
plugins: [
expect.objectContaining({
slug: 'dummy-plugin',
title: 'Dummy Plugin',
}),
],
}),
);
expect(stdout).not.toContain('dummy-plugin');
});
});
32 changes: 20 additions & 12 deletions packages/ci/src/lib/cli/commands/print-config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import { executeProcess, stringifyError } from '@code-pushup/utils';
import { rm } from 'node:fs/promises';
import path from 'node:path';
import {
executeProcess,
generateRandomId,
readJsonFile,
stringifyError,
} from '@code-pushup/utils';
import type { CommandContext } from '../context.js';

export async function runPrintConfig({
Expand All @@ -7,27 +14,28 @@
directory,
silent,
}: CommandContext): Promise<unknown> {
// random file name so command can be run in parallel
const outputFile = `code-pushup.${generateRandomId()}.config.json`;
const outputPath = path.join(directory, outputFile);

const { stdout } = await executeProcess({
command: bin,
args: [...(config ? [`--config=${config}`] : []), 'print-config'],
args: [
...(config ? [`--config=${config}`] : []),

Check failure on line 24 in packages/ci/src/lib/cli/commands/print-config.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> Code coverage | Branch coverage

1st branch is not taken in any test case.
'print-config',
`--output=${outputFile}`,
],
cwd: directory,
});
if (!silent) {
console.info(stdout);
}

// workaround for 1st lines like `> nx run utils:code-pushup -- print-config`
const lines = stdout.split(/\r?\n/);
const jsonLines = lines.slice(lines.indexOf('{'), lines.indexOf('}') + 1);
const stdoutSanitized = jsonLines.join('\n');

try {
return JSON.parse(stdoutSanitized) as unknown;
const content = await readJsonFile(outputPath);
await rm(outputPath);
return content;
} catch (error) {
if (silent) {
console.info('Invalid output from print-config:');
console.info(stdout);
}
throw new Error(
`Error parsing output of print-config command - ${stringifyError(error)}`,
);
Expand Down
2 changes: 1 addition & 1 deletion packages/ci/src/lib/run-monorepo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ async function runProjectsInBulk(
const currProjectReports = await Promise.all(
projects.map(async (project): Promise<ProjectReport> => {
const ctx = createCommandContext(settings, project);
const config = await printPersistConfig(ctx, settings);
const config = await printPersistConfig(ctx);
const reports = persistedFilesFromConfig(config, ctx);
return { project, reports, config, ctx };
}),
Expand Down
7 changes: 3 additions & 4 deletions packages/ci/src/lib/run-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export async function runOnProject(
logger.info(`Running Code PushUp on monorepo project ${project.name}`);
}

const config = await printPersistConfig(ctx, settings);
const config = await printPersistConfig(ctx);
logger.debug(
`Loaded persist config from print-config command - ${JSON.stringify(config.persist)}`,
);
Expand Down Expand Up @@ -267,7 +267,7 @@ export async function checkPrintConfig(
? `Executing print-config for project ${project.name}`
: 'Executing print-config';
try {
const config = await printPersistConfig(ctx, settings);
const config = await printPersistConfig(ctx);
logger.debug(
`${operation} verified code-pushup installed in base branch ${base.ref}`,
);
Expand All @@ -283,9 +283,8 @@ export async function checkPrintConfig(

export async function printPersistConfig(
ctx: CommandContext,
settings: Settings,
): Promise<Pick<CoreConfig, 'persist'>> {
const json = await runPrintConfig({ ...ctx, silent: !settings.debug });
const json = await runPrintConfig(ctx);
return parsePersistConfig(json);
}

Expand Down
30 changes: 19 additions & 11 deletions packages/ci/src/lib/run.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,21 @@ describe('runInCI', () => {
break;

case 'print-config':
stdout = await readFile(fixturePaths.config, 'utf8');
let content = await readFile(fixturePaths.config, 'utf8');
if (nxMatch) {
// simulate effect of custom persist.outputDir per Nx project
const config = JSON.parse(stdout) as CoreConfig;
const config = JSON.parse(content) as CoreConfig;
// eslint-disable-next-line functional/immutable-data
config.persist!.outputDir = outputDir;
stdout = JSON.stringify(config, null, 2);
content = JSON.stringify(config, null, 2);
}
const outputFile = args
?.find(arg => arg.startsWith('--output='))
?.split('=')[1];
if (outputFile) {
await writeFile(path.join(cwd as string, outputFile), content);
} else {
stdout = content;
}
break;

Expand Down Expand Up @@ -235,7 +243,7 @@ describe('runInCI', () => {
expect(utils.executeProcess).toHaveBeenCalledTimes(2);
expect(utils.executeProcess).toHaveBeenNthCalledWith(1, {
command: options.bin,
args: ['print-config'],
args: ['print-config', expect.stringMatching(/^--output=.*\.json$/)],
cwd: workDir,
} satisfies utils.ProcessConfig);
expect(utils.executeProcess).toHaveBeenNthCalledWith(2, {
Expand Down Expand Up @@ -307,7 +315,7 @@ describe('runInCI', () => {
expect(utils.executeProcess).toHaveBeenCalledTimes(5);
expect(utils.executeProcess).toHaveBeenNthCalledWith(1, {
command: options.bin,
args: ['print-config'],
args: ['print-config', expect.stringMatching(/^--output=.*\.json$/)],
cwd: workDir,
} satisfies utils.ProcessConfig);
expect(utils.executeProcess).toHaveBeenNthCalledWith(2, {
Expand All @@ -317,7 +325,7 @@ describe('runInCI', () => {
} satisfies utils.ProcessConfig);
expect(utils.executeProcess).toHaveBeenNthCalledWith(3, {
command: options.bin,
args: ['print-config'],
args: ['print-config', expect.stringMatching(/^--output=.*\.json$/)],
cwd: workDir,
} satisfies utils.ProcessConfig);
expect(utils.executeProcess).toHaveBeenNthCalledWith(4, {
Expand Down Expand Up @@ -383,7 +391,7 @@ describe('runInCI', () => {
expect(utils.executeProcess).toHaveBeenCalledTimes(3);
expect(utils.executeProcess).toHaveBeenNthCalledWith(1, {
command: options.bin,
args: ['print-config'],
args: ['print-config', expect.stringMatching(/^--output=.*\.json$/)],
cwd: workDir,
} satisfies utils.ProcessConfig);
expect(utils.executeProcess).toHaveBeenNthCalledWith(2, {
Expand Down Expand Up @@ -577,7 +585,7 @@ describe('runInCI', () => {
).toHaveLength(4); // 1 autorun for all projects, 3 print-configs for each project
expect(utils.executeProcess).toHaveBeenCalledWith({
command: run,
args: ['print-config'],
args: ['print-config', expect.stringMatching(/^--output=.*\.json$/)],
cwd: expect.stringContaining(workDir),
} satisfies utils.ProcessConfig);
expect(utils.executeProcess).toHaveBeenCalledWith({
Expand Down Expand Up @@ -746,7 +754,7 @@ describe('runInCI', () => {
).toHaveLength(10);
expect(utils.executeProcess).toHaveBeenCalledWith({
command: run,
args: ['print-config'],
args: ['print-config', expect.stringMatching(/^--output=.*\.json$/)],
cwd: expect.stringContaining(workDir),
} satisfies utils.ProcessConfig);
expect(utils.executeProcess).toHaveBeenCalledWith({
Expand Down Expand Up @@ -922,7 +930,7 @@ describe('runInCI', () => {
).toHaveLength(6); // 3 autoruns and 3 print-configs for each project
expect(utils.executeProcess).toHaveBeenCalledWith({
command: options.bin,
args: ['print-config'],
args: ['print-config', expect.stringMatching(/^--output=.*\.json$/)],
cwd: expect.stringContaining(workDir),
} satisfies utils.ProcessConfig);
expect(utils.executeProcess).toHaveBeenCalledWith({
Expand Down Expand Up @@ -1077,7 +1085,7 @@ describe('runInCI', () => {
).toHaveLength(10);
expect(utils.executeProcess).toHaveBeenCalledWith({
command: options.bin,
args: ['print-config'],
args: ['print-config', expect.stringMatching(/^--output=.*\.json$/)],
cwd: expect.stringContaining(workDir),
} satisfies utils.ProcessConfig);
expect(utils.executeProcess).toHaveBeenCalledWith({
Expand Down
6 changes: 5 additions & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,11 @@ Usage:
Description:
Print the resolved configuration.

Refer to the [Common Command Options](#common-command-options) for the list of available options.
In addition to the [Common Command Options](#common-command-options), the following options are recognized by the `print-config` command:

| Option | Required | Type | Description |
| -------------- | :------: | -------- | -------------------------------------------------------- |
| **`--output`** | no | `string` | Path to output file to print config (default is stdout). |

### `merge-diffs` command

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/lib/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { yargsCollectCommandObject } from './collect/collect-command.js';
import { yargsCompareCommandObject } from './compare/compare-command.js';
import { yargsHistoryCommandObject } from './history/history-command.js';
import { yargsMergeDiffsCommandObject } from './merge-diffs/merge-diffs-command.js';
import { yargsConfigCommandObject } from './print-config/print-config-command.js';
import { yargsPrintConfigCommandObject } from './print-config/print-config-command.js';
import { yargsUploadCommandObject } from './upload/upload-command.js';

export const commands: CommandModule[] = [
Expand All @@ -17,6 +17,6 @@ export const commands: CommandModule[] = [
yargsUploadCommandObject(),
yargsHistoryCommandObject(),
yargsCompareCommandObject(),
yargsConfigCommandObject(),
yargsPrintConfigCommandObject(),
yargsMergeDiffsCommandObject(),
];
3 changes: 3 additions & 0 deletions packages/cli/src/lib/implementation/print-config.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type PrintConfigOptions = {

Check failure on line 1 in packages/cli/src/lib/implementation/print-config.model.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> Code coverage | Branch coverage

1st branch is not taken in any test case.
output?: string;
};

Check warning on line 3 in packages/cli/src/lib/implementation/print-config.model.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> Code coverage | Line coverage

Lines 1-3 are not covered in any test case.
14 changes: 14 additions & 0 deletions packages/cli/src/lib/implementation/print-config.options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Options } from 'yargs';

Check failure on line 1 in packages/cli/src/lib/implementation/print-config.options.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> Code coverage | Branch coverage

1st branch is not taken in any test case.
import type { PrintConfigOptions } from './print-config.model.js';

export function yargsPrintConfigOptionsDefinition(): Record<
keyof PrintConfigOptions,
Options
> {
return {
output: {
describe: 'Output file path to use instead of stdout',
type: 'string',
},
};
}
26 changes: 21 additions & 5 deletions packages/cli/src/lib/print-config/print-config-command.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
import { bold } from 'ansis';

Check failure on line 1 in packages/cli/src/lib/print-config/print-config-command.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> Code coverage | Branch coverage

1st branch is not taken in any test case.
import { mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path';
import type { CommandModule } from 'yargs';
import { ui } from '@code-pushup/utils';
import { filterKebabCaseKeys } from '../implementation/global.utils.js';
import type { PrintConfigOptions } from '../implementation/print-config.model.js';
import { yargsPrintConfigOptionsDefinition } from '../implementation/print-config.options.js';

export function yargsConfigCommandObject() {
export function yargsPrintConfigCommandObject() {

Check warning on line 10 in packages/cli/src/lib/print-config/print-config-command.ts

View workflow job for this annotation

GitHub Actions / Code PushUp

<✓> JSDoc coverage | Functions coverage

Missing functions documentation for yargsPrintConfigCommandObject
const command = 'print-config';
return {
command,
describe: 'Print config',
handler: yargsArgs => {
const { _, $0, ...args } = yargsArgs;
builder: yargsPrintConfigOptionsDefinition(),
handler: async yargsArgs => {
// it is important to filter out kebab case keys
// because yargs duplicates options in camel case and kebab case
const cleanArgs = filterKebabCaseKeys(args);
ui().logger.log(JSON.stringify(cleanArgs, null, 2));
const { _, $0, ...args } = filterKebabCaseKeys(yargsArgs);
const { output, ...config } = args as PrintConfigOptions &
Record<string, unknown>;

const content = JSON.stringify(config, null, 2);

if (output) {
await mkdir(path.dirname(output), { recursive: true });
await writeFile(output, content);
ui().logger.info(`Config printed to file ${bold(output)}`);
} else {
ui().logger.log(content);
}
},
} satisfies CommandModule;
}
46 changes: 35 additions & 11 deletions packages/cli/src/lib/print-config/print-config-command.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,54 @@
import { readFile } from 'node:fs/promises';
import path from 'node:path';
import { describe, expect, vi } from 'vitest';
import { MEMFS_VOLUME } from '@code-pushup/test-utils';
import { ui } from '@code-pushup/utils';
import { DEFAULT_CLI_CONFIGURATION } from '../../../mocks/constants.js';
import { yargsCli } from '../yargs-cli.js';
import { yargsConfigCommandObject } from './print-config-command.js';
import { yargsPrintConfigCommandObject } from './print-config-command.js';

vi.mock('@code-pushup/core', async () => {
const { CORE_CONFIG_MOCK }: typeof import('@code-pushup/test-utils') =
await vi.importActual('@code-pushup/test-utils');
const core: object = await vi.importActual('@code-pushup/core');
return {
...core,
readRcByPath: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
autoloadRc: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
};
});

describe('print-config-command', () => {
it('should log config to stdout by default', async () => {
await yargsCli(['print-config'], {
...DEFAULT_CLI_CONFIGURATION,
commands: [yargsPrintConfigCommandObject()],
}).parseAsync();

expect(ui()).toHaveLogged('log', expect.stringContaining('"plugins": ['));
});

it('should write config to file if output option is given', async () => {
const outputPath = path.join(MEMFS_VOLUME, 'config.json');
await yargsCli(['print-config', `--output=${outputPath}`], {
...DEFAULT_CLI_CONFIGURATION,
commands: [yargsPrintConfigCommandObject()],
}).parseAsync();

await expect(readFile(outputPath, 'utf8')).resolves.toContain(
'"plugins": [',
);
expect(ui()).not.toHaveLogged(
'log',
expect.stringContaining('"plugins": ['),
);
expect(ui()).toHaveLogged('info', `Config printed to file ${outputPath}`);
});

it('should filter out meta arguments and kebab duplicates', async () => {
await yargsCli(
[
'print-config',
'--verbose',
`--config=/test/code-pushup.config.ts`,
'--persist.outputDir=destinationDir',
],
{ ...DEFAULT_CLI_CONFIGURATION, commands: [yargsConfigCommandObject()] },
).parseAsync();
await yargsCli(['print-config', '--persist.outputDir=destinationDir'], {
...DEFAULT_CLI_CONFIGURATION,
commands: [yargsPrintConfigCommandObject()],
}).parseAsync();

expect(ui()).not.toHaveLogged('log', expect.stringContaining('"$0":'));
expect(ui()).not.toHaveLogged('log', expect.stringContaining('"_":'));
Expand Down
Loading