Skip to content

Commit 4367e53

Browse files
BioPhotonASmatejchalk
authored
feat(plugin-eslint): add artefact options (#1089)
This PR includes: - refactor `generateAuditOutputs` to `createRunnerFunction` - use `createRunnerFunction` instead of `createRunnerConfig` - update tests --------- Co-authored-by: AS <ASiuta_ext@mapal-os.com> Co-authored-by: Matěj Chalk <34691111+matejchalk@users.noreply.github.com>
1 parent a6d3965 commit 4367e53

File tree

9 files changed

+99
-189
lines changed

9 files changed

+99
-189
lines changed

packages/plugin-eslint/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
"glob": "^11.0.0",
4242
"@code-pushup/utils": "0.77.0",
4343
"@code-pushup/models": "0.77.0",
44-
"yargs": "^17.7.2",
4544
"zod": "^4.0.5"
4645
},
4746
"peerDependencies": {

packages/plugin-eslint/src/bin.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

packages/plugin-eslint/src/lib/__snapshots__/eslint-plugin.int.test.ts.snap

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -346,16 +346,7 @@ exports[`eslintPlugin > should initialize ESLint plugin for React application 1`
346346
],
347347
"icon": "eslint",
348348
"packageName": "@code-pushup/eslint-plugin",
349-
"runner": {
350-
"args": [
351-
""<dirname>/bin.js"",
352-
"--runnerConfigPath="node_modules/.code-pushup/eslint/<timestamp>/plugin-config.json"",
353-
"--runnerOutputPath="node_modules/.code-pushup/eslint/<timestamp>/runner-output.json"",
354-
],
355-
"command": "node",
356-
"configFile": "node_modules/.code-pushup/eslint/<timestamp>/plugin-config.json",
357-
"outputFile": "node_modules/.code-pushup/eslint/<timestamp>/runner-output.json",
358-
},
349+
"runner": [Function],
359350
"slug": "eslint",
360351
"title": "ESLint",
361352
"version": Any<String>,

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>;

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

Lines changed: 32 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import path from 'node:path';
33
import process from 'node:process';
44
import { fileURLToPath } from 'node:url';
55
import type { MockInstance } from 'vitest';
6-
import type { Audit, PluginConfig, RunnerConfig } from '@code-pushup/models';
7-
import { toUnixPath } from '@code-pushup/utils';
6+
import type { Audit } from '@code-pushup/models';
87
import { eslintPlugin } from './eslint-plugin.js';
98

109
describe('eslintPlugin', () => {
@@ -15,27 +14,6 @@ describe('eslintPlugin', () => {
1514
let cwdSpy: MockInstance<[], string>;
1615
let platformSpy: MockInstance<[], NodeJS.Platform>;
1716

18-
const replaceAbsolutePath = (plugin: PluginConfig): PluginConfig => ({
19-
...plugin,
20-
runner: {
21-
...(plugin.runner as RunnerConfig),
22-
args: (plugin.runner as RunnerConfig).args?.map(arg =>
23-
toUnixPath(arg.replace(path.dirname(thisDir), '<dirname>')).replace(
24-
/\/eslint\/\d+\//,
25-
'/eslint/<timestamp>/',
26-
),
27-
),
28-
...((plugin.runner as RunnerConfig).configFile && {
29-
configFile: toUnixPath(
30-
(plugin.runner as RunnerConfig).configFile!,
31-
).replace(/\/eslint\/\d+\//, '/eslint/<timestamp>/'),
32-
}),
33-
outputFile: toUnixPath(
34-
(plugin.runner as RunnerConfig).outputFile,
35-
).replace(/\/eslint\/\d+\//, '/eslint/<timestamp>/'),
36-
},
37-
});
38-
3917
beforeAll(() => {
4018
cwdSpy = vi.spyOn(process, 'cwd');
4119
// Linux produces extra quotation marks for globs
@@ -55,7 +33,7 @@ describe('eslintPlugin', () => {
5533
patterns: ['src/**/*.js', 'src/**/*.jsx'],
5634
});
5735

58-
expect(replaceAbsolutePath(plugin)).toMatchSnapshot({
36+
expect(plugin).toMatchSnapshot({
5937
version: expect.any(String),
6038
});
6139
});
@@ -68,18 +46,17 @@ describe('eslintPlugin', () => {
6846
});
6947

7048
// expect rule from extended base eslint.config.js
71-
expect(plugin.audits).toContainEqual(
72-
expect.objectContaining<Audit>({
73-
slug: expect.stringMatching(/^nx-enforce-module-boundaries/),
74-
title: expect.any(String),
75-
description: expect.stringContaining('sourceTag'),
76-
}),
77-
);
78-
// expect rule from nx-plugin project's eslint.config.js
79-
expect(plugin.audits).toContainEqual(
80-
expect.objectContaining<Partial<Audit>>({
81-
slug: 'nx-nx-plugin-checks',
82-
}),
49+
expect(plugin.audits).toStrictEqual(
50+
expect.arrayContaining([
51+
expect.objectContaining<Audit>({
52+
slug: expect.stringMatching(/^nx-enforce-module-boundaries/),
53+
title: expect.any(String),
54+
description: expect.stringContaining('sourceTag'),
55+
}),
56+
expect.objectContaining<Partial<Audit>>({
57+
slug: 'nx-nx-plugin-checks',
58+
}),
59+
]),
8360
);
8461
});
8562

@@ -152,12 +129,30 @@ describe('eslintPlugin', () => {
152129
await expect(
153130
// @ts-expect-error simulating invalid non-TS config
154131
eslintPlugin({ eslintrc: '.eslintrc.json' }),
155-
).rejects.toThrow('Invalid input');
132+
).rejects.toThrow('Failed parsing ESLint plugin config');
156133
});
157134

158135
it("should throw if eslintrc file doesn't exist", async () => {
159136
await expect(
160137
eslintPlugin({ eslintrc: '.eslintrc.yml', patterns: '**/*.js' }),
161138
).rejects.toThrow(/Failed to load url .*\.eslintrc.yml/);
162139
});
140+
141+
it('should initialize with artifact options', async () => {
142+
cwdSpy.mockReturnValue(path.join(fixturesDir, 'todos-app'));
143+
const plugin = await eslintPlugin(
144+
{
145+
eslintrc: 'eslint.config.js',
146+
patterns: ['src/**/*.js'],
147+
},
148+
{
149+
artifacts: {
150+
artifactsPaths: './artifacts/eslint-output.json',
151+
generateArtifactsCommand: 'echo "Generating artifacts"',
152+
},
153+
},
154+
);
155+
156+
expect(plugin.runner).toBeTypeOf('function');
157+
});
163158
});

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

Lines changed: 9 additions & 13 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 {
@@ -11,7 +9,7 @@ import {
119
} from './config.js';
1210
import { ESLINT_PLUGIN_SLUG } from './constants.js';
1311
import { listAuditsAndGroups } from './meta/index.js';
14-
import { createRunnerConfig } from './runner/index.js';
12+
import { createRunnerFunction } from './runner/index.js';
1513

1614
/**
1715
* Instantiates Code PushUp ESLint plugin for use in core config.
@@ -42,20 +40,14 @@ export async function eslintPlugin(
4240
schemaType: 'ESLint plugin config',
4341
});
4442

45-
const customGroups = options
43+
const { groups: customGroups, artifacts } = options
4644
? parseSchema(eslintPluginOptionsSchema, options, {
4745
schemaType: 'ESLint plugin options',
48-
}).groups
49-
: undefined;
46+
})
47+
: {};
5048

5149
const { audits, groups } = await listAuditsAndGroups(targets, customGroups);
5250

53-
const runnerScriptPath = path.join(
54-
fileURLToPath(path.dirname(import.meta.url)),
55-
'..',
56-
'bin.js',
57-
);
58-
5951
const packageJson = createRequire(import.meta.url)(
6052
'../../package.json',
6153
) as typeof import('../../package.json');
@@ -72,6 +64,10 @@ export async function eslintPlugin(
7264
audits,
7365
groups,
7466

75-
runner: await createRunnerConfig(runnerScriptPath, audits, targets),
67+
runner: await createRunnerFunction({
68+
audits,
69+
targets,
70+
...(artifacts ? { artifacts } : {}),
71+
}),
7672
};
7773
}

packages/plugin-eslint/src/lib/runner.int.test.ts

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,37 +3,29 @@ import path from 'node:path';
33
import process from 'node:process';
44
import { fileURLToPath } from 'node:url';
55
import { type MockInstance, describe, expect, it } from 'vitest';
6-
import type {
7-
AuditOutput,
8-
AuditOutputs,
9-
Issue,
10-
RunnerFilesPaths,
6+
import {
7+
type Audit,
8+
type AuditOutput,
9+
type AuditOutputs,
10+
DEFAULT_PERSIST_OUTPUT_DIR,
11+
type Issue,
1112
} from '@code-pushup/models';
1213
import { osAgnosticAuditOutputs } from '@code-pushup/test-utils';
13-
import { readJsonFile } from '@code-pushup/utils';
1414
import type { ESLintTarget } from './config.js';
1515
import { listAuditsAndGroups } from './meta/index.js';
16-
import { createRunnerConfig, executeRunner } from './runner/index.js';
16+
import { createRunnerFunction } from './runner/index.js';
1717

1818
describe('executeRunner', () => {
1919
let cwdSpy: MockInstance<[], string>;
2020
let platformSpy: MockInstance<[], NodeJS.Platform>;
2121

22-
const createPluginConfig = async (
22+
const prepareRunnerArgs = async (
2323
eslintrc: ESLintTarget['eslintrc'],
24-
): Promise<RunnerFilesPaths> => {
24+
): Promise<{ audits: Audit[]; targets: ESLintTarget[] }> => {
2525
const patterns = ['src/**/*.js', 'src/**/*.jsx'];
2626
const targets: ESLintTarget[] = [{ eslintrc, patterns }];
2727
const { audits } = await listAuditsAndGroups(targets);
28-
const { outputFile, configFile } = await createRunnerConfig(
29-
'bin.js',
30-
audits,
31-
targets,
32-
);
33-
return {
34-
runnerOutputPath: outputFile,
35-
runnerConfigPath: configFile!,
36-
};
28+
return { audits, targets };
3729
};
3830

3931
const appDir = path.join(
@@ -57,24 +49,23 @@ describe('executeRunner', () => {
5749
});
5850

5951
it('should execute ESLint and create audit results for React application', async () => {
60-
const runnerPaths = await createPluginConfig('eslint.config.js');
61-
await executeRunner(runnerPaths);
62-
63-
const json = await readJsonFile<AuditOutputs>(runnerPaths.runnerOutputPath);
64-
expect(osAgnosticAuditOutputs(json)).toMatchSnapshot();
52+
const args = await prepareRunnerArgs('eslint.config.js');
53+
const runnerFn = await createRunnerFunction(args);
54+
const res = (await runnerFn({
55+
outputDir: DEFAULT_PERSIST_OUTPUT_DIR,
56+
})) as AuditOutputs;
57+
expect(osAgnosticAuditOutputs(res)).toMatchSnapshot();
6558
});
6659

6760
it.skipIf(process.platform === 'win32')(
6861
'should execute runner with custom config using @code-pushup/eslint-config',
6962
async () => {
70-
const runnerPaths = await createPluginConfig(
71-
'code-pushup.eslint.config.mjs',
72-
);
73-
await executeRunner(runnerPaths);
63+
const eslintTarget = 'code-pushup.eslint.config.mjs';
64+
const runnerFn = await createRunnerFunction({
65+
...(await prepareRunnerArgs(eslintTarget)),
66+
});
7467

75-
const json = await readJsonFile<AuditOutput[]>(
76-
runnerPaths.runnerOutputPath,
77-
);
68+
const json = await runnerFn({ outputDir: DEFAULT_PERSIST_OUTPUT_DIR });
7869
// expect warnings from unicorn/filename-case rule from default config
7970
expect(json).toContainEqual(
8071
expect.objectContaining<Partial<AuditOutput>>({

0 commit comments

Comments
 (0)