diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index 91c22fb10..eac353271 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -138,3 +138,4 @@ export { artifactGenerationCommandSchema, pluginArtifactOptionsSchema, } from './lib/configuration.js'; +export type { PluginArtifactOptions } from './lib/configuration.js'; diff --git a/packages/plugin-eslint/src/lib/config.ts b/packages/plugin-eslint/src/lib/config.ts index 4a3133d2c..2d4006205 100644 --- a/packages/plugin-eslint/src/lib/config.ts +++ b/packages/plugin-eslint/src/lib/config.ts @@ -1,4 +1,5 @@ import { z } from 'zod'; +import { pluginArtifactOptionsSchema } from '@code-pushup/models'; import { toArray } from '@code-pushup/utils'; const patternsSchema = z.union([z.string(), z.array(z.string()).min(1)], { @@ -64,5 +65,6 @@ export type CustomGroup = z.infer; export const eslintPluginOptionsSchema = z.object({ groups: z.array(customGroupSchema).optional(), + artifacts: pluginArtifactOptionsSchema.optional(), }); export type ESLintPluginOptions = z.infer; diff --git a/packages/plugin-eslint/src/lib/eslint-plugin.int.test.ts b/packages/plugin-eslint/src/lib/eslint-plugin.int.test.ts index 245611811..4a466d7fc 100644 --- a/packages/plugin-eslint/src/lib/eslint-plugin.int.test.ts +++ b/packages/plugin-eslint/src/lib/eslint-plugin.int.test.ts @@ -160,4 +160,30 @@ describe('eslintPlugin', () => { eslintPlugin({ eslintrc: '.eslintrc.yml', patterns: '**/*.js' }), ).rejects.toThrow(/Failed to load url .*\.eslintrc.yml/); }); + + it('should initialize with artifact options', async () => { + cwdSpy.mockReturnValue(path.join(fixturesDir, 'todos-app')); + const plugin = await eslintPlugin( + { + eslintrc: 'eslint.config.js', + patterns: ['src/**/*.js'], + }, + { + artifacts: { + artifactsPaths: './artifacts/eslint-output.json', + generateArtifactsCommand: 'echo "Generating artifacts"', + }, + }, + ); + + expect(typeof plugin.runner).toBe('object'); + const runnerConfig = plugin.runner as { + command: string; + args?: string[]; + outputFile: string; + }; + expect(runnerConfig.command).toBe('node'); + expect(runnerConfig.args).toContain('echo "Generating artifacts"'); + expect(runnerConfig.outputFile).toBe('./artifacts/eslint-output.json'); + }); }); diff --git a/packages/plugin-eslint/src/lib/eslint-plugin.ts b/packages/plugin-eslint/src/lib/eslint-plugin.ts index 285a39e50..7f4513557 100644 --- a/packages/plugin-eslint/src/lib/eslint-plugin.ts +++ b/packages/plugin-eslint/src/lib/eslint-plugin.ts @@ -41,12 +41,14 @@ export async function eslintPlugin( schemaType: 'ESLint plugin config', }); - const customGroups = options + const parsedOptions = options ? parseSchema(eslintPluginOptionsSchema, options, { schemaType: 'ESLint plugin options', - }).groups + }) : undefined; + const customGroups = parsedOptions?.groups; + const { audits, groups } = await listAuditsAndGroups(targets, customGroups); const runnerScriptPath = path.join( @@ -71,6 +73,11 @@ export async function eslintPlugin( audits, groups, - runner: await createRunnerConfig(runnerScriptPath, audits, targets), + runner: await createRunnerConfig( + runnerScriptPath, + audits, + targets, + parsedOptions?.artifacts, + ), }; } diff --git a/packages/plugin-eslint/src/lib/runner/index.ts b/packages/plugin-eslint/src/lib/runner/index.ts index 7e5e8ee9d..ebe0f1a6f 100644 --- a/packages/plugin-eslint/src/lib/runner/index.ts +++ b/packages/plugin-eslint/src/lib/runner/index.ts @@ -3,9 +3,11 @@ import path from 'node:path'; import type { Audit, AuditOutput, + PluginArtifactOptions, RunnerConfig, RunnerFilesPaths, } from '@code-pushup/models'; +import { pluginArtifactOptionsSchema } from '@code-pushup/models'; import { asyncSequential, createRunnerFiles, @@ -51,23 +53,60 @@ export async function createRunnerConfig( scriptPath: string, audits: Audit[], targets: ESLintTarget[], + artifactOptions?: PluginArtifactOptions, ): Promise { + const parsedOptions = artifactOptions + ? pluginArtifactOptionsSchema.parse(artifactOptions) + : undefined; + const config: ESLintPluginRunnerConfig = { targets, - slugs: audits.map(audit => audit.slug), + slugs: audits.map(a => a.slug), }; - const { runnerConfigPath, runnerOutputPath } = await createRunnerFiles( - 'eslint', - JSON.stringify(config), - ); + + const { runnerConfigPath, runnerOutputPath } = parsedOptions + ? await createCustomRunnerPaths(parsedOptions, config) + : await createRunnerFiles('eslint', JSON.stringify(config)); + + const args = [ + filePathToCliArg(scriptPath), + ...objectToCliArgs({ runnerConfigPath, runnerOutputPath }), + ...resolveCommandArgs(parsedOptions?.generateArtifactsCommand), + ]; return { command: 'node', - args: [ - filePathToCliArg(scriptPath), - ...objectToCliArgs({ runnerConfigPath, runnerOutputPath }), - ], + args, configFile: runnerConfigPath, outputFile: runnerOutputPath, }; } + +async function createCustomRunnerPaths( + options: PluginArtifactOptions, + config: ESLintPluginRunnerConfig, +): Promise { + const artifactPaths = Array.isArray(options.artifactsPaths) + ? options.artifactsPaths + : [options.artifactsPaths]; + + const runnerOutputPath = artifactPaths[0] ?? ''; + const runnerConfigPath = path.join( + path.dirname(runnerOutputPath), + 'plugin-config.json', + ); + + await ensureDirectoryExists(path.dirname(runnerConfigPath)); + await writeFile(runnerConfigPath, JSON.stringify(config)); + + return { runnerConfigPath, runnerOutputPath }; +} + +function resolveCommandArgs( + command?: string | { command: string; args?: string[] }, +): string[] { + if (!command) return []; + return typeof command === 'string' + ? [command] + : [command.command, ...(command.args ?? [])]; +}