@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,30 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isoTimestampSchema, sourceIdSchema, sourceTypeSchema } from "./primitives.js";
|
|
3
|
+
/**
|
|
4
|
+
* Authentication metadata only. `credentialRef` names where a credential lives (for example an
|
|
5
|
+
* environment variable name); the credential itself can never be represented here.
|
|
6
|
+
*/
|
|
7
|
+
export const authenticationSchema = z.strictObject({
|
|
8
|
+
method: z.enum(["none", "token", "oauth", "ssh"]),
|
|
9
|
+
credentialRef: z.string().min(1).optional()
|
|
10
|
+
});
|
|
11
|
+
export const sourceVersionSchema = z.strictObject({
|
|
12
|
+
kind: z.enum(["git-commit", "package-version", "tool-version", "file-version", "content-hash"]),
|
|
13
|
+
value: z.string().min(1)
|
|
14
|
+
});
|
|
15
|
+
export const sourceSchema = z.strictObject({
|
|
16
|
+
id: sourceIdSchema,
|
|
17
|
+
type: sourceTypeSchema,
|
|
18
|
+
location: z.string().min(1),
|
|
19
|
+
/** Absent when the source exposes no version. The compiler must not invent one. */
|
|
20
|
+
version: sourceVersionSchema.optional(),
|
|
21
|
+
authentication: authenticationSchema.optional(),
|
|
22
|
+
lastSyncedAt: isoTimestampSchema.optional(),
|
|
23
|
+
/** Fact-path patterns this source is able to supply, for example `props.*.required`. */
|
|
24
|
+
capabilities: z.array(z.string().min(1)),
|
|
25
|
+
health: z.strictObject({
|
|
26
|
+
status: z.enum(["ok", "degraded", "failed"]),
|
|
27
|
+
diagnosticCount: z.number().int().nonnegative()
|
|
28
|
+
})
|
|
29
|
+
});
|
|
30
|
+
//# sourceMappingURL=sources.js.map
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { deprecationSchema } from "./components.js";
|
|
3
|
+
import { factsSchema } from "./facts.js";
|
|
4
|
+
import { extensionsSchema, jsonValueSchema, themeIdSchema, tokenIdSchema } from "./primitives.js";
|
|
5
|
+
export const TOKEN_CATEGORIES = [
|
|
6
|
+
"color",
|
|
7
|
+
"typography",
|
|
8
|
+
"spacing",
|
|
9
|
+
"sizing",
|
|
10
|
+
"border",
|
|
11
|
+
"shadow",
|
|
12
|
+
"breakpoint",
|
|
13
|
+
"motion",
|
|
14
|
+
"other"
|
|
15
|
+
];
|
|
16
|
+
export const tokenCategorySchema = z.enum(TOKEN_CATEGORIES);
|
|
17
|
+
/** A token value is either a literal or a reference to another token (a semantic alias), never both. */
|
|
18
|
+
export const tokenValueSchema = z
|
|
19
|
+
.strictObject({ value: jsonValueSchema.optional(), aliasOf: tokenIdSchema.optional() })
|
|
20
|
+
.refine((v) => (v.value === undefined) !== (v.aliasOf === undefined), {
|
|
21
|
+
message: "exactly one of 'value' or 'aliasOf' is required"
|
|
22
|
+
});
|
|
23
|
+
export const themeSchema = z.strictObject({
|
|
24
|
+
id: themeIdSchema,
|
|
25
|
+
name: z.string().min(1),
|
|
26
|
+
description: z.string().optional(),
|
|
27
|
+
default: z.boolean().optional()
|
|
28
|
+
});
|
|
29
|
+
export const tokenSchema = z.strictObject({
|
|
30
|
+
id: tokenIdSchema,
|
|
31
|
+
name: z.string().min(1),
|
|
32
|
+
category: tokenCategorySchema,
|
|
33
|
+
description: z.string().optional(),
|
|
34
|
+
value: tokenValueSchema,
|
|
35
|
+
/** Per-theme values keyed by theme id. */
|
|
36
|
+
modes: z.record(themeIdSchema, tokenValueSchema).optional(),
|
|
37
|
+
deprecated: deprecationSchema.optional(),
|
|
38
|
+
facts: factsSchema,
|
|
39
|
+
extensions: extensionsSchema.optional()
|
|
40
|
+
});
|
|
41
|
+
//# sourceMappingURL=tokens.js.map
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
import { sortDiagnostics } from "../shared/index.js";
|
|
2
|
+
import { CONTRACT_VERSION, contractSchema, SUPPORTED_CONTRACT_VERSIONS } from "./contract.js";
|
|
3
|
+
import { computeEvidenceId } from "./evidence.js";
|
|
4
|
+
import { resolveFactPath } from "./fact-path.js";
|
|
5
|
+
import { computeFactFingerprint } from "./facts.js";
|
|
6
|
+
import { computeContentHash } from "./integrity.js";
|
|
7
|
+
import { checkPatterns, constraintReferences, newInOnePointOne, patternReferences } from "./pattern-checks.js";
|
|
8
|
+
/**
|
|
9
|
+
* Structural validation. Major-version mismatches are reported before schema errors. Minor versions
|
|
10
|
+
* are additive (1.1.0 only added optional fields), so older documents are read as they are and there is
|
|
11
|
+
* nothing to migrate; a migration registry is added with the first breaking schema change.
|
|
12
|
+
*/
|
|
13
|
+
export function parseContract(input) {
|
|
14
|
+
const version = declaredVersion(input);
|
|
15
|
+
if (version !== undefined && version.split(".")[0] !== CONTRACT_VERSION.split(".")[0]) {
|
|
16
|
+
return {
|
|
17
|
+
ok: false,
|
|
18
|
+
diagnostics: [
|
|
19
|
+
{
|
|
20
|
+
code: "contract.unsupported-version",
|
|
21
|
+
severity: "error",
|
|
22
|
+
message: `Contract version ${version} is not supported; this build reads ${SUPPORTED_CONTRACT_VERSIONS.join(" and ")}`
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const result = contractSchema.safeParse(input);
|
|
28
|
+
return result.success
|
|
29
|
+
? { ok: true, value: result.data }
|
|
30
|
+
: { ok: false, diagnostics: schemaDiagnostics(result.error) };
|
|
31
|
+
}
|
|
32
|
+
/** Semantic validation: cross-references, ordering, fact paths, approvals, hashes and evidence links. */
|
|
33
|
+
export function verifyContract(contract, evidenceFile) {
|
|
34
|
+
const found = [];
|
|
35
|
+
const report = (code, message, entityId) => {
|
|
36
|
+
found.push({ code, severity: "error", message, ...(entityId ? { entityId } : {}) });
|
|
37
|
+
};
|
|
38
|
+
const sourceIds = new Set(contract.sources.map((s) => s.id));
|
|
39
|
+
const themeIds = new Set(contract.themes.map((t) => t.id));
|
|
40
|
+
const entityIds = new Set();
|
|
41
|
+
checkOrdering("source", contract.sources.map((s) => s.id), report);
|
|
42
|
+
for (const [kind, ids] of collectionIds(contract)) {
|
|
43
|
+
checkOrdering(kind, ids, report);
|
|
44
|
+
for (const id of ids)
|
|
45
|
+
entityIds.add(id);
|
|
46
|
+
}
|
|
47
|
+
for (const { from, to } of entityReferences(contract)) {
|
|
48
|
+
if (!entityIds.has(to))
|
|
49
|
+
report("contract.dangling-reference", `${from} references unknown entity ${to}`, from);
|
|
50
|
+
}
|
|
51
|
+
for (const { from, sourceId } of sourceReferences(contract)) {
|
|
52
|
+
if (!sourceIds.has(sourceId))
|
|
53
|
+
report("contract.unknown-source", `${from} references unknown source ${sourceId}`, from);
|
|
54
|
+
}
|
|
55
|
+
for (const token of contract.tokens) {
|
|
56
|
+
for (const theme of Object.keys(token.modes ?? {})) {
|
|
57
|
+
if (!themeIds.has(theme))
|
|
58
|
+
report("contract.dangling-reference", `${token.id} has a mode for unknown theme ${theme}`, token.id);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const frameworks = new Set(contract.metadata.frameworks);
|
|
62
|
+
for (const component of contract.components) {
|
|
63
|
+
for (const impl of component.implementations) {
|
|
64
|
+
if (!frameworks.has(impl.framework)) {
|
|
65
|
+
report("contract.framework-mismatch", `${component.id} uses framework ${impl.framework} missing from metadata.frameworks`, component.id);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
checkPatterns(contract, report, found);
|
|
70
|
+
if (contract.contractVersion === "1.0.0")
|
|
71
|
+
for (const { entityId, reason } of newInOnePointOne(contract))
|
|
72
|
+
report("contract.field-newer-than-version", `${entityId} ${reason}, which contract 1.0.0 cannot contain; the document must declare 1.1.0`, entityId);
|
|
73
|
+
const evidenceIdsUsed = new Set();
|
|
74
|
+
for (const entity of factBearingEntities(contract)) {
|
|
75
|
+
for (const [path, fact] of Object.entries(entity.facts)) {
|
|
76
|
+
for (const id of fact.evidenceIds)
|
|
77
|
+
evidenceIdsUsed.add(id);
|
|
78
|
+
checkFact({ entity, path, fact, report });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
for (const conflict of contract.conflicts)
|
|
82
|
+
for (const claim of conflict.claims)
|
|
83
|
+
for (const id of claim.evidenceIds)
|
|
84
|
+
evidenceIdsUsed.add(id);
|
|
85
|
+
for (const token of contract.tokens)
|
|
86
|
+
checkAliasCycles({ token, tokens: contract.tokens, themeIds: [...themeIds], report });
|
|
87
|
+
if (computeContentHash(contract) !== contract.integrity.contentHash) {
|
|
88
|
+
report("contract.hash-mismatch", "integrity.contentHash does not match the contract content");
|
|
89
|
+
}
|
|
90
|
+
if (evidenceFile)
|
|
91
|
+
checkEvidence({ contract, evidenceFile, evidenceIdsUsed, sourceIds, report });
|
|
92
|
+
return sortDiagnostics(found);
|
|
93
|
+
}
|
|
94
|
+
function checkFact(input) {
|
|
95
|
+
const { entity, path, fact, report } = input;
|
|
96
|
+
const resolved = resolveFactPath(entity, path);
|
|
97
|
+
if (!resolved.found) {
|
|
98
|
+
report("contract.unresolvable-fact-path", `${entity.id} has a fact for '${path}' but the path does not resolve`, entity.id);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
if (fact.status === "approved" && fact.approval) {
|
|
102
|
+
const fingerprint = computeFactFingerprint({ entityId: entity.id, path, value: resolved.value });
|
|
103
|
+
if (fingerprint !== fact.approval.fingerprint) {
|
|
104
|
+
report("contract.approval-stale", `Approval of ${entity.id} '${path}' no longer matches its current value`, entity.id);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function checkOrdering(kind, ids, report) {
|
|
109
|
+
for (let i = 1; i < ids.length; i++) {
|
|
110
|
+
const previous = ids[i - 1];
|
|
111
|
+
const current = ids[i];
|
|
112
|
+
if (current === previous)
|
|
113
|
+
report("contract.duplicate-id", `Duplicate ${kind} id ${current}`, current);
|
|
114
|
+
else if (current < previous)
|
|
115
|
+
report("contract.unsorted", `${kind} ids must be sorted: ${current} follows ${previous}`, current);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
function checkAliasCycles(input) {
|
|
119
|
+
const { token, tokens, themeIds, report } = input;
|
|
120
|
+
const byId = new Map(tokens.map((t) => [t.id, t]));
|
|
121
|
+
for (const layer of [undefined, ...themeIds]) {
|
|
122
|
+
const seen = new Set([token.id]);
|
|
123
|
+
let current = token;
|
|
124
|
+
while (current) {
|
|
125
|
+
const modes = current.modes ?? {};
|
|
126
|
+
const value = (layer && modes[layer]) || current.value;
|
|
127
|
+
if (!value.aliasOf)
|
|
128
|
+
break;
|
|
129
|
+
if (seen.has(value.aliasOf)) {
|
|
130
|
+
report("contract.token-alias-cycle", `Token ${token.id} is part of an alias cycle${layer ? ` in ${layer}` : ""}`, token.id);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
seen.add(value.aliasOf);
|
|
134
|
+
current = byId.get(value.aliasOf);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function checkEvidence(input) {
|
|
139
|
+
const { contract, evidenceFile, evidenceIdsUsed, sourceIds, report } = input;
|
|
140
|
+
if (evidenceFile.contractContentHash !== contract.integrity.contentHash) {
|
|
141
|
+
report("contract.evidence-hash-mismatch", "Evidence file belongs to a different contract content hash");
|
|
142
|
+
}
|
|
143
|
+
const known = new Set();
|
|
144
|
+
checkOrdering("evidence", evidenceFile.evidence.map((e) => e.id), report);
|
|
145
|
+
for (const evidence of evidenceFile.evidence) {
|
|
146
|
+
known.add(evidence.id);
|
|
147
|
+
if (computeEvidenceId(evidence) !== evidence.id)
|
|
148
|
+
report("contract.evidence-id-mismatch", `Evidence ${evidence.id} id does not match its content`);
|
|
149
|
+
if (!sourceIds.has(evidence.sourceId))
|
|
150
|
+
report("contract.unknown-source", `Evidence ${evidence.id} references unknown source ${evidence.sourceId}`);
|
|
151
|
+
}
|
|
152
|
+
for (const id of [...evidenceIdsUsed].sort()) {
|
|
153
|
+
if (!known.has(id))
|
|
154
|
+
report("contract.unknown-evidence", `Evidence ${id} is referenced but missing from the evidence file`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function collectionIds(contract) {
|
|
158
|
+
return [
|
|
159
|
+
["component", contract.components.map((e) => e.id)],
|
|
160
|
+
["token", contract.tokens.map((e) => e.id)],
|
|
161
|
+
["theme", contract.themes.map((e) => e.id)],
|
|
162
|
+
["pattern", contract.patterns.map((e) => e.id)],
|
|
163
|
+
["guideline", contract.guidelines.map((e) => e.id)],
|
|
164
|
+
["constraint", contract.constraints.map((e) => e.id)],
|
|
165
|
+
["policy", contract.policies.map((e) => e.id)],
|
|
166
|
+
["conflict", contract.conflicts.map((e) => e.id)]
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
function factBearingEntities(contract) {
|
|
170
|
+
return [
|
|
171
|
+
...contract.components,
|
|
172
|
+
...contract.tokens,
|
|
173
|
+
...contract.patterns,
|
|
174
|
+
...contract.guidelines,
|
|
175
|
+
...contract.constraints
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
function entityReferences(contract) {
|
|
179
|
+
const refs = [];
|
|
180
|
+
const add = (from, to) => {
|
|
181
|
+
if (to)
|
|
182
|
+
refs.push({ from, to });
|
|
183
|
+
};
|
|
184
|
+
for (const c of contract.components) {
|
|
185
|
+
add(c.id, c.deprecated?.replacedBy);
|
|
186
|
+
for (const prop of c.props)
|
|
187
|
+
add(c.id, prop.deprecated?.replacedBy);
|
|
188
|
+
for (const event of c.events)
|
|
189
|
+
add(c.id, event.deprecated?.replacedBy);
|
|
190
|
+
for (const slot of c.slots)
|
|
191
|
+
for (const target of slot.accepts ?? [])
|
|
192
|
+
add(c.id, target);
|
|
193
|
+
for (const rel of c.composition)
|
|
194
|
+
add(c.id, rel.target);
|
|
195
|
+
}
|
|
196
|
+
for (const t of contract.tokens) {
|
|
197
|
+
add(t.id, t.deprecated?.replacedBy);
|
|
198
|
+
add(t.id, t.value.aliasOf);
|
|
199
|
+
for (const mode of Object.values(t.modes ?? {}))
|
|
200
|
+
add(t.id, mode.aliasOf);
|
|
201
|
+
}
|
|
202
|
+
for (const p of contract.patterns)
|
|
203
|
+
for (const to of [...p.components, ...p.guidelines])
|
|
204
|
+
add(p.id, to);
|
|
205
|
+
for (const g of contract.guidelines)
|
|
206
|
+
for (const to of g.appliesTo)
|
|
207
|
+
add(g.id, to);
|
|
208
|
+
for (const c of contract.constraints)
|
|
209
|
+
for (const to of c.subjects)
|
|
210
|
+
add(c.id, to);
|
|
211
|
+
for (const { from, to } of [...patternReferences(contract), ...constraintReferences(contract)])
|
|
212
|
+
add(from, to);
|
|
213
|
+
for (const p of contract.policies)
|
|
214
|
+
for (const to of [...(p.scope.entities ?? []), ...p.basedOn])
|
|
215
|
+
add(p.id, to);
|
|
216
|
+
for (const c of contract.conflicts)
|
|
217
|
+
add(c.id, c.subject);
|
|
218
|
+
return refs;
|
|
219
|
+
}
|
|
220
|
+
function sourceReferences(contract) {
|
|
221
|
+
const refs = [];
|
|
222
|
+
for (const c of contract.components) {
|
|
223
|
+
for (const impl of c.implementations)
|
|
224
|
+
refs.push({ from: c.id, sourceId: impl.sourceId });
|
|
225
|
+
for (const example of c.examples)
|
|
226
|
+
if (example.sourceId)
|
|
227
|
+
refs.push({ from: c.id, sourceId: example.sourceId });
|
|
228
|
+
}
|
|
229
|
+
for (const g of contract.guidelines)
|
|
230
|
+
for (const ref of g.references)
|
|
231
|
+
refs.push({ from: g.id, sourceId: ref.sourceId });
|
|
232
|
+
for (const c of contract.conflicts)
|
|
233
|
+
for (const claim of c.claims)
|
|
234
|
+
refs.push({ from: c.id, sourceId: claim.sourceId });
|
|
235
|
+
return refs;
|
|
236
|
+
}
|
|
237
|
+
function declaredVersion(input) {
|
|
238
|
+
if (typeof input !== "object" || input === null)
|
|
239
|
+
return undefined;
|
|
240
|
+
const version = input.contractVersion;
|
|
241
|
+
return typeof version === "string" ? version : undefined;
|
|
242
|
+
}
|
|
243
|
+
function schemaDiagnostics(error) {
|
|
244
|
+
return sortDiagnostics(error.issues.map((issue) => ({
|
|
245
|
+
code: "contract.invalid-schema",
|
|
246
|
+
severity: "error",
|
|
247
|
+
message: `${formatPath(issue.path)}: ${issue.message}`
|
|
248
|
+
})));
|
|
249
|
+
}
|
|
250
|
+
function formatPath(path) {
|
|
251
|
+
return path.reduce((acc, part) => (typeof part === "number" ? `${acc}[${part}]` : `${acc}.${String(part)}`), "$");
|
|
252
|
+
}
|
|
253
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { effectiveParts, isPatternConstraintKind } from "../contract/index.js";
|
|
2
|
+
import { patternStatus } from "./patterns.js";
|
|
3
|
+
import { cmp } from "./system.js";
|
|
4
|
+
const MAX_DEPTH = 6;
|
|
5
|
+
const COMPOSING = new Set(["composes", "contains", "uses", "requires"]);
|
|
6
|
+
const kindOf = (id) => id.slice(0, Math.max(0, id.indexOf(":")));
|
|
7
|
+
function describe(ds, id) {
|
|
8
|
+
const name = ds.component(id)?.name ?? ds.pattern(id)?.name;
|
|
9
|
+
return { id, kind: kindOf(id), ...(name ? { name } : {}) };
|
|
10
|
+
}
|
|
11
|
+
function summary(p) {
|
|
12
|
+
const s = patternStatus(p);
|
|
13
|
+
return {
|
|
14
|
+
id: p.id,
|
|
15
|
+
name: p.name,
|
|
16
|
+
category: p.category,
|
|
17
|
+
status: s.overall,
|
|
18
|
+
approved: s.overall === "approved"
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A pattern read as a page blueprint: what it is made of, followed through the patterns it composes to any depth,
|
|
23
|
+
* its regions, variations, required interaction states, responsive expectations and prohibited combinations, and
|
|
24
|
+
* how much of it is approved. A variation, when named, is applied to the pattern's own composition (patterns it
|
|
25
|
+
* composes contribute their base composition).
|
|
26
|
+
*/
|
|
27
|
+
export function archetypeView(ds, root, options = {}) {
|
|
28
|
+
const names = (root.variations ?? []).map((v) => v.name);
|
|
29
|
+
if (options.variation !== undefined && !names.includes(options.variation))
|
|
30
|
+
return {
|
|
31
|
+
ok: false,
|
|
32
|
+
message: `${root.id} has no variation "${options.variation}"${names.length > 0 ? `; its variations are ${names.join(", ")}` : " (it has none)"}`,
|
|
33
|
+
variations: names
|
|
34
|
+
};
|
|
35
|
+
const members = new Map();
|
|
36
|
+
const missing = new Set();
|
|
37
|
+
let truncated = false;
|
|
38
|
+
const node = (p, variation, trail, depth) => {
|
|
39
|
+
const parts = effectiveParts(p, variation);
|
|
40
|
+
const seen = new Set();
|
|
41
|
+
const expand = (ref, via) => {
|
|
42
|
+
if (!ref.startsWith("pattern:"))
|
|
43
|
+
return undefined;
|
|
44
|
+
const n = ds.pattern(ref);
|
|
45
|
+
if (!n) {
|
|
46
|
+
missing.add(ref);
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
if (trail.includes(ref) || ref === p.id)
|
|
50
|
+
return { cycle: true };
|
|
51
|
+
if (depth + 1 >= MAX_DEPTH) {
|
|
52
|
+
truncated = true;
|
|
53
|
+
return { truncated: true };
|
|
54
|
+
}
|
|
55
|
+
const known = members.get(ref);
|
|
56
|
+
if (!known || known.depth > depth + 1)
|
|
57
|
+
members.set(ref, { pattern: n, depth: depth + 1, via });
|
|
58
|
+
return node(n, undefined, [...trail, p.id], depth + 1);
|
|
59
|
+
};
|
|
60
|
+
const rendered = parts.map((x) => {
|
|
61
|
+
seen.add(x.ref);
|
|
62
|
+
const expanded = x.role === "prohibited" ? undefined : expand(x.ref, `${p.id} part ${x.name}`);
|
|
63
|
+
return {
|
|
64
|
+
name: x.name,
|
|
65
|
+
role: x.role,
|
|
66
|
+
...(x.region ? { region: x.region } : {}),
|
|
67
|
+
ref: describe(ds, x.ref),
|
|
68
|
+
...(expanded ? { expanded } : {})
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
const related = (p.relationships ?? [])
|
|
72
|
+
.filter((r) => COMPOSING.has(r.type))
|
|
73
|
+
.map((r) => {
|
|
74
|
+
const expanded = seen.has(r.target) ? undefined : expand(r.target, `${p.id} ${r.type}`);
|
|
75
|
+
return {
|
|
76
|
+
id: r.id,
|
|
77
|
+
type: r.type,
|
|
78
|
+
target: describe(ds, r.target),
|
|
79
|
+
...(expanded ? { expanded } : {})
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
return { pattern: summary(p), parts: rendered, related };
|
|
83
|
+
};
|
|
84
|
+
members.set(root.id, { pattern: root, depth: 0, via: "the archetype itself" });
|
|
85
|
+
const tree = node(root, options.variation, [], 0);
|
|
86
|
+
const set = [...members.values()].sort((a, b) => a.depth - b.depth || cmp(a.pattern.id, b.pattern.id));
|
|
87
|
+
const constraints = set.flatMap(({ pattern }) => (pattern.constraints ?? []).flatMap((id) => {
|
|
88
|
+
const c = ds.contract.constraints.find((x) => x.id === id);
|
|
89
|
+
if (!c)
|
|
90
|
+
return [];
|
|
91
|
+
const enforcedBy = ds.contract.policies.filter((p) => p.basedOn.includes(c.id)).map((p) => p.id);
|
|
92
|
+
return [{ pattern, constraint: c, enforcedBy }];
|
|
93
|
+
}));
|
|
94
|
+
const requiredStates = constraints
|
|
95
|
+
.filter((x) => x.constraint.kind === "required-state")
|
|
96
|
+
.map(({ pattern, constraint, enforcedBy }) => {
|
|
97
|
+
const d = (constraint.details ?? {});
|
|
98
|
+
return {
|
|
99
|
+
pattern: pattern.id,
|
|
100
|
+
constraint: constraint.id,
|
|
101
|
+
workflow: d.workflow ?? null,
|
|
102
|
+
state: d.state ?? null,
|
|
103
|
+
minimum: d.minimum ?? "structural",
|
|
104
|
+
enforced: enforcedBy.length > 0,
|
|
105
|
+
enforcedBy
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
const structural = constraints
|
|
109
|
+
.filter((x) => x.constraint.kind !== "required-state" && isPatternConstraintKind(x.constraint.kind))
|
|
110
|
+
.map(({ pattern, constraint, enforcedBy }) => ({
|
|
111
|
+
pattern: pattern.id,
|
|
112
|
+
constraint: constraint.id,
|
|
113
|
+
kind: constraint.kind,
|
|
114
|
+
enforced: enforcedBy.length > 0,
|
|
115
|
+
enforcedBy
|
|
116
|
+
}));
|
|
117
|
+
const responsive = set.flatMap(({ pattern }) => (pattern.layout?.regions ?? []).flatMap((r) => (r.responsive ?? []).map((x) => ({
|
|
118
|
+
pattern: pattern.id,
|
|
119
|
+
region: r.name,
|
|
120
|
+
breakpoint: x.breakpoint,
|
|
121
|
+
behavior: x.behavior
|
|
122
|
+
}))));
|
|
123
|
+
const parts = effectiveParts(root, options.variation);
|
|
124
|
+
const regions = (root.layout?.regions ?? []).map((r) => ({
|
|
125
|
+
name: r.name,
|
|
126
|
+
required: r.required,
|
|
127
|
+
...(r.parent ? { parent: r.parent } : {}),
|
|
128
|
+
parts: parts.filter((x) => x.region === r.name && x.role !== "prohibited").map((x) => x.name),
|
|
129
|
+
...(r.responsive ? { responsive: r.responsive } : {})
|
|
130
|
+
}));
|
|
131
|
+
const variations = (root.variations ?? []).map((v) => ({
|
|
132
|
+
name: v.name,
|
|
133
|
+
...(v.description ? { description: v.description } : {}),
|
|
134
|
+
...(v.when ? { when: v.when } : {}),
|
|
135
|
+
roles: (v.roles ?? []),
|
|
136
|
+
addedParts: (v.parts ?? []).map((x) => ({ name: x.name, role: x.role, ref: describe(ds, x.ref) })),
|
|
137
|
+
fact: ds.factView(root.facts, `variations.${v.name}`) ?? null
|
|
138
|
+
}));
|
|
139
|
+
const inSet = new Set(set.map((x) => x.pattern.id));
|
|
140
|
+
const prohibitedTogether = set.flatMap(({ pattern }) => (pattern.relationships ?? [])
|
|
141
|
+
.filter((r) => r.type === "prohibited-with")
|
|
142
|
+
.map((r) => ({ pattern: pattern.id, with: r.target, composedHere: inSet.has(r.target) })));
|
|
143
|
+
const unapproved = set.filter((x) => !summary(x.pattern).approved);
|
|
144
|
+
const rootStatus = patternStatus(root);
|
|
145
|
+
const notes = [];
|
|
146
|
+
if (rootStatus.overall !== "approved")
|
|
147
|
+
notes.push(`This archetype is not fully approved (${rootStatus.overall}). Treat it as a proposal.`);
|
|
148
|
+
const nested = unapproved.filter((x) => x.pattern.id !== root.id);
|
|
149
|
+
if (rootStatus.overall === "approved" && nested.length > 0)
|
|
150
|
+
notes.push(`This archetype is approved, but ${nested.length} pattern(s) it builds on are not: ${nested.map((x) => x.pattern.id).join(", ")}. Approval of the archetype does not approve them.`);
|
|
151
|
+
if (missing.size > 0)
|
|
152
|
+
notes.push(`It refers to pattern(s) the contract does not have: ${[...missing].sort(cmp).join(", ")}.`);
|
|
153
|
+
if (truncated)
|
|
154
|
+
notes.push(`Composition was followed ${MAX_DEPTH} levels deep and then stopped.`);
|
|
155
|
+
const unenforced = requiredStates.filter((x) => x.enforced !== true).length + structural.filter((x) => !x.enforced).length;
|
|
156
|
+
if (constraints.length > 0 && unenforced > 0)
|
|
157
|
+
notes.push(`${unenforced} constraint(s) have no approved policy, so \`validate\` does not enforce them.`);
|
|
158
|
+
if (options.variation)
|
|
159
|
+
notes.push(`Showing the "${options.variation}" variation. Patterns it composes are shown as written.`);
|
|
160
|
+
return {
|
|
161
|
+
ok: true,
|
|
162
|
+
view: {
|
|
163
|
+
id: root.id,
|
|
164
|
+
name: root.name,
|
|
165
|
+
category: root.category,
|
|
166
|
+
...(root.intent ? { intent: root.intent } : {}),
|
|
167
|
+
...(options.variation ? { variation: options.variation } : {}),
|
|
168
|
+
status: { overall: rootStatus.overall, counts: rootStatus.counts, total: rootStatus.total },
|
|
169
|
+
approved: rootStatus.overall === "approved",
|
|
170
|
+
regions,
|
|
171
|
+
composition: tree,
|
|
172
|
+
patterns: set.map((x) => ({ ...summary(x.pattern), depth: x.depth, via: x.via })),
|
|
173
|
+
unapproved: unapproved.map((x) => x.pattern.id),
|
|
174
|
+
variations,
|
|
175
|
+
prohibited: {
|
|
176
|
+
parts: parts
|
|
177
|
+
.filter((x) => x.role === "prohibited")
|
|
178
|
+
.map((x) => ({ name: x.name, ref: describe(ds, x.ref) })),
|
|
179
|
+
patterns: prohibitedTogether
|
|
180
|
+
},
|
|
181
|
+
requiredStates,
|
|
182
|
+
constraints: structural,
|
|
183
|
+
responsive,
|
|
184
|
+
notes
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=archetype.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { archetypeView } from "./archetype.js";
|
|
2
|
+
export { patternRelationships, patternStatus, patternsView, patternView } from "./patterns.js";
|
|
3
|
+
export { search } from "./search.js";
|
|
4
|
+
export { cmp, DesignSystem, normalize, sanitizeName } from "./system.js";
|
|
5
|
+
export { catalog, componentView, compositionView, guidelinesView, overview, page, tokensView, tokenView, usageView } from "./views.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|