Skip to content

Commit 7d5c99e

Browse files
committed
feat(models): setup types and parser with zod
1 parent 97a4a89 commit 7d5c99e

15 files changed

+562
-1
lines changed

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"dist/packages/*"
99
],
1010
"dependencies": {
11-
"bundle-require": "^4.0.1"
11+
"bundle-require": "^4.0.1",
12+
"zod": "^3.22.1"
1213
},
1314
"devDependencies": {
1415
"@nx/esbuild": "16.7.4",

packages/models/.eslintrc.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"extends": ["../../.eslintrc.json"],
3+
"ignorePatterns": ["!**/*"],
4+
"overrides": [
5+
{
6+
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
7+
"rules": {}
8+
},
9+
{
10+
"files": ["*.ts", "*.tsx"],
11+
"rules": {}
12+
},
13+
{
14+
"files": ["*.js", "*.jsx"],
15+
"rules": {}
16+
}
17+
]
18+
}

packages/models/README.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# models
2+
3+
Model definitions for the CLI configuration as well as plugin types and respective parser.
4+
5+
## Usage
6+
7+
```ts
8+
import { CoreConfigSchema, pluginConfigSchema } from '@qm/cli-models';
9+
10+
export default {
11+
// ...
12+
plugins: [pluginConfigSchema.parse({ ... })],
13+
} satisfies CoreConfigSchema;
14+
```

packages/models/project.json

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "@quality-metrics/cli/models",
3+
"$schema": "../../node_modules/nx/schemas/project-schema.json",
4+
"sourceRoot": "libs/cli-models/src",
5+
"projectType": "library",
6+
"targets": {
7+
"lint": {
8+
"executor": "@nx/linter:eslint",
9+
"outputs": ["{options.outputFile}"],
10+
"options": {
11+
"lintFilePatterns": ["libs/cli-models/**/*.ts"]
12+
}
13+
},
14+
"generate-schema-from-type": {
15+
"command": "npx json-schema-to-zod -s libs/cli-models/schema.json -t libs/cli/schema.ts --deref"
16+
}
17+
},
18+
"tags": ["scope:cli", "type:util"]
19+
}

packages/models/src/index.ts

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
export { coreConfigSchema, CoreConfigSchema } from './lib/core-config';
2+
export { uploadConfigSchema, UploadConfigSchema } from './lib/upload';
3+
export { pluginConfigSchema, PluginConfigSchema } from './lib/plugins';
4+
export { runnerOutputSchema, RunnerOutputSchema } from './lib/plugin-output';
5+
export { persistConfigSchema, PersistConfigSchema } from './lib/persist';
6+
export {
7+
categoryConfigSchema,
8+
CategoryConfigSchema,
9+
} from './lib/category-config';
10+
export { budgetSchema, BudgetSchema } from './lib/budgets';
11+
export {
12+
globalCliArgsSchema,
13+
GlobalCliArgsSchema,
14+
} from './lib/global-cli-options';

