@invariant-ui/cli 0.0.0-stage → 0.2.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/LICENSE +105 -0
- package/README.md +250 -2
- package/dist/agent-kit/apply.js +232 -0
- package/dist/agent-kit/guidance.js +236 -0
- package/dist/agent-kit/index.js +5 -0
- package/dist/agent-kit/integrations.js +121 -0
- package/dist/agent-kit/plugin.js +115 -0
- package/dist/artifact-generator/index.js +75 -0
- package/dist/browser-verify/driver.js +2 -0
- package/dist/browser-verify/index.js +4 -0
- package/dist/browser-verify/playwright.js +82 -0
- package/dist/browser-verify/runner.js +175 -0
- package/dist/browser-verify/spec.js +103 -0
- package/dist/cli/args.js +77 -0
- package/dist/cli/baseline.js +64 -0
- package/dist/cli/cli.js +121 -0
- package/dist/cli/commands-browser.js +104 -0
- package/dist/cli/commands-discover.js +312 -0
- package/dist/cli/commands-enforcement.js +35 -0
- package/dist/cli/commands-governance.js +179 -0
- package/dist/cli/commands-hook.js +269 -0
- package/dist/cli/commands-patterns.js +324 -0
- package/dist/cli/commands-setup.js +485 -0
- package/dist/cli/commands-validate.js +157 -0
- package/dist/cli/commands.js +517 -0
- package/dist/cli/config.js +197 -0
- package/dist/cli/diff.js +66 -0
- package/dist/cli/enforcement-log.js +83 -0
- package/dist/cli/enforcement-report.js +96 -0
- package/dist/cli/errors.js +10 -0
- package/dist/cli/format.js +22 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/io.js +2 -0
- package/dist/cli/main.js +31 -0
- package/dist/cli/pipeline.js +262 -0
- package/dist/cli/publish.js +77 -0
- package/dist/cli/report.js +127 -0
- package/dist/cli/starter-rules.js +57 -0
- package/dist/cli/store.js +60 -0
- package/dist/cli/version.js +5 -0
- package/dist/cli/watch.js +68 -0
- package/dist/cli/workflow.js +52 -0
- package/dist/compiler-angular/adapter.js +115 -0
- package/dist/compiler-angular/analyze.js +222 -0
- package/dist/compiler-angular/classes.js +446 -0
- package/dist/compiler-angular/declared.js +285 -0
- package/dist/compiler-angular/emit.js +183 -0
- package/dist/compiler-angular/index.js +4 -0
- package/dist/compiler-angular/template.js +142 -0
- package/dist/compiler-core/adapter.js +2 -0
- package/dist/compiler-core/adapters/inventory.js +312 -0
- package/dist/compiler-core/compile.js +148 -0
- package/dist/compiler-core/component-facts.js +123 -0
- package/dist/compiler-core/discovery/docs.js +66 -0
- package/dist/compiler-core/discovery/entries.js +270 -0
- package/dist/compiler-core/discovery/exports.js +207 -0
- package/dist/compiler-core/discovery/graph.js +59 -0
- package/dist/compiler-core/discovery/index.js +103 -0
- package/dist/compiler-core/discovery/packages.js +148 -0
- package/dist/compiler-core/discovery/schema.js +144 -0
- package/dist/compiler-core/discovery/storybook.js +232 -0
- package/dist/compiler-core/discovery/support.js +82 -0
- package/dist/compiler-core/discovery/tokens.js +186 -0
- package/dist/compiler-core/discovery/tooling.js +146 -0
- package/dist/compiler-core/fs.js +89 -0
- package/dist/compiler-core/glob.js +3 -0
- package/dist/compiler-core/index.js +16 -0
- package/dist/compiler-core/ir-builder.js +68 -0
- package/dist/compiler-core/json-schema.js +11 -0
- package/dist/compiler-core/testing/index.js +81 -0
- package/dist/compiler-core/ts/ast.js +166 -0
- package/dist/compiler-core/ts/members.js +163 -0
- package/dist/compiler-core/ts/program.js +81 -0
- package/dist/compiler-core/ts/static-eval.js +145 -0
- package/dist/compiler-core/ts/tsconfig-paths.js +60 -0
- package/dist/compiler-core/walk.js +46 -0
- package/dist/compiler-ir/index.js +4 -0
- package/dist/compiler-ir/ir.js +67 -0
- package/dist/compiler-ir/json-schema.js +6 -0
- package/dist/compiler-ir/validate.js +116 -0
- package/dist/compiler-react/adapter.js +147 -0
- package/dist/compiler-react/analyze.js +199 -0
- package/dist/compiler-react/behavior.js +75 -0
- package/dist/compiler-react/detect.js +558 -0
- package/dist/compiler-react/emit.js +188 -0
- package/dist/compiler-react/index.js +2 -0
- package/dist/compiler-react/props.js +157 -0
- package/dist/compiler-react/styling.js +138 -0
- package/dist/compiler-react/usage.js +116 -0
- package/dist/compiler-react/variants.js +111 -0
- package/dist/compiler-storybook/adapter.js +253 -0
- package/dist/compiler-storybook/csf.js +521 -0
- package/dist/compiler-storybook/emit.js +202 -0
- package/dist/compiler-storybook/index-json.js +41 -0
- package/dist/compiler-storybook/index.js +5 -0
- package/dist/compiler-storybook/mdx.js +120 -0
- package/dist/compiler-tokens/adapter.js +158 -0
- package/dist/compiler-tokens/css.js +59 -0
- package/dist/compiler-tokens/index.js +5 -0
- package/dist/compiler-tokens/json-lines.js +106 -0
- package/dist/compiler-tokens/json-tokens.js +52 -0
- package/dist/compiler-vue/adapter.js +107 -0
- package/dist/compiler-vue/analyze.js +344 -0
- package/dist/compiler-vue/declared.js +343 -0
- package/dist/compiler-vue/emit.js +129 -0
- package/dist/compiler-vue/index.js +5 -0
- package/dist/compiler-vue/script.js +472 -0
- package/dist/compiler-vue/sfc.js +44 -0
- package/dist/compiler-vue/template.js +73 -0
- package/dist/contract/archetype-checks.js +153 -0
- package/dist/contract/components.js +102 -0
- package/dist/contract/conflicts.js +39 -0
- package/dist/contract/contract.js +46 -0
- package/dist/contract/evidence.js +84 -0
- package/dist/contract/fact-path.js +39 -0
- package/dist/contract/facts.js +62 -0
- package/dist/contract/index.js +16 -0
- package/dist/contract/integrity.js +21 -0
- package/dist/contract/json-schema.js +11 -0
- package/dist/contract/knowledge.js +95 -0
- package/dist/contract/pattern-checks.js +254 -0
- package/dist/contract/patterns.js +225 -0
- package/dist/contract/primitives.js +81 -0
- package/dist/contract/sources.js +30 -0
- package/dist/contract/tokens.js +41 -0
- package/dist/contract/validate.js +253 -0
- package/dist/contract-query/archetype.js +188 -0
- package/dist/contract-query/index.js +6 -0
- package/dist/contract-query/patterns.js +298 -0
- package/dist/contract-query/search.js +132 -0
- package/dist/contract-query/system.js +148 -0
- package/dist/contract-query/views.js +288 -0
- package/dist/governance/decisions.js +229 -0
- package/dist/governance/index.js +5 -0
- package/dist/governance/patterns-file.js +156 -0
- package/dist/governance/patterns.js +510 -0
- package/dist/governance/policies.js +148 -0
- package/dist/mcp/index.js +7 -0
- package/dist/mcp/loader.js +70 -0
- package/dist/mcp/protocol.js +27 -0
- package/dist/mcp/resources.js +138 -0
- package/dist/mcp/server.js +177 -0
- package/dist/mcp/stdio.js +28 -0
- package/dist/mcp/tools.js +267 -0
- package/dist/reconciliation/code.js +62 -0
- package/dist/reconciliation/components.js +401 -0
- package/dist/reconciliation/conflicts.js +126 -0
- package/dist/reconciliation/facts.js +41 -0
- package/dist/reconciliation/index.js +7 -0
- package/dist/reconciliation/reconcile.js +134 -0
- package/dist/reconciliation/resolve.js +158 -0
- package/dist/reconciliation/review.js +81 -0
- package/dist/reconciliation/storybook.js +370 -0
- package/dist/reconciliation/tokens.js +179 -0
- package/dist/reconciliation/util.js +55 -0
- package/dist/semantic-engine/additive.js +51 -0
- package/dist/semantic-engine/compounds.js +91 -0
- package/dist/semantic-engine/documents.js +157 -0
- package/dist/semantic-engine/enrich.js +42 -0
- package/dist/semantic-engine/index.js +7 -0
- package/dist/semantic-engine/infer.js +38 -0
- package/dist/semantic-engine/tokens.js +120 -0
- package/dist/shared/canonical.js +65 -0
- package/dist/shared/diagnostic.js +78 -0
- package/dist/shared/glob.js +98 -0
- package/dist/shared/index.js +4 -0
- package/dist/shared/json.js +2 -0
- package/dist/validation/card.js +50 -0
- package/dist/validation/discovery/arrangements.js +162 -0
- package/dist/validation/discovery/discover.js +70 -0
- package/dist/validation/discovery/index.js +8 -0
- package/dist/validation/discovery/model.js +127 -0
- package/dist/validation/discovery/promote.js +24 -0
- package/dist/validation/discovery/reviews.js +63 -0
- package/dist/validation/discovery/shapes.js +47 -0
- package/dist/validation/discovery/similar-files.js +146 -0
- package/dist/validation/discovery/util.js +47 -0
- package/dist/validation/index.js +11 -0
- package/dist/validation/interaction-facts.js +341 -0
- package/dist/validation/interaction-workflows.js +633 -0
- package/dist/validation/literals.js +251 -0
- package/dist/validation/pattern-rules.js +608 -0
- package/dist/validation/policy.js +241 -0
- package/dist/validation/report.js +163 -0
- package/dist/validation/rules.js +269 -0
- package/dist/validation/scan.js +295 -0
- package/dist/validation/text.js +34 -0
- package/dist/validation/validate-code.js +513 -0
- package/dist/validation/validate-usage.js +196 -0
- package/package.json +38 -4
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { approvalSchema, factsSchema } from "./facts.js";
|
|
3
|
+
import { behaviorSchema, compositionSchema, layoutSchema, PATTERN_CATEGORIES, PATTERN_CONSTRAINT_KINDS, patternAccessibilitySchema, patternRelationshipSchema, patternVariationSchema } from "./patterns.js";
|
|
4
|
+
import { componentIdSchema, constraintIdSchema, entityIdSchema, extensionsSchema, frameworkSchema, guidelineIdSchema, jsonObjectSchema, patternIdSchema, policyIdSchema, sourceIdSchema } from "./primitives.js";
|
|
5
|
+
export const documentRefSchema = z.strictObject({
|
|
6
|
+
sourceId: sourceIdSchema,
|
|
7
|
+
documentId: z.string().min(1),
|
|
8
|
+
title: z.string().optional(),
|
|
9
|
+
url: z.url().optional()
|
|
10
|
+
});
|
|
11
|
+
export const patternSchema = z.strictObject({
|
|
12
|
+
id: patternIdSchema,
|
|
13
|
+
name: z.string().min(1),
|
|
14
|
+
category: z.enum(PATTERN_CATEGORIES),
|
|
15
|
+
description: z.string().min(1),
|
|
16
|
+
/** What the pattern is for, in one sentence (1.1.0). */
|
|
17
|
+
intent: z.string().min(1).optional(),
|
|
18
|
+
components: z.array(componentIdSchema),
|
|
19
|
+
guidelines: z.array(guidelineIdSchema),
|
|
20
|
+
/** The named parts a pattern is made of (1.1.0). */
|
|
21
|
+
composition: compositionSchema.optional(),
|
|
22
|
+
/** Named, approved ways the composition may differ (1.1.0). */
|
|
23
|
+
variations: z.array(patternVariationSchema).optional(),
|
|
24
|
+
/** Regions and their structure (1.1.0). */
|
|
25
|
+
layout: layoutSchema.optional(),
|
|
26
|
+
/** States and transitions (1.1.0). */
|
|
27
|
+
behavior: behaviorSchema.optional(),
|
|
28
|
+
accessibility: patternAccessibilitySchema.optional(),
|
|
29
|
+
/** Typed edges to other patterns and components (1.1.0). */
|
|
30
|
+
relationships: z.array(patternRelationshipSchema).optional(),
|
|
31
|
+
/** Constraints this pattern owns. Policies make them enforceable (1.1.0). */
|
|
32
|
+
constraints: z.array(constraintIdSchema).optional(),
|
|
33
|
+
/** Where the pattern applies: the files and frameworks that are instances of it (1.1.0). */
|
|
34
|
+
appliesTo: z
|
|
35
|
+
.strictObject({
|
|
36
|
+
paths: z.array(z.string().min(1)).min(1).optional(),
|
|
37
|
+
frameworks: z.array(frameworkSchema).min(1).optional()
|
|
38
|
+
})
|
|
39
|
+
.optional(),
|
|
40
|
+
/**
|
|
41
|
+
* Opt in: files in `appliesTo.paths` are also held to the constraints of every pattern this one composes (1.1.0).
|
|
42
|
+
* It has effect only while both this flag and `appliesTo` are approved.
|
|
43
|
+
*/
|
|
44
|
+
inheritsConstraints: z.boolean().optional(),
|
|
45
|
+
facts: factsSchema,
|
|
46
|
+
extensions: extensionsSchema.optional()
|
|
47
|
+
});
|
|
48
|
+
export const guidelineSchema = z.strictObject({
|
|
49
|
+
id: guidelineIdSchema,
|
|
50
|
+
name: z.string().min(1),
|
|
51
|
+
kind: z.enum(["usage", "accessibility", "composition", "visual-principle", "do-dont", "reference"]),
|
|
52
|
+
statement: z.string().min(1),
|
|
53
|
+
appliesTo: z.array(entityIdSchema),
|
|
54
|
+
examples: z.array(z.strictObject({ kind: z.enum(["do", "dont"]), text: z.string().min(1) })),
|
|
55
|
+
references: z.array(documentRefSchema),
|
|
56
|
+
facts: factsSchema,
|
|
57
|
+
extensions: extensionsSchema.optional()
|
|
58
|
+
});
|
|
59
|
+
export const constraintSchema = z.strictObject({
|
|
60
|
+
id: constraintIdSchema,
|
|
61
|
+
name: z.string().min(1),
|
|
62
|
+
kind: z.enum([
|
|
63
|
+
"valid-combination",
|
|
64
|
+
"invalid-combination",
|
|
65
|
+
"required-prop",
|
|
66
|
+
"deprecated-api",
|
|
67
|
+
"approved-alternative",
|
|
68
|
+
"framework-restriction",
|
|
69
|
+
...PATTERN_CONSTRAINT_KINDS
|
|
70
|
+
]),
|
|
71
|
+
description: z.string().min(1),
|
|
72
|
+
subjects: z.array(entityIdSchema).min(1),
|
|
73
|
+
details: jsonObjectSchema.optional(),
|
|
74
|
+
facts: factsSchema,
|
|
75
|
+
extensions: extensionsSchema.optional()
|
|
76
|
+
});
|
|
77
|
+
/**
|
|
78
|
+
* Organization-approved rule. Policies are never produced by inference: approval metadata is
|
|
79
|
+
* mandatory in the schema, so an unapproved rule cannot be represented as a policy.
|
|
80
|
+
*/
|
|
81
|
+
export const policySchema = z.strictObject({
|
|
82
|
+
id: policyIdSchema,
|
|
83
|
+
name: z.string().min(1),
|
|
84
|
+
statement: z.string().min(1),
|
|
85
|
+
severity: z.enum(["error", "warning", "info"]),
|
|
86
|
+
enforcement: z.enum(["block", "warn", "report-only"]),
|
|
87
|
+
scope: z.strictObject({
|
|
88
|
+
entities: z.array(entityIdSchema).optional(),
|
|
89
|
+
frameworks: z.array(frameworkSchema).optional(),
|
|
90
|
+
paths: z.array(z.string().min(1)).optional()
|
|
91
|
+
}),
|
|
92
|
+
basedOn: z.array(z.union([guidelineIdSchema, constraintIdSchema])),
|
|
93
|
+
approval: approvalSchema.omit({ fingerprint: true, priorStatus: true })
|
|
94
|
+
});
|
|
95
|
+
//# sourceMappingURL=knowledge.js.map
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { checkArchetypes, checkVariations } from "./archetype-checks.js";
|
|
2
|
+
import { ACYCLIC_RELATIONSHIPS, constraintDetailsSchemas, isPatternConstraintKind, PATTERN_CATEGORIES_1_1, PATTERN_CONSTRAINT_KINDS, PATTERN_ONLY_RELATIONSHIPS } from "./patterns.js";
|
|
3
|
+
const duplicates = (names) => [
|
|
4
|
+
...new Set(names.filter((n, i) => names.indexOf(n) !== i))
|
|
5
|
+
];
|
|
6
|
+
/** Where a constraint's `details` point at components and patterns, as entity ids (region refs are local). */
|
|
7
|
+
export function constraintReferences(contract) {
|
|
8
|
+
const refs = [];
|
|
9
|
+
for (const c of contract.constraints) {
|
|
10
|
+
if (!isPatternConstraintKind(c.kind))
|
|
11
|
+
continue;
|
|
12
|
+
const parsed = constraintDetailsSchemas[c.kind].safeParse(c.details);
|
|
13
|
+
if (!parsed.success)
|
|
14
|
+
continue;
|
|
15
|
+
// Only component and pattern ids are references; other strings (a workflow name, a minimum) are not.
|
|
16
|
+
for (const value of stringsIn(parsed.data))
|
|
17
|
+
if (/^(?:component|pattern):/.test(value))
|
|
18
|
+
refs.push({ from: c.id, to: value });
|
|
19
|
+
}
|
|
20
|
+
return refs;
|
|
21
|
+
}
|
|
22
|
+
function stringsIn(value) {
|
|
23
|
+
if (typeof value === "string")
|
|
24
|
+
return [value];
|
|
25
|
+
if (Array.isArray(value))
|
|
26
|
+
return value.flatMap(stringsIn);
|
|
27
|
+
if (value && typeof value === "object")
|
|
28
|
+
return Object.values(value).flatMap(stringsIn);
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
/** Entity ids a pattern points at, with the fact path that holds the claim. */
|
|
32
|
+
export function patternReferences(contract) {
|
|
33
|
+
const refs = [];
|
|
34
|
+
for (const p of contract.patterns) {
|
|
35
|
+
for (const part of p.composition?.parts ?? [])
|
|
36
|
+
refs.push({ from: p.id, to: part.ref });
|
|
37
|
+
for (const v of p.variations ?? [])
|
|
38
|
+
for (const part of v.parts ?? [])
|
|
39
|
+
refs.push({ from: p.id, to: part.ref });
|
|
40
|
+
for (const rel of p.relationships ?? [])
|
|
41
|
+
refs.push({ from: p.id, to: rel.target });
|
|
42
|
+
for (const id of p.constraints ?? [])
|
|
43
|
+
refs.push({ from: p.id, to: id });
|
|
44
|
+
for (const region of p.layout?.regions ?? []) {
|
|
45
|
+
for (const target of region.accepts ?? [])
|
|
46
|
+
refs.push({ from: p.id, to: target });
|
|
47
|
+
if (region.spacing)
|
|
48
|
+
refs.push({ from: p.id, to: region.spacing });
|
|
49
|
+
if (region.grid?.gap)
|
|
50
|
+
refs.push({ from: p.id, to: region.grid.gap });
|
|
51
|
+
for (const r of region.responsive ?? [])
|
|
52
|
+
refs.push({ from: p.id, to: r.breakpoint });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return refs;
|
|
56
|
+
}
|
|
57
|
+
/** Things a 1.0.0 document cannot contain. Returns one reason per offending entity. */
|
|
58
|
+
export function newInOnePointOne(contract) {
|
|
59
|
+
const out = [];
|
|
60
|
+
const newCategories = PATTERN_CATEGORIES_1_1;
|
|
61
|
+
const newKinds = PATTERN_CONSTRAINT_KINDS;
|
|
62
|
+
for (const p of contract.patterns) {
|
|
63
|
+
const fields = [
|
|
64
|
+
"intent",
|
|
65
|
+
"composition",
|
|
66
|
+
"layout",
|
|
67
|
+
"behavior",
|
|
68
|
+
"accessibility",
|
|
69
|
+
"relationships",
|
|
70
|
+
"constraints",
|
|
71
|
+
"variations",
|
|
72
|
+
"appliesTo",
|
|
73
|
+
"inheritsConstraints"
|
|
74
|
+
].filter((f) => p[f] !== undefined);
|
|
75
|
+
if (fields.length > 0)
|
|
76
|
+
out.push({ entityId: p.id, reason: `uses ${fields.join(", ")}` });
|
|
77
|
+
if (newCategories.includes(p.category))
|
|
78
|
+
out.push({ entityId: p.id, reason: `has category "${p.category}"` });
|
|
79
|
+
}
|
|
80
|
+
for (const c of contract.constraints)
|
|
81
|
+
if (newKinds.includes(c.kind))
|
|
82
|
+
out.push({ entityId: c.id, reason: `has kind "${c.kind}"` });
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Semantic checks for pattern contracts: unique names, region and state-machine integrity, relationship rules,
|
|
87
|
+
* cycles, and the typed details of structural constraints. References to other entities are checked with the
|
|
88
|
+
* rest of the contract by `entityReferences`.
|
|
89
|
+
*/
|
|
90
|
+
export function checkPatterns(contract, error, found) {
|
|
91
|
+
const warn = (code, message, entityId) => {
|
|
92
|
+
found.push({ code, severity: "warning", message, entityId });
|
|
93
|
+
};
|
|
94
|
+
const ownedConstraints = new Map();
|
|
95
|
+
for (const p of contract.patterns)
|
|
96
|
+
for (const id of p.constraints ?? [])
|
|
97
|
+
ownedConstraints.set(id, p);
|
|
98
|
+
for (const p of contract.patterns) {
|
|
99
|
+
checkNames(p, error);
|
|
100
|
+
checkRegions(p, error);
|
|
101
|
+
checkBehavior(p, error, warn);
|
|
102
|
+
checkRelationships(p, error);
|
|
103
|
+
checkVariations(p, error);
|
|
104
|
+
}
|
|
105
|
+
checkConstraints(contract, ownedConstraints, error);
|
|
106
|
+
checkCycles(contract, error);
|
|
107
|
+
checkArchetypes(contract, error, warn);
|
|
108
|
+
}
|
|
109
|
+
function checkNames(p, error) {
|
|
110
|
+
const groups = [
|
|
111
|
+
["region", (p.layout?.regions ?? []).map((r) => r.name)],
|
|
112
|
+
["composition part", (p.composition?.parts ?? []).map((x) => x.name)],
|
|
113
|
+
["state", (p.behavior?.states ?? []).map((s) => s.name)],
|
|
114
|
+
["transition", (p.behavior?.transitions ?? []).map((t) => t.id)],
|
|
115
|
+
["relationship", (p.relationships ?? []).map((r) => r.id)],
|
|
116
|
+
["accessibility requirement", (p.accessibility?.requirements ?? []).map((r) => r.id)]
|
|
117
|
+
];
|
|
118
|
+
for (const [what, names] of groups)
|
|
119
|
+
for (const name of duplicates(names))
|
|
120
|
+
error("contract.pattern-duplicate-name", `${p.id} has more than one ${what} named "${name}"`, p.id);
|
|
121
|
+
}
|
|
122
|
+
function checkRegions(p, error) {
|
|
123
|
+
const regions = p.layout?.regions ?? [];
|
|
124
|
+
const names = new Set(regions.map((r) => r.name));
|
|
125
|
+
for (const r of regions) {
|
|
126
|
+
if (r.parent !== undefined && !names.has(r.parent))
|
|
127
|
+
error("contract.pattern-region-unknown", `${p.id} region "${r.name}" has unknown parent "${r.parent}"`, p.id);
|
|
128
|
+
}
|
|
129
|
+
const parentOf = new Map(regions.map((r) => [r.name, r.parent]));
|
|
130
|
+
for (const r of regions) {
|
|
131
|
+
const seen = new Set([r.name]);
|
|
132
|
+
for (let at = r.parent; at !== undefined; at = parentOf.get(at)) {
|
|
133
|
+
if (seen.has(at)) {
|
|
134
|
+
error("contract.pattern-region-cycle", `${p.id} region "${r.name}" is its own ancestor`, p.id);
|
|
135
|
+
break;
|
|
136
|
+
}
|
|
137
|
+
seen.add(at);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
for (const part of p.composition?.parts ?? []) {
|
|
141
|
+
if (part.region !== undefined && !names.has(part.region))
|
|
142
|
+
error("contract.pattern-region-unknown", `${p.id} part "${part.name}" is placed in unknown region "${part.region}"`, p.id);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function checkBehavior(p, error, warn) {
|
|
146
|
+
const b = p.behavior;
|
|
147
|
+
if (!b)
|
|
148
|
+
return;
|
|
149
|
+
const states = new Set(b.states.map((s) => s.name));
|
|
150
|
+
if (!states.has(b.initial))
|
|
151
|
+
error("contract.pattern-state-unknown", `${p.id} initial state "${b.initial}" is not a state`, p.id);
|
|
152
|
+
for (const t of b.transitions)
|
|
153
|
+
for (const end of [t.from, t.to])
|
|
154
|
+
if (!states.has(end))
|
|
155
|
+
error("contract.pattern-state-unknown", `${p.id} transition "${t.id}" uses unknown state "${end}"`, p.id);
|
|
156
|
+
const reached = new Set(states.has(b.initial) ? [b.initial] : []);
|
|
157
|
+
for (let grew = true; grew;) {
|
|
158
|
+
grew = false;
|
|
159
|
+
for (const t of b.transitions)
|
|
160
|
+
if (reached.has(t.from) && states.has(t.to) && !reached.has(t.to)) {
|
|
161
|
+
reached.add(t.to);
|
|
162
|
+
grew = true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (const name of states)
|
|
166
|
+
if (!reached.has(name) && states.has(b.initial))
|
|
167
|
+
warn("contract.pattern-unreachable-state", `${p.id} state "${name}" cannot be reached from "${b.initial}"`, p.id);
|
|
168
|
+
}
|
|
169
|
+
function checkRelationships(p, error) {
|
|
170
|
+
const seen = new Set();
|
|
171
|
+
for (const rel of p.relationships ?? []) {
|
|
172
|
+
if (rel.target === p.id)
|
|
173
|
+
error("contract.pattern-self-relationship", `${p.id} has a ${rel.type} relationship to itself`, p.id);
|
|
174
|
+
if (PATTERN_ONLY_RELATIONSHIPS.includes(rel.type) &&
|
|
175
|
+
!rel.target.startsWith("pattern:"))
|
|
176
|
+
error("contract.pattern-relationship-target", `${p.id} relationship "${rel.id}" (${rel.type}) must target a pattern, not ${rel.target}`, p.id);
|
|
177
|
+
const key = `${rel.type} ${rel.target}`;
|
|
178
|
+
if (seen.has(key))
|
|
179
|
+
error("contract.pattern-duplicate-relationship", `${p.id} repeats ${rel.type} ${rel.target}`, p.id);
|
|
180
|
+
seen.add(key);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function checkConstraints(contract, owned, error) {
|
|
184
|
+
for (const c of contract.constraints) {
|
|
185
|
+
if (!isPatternConstraintKind(c.kind))
|
|
186
|
+
continue;
|
|
187
|
+
const parsed = constraintDetailsSchemas[c.kind].safeParse(c.details);
|
|
188
|
+
if (!parsed.success) {
|
|
189
|
+
const issue = parsed.error.issues[0];
|
|
190
|
+
error("contract.constraint-details-invalid", `${c.id} (${c.kind}) has invalid details at ${issue?.path.join(".") || "(root)"}: ${issue?.message ?? "schema error"}`, c.id);
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const regions = stringsIn(parsed.data).filter((v) => v.startsWith("region:"));
|
|
194
|
+
if (regions.length === 0)
|
|
195
|
+
continue;
|
|
196
|
+
const pattern = owned.get(c.id);
|
|
197
|
+
if (!pattern) {
|
|
198
|
+
error("contract.pattern-region-unknown", `${c.id} refers to ${regions.join(", ")}, but no pattern owns the constraint`, c.id);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const known = new Set((pattern.layout?.regions ?? []).map((r) => `region:${r.name}`));
|
|
202
|
+
for (const region of regions)
|
|
203
|
+
if (!known.has(region))
|
|
204
|
+
error("contract.pattern-region-unknown", `${c.id} refers to ${region}, which ${pattern.id} does not define`, c.id);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
/** Patterns pointing at patterns through the relationship types that nest or depend, and through composition parts. */
|
|
208
|
+
function edges(contract) {
|
|
209
|
+
const ids = new Set(contract.patterns.map((p) => p.id));
|
|
210
|
+
const graph = new Map();
|
|
211
|
+
for (const p of contract.patterns) {
|
|
212
|
+
const out = new Set();
|
|
213
|
+
for (const rel of p.relationships ?? [])
|
|
214
|
+
if (ACYCLIC_RELATIONSHIPS.includes(rel.type) && ids.has(rel.target))
|
|
215
|
+
out.add(rel.target);
|
|
216
|
+
for (const part of [
|
|
217
|
+
...(p.composition?.parts ?? []),
|
|
218
|
+
...(p.variations ?? []).flatMap((v) => v.parts ?? [])
|
|
219
|
+
])
|
|
220
|
+
if (part.role !== "prohibited" && ids.has(part.ref))
|
|
221
|
+
out.add(part.ref);
|
|
222
|
+
graph.set(p.id, [...out].sort());
|
|
223
|
+
}
|
|
224
|
+
return graph;
|
|
225
|
+
}
|
|
226
|
+
function checkCycles(contract, error) {
|
|
227
|
+
const graph = edges(contract);
|
|
228
|
+
const state = new Map();
|
|
229
|
+
const reported = new Set();
|
|
230
|
+
const visit = (id, path) => {
|
|
231
|
+
state.set(id, "open");
|
|
232
|
+
for (const next of graph.get(id) ?? []) {
|
|
233
|
+
if (next === id)
|
|
234
|
+
continue; // reported as a self-relationship
|
|
235
|
+
if (state.get(next) === "open") {
|
|
236
|
+
const cycle = [...path.slice(path.indexOf(next)), id].filter((_, i, a) => i < a.length);
|
|
237
|
+
const start = cycle.indexOf([...cycle].sort()[0]);
|
|
238
|
+
const normal = [...cycle.slice(start), ...cycle.slice(0, start)];
|
|
239
|
+
const key = normal.join(" > ");
|
|
240
|
+
if (!reported.has(key)) {
|
|
241
|
+
reported.add(key);
|
|
242
|
+
error("contract.pattern-cycle", `Patterns form a cycle: ${[...normal, normal[0]].join(" -> ")}`, normal[0]);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else if (state.get(next) === undefined)
|
|
246
|
+
visit(next, [...path, id]);
|
|
247
|
+
}
|
|
248
|
+
state.set(id, "done");
|
|
249
|
+
};
|
|
250
|
+
for (const id of [...graph.keys()].sort())
|
|
251
|
+
if (state.get(id) === undefined)
|
|
252
|
+
visit(id, []);
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=pattern-checks.js.map
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { componentIdSchema, patternIdSchema, tokenIdSchema } from "./primitives.js";
|
|
3
|
+
/*
|
|
4
|
+
* Pattern contracts (contract 1.1.0). Every field here is optional on a pattern, so a 1.0.0 document is also a
|
|
5
|
+
* valid 1.1.0 document. Nothing in this file refers to a frontend framework or to how a rule is evaluated.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Names of regions, parts, states, transitions and relationships inside one pattern. They are fact-path
|
|
9
|
+
* segments (`layout.regions.header.required`), so unlike entity names they cannot contain dots.
|
|
10
|
+
*/
|
|
11
|
+
export const partNameSchema = z
|
|
12
|
+
.string()
|
|
13
|
+
.max(64)
|
|
14
|
+
.regex(/^[a-z0-9][a-z0-9_-]*$/);
|
|
15
|
+
/** How a requirement can be verified at best. A validator may never claim more than the method it used. */
|
|
16
|
+
export const verifiableBySchema = z.enum(["static", "runtime", "manual"]);
|
|
17
|
+
/** What a part or a relationship can point at. */
|
|
18
|
+
export const patternTargetSchema = z.union([componentIdSchema, patternIdSchema]);
|
|
19
|
+
/** A named region of the pattern that owns the constraint, written `region:<name>`. */
|
|
20
|
+
export const regionRefSchema = z.string().regex(/^region:[a-z0-9][a-z0-9_-]*$/);
|
|
21
|
+
/** What a structural constraint can talk about: a component, a pattern, or a region of the owning pattern. */
|
|
22
|
+
export const elementRefSchema = z.union([componentIdSchema, patternIdSchema, regionRefSchema]);
|
|
23
|
+
const lengthSchema = z.strictObject({
|
|
24
|
+
value: z.number().positive().finite(),
|
|
25
|
+
unit: z.enum(["px", "rem", "em", "%", "ch", "vw"])
|
|
26
|
+
});
|
|
27
|
+
export const layoutRegionSchema = z.strictObject({
|
|
28
|
+
name: partNameSchema,
|
|
29
|
+
description: z.string().min(1).optional(),
|
|
30
|
+
required: z.boolean(),
|
|
31
|
+
/** The enclosing region, which gives the layout its hierarchy. */
|
|
32
|
+
parent: partNameSchema.optional(),
|
|
33
|
+
/** Components and patterns that may fill the region. Absent means no restriction is stated. */
|
|
34
|
+
accepts: z.array(patternTargetSchema).optional(),
|
|
35
|
+
width: z.strictObject({ min: lengthSchema.optional(), max: lengthSchema.optional() }).optional(),
|
|
36
|
+
align: z.enum(["start", "center", "end", "stretch"]).optional(),
|
|
37
|
+
grid: z
|
|
38
|
+
.strictObject({ columns: z.number().int().min(1).max(24), gap: tokenIdSchema.optional() })
|
|
39
|
+
.optional(),
|
|
40
|
+
spacing: tokenIdSchema.optional(),
|
|
41
|
+
responsive: z
|
|
42
|
+
.array(z.strictObject({
|
|
43
|
+
breakpoint: tokenIdSchema,
|
|
44
|
+
behavior: z.enum(["unchanged", "stack", "collapse", "hide", "reflow"])
|
|
45
|
+
}))
|
|
46
|
+
.optional()
|
|
47
|
+
});
|
|
48
|
+
export const layoutSchema = z.strictObject({ regions: z.array(layoutRegionSchema).min(1) });
|
|
49
|
+
export const compositionPartSchema = z.strictObject({
|
|
50
|
+
name: partNameSchema,
|
|
51
|
+
ref: patternTargetSchema,
|
|
52
|
+
role: z.enum(["required", "optional", "prohibited"]),
|
|
53
|
+
/** The layout region the part belongs in. */
|
|
54
|
+
region: partNameSchema.optional(),
|
|
55
|
+
description: z.string().min(1).optional()
|
|
56
|
+
});
|
|
57
|
+
export const compositionSchema = z.strictObject({ parts: z.array(compositionPartSchema).min(1) });
|
|
58
|
+
/**
|
|
59
|
+
* A named, approved way a pattern may differ: some parts change role (a required part becomes optional or
|
|
60
|
+
* prohibited) and some parts exist only here. Everything else is the pattern's base composition. A variation
|
|
61
|
+
* is how an archetype says "this is allowed" without becoming a second pattern.
|
|
62
|
+
*/
|
|
63
|
+
export const patternVariationSchema = z
|
|
64
|
+
.strictObject({
|
|
65
|
+
/** Names the variation; evidence for it is addressed as `variations.<name>`. */
|
|
66
|
+
name: partNameSchema,
|
|
67
|
+
description: z.string().min(1).optional(),
|
|
68
|
+
/** When this variation is the right one, in words. */
|
|
69
|
+
when: z.string().min(1).optional(),
|
|
70
|
+
/** Parts of the base composition whose role differs in this variation. */
|
|
71
|
+
roles: z
|
|
72
|
+
.array(z.strictObject({ part: partNameSchema, role: z.enum(["required", "optional", "prohibited"]) }))
|
|
73
|
+
.optional(),
|
|
74
|
+
/** Parts that exist only in this variation. */
|
|
75
|
+
parts: z.array(compositionPartSchema).optional()
|
|
76
|
+
})
|
|
77
|
+
.refine((v) => (v.roles?.length ?? 0) + (v.parts?.length ?? 0) > 0, {
|
|
78
|
+
message: "a variation changes at least one part's role or adds a part"
|
|
79
|
+
});
|
|
80
|
+
export const behaviorStateSchema = z.strictObject({
|
|
81
|
+
name: partNameSchema,
|
|
82
|
+
description: z.string().min(1).optional(),
|
|
83
|
+
/** Feedback a user must be given while in this state. */
|
|
84
|
+
feedback: z.string().min(1).optional(),
|
|
85
|
+
terminal: z.boolean().optional(),
|
|
86
|
+
verifiableBy: verifiableBySchema.optional()
|
|
87
|
+
});
|
|
88
|
+
export const behaviorTransitionSchema = z.strictObject({
|
|
89
|
+
id: partNameSchema,
|
|
90
|
+
from: partNameSchema,
|
|
91
|
+
to: partNameSchema,
|
|
92
|
+
trigger: z.string().min(1).optional(),
|
|
93
|
+
verifiableBy: verifiableBySchema.optional()
|
|
94
|
+
});
|
|
95
|
+
/** An explicit state machine, enough to describe common interface interactions. It is data, not a workflow engine. */
|
|
96
|
+
export const behaviorSchema = z.strictObject({
|
|
97
|
+
initial: partNameSchema,
|
|
98
|
+
states: z.array(behaviorStateSchema).min(1),
|
|
99
|
+
transitions: z.array(behaviorTransitionSchema)
|
|
100
|
+
});
|
|
101
|
+
export const patternAccessibilitySchema = z.strictObject({
|
|
102
|
+
requirements: z
|
|
103
|
+
.array(z.strictObject({
|
|
104
|
+
id: partNameSchema,
|
|
105
|
+
statement: z.string().min(1),
|
|
106
|
+
verifiableBy: verifiableBySchema.optional()
|
|
107
|
+
}))
|
|
108
|
+
.min(1)
|
|
109
|
+
});
|
|
110
|
+
export const PATTERN_RELATIONSHIP_TYPES = [
|
|
111
|
+
"contains",
|
|
112
|
+
"requires",
|
|
113
|
+
"uses",
|
|
114
|
+
"extends",
|
|
115
|
+
"composes",
|
|
116
|
+
"alternative-to",
|
|
117
|
+
"prohibited-with",
|
|
118
|
+
"supersedes"
|
|
119
|
+
];
|
|
120
|
+
/** Relationship types whose target must be another pattern. `contains` and `uses` may also name a component. */
|
|
121
|
+
export const PATTERN_ONLY_RELATIONSHIPS = [
|
|
122
|
+
"requires",
|
|
123
|
+
"extends",
|
|
124
|
+
"composes",
|
|
125
|
+
"alternative-to",
|
|
126
|
+
"prohibited-with",
|
|
127
|
+
"supersedes"
|
|
128
|
+
];
|
|
129
|
+
/** Relationship types that must not form a cycle. `uses`, `alternative-to` and `prohibited-with` may. */
|
|
130
|
+
export const ACYCLIC_RELATIONSHIPS = [
|
|
131
|
+
"contains",
|
|
132
|
+
"requires",
|
|
133
|
+
"extends",
|
|
134
|
+
"composes",
|
|
135
|
+
"supersedes"
|
|
136
|
+
];
|
|
137
|
+
export const patternRelationshipSchema = z.strictObject({
|
|
138
|
+
/** Stable within the pattern; evidence for the edge is addressed as `relationships.<id>`. */
|
|
139
|
+
id: partNameSchema,
|
|
140
|
+
type: z.enum(PATTERN_RELATIONSHIP_TYPES),
|
|
141
|
+
target: patternTargetSchema,
|
|
142
|
+
description: z.string().min(1).optional()
|
|
143
|
+
});
|
|
144
|
+
/** Constraint kinds added for patterns. They carry a typed `details` object, validated by `verifyContract`. */
|
|
145
|
+
export const PATTERN_CONSTRAINT_KINDS = [
|
|
146
|
+
"required-child",
|
|
147
|
+
"forbidden-child",
|
|
148
|
+
"requires-parent",
|
|
149
|
+
"required-region",
|
|
150
|
+
"ordering",
|
|
151
|
+
"cardinality",
|
|
152
|
+
"required-state"
|
|
153
|
+
];
|
|
154
|
+
const count = z.number().int().min(0);
|
|
155
|
+
/**
|
|
156
|
+
* The interactions Invariant knows how to look for in code, and the states each can be required to represent.
|
|
157
|
+
* A state here means "the code represents this", not "this works at runtime"; see `evidenceLevelSchema`.
|
|
158
|
+
*/
|
|
159
|
+
export const INTERACTION_STATES = {
|
|
160
|
+
"form-submission": ["submitting", "error", "success", "guarded"],
|
|
161
|
+
"destructive-confirmation": ["confirming", "cancellable"],
|
|
162
|
+
"search-results": ["connected", "loading", "empty", "error", "clearable"]
|
|
163
|
+
};
|
|
164
|
+
export const interactionWorkflowSchema = z.enum(Object.keys(INTERACTION_STATES));
|
|
165
|
+
/**
|
|
166
|
+
* How much a check established. Static analysis reaches `structural` (the code has the piece and shows it in the
|
|
167
|
+
* interface) and `static-inferred` (a handler's code shows it happening, such as a pending flag set around an
|
|
168
|
+
* await). `runtime-verified` is reserved for results that come from running the code, which static analysis
|
|
169
|
+
* never produces.
|
|
170
|
+
*/
|
|
171
|
+
export const EVIDENCE_LEVELS = ["structural", "static-inferred", "runtime-verified"];
|
|
172
|
+
export const evidenceLevelSchema = z.enum(EVIDENCE_LEVELS);
|
|
173
|
+
export const constraintDetailsSchemas = {
|
|
174
|
+
"required-child": z.strictObject({
|
|
175
|
+
parent: elementRefSchema,
|
|
176
|
+
child: elementRefSchema,
|
|
177
|
+
min: z.number().int().min(1).optional()
|
|
178
|
+
}),
|
|
179
|
+
"forbidden-child": z.strictObject({ parent: elementRefSchema, child: elementRefSchema }),
|
|
180
|
+
"requires-parent": z.strictObject({ child: elementRefSchema, parents: z.array(elementRefSchema).min(1) }),
|
|
181
|
+
"required-region": z.strictObject({ region: regionRefSchema }),
|
|
182
|
+
ordering: z.strictObject({
|
|
183
|
+
before: elementRefSchema,
|
|
184
|
+
after: elementRefSchema,
|
|
185
|
+
within: elementRefSchema.optional()
|
|
186
|
+
}),
|
|
187
|
+
cardinality: z
|
|
188
|
+
.strictObject({
|
|
189
|
+
target: elementRefSchema,
|
|
190
|
+
within: elementRefSchema.optional(),
|
|
191
|
+
min: count.optional(),
|
|
192
|
+
max: count.optional()
|
|
193
|
+
})
|
|
194
|
+
.refine((d) => d.min !== undefined || d.max !== undefined, { message: "state min, max or both" })
|
|
195
|
+
.refine((d) => d.min === undefined || d.max === undefined || d.max >= d.min, {
|
|
196
|
+
message: "max must not be below min"
|
|
197
|
+
}),
|
|
198
|
+
"required-state": z
|
|
199
|
+
.strictObject({
|
|
200
|
+
workflow: interactionWorkflowSchema,
|
|
201
|
+
state: z.string().min(1).max(40),
|
|
202
|
+
/** The weakest evidence that satisfies the rule. Default `structural`. */
|
|
203
|
+
minimum: z.enum(["structural", "static-inferred"]).optional()
|
|
204
|
+
})
|
|
205
|
+
.refine((d) => INTERACTION_STATES[d.workflow].includes(d.state), {
|
|
206
|
+
message: "state is not one of the workflow's states",
|
|
207
|
+
path: ["state"]
|
|
208
|
+
})
|
|
209
|
+
};
|
|
210
|
+
export const isPatternConstraintKind = (kind) => PATTERN_CONSTRAINT_KINDS.includes(kind);
|
|
211
|
+
/** Pattern categories introduced in 1.1.0. */
|
|
212
|
+
export const PATTERN_CATEGORIES_1_1 = ["interaction", "page"];
|
|
213
|
+
export const PATTERN_CATEGORIES = [
|
|
214
|
+
"layout",
|
|
215
|
+
"navigation",
|
|
216
|
+
"form",
|
|
217
|
+
"data-table",
|
|
218
|
+
"card",
|
|
219
|
+
"dialog",
|
|
220
|
+
"workflow",
|
|
221
|
+
"composition",
|
|
222
|
+
"other",
|
|
223
|
+
...PATTERN_CATEGORIES_1_1
|
|
224
|
+
];
|
|
225
|
+
//# sourceMappingURL=patterns.js.map
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const SEGMENT = "[A-Za-z0-9_:-]+";
|
|
3
|
+
/** Source ids are plain slugs: `ui-library`, `storybook`. */
|
|
4
|
+
export const sourceIdSchema = z
|
|
5
|
+
.string()
|
|
6
|
+
.max(64)
|
|
7
|
+
.regex(/^[a-z0-9][a-z0-9._-]*$/);
|
|
8
|
+
export const slugSchema = sourceIdSchema;
|
|
9
|
+
export const ENTITY_KINDS = [
|
|
10
|
+
"component",
|
|
11
|
+
"token",
|
|
12
|
+
"theme",
|
|
13
|
+
"pattern",
|
|
14
|
+
"guideline",
|
|
15
|
+
"constraint",
|
|
16
|
+
"policy",
|
|
17
|
+
"conflict"
|
|
18
|
+
];
|
|
19
|
+
const NAME_BODY = "[a-z0-9][a-z0-9._-]*(?:/[a-z0-9][a-z0-9._-]*)*";
|
|
20
|
+
// Theme ids carry no dots or slashes: they appear as keys inside dotted fact paths.
|
|
21
|
+
const THEME_BODY = "[a-z0-9][a-z0-9_-]*";
|
|
22
|
+
/** Stable entity ids are `<kind>:<canonical-name>`, for example `component:button`. */
|
|
23
|
+
export function entityIdSchemaFor(kind) {
|
|
24
|
+
const body = kind === "theme" ? THEME_BODY : NAME_BODY;
|
|
25
|
+
return z.string().regex(new RegExp(`^${kind}:${body}$`));
|
|
26
|
+
}
|
|
27
|
+
export const entityIdSchema = z.string().regex(new RegExp(`^(?:${ENTITY_KINDS.join("|")}):${NAME_BODY}$`));
|
|
28
|
+
export const componentIdSchema = entityIdSchemaFor("component");
|
|
29
|
+
export const tokenIdSchema = entityIdSchemaFor("token");
|
|
30
|
+
export const themeIdSchema = entityIdSchemaFor("theme");
|
|
31
|
+
export const patternIdSchema = entityIdSchemaFor("pattern");
|
|
32
|
+
export const guidelineIdSchema = entityIdSchemaFor("guideline");
|
|
33
|
+
export const constraintIdSchema = entityIdSchemaFor("constraint");
|
|
34
|
+
export const policyIdSchema = entityIdSchemaFor("policy");
|
|
35
|
+
export const conflictIdSchema = entityIdSchemaFor("conflict");
|
|
36
|
+
/**
|
|
37
|
+
* Dotted path to a field inside an entity, for example `props.size.allowedValues`.
|
|
38
|
+
* Items of keyed arrays (props, events, slots, ...) are addressed by name or id.
|
|
39
|
+
*/
|
|
40
|
+
export const factPathSchema = z.string().regex(new RegExp(`^${SEGMENT}(?:\\.${SEGMENT})*$`));
|
|
41
|
+
export const isoTimestampSchema = z.iso.datetime();
|
|
42
|
+
export const sha256HexSchema = z.string().regex(/^[0-9a-f]{64}$/);
|
|
43
|
+
export const confidenceSchema = z.number().min(0).max(1);
|
|
44
|
+
export const jsonValueSchema = z.lazy(() => z.union([
|
|
45
|
+
z.string(),
|
|
46
|
+
z.number().finite(),
|
|
47
|
+
z.boolean(),
|
|
48
|
+
z.null(),
|
|
49
|
+
z.array(jsonValueSchema),
|
|
50
|
+
z.record(z.string(), jsonValueSchema)
|
|
51
|
+
]));
|
|
52
|
+
export const jsonObjectSchema = z.record(z.string(), jsonValueSchema);
|
|
53
|
+
/**
|
|
54
|
+
* Framework-specific data lives here, namespaced by framework or tool (`react`, `angular`, `vue`, ...).
|
|
55
|
+
* The core model never interprets it.
|
|
56
|
+
*/
|
|
57
|
+
export const extensionsSchema = z.record(slugSchema, jsonObjectSchema);
|
|
58
|
+
export const FRAMEWORKS = ["react", "angular", "vue", "other"];
|
|
59
|
+
export const frameworkSchema = z.enum(FRAMEWORKS);
|
|
60
|
+
export const SOURCE_TYPES = [
|
|
61
|
+
"react",
|
|
62
|
+
"angular",
|
|
63
|
+
"vue",
|
|
64
|
+
"storybook",
|
|
65
|
+
"figma",
|
|
66
|
+
"zeroheight",
|
|
67
|
+
"tokens",
|
|
68
|
+
"markdown",
|
|
69
|
+
"git",
|
|
70
|
+
"npm",
|
|
71
|
+
/** A human-authored pattern definition file. Adapters never emit it, so it is not an IR source type. */
|
|
72
|
+
"patterns"
|
|
73
|
+
];
|
|
74
|
+
export const sourceTypeSchema = z.enum(SOURCE_TYPES);
|
|
75
|
+
export const relativePathSchema = z
|
|
76
|
+
.string()
|
|
77
|
+
.min(1)
|
|
78
|
+
.refine((p) => !p.startsWith("/") && !/^[A-Za-z]:/.test(p) && !p.includes("\\") && !p.split("/").includes(".."), {
|
|
79
|
+
message: "must be a POSIX path relative to the source root, with no '..' segments"
|
|
80
|
+
});
|
|
81
|
+
//# sourceMappingURL=primitives.js.map
|