@usefragments/core 1.10.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-MZ4SW3TP.js → chunk-3IOWHECM.js} +8 -1
- package/dist/{chunk-MZ4SW3TP.js.map → chunk-3IOWHECM.js.map} +1 -1
- package/dist/chunk-7ULGH74M.js +66 -0
- package/dist/chunk-7ULGH74M.js.map +1 -0
- package/dist/{chunk-XN3LSDPY.js → chunk-BMPYIUZE.js} +612 -344
- package/dist/chunk-BMPYIUZE.js.map +1 -0
- package/dist/{chunk-YF65VYRY.js → chunk-ML5S6QNU.js} +191 -169
- package/dist/chunk-ML5S6QNU.js.map +1 -0
- package/dist/{chunk-3LLRNCPX.js → chunk-MZ2FS7U4.js} +1 -1
- package/dist/chunk-MZ2FS7U4.js.map +1 -0
- package/dist/chunk-PWIJMOI4.js +202 -0
- package/dist/chunk-PWIJMOI4.js.map +1 -0
- package/dist/chunk-RYFULE43.js +578 -0
- package/dist/chunk-RYFULE43.js.map +1 -0
- package/dist/codes/index.d.ts +2 -2
- package/dist/codes/index.js +3 -2
- package/dist/compiled-types/index.d.ts +327 -2
- package/dist/compiled-types/index.js +1 -1
- package/dist/generate/index.d.ts +3 -2
- package/dist/{governance-CLk_wkP9.d.ts → governance-hOPXGbbs.d.ts} +474 -515
- package/dist/governance-telemetry.d.ts +6 -0
- package/dist/governance-telemetry.js +1 -1
- package/dist/{index-_sxhUNqx.d.ts → index-C8bcXVav.d.ts} +448 -448
- package/dist/index.d.ts +3280 -1305
- package/dist/index.js +2414 -350
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +228 -0
- package/dist/manifest.js +24 -0
- package/dist/manifest.js.map +1 -0
- package/dist/preview/index.js +45 -1
- package/dist/preview/index.js.map +1 -1
- package/dist/preview-runtime.d.ts +1 -2
- package/dist/preview-runtime.js +150 -14
- package/dist/preview-runtime.js.map +1 -1
- package/dist/react-types.d.ts +1 -2
- package/dist/registry.d.ts +1412 -203
- package/dist/registry.js +30 -3
- package/dist/schemas/index.d.ts +1 -1
- package/dist/schemas/index.js +3 -2
- package/dist/storyAdapter.d.ts +1 -2
- package/dist/storyAdapter.js +11 -49
- package/dist/storyAdapter.js.map +1 -1
- package/dist/test-utils.d.ts +3 -2
- package/dist/topology/index.d.ts +1 -1
- package/dist/topology/index.js +1 -1
- package/package.json +8 -2
- package/src/__tests__/contract-parser.test.ts +318 -277
- package/src/__tests__/preview-runtime-hook.test.tsx +315 -0
- package/src/__tests__/preview-runtime.test.tsx +30 -8
- package/src/__tests__/schema.test.ts +191 -14
- package/src/analysis-plan/analysis-plan-v1.test.ts +320 -0
- package/src/analysis-plan/coverage.ts +181 -0
- package/src/analysis-plan/digest.ts +141 -0
- package/src/analysis-plan/index.ts +34 -0
- package/src/analysis-plan/types.ts +207 -0
- package/src/approved-contract-tokens.test.ts +39 -0
- package/src/approved-contract-tokens.ts +18 -0
- package/src/codes/__tests__/codes.test.ts +13 -0
- package/src/codes/codes.ts +40 -0
- package/src/compiled-types/index.ts +640 -39
- package/src/compiled-types/parse.test.ts +145 -4
- package/src/component-contract.ts +95 -53
- package/src/composition.ts +7 -13
- package/src/constants.ts +3 -6
- package/src/contract/hash.test.ts +20 -0
- package/src/contract/hash.ts +66 -9
- package/src/contract/index.ts +24 -1
- package/src/contract/manifest.test.ts +94 -0
- package/src/contract/manifest.ts +68 -0
- package/src/contract/preimage.test.ts +219 -1
- package/src/contract/preimage.ts +326 -6
- package/src/contract/stamp.test.ts +3 -0
- package/src/contract/stamp.ts +1 -1
- package/src/contract-parser.ts +54 -30
- package/src/defineFragment.test.ts +476 -91
- package/src/defineFragment.ts +204 -114
- package/src/domain-ids.test.ts +35 -0
- package/src/domain-ids.ts +61 -0
- package/src/evaluation/evaluate.test.ts +522 -0
- package/src/evaluation/evaluate.ts +690 -0
- package/src/evaluation/evaluation-v2-receipt-v1.test.ts +772 -0
- package/src/evaluation/index.ts +58 -0
- package/src/evaluation/receipt.ts +753 -0
- package/src/evaluation/types.ts +406 -0
- package/src/facts/builders.ts +2 -0
- package/src/facts/compile.ts +29 -6
- package/src/facts/fact-index.ts +13 -3
- package/src/facts/fact-integrity-v1.test.ts +172 -0
- package/src/facts/facts.test.ts +15 -0
- package/src/facts/ids.ts +46 -3
- package/src/facts/index.ts +14 -1
- package/src/facts/integrity.ts +134 -0
- package/src/facts/types.ts +36 -0
- package/src/governance-integrity.test.ts +1 -0
- package/src/governance-integrity.ts +5 -3
- package/src/governance-telemetry.ts +8 -0
- package/src/governance.ts +70 -8
- package/src/index.ts +230 -37
- package/src/preview/validation.test.ts +62 -0
- package/src/preview/validation.ts +48 -2
- package/src/preview-runtime.tsx +227 -20
- package/src/registry-install-plan.ts +200 -109
- package/src/registry-shards.test.ts +263 -0
- package/src/registry.ts +237 -0
- package/src/repository-binding.test.ts +50 -0
- package/src/repository-binding.ts +96 -0
- package/src/rules/families.test.ts +36 -0
- package/src/rules/finding.ts +7 -2
- package/src/rules/index.ts +17 -1
- package/src/rules/rule-config.test.ts +66 -0
- package/src/rules/rule-config.ts +73 -0
- package/src/rules/rules.test.ts +26 -0
- package/src/rules/tokens-css-vars-must-be-defined.test.ts +51 -2
- package/src/rules/tokens-css-vars-must-be-defined.ts +34 -1
- package/src/schema.ts +293 -113
- package/src/schemas/index.ts +1 -1
- package/src/storyAdapter.test.ts +68 -12
- package/src/storyAdapter.ts +44 -75
- package/src/topology/resolve-area.ts +1 -1
- package/src/types.ts +258 -40
- package/dist/chunk-3LLRNCPX.js.map +0 -1
- package/dist/chunk-RANPUC6C.js +0 -72
- package/dist/chunk-RANPUC6C.js.map +0 -1
- package/dist/chunk-XN3LSDPY.js.map +0 -1
- package/dist/chunk-YF65VYRY.js.map +0 -1
- package/src/fragment-types.ts +0 -214
- package/src/react-create-element.test.ts +0 -22
- package/src/react-create-element.ts +0 -12
- package/src/storyFilters.test.ts +0 -350
- package/src/storyFilters.ts +0 -253
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* by AI agents, MCP servers, and context generators.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ComponentGovernanceRecord } from
|
|
8
|
+
import type { ComponentGovernanceRecord } from "../governance.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Component metadata
|
|
@@ -14,6 +14,7 @@ export interface FragmentMeta {
|
|
|
14
14
|
name: string;
|
|
15
15
|
description: string;
|
|
16
16
|
category: string;
|
|
17
|
+
aliases?: string[];
|
|
17
18
|
tags?: string[];
|
|
18
19
|
status?: "stable" | "beta" | "deprecated" | "experimental";
|
|
19
20
|
since?: string;
|
|
@@ -28,10 +29,29 @@ export interface FragmentMeta {
|
|
|
28
29
|
export interface FragmentUsage {
|
|
29
30
|
when: string[];
|
|
30
31
|
whenNot: string[];
|
|
32
|
+
choose?: Record<string, string>;
|
|
33
|
+
/** Serialized v3 anti-pattern guidance; `good` is checked JSX source. */
|
|
34
|
+
dont?: Array<{
|
|
35
|
+
reason: string;
|
|
36
|
+
bad: string;
|
|
37
|
+
good: string;
|
|
38
|
+
}>;
|
|
31
39
|
guidelines?: string[];
|
|
32
40
|
accessibility?: string[];
|
|
33
41
|
}
|
|
34
42
|
|
|
43
|
+
/** Static state-space hints carried from a v3 fragment definition. */
|
|
44
|
+
export interface CompiledFragmentMatrix {
|
|
45
|
+
axes?: Record<string, "auto" | readonly string[]>;
|
|
46
|
+
forced?: string[];
|
|
47
|
+
worstCase?: Record<string, unknown>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Optional design-tool reference carried from a v3 fragment definition. */
|
|
51
|
+
export interface CompiledFragmentDesign {
|
|
52
|
+
figmaNode?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
35
55
|
/**
|
|
36
56
|
* Definition for a single prop
|
|
37
57
|
*/
|
|
@@ -50,6 +70,8 @@ export interface PropDefinition {
|
|
|
50
70
|
step?: number;
|
|
51
71
|
presetColors?: string[];
|
|
52
72
|
};
|
|
73
|
+
/** Visibility annotation carried from a v3 fragment definition. */
|
|
74
|
+
visibility?: "public" | "internal" | "hidden";
|
|
53
75
|
}
|
|
54
76
|
|
|
55
77
|
/**
|
|
@@ -72,20 +94,38 @@ export interface FragmentContract {
|
|
|
72
94
|
/** Per-component performance budget override in bytes (gzipped) */
|
|
73
95
|
performanceBudget?: number;
|
|
74
96
|
/** Sub-component slot metadata for compound components */
|
|
75
|
-
compoundChildren?: Record<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
97
|
+
compoundChildren?: Record<
|
|
98
|
+
string,
|
|
99
|
+
{
|
|
100
|
+
required?: boolean;
|
|
101
|
+
accepts?: string[];
|
|
102
|
+
description?: string;
|
|
103
|
+
}
|
|
104
|
+
>;
|
|
80
105
|
/** Canonical JSX usage examples showing how to assemble the component */
|
|
81
106
|
canonicalUsage?: string[];
|
|
82
107
|
}
|
|
83
108
|
|
|
109
|
+
/**
|
|
110
|
+
* Per-component preview configuration carried from either contract input.
|
|
111
|
+
* JSON contracts use module/css hints; v3 definitions use provider and
|
|
112
|
+
* visual-diff-region hints. Both are projections of the same compiled object.
|
|
113
|
+
*/
|
|
114
|
+
export interface CompiledFragmentPreview {
|
|
115
|
+
setupModule?: string;
|
|
116
|
+
wrapperModule?: string;
|
|
117
|
+
wrapperExport?: string;
|
|
118
|
+
css?: string[];
|
|
119
|
+
theme?: "light" | "dark";
|
|
120
|
+
providers?: unknown[];
|
|
121
|
+
dynamicRegions?: string[];
|
|
122
|
+
}
|
|
123
|
+
|
|
84
124
|
/**
|
|
85
125
|
* Provenance tracking for generated fragments
|
|
86
126
|
*/
|
|
87
127
|
export interface FragmentGenerated {
|
|
88
|
-
source: "storybook" | "manual" | "ai" | "extracted" | "merged" | "migrated";
|
|
128
|
+
source: "storybook" | "manual" | "ai" | "scan" | "extracted" | "merged" | "migrated";
|
|
89
129
|
sourceFile?: string;
|
|
90
130
|
/** @deprecated Use provenance.verified instead */
|
|
91
131
|
confidence?: number;
|
|
@@ -124,7 +164,7 @@ export interface PerformanceData {
|
|
|
124
164
|
/** Raw (minified, not gzipped) bundle size in bytes */
|
|
125
165
|
rawSize: number;
|
|
126
166
|
/** Complexity classification */
|
|
127
|
-
complexity:
|
|
167
|
+
complexity: "lightweight" | "moderate" | "heavy";
|
|
128
168
|
/** Percentage of budget used (0-100+) */
|
|
129
169
|
budgetPercent: number;
|
|
130
170
|
/** Whether the component exceeds its budget */
|
|
@@ -137,7 +177,7 @@ export interface PerformanceData {
|
|
|
137
177
|
|
|
138
178
|
export interface ObservedUsageProp {
|
|
139
179
|
name: string;
|
|
140
|
-
kind:
|
|
180
|
+
kind: "static" | "dynamic" | "spread" | "jsx" | "boolean" | "null";
|
|
141
181
|
value?: string | number | boolean | null;
|
|
142
182
|
}
|
|
143
183
|
|
|
@@ -171,9 +211,18 @@ export interface CompiledFragment {
|
|
|
171
211
|
description: string;
|
|
172
212
|
code?: string;
|
|
173
213
|
figma?: string;
|
|
214
|
+
canonical?: boolean;
|
|
174
215
|
args?: Record<string, unknown>;
|
|
175
216
|
}>;
|
|
176
217
|
contract?: FragmentContract;
|
|
218
|
+
/** Per-component preview/runtime hints from the authored contract. */
|
|
219
|
+
preview?: CompiledFragmentPreview;
|
|
220
|
+
/** Authored state-space hints for the Workshop matrix. */
|
|
221
|
+
matrix?: CompiledFragmentMatrix;
|
|
222
|
+
/** Optional design-tool comparison reference. */
|
|
223
|
+
design?: CompiledFragmentDesign;
|
|
224
|
+
/** Design-token references used by this component. */
|
|
225
|
+
tokens?: string[];
|
|
177
226
|
ai?: AIMetadata;
|
|
178
227
|
/** Framework hint from contract, used by preview adapters and Cloud */
|
|
179
228
|
framework?: string;
|
|
@@ -260,7 +309,7 @@ export interface CompiledFragmentsFile {
|
|
|
260
309
|
blocks?: Record<string, CompiledBlock>;
|
|
261
310
|
tokens?: CompiledTokenData;
|
|
262
311
|
/** Component relationship graph for AI structural queries */
|
|
263
|
-
graph?: import(
|
|
312
|
+
graph?: import("../graph/types.js").SerializedComponentGraph;
|
|
264
313
|
/** Performance measurement summary */
|
|
265
314
|
performanceSummary?: PerformanceSummary;
|
|
266
315
|
/** @deprecated Use blocks instead */
|
|
@@ -268,12 +317,15 @@ export interface CompiledFragmentsFile {
|
|
|
268
317
|
}
|
|
269
318
|
|
|
270
319
|
export class CompiledFragmentsFileValidationError extends Error {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
) {
|
|
275
|
-
|
|
320
|
+
readonly source: string;
|
|
321
|
+
readonly issues: string[];
|
|
322
|
+
|
|
323
|
+
constructor(source: string, issues: readonly string[]) {
|
|
324
|
+
const orderedIssues = [...issues].sort(compareStrings);
|
|
325
|
+
super(`Invalid compiled Fragments catalog at ${source}: ${orderedIssues.join("; ")}`);
|
|
276
326
|
this.name = "CompiledFragmentsFileValidationError";
|
|
327
|
+
this.source = source;
|
|
328
|
+
this.issues = orderedIssues;
|
|
277
329
|
}
|
|
278
330
|
}
|
|
279
331
|
|
|
@@ -285,10 +337,10 @@ export function parseCompiledFragmentsFile(
|
|
|
285
337
|
if (typeof input === "string") {
|
|
286
338
|
try {
|
|
287
339
|
value = JSON.parse(input);
|
|
288
|
-
} catch
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
]);
|
|
340
|
+
} catch {
|
|
341
|
+
// Native JSON.parse messages differ across Node and browsers. The public
|
|
342
|
+
// contract error must not vary with the runtime that validates it.
|
|
343
|
+
throw new CompiledFragmentsFileValidationError(source, ["invalid JSON"]);
|
|
292
344
|
}
|
|
293
345
|
}
|
|
294
346
|
|
|
@@ -305,20 +357,34 @@ export function parseCompiledFragmentsFile(
|
|
|
305
357
|
if (!isRecord(value.fragments)) {
|
|
306
358
|
issues.push("fragments must be an object");
|
|
307
359
|
} else {
|
|
308
|
-
|
|
360
|
+
const names = new Map<string, string>();
|
|
361
|
+
for (const [key, fragment] of sortedEntries(value.fragments)) {
|
|
309
362
|
validateCompiledFragment(key, fragment, issues);
|
|
363
|
+
if (
|
|
364
|
+
!isRecord(fragment) ||
|
|
365
|
+
!isRecord(fragment.meta) ||
|
|
366
|
+
typeof fragment.meta.name !== "string"
|
|
367
|
+
) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
const previousKey = names.get(fragment.meta.name);
|
|
371
|
+
if (previousKey !== undefined) {
|
|
372
|
+
issues.push(
|
|
373
|
+
`fragments.${key}.meta.name duplicates fragments.${previousKey}.meta.name (${JSON.stringify(fragment.meta.name)})`
|
|
374
|
+
);
|
|
375
|
+
} else {
|
|
376
|
+
names.set(fragment.meta.name, key);
|
|
377
|
+
}
|
|
310
378
|
}
|
|
311
379
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
321
|
-
}
|
|
380
|
+
validateOptionalString(value.generatorVersion, "generatorVersion", issues);
|
|
381
|
+
validateOptionalString(value.packageName, "packageName", issues);
|
|
382
|
+
validateOptionalStringArray(value.buildInputs, "buildInputs", issues);
|
|
383
|
+
validateCompiledBlockMap(value.blocks, "blocks", issues);
|
|
384
|
+
validateCompiledBlockMap(value.recipes, "recipes", issues);
|
|
385
|
+
validateCompiledTokens(value.tokens, issues);
|
|
386
|
+
validateSerializedGraph(value.graph, issues);
|
|
387
|
+
validatePerformanceSummary(value.performanceSummary, issues);
|
|
322
388
|
if (issues.length > 0) throw new CompiledFragmentsFileValidationError(source, issues);
|
|
323
389
|
return value as unknown as CompiledFragmentsFile;
|
|
324
390
|
}
|
|
@@ -329,19 +395,554 @@ function validateCompiledFragment(key: string, value: unknown, issues: string[])
|
|
|
329
395
|
issues.push(`${path} must be an object`);
|
|
330
396
|
return;
|
|
331
397
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
398
|
+
validateString(value.filePath, `${path}.filePath`, issues);
|
|
399
|
+
validateFragmentMeta(value.meta, `${path}.meta`, issues);
|
|
400
|
+
validateFragmentUsage(value.usage, `${path}.usage`, issues);
|
|
401
|
+
if (value.guidance !== undefined) {
|
|
402
|
+
validateFragmentUsage(value.guidance, `${path}.guidance`, issues);
|
|
403
|
+
}
|
|
404
|
+
validateFragmentProps(value.props, `${path}.props`, issues);
|
|
405
|
+
validateFragmentVariants(value.variants, `${path}.variants`, issues);
|
|
406
|
+
validateOptionalRecordArray(value.governance, `${path}.governance`, issues);
|
|
407
|
+
validateRelations(value.relations, `${path}.relations`, issues);
|
|
408
|
+
validateFragmentPreview(value.preview, `${path}.preview`, issues);
|
|
409
|
+
validateFragmentMatrix(value.matrix, `${path}.matrix`, issues);
|
|
410
|
+
validateFragmentDesign(value.design, `${path}.design`, issues);
|
|
411
|
+
validateOptionalStringArray(value.tokens, `${path}.tokens`, issues);
|
|
412
|
+
validateOptionalRecord(value.ai, `${path}.ai`, issues);
|
|
413
|
+
validateOptionalString(value.framework, `${path}.framework`, issues);
|
|
414
|
+
validateOptionalStringArray(value.propsSummary, `${path}.propsSummary`, issues);
|
|
415
|
+
validateOptionalString(value.sourcePath, `${path}.sourcePath`, issues);
|
|
416
|
+
validateOptionalString(value.exportName, `${path}.exportName`, issues);
|
|
417
|
+
validateFragmentProvenance(value.provenance, `${path}.provenance`, issues);
|
|
418
|
+
validateFragmentGenerated(value._generated, `${path}._generated`, issues);
|
|
419
|
+
validateObservedUsages(value.usages, `${path}.usages`, issues);
|
|
420
|
+
validatePerformanceData(value.performance, `${path}.performance`, issues);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function validateFragmentMeta(value: unknown, path: string, issues: string[]): void {
|
|
424
|
+
if (!isRecord(value)) {
|
|
425
|
+
issues.push(`${path} must be an object`);
|
|
426
|
+
return;
|
|
335
427
|
}
|
|
336
|
-
|
|
337
|
-
|
|
428
|
+
validateString(value.name, `${path}.name`, issues);
|
|
429
|
+
validateString(value.description, `${path}.description`, issues);
|
|
430
|
+
validateString(value.category, `${path}.category`, issues);
|
|
431
|
+
validateOptionalStringArray(value.aliases, `${path}.aliases`, issues);
|
|
432
|
+
validateOptionalStringArray(value.tags, `${path}.tags`, issues);
|
|
338
433
|
if (
|
|
339
|
-
|
|
340
|
-
!
|
|
341
|
-
!Array.isArray(value.usage.whenNot)
|
|
434
|
+
value.status !== undefined &&
|
|
435
|
+
!["stable", "beta", "deprecated", "experimental"].includes(String(value.status))
|
|
342
436
|
) {
|
|
343
|
-
issues.push(`${path}.
|
|
437
|
+
issues.push(`${path}.status must be stable, beta, deprecated, or experimental`);
|
|
438
|
+
}
|
|
439
|
+
validateOptionalString(value.since, `${path}.since`, issues);
|
|
440
|
+
validateOptionalString(value.figma, `${path}.figma`, issues);
|
|
441
|
+
validateOptionalRecord(value.figmaProps, `${path}.figmaProps`, issues);
|
|
442
|
+
if (value.dependencies !== undefined) {
|
|
443
|
+
if (!Array.isArray(value.dependencies)) {
|
|
444
|
+
issues.push(`${path}.dependencies must be an array`);
|
|
445
|
+
} else {
|
|
446
|
+
value.dependencies.forEach((dependency, index) => {
|
|
447
|
+
const dependencyPath = `${path}.dependencies.${index}`;
|
|
448
|
+
if (!isRecord(dependency)) {
|
|
449
|
+
issues.push(`${dependencyPath} must be an object`);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
validateString(dependency.name, `${dependencyPath}.name`, issues);
|
|
453
|
+
validateString(dependency.version, `${dependencyPath}.version`, issues);
|
|
454
|
+
validateOptionalString(dependency.reason, `${dependencyPath}.reason`, issues);
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function validateFragmentUsage(value: unknown, path: string, issues: string[]): void {
|
|
461
|
+
if (!isRecord(value)) {
|
|
462
|
+
issues.push(`${path} must be an object`);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
validateStringArray(value.when, `${path}.when`, issues);
|
|
466
|
+
validateStringArray(value.whenNot, `${path}.whenNot`, issues);
|
|
467
|
+
validateOptionalStringArray(value.guidelines, `${path}.guidelines`, issues);
|
|
468
|
+
validateOptionalStringArray(value.accessibility, `${path}.accessibility`, issues);
|
|
469
|
+
if (value.choose !== undefined) {
|
|
470
|
+
validateStringRecord(value.choose, `${path}.choose`, issues);
|
|
471
|
+
}
|
|
472
|
+
if (value.dont !== undefined) {
|
|
473
|
+
if (!Array.isArray(value.dont)) {
|
|
474
|
+
issues.push(`${path}.dont must be an array`);
|
|
475
|
+
} else {
|
|
476
|
+
value.dont.forEach((entry, index) => {
|
|
477
|
+
const entryPath = `${path}.dont.${index}`;
|
|
478
|
+
if (!isRecord(entry)) {
|
|
479
|
+
issues.push(`${entryPath} must be an object`);
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
validateString(entry.reason, `${entryPath}.reason`, issues);
|
|
483
|
+
validateString(entry.bad, `${entryPath}.bad`, issues);
|
|
484
|
+
validateString(entry.good, `${entryPath}.good`, issues);
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
function validateFragmentProps(value: unknown, path: string, issues: string[]): void {
|
|
491
|
+
if (!isRecord(value)) {
|
|
492
|
+
issues.push(`${path} must be an object`);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
for (const [name, prop] of sortedEntries(value)) {
|
|
496
|
+
const propPath = `${path}.${name}`;
|
|
497
|
+
if (!isRecord(prop)) {
|
|
498
|
+
issues.push(`${propPath} must be an object`);
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
validateString(prop.type, `${propPath}.type`, issues);
|
|
502
|
+
validateString(prop.description, `${propPath}.description`, issues);
|
|
503
|
+
validateOptionalStringArray(prop.values, `${propPath}.values`, issues);
|
|
504
|
+
validateOptionalBoolean(prop.required, `${propPath}.required`, issues);
|
|
505
|
+
validateOptionalStringArray(prop.constraints, `${propPath}.constraints`, issues);
|
|
506
|
+
validateOptionalRecord(prop.typeDetails, `${propPath}.typeDetails`, issues);
|
|
507
|
+
validateOptionalString(prop.controlType, `${propPath}.controlType`, issues);
|
|
508
|
+
validateOptionalRecord(prop.controlOptions, `${propPath}.controlOptions`, issues);
|
|
509
|
+
if (
|
|
510
|
+
prop.visibility !== undefined &&
|
|
511
|
+
prop.visibility !== "public" &&
|
|
512
|
+
prop.visibility !== "internal" &&
|
|
513
|
+
prop.visibility !== "hidden"
|
|
514
|
+
) {
|
|
515
|
+
issues.push(`${propPath}.visibility must be public, internal, or hidden`);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function validateFragmentVariants(value: unknown, path: string, issues: string[]): void {
|
|
521
|
+
if (!Array.isArray(value)) {
|
|
522
|
+
issues.push(`${path} must be an array`);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const names = new Set<string>();
|
|
526
|
+
value.forEach((variant, index) => {
|
|
527
|
+
const variantPath = `${path}.${index}`;
|
|
528
|
+
if (!isRecord(variant)) {
|
|
529
|
+
issues.push(`${variantPath} must be an object`);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
validateString(variant.name, `${variantPath}.name`, issues);
|
|
533
|
+
validateString(variant.description, `${variantPath}.description`, issues);
|
|
534
|
+
validateOptionalString(variant.code, `${variantPath}.code`, issues);
|
|
535
|
+
validateOptionalString(variant.figma, `${variantPath}.figma`, issues);
|
|
536
|
+
validateOptionalBoolean(variant.canonical, `${variantPath}.canonical`, issues);
|
|
537
|
+
validateOptionalRecord(variant.args, `${variantPath}.args`, issues);
|
|
538
|
+
if (typeof variant.name === "string") {
|
|
539
|
+
if (names.has(variant.name)) {
|
|
540
|
+
issues.push(
|
|
541
|
+
`${variantPath}.name duplicates an earlier variant (${JSON.stringify(variant.name)})`
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
names.add(variant.name);
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function validateRelations(value: unknown, path: string, issues: string[]): void {
|
|
550
|
+
if (value === undefined) return;
|
|
551
|
+
if (!Array.isArray(value)) {
|
|
552
|
+
issues.push(`${path} must be an array`);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
value.forEach((relation, index) => {
|
|
556
|
+
const relationPath = `${path}.${index}`;
|
|
557
|
+
if (!isRecord(relation)) {
|
|
558
|
+
issues.push(`${relationPath} must be an object`);
|
|
559
|
+
return;
|
|
560
|
+
}
|
|
561
|
+
validateString(relation.component, `${relationPath}.component`, issues);
|
|
562
|
+
validateString(relation.relationship, `${relationPath}.relationship`, issues);
|
|
563
|
+
validateString(relation.note, `${relationPath}.note`, issues);
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function validateFragmentPreview(value: unknown, path: string, issues: string[]): void {
|
|
568
|
+
if (value === undefined) return;
|
|
569
|
+
if (!isRecord(value)) {
|
|
570
|
+
issues.push(`${path} must be an object`);
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
validateOptionalString(value.setupModule, `${path}.setupModule`, issues);
|
|
574
|
+
validateOptionalString(value.wrapperModule, `${path}.wrapperModule`, issues);
|
|
575
|
+
validateOptionalString(value.wrapperExport, `${path}.wrapperExport`, issues);
|
|
576
|
+
validateOptionalStringArray(value.css, `${path}.css`, issues);
|
|
577
|
+
validateOptionalStringArray(value.dynamicRegions, `${path}.dynamicRegions`, issues);
|
|
578
|
+
if (value.providers !== undefined && !Array.isArray(value.providers)) {
|
|
579
|
+
issues.push(`${path}.providers must be an array`);
|
|
580
|
+
}
|
|
581
|
+
if (value.theme !== undefined && value.theme !== "light" && value.theme !== "dark") {
|
|
582
|
+
issues.push(`${path}.theme must be light or dark`);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function validateFragmentMatrix(value: unknown, path: string, issues: string[]): void {
|
|
587
|
+
if (value === undefined) return;
|
|
588
|
+
if (!isRecord(value)) {
|
|
589
|
+
issues.push(`${path} must be an object`);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
if (value.axes !== undefined) {
|
|
593
|
+
if (!isRecord(value.axes)) {
|
|
594
|
+
issues.push(`${path}.axes must be an object`);
|
|
595
|
+
} else {
|
|
596
|
+
for (const [axis, values] of sortedEntries(value.axes)) {
|
|
597
|
+
if (values !== "auto" && !isStringArray(values)) {
|
|
598
|
+
issues.push(`${path}.axes.${axis} must be auto or an array of strings`);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
validateOptionalStringArray(value.forced, `${path}.forced`, issues);
|
|
604
|
+
validateOptionalRecord(value.worstCase, `${path}.worstCase`, issues);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function validateFragmentDesign(value: unknown, path: string, issues: string[]): void {
|
|
608
|
+
if (value === undefined) return;
|
|
609
|
+
if (!isRecord(value)) {
|
|
610
|
+
issues.push(`${path} must be an object`);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
validateOptionalString(value.figmaNode, `${path}.figmaNode`, issues);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function validateFragmentProvenance(value: unknown, path: string, issues: string[]): void {
|
|
617
|
+
if (value === undefined) return;
|
|
618
|
+
if (!isRecord(value)) {
|
|
619
|
+
issues.push(`${path} must be an object`);
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
validateString(value.source, `${path}.source`, issues);
|
|
623
|
+
validateBoolean(value.verified, `${path}.verified`, issues);
|
|
624
|
+
validateOptionalString(value.frameworkSupport, `${path}.frameworkSupport`, issues);
|
|
625
|
+
validateOptionalString(value.sourceHash, `${path}.sourceHash`, issues);
|
|
626
|
+
validateOptionalString(value.extractedAt, `${path}.extractedAt`, issues);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function validateFragmentGenerated(value: unknown, path: string, issues: string[]): void {
|
|
630
|
+
if (value === undefined) return;
|
|
631
|
+
if (!isRecord(value)) {
|
|
632
|
+
issues.push(`${path} must be an object`);
|
|
633
|
+
return;
|
|
344
634
|
}
|
|
635
|
+
const sources = ["storybook", "manual", "ai", "scan", "extracted", "merged", "migrated"];
|
|
636
|
+
if (typeof value.source !== "string" || !sources.includes(value.source)) {
|
|
637
|
+
issues.push(`${path}.source must be a supported provenance source`);
|
|
638
|
+
}
|
|
639
|
+
validateOptionalString(value.sourceFile, `${path}.sourceFile`, issues);
|
|
640
|
+
validateOptionalNumber(value.confidence, `${path}.confidence`, issues);
|
|
641
|
+
validateOptionalBoolean(value.verified, `${path}.verified`, issues);
|
|
642
|
+
validateOptionalString(value.timestamp, `${path}.timestamp`, issues);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
function validateObservedUsages(value: unknown, path: string, issues: string[]): void {
|
|
646
|
+
if (value === undefined) return;
|
|
647
|
+
if (!Array.isArray(value)) {
|
|
648
|
+
issues.push(`${path} must be an array`);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
value.forEach((usage, index) => {
|
|
652
|
+
const usagePath = `${path}.${index}`;
|
|
653
|
+
if (!isRecord(usage)) {
|
|
654
|
+
issues.push(`${usagePath} must be an object`);
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
validateString(usage.file, `${usagePath}.file`, issues);
|
|
658
|
+
validateNumber(usage.line, `${usagePath}.line`, issues);
|
|
659
|
+
validateNumber(usage.column, `${usagePath}.column`, issues);
|
|
660
|
+
validateOptionalString(usage.parentElement, `${usagePath}.parentElement`, issues);
|
|
661
|
+
validateOptionalBoolean(usage.conditional, `${usagePath}.conditional`, issues);
|
|
662
|
+
if (!Array.isArray(usage.props)) {
|
|
663
|
+
issues.push(`${usagePath}.props must be an array`);
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
usage.props.forEach((prop, propIndex) => {
|
|
667
|
+
const propPath = `${usagePath}.props.${propIndex}`;
|
|
668
|
+
if (!isRecord(prop)) {
|
|
669
|
+
issues.push(`${propPath} must be an object`);
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
validateString(prop.name, `${propPath}.name`, issues);
|
|
673
|
+
if (
|
|
674
|
+
typeof prop.kind !== "string" ||
|
|
675
|
+
!["static", "dynamic", "spread", "jsx", "boolean", "null"].includes(prop.kind)
|
|
676
|
+
) {
|
|
677
|
+
issues.push(`${propPath}.kind must be a supported observation kind`);
|
|
678
|
+
}
|
|
679
|
+
if (
|
|
680
|
+
prop.value !== undefined &&
|
|
681
|
+
prop.value !== null &&
|
|
682
|
+
typeof prop.value !== "string" &&
|
|
683
|
+
typeof prop.value !== "number" &&
|
|
684
|
+
typeof prop.value !== "boolean"
|
|
685
|
+
) {
|
|
686
|
+
issues.push(`${propPath}.value must be a JSON scalar`);
|
|
687
|
+
}
|
|
688
|
+
});
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function validatePerformanceData(value: unknown, path: string, issues: string[]): void {
|
|
693
|
+
if (value === undefined) return;
|
|
694
|
+
if (!isRecord(value)) {
|
|
695
|
+
issues.push(`${path} must be an object`);
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
validateNumber(value.bundleSize, `${path}.bundleSize`, issues);
|
|
699
|
+
validateNumber(value.rawSize, `${path}.rawSize`, issues);
|
|
700
|
+
validateString(value.complexity, `${path}.complexity`, issues);
|
|
701
|
+
validateNumber(value.budgetPercent, `${path}.budgetPercent`, issues);
|
|
702
|
+
validateBoolean(value.overBudget, `${path}.overBudget`, issues);
|
|
703
|
+
validateString(value.measuredAt, `${path}.measuredAt`, issues);
|
|
704
|
+
if (value.imports !== undefined) {
|
|
705
|
+
if (!Array.isArray(value.imports)) {
|
|
706
|
+
issues.push(`${path}.imports must be an array`);
|
|
707
|
+
} else {
|
|
708
|
+
value.imports.forEach((entry, index) => {
|
|
709
|
+
const entryPath = `${path}.imports.${index}`;
|
|
710
|
+
if (!isRecord(entry)) {
|
|
711
|
+
issues.push(`${entryPath} must be an object`);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
validateString(entry.path, `${entryPath}.path`, issues);
|
|
715
|
+
validateNumber(entry.bytes, `${entryPath}.bytes`, issues);
|
|
716
|
+
validateNumber(entry.percent, `${entryPath}.percent`, issues);
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
function validateCompiledBlockMap(value: unknown, path: string, issues: string[]): void {
|
|
723
|
+
if (value === undefined) return;
|
|
724
|
+
if (!isRecord(value)) {
|
|
725
|
+
issues.push(`${path} must be an object`);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const names = new Set<string>();
|
|
729
|
+
for (const [key, block] of sortedEntries(value)) {
|
|
730
|
+
const blockPath = `${path}.${key}`;
|
|
731
|
+
if (!isRecord(block)) {
|
|
732
|
+
issues.push(`${blockPath} must be an object`);
|
|
733
|
+
continue;
|
|
734
|
+
}
|
|
735
|
+
validateString(block.filePath, `${blockPath}.filePath`, issues);
|
|
736
|
+
validateString(block.name, `${blockPath}.name`, issues);
|
|
737
|
+
validateString(block.description, `${blockPath}.description`, issues);
|
|
738
|
+
validateString(block.category, `${blockPath}.category`, issues);
|
|
739
|
+
validateStringArray(block.components, `${blockPath}.components`, issues);
|
|
740
|
+
validateString(block.code, `${blockPath}.code`, issues);
|
|
741
|
+
validateOptionalStringArray(block.tags, `${blockPath}.tags`, issues);
|
|
742
|
+
if (typeof block.name === "string") {
|
|
743
|
+
if (names.has(block.name)) {
|
|
744
|
+
issues.push(
|
|
745
|
+
`${blockPath}.name duplicates an earlier block (${JSON.stringify(block.name)})`
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
names.add(block.name);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
function validateCompiledTokens(value: unknown, issues: string[]): void {
|
|
754
|
+
if (value === undefined) return;
|
|
755
|
+
if (!isRecord(value)) {
|
|
756
|
+
issues.push("tokens must be an object");
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
validateString(value.prefix, "tokens.prefix", issues);
|
|
760
|
+
validateNumber(value.total, "tokens.total", issues);
|
|
761
|
+
if (!isRecord(value.categories)) {
|
|
762
|
+
issues.push("tokens.categories must be an object");
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
for (const [category, entries] of sortedEntries(value.categories)) {
|
|
766
|
+
const categoryPath = `tokens.categories.${category}`;
|
|
767
|
+
if (!Array.isArray(entries)) {
|
|
768
|
+
issues.push(`${categoryPath} must be an array`);
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
entries.forEach((entry, index) => {
|
|
772
|
+
const entryPath = `${categoryPath}.${index}`;
|
|
773
|
+
if (!isRecord(entry)) {
|
|
774
|
+
issues.push(`${entryPath} must be an object`);
|
|
775
|
+
return;
|
|
776
|
+
}
|
|
777
|
+
validateString(entry.name, `${entryPath}.name`, issues);
|
|
778
|
+
validateOptionalString(entry.value, `${entryPath}.value`, issues);
|
|
779
|
+
validateOptionalString(entry.description, `${entryPath}.description`, issues);
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
function validateSerializedGraph(value: unknown, issues: string[]): void {
|
|
785
|
+
if (value === undefined) return;
|
|
786
|
+
if (!isRecord(value)) {
|
|
787
|
+
issues.push("graph must be an object");
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
if (!Array.isArray(value.nodes)) {
|
|
791
|
+
issues.push("graph.nodes must be an array");
|
|
792
|
+
} else {
|
|
793
|
+
const nodeNames = new Set<string>();
|
|
794
|
+
value.nodes.forEach((node, index) => {
|
|
795
|
+
const nodePath = `graph.nodes.${index}`;
|
|
796
|
+
if (!isRecord(node)) {
|
|
797
|
+
issues.push(`${nodePath} must be an object`);
|
|
798
|
+
return;
|
|
799
|
+
}
|
|
800
|
+
validateString(node.name, `${nodePath}.name`, issues);
|
|
801
|
+
validateString(node.category, `${nodePath}.category`, issues);
|
|
802
|
+
validateString(node.status, `${nodePath}.status`, issues);
|
|
803
|
+
validateOptionalStringArray(node.subComponents, `${nodePath}.subComponents`, issues);
|
|
804
|
+
if (typeof node.name === "string") {
|
|
805
|
+
if (nodeNames.has(node.name)) {
|
|
806
|
+
issues.push(
|
|
807
|
+
`${nodePath}.name duplicates an earlier graph node (${JSON.stringify(node.name)})`
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
nodeNames.add(node.name);
|
|
811
|
+
}
|
|
812
|
+
});
|
|
813
|
+
}
|
|
814
|
+
if (!Array.isArray(value.edges)) {
|
|
815
|
+
issues.push("graph.edges must be an array");
|
|
816
|
+
} else {
|
|
817
|
+
value.edges.forEach((edge, index) => {
|
|
818
|
+
const edgePath = `graph.edges.${index}`;
|
|
819
|
+
if (!isRecord(edge)) {
|
|
820
|
+
issues.push(`${edgePath} must be an object`);
|
|
821
|
+
return;
|
|
822
|
+
}
|
|
823
|
+
validateString(edge.s, `${edgePath}.s`, issues);
|
|
824
|
+
validateString(edge.t, `${edgePath}.t`, issues);
|
|
825
|
+
validateString(edge.ty, `${edgePath}.ty`, issues);
|
|
826
|
+
validateNumber(edge.w, `${edgePath}.w`, issues);
|
|
827
|
+
validateOptionalString(edge.no, `${edgePath}.no`, issues);
|
|
828
|
+
validateString(edge.p, `${edgePath}.p`, issues);
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
if (!isRecord(value.health)) {
|
|
832
|
+
issues.push("graph.health must be an object");
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
validateStringArray(value.health.orphans, "graph.health.orphans", issues);
|
|
836
|
+
validateNumber(value.health.compositionCoverage, "graph.health.compositionCoverage", issues);
|
|
837
|
+
validateNumber(value.health.averageDegree, "graph.health.averageDegree", issues);
|
|
838
|
+
validateNumber(value.health.nodeCount, "graph.health.nodeCount", issues);
|
|
839
|
+
validateNumber(value.health.edgeCount, "graph.health.edgeCount", issues);
|
|
840
|
+
if (!Array.isArray(value.health.connectedComponents)) {
|
|
841
|
+
issues.push("graph.health.connectedComponents must be an array");
|
|
842
|
+
} else {
|
|
843
|
+
value.health.connectedComponents.forEach((component, index) =>
|
|
844
|
+
validateStringArray(component, `graph.health.connectedComponents.${index}`, issues)
|
|
845
|
+
);
|
|
846
|
+
}
|
|
847
|
+
if (!Array.isArray(value.health.hubs)) {
|
|
848
|
+
issues.push("graph.health.hubs must be an array");
|
|
849
|
+
} else {
|
|
850
|
+
value.health.hubs.forEach((hub, index) => {
|
|
851
|
+
const hubPath = `graph.health.hubs.${index}`;
|
|
852
|
+
if (!isRecord(hub)) {
|
|
853
|
+
issues.push(`${hubPath} must be an object`);
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
validateString(hub.name, `${hubPath}.name`, issues);
|
|
857
|
+
validateNumber(hub.degree, `${hubPath}.degree`, issues);
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
function validatePerformanceSummary(value: unknown, issues: string[]): void {
|
|
863
|
+
if (value === undefined) return;
|
|
864
|
+
if (!isRecord(value)) {
|
|
865
|
+
issues.push("performanceSummary must be an object");
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
validateString(value.preset, "performanceSummary.preset", issues);
|
|
869
|
+
validateNumber(value.budget, "performanceSummary.budget", issues);
|
|
870
|
+
validateNumber(value.total, "performanceSummary.total", issues);
|
|
871
|
+
validateNumber(value.overBudget, "performanceSummary.overBudget", issues);
|
|
872
|
+
if (!isRecord(value.tiers)) {
|
|
873
|
+
issues.push("performanceSummary.tiers must be an object");
|
|
874
|
+
} else {
|
|
875
|
+
for (const [tier, count] of sortedEntries(value.tiers)) {
|
|
876
|
+
validateNumber(count, `performanceSummary.tiers.${tier}`, issues);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function validateString(value: unknown, path: string, issues: string[]): void {
|
|
882
|
+
if (typeof value !== "string") issues.push(`${path} must be a string`);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
function validateOptionalString(value: unknown, path: string, issues: string[]): void {
|
|
886
|
+
if (value !== undefined) validateString(value, path, issues);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
function validateNumber(value: unknown, path: string, issues: string[]): void {
|
|
890
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
891
|
+
issues.push(`${path} must be a finite number`);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function validateOptionalNumber(value: unknown, path: string, issues: string[]): void {
|
|
896
|
+
if (value !== undefined) validateNumber(value, path, issues);
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
function validateBoolean(value: unknown, path: string, issues: string[]): void {
|
|
900
|
+
if (typeof value !== "boolean") issues.push(`${path} must be a boolean`);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
function validateOptionalBoolean(value: unknown, path: string, issues: string[]): void {
|
|
904
|
+
if (value !== undefined) validateBoolean(value, path, issues);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
function validateStringArray(value: unknown, path: string, issues: string[]): void {
|
|
908
|
+
if (!isStringArray(value)) issues.push(`${path} must be an array of strings`);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
function validateOptionalStringArray(value: unknown, path: string, issues: string[]): void {
|
|
912
|
+
if (value !== undefined) validateStringArray(value, path, issues);
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function validateStringRecord(value: unknown, path: string, issues: string[]): void {
|
|
916
|
+
if (!isRecord(value)) {
|
|
917
|
+
issues.push(`${path} must be an object of strings`);
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
for (const [key, entry] of sortedEntries(value)) {
|
|
921
|
+
validateString(entry, `${path}.${key}`, issues);
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
function validateOptionalRecord(value: unknown, path: string, issues: string[]): void {
|
|
926
|
+
if (value !== undefined && !isRecord(value)) issues.push(`${path} must be an object`);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
function validateOptionalRecordArray(value: unknown, path: string, issues: string[]): void {
|
|
930
|
+
if (value === undefined) return;
|
|
931
|
+
if (!Array.isArray(value) || value.some((entry) => !isRecord(entry))) {
|
|
932
|
+
issues.push(`${path} must be an array of objects`);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
function isStringArray(value: unknown): value is string[] {
|
|
937
|
+
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function sortedEntries(value: Record<string, unknown>): Array<[string, unknown]> {
|
|
941
|
+
return Object.entries(value).sort(([left], [right]) => compareStrings(left, right));
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
function compareStrings(left: string, right: string): number {
|
|
945
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
345
946
|
}
|
|
346
947
|
|
|
347
948
|
function isRecord(value: unknown): value is Record<string, unknown> {
|