Skip to content

feat(models): add generic artifact generation to enable caching #1023

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jul 21, 2025
Merged
168 changes: 95 additions & 73 deletions packages/models/docs/models-reference.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions packages/models/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,7 @@ export {
type Tree,
} from './lib/tree.js';
export { uploadConfigSchema, type UploadConfig } from './lib/upload-config.js';
export {
artifactGenerationCommand as toolCommandSchema,
pluginArtefactOptionsSchema,
} from './lib/configuration';
24 changes: 24 additions & 0 deletions packages/models/src/lib/configuration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { z } from 'zod';

/**
* Generic schema for a tool command configuration, reusable across plugins.
*/
export const artifactGenerationCommand = z.object({
command: z.string({ description: 'Command to run the tool.' }).min(1),
args: z
.array(z.string(), {
description: 'Arguments to be passed to the tool.',
})
.optional(),
});

export type ArtefactGenerationCommand = z.infer<
typeof artifactGenerationCommand
>;

export const pluginArtefactOptionsSchema = z.object({
generateArtefacts: artifactGenerationCommand.optional(),
artefactsPaths: z.union([z.string(), z.array(z.string())]),
});

export type PluginArtefactOptions = z.infer<typeof pluginArtefactOptionsSchema>;
95 changes: 95 additions & 0 deletions packages/models/src/lib/configuration.unit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import { describe, expect, it } from 'vitest';
import {
artifactGenerationCommand,
pluginArtefactOptionsSchema,
} from './configuration.js';

describe('artifactGenerationCommand', () => {
it('should validate a command with required fields', () => {
const data = { command: 'npx' };
const result = artifactGenerationCommand.safeParse(data);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.command).toBe('npx');
expect(result.data.args).toBeUndefined();
}
});

it('should validate a command with args', () => {
const data = { command: 'npx', args: ['eslint', 'src/'] };
const result = artifactGenerationCommand.safeParse(data);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.command).toBe('npx');
expect(result.data.args).toEqual(['eslint', 'src/']);
}
});

it('should fail if command is missing', () => {
const data = { args: ['eslint', 'src/'] };
const result = artifactGenerationCommand.safeParse(data);
expect(result.success).toBe(false);
});

it('should fail if command is empty', () => {
const data = { command: '' };
const result = artifactGenerationCommand.safeParse(data);
expect(result.success).toBe(false);
});

it('should fail if args is not an array of strings', () => {
const data = { command: 'npx', args: [123, true] };
const result = artifactGenerationCommand.safeParse(data);
expect(result.success).toBe(false);
});
});

describe('pluginArtefactOptionsSchema', () => {
it('should validate with only artefactsPaths as string', () => {
const data = { artefactsPaths: 'dist/report.json' };
const { success } = pluginArtefactOptionsSchema.safeParse(data);
expect(success).toBe(true);
});

it('should validate with artefactsPaths as array of strings', () => {
const data = { artefactsPaths: ['dist/report.json', 'dist/summary.json'] };
const { success } = pluginArtefactOptionsSchema.safeParse(data);
expect(success).toBe(true);
});

it('should fail if artefactsPaths is an empty array', () => {
const data = { artefactsPaths: [] };
const { success } = pluginArtefactOptionsSchema.safeParse(data);
expect(success).toBe(false);
});

it('should validate with generateArtefacts and artefactsPaths', () => {
const data = {
generateArtefacts: { command: 'npm', args: ['run', 'build'] },
artefactsPaths: ['dist/report.json'],
};
const { success } = pluginArtefactOptionsSchema.safeParse(data);
expect(success).toBe(true);
});

it('should fail if artefactsPaths is missing', () => {
const data = { generateArtefacts: { command: 'npm' } };
const { success } = pluginArtefactOptionsSchema.safeParse(data);
expect(success).toBe(false);
});

it('should fail if artefactsPaths is not string or array of strings', () => {
const data = { artefactsPaths: 123 };
const { success } = pluginArtefactOptionsSchema.safeParse(data);
expect(success).toBe(false);
});

it('should fail if generateArtefacts is invalid', () => {
const data = {
generateArtefacts: { command: '' },
artefactsPaths: 'dist/report.json',
};
const { success } = pluginArtefactOptionsSchema.safeParse(data);
expect(success).toBe(false);
});
});
14 changes: 2 additions & 12 deletions packages/plugin-coverage/src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { z } from 'zod';
import { toolCommandSchema } from '@code-pushup/models';

export const coverageTypeSchema = z.enum(['function', 'branch', 'line']);
export type CoverageType = z.infer<typeof coverageTypeSchema>;
Expand Down Expand Up @@ -26,18 +27,7 @@ export const coverageResultSchema = z.union([
export type CoverageResult = z.infer<typeof coverageResultSchema>;

export const coveragePluginConfigSchema = z.object({
coverageToolCommand: z
.object({
command: z
.string({ description: 'Command to run coverage tool.' })
.min(1),
args: z
.array(z.string(), {
description: 'Arguments to be passed to the coverage tool.',
})
.optional(),
})
.optional(),
coverageToolCommand: toolCommandSchema.optional(),
continueOnCommandFail: z
.boolean({
description:
Expand Down
Loading
Loading