Skip to content

Commit 246eec8

Browse files
committed
feat(plugin-lighthouse): implement multiple URL support
1 parent 1a0e062 commit 246eec8

15 files changed

+1661
-70
lines changed

code-pushup.preset.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import {
2020
} from './packages/plugin-jsdocs/src/lib/constants.js';
2121
import { filterGroupsByOnlyAudits } from './packages/plugin-jsdocs/src/lib/utils.js';
2222
import lighthousePlugin, {
23+
type LighthouseUrls,
2324
lighthouseGroupRef,
25+
mergeLighthouseCategories,
2426
} from './packages/plugin-lighthouse/src/index.js';
2527
import typescriptPlugin, {
2628
type TypescriptPluginOptions,
@@ -135,11 +137,14 @@ export const jsPackagesCoreConfig = async (): Promise<CoreConfig> => ({
135137
});
136138

137139
export const lighthouseCoreConfig = async (
138-
url: string,
139-
): Promise<CoreConfig> => ({
140-
plugins: [await lighthousePlugin(url)],
141-
categories: lighthouseCategories,
142-
});
140+
urls: LighthouseUrls,
141+
): Promise<CoreConfig> => {
142+
const lhPlugin = await lighthousePlugin(urls);
143+
return {
144+
plugins: [lhPlugin],
145+
categories: mergeLighthouseCategories(lhPlugin, lighthouseCategories),
146+
};
147+
};
143148

