Skip to content

Commit

Permalink
feat(cli): add autoloading of the config file (#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
BioPhoton committed Jan 26, 2024
1 parent 1cd3cda commit aef86c9
Show file tree
Hide file tree
Showing 19 changed files with 350 additions and 84 deletions.
5 changes: 3 additions & 2 deletions e2e/cli-e2e/tests/__snapshots__/help.e2e.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ Options:
[boolean] [default: true]
--verbose When true creates more verbose output. This is help
ful when debugging. [boolean] [default: false]
--config Path the the config file, e.g. code-pushup.config.j
s [string] [default: \\"code-pushup.config.js\\"]
--config Path the the config file, e.g. code-pushup.config.t
s. By default it loads code-pushup.config.(ts|mjs|j
s). [string]
--persist.outputDir Directory for the produced reports [string]
--persist.filename Filename for the produced reports. [string]
--persist.format Format of the report output. e.g. \`md\`, \`json\`
Expand Down
9 changes: 4 additions & 5 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ _If you're looking for programmatic usage, then refer to the underlying [@code-p

```js
export default {
persist: {
outputDir: '.code-pushup',
format: ['json', 'md'],
},
plugins: [
// ...
],
Expand Down Expand Up @@ -154,7 +150,10 @@ Each example is fully tested to demonstrate best practices for plugin testing as
| ---------------- | --------- | ----------------------- | ---------------------------------------------------------------------- |
| **`--progress`** | `boolean` | `true` | Show progress bar in stdout. |
| **`--verbose`** | `boolean` | `false` | When true creates more verbose output. This is helpful when debugging. |
| **`--config`** | `string` | `code-pushup.config.js` | Path to the config file, e.g. code-pushup.config.js |
| **`--config`** | `string` | `code-pushup.config.ts` | Path to the config file, e.g. code-pushup.config.(ts\|mjs\|js) |

> [!NOTE]
> By default, the CLI loads `code-pushup.config.(ts|mjs|js)` if no config path is provided with `--config`.
### Common Command Options

Expand Down
13 changes: 4 additions & 9 deletions packages/cli/src/lib/autorun/autorun-command.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { vol } from 'memfs';
import { describe, expect, it, vi } from 'vitest';
import { PortalUploadArgs, uploadToPortal } from '@code-pushup/portal-client';
import {
collectAndPersistReports,
readCodePushupConfig,
} from '@code-pushup/core';
import { collectAndPersistReports, readRcByPath } from '@code-pushup/core';
import { MEMFS_VOLUME, MINIMAL_REPORT_MOCK } from '@code-pushup/testing-utils';
import { DEFAULT_CLI_CONFIGURATION } from '../../../mocks/constants';
import { yargsCli } from '../yargs-cli';
Expand All @@ -17,7 +14,7 @@ vi.mock('@code-pushup/core', async () => {
return {
...core,
collectAndPersistReports: vi.fn().mockResolvedValue({}),
readCodePushupConfig: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
readRcByPath: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
};
});

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

expect(readCodePushupConfig).toHaveBeenCalledWith(
'/test/code-pushup.config.ts',
);
expect(readRcByPath).toHaveBeenCalledWith('/test/code-pushup.config.ts');

expect(collectAndPersistReports).toHaveBeenCalledWith(
expect.objectContaining({
Expand All @@ -59,7 +54,7 @@ describe('autorun-command', () => {
}),
);

// values come from CORE_CONFIG_MOCK returned by readCodePushupConfig mock
// values come from CORE_CONFIG_MOCK returned by readRcByPath mock
expect(uploadToPortal).toHaveBeenCalledWith({
apiKey: 'dummy-api-key',
server: 'https://example.com/api',
Expand Down
19 changes: 5 additions & 14 deletions packages/cli/src/lib/collect/collect-command.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import { describe, expect, it, vi } from 'vitest';
import {
collectAndPersistReports,
readCodePushupConfig,
} from '@code-pushup/core';
import { collectAndPersistReports, readRcByPath } from '@code-pushup/core';
import { DEFAULT_CLI_CONFIGURATION } from '../../../mocks/constants';
import { yargsCli } from '../yargs-cli';
import { yargsCollectCommandObject } from './collect-command';
Expand All @@ -14,7 +11,7 @@ vi.mock('@code-pushup/core', async () => {
return {
...core,
collectAndPersistReports: vi.fn().mockResolvedValue({}),
readCodePushupConfig: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
readRcByPath: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
};
});

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

expect(readCodePushupConfig).toHaveBeenCalledWith(
'/test/code-pushup.config.ts',
);
expect(readRcByPath).toHaveBeenCalledWith('/test/code-pushup.config.ts');

expect(collectAndPersistReports).toHaveBeenCalledWith(
expect.objectContaining({
Expand Down Expand Up @@ -57,9 +52,7 @@ describe('collect-command', () => {
},
).parseAsync();

expect(readCodePushupConfig).toHaveBeenCalledWith(
'/test/code-pushup.config.ts',
);
expect(readRcByPath).toHaveBeenCalledWith('/test/code-pushup.config.ts');

expect(collectAndPersistReports).toHaveBeenCalledWith(
expect.objectContaining({
Expand All @@ -86,9 +79,7 @@ describe('collect-command', () => {
},
).parseAsync();

expect(readCodePushupConfig).toHaveBeenCalledWith(
'/test/code-pushup.config.ts',
);
expect(readRcByPath).toHaveBeenCalledWith('/test/code-pushup.config.ts');

expect(collectAndPersistReports).toHaveBeenCalledWith(
expect.objectContaining({
Expand Down
7 changes: 4 additions & 3 deletions packages/cli/src/lib/implementation/core-config.middleware.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { readCodePushupConfig } from '@code-pushup/core';
import { autoloadRc, readRcByPath } from '@code-pushup/core';
import {
CoreConfig,
PERSIST_FILENAME,
Expand All @@ -17,12 +17,13 @@ export async function coreConfigMiddleware<
upload: cliUpload,
...remainingCliOptions
} = args as GeneralCliOptions & Required<CoreConfig>;
const rcOptions = await readCodePushupConfig(config);
// if config path is given use it otherwise auto-load
const importedRc = config ? await readRcByPath(config) : await autoloadRc();
const {
persist: rcPersist,
upload: rcUpload,
...remainingRcConfig
} = rcOptions;
} = importedRc;

const parsedProcessArgs: CoreConfig & GeneralCliOptions = {
config,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { describe, expect, vi } from 'vitest';
import { ConfigPathError } from '@code-pushup/core';
import { CoreConfig } from '@code-pushup/models';
import { CORE_CONFIG_MOCK } from '@code-pushup/testing-utils';
import { coreConfigMiddleware } from './core-config.middleware';

vi.mock('@code-pushup/core', async () => {
const core: object = await vi.importActual('@code-pushup/core');
return {
...core,
readRcByPath: vi.fn().mockImplementation((filepath: string): CoreConfig => {
const extension = filepath.split('.');
if (filepath.includes('throw-error')) {
throw new ConfigPathError(filepath);
}
return {
...CORE_CONFIG_MOCK,
upload: {
...CORE_CONFIG_MOCK.upload,
project: `cli-${extension}`,
},
};
}),
autoloadRc: vi.fn().mockImplementation(
(): CoreConfig => ({
...CORE_CONFIG_MOCK,
upload: {
...CORE_CONFIG_MOCK.upload,
project: `cli-autoload`,
},
}),
),
};
});

describe('coreConfigMiddleware', () => {
it('should load code-pushup.config.(ts|mjs|js) by default', async () => {
const config = await coreConfigMiddleware({});
expect(config?.upload?.project).toBe('cli-autoload');
});

it.each(['ts', 'mjs', 'js'])(
'should load a valid .%s config',
async extension => {
const config = await coreConfigMiddleware({
config: `code-pushup.config.${extension}`,
});
expect(config.config).toContain(`code-pushup.config.${extension}`);
expect(config.upload?.project).toContain(extension);
},
);

it('should throw with invalid config path', async () => {
await expect(
coreConfigMiddleware({ config: 'throw-error' }),
).rejects.toThrow(/Provided path .* is not valid./);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,33 @@ vi.mock('@code-pushup/core', async () => {
const core = await vi.importActual('@code-pushup/core');
return {
...(core as object),
readCodePushupConfig: vi
.fn()
.mockImplementation((filepath: string): CoreConfig => {
const allPersistOptions = {
...CORE_CONFIG_MOCK,
persist: {
filename: 'rc-filename',
format: ['json', 'md'],
outputDir: 'rc-outputDir',
},
};
readRcByPath: vi.fn().mockImplementation((filepath: string): CoreConfig => {
const allPersistOptions = {
...CORE_CONFIG_MOCK,
persist: {
filename: 'rc-filename',
format: ['json', 'md'],
outputDir: 'rc-outputDir',
},
};

const persistOnlyFilename = {
...CORE_CONFIG_MOCK,
persist: {
filename: 'rc-filename',
},
};
const persistOnlyFilename = {
...CORE_CONFIG_MOCK,
persist: {
filename: 'rc-filename',
},
};

const noPersistFilename = CORE_CONFIG_MOCK;
const noPersistFilename = CORE_CONFIG_MOCK;

return filepath.includes('all-persist-options')
? allPersistOptions
: filepath.includes('no-persist')
? noPersistFilename
: filepath.includes('persist-only-filename')
? persistOnlyFilename
: CORE_CONFIG_MOCK;
}),
return filepath.includes('all-persist-options')
? allPersistOptions
: filepath.includes('no-persist')
? noPersistFilename
: filepath.includes('persist-only-filename')
? persistOnlyFilename
: CORE_CONFIG_MOCK;
}),
};
});

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/lib/implementation/global.options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ export function yargsGlobalOptionsDefinition(): Record<
default: false,
},
config: {
describe: 'Path the the config file, e.g. code-pushup.config.js',
describe:
'Path the the config file, e.g. code-pushup.config.ts. By default it loads code-pushup.config.(ts|mjs|js).',
type: 'string',
default: 'code-pushup.config.js',
},
};
}
3 changes: 2 additions & 1 deletion packages/cli/src/lib/implementation/only-plugins.options.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Options } from 'yargs';
import { coerceArray } from './global.utils';

export const onlyPluginsOption: Options = {
describe: 'List of plugins to run. If not set all plugins are run.',
type: 'array',
default: [],
coerce: (arg: string[]) => arg.flatMap(v => v.split(',')),
coerce: coerceArray,
};

export function yargsOnlyPluginsOptionsDefinition(): Record<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ vi.mock('@code-pushup/core', async () => {
const core: object = await vi.importActual('@code-pushup/core');
return {
...core,
readCodePushupConfig: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
readRcByPath: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
};
});

Expand Down
10 changes: 4 additions & 6 deletions packages/cli/src/lib/upload/upload-command.unit.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { vol } from 'memfs';
import { describe, expect, it } from 'vitest';
import { PortalUploadArgs, uploadToPortal } from '@code-pushup/portal-client';
import { readCodePushupConfig } from '@code-pushup/core';
import { readRcByPath } from '@code-pushup/core';
import {
ISO_STRING_REGEXP,
MEMFS_VOLUME,
Expand All @@ -17,7 +17,7 @@ vi.mock('@code-pushup/core', async () => {
const core: object = await vi.importActual('@code-pushup/core');
return {
...core,
readCodePushupConfig: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
readRcByPath: vi.fn().mockResolvedValue(CORE_CONFIG_MOCK),
};
});

Expand Down Expand Up @@ -46,11 +46,9 @@ describe('upload-command-object', () => {
},
).parseAsync();

expect(readCodePushupConfig).toHaveBeenCalledWith(
'/test/code-pushup.config.ts',
);
expect(readRcByPath).toHaveBeenCalledWith('/test/code-pushup.config.ts');

// values come from CORE_CONFIG_MOCK returned by readCodePushupConfig mock
// values come from CORE_CONFIG_MOCK returned by readRcByPath mock
expect(uploadToPortal).toHaveBeenCalledWith({
apiKey: 'dummy-api-key',
server: 'https://example.com/api',
Expand Down
8 changes: 0 additions & 8 deletions packages/cli/src/lib/yargs-cli.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,6 @@ describe('yargsCli', () => {
expect(parsedArgv.progress).toBe(true);
});

it('should provide a correct default value for the config', async () => {
const parsedArgv = await yargsCli<GeneralCliOptions>([], {
options,
}).parseAsync();
expect(parsedArgv.config).toBe('code-pushup.config.js');
});

it('should parse an empty array as a default onlyPlugins option', async () => {
const parsedArgv = await yargsCli<GeneralCliOptions & OnlyPluginsOptions>(
[],
Expand Down Expand Up @@ -102,7 +95,6 @@ describe('yargsCli', () => {
expect.objectContaining({
// default values
progress: true,
config: expect.stringContaining('code-pushup.config'),
// overridden arguments
verbose: true,
persist: expect.objectContaining({
Expand Down
6 changes: 4 additions & 2 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { readCodePushupConfig } from './lib/implementation/read-code-pushup-config';
export {
persistReport,
PersistError,
Expand All @@ -16,6 +17,7 @@ export {
CollectAndPersistReportsOptions,
} from './lib/collect-and-persist';
export {
readCodePushupConfig,
autoloadRc,
readRcByPath,
ConfigPathError,
} from './lib/implementation/read-code-pushup-config';
} from './lib/implementation/read-rc-file';
Loading

0 comments on commit aef86c9

Please sign in to comment.