Skip to content

Commit 1c1e201

Browse files
committed
feat(plugin-eslint): add artifacts options
1 parent 6175c5b commit 1c1e201

7 files changed

Lines changed: 139 additions & 15 deletions

File tree

packages/models/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,5 @@ export { uploadConfigSchema, type UploadConfig } from './lib/upload-config.js';
145145
export {
146146
artifactGenerationCommandSchema,
147147
pluginArtifactOptionsSchema,
148+
type PluginArtifactOptions,
148149
} from './lib/configuration.js';

packages/plugin-eslint/src/bin.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
1+
import path from 'node:path';
12
import process from 'node:process';
23
import { Parser } from 'yargs/helpers';
4+
import { ESLINT_PLUGIN_SLUG } from './lib/constants.js';
35
import { executeRunner } from './lib/runner/index.js';
46

5-
const { runnerConfigPath, runnerOutputPath } = Parser(process.argv);
7+
const { runnerConfigPath, runnerOutputPath, outputDir } = Parser(process.argv);
68

7-
await executeRunner({ runnerConfigPath, runnerOutputPath });
9+
await executeRunner({
10+
runnerConfigPath,
11+
runnerOutputPath,
12+
persistOutputDir: path.join(outputDir, ESLINT_PLUGIN_SLUG),
13+
});

packages/plugin-eslint/src/lib/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { z } from 'zod';
2+
import { pluginArtifactOptionsSchema } from '@code-pushup/models';
23
import { toArray } from '@code-pushup/utils';
34

45
const patternsSchema = z
@@ -61,5 +62,6 @@ export type CustomGroup = z.infer<typeof customGroupSchema>;
6162

