Skip to content

Commit 10d3b9a

Browse files
committed
feat: add audit output caching for execute plugin
1 parent d32a643 commit 10d3b9a

File tree

9 files changed

+623
-245
lines changed

9 files changed

+623
-245
lines changed

packages/core/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ export type { ReportsToCompare } from './lib/implementation/compare-scorables.js
1717
export {
1818
executePlugin,
1919
executePlugins,
20-
PluginOutputMissingAuditError,
2120
} from './lib/implementation/execute-plugin.js';
21+
export { AuditOutputsMissingAuditError } from './lib/implementation/runner.js';
2222
export {
2323
PersistDirError,
2424
PersistError,

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

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,36 @@
11
import { createRequire } from 'node:module';
2-
import type { CoreConfig, Report } from '@code-pushup/models';
2+
import {
3+
type CoreConfig,
4+
DEFAULT_PERSIST_OUTPUT_DIR,
5+
type PersistConfig,
6+
type Report,
7+
} from '@code-pushup/models';
38
import { calcDuration, getLatestCommit } from '@code-pushup/utils';
49
import type { GlobalOptions } from '../types.js';
510
import { executePlugins } from './execute-plugin.js';
611

7-
export type CollectOptions = Pick<CoreConfig, 'plugins' | 'categories'> &
8-
Partial<GlobalOptions>;
12+
export type CollectOptions = Pick<CoreConfig, 'plugins' | 'categories'> & {
13+
persist?: Required<Pick<PersistConfig, 'outputDir'>>;
14+
} & Partial<GlobalOptions>;
915

1016
/**
1117
* Run audits, collect plugin output and aggregate it into a JSON object
1218
* @param options
1319
*/
1420
export async function collect(options: CollectOptions): Promise<Report> {
15-
const { plugins, categories } = options;
21+
const { plugins, categories, persist, ...otherOptions } = options;
1622
const date = new Date().toISOString();
1723
const start = performance.now();
1824
const commit = await getLatestCommit();
19-
const pluginOutputs = await executePlugins(plugins, options);
25+
const pluginOutputs = await executePlugins(
26+
{
27+
plugins,
28+
persist: { ...persist, outputDir: DEFAULT_PERSIST_OUTPUT_DIR },
29+
// implement together with CLI option
30+
cache: {},
31+
},
32+
otherOptions,
33+
);
2034
const packageJson = createRequire(import.meta.url)(
2135
'../../../package.json',
2236
) as typeof import('../../../package.json');

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

Lines changed: 53 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import { bold } from 'ansis';
2-
import {
3-
type Audit,
4-
type AuditOutput,
5-
type AuditOutputs,
6-
type AuditReport,
7-
type PluginConfig,
8-
type PluginReport,
9-
auditOutputsSchema,
2+
import type {
3+
Audit,
4+
AuditOutput,
5+
AuditReport,
6+
CacheConfig,
7+
PersistConfig,
8+
PluginConfig,
9+
PluginReport,
1010
} from '@code-pushup/models';
1111
import {
1212
type ProgressBar,
@@ -15,29 +15,19 @@ import {
1515
logMultipleResults,
1616
pluralizeToken,
1717
} from '@code-pushup/utils';
18-
import { normalizeAuditOutputs } from '../normalize.js';
19-
import { executeRunnerConfig, executeRunnerFunction } from './runner.js';
20-
21-
/**
22-
* Error thrown when plugin output is invalid.
23-
*/
24-
export class PluginOutputMissingAuditError extends Error {
25-
constructor(auditSlug: string) {
26-
super(
27-
`Audit metadata not present in plugin config. Missing slug: ${bold(
28-
auditSlug,
29-
)}`,
30-
);
31-
}
32-
}
18+
import {
19+
executePluginRunner,
20+
readRunnerResults,
21+
writeRunnerResults,
22+
} from './runner.js';
3323

3424
/**
3525
* Execute a plugin.
3626
*
3727
* @public
3828
* @param pluginConfig - {@link ProcessConfig} object with runner and meta
3929
* @returns {Promise<AuditOutput[]>} - audit outputs from plugin runner
40-
* @throws {PluginOutputMissingAuditError} - if plugin runner output is invalid
30+
* @throws {AuditOutputsMissingAuditError} - if plugin runner output is invalid
4131
*
4232
* @example
4333
* // plugin execution
@@ -54,7 +44,12 @@ export class PluginOutputMissingAuditError extends Error {
5444
*/
5545
export async function executePlugin(
5646
pluginConfig: PluginConfig,
47+
opt: {
48+
cache: CacheConfig;
49+
persist: Required<Pick<PersistConfig, 'outputDir'>>;
50+
},
5751
): Promise<PluginReport> {
52+
const { cache, persist } = opt;
5853
const {
5954
runner,
6055
audits: pluginConfigAudits,
@@ -63,26 +58,25 @@ export async function executePlugin(
6358
groups,
6459
...pluginMeta
6560
} = pluginConfig;
66-
67-
// execute plugin runner
68-
const runnerResult =
69-
typeof runner === 'object'
70-
? await executeRunnerConfig(runner)
71-
: await executeRunnerFunction(runner);
72-
const { audits: unvalidatedAuditOutputs, ...executionMeta } = runnerResult;
73-
74-
// validate auditOutputs
75-
const result = auditOutputsSchema.safeParse(unvalidatedAuditOutputs);
76-
if (!result.success) {
77-
throw new Error(`Audit output is invalid: ${result.error.message}`);
61+
const { write: cacheWrite = false, read: cacheRead = false } = cache;
62+
const { outputDir } = persist;
63+
64+
const { audits, ...executionMeta } = cacheRead
65+
? // IF not null, take the result from cache
66+
((await readRunnerResults(pluginMeta.slug, outputDir)) ??
67+
// ELSE execute the plugin runner
68+
(await executePluginRunner(pluginConfig)))
69+
: await executePluginRunner(pluginConfig);
70+
71+
if (cacheWrite) {
72+
await writeRunnerResults(pluginMeta.slug, outputDir, {
73+
...executionMeta,
74+
audits,
75+
});
7876
}
79-
const auditOutputs = result.data;
80-
auditOutputsCorrelateWithPluginOutput(auditOutputs, pluginConfigAudits);
81-
82-
const normalizedAuditOutputs = await normalizeAuditOutputs(auditOutputs);
8377

8478
// enrich `AuditOutputs` to `AuditReport`
85-
const auditReports: AuditReport[] = normalizedAuditOutputs.map(
79+
const auditReports: AuditReport[] = audits.map(
8680
(auditOutput: AuditOutput) => ({
8781
...auditOutput,
8882
...(pluginConfigAudits.find(
@@ -103,13 +97,18 @@ export async function executePlugin(
10397
}
10498

10599
const wrapProgress = async (
106-
pluginCfg: PluginConfig,
100+
cfg: {
101+
plugin: PluginConfig;
102+
persist: Required<Pick<PersistConfig, 'outputDir'>>;
103+
cache: CacheConfig;
104+
},
107105
steps: number,
108106
progressBar: ProgressBar | null,
109107
) => {
108+
const { plugin: pluginCfg, ...rest } = cfg;
110109
progressBar?.updateTitle(`Executing ${bold(pluginCfg.title)}`);
111110
try {
112-
const pluginReport = await executePlugin(pluginCfg);
111+
const pluginReport = await executePlugin(pluginCfg, rest);
113112
progressBar?.incrementInSteps(steps);
114113
return pluginReport;
115114
} catch (error) {
@@ -146,15 +145,24 @@ const wrapProgress = async (
146145
*
147146
*/
148147
export async function executePlugins(
149-
plugins: PluginConfig[],
148+
cfg: {
149+
plugins: PluginConfig[];
150+
persist: Required<Pick<PersistConfig, 'outputDir'>>;
151+
cache: CacheConfig;
152+
},
150153
options?: { progress?: boolean },
151154
): Promise<PluginReport[]> {
155+
const { plugins, ...cacheCfg } = cfg;
152156
const { progress = false } = options ?? {};
153157

154158
const progressBar = progress ? getProgressBar('Run plugins') : null;
155159

156160
const pluginsResult = plugins.map(pluginCfg =>
157-
wrapProgress(pluginCfg, plugins.length, progressBar),
161+
wrapProgress(
162+
{ plugin: pluginCfg, ...cacheCfg },
163+
plugins.length,
164+
progressBar,
165+
),
158166
);
159167

160168
const errorsTransform = ({ reason }: PromiseRejectedResult) => String(reason);
@@ -179,17 +187,3 @@ export async function executePlugins(
179187

180188
return fulfilled.map(result => result.value);
181189
}
182-
183-
function auditOutputsCorrelateWithPluginOutput(
184-
auditOutputs: AuditOutputs,
185-
pluginConfigAudits: PluginConfig['audits'],
186-
) {
187-
auditOutputs.forEach(auditOutput => {
188-
const auditMetadata = pluginConfigAudits.find(
189-
audit => audit.slug === auditOutput.slug,
190-
);
191-
if (!auditMetadata) {
192-
throw new PluginOutputMissingAuditError(auditOutput.slug);
193-
}
194-
});
195-
}

0 commit comments

Comments
 (0)