Skip to content

Commit fcb7aa3

Browse files
committed
feat(models,core): make runner args extensible by nesting persist config
1 parent 4c96419 commit fcb7aa3

File tree

10 files changed

+94
-74
lines changed

10 files changed

+94
-74
lines changed

packages/core/src/lib/implementation/collect.ts

Lines changed: 8 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
import { createRequire } from 'node:module';
2-
import {
3-
type CacheConfigObject,
4-
type CoreConfig,
5-
DEFAULT_PERSIST_OUTPUT_DIR,
6-
type PersistConfig,
7-
type Report,
2+
import type {
3+
CacheConfigObject,
4+
CoreConfig,
5+
PersistConfig,
6+
Report,
87
} from '@code-pushup/models';
98
import { calcDuration, getLatestCommit } from '@code-pushup/utils';
109
import type { GlobalOptions } from '../types.js';
1110
import { executePlugins } from './execute-plugin.js';
1211

1312
export type CollectOptions = Pick<CoreConfig, 'plugins' | 'categories'> & {
14-
persist?: Required<Pick<PersistConfig, 'outputDir'>>;
13+
persist?: PersistConfig;
1514
cache: CacheConfigObject;
1615
} & Partial<GlobalOptions>;
1716

@@ -20,16 +19,12 @@ export type CollectOptions = Pick<CoreConfig, 'plugins' | 'categories'> & {
2019
* @param options
2120
*/
2221
export async function collect(options: CollectOptions): Promise<Report> {
23-
const { plugins, categories, persist, cache, ...otherOptions } = options;
22+
const { plugins, categories, persist = {}, cache, ...otherOptions } = options;
2423
const date = new Date().toISOString();
2524
const start = performance.now();
2625
const commit = await getLatestCommit();
2726
const pluginOutputs = await executePlugins(
28-
{
29-
plugins,
30-
persist: { outputDir: DEFAULT_PERSIST_OUTPUT_DIR, ...persist },
31-
cache,
32-
},
27+
{ plugins, persist, cache },
3328
otherOptions,
3429
);
3530
const packageJson = createRequire(import.meta.url)(

packages/core/src/lib/implementation/execute-plugin.ts

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { bold } from 'ansis';
2-
import type {
3-
Audit,
4-
AuditOutput,
5-
AuditReport,
6-
CacheConfigObject,
7-
PersistConfig,
8-
PluginConfig,
9-
PluginReport,
2+
import {
3+
type AuditOutput,
4+
type AuditReport,
5+
type CacheConfigObject,
6+
DEFAULT_PERSIST_CONFIG,
7+
type PersistConfig,
8+
type PluginConfig,
9+
type PluginReport,
10+
type RunnerArgs,
1011
} from '@code-pushup/models';
1112
import {
1213
type ProgressBar,
@@ -48,10 +49,9 @@ export async function executePlugin(
4849
pluginConfig: PluginConfig,
4950
opt: {
5051
cache: CacheConfigObject;
51-
persist: Required<Pick<PersistConfig, 'outputDir'>>;
52+
persist: PersistConfig;
5253
},
5354
): Promise<PluginReport> {
54-
const { cache, persist } = opt;
5555
const {
5656
runner,
5757
audits: pluginConfigAudits,
@@ -61,15 +61,19 @@ export async function executePlugin(
6161
scoreTargets,
6262
...pluginMeta
6363
} = pluginConfig;
64-
const { write: cacheWrite = false, read: cacheRead = false } = cache;
65-
const { outputDir } = persist;
64+
const { write: cacheWrite = false, read: cacheRead = false } = opt.cache;
65+
66+
const args: RunnerArgs = {
67+
persist: { ...DEFAULT_PERSIST_CONFIG, ...opt.persist },
68+
};
69+
const { outputDir } = args.persist;
6670

6771
const { audits, ...executionMeta } = cacheRead
6872
? // IF not null, take the result from cache
6973
((await readRunnerResults(pluginMeta.slug, outputDir)) ??
7074
// ELSE execute the plugin runner
71-
(await executePluginRunner(pluginConfig, persist)))
72-
: await executePluginRunner(pluginConfig, persist);
75+
(await executePluginRunner(pluginConfig, args)))
76+
: await executePluginRunner(pluginConfig, args);
7377

7478
if (cacheWrite) {
7579
await writeRunnerResults(pluginMeta.slug, outputDir, {
@@ -87,9 +91,8 @@ export async function executePlugin(
8791
const auditReports: AuditReport[] = scoredAuditsWithTarget.map(
8892
(auditOutput: AuditOutput) => ({
8993
...auditOutput,
90-
...(pluginConfigAudits.find(
91-
audit => audit.slug === auditOutput.slug,
92-
) as Audit),
94+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
95+
...pluginConfigAudits.find(audit => audit.slug === auditOutput.slug)!,
9396
}),
9497
);
9598

@@ -107,7 +110,7 @@ export async function executePlugin(
107110
const wrapProgress = async (
108111
cfg: {
109112
plugin: PluginConfig;
110-
persist: Required<Pick<PersistConfig, 'outputDir'>>;
113+
persist: PersistConfig;
111114
cache: CacheConfigObject;
112115
},
113116
steps: number,
@@ -155,7 +158,7 @@ const wrapProgress = async (
155158
export async function executePlugins(
156159
cfg: {
157160
plugins: PluginConfig[];
158-
persist: Required<Pick<PersistConfig, 'outputDir'>>;
161+
persist: PersistConfig;
159162
cache: CacheConfigObject;
160163
},
161164
options?: { progress?: boolean },

packages/core/src/lib/implementation/execute-plugin.unit.test.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { bold } from 'ansis';
22
import { vol } from 'memfs';
33
import { describe, expect, it, vi } from 'vitest';
4-
import type { AuditOutputs, PluginConfig } from '@code-pushup/models';
4+
import {
5+
type AuditOutputs,
6+
DEFAULT_PERSIST_CONFIG,
7+
type PluginConfig,
8+
} from '@code-pushup/models';
59
import {
610
MEMFS_VOLUME,
711
MINIMAL_PLUGIN_CONFIG_MOCK,
@@ -26,7 +30,7 @@ describe('executePlugin', () => {
2630

2731
await expect(
2832
executePlugin(MINIMAL_PLUGIN_CONFIG_MOCK, {
29-
persist: { outputDir: '' },
33+
persist: {},
3034
cache: { read: false, write: false },
3135
}),
3236
).resolves.toStrictEqual({
@@ -47,7 +51,7 @@ describe('executePlugin', () => {
4751

4852
expect(executePluginRunnerSpy).toHaveBeenCalledWith(
4953
MINIMAL_PLUGIN_CONFIG_MOCK,
50-
{ outputDir: '' },
54+
{ persist: DEFAULT_PERSIST_CONFIG },
5155
);
5256
});
5357

@@ -132,7 +136,7 @@ describe('executePlugin', () => {
132136

133137
expect(executePluginRunnerSpy).toHaveBeenCalledWith(
134138
MINIMAL_PLUGIN_CONFIG_MOCK,
135-
{ outputDir: MEMFS_VOLUME },
139+
{ persist: { ...DEFAULT_PERSIST_CONFIG, outputDir: MEMFS_VOLUME } },
136140
);
137141
});
138142

packages/core/src/lib/implementation/runner.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import { writeFile } from 'node:fs/promises';
33
import path from 'node:path';
44
import {
55
type AuditOutputs,
6-
type PersistConfig,
76
type PluginConfig,
7+
type RunnerArgs,
88
type RunnerConfig,
99
type RunnerFunction,
1010
auditOutputsSchema,
@@ -33,14 +33,14 @@ export type ValidatedRunnerResult = Omit<RunnerResult, 'audits'> & {
3333
};
3434

3535
export async function executeRunnerConfig(
36-
cfg: RunnerConfig,
37-
config: Required<Pick<PersistConfig, 'outputDir'>>,
36+
config: RunnerConfig,
37+
args: RunnerArgs,
3838
): Promise<RunnerResult> {
39-
const { args, command, outputFile, outputTransform } = cfg;
39+
const { outputFile, outputTransform } = config;
4040

4141
const { duration, date } = await executeProcess({
42-
command,
43-
args: [...(args ?? []), ...objectToCliArgs(config)],
42+
command: config.command,
43+
args: [...(config.args ?? []), ...objectToCliArgs(args)],
4444
observer: {
4545
onStdout: stdout => {
4646
if (isVerbose()) {
@@ -69,13 +69,13 @@ export async function executeRunnerConfig(
6969

7070
export async function executeRunnerFunction(
7171
runner: RunnerFunction,
72-
config: PersistConfig,
72+
args: RunnerArgs,
7373
): Promise<RunnerResult> {
7474
const date = new Date().toISOString();
7575
const start = performance.now();
7676

7777
// execute plugin runner
78-
const audits = await runner(config);
78+
const audits = await runner(args);
7979

8080
// create runner result
8181
return {
@@ -100,13 +100,13 @@ export class AuditOutputsMissingAuditError extends Error {
100100

101101
export async function executePluginRunner(
102102
pluginConfig: Pick<PluginConfig, 'audits' | 'runner'>,
103-
persist: Required<Pick<PersistConfig, 'outputDir'>>,
103+
args: RunnerArgs,
104104
): Promise<Omit<RunnerResult, 'audits'> & { audits: AuditOutputs }> {
105105
const { audits: pluginConfigAudits, runner } = pluginConfig;
106106
const runnerResult: RunnerResult =
107107
typeof runner === 'object'
108-
? await executeRunnerConfig(runner, persist)
109-
: await executeRunnerFunction(runner, persist);
108+
? await executeRunnerConfig(runner, args)
109+
: await executeRunnerFunction(runner, args);
110110
const { audits: unvalidatedAuditOutputs, ...executionMeta } = runnerResult;
111111

112112
const result = auditOutputsSchema.safeParse(unvalidatedAuditOutputs);

packages/models/docs/models-reference.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1404,9 +1404,21 @@ _Object containing the following properties:_
14041404

14051405
_(\*) Required._
14061406

1407+
## RunnerArgs
1408+
1409+
Arguments passed to runner
1410+
1411+
_Object containing the following properties:_
1412+
1413+
| Property | Description | Type |
1414+
| :----------------- | :----------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1415+
| **`persist`** (\*) | Persist config with defaults applied | _Object with properties:_<ul><li>**`outputDir`** (\*): `string` (_min length: 1_)</li><li>**`filename`** (\*): `string` (_regex: `/^(?!.*[ \\/:*?"<>\|]).+$/`, min length: 1_)</li><li>**`format`** (\*): _Array of [Format](#format) items_</li><li>**`skipReports`** (\*): `boolean`</li></ul> |
1416+
1417+
_(\*) Required._
1418+
14071419
## RunnerConfig
14081420

1409-
How to execute runner
1421+
How to execute runner using shell script
14101422

14111423
_Object containing the following properties:_
14121424

@@ -1433,11 +1445,13 @@ _(\*) Required._
14331445

14341446
## RunnerFunction
14351447

1448+
Callback function for async runner execution in JS/TS
1449+
14361450
_Function._
14371451

14381452
_Parameters:_
14391453

1440-
1. [PersistConfig](#persistconfig)
1454+
1. [RunnerArgs](#runnerargs)
14411455

14421456
_Returns:_
14431457

packages/models/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export {
5353
DEFAULT_PERSIST_FILENAME,
5454
DEFAULT_PERSIST_FORMAT,
5555
DEFAULT_PERSIST_OUTPUT_DIR,
56+
DEFAULT_PERSIST_SKIP_REPORT,
5657
} from './lib/implementation/constants.js';
5758
export {
5859
MAX_DESCRIPTION_LENGTH,
@@ -117,9 +118,11 @@ export {
117118
type ReportsDiff,
118119
} from './lib/reports-diff.js';
119120
export {
121+
runnerArgsSchema,
120122
runnerConfigSchema,
121123
runnerFilesPathsSchema,
122124
runnerFunctionSchema,
125+
type RunnerArgs,
123126
type RunnerConfig,
124127
type RunnerFilesPaths,
125128
type RunnerFunction,

packages/models/src/lib/runner-config.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,15 @@ export const outputTransformSchema = convertAsyncZodFunctionToSchema(
1212
);
1313
export type OutputTransform = z.infer<typeof outputTransformSchema>;
1414

15+
export const runnerArgsSchema = z
16+
.object({
17+
persist: persistConfigSchema
18+
.required()
19+
.describe('Persist config with defaults applied'),
20+
})
21+
.describe('Arguments passed to runner');
22+
export type RunnerArgs = z.infer<typeof runnerArgsSchema>;
23+
1524
export const runnerConfigSchema = z
1625
.object({
1726
command: z.string().describe('Shell command to execute'),
@@ -20,22 +29,19 @@ export const runnerConfigSchema = z
2029
outputTransform: outputTransformSchema.optional(),
2130
configFile: filePathSchema.describe('Runner config path').optional(),
2231
})
23-
.describe('How to execute runner');
24-
32+
.describe('How to execute runner using shell script');
2533
export type RunnerConfig = z.infer<typeof runnerConfigSchema>;
2634

2735
export const runnerFunctionSchema = convertAsyncZodFunctionToSchema(
2836
z.function({
29-
input: [persistConfigSchema],
37+
input: [runnerArgsSchema],
3038
output: z.union([auditOutputsSchema, z.promise(auditOutputsSchema)]),
3139
}),
32-
);
33-
40+
).describe('Callback function for async runner execution in JS/TS');
3441
export type RunnerFunction = z.infer<typeof runnerFunctionSchema>;
3542

3643
export const runnerFilesPathsSchema = z.object({
3744
runnerConfigPath: filePathSchema.describe('Runner config path'),
3845
runnerOutputPath: filePathSchema.describe('Runner output path'),
3946
});
40-
4147
export type RunnerFilesPaths = z.infer<typeof runnerFilesPathsSchema>;

packages/plugin-eslint/src/lib/runner.int.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
type Audit,
88
type AuditOutput,
99
type AuditOutputs,
10-
DEFAULT_PERSIST_OUTPUT_DIR,
10+
DEFAULT_PERSIST_CONFIG,
1111
type Issue,
1212
} from '@code-pushup/models';
1313
import { osAgnosticAuditOutputs } from '@code-pushup/test-utils';
@@ -50,9 +50,9 @@ describe('executeRunner', () => {
5050

5151
it('should execute ESLint and create audit results for React application', async () => {
5252
const args = await prepareRunnerArgs('eslint.config.js');
53-
const runnerFn = await createRunnerFunction(args);
53+
const runnerFn = createRunnerFunction(args);
5454
const res = (await runnerFn({
55-
outputDir: DEFAULT_PERSIST_OUTPUT_DIR,
55+
persist: DEFAULT_PERSIST_CONFIG,
5656
})) as AuditOutputs;
5757
expect(osAgnosticAuditOutputs(res)).toMatchSnapshot();
5858
});
@@ -61,11 +61,11 @@ describe('executeRunner', () => {
6161
'should execute runner with custom config using @code-pushup/eslint-config',
6262
async () => {
6363
const eslintTarget = 'code-pushup.eslint.config.mjs';
64-
const runnerFn = await createRunnerFunction({
64+
const runnerFn = createRunnerFunction({
6565
...(await prepareRunnerArgs(eslintTarget)),
6666
});
6767

68-
const json = await runnerFn({ outputDir: DEFAULT_PERSIST_OUTPUT_DIR });
68+
const json = await runnerFn({ persist: DEFAULT_PERSIST_CONFIG });
6969
// expect warnings from unicorn/filename-case rule from default config
7070
expect(json).toContainEqual(
7171
expect.objectContaining<Partial<AuditOutput>>({

packages/plugin-eslint/src/lib/runner/index.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import type {
22
Audit,
33
AuditOutput,
44
AuditOutputs,
5-
PersistConfig,
65
PluginArtifactOptions,
76
RunnerFunction,
87
} from '@code-pushup/models';
@@ -23,15 +22,12 @@ export function createRunnerFunction(options: {
2322
slugs: audits.map(audit => audit.slug),
2423
};
2524

26-
return async ({ outputDir }: PersistConfig): Promise<AuditOutputs> => {
25+
return async (): Promise<AuditOutputs> => {
2726
ui().logger.log(`ESLint plugin executing ${targets.length} lint targets`);
2827

2928
const linterOutputs = artifacts
3029
? await loadArtifacts(artifacts)
31-
: await asyncSequential(
32-
targets.map(target => ({ ...target, outputDir })),
33-
lint,
34-
);
30+
: await asyncSequential(targets, lint);
3531
const lintResults = mergeLinterOutputs(linterOutputs);
3632
const failedAudits = lintResultsToAudits(lintResults);
3733

0 commit comments

Comments
 (0)