packages/models/src/lib/budgets.ts

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { z } from 'zod';
2+
3+
/**
4+
* Define Zod schema for the Budget type
5+
*
6+
* @example
7+
*
8+
* // Type assertion for valid budget data
9+
* const validBudget = {
10+
* ref: 'eslint#max-lines',
11+
* minScore: 0.8,
12+
* maxWarnings: 5,
13+
* maxValue: 1000,
14+
* } as z.infer<typeof budgetSchema>;
15+
*
16+
* // Validate the data against the schema
17+
* const validationResult = budgetSchema.safeParse(validBudget);
18+
*
19+
* if (validationResult.success) {
20+
* console.log('Valid budget:', validationResult.data);
21+
* } else {
22+
* console.error('Invalid budget:', validationResult.error);
23+
* }
24+
*
25+
**/
26+
export const budgetSchema = z.object({
27+
ref: z.string({
28+
description:
29+
"reference to audit ('eslint#max-lines') or category ('categories:performance')",
30+
}),
31+
minScore: z
32+
.number({
33+
description: 'fail assertion if score too low',
34+
})
35+
.min(0)
36+
.max(1)
37+
.optional(),
38+
maxWarnings: z
39+
.number({
40+
description: 'fail assertion if too many warnings',
41+
})
42+
.optional(),
43+
maxValue: z
44+
.number({
45+
description: 'fail assertion if value too high',
46+
})
47+
.optional(),
48+
});
49+
50+
export type BudgetSchema = z.infer<typeof budgetSchema>;
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import {z} from 'zod';
2+
3+
/**
4+
*
5+
* Define Zod schema for the CategoryConfig type
6+
*
7+
* @example
8+
*
9+
* // Example data for the CategoryConfig type
10+
* const categoryConfigData = {
11+
* slug: 'performance',
12+
* title: 'Performance Metrics',
13+
* description: 'This category includes performance-related metrics.',
14+
* metrics: [
15+
* { ref: 'eslint#max-lines', weight: 0.5 },
16+
* { ref: 'categories:lhci#performance', weight: 0.8 },
17+
* ],
18+
* };
19+
*
20+
* // Validate the data against the schema
21+
* const validationResult = categoryConfigSchema.safeParse(categoryConfigData);
22+
*
23+
* if (validationResult.success) {
24+
* console.log('Valid category config:', validationResult.data);
25+
* } else {
26+
* console.error('Invalid category config:', validationResult.error);
27+
* }
28+
*/
29+
export const categoryConfigSchema = z.object(
30+
{
31+
slug: z.string({
32+
description: 'Human-readable unique ID',
33+
}),
34+
title: z.string({
35+
description: 'Display name for the category',
36+
}),
37+
description: z
38+
.string({
39+
description: 'Optional description in Markdown format',
40+
})
41+
.optional(),
42+
metrics: z.array(
43+
z.object({
44+
ref: z.string({
45+
description:
46+
"Reference to a plugin's audit (e.g. 'eslint#max-lines') or category (e.g. 'categories:lhci#performance')",
47+
}),
48+
weight: z.number({
49+
description:
50+
'Coefficient for the given score (use weight 0 if only for display)',
51+
}).default(0).optional(),
52+
}, {description: "Array of metrics associated with the category"})
53+
),
54+
},
55+
{
56+
description: 'Weighted references to plugin-specific audits/categories',
57+
},
58+
);
59+
60+
export type CategoryConfigSchema = z.infer<typeof categoryConfigSchema>;
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { z } from 'zod';
2+
import { pluginConfigSchema } from './plugins';
3+
import { categoryConfigSchema } from './category-config';
4+
import { uploadConfigSchema } from './upload';
5+
import { budgetSchema } from './budgets';
6+
import { persistConfigSchema } from './persist';
7+
8+
/**
9+
* Define Zod schema for the CoreConfig type
10+
*
11+
* @example
12+
*
13+
* // Example data for the CoreConfig type
14+
* const coreConfigData = {
15+
* // ... populate with example data ...
16+
* };
17+
*
18+
* // Validate the data against the schema
19+
* const validationResult = coreConfigSchema.safeParse(coreConfigData);
20+
*
21+
* if (validationResult.success) {
22+
* console.log('Valid plugin config:', validationResult.data);
23+
* } else {
24+
* console.error('Invalid plugin config:', validationResult.error);
25+
* }
26+
*/
27+
export const coreConfigSchema = z.object({
28+
plugins: z.array(pluginConfigSchema, {
29+
description: 'list of plugins to be used (built-in, 3rd party, or custom)',
30+
}),
31+
/** portal configuration for persisting results */
32+
persist: persistConfigSchema,
33+
/** portal configuration for uploading results */
34+
upload: uploadConfigSchema.optional(),
35+
categories: z.array(categoryConfigSchema, {
36+
description: 'categorization of individual audits',
37+
}),
38+
budgets: z
39+
.array(budgetSchema, {
40+
description: 'budget rules for assertion',
41+
})
42+
.optional(),
43+
});
44+
45+
export type CoreConfigSchema = z.infer<typeof coreConfigSchema>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { z } from 'zod';
2+
3+
/**
4+
* Define Zod schema for the GlobalCliArgs type
5+
*
6+
* @example
7+
*
8+
* // Example data for the GlobalCliArgs type
9+
* const uploadConfigData = {
10+
* server: 'https://example.com/api',
11+
* apiKey: 'your-api-key',
12+
* };
13+
*
14+
* // Validate the data against the schema
15+
* const validationResult = globalCliArgsSchema.safeParse(globalCliArgsData);
16+
*
17+
* if (validationResult.success) {
18+
* console.log('Valid config:', validationResult.data);
19+
* } else {
20+
* console.error('Invalid config:', validationResult.error);
21+
* }
22+
*
23+
*/
24+
export const globalCliArgsSchema = z.object({
25+
interactive: z
26+
.boolean({
27+
description:
28+
'flag if interactivity should be considered. Useful for CI runs.',
29+
})
30+
.default(true),
31+
verbose: z
32+
.boolean({
33+
description: 'Outputs additional information for a run',
34+
})
35+
.default(false),
36+
configPath: z
37+
.string({
38+
description: "Path to config.js. defaults to 'cpu-config.js'",
39+
})
40+
.optional()
41+
.default('cpu-config.js'),
42+
});
43+
44+
export type GlobalCliArgsSchema = z.infer<typeof globalCliArgsSchema>;

packages/models/src/lib/persist.ts

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {z} from 'zod';
2+
3+
/**
4+
* Define Zod schema for the PersistConfig type
5+
*
6+
* @example
7+
*
8+
* // Example data for the UploadConfig type
9+
* const uploadConfigData = {
10+
* server: 'https://example.com/api',
11+
* apiKey: 'your-api-key',
12+
* };
13+
*
14+
* // Validate the data against the schema
15+
* const validationResult = uploadConfigSchema.safeParse(uploadConfigData);
16+
*
17+
* if (validationResult.success) {
18+
* console.log('Valid persist config:', validationResult.data);
19+
* } else {
20+
* console.error('Invalid persist config:', validationResult.error);
21+
* }
22+
*
23+
*/
24+
export const persistConfigSchema = z.object({
25+
outputPath: z.string({
26+
description: 'Artefacts folder',
27+
}),
28+
});
29+
30+
export type PersistConfigSchema = z.infer<typeof persistConfigSchema>;

0 commit comments

Comments
 (0)