Skip to content

Commit a1fde20

Browse files
committed
feat(cli): add --output=<file> option to print-config command
1 parent e88bf35 commit a1fde20

File tree

7 files changed

+107
-20
lines changed

7 files changed

+107
-20
lines changed

e2e/cli-e2e/tests/print-config.e2e.test.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { cp } from 'node:fs/promises';
1+
import { cp, readFile } from 'node:fs/promises';
22
import path from 'node:path';
33
import { beforeAll, expect } from 'vitest';
44
import { nxTargetProject } from '@code-pushup/test-nx-utils';
@@ -72,4 +72,30 @@ describe('CLI print-config', () => {
7272
);
7373
},
7474
);
75+
76+
it('should print config to output file', async () => {
77+
const { code, stdout } = await executeProcess({
78+
command: 'npx',
79+
args: ['@code-pushup/cli', 'print-config', '--output=config.json'],
80+
cwd: testFileDummySetup,
81+
});
82+
83+
expect(code).toBe(0);
84+
85+
const output = await readFile(
86+
path.join(testFileDummySetup, 'config.json'),
87+
'utf8',
88+
);
89+
expect(JSON.parse(output)).toEqual(
90+
expect.objectContaining({
91+
plugins: [
92+
expect.objectContaining({
93+
slug: 'dummy-plugin',
94+
title: 'Dummy Plugin',
95+
}),
96+
],
97+
}),
98+
);
99+
expect(stdout).not.toContain('dummy-plugin');
100+
});
75101
});

packages/cli/README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,11 @@ Usage:
303303
Description:
304304
Print the resolved configuration.
305305