144149
export const jsDocsCoreConfig = (
145150
config: JsDocsPluginConfig | string[],

packages/plugin-lighthouse/src/index.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ export {
66
LIGHTHOUSE_PLUGIN_SLUG,
77
LIGHTHOUSE_OUTPUT_PATH,
88
} from './lib/constants.js';
9-
export {
10-
lighthouseAuditRef,
11-
lighthouseGroupRef,
12-
type LighthouseGroupSlugs,
13-
} from './lib/utils.js';
14-
export type { LighthouseOptions } from './lib/types.js';
9+
export { lighthouseAuditRef, lighthouseGroupRef } from './lib/utils.js';
10+
export type {
11+
LighthouseGroupSlugs,
12+
LighthouseOptions,
13+
LighthouseUrls,
14+
} from './lib/types.js';
1515
export { lighthousePlugin } from './lib/lighthouse-plugin.js';
1616
export default lighthousePlugin;
17+
export { mergeLighthouseCategories } from './lib/merge-categories.js';

packages/plugin-lighthouse/src/lib/constants.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,13 @@ export const LIGHTHOUSE_OUTPUT_PATH = path.join(
1010
DEFAULT_PERSIST_OUTPUT_DIR,
1111
LIGHTHOUSE_PLUGIN_SLUG,
1212
);
13+
14+
export const LIGHTHOUSE_GROUP_SLUGS = [
15+
'performance',
16+
'accessibility',
17+
'best-practices',
18+
'seo',
19+
'pwa',
20+
] as const;
21+
22+
export const SINGLE_URL_THRESHOLD = 1;

packages/plugin-lighthouse/src/lib/lighthouse-plugin.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,24 @@ import { createRequire } from 'node:module';
22
import type { PluginConfig } from '@code-pushup/models';
33
import { LIGHTHOUSE_PLUGIN_SLUG } from './constants.js';
44
import { normalizeFlags } from './normalize-flags.js';
5-
import {
6-
LIGHTHOUSE_GROUPS,
7-
LIGHTHOUSE_NAVIGATION_AUDITS,
8-
} from './runner/constants.js';
5+
import { normalizeUrlInput, processAuditsAndGroups } from './processing.js';
96
import { createRunnerFunction } from './runner/runner.js';
10-
import type { LighthouseOptions } from './types.js';
11-
import { markSkippedAuditsAndGroups } from './utils.js';
7+
import type { LighthouseOptions, LighthouseUrls } from './types.js';
128

139
export function lighthousePlugin(
14-
url: string,
10+
urls: LighthouseUrls,
1511
flags?: LighthouseOptions,
1612
): PluginConfig {
1713
const { skipAudits, onlyAudits, onlyCategories, ...unparsedFlags } =
1814
normalizeFlags(flags ?? {});
1915

20-
const { audits, groups } = markSkippedAuditsAndGroups(
21-
LIGHTHOUSE_NAVIGATION_AUDITS,
22-
LIGHTHOUSE_GROUPS,
23-
{ skipAudits, onlyAudits, onlyCategories },
24-
);
16+
const normalizedUrls = normalizeUrlInput(urls);
17+
18+
const { audits, groups } = processAuditsAndGroups(normalizedUrls, {
19+
skipAudits,
20+
onlyAudits,
21+
onlyCategories,
22+
});
2523

2624
const packageJson = createRequire(import.meta.url)(
2725
'../../package.json',
@@ -35,7 +33,7 @@ export function lighthousePlugin(
3533
icon: 'lighthouse',
3634
audits,
3735
groups,
38-
runner: createRunnerFunction(url, {
36+
runner: createRunnerFunction(normalizedUrls, {
3937
skipAudits,
4038
onlyAudits,
4139
onlyCategories,

packages/plugin-lighthouse/src/lib/lighthouse-plugin.unit.test.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,28 @@ describe('lighthousePlugin-config-object', () => {
1818
]);
1919
});
2020

21+
it('should create valid plugin config with multiple URLs', () => {
22+
const urls = [
23+
'https://code-pushup-portal.com',
24+
'https://code-pushup-portal.com/about',
25+
];
26+
const pluginConfig = lighthousePlugin(urls);
27+
expect(() => pluginConfigSchema.parse(pluginConfig)).not.toThrow();
28+
29+
const { audits, groups } = pluginConfig;
30+
expect(audits.length).toBeGreaterThan(100);
31+
expect(groups).toStrictEqual([
32+
expect.objectContaining({ slug: 'performance-1' }),
33+
expect.objectContaining({ slug: 'accessibility-1' }),
34+
expect.objectContaining({ slug: 'best-practices-1' }),
35+
expect.objectContaining({ slug: 'seo-1' }),
36+
expect.objectContaining({ slug: 'performance-2' }),
37+
expect.objectContaining({ slug: 'accessibility-2' }),
38+
expect.objectContaining({ slug: 'best-practices-2' }),
39+
expect.objectContaining({ slug: 'seo-2' }),
40+
]);
41+
});
42+
2143
it.each([
2244
[
2345
{ onlyAudits: ['first-contentful-paint'] },
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import type { CategoryConfig, Group, PluginConfig } from '@code-pushup/models';
2+
import { LIGHTHOUSE_GROUP_SLUGS, LIGHTHOUSE_PLUGIN_SLUG } from './constants.js';
3+
import { orderSlug, shouldExpandForUrls } from './processing.js';
4+
import { LIGHTHOUSE_GROUPS } from './runner/constants.js';
5+
import type { LighthouseGroupSlugs } from './types.js';
6+
import { isLighthouseGroupSlug } from './utils.js';
7+
8+
export function mergeLighthouseCategories(
9+
{ groups }: Pick<PluginConfig, 'groups'>,
10+
categories?: CategoryConfig[],
11+
): CategoryConfig[] {
12+
if (!groups) {
13+
return categories ?? [];
14+
}
15+
if (!categories) {
16+
return createCategories(groups);
17+
}
18+
return expandCategories(categories, groups);
19+
}
20+
21+
function createCategories(groups: Group[]): CategoryConfig[] {
22+
const urlCount = countUrls(groups);
23+
if (!shouldExpandForUrls(urlCount)) {
24+
return [];
25+
}
26+
return extractGroupSlugs(groups)
27+
.filter(isLighthouseGroupSlug)
28+
.map(slug => createAggregatedCategory(slug, urlCount));
29+
}
30+
31+
function expandCategories(
32+
categories: CategoryConfig[],
33+
groups: Group[],
34+
): CategoryConfig[] {
35+
const urlCount = countUrls(groups);
36+
if (!shouldExpandForUrls(urlCount)) {
37+
return categories;
38+
}
39+
return categories.map(category =>
40+
expandAggregatedCategory(category, urlCount),
41+
);
42+
}
43+
44+
export function createAggregatedCategory(
45+
groupSlug: LighthouseGroupSlugs,
46+
urlCount: number,
47+
): CategoryConfig {
48+
const group = LIGHTHOUSE_GROUPS.find(({ slug }) => slug === groupSlug);
49+
if (!group) {
50+
const availableSlugs = LIGHTHOUSE_GROUP_SLUGS.join(', ');
51+
throw new Error(
52+
`Invalid Lighthouse group slug: "${groupSlug}". Available groups: ${availableSlugs}`,
53+
);
54+
}
55+
return {
56+
slug: group.slug,
57+
title: group.title,
58+
...(group.description && { description: group.description }),
59+
refs: Array.from({ length: urlCount }, (_, i) => ({
60+
plugin: LIGHTHOUSE_PLUGIN_SLUG,
61+
slug: orderSlug(group.slug, i),
62+
type: 'group',
63+
weight: 1,
64+
})),
65+
};
66+
}
67+
68+
export function expandAggregatedCategory(
69+
category: CategoryConfig,
70+
urlCount: number,
71+
): CategoryConfig {
72+
return {
73+
...category,
74+
refs: category.refs.flatMap(ref => {
75+
if (ref.plugin === LIGHTHOUSE_PLUGIN_SLUG) {
76+
return Array.from({ length: urlCount }, (_, i) => ({
77+
...ref,
78+
slug: orderSlug(ref.slug, i),
79+
}));
80+
}
81+
return [ref];
82+
}),
83+
};
84+
}
85+
86+
export function countUrls(groups: Group[]): number {
87+
const suffixes = groups
88+
.map(({ slug }) => slug.match(/-(\d+)$/)?.[1])
89+
.filter(Boolean)
90+
.map(Number)
91+
.filter(n => !Number.isNaN(n));
92+
return suffixes.length > 0 ? Math.max(...suffixes) : 1;
93+
}
94+
95+
export function extractGroupSlugs(groups: Group[]): string[] {
96+
return [...new Set(groups.map(({ slug }) => slug.replace(/-\d+$/, '')))];
97+
}

0 commit comments

Comments
 (0)