diff --git a/packages/models/docs/models-reference.md b/packages/models/docs/models-reference.md index 2100fd650..7bdd9d232 100644 --- a/packages/models/docs/models-reference.md +++ b/packages/models/docs/models-reference.md @@ -1,5 +1,12 @@ # Code PushUp models reference +## ArtifactGenerationCommand + +_Union of the following possible types:_ + +- `string` (_min length: 1_) +- _Object with properties:_ + ## AuditDetails Detailed information @@ -1227,6 +1234,17 @@ _Object containing the following properties:_ _All properties are optional._ +## PluginArtifactOptions + +_Object containing the following properties:_ + +| Property | Type | +| :------------------------- | :------------------------------------------------------ | +| `generateArtifactsCommand` | [ArtifactGenerationCommand](#artifactgenerationcommand) | +| **`artifactsPaths`** (\*) | `string` _or_ `Array` (_min: 1_) | + +_(\*) Required._ + ## PluginConfig _Object containing the following properties:_ diff --git a/packages/models/src/index.ts b/packages/models/src/index.ts index fcef50523..f0cd740f3 100644 --- a/packages/models/src/index.ts +++ b/packages/models/src/index.ts @@ -132,3 +132,7 @@ export { type Tree, } from './lib/tree.js'; export { uploadConfigSchema, type UploadConfig } from './lib/upload-config.js'; +export { + artifactGenerationCommandSchema, + pluginArtifactOptionsSchema, +} from './lib/configuration.js'; diff --git a/packages/models/src/lib/configuration.ts b/packages/models/src/lib/configuration.ts new file mode 100644 index 000000000..72eca414d --- /dev/null +++ b/packages/models/src/lib/configuration.ts @@ -0,0 +1,19 @@ +import { z } from 'zod'; + +/** + * Generic schema for a tool command configuration, reusable across plugins. + */ +export const artifactGenerationCommandSchema = z.union([ + z.string({ description: 'Generate artifact files' }).min(1), + z.object({ + command: z.string({ description: 'Generate artifact files' }).min(1), + args: z.array(z.string()).optional(), + }), +]); + +export const pluginArtifactOptionsSchema = z.object({ + generateArtifactsCommand: artifactGenerationCommandSchema.optional(), + artifactsPaths: z.union([z.string(), z.array(z.string()).min(1)]), +}); + +export type PluginArtifactOptions = z.infer; diff --git a/packages/models/src/lib/configuration.unit.test.ts b/packages/models/src/lib/configuration.unit.test.ts new file mode 100644 index 000000000..8e66e2820 --- /dev/null +++ b/packages/models/src/lib/configuration.unit.test.ts @@ -0,0 +1,127 @@ +import { describe, expect, it } from 'vitest'; +import { + artifactGenerationCommandSchema, + pluginArtifactOptionsSchema, +} from './configuration.js'; + +describe('artifactGenerationCommandSchema', () => { + it('should validate a command with required fields', () => { + const data = { command: 'npx' }; + expect(artifactGenerationCommandSchema.safeParse(data)).toStrictEqual({ + success: true, + data: { command: 'npx' }, + }); + }); + + it('should validate a command with args', () => { + const data = { command: 'npx', args: ['eslint', 'src/'] }; + expect(artifactGenerationCommandSchema.safeParse(data)).toStrictEqual({ + success: true, + data: { command: 'npx', args: ['eslint', 'src/'] }, + }); + }); + + it('should fail if command is missing', () => { + const data = { args: ['eslint', 'src/'] }; + expect(artifactGenerationCommandSchema.safeParse(data).success).toBe(false); + }); + + it('should fail if command is empty', () => { + const data = { command: '' }; + expect(artifactGenerationCommandSchema.safeParse(data).success).toBe(false); + }); + + it('should fail if args is not an array of strings', () => { + const data = { command: 'npx', args: [123, true] }; + expect(artifactGenerationCommandSchema.safeParse(data).success).toBe(false); + }); +}); + +describe('pluginArtifactOptionsSchema', () => { + it('should validate with only artifactsPaths as string', () => { + const data = { artifactsPaths: 'dist/report.json' }; + expect(pluginArtifactOptionsSchema.safeParse(data)).toStrictEqual({ + success: true, + data: { + artifactsPaths: 'dist/report.json', + }, + }); + }); + + it('should validate with artifactsPaths as array of strings', () => { + const data = { artifactsPaths: ['dist/report.json', 'dist/summary.json'] }; + expect(pluginArtifactOptionsSchema.safeParse(data)).toStrictEqual({ + success: true, + data: { + artifactsPaths: ['dist/report.json', 'dist/summary.json'], + }, + }); + }); + + it('should fail if artifactsPaths is an empty array', () => { + const data = { artifactsPaths: [] }; + expect(pluginArtifactOptionsSchema.safeParse(data).success).toBe(false); + }); + + it('should validate with generateArtifactsCommand and artifactsPaths', () => { + const data = { + generateArtifactsCommand: { command: 'npm', args: ['run', 'build'] }, + artifactsPaths: ['dist/report.json'], + }; + expect(pluginArtifactOptionsSchema.safeParse(data)).toStrictEqual({ + success: true, + data: { + generateArtifactsCommand: { command: 'npm', args: ['run', 'build'] }, + artifactsPaths: ['dist/report.json'], + }, + }); + }); + + it('should fail if artifactsPaths is missing', () => { + const data = { generateArtifactsCommand: { command: 'npm' } }; + expect(pluginArtifactOptionsSchema.safeParse(data).success).toBe(false); + }); + + it('should fail if artifactsPaths is not string or array of strings', () => { + const data = { artifactsPaths: 123 }; + expect(pluginArtifactOptionsSchema.safeParse(data).success).toBe(false); + }); + + it('should fail if generateArtifactsCommand is invalid', () => { + const data = { + generateArtifactsCommand: { command: '' }, + artifactsPaths: 'dist/report.json', + }; + expect(pluginArtifactOptionsSchema.safeParse(data).success).toBe(false); + }); + + it('should validate with generateArtifactsCommand as a string', () => { + const data = { + generateArtifactsCommand: 'yarn test --coverage', + artifactsPaths: 'coverage/lcov.info', + }; + expect(pluginArtifactOptionsSchema.safeParse(data)).toStrictEqual({ + success: true, + data: { + generateArtifactsCommand: 'yarn test --coverage', + artifactsPaths: 'coverage/lcov.info', + }, + }); + }); + + it('should fail if generateArtifactsCommand is an empty string', () => { + const data = { + generateArtifactsCommand: '', + artifactsPaths: 'coverage/lcov.info', + }; + expect(pluginArtifactOptionsSchema.safeParse(data).success).toBe(false); + }); + + it('should fail if generateArtifactsCommand is a number', () => { + const data = { + generateArtifactsCommand: 123, + artifactsPaths: 'coverage/lcov.info', + }; + expect(pluginArtifactOptionsSchema.safeParse(data).success).toBe(false); + }); +});