Skip to content

Commit 4d54550

Browse files
committed
feat(models): extend schemas with audit scoreTarget
1 parent 2ff9c60 commit 4d54550

File tree

7 files changed

+84
-25
lines changed

7 files changed

+84
-25
lines changed

packages/models/docs/models-reference.md

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ _Object containing the following properties:_
4949
| `displayValue` | Formatted value (e.g. '0.9 s', '2.1 MB') | `string` |
5050
| **`value`** (\*) | Raw numeric value | `number` (_≥0_) |
5151
| **`score`** (\*) | Value between 0 and 1 | `number` (_≥0, ≤1_) |
52+
| `scoreTarget` | Pass/fail score threshold (0-1) | `number` (_≥0, ≤1_) |
5253
| `details` | Detailed information | [AuditDetails](#auditdetails) |
5354

5455
_(\*) Required._
@@ -73,6 +74,7 @@ _Object containing the following properties:_
7374
| `displayValue` | Formatted value (e.g. '0.9 s', '2.1 MB') | `string` |
7475
| **`value`** (\*) | Raw numeric value | `number` (_≥0_) |
7576
| **`score`** (\*) | Value between 0 and 1 | `number` (_≥0, ≤1_) |
77+
| `scoreTarget` | Pass/fail score threshold (0-1) | `number` (_≥0, ≤1_) |
7678
| `details` | Detailed information | [AuditDetails](#auditdetails) |
7779

7880
_(\*) Required._
@@ -1282,20 +1284,21 @@ _(\*) Required._
12821284

12831285
_Object containing the following properties:_
12841286

1285-
| Property | Description | Type |
1286-
| :---------------- | :---------------------------------------- | :------------------------------------------------------------------- |
1287-
| `packageName` | NPM package name | `string` |
1288-
| `version` | NPM version of the package | `string` |
1289-
| **`title`** (\*) | Descriptive name | `string` (_max length: 256_) |
1290-
| `description` | Description (markdown) | `string` (_max length: 65536_) |
1291-
| `docsUrl` | Plugin documentation site | `string` (_url_) (_optional_) _or_ `''` |
1292-
| `isSkipped` | | `boolean` |
1293-
| **`slug`** (\*) | Unique plugin slug within core config | `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) |
1294-
| **`icon`** (\*) | Icon from VSCode Material Icons extension | [MaterialIcon](#materialicon) |
1295-
| **`runner`** (\*) | | [RunnerConfig](#runnerconfig) _or_ [RunnerFunction](#runnerfunction) |
1296-
| **`audits`** (\*) | List of audits maintained in a plugin | _Array of at least 1 [Audit](#audit) items_ |
1297-
| `groups` | List of groups | _Array of [Group](#group) items_ |
1298-
| `context` | Plugin-specific context data for helpers | [PluginContext](#plugincontext) |
1287+
| Property | Description | Type |
1288+
| :---------------- | :--------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------- |
1289+
| `packageName` | NPM package name | `string` |
1290+
| `version` | NPM version of the package | `string` |
1291+
| **`title`** (\*) | Descriptive name | `string` (_max length: 256_) |
1292+
| `description` | Description (markdown) | `string` (_max length: 65536_) |
1293+
| `docsUrl` | Plugin documentation site | `string` (_url_) (_optional_) _or_ `''` |
1294+
| `isSkipped` | | `boolean` |
1295+
| **`slug`** (\*) | Unique plugin slug within core config | `string` (_regex: `/^[a-z\d]+(?:-[a-z\d]+)*$/`, max length: 128_) |
1296+
| **`icon`** (\*) | Icon from VSCode Material Icons extension | [MaterialIcon](#materialicon) |
1297+
| **`runner`** (\*) | | [RunnerConfig](#runnerconfig) _or_ [RunnerFunction](#runnerfunction) |
1298+
| **`audits`** (\*) | List of audits maintained in a plugin | _Array of at least 1 [Audit](#audit) items_ |
1299+
| `groups` | List of groups | _Array of [Group](#group) items_ |
1300+
| `scoreTarget` | Score targets that trigger a perfect score. Number for all audits or record { slug: target } for specific audits | `number` (_≥0, ≤1_) (_optional_) _or_ _Object with dynamic keys of type_ `string` _and values of type_ `number` (_≥0, ≤1_) |
1301+
| `context` | Plugin-specific context data for helpers | [PluginContext](#plugincontext) |
12991302

13001303
_(\*) Required._
13011304

packages/models/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ export {
8080
type PluginConfig,
8181
type PluginContext,
8282
type PluginMeta,
83+
type PluginScoreTarget,
8384
} from './lib/plugin-config.js';
8485
export {
8586
auditReportSchema,

packages/models/src/lib/audit-output.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createDuplicateSlugsCheck } from './implementation/checks.js';
33
import {
44
nonnegativeNumberSchema,
55
scoreSchema,
6+
scoreTargetSchema,
67
slugSchema,
78
} from './implementation/schemas.js';
89
import { issueSchema } from './issue.js';
@@ -34,6 +35,7 @@ export const auditOutputSchema = z
3435
displayValue: auditDisplayValueSchema,
3536
value: auditValueSchema,
3637
score: scoreSchema,
38+
scoreTarget: scoreTargetSchema,
3739
details: auditDetailsSchema.optional(),
3840
})
3941
.describe('Audit information');

packages/models/src/lib/audit-output.unit.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,18 @@ describe('auditOutputSchema', () => {
8787
).not.toThrow();
8888
});
8989

90+
it('should accept a valid audit output with a score target', () => {
91+
expect(() =>
92+
auditOutputSchema.parse({
93+
slug: 'total-blocking-time',
94+
score: 0.91,
95+
scoreTarget: 0.9,
96+
value: 183.5,
97+
displayValue: '180 ms',
98+
} satisfies AuditOutput),
99+
).not.toThrow();
100+
});
101+
90102
it('should accept a decimal value', () => {
91103
expect(() =>
92104
auditOutputSchema.parse({

packages/models/src/lib/audit.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ export const auditSchema = z
66
.object({
77
slug: slugSchema.describe('ID (unique within plugin)'),
88
})
9-
.merge(
9+
.extend(
1010
metaSchema({
1111
titleDescription: 'Descriptive name',
1212
descriptionDescription: 'Description (markdown)',
1313
docsUrlDescription: 'Link to documentation (rationale)',
1414
description: 'List of scorable metrics for the given plugin',
1515
isSkippedDescription: 'Indicates whether the audit is skipped',
16-
}),
16+
}).shape,
1717
);
1818

1919
export type Audit = z.infer<typeof auditSchema>;

packages/models/src/lib/plugin-config.ts

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
materialIconSchema,
77
metaSchema,
88
packageVersionSchema,
9+
scoreTargetSchema,
910
slugSchema,
1011
} from './implementation/schemas.js';
1112
import { formatSlugsList, hasMissingStrings } from './implementation/utils.js';
@@ -18,31 +19,42 @@ export const pluginContextSchema = z
1819
export type PluginContext = z.infer<typeof pluginContextSchema>;
1920

2021
export const pluginMetaSchema = packageVersionSchema()
21-
.merge(
22+
.extend(
2223
metaSchema({
2324
titleDescription: 'Descriptive name',
2425
descriptionDescription: 'Description (markdown)',
2526
docsUrlDescription: 'Plugin documentation site',
2627
description: 'Plugin metadata',
27-
}),
28+
}).shape,
2829
)
29-
.merge(
30-
z.object({
31-
slug: slugSchema.describe('Unique plugin slug within core config'),
32-
icon: materialIconSchema,
33-
}),
34-
);
30+
.extend({
31+
slug: slugSchema.describe('Unique plugin slug within core config'),
32+
icon: materialIconSchema,
33+
});
3534
export type PluginMeta = z.infer<typeof pluginMetaSchema>;
3635

36+
export const pluginScoreTargetSchema = z
37+
.union([
38+
scoreTargetSchema,
39+
z.record(z.string(), scoreTargetSchema.nonoptional()),
40+
])
41+
.describe(
42+
'Score targets that trigger a perfect score. Number for all audits or record { slug: target } for specific audits',
43+
)
44+
.optional();
45+
46+
export type PluginScoreTarget = z.infer<typeof pluginScoreTargetSchema>;
47+
3748
export const pluginDataSchema = z.object({
3849
runner: z.union([runnerConfigSchema, runnerFunctionSchema]),
3950
audits: pluginAuditsSchema,
4051
groups: groupsSchema,
52+
scoreTarget: pluginScoreTargetSchema,
4153
context: pluginContextSchema,
4254
});
4355

4456
export const pluginConfigSchema = pluginMetaSchema
45-
.merge(pluginDataSchema)
57+
.extend(pluginDataSchema.shape)
4658
.check(createCheck(findMissingSlugsInGroupRefs));
4759

4860
export type PluginConfig = z.infer<typeof pluginConfigSchema>;

packages/models/src/lib/plugin-config.unit.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,35 @@ describe('pluginConfigSchema', () => {
4040
).not.toThrow();
4141
});
4242

43+
it('should accept a valid plugin configuration with a score target', () => {
44+
expect(() =>
45+
pluginConfigSchema.parse({
46+
slug: 'lighthouse',
47+
title: 'Lighthouse',
48+
icon: 'lighthouse',
49+
runner: async () => [
50+
{
51+
slug: 'first-contentful-paint',
52+
score: 0.28,
53+
value: 3752,
54+
displayValue: '3.8 s',
55+
},
56+
{
57+
slug: 'total-blocking-time',
58+
score: 0.91,
59+
value: 183.5,
60+
displayValue: '180 ms',
61+
},
62+
],
63+
scoreTarget: { 'total-blocking-time': 0.9 },
64+
audits: [
65+
{ slug: 'first-contentful-paint', title: 'First Contentful Paint' },
66+
{ slug: 'total-blocking-time', title: 'Total Blocking Time' },
67+
],
68+
} satisfies PluginConfig),
69+
).not.toThrow();
70+
});
71+
4372
it('should throw for a plugin configuration without audits', () => {
4473
expect(() =>
4574
pluginConfigSchema.parse({

0 commit comments

Comments
 (0)