Skip to content

Commit b9574de

Browse files
committed
fix: nest files with timestamp for plugin runners
1 parent 29cf02a commit b9574de

File tree

18 files changed

+203
-106
lines changed

18 files changed

+203
-106
lines changed

packages/models/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,11 @@ export {
9999
onProgressSchema,
100100
runnerConfigSchema,
101101
runnerFunctionSchema,
102+
runnerFilesPathsSchema,
102103
type OnProgress,
103104
type RunnerConfig,
104105
type RunnerFunction,
106+
type RunnerFilesPaths,
105107
} from './lib/runner-config.js';
106108
export {
107109
tableAlignmentSchema,

packages/models/src/lib/runner-config.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,10 @@ export const runnerFunctionSchema = z
3737
.returns(z.union([auditOutputsSchema, z.promise(auditOutputsSchema)]));
3838

3939
export type RunnerFunction = z.infer<typeof runnerFunctionSchema>;
40+
41+
export const runnerFilesPathsSchema = z.object({
42+
runnerConfigPath: filePathSchema.describe('Runner config path'),
43+
runnerOutputPath: filePathSchema.describe('Runner output path'),
44+
});
45+
46+
export type RunnerFilesPaths = z.infer<typeof runnerFilesPathsSchema>;

packages/plugin-coverage/src/bin.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
import process from 'node:process';
2+
import { Parser } from 'yargs/helpers';
13
import { executeRunner } from './lib/runner/index.js';
24

3-
await executeRunner();
5+
const { runnerConfigPath, runnerOutputPath } = Parser(process.argv);
6+
7+
await executeRunner({ runnerConfigPath, runnerOutputPath });
Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1 @@
1-
import path from 'node:path';
2-
import { pluginWorkDir } from '@code-pushup/utils';
3-
4-
export const WORKDIR = pluginWorkDir('coverage');
5-
export const RUNNER_OUTPUT_PATH = path.join(WORKDIR, 'runner-output.json');
6-
export const PLUGIN_CONFIG_PATH = path.join(
7-
process.cwd(),
8-
WORKDIR,
9-
'plugin-config.json',
10-
);
11-
121
export const INVALID_FUNCTION_NAME = '(empty-report)';

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

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,31 @@
11
import { bold } from 'ansis';
22
import { writeFile } from 'node:fs/promises';
33
import path from 'node:path';
4-
import type { AuditOutputs, RunnerConfig } from '@code-pushup/models';
4+
import type {
5+
AuditOutputs,
6+
RunnerConfig,
7+
RunnerFilesPaths,
8+
} from '@code-pushup/models';
59
import {
610
ProcessError,
11+
createRunnerFiles,
712
ensureDirectoryExists,
813
executeProcess,
914
filePathToCliArg,
15+
objectToCliArgs,
1016
readJsonFile,
1117
ui,
1218
} from '@code-pushup/utils';
1319
import type { FinalCoveragePluginConfig } from '../config.js';
1420
import { applyMaxScoreAboveThreshold } from '../utils.js';
15-
import { PLUGIN_CONFIG_PATH, RUNNER_OUTPUT_PATH } from './constants.js';
1621
import { lcovResultsToAuditOutputs } from './lcov/lcov-runner.js';
1722

18-
export async function executeRunner(): Promise<void> {
23+
export async function executeRunner({
24+
runnerConfigPath,
25+
runnerOutputPath,
26+
}: RunnerFilesPaths): Promise<void> {
1927
const { reports, coverageToolCommand, coverageTypes } =
20-
await readJsonFile<FinalCoveragePluginConfig>(PLUGIN_CONFIG_PATH);
28+
await readJsonFile<FinalCoveragePluginConfig>(runnerConfigPath);
2129

2230
// Run coverage tool if provided
2331
if (coverageToolCommand != null) {
@@ -41,24 +49,29 @@ export async function executeRunner(): Promise<void> {
4149
// Calculate coverage from LCOV results
4250
const auditOutputs = await lcovResultsToAuditOutputs(reports, coverageTypes);
4351

44-
await ensureDirectoryExists(path.dirname(RUNNER_OUTPUT_PATH));
45-
await writeFile(RUNNER_OUTPUT_PATH, JSON.stringify(auditOutputs));
52+
await ensureDirectoryExists(path.dirname(runnerOutputPath));
53+
await writeFile(runnerOutputPath, JSON.stringify(auditOutputs));
4654
}
4755

4856
export async function createRunnerConfig(
4957
scriptPath: string,
5058
config: FinalCoveragePluginConfig,
5159
): Promise<RunnerConfig> {
5260
// Create JSON config for executeRunner
53-
await ensureDirectoryExists(path.dirname(PLUGIN_CONFIG_PATH));
54-
await writeFile(PLUGIN_CONFIG_PATH, JSON.stringify(config));
61+
const { runnerConfigPath, runnerOutputPath } = await createRunnerFiles(
62+
'coverage',
63+
JSON.stringify(config),
64+
);
5565

5666
const threshold = config.perfectScoreThreshold;
5767

5868
return {
5969
command: 'node',
60-
args: [filePathToCliArg(scriptPath)],
61-
outputFile: RUNNER_OUTPUT_PATH,
70+
args: [
71+
filePathToCliArg(scriptPath),
72+
...objectToCliArgs({ runnerConfigPath, runnerOutputPath }),
73+
],
74+
outputFile: runnerOutputPath,
6275
...(threshold != null && {
6376
outputTransform: outputs =>
6477
applyMaxScoreAboveThreshold(outputs as AuditOutputs, threshold),

packages/plugin-coverage/src/lib/runner/runner.integration.test.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,12 @@
1-
import { writeFile } from 'node:fs/promises';
21
import path from 'node:path';
32
import { fileURLToPath } from 'node:url';
4-
import { describe, it } from 'vitest';
53
import type {
64
AuditOutput,
75
AuditOutputs,
86
RunnerConfig,
97
} from '@code-pushup/models';
10-
import { readJsonFile, removeDirectoryIfExists } from '@code-pushup/utils';
8+
import { createRunnerFiles, readJsonFile } from '@code-pushup/utils';
119
import type { FinalCoveragePluginConfig } from '../config.js';
12-
import {
13-
PLUGIN_CONFIG_PATH,
14-
RUNNER_OUTPUT_PATH,
15-
WORKDIR,
16-
} from './constants.js';
1710
import { createRunnerConfig, executeRunner } from './index.js';
1811

1912
describe('createRunnerConfig', () => {
@@ -25,26 +18,32 @@ describe('createRunnerConfig', () => {
2518
});
2619
expect(runnerConfig).toStrictEqual<RunnerConfig>({
2720
command: 'node',
28-
args: ['"executeRunner.ts"'],
21+
args: [
22+
'"executeRunner.ts"',
23+
expect.stringContaining('plugin-config.json'),
24+
expect.stringContaining('runner-output.json'),
25+
],
2926
outputTransform: expect.any(Function),
3027
outputFile: expect.stringContaining('runner-output.json'),
3128
});
3229
});
3330

3431
it('should provide plugin config to runner in JSON file', async () => {
35-
await removeDirectoryIfExists(WORKDIR);
36-
3732
const pluginConfig: FinalCoveragePluginConfig = {
3833
coverageTypes: ['line'],
3934
reports: ['coverage/lcov.info'],
4035
coverageToolCommand: { command: 'npm', args: ['run', 'test'] },
4136
perfectScoreThreshold: 85,
4237
};
4338

44-
await createRunnerConfig('executeRunner.ts', pluginConfig);
39+
const { outputFile } = await createRunnerConfig(
40+
'executeRunner.ts',
41+
pluginConfig,
42+
);
4543

46-
const config =
47-
await readJsonFile<FinalCoveragePluginConfig>(PLUGIN_CONFIG_PATH);
44+
const config = await readJsonFile<FinalCoveragePluginConfig>(
45+
outputFile.replace('runner-output.json', 'plugin-config.json'),
46+
);
4847
expect(config).toStrictEqual(pluginConfig);
4948
});
5049
});
@@ -65,10 +64,15 @@ describe('executeRunner', () => {
6564
coverageTypes: ['line'],
6665
};
6766

68-
await writeFile(PLUGIN_CONFIG_PATH, JSON.stringify(config));
69-
await executeRunner();
67+
const runnerFiles = await createRunnerFiles(
68+
'coverage',
69+
JSON.stringify(config),
70+
);
71+
await executeRunner(runnerFiles);
7072

71-
const results = await readJsonFile<AuditOutputs>(RUNNER_OUTPUT_PATH);
73+
const results = await readJsonFile<AuditOutputs>(
74+
runnerFiles.runnerOutputPath,
75+
);
7276
expect(results).toStrictEqual([
7377
expect.objectContaining({
7478
slug: 'line-coverage',

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,11 @@ exports[`eslintPlugin > should initialize ESLint plugin for React application 1`
349349
"runner": {
350350
"args": [
351351
""<dirname>/bin.js"",
352+
"--runnerConfigPath="<dirname>/node_modules/.code-pushup/eslint/<timestamp>/plugin-config.json"",
353+
"--runnerOutputPath="<dirname>/node_modules/.code-pushup/eslint/<timestamp>/runner-output.json"",
352354
],
353355
"command": "node",
354-
"outputFile": "node_modules/.code-pushup/eslint/runner-output.json",
356+
"outputFile": "<dirname>/node_modules/.code-pushup/eslint/<timestamp>/runner-output.json",
355357
},
356358
"slug": "eslint",
357359
"title": "ESLint",

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import os from 'node:os';
22
import path from 'node:path';
3+
import process from 'node:process';
34
import { fileURLToPath } from 'node:url';
45
import type { MockInstance } from 'vitest';
56
import type { Audit, PluginConfig, RunnerConfig } from '@code-pushup/models';
@@ -18,10 +19,22 @@ describe('eslintPlugin', () => {
1819
...plugin,
1920
runner: {
2021
...(plugin.runner as RunnerConfig),
21-
args: (plugin.runner as RunnerConfig).args?.map(arg =>
22-
toUnixPath(arg.replace(path.dirname(thisDir), '<dirname>')),
23-
),
24-
outputFile: toUnixPath((plugin.runner as RunnerConfig).outputFile),
22+
args: (plugin.runner as RunnerConfig).args?.map(arg => {
23+
if (arg.includes('bin.js')) {
24+
return toUnixPath(arg.replace(path.dirname(thisDir), '<dirname>'));
25+
}
26+
27+
return toUnixPath(arg.replace(process.cwd(), '<dirname>')).replace(
28+
/\/eslint\/\d+\//,
29+
'/eslint/<timestamp>/',
30+
);
31+
}),
32+
outputFile: toUnixPath(
33+
(plugin.runner as RunnerConfig).outputFile.replace(
34+
process.cwd(),
35+
'<dirname>',
36+
),
37+
).replace(/\/eslint\/\d+\//, '/eslint/<timestamp>/'),
2538
},
2639
});
2740

@@ -38,6 +51,7 @@ describe('eslintPlugin', () => {
3851

3952
it('should initialize ESLint plugin for React application', async () => {
4053
cwdSpy.mockReturnValue(path.join(fixturesDir, 'todos-app'));
54+
4155
const plugin = await eslintPlugin({
4256
eslintrc: 'eslint.config.js',
4357
patterns: ['src/**/*.js', 'src/**/*.jsx'],

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export { detectConfigVersion, type ConfigFormat } from './versions/index.js';
1313

1414
export async function listAuditsAndGroups(
1515
targets: ESLintTarget[],
16-
customGroups: CustomGroup[] | undefined,
16+
customGroups?: CustomGroup[] | undefined,
1717
): Promise<{ audits: Audit[]; groups: Group[] }> {
1818
const rules = await listRules(targets);
1919

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

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,36 @@ import os from 'node:os';
22
import path from 'node:path';
33
import { fileURLToPath } from 'node:url';
44
import { type MockInstance, describe, expect, it } from 'vitest';
5-
import type { AuditOutput, AuditOutputs, Issue } from '@code-pushup/models';
5+
import type {
6+
AuditOutput,
7+
AuditOutputs,
8+
Issue,
9+
RunnerFilesPaths,
10+
} from '@code-pushup/models';
611
import { osAgnosticAuditOutputs } from '@code-pushup/test-utils';
712
import { readJsonFile } from '@code-pushup/utils';
813
import type { ESLintTarget } from './config.js';
914
import { listAuditsAndGroups } from './meta/index.js';
10-
import {
11-
RUNNER_OUTPUT_PATH,
12-
createRunnerConfig,
13-
executeRunner,
14-
} from './runner/index.js';
15+
import { createRunnerConfig, executeRunner } from './runner/index.js';
1516

1617
describe('executeRunner', () => {
1718
let cwdSpy: MockInstance<[], string>;
1819
let platformSpy: MockInstance<[], NodeJS.Platform>;
1920

20-
const createPluginConfig = async (eslintrc: ESLintTarget['eslintrc']) => {
21+
const createPluginConfig = async (
22+
eslintrc: ESLintTarget['eslintrc'],
23+
): Promise<RunnerFilesPaths> => {
2124
const patterns = ['src/**/*.js', 'src/**/*.jsx'];
2225
const targets: ESLintTarget[] = [{ eslintrc, patterns }];
2326
const { audits } = await listAuditsAndGroups(targets);
24-
await createRunnerConfig('bin.js', audits, targets);
27+
const { outputFile } = await createRunnerConfig('bin.js', audits, targets);
28+
return {
29+
runnerOutputPath: outputFile,
30+
runnerConfigPath: outputFile.replace(
31+
'runner-output.json',
32+
'plugin-config.json',
33+
),
34+
};
2535
};
2636

2737
const appDir = path.join(
@@ -45,18 +55,22 @@ describe('executeRunner', () => {
4555
});
4656

4757
it('should execute ESLint and create audit results for React application', async () => {
48-
await createPluginConfig('eslint.config.js');
49-
await executeRunner();
58+
const runnerPaths = await createPluginConfig('eslint.config.js');
59+
await executeRunner(runnerPaths);
5060

51-
const json = await readJsonFile<AuditOutputs>(RUNNER_OUTPUT_PATH);
61+
const json = await readJsonFile<AuditOutputs>(runnerPaths.runnerOutputPath);
5262
expect(osAgnosticAuditOutputs(json)).toMatchSnapshot();
5363
});
5464

5565
it('should execute runner with custom config using @code-pushup/eslint-config', async () => {
56-
await createPluginConfig('code-pushup.eslint.config.mjs');
57-
await executeRunner();
66+
const runnerPaths = await createPluginConfig(
67+
'code-pushup.eslint.config.mjs',
68+
);
69+
await executeRunner(runnerPaths);
5870

59-
const json = await readJsonFile<AuditOutput[]>(RUNNER_OUTPUT_PATH);
71+
const json = await readJsonFile<AuditOutput[]>(
72+
runnerPaths.runnerOutputPath,
73+
);
6074
// expect warnings from unicorn/filename-case rule from default config
6175
expect(json).toContainEqual(
6276
expect.objectContaining<Partial<AuditOutput>>({

0 commit comments

Comments
 (0)