Skip to content

Commit a070fb0

Browse files
committed
feat(core): log group for each plugin execution
1 parent e4e33a5 commit a070fb0

File tree

3 files changed

+68
-154
lines changed

3 files changed

+68
-154
lines changed

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

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,8 @@ import {
1111
} from '@code-pushup/models';
1212
import {
1313
asyncSequential,
14-
groupByStatus,
15-
pluralizeToken,
14+
logger,
1615
scoreAuditsWithTarget,
17-
settlePromise,
18-
stringifyError,
1916
} from '@code-pushup/utils';
2017
import {
2118
executePluginRunner,
@@ -120,33 +117,18 @@ export async function executePlugin(
120117
* console.error(error); // Plugin output is invalid
121118
* }
122119
*/
123-
export async function executePlugins(config: {
120+
export function executePlugins(config: {
124121
plugins: PluginConfig[];
125122
persist: PersistConfig;
126123
cache: CacheConfigObject;
127124
}): Promise<PluginReport[]> {
128-
const results = await asyncSequential(config.plugins, pluginConfig =>
129-
settlePromise(
130-
executePlugin(pluginConfig, config).catch((error: unknown) => {
131-
throw new Error(
132-
`- Plugin ${ansis.bold(pluginConfig.title)} (${ansis.bold(pluginConfig.slug)}) produced the following error:\n - ${stringifyError(error)}`,
133-
);
134-
}),
135-
),
136-
);
137-
138-
const { fulfilled, rejected } = groupByStatus(results);
139-
if (rejected.length > 0) {
140-
const errorMessages = rejected
141-
.map(({ reason }) => stringifyError(reason))
142-
.join('\n');
143-
throw new Error(
144-
`Executing ${pluralizeToken(
145-
'plugin',
146-
rejected.length,
147-
)} failed.\n\n${errorMessages}\n\n`,
148-
);
149-
}
150-
151-
return fulfilled.map(result => result.value);
125+
return asyncSequential(config.plugins, async (pluginConfig, index) => {
126+
const suffix = ansis.gray(`[${index + 1}/${config.plugins.length}]`);
127+
const title = `Running plugin "${pluginConfig.title}" ${suffix}`;
128+
const message = `Completed "${pluginConfig.title}" plugin execution`;
129+
return logger.group(title, async () => {
130+
const result = await executePlugin(pluginConfig, config);
131+
return { message, result };
132+
});
133+
});
152134
}

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

Lines changed: 55 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
MEMFS_VOLUME,
1111
MINIMAL_PLUGIN_CONFIG_MOCK,
1212
} from '@code-pushup/test-utils';
13+
import { logger } from '@code-pushup/utils';
1314
import { executePlugin, executePlugins } from './execute-plugin.js';
1415
import * as runnerModule from './runner.js';
1516

@@ -232,145 +233,79 @@ describe('executePlugins', () => {
232233
expect(pluginResult[0]?.audits[0]?.slug).toBe('node-version');
233234
});
234235

235-
it('should throw for invalid audit output', async () => {
236-
const slug = 'simulate-invalid-audit-slug';
237-
const title = 'Simulate an invalid audit slug in outputs';
238-
await expect(() =>
239-
executePlugins({
240-
plugins: [
241-
{
242-
...MINIMAL_PLUGIN_CONFIG_MOCK,
243-
slug,
244-
title,
245-
runner: () => [
246-
{
247-
slug: 'invalid-audit-slug-',
248-
score: 0.3,
249-
value: 16,
250-
displayValue: '16.0.0',
251-
},
252-
],
253-
},
254-
] satisfies PluginConfig[],
255-
persist: { outputDir: '.code-pushup' },
256-
cache: { read: false, write: false },
257-
}),
258-
).rejects.toThrow(
259-
`Executing 1 plugin failed.\n\n- Plugin ${ansis.bold(
260-
title,
261-
)} (${ansis.bold(slug)}) produced the following error:\n - Audit output is invalid`,
262-
);
263-
});
236+
it('should throw for 1st invalid audit output', async () => {
237+
const plugins = [
238+
{
239+
...MINIMAL_PLUGIN_CONFIG_MOCK,
240+
runner: () => [{ slug: 'my-audit', score: 0 }] as AuditOutputs,
241+
},
242+
{ ...MINIMAL_PLUGIN_CONFIG_MOCK, runner: vi.fn() },
243+
] as const satisfies PluginConfig[];
264244

265-
it('should throw for one failing plugin', async () => {
266-
const missingAuditSlug = 'missing-audit-slug';
267245
await expect(() =>
268246
executePlugins({
269-
plugins: [
270-
{
271-
...MINIMAL_PLUGIN_CONFIG_MOCK,
272-
slug: 'plg1',
273-
title: 'plg1',
274-
runner: () => [
275-
{
276-
slug: `${missingAuditSlug}-a`,
277-
score: 0.3,
278-
value: 16,
279-
displayValue: '16.0.0',
280-
},
281-
],
282-
},
283-
] satisfies PluginConfig[],
247+
plugins,
284248
persist: { outputDir: '.code-pushup' },
285249
cache: { read: false, write: false },
286250
}),
287-
).rejects.toThrow('Executing 1 plugin failed.\n\n');
251+
).rejects.toThrow(`Invalid ${ansis.bold('AuditOutputs')}
252+
✖ Invalid input: expected number, received undefined
253+
→ at [0].value
254+
`);
255+
expect(plugins[1].runner).not.toHaveBeenCalled();
288256
});
289257

290-
it('should throw for multiple failing plugins', async () => {
291-
const missingAuditSlug = 'missing-audit-slug';
292-
await expect(() =>
258+
it('should resolve plugin reports', async () => {
259+
await expect(
293260
executePlugins({
294-
plugins: [
295-
{
296-
...MINIMAL_PLUGIN_CONFIG_MOCK,
297-
slug: 'plg1',
298-
title: 'plg1',
299-
runner: () => [
300-
{
301-
slug: `${missingAuditSlug}-a`,
302-
score: 0.3,
303-
value: 16,
304-
displayValue: '16.0.0',
305-
},
306-
],
307-
},
308-
{
309-
...MINIMAL_PLUGIN_CONFIG_MOCK,
310-
slug: 'plg2',
311-
title: 'plg2',
312-
runner: () => [
313-
{
314-
slug: `${missingAuditSlug}-b`,
315-
score: 0.3,
316-
value: 16,
317-
displayValue: '16.0.0',
318-
},
319-
],
320-
},
321-
] satisfies PluginConfig[],
261+
plugins: [MINIMAL_PLUGIN_CONFIG_MOCK],
322262
persist: { outputDir: '.code-pushup' },
323263
cache: { read: false, write: false },
324264
}),
325-
).rejects.toThrow('Executing 2 plugins failed.\n\n');
265+
).resolves.toEqual([
266+
expect.objectContaining({
267+
slug: 'node',
268+
title: 'Node',
269+
audits: expect.arrayContaining([
270+
expect.objectContaining({
271+
slug: 'node-version',
272+
title: 'Node version',
273+
score: 0.3,
274+
value: 16,
275+
}),
276+
]),
277+
}),
278+
]);
326279
});
327280

328-
it('should throw with indentation in message', async () => {
329-
const missingAuditSlug = 'missing-audit-slug';
330-
331-
await expect(() =>
281+
it('should print log groups for each plugin', async () => {
282+
await expect(
332283
executePlugins({
333284
plugins: [
334-
{
335-
...MINIMAL_PLUGIN_CONFIG_MOCK,
336-
slug: 'plg1',
337-
title: 'plg1',
338-
runner: () => [
339-
{
340-
slug: `${missingAuditSlug}-a`,
341-
score: 0.3,
342-
value: 16,
343-
displayValue: '16.0.0',
344-
},
345-
],
346-
},
347-
{
348-
...MINIMAL_PLUGIN_CONFIG_MOCK,
349-
slug: 'plg2',
350-
title: 'plg2',
351-
runner: () => [
352-
{
353-
slug: `${missingAuditSlug}-b`,
354-
score: 0.3,
355-
value: 16,
356-
displayValue: '16.0.0',
357-
},
358-
],
359-
},
360-
] satisfies PluginConfig[],
285+
{ ...MINIMAL_PLUGIN_CONFIG_MOCK, title: 'Plugin A' },
286+
{ ...MINIMAL_PLUGIN_CONFIG_MOCK, title: 'Plugin B' },
287+
{ ...MINIMAL_PLUGIN_CONFIG_MOCK, title: 'Plugin C' },
288+
],
361289
persist: { outputDir: '.code-pushup' },
362290
cache: { read: false, write: false },
363291
}),
364-
).rejects.toThrow(
365-
`- Plugin ${ansis.bold('plg1')} (${ansis.bold(
366-
'plg1',
367-
)}) produced the following error:\n - Audit metadata not present in plugin config. Missing slug: ${ansis.bold(
368-
'missing-audit-slug-a',
369-
)}\n- Plugin ${ansis.bold('plg2')} (${ansis.bold(
370-
'plg2',
371-
)}) produced the following error:\n - Audit metadata not present in plugin config. Missing slug: ${ansis.bold(
372-
'missing-audit-slug-b',
373-
)}\n\n`,
292+
).resolves.toBeArrayOfSize(3);
293+
294+
expect(logger.group).toHaveBeenCalledTimes(3);
295+
expect(logger.group).toHaveBeenNthCalledWith(
296+
1,
297+
`Running plugin "Plugin A" ${ansis.gray('[1/3]')}`,
298+
expect.any(Function),
299+
);
300+
expect(logger.group).toHaveBeenNthCalledWith(
301+
2,
302+
`Running plugin "Plugin B" ${ansis.gray('[2/3]')}`,
303+
expect.any(Function),
304+
);
305+
expect(logger.group).toHaveBeenNthCalledWith(
306+
3,
307+
`Running plugin "Plugin C" ${ansis.gray('[3/3]')}`,
308+
expect.any(Function),
374309
);
375310
});
376311

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
type RunnerConfig,
99
type RunnerFunction,
1010
auditOutputsSchema,
11+
validate,
1112
} from '@code-pushup/models';
1213
import {
1314
calcDuration,
@@ -87,11 +88,7 @@ export async function executePluginRunner(
8788

8889
const duration = calcDuration(start);
8990

90-
const result = auditOutputsSchema.safeParse(unvalidatedAuditOutputs);
91-
if (!result.success) {
92-
throw new Error(`Audit output is invalid: ${result.error.message}`);
93-
}
94-
const auditOutputs = result.data;
91+
const auditOutputs = validate(auditOutputsSchema, unvalidatedAuditOutputs);
9592
auditOutputsCorrelateWithPluginOutput(auditOutputs, pluginConfigAudits);
9693

9794
return {

0 commit comments

Comments
 (0)