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:_
- `command`: `string` (_min length: 1_) - Generate artifact files
- `args`: `Array`
+
## 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);
+ });
+});