306-
Refer to the [Common Command Options](#common-command-options) for the list of available options.
306+
In addition to the [Common Command Options](#common-command-options), the following options are recognized by the `print-config` command:
307+
308+
| Option | Required | Type | Description |
309+
| -------------- | :------: | -------- | -------------------------------------------------------- |
310+
| **`--output`** | no | `string` | Path to output file to print config (default is stdout). |
307311

308312
### `merge-diffs` command
309313

packages/cli/src/lib/commands.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { yargsCollectCommandObject } from './collect/collect-command.js';
44
import { yargsCompareCommandObject } from './compare/compare-command.js';
55
import { yargsHistoryCommandObject } from './history/history-command.js';
66
import { yargsMergeDiffsCommandObject } from './merge-diffs/merge-diffs-command.js';
7-
import { yargsConfigCommandObject } from './print-config/print-config-command.js';
7+
import { yargsPrintConfigCommandObject } from './print-config/print-config-command.js';
88
import { yargsUploadCommandObject } from './upload/upload-command.js';
99

1010
export const commands: CommandModule[] = [
@@ -17,6 +17,6 @@ export const commands: CommandModule[] = [
1717
yargsUploadCommandObject(),
1818
yargsHistoryCommandObject(),
1919
yargsCompareCommandObject(),
20-
yargsConfigCommandObject(),
20+
yargsPrintConfigCommandObject(),
2121
yargsMergeDiffsCommandObject(),
2222
];
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type PrintConfigOptions = {
2+
output?: string;
3+
};
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { Options } from 'yargs';
2+
import type { PrintConfigOptions } from './print-config.model.js';
3+
4+
export function yargsPrintConfigOptionsDefinition(): Record<
5+
keyof PrintConfigOptions,
6+
Options
7+
> {
8+
return {
9+
output: {
10+
describe: 'Output file path to use instead of stdout',
11+
type: 'string',
12+
},
13+
};
14+
}
Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,34 @@
1+
import { bold } from 'ansis';
2+
import { mkdir, writeFile } from 'node:fs/promises';
3+
import path from 'node:path';
14
import type { CommandModule } from 'yargs';
25
import { ui } from '@code-pushup/utils';
36
import { filterKebabCaseKeys } from '../implementation/global.utils.js';
7+
import type { PrintConfigOptions } from '../implementation/print-config.model.js';
8+
import { yargsPrintConfigOptionsDefinition } from '../implementation/print-config.options.js';
49

5-
export function yargsConfigCommandObject() {
10+
export function yargsPrintConfigCommandObject() {
611
const command = 'print-config';
712
return {
813
command,
914
describe: 'Print config',
10-
handler: yargsArgs => {
11-
const { _, $0, ...args } = yargsArgs;
15+
builder: yargsPrintConfigOptionsDefinition(),
16+
handler: async yargsArgs => {
1217
// it is important to filter out kebab case keys
1318
// because yargs duplicates options in camel case and kebab case
14-
const cleanArgs = filterKebabCaseKeys(args);
15-
ui().logger.log(JSON.stringify(cleanArgs, null, 2));
19+
const { _, $0, ...args } = filterKebabCaseKeys(yargsArgs);
20+
const { output, ...config } = args as PrintConfigOptions &
21+
Record<string, unknown>;
22+
23+
const content = JSON.stringify(config, null, 2);
24+
25+
if (output) {
26+
await mkdir(path.dirname(output), { recursive: true });
27+
await writeFile(output, content);
28+
ui().logger.info(`Config printed to file ${bold(output)}`);
29+
} else {
30+
ui().logger.log(content);
31+
}
1632
},
1733
} satisfies CommandModule;
1834
}

packages/cli/src/lib/print-config/print-config-command.unit.test.ts

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,54 @@
1+
import { readFile } from 'node:fs/promises';
2+
import path from 'node:path';
13
import { describe, expect, vi } from 'vitest';
4+
import { MEMFS_VOLUME } from '@code-pushup/test-utils';
25
import { ui } from '@code-pushup/utils';
36
import { DEFAULT_CLI_CONFIGURATION } from '../../../mocks/constants.js';
47
import { yargsCli } from '../yargs-cli.js';
5-
import { yargsConfigCommandObject } from './print-config-command.js';
8+
import { yargsPrintConfigCommandObject } from './print-config-command.js';
69

710
vi.mock('@code-pushup/core', async () => {
811
const { CORE_CONFIG_MOCK }: typeof import('@code-pushup/test-utils') =
912
await vi.importActual('@code-pushup/test-utils');
1013
const core: object = await vi.importActual('@code-pushup/core');
1114
return {
1215
...core,
13-
readRcByPath: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
16+
autoloadRc: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
1417
};
1518
});
1619

1720
describe('print-config-command', () => {
21+
it('should log config to stdout by default', async () => {
22+
await yargsCli(['print-config'], {
23+
...DEFAULT_CLI_CONFIGURATION,
24+
commands: [yargsPrintConfigCommandObject()],
25+
}).parseAsync();
26+
27+
expect(ui()).toHaveLogged('log', expect.stringContaining('"plugins": ['));
28+
});
29+
30+
it('should write config to file if output option is given', async () => {
31+
const outputPath = path.join(MEMFS_VOLUME, 'config.json');
32+
await yargsCli(['print-config', `--output=${outputPath}`], {
33+
...DEFAULT_CLI_CONFIGURATION,
34+
commands: [yargsPrintConfigCommandObject()],
35+
}).parseAsync();
36+
37+
await expect(readFile(outputPath, 'utf8')).resolves.toContain(
38+
'"plugins": [',
39+
);
40+
expect(ui()).not.toHaveLogged(
41+
'log',
42+
expect.stringContaining('"plugins": ['),
43+
);
44+
expect(ui()).toHaveLogged('info', `Config printed to file ${outputPath}`);
45+
});
46+
1847
it('should filter out meta arguments and kebab duplicates', async () => {
19-
await yargsCli(
20-
[
21-
'print-config',
22-
'--verbose',
23-
`--config=/test/code-pushup.config.ts`,
24-
'--persist.outputDir=destinationDir',
25-
],
26-
{ ...DEFAULT_CLI_CONFIGURATION, commands: [yargsConfigCommandObject()] },
27-
).parseAsync();
48+
await yargsCli(['print-config', '--persist.outputDir=destinationDir'], {
49+
...DEFAULT_CLI_CONFIGURATION,
50+
commands: [yargsPrintConfigCommandObject()],
51+
}).parseAsync();
2852

2953
expect(ui()).not.toHaveLogged('log', expect.stringContaining('"$0":'));
3054
expect(ui()).not.toHaveLogged('log', expect.stringContaining('"_":'));

0 commit comments

Comments
 (0)