6263
export const eslintPluginOptionsSchema = z.object({
6364
groups: z.array(customGroupSchema).optional(),
65+
artifacts: pluginArtifactOptionsSchema.optional(),
6466
});
6567
export type ESLintPluginOptions = z.infer<typeof eslintPluginOptionsSchema>;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const ESLINT_PLUGIN_SLUG = 'eslint';

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

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import { createRequire } from 'node:module';
2-
import path from 'node:path';
3-
import { fileURLToPath } from 'node:url';
42
import type { PluginConfig } from '@code-pushup/models';
53
import { parseSchema } from '@code-pushup/utils';
64
import {
@@ -9,8 +7,9 @@ import {
97
eslintPluginConfigSchema,
108
eslintPluginOptionsSchema,
119
} from './config.js';
10+
import { ESLINT_PLUGIN_SLUG } from './constants.js';
1211
import { listAuditsAndGroups } from './meta/index.js';
13-
import { createRunnerConfig } from './runner/index.js';
12+
import { createRunnerFunction } from './runner/index.js';
1413

1514
/**
1615
* Instantiates Code PushUp ESLint plugin for use in core config.
@@ -49,18 +48,12 @@ export async function eslintPlugin(
4948

5049
const { audits, groups } = await listAuditsAndGroups(targets, customGroups);
5150

52-
const runnerScriptPath = path.join(
53-
fileURLToPath(path.dirname(import.meta.url)),
54-
'..',
55-
'bin.js',
56-
);
57-
5851
const packageJson = createRequire(import.meta.url)(
5952
'../../package.json',
6053
) as typeof import('../../package.json');
6154

6255
return {
63-
slug: 'eslint',
56+
slug: ESLINT_PLUGIN_SLUG,
6457
title: 'ESLint',
6558
icon: 'eslint',
6659
description: 'Official Code PushUp ESLint plugin',
@@ -71,6 +64,10 @@ export async function eslintPlugin(
7164
audits,
7265
groups,
7366

74-
runner: await createRunnerConfig(runnerScriptPath, audits, targets),
67+
runner: await createRunnerFunction({
68+
audits,
69+
targets,
70+
artifacts: options?.artifacts,
71+
}),
7572
};
7673
}

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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,20 @@ import { lintResultsToAudits, mergeLinterOutputs } from './transform.js';
2222
export async function executeRunner({
2323
runnerConfigPath,
2424
runnerOutputPath,
25-
}: RunnerFilesPaths): Promise<void> {
25+
persistOutputDir,
26+
}: RunnerFilesPaths & { persistOutputDir: string }): Promise<void> {
2627
const { slugs, targets } =
2728
await readJsonFile<ESLintPluginRunnerConfig>(runnerConfigPath);
2829

2930
ui().logger.log(`ESLint plugin executing ${targets.length} lint targets`);
3031

31-
const linterOutputs = await asyncSequential(targets, lint);
32+
const linterOutputs = await asyncSequential(
33+
targets.map(target => ({
34+
...target,
35+
outputDir: persistOutputDir,
36+
})),
37+
lint,
38+
);
3239
const lintResults = mergeLinterOutputs(linterOutputs);
3340
const failedAudits = lintResultsToAudits(lintResults);
3441

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { beforeEach, describe, expect, it, vi } from 'vitest';
2+
import type { PersistConfig } from '@code-pushup/models';
3+
import * as utilsModule from '@code-pushup/utils';
4+
import { createRunnerFunction } from './index.js';
5+
import * as lintModule from './lint.js';
6+
import * as transformModule from './transform.js';
7+
import type { LinterOutput } from './types.js';
8+
import * as utilsRunnerModule from './utils.js';
9+
10+
describe('createRunnerFunction', () => {
11+
const asyncSequentialSpy = vi.spyOn(utilsModule, 'asyncSequential');
12+
const uiLoggerLogSpy = vi.fn();
13+
vi.spyOn(utilsModule, 'ui').mockReturnValue({
14+
logger: {
15+
log: uiLoggerLogSpy,
16+
flushLogs: vi.fn(),
17+
},
18+
switchMode: vi.fn(),
19+
} as any);
20+
const lintSpy = vi.spyOn(lintModule, 'lint');
21+
const mergeLinterOutputsSpy = vi.spyOn(transformModule, 'mergeLinterOutputs');
22+
const lintResultsToAuditsSpy = vi.spyOn(
23+
transformModule,
24+
'lintResultsToAudits',
25+
);
26+
const loadArtifactsSpy = vi.spyOn(utilsRunnerModule, 'loadArtifacts');
27+
28+
const mockLinterOutputs: LinterOutput[] = [
29+
{
30+
results: [],
31+
ruleOptionsPerFile: {},
32+
},
33+
];
34+
35+
const mockMergedOutput: LinterOutput = {
36+
results: [],
37+
ruleOptionsPerFile: {},
38+
};
39+
40+
const mockFailedAudits = [
41+
{
42+
slug: 'no-unused-vars',
43+
score: 0,
44+
value: 1,
45+
displayValue: '1 error',
46+
details: { issues: [] },
47+
},
48+
];
49+
50+
beforeEach(() => {
51+
vi.clearAllMocks();
52+
asyncSequentialSpy.mockResolvedValue(mockLinterOutputs);
53+
mergeLinterOutputsSpy.mockReturnValue(mockMergedOutput);
54+
lintResultsToAuditsSpy.mockReturnValue(mockFailedAudits);
55+
loadArtifactsSpy.mockResolvedValue(mockLinterOutputs);
56+
});
57+
58+
it('should create a runner function and call dependencies', async () => {
59+
const runnerFunction = await createRunnerFunction({
60+
audits: [
61+
{
62+
slug: 'no-unused-vars',
63+
title: 'No unused vars',
64+
description: 'Test rule',
65+
},
66+
],
67+
targets: [{ eslintrc: '.eslintrc.js', patterns: ['src/**/*.js'] }],
68+
});
69+
const persistConfig: PersistConfig = { outputDir: '/tmp/output' };
70+
await expect(runnerFunction(persistConfig)).resolves.not.toThrow();
71+
expect(uiLoggerLogSpy).toHaveBeenCalledWith(
72+
'ESLint plugin executing 1 lint targets',
73+
);
74+
expect(asyncSequentialSpy).toHaveBeenCalledWith(
75+
expect.arrayContaining([
76+
expect.objectContaining({
77+
outputDir: '/tmp/output',
78+
}),
79+
]),
80+
lintSpy,
81+
);
82+
expect(mergeLinterOutputsSpy).toHaveBeenCalledWith(mockLinterOutputs);
83+
expect(lintResultsToAuditsSpy).toHaveBeenCalledWith(mockMergedOutput);
84+
});
85+
86+
it('should call loadArtifacts when artifacts provided', async () => {
87+
const runnerFunction = await createRunnerFunction({
88+
audits: [
89+
{
90+
slug: 'no-unused-vars',
91+
title: 'No unused vars',
92+
description: 'Test rule',
93+
},
94+
],
95+
targets: [{ eslintrc: '.eslintrc.js', patterns: ['src/**/*.js'] }],
96+
artifacts: {
97+
artifactsPaths: ['/path/to/artifact.json'],
98+
},
99+
});
100+
const persistConfig: PersistConfig = { outputDir: '/tmp/output' };
101+
await expect(runnerFunction(persistConfig)).resolves.not.toThrow();
102+
103+
expect(loadArtifactsSpy).toHaveBeenCalledWith({
104+
artifactsPaths: ['/path/to/artifact.json'],
105+
});
106+
expect(asyncSequentialSpy).not.toHaveBeenCalled();
107+
expect(mergeLinterOutputsSpy).toHaveBeenCalledWith(mockLinterOutputs);
108+
expect(lintResultsToAuditsSpy).toHaveBeenCalledWith(mockMergedOutput);
109+
});
110+
});

0 commit comments

Comments
 (0)