Skip to content

Commit aef86c9

Browse files
authored
feat(cli): add autoloading of the config file (#361)
1 parent 1cd3cda commit aef86c9

19 files changed

+350
-84
lines changed

e2e/cli-e2e/tests/__snapshots__/help.e2e.test.ts.snap

+3-2
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ Options:
1616
[boolean] [default: true]
1717
--verbose When true creates more verbose output. This is help
1818
ful when debugging. [boolean] [default: false]
19-
--config Path the the config file, e.g. code-pushup.config.j
20-
s [string] [default: \\"code-pushup.config.js\\"]
19+
--config Path the the config file, e.g. code-pushup.config.t
20+
s. By default it loads code-pushup.config.(ts|mjs|j
21+
s). [string]
2122
--persist.outputDir Directory for the produced reports [string]
2223
--persist.filename Filename for the produced reports. [string]
2324
--persist.format Format of the report output. e.g. \`md\`, \`json\`

packages/cli/README.md

+4-5
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ _If you're looking for programmatic usage, then refer to the underlying [@code-p
4040

4141
```js
4242
export default {
43-
persist: {
44-
outputDir: '.code-pushup',
45-
format: ['json', 'md'],
46-
},
4743
plugins: [
4844
// ...
4945
],
@@ -154,7 +150,10 @@ Each example is fully tested to demonstrate best practices for plugin testing as
154150
| ---------------- | --------- | ----------------------- | ---------------------------------------------------------------------- |
155151
| **`--progress`** | `boolean` | `true` | Show progress bar in stdout. |
156152
| **`--verbose`** | `boolean` | `false` | When true creates more verbose output. This is helpful when debugging. |
157-
| **`--config`** | `string` | `code-pushup.config.js` | Path to the config file, e.g. code-pushup.config.js |
153+
| **`--config`** | `string` | `code-pushup.config.ts` | Path to the config file, e.g. code-pushup.config.(ts\|mjs\|js) |
154+
155+
> [!NOTE]
156+
> By default, the CLI loads `code-pushup.config.(ts|mjs|js)` if no config path is provided with `--config`.
158157

159158
### Common Command Options
160159

packages/cli/src/lib/autorun/autorun-command.unit.test.ts

+4-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import { vol } from 'memfs';
22
import { describe, expect, it, vi } from 'vitest';
33
import { PortalUploadArgs, uploadToPortal } from '@code-pushup/portal-client';
4-
import {
5-
collectAndPersistReports,
6-
readCodePushupConfig,
7-
} from '@code-pushup/core';
4+
import { collectAndPersistReports, readRcByPath } from '@code-pushup/core';
85
import { MEMFS_VOLUME, MINIMAL_REPORT_MOCK } from '@code-pushup/testing-utils';
96
import { DEFAULT_CLI_CONFIGURATION } from '../../../mocks/constants';
107
import { yargsCli } from '../yargs-cli';
@@ -17,7 +14,7 @@ vi.mock('@code-pushup/core', async () => {
1714
return {
1815
...core,
1916
collectAndPersistReports: vi.fn().mockResolvedValue({}),
20-
readCodePushupConfig: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
17+
readRcByPath: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
2118
};
2219
});
2320

@@ -44,9 +41,7 @@ describe('autorun-command', () => {
4441
},
4542
).parseAsync();
4643

47-
expect(readCodePushupConfig).toHaveBeenCalledWith(
48-
'/test/code-pushup.config.ts',
49-
);
44+
expect(readRcByPath).toHaveBeenCalledWith('/test/code-pushup.config.ts');
5045

5146
expect(collectAndPersistReports).toHaveBeenCalledWith(
5247
expect.objectContaining({
@@ -59,7 +54,7 @@ describe('autorun-command', () => {
5954
}),
6055
);
6156

62-
// values come from CORE_CONFIG_MOCK returned by readCodePushupConfig mock
57+
// values come from CORE_CONFIG_MOCK returned by readRcByPath mock
6358
expect(uploadToPortal).toHaveBeenCalledWith({
6459
apiKey: 'dummy-api-key',
6560
server: 'https://example.com/api',

packages/cli/src/lib/collect/collect-command.unit.test.ts

+5-14
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
import { describe, expect, it, vi } from 'vitest';
2-
import {
3-
collectAndPersistReports,
4-
readCodePushupConfig,
5-
} from '@code-pushup/core';
2+
import { collectAndPersistReports, readRcByPath } from '@code-pushup/core';
63
import { DEFAULT_CLI_CONFIGURATION } from '../../../mocks/constants';
74
import { yargsCli } from '../yargs-cli';
85
import { yargsCollectCommandObject } from './collect-command';
@@ -14,7 +11,7 @@ vi.mock('@code-pushup/core', async () => {
1411
return {
1512
...core,
1613
collectAndPersistReports: vi.fn().mockResolvedValue({}),
17-
readCodePushupConfig: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
14+
readRcByPath: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
1815
};
1916
});
2017

@@ -26,9 +23,7 @@ describe('collect-command', () => {
2623
commands: [yargsCollectCommandObject()],
2724
}).parseAsync();
2825

29-
expect(readCodePushupConfig).toHaveBeenCalledWith(
30-
'/test/code-pushup.config.ts',
31-
);
26+
expect(readRcByPath).toHaveBeenCalledWith('/test/code-pushup.config.ts');
3227

3328
expect(collectAndPersistReports).toHaveBeenCalledWith(
3429
expect.objectContaining({
@@ -57,9 +52,7 @@ describe('collect-command', () => {
5752
},
5853
).parseAsync();
5954

60-
expect(readCodePushupConfig).toHaveBeenCalledWith(
61-
'/test/code-pushup.config.ts',
62-
);
55+
expect(readRcByPath).toHaveBeenCalledWith('/test/code-pushup.config.ts');
6356

6457
expect(collectAndPersistReports).toHaveBeenCalledWith(
6558
expect.objectContaining({
@@ -86,9 +79,7 @@ describe('collect-command', () => {
8679
},
8780
).parseAsync();
8881

89-
expect(readCodePushupConfig).toHaveBeenCalledWith(
90-
'/test/code-pushup.config.ts',
91-
);
82+
expect(readRcByPath).toHaveBeenCalledWith('/test/code-pushup.config.ts');
9283

9384
expect(collectAndPersistReports).toHaveBeenCalledWith(
9485
expect.objectContaining({

packages/cli/src/lib/implementation/core-config.middleware.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { readCodePushupConfig } from '@code-pushup/core';
1+
import { autoloadRc, readRcByPath } from '@code-pushup/core';
22
import {
33
CoreConfig,
44
PERSIST_FILENAME,
@@ -17,12 +17,13 @@ export async function coreConfigMiddleware<
1717
upload: cliUpload,
1818
...remainingCliOptions
1919
} = args as GeneralCliOptions & Required<CoreConfig>;
20-
const rcOptions = await readCodePushupConfig(config);
20+
// if config path is given use it otherwise auto-load
21+
const importedRc = config ? await readRcByPath(config) : await autoloadRc();
2122
const {
2223
persist: rcPersist,
2324
upload: rcUpload,
2425
...remainingRcConfig
25-
} = rcOptions;
26+
} = importedRc;
2627

2728
const parsedProcessArgs: CoreConfig & GeneralCliOptions = {
2829
config,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { describe, expect, vi } from 'vitest';
2+
import { ConfigPathError } from '@code-pushup/core';
3+
import { CoreConfig } from '@code-pushup/models';
4+
import { CORE_CONFIG_MOCK } from '@code-pushup/testing-utils';
5+
import { coreConfigMiddleware } from './core-config.middleware';
6+
7+
vi.mock('@code-pushup/core', async () => {
8+
const core: object = await vi.importActual('@code-pushup/core');
9+
return {
10+
...core,
11+
readRcByPath: vi.fn().mockImplementation((filepath: string): CoreConfig => {
12+
const extension = filepath.split('.');
13+
if (filepath.includes('throw-error')) {
14+
throw new ConfigPathError(filepath);
15+
}
16+
return {
17+
...CORE_CONFIG_MOCK,
18+
upload: {
19+
...CORE_CONFIG_MOCK.upload,
20+
project: `cli-${extension}`,
21+
},
22+
};
23+
}),
24+
autoloadRc: vi.fn().mockImplementation(
25+
(): CoreConfig => ({
26+
...CORE_CONFIG_MOCK,
27+
upload: {
28+
...CORE_CONFIG_MOCK.upload,
29+
project: `cli-autoload`,
30+
},
31+
}),
32+
),
33+
};
34+
});
35+
36+
describe('coreConfigMiddleware', () => {
37+
it('should load code-pushup.config.(ts|mjs|js) by default', async () => {
38+
const config = await coreConfigMiddleware({});
39+
expect(config?.upload?.project).toBe('cli-autoload');
40+
});
41+
42+
it.each(['ts', 'mjs', 'js'])(
43+
'should load a valid .%s config',
44+
async extension => {
45+
const config = await coreConfigMiddleware({
46+
config: `code-pushup.config.${extension}`,
47+
});
48+
expect(config.config).toContain(`code-pushup.config.${extension}`);
49+
expect(config.upload?.project).toContain(extension);
50+
},
51+
);
52+
53+
it('should throw with invalid config path', async () => {
54+
await expect(
55+
coreConfigMiddleware({ config: 'throw-error' }),
56+
).rejects.toThrow(/Provided path .* is not valid./);
57+
});
58+
});

packages/cli/src/lib/implementation/core-config.options-and-middleware.integration.test.ts

+24-26
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,33 @@ vi.mock('@code-pushup/core', async () => {
1414
const core = await vi.importActual('@code-pushup/core');
1515
return {
1616
...(core as object),
17-
readCodePushupConfig: vi
18-
.fn()
19-
.mockImplementation((filepath: string): CoreConfig => {
20-
const allPersistOptions = {
21-
...CORE_CONFIG_MOCK,
22-
persist: {
23-
filename: 'rc-filename',
24-
format: ['json', 'md'],
25-
outputDir: 'rc-outputDir',
26-
},
27-
};
17+
readRcByPath: vi.fn().mockImplementation((filepath: string): CoreConfig => {
18+
const allPersistOptions = {
19+
...CORE_CONFIG_MOCK,
20+
persist: {
21+
filename: 'rc-filename',
22+
format: ['json', 'md'],
23+
outputDir: 'rc-outputDir',
24+
},
25+
};
2826

29-
const persistOnlyFilename = {
30-
...CORE_CONFIG_MOCK,
31-
persist: {
32-
filename: 'rc-filename',
33-
},
34-
};
27+
const persistOnlyFilename = {
28+
...CORE_CONFIG_MOCK,
29+
persist: {
30+
filename: 'rc-filename',
31+
},
32+
};
3533

36-
const noPersistFilename = CORE_CONFIG_MOCK;
34+
const noPersistFilename = CORE_CONFIG_MOCK;
3735

38-
return filepath.includes('all-persist-options')
39-
? allPersistOptions
40-
: filepath.includes('no-persist')
41-
? noPersistFilename
42-
: filepath.includes('persist-only-filename')
43-
? persistOnlyFilename
44-
: CORE_CONFIG_MOCK;
45-
}),
36+
return filepath.includes('all-persist-options')
37+
? allPersistOptions
38+
: filepath.includes('no-persist')
39+
? noPersistFilename
40+
: filepath.includes('persist-only-filename')
41+
? persistOnlyFilename
42+
: CORE_CONFIG_MOCK;
43+
}),
4644
};
4745
});
4846

packages/cli/src/lib/implementation/global.options.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ export function yargsGlobalOptionsDefinition(): Record<
1818
default: false,
1919
},
2020
config: {
21-
describe: 'Path the the config file, e.g. code-pushup.config.js',
21+
describe:
22+
'Path the the config file, e.g. code-pushup.config.ts. By default it loads code-pushup.config.(ts|mjs|js).',
2223
type: 'string',
23-
default: 'code-pushup.config.js',
2424
},
2525
};
2626
}

packages/cli/src/lib/implementation/only-plugins.options.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { Options } from 'yargs';
2+
import { coerceArray } from './global.utils';
23

34
export const onlyPluginsOption: Options = {
45
describe: 'List of plugins to run. If not set all plugins are run.',
56
type: 'array',
67
default: [],
7-
coerce: (arg: string[]) => arg.flatMap(v => v.split(',')),
8+
coerce: coerceArray,
89
};
910

1011
export function yargsOnlyPluginsOptionsDefinition(): Record<

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ vi.mock('@code-pushup/core', async () => {
99
const core: object = await vi.importActual('@code-pushup/core');
1010
return {
1111
...core,
12-
readCodePushupConfig: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
12+
readRcByPath: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
1313
};
1414
});
1515

packages/cli/src/lib/upload/upload-command.unit.test.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { vol } from 'memfs';
22
import { describe, expect, it } from 'vitest';
33
import { PortalUploadArgs, uploadToPortal } from '@code-pushup/portal-client';
4-
import { readCodePushupConfig } from '@code-pushup/core';
4+
import { readRcByPath } from '@code-pushup/core';
55
import {
66
ISO_STRING_REGEXP,
77
MEMFS_VOLUME,
@@ -17,7 +17,7 @@ vi.mock('@code-pushup/core', async () => {
1717
const core: object = await vi.importActual('@code-pushup/core');
1818
return {
1919
...core,
20-
readCodePushupConfig: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
20+
readRcByPath: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
2121
};
2222
});
2323

@@ -46,11 +46,9 @@ describe('upload-command-object', () => {
4646
},
4747
).parseAsync();
4848

49-
expect(readCodePushupConfig).toHaveBeenCalledWith(
50-
'/test/code-pushup.config.ts',
51-
);
49+
expect(readRcByPath).toHaveBeenCalledWith('/test/code-pushup.config.ts');
5250

53-
// values come from CORE_CONFIG_MOCK returned by readCodePushupConfig mock
51+
// values come from CORE_CONFIG_MOCK returned by readRcByPath mock
5452
expect(uploadToPortal).toHaveBeenCalledWith({
5553
apiKey: 'dummy-api-key',
5654
server: 'https://example.com/api',

packages/cli/src/lib/yargs-cli.integration.test.ts

-8
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@ describe('yargsCli', () => {
1616
expect(parsedArgv.progress).toBe(true);
1717
});
1818

19-
it('should provide a correct default value for the config', async () => {
20-
const parsedArgv = await yargsCli<GeneralCliOptions>([], {
21-
options,
22-
}).parseAsync();
23-
expect(parsedArgv.config).toBe('code-pushup.config.js');
24-
});
25-
2619
it('should parse an empty array as a default onlyPlugins option', async () => {
2720
const parsedArgv = await yargsCli<GeneralCliOptions & OnlyPluginsOptions>(
2821
[],
@@ -102,7 +95,6 @@ describe('yargsCli', () => {
10295
expect.objectContaining({
10396
// default values
10497
progress: true,
105-
config: expect.stringContaining('code-pushup.config'),
10698
// overridden arguments
10799
verbose: true,
108100
persist: expect.objectContaining({

packages/core/src/index.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export { readCodePushupConfig } from './lib/implementation/read-code-pushup-config';
12
export {
23
persistReport,
34
PersistError,
@@ -16,6 +17,7 @@ export {
1617
CollectAndPersistReportsOptions,
1718
} from './lib/collect-and-persist';
1819
export {
19-
readCodePushupConfig,
20+
autoloadRc,
21+
readRcByPath,
2022
ConfigPathError,
21-
} from './lib/implementation/read-code-pushup-config';
23+
} from './lib/implementation/read-rc-file';

0 commit comments

Comments
 (0)