Skip to content

Commit 328d961

Browse files
committed
feat(create-cli): add categories codegen
1 parent d13cc6e commit 328d961

File tree

3 files changed

+168
-2
lines changed

3 files changed

+168
-2
lines changed

packages/create-cli/src/lib/setup/codegen.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import path from 'node:path';
2+
import type { CategoryRef } from '@code-pushup/models';
23
import { toUnixPath } from '@code-pushup/utils';
34
import type {
45
ConfigFileFormat,
@@ -43,11 +44,13 @@ export function generateConfigSource(
4344
if (format === 'ts') {
4445
builder.addLine('export default {');
4546
addPlugins(builder, plugins);
47+
addCategories(builder, plugins);
4648
builder.addLine('} satisfies CoreConfig;');
4749
} else {
4850
builder.addLine("/** @type {import('@code-pushup/models').CoreConfig} */");
4951
builder.addLine('export default {');
5052
addPlugins(builder, plugins);
53+
addCategories(builder, plugins);
5154
builder.addLine('};');
5255
}
5356
return builder.toString();
@@ -172,6 +175,33 @@ function addPresetExport(
172175
}
173176
builder.addLine('return {', 1);
174177
addPlugins(builder, plugins, 2);
178+
addCategories(builder, plugins, 2);
175179
builder.addLine('};', 1);
176180
builder.addLine('}');
177181
}
182+
183+
function addCategories(
184+
builder: CodeBuilder,
185+
plugins: PluginCodegenResult[],
186+
depth = 1,
187+
): void {
188+
const categories = plugins.flatMap(p => p.categories ?? []);
189+
if (categories.length === 0) {
190+
return;
191+
}
192+
builder.addLine('categories: [', depth);
193+
categories.forEach(({ slug, title, refs }) => {
194+
builder.addLine('{', depth + 1);
195+
builder.addLine(`slug: '${slug}',`, depth + 2);
196+
builder.addLine(`title: '${title}',`, depth + 2);
197+
builder.addLine('refs: [', depth + 2);
198+
builder.addLines(refs.map(formatCategoryRef), depth + 3);
199+
builder.addLine('],', depth + 2);
200+
builder.addLine('},', depth + 1);
201+
});
202+
builder.addLine('],', depth);
203+
}
204+
205+
function formatCategoryRef(ref: CategoryRef): string {
206+
return `{ type: '${ref.type}', plugin: '${ref.plugin}', slug: '${ref.slug}', weight: ${ref.weight} },`;
207+
}

packages/create-cli/src/lib/setup/codegen.unit.test.ts

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { CategoryConfig } from '@code-pushup/models';
12
import {
23
computeRelativePresetImport,
34
generateConfigSource,
@@ -16,6 +17,24 @@ const ESLINT_PLUGIN: PluginCodegenResult = {
1617
pluginInit: "await eslintPlugin({ patterns: '.' })",
1718
};
1819

20+
const ESLINT_CATEGORIES: CategoryConfig[] = [
21+
{
22+
slug: 'bug-prevention',
23+
title: 'Bug prevention',
24+
refs: [{ type: 'group', plugin: 'eslint', slug: 'problems', weight: 1 }],
25+
},
26+
{
27+
slug: 'code-style',
28+
title: 'Code style',
29+
refs: [{ type: 'group', plugin: 'eslint', slug: 'suggestions', weight: 1 }],
30+
},
31+
];
32+
33+
const ESLINT_PLUGIN_WITH_CATEGORIES: PluginCodegenResult = {
34+
...ESLINT_PLUGIN,
35+
categories: ESLINT_CATEGORIES,
36+
};
37+
1938
describe('generateConfigSource', () => {
2039
describe('TypeScript format', () => {
2140
it('should generate config with TODO placeholder when no plugins provided', () => {
@@ -201,6 +220,86 @@ describe('generateConfigSource', () => {
201220
);
202221
});
203222
});
223+
224+
describe('categories', () => {
225+
it('should include categories block when plugin provides categories', () => {
226+
expect(generateConfigSource([ESLINT_PLUGIN_WITH_CATEGORIES], 'ts'))
227+
.toMatchInlineSnapshot(`
228+
"import eslintPlugin from '@code-pushup/eslint-plugin';
229+
import type { CoreConfig } from '@code-pushup/models';
230+
231+
export default {
232+
plugins: [
233+
await eslintPlugin({ patterns: '.' }),
234+
],
235+
categories: [
236+
{
237+
slug: 'bug-prevention',
238+
title: 'Bug prevention',
239+
refs: [
240+
{ type: 'group', plugin: 'eslint', slug: 'problems', weight: 1 },
241+
],
242+
},
243+
{
244+
slug: 'code-style',
245+
title: 'Code style',
246+
refs: [
247+
{ type: 'group', plugin: 'eslint', slug: 'suggestions', weight: 1 },
248+
],
249+
},
250+
],
251+
} satisfies CoreConfig;
252+
"
253+
`);
254+
});
255+
256+
it('should omit categories block when no categories provided', () => {
257+
const source = generateConfigSource([ESLINT_PLUGIN], 'ts');
258+
expect(source).not.toContain('categories');
259+
});
260+
261+
it('should merge categories from multiple plugins', () => {
262+
const coveragePlugin: PluginCodegenResult = {
263+
imports: [
264+
{
265+
moduleSpecifier: '@code-pushup/coverage-plugin',
266+
defaultImport: 'coveragePlugin',
267+
},
268+
],
269+
pluginInit: 'await coveragePlugin()',
270+
categories: [
271+
{
272+
slug: 'code-coverage',
273+
title: 'Code coverage',
274+
refs: [
275+
{
276+
type: 'group',
277+
plugin: 'coverage',
278+
slug: 'coverage',
279+
weight: 1,
280+
},
281+
],
282+
},
283+
],
284+
};
285+
const source = generateConfigSource(
286+
[ESLINT_PLUGIN_WITH_CATEGORIES, coveragePlugin],
287+
'ts',
288+
);
289+
expect(source).toContain("slug: 'bug-prevention'");
290+
expect(source).toContain("slug: 'code-style'");
291+
expect(source).toContain("slug: 'code-coverage'");
292+
});
293+
294+
it('should include categories in JS format config', () => {
295+
const source = generateConfigSource(
296+
[ESLINT_PLUGIN_WITH_CATEGORIES],
297+
'js',
298+
);
299+
expect(source).toContain('categories: [');
300+
expect(source).toContain("slug: 'bug-prevention'");
301+
});
302+
});
204303
});
205304

206305
describe('generatePresetSource', () => {
@@ -243,6 +342,43 @@ describe('generatePresetSource', () => {
243342
"
244343
`);
245344
});
345+
346+
it('should include categories in TS preset source', () => {
347+
expect(generatePresetSource([ESLINT_PLUGIN_WITH_CATEGORIES], 'ts'))
348+
.toMatchInlineSnapshot(`
349+
"import eslintPlugin from '@code-pushup/eslint-plugin';
350+
import type { CoreConfig } from '@code-pushup/models';
351+
352+
/**
353+
* Creates a Code PushUp config for a project.
354+
* @param project Project name
355+
*/
356+
export async function createConfig(project: string): Promise<CoreConfig> {
357+
return {
358+
plugins: [
359+
await eslintPlugin({ patterns: '.' }),
360+
],
361+
categories: [
362+
{
363+
slug: 'bug-prevention',
364+
title: 'Bug prevention',
365+
refs: [
366+
{ type: 'group', plugin: 'eslint', slug: 'problems', weight: 1 },
367+
],
368+
},
369+
{
370+
slug: 'code-style',
371+
title: 'Code style',
372+
refs: [
373+
{ type: 'group', plugin: 'eslint', slug: 'suggestions', weight: 1 },
374+
],
375+
},
376+
],
377+
};
378+
}
379+
"
380+
`);
381+
});
246382
});
247383

248384
describe('generateProjectSource', () => {

packages/create-cli/src/lib/setup/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { PluginMeta } from '@code-pushup/models';
1+
import type { CategoryConfig, PluginMeta } from '@code-pushup/models';
22
import type { MonorepoTool } from '@code-pushup/utils';
33

44
export const CI_PROVIDERS = ['github', 'gitlab', 'none'] as const;
@@ -65,7 +65,7 @@ export type ImportDeclarationStructure = {
6565
export type PluginCodegenResult = {
6666
imports: ImportDeclarationStructure[];
6767
pluginInit: string;
68-
// TODO: add categories support (categoryRefs for generated categories array)
68+
categories?: CategoryConfig[];
6969
};
7070

7171
export type ScopedPluginResult = {

0 commit comments

Comments
 (0)