@reqvire-org/ads 0.3.20 → 0.3.21
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/README.md +2 -0
- package/dist/behaviors.mjs +14 -4
- package/dist/bootstrap-ds/README.md +1 -1
- package/dist/bootstrap-ds/design-system/tokens/{Foundation.scenario.tsx → Foundation.ads.scenario.tsx} +1 -1
- package/dist/bootstrap-ds/tsconfig.build.json +9 -2
- package/dist/boundaries.mjs +2 -0
- package/dist/check.mjs +25 -5
- package/dist/config.mjs +21 -4
- package/dist/contract-migration.mjs +110 -0
- package/dist/contracts.d.mts +22 -0
- package/dist/evidence.mjs +1 -1
- package/dist/index.mjs +1 -0
- package/dist/init.mjs +1 -1
- package/dist/mcp/catalog.mjs +9 -2
- package/dist/mcp/context.mjs +1 -1
- package/dist/mcp/edit-preparation.mjs +6 -3
- package/dist/mcp/preview-widget.bundle.js +41 -39
- package/dist/mcp/server.mjs +3 -1
- package/dist/paint-contracts.mjs +124 -0
- package/dist/preview-resources.mjs +1 -1
- package/dist/public-props.mjs +2 -2
- package/dist/runtime-contracts.mjs +225 -0
- package/dist/scenario-trace.mjs +32 -4
- package/dist/scenarios-vite.mjs +1 -1
- package/dist/scenarios.mjs +82 -26
- package/dist/scoped-tokens.mjs +9 -4
- package/dist/skills/ads-break-ui/SKILL.md +4 -0
- package/dist/skills/ads-break-ui/references/contracts.md +250 -0
- package/dist/skills/ads-designer/SKILL.md +4 -0
- package/dist/skills/ads-designer/assets/bootstrap-ds/README.md +1 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/{Foundation.scenario.tsx → Foundation.ads.scenario.tsx} +1 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/tsconfig.build.json +9 -2
- package/dist/skills/ads-designer/references/contracts.md +250 -0
- package/dist/skills/ads-import/SKILL.md +4 -0
- package/dist/skills/ads-import/references/contracts.md +250 -0
- package/dist/skills/ads-migrate/SKILL.md +4 -0
- package/dist/skills/ads-migrate/references/contracts.md +250 -0
- package/dist/skills/ads-motion/SKILL.md +4 -0
- package/dist/skills/ads-motion/references/contracts.md +250 -0
- package/dist/token-origins.mjs +24 -0
- package/dist/tokens.mjs +4 -3
- package/dist/unit-contracts.mjs +203 -0
- package/dist/units.mjs +9 -3
- package/dist/verification/browser.mjs +156 -26
- package/dist/verification/playwright-reporter.cjs +13 -3
- package/docs/ADS.md +8 -1
- package/docs/ARCHITECTURE.md +36 -0
- package/docs/BEHAVIORS.md +8 -0
- package/docs/CHANGELOG.md +19 -0
- package/docs/CONTRACTS.md +250 -0
- package/docs/GUIDE.md +26 -1
- package/docs/MCP.md +15 -0
- package/package.json +4 -1
- /package/dist/bootstrap-ds/design-system/tokens/{Foundation.fixture.ts → Foundation.ads.fixture.ts} +0 -0
- /package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/{Foundation.fixture.ts → Foundation.ads.fixture.ts} +0 -0
package/dist/scenarios.mjs
CHANGED
|
@@ -3,13 +3,16 @@ import { existsSync } from "node:fs";
|
|
|
3
3
|
import ts from "typescript";
|
|
4
4
|
import { publicApi, scenarioApi } from "./public-api.mjs";
|
|
5
5
|
import { uiOperation } from "./ui-framework.mjs";
|
|
6
|
-
import { isDevelopmentFile, isInside, isScenarioFile, sourceFiles } from "./config.mjs";
|
|
6
|
+
import { isDevelopmentFile, isInside, isScenarioFile, scenarioIdentity, sourceFiles } from "./config.mjs";
|
|
7
7
|
import { projectCompilerOptions } from "./boundaries.mjs";
|
|
8
8
|
import { inspectTokens } from "./tokens.mjs";
|
|
9
9
|
import { isSharedToken } from "./token-policy.mjs";
|
|
10
10
|
import { validTokenSamples } from "./token-samples.mjs";
|
|
11
11
|
import { scopedTokens } from "./scoped-tokens.mjs";
|
|
12
12
|
import { createScenarioTrace } from "./scenario-trace.mjs";
|
|
13
|
+
import { inspectRuntimeContracts } from "./runtime-contracts.mjs";
|
|
14
|
+
import { inspectUnitContracts } from "./unit-contracts.mjs";
|
|
15
|
+
import { inspectPaintContracts } from "./paint-contracts.mjs";
|
|
13
16
|
|
|
14
17
|
export const scenarioArtifact = (root, id) => path.join(root, ".ds/scenarios", id, "index.html");
|
|
15
18
|
|
|
@@ -37,42 +40,53 @@ export function publicUi(program, options) {
|
|
|
37
40
|
export function inspectScenarios(options, suppliedProgram) {
|
|
38
41
|
const { root, dsRoot } = options;
|
|
39
42
|
const files = sourceFiles(dsRoot).filter(isScenarioFile);
|
|
40
|
-
const program = suppliedProgram ?? ts.createProgram([path.join(dsRoot, options.config.publicEntry), path.join(dsRoot, options.config.reviewEntry), ...files], { ...projectCompilerOptions(options), allowJs: true });
|
|
43
|
+
const program = suppliedProgram ?? inspectUnitContracts(options).program ?? ts.createProgram([path.join(dsRoot, options.config.publicEntry), path.join(dsRoot, options.config.reviewEntry), ...files], { ...projectCompilerOptions(options), allowJs: true });
|
|
41
44
|
const apis = scenarioApi(program, options);
|
|
42
45
|
const duplicate = apis.find((item, index) => apis.some((other, otherIndex) => index !== otherIndex && item.name === other.name));
|
|
43
46
|
const known = new Map(apis.filter(item => item.kind !== "controllers").map((item) => [item.name, item]));
|
|
44
47
|
const checker = program.getTypeChecker();
|
|
48
|
+
const declarativeParts = inspectRuntimeContracts(options, program).parts;
|
|
49
|
+
const contractDeclarations=inspectUnitContracts(options).contracts;
|
|
50
|
+
const bindingTrace = contractDeclarations.some(contract=>contract.paintInputs?.length) ? createScenarioTrace(program, options) : null;
|
|
45
51
|
const behaviorApis = new Map(publicApi(program, options).filter(item => !item.renderable && item.behavioral).map(item => [item.name, item]));
|
|
46
|
-
const tokenInventory = inspectTokens(root);
|
|
52
|
+
const tokenInventory = inspectTokens(root, options);
|
|
47
53
|
const tokenNames = new Set(tokenInventory.tokens.map(token => token.name));
|
|
48
54
|
const scoped = scopedTokens(options);
|
|
49
55
|
const scenarios = [];
|
|
50
|
-
const diagnostics = [];
|
|
56
|
+
const diagnostics = [...inspectUnitContracts(options).diagnostics,...inspectRuntimeContracts(options,program).diagnostics,...inspectPaintContracts(options).diagnostics];
|
|
51
57
|
const ids = new Set();
|
|
52
|
-
function report(file, message
|
|
58
|
+
function report(file, message, node, detail = {}) {
|
|
59
|
+
const position = node?.getSourceFile().getLineAndCharacterOfPosition(node.getStart());
|
|
60
|
+
diagnostics.push({ file: path.relative(root, file).replaceAll("\\", "/"), rule: "scenario-contract", line: (position?.line ?? 0) + 1, column: (position?.character ?? 0) + 1, message, ...detail });
|
|
61
|
+
}
|
|
53
62
|
if (duplicate) report(path.join(dsRoot, options.config.reviewEntry), `Review export ${duplicate.name} collides with the production API. Use a distinct development export so scenario coverage is unambiguous.`);
|
|
54
63
|
for (const file of files) {
|
|
55
64
|
const relative = path.relative(dsRoot, file).replaceAll("\\", "/");
|
|
56
65
|
let kind = isInside(path.join(dsRoot, options.config.tokenDir), file) ? "tokens" : relative.split("/")[0];
|
|
57
66
|
if (!["tokens", "components", "patterns", "composition"].includes(kind)) { report(file, "Scenarios live beside tokens/components/patterns or under composition/, not inside showcase pages."); continue; }
|
|
58
|
-
|
|
59
|
-
if (id.length > 256) { report(file, "Scenario paths must fit within 256 characters."); continue; }
|
|
60
|
-
if (ids.has(id)) { report(file, `Duplicate scenario id ${id}. Use one entrypoint per identity.`); continue; }
|
|
61
|
-
ids.add(id);
|
|
67
|
+
let id = scenarioIdentity(file, dsRoot);
|
|
62
68
|
const source = program.getSourceFile(file);
|
|
63
69
|
if (!source) { report(file, "Cannot inspect scenario source."); continue; }
|
|
64
|
-
let metadata;
|
|
70
|
+
let metadata, metadataExpression;
|
|
65
71
|
let entry = false;
|
|
66
72
|
for (const node of source.statements) {
|
|
67
73
|
if (ts.isExportAssignment(node) || node.modifiers?.some((item) => item.kind === ts.SyntaxKind.DefaultKeyword)) entry = true;
|
|
68
74
|
if (ts.isVariableStatement(node) && node.modifiers?.some((item) => item.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
69
|
-
for (const declaration of node.declarationList.declarations) if (ts.isIdentifier(declaration.name) && declaration.name.text === "scenario") metadata = literal(
|
|
75
|
+
for (const declaration of node.declarationList.declarations) if (ts.isIdentifier(declaration.name) && declaration.name.text === "scenario") { metadataExpression = declaration.initializer; metadata = literal(metadataExpression, checker); }
|
|
70
76
|
}
|
|
71
77
|
}
|
|
78
|
+
if(metadata?.id!==undefined) {
|
|
79
|
+
const owner=scoped.inventory.owner(relative);
|
|
80
|
+
if(typeof metadata.id!=="string"||metadata.id.length>256||!/^[\w-]+(?:[/.][\w-]+)*$/.test(metadata.id)||!owner||!(metadata.id===owner||metadata.id.startsWith(owner+"/")||metadata.id.startsWith(owner+".")))report(file,"Scenario id must be a safe stable identity inside this owner; it cannot capture another layer/owner.",metadataExpression,{entry:"id"});
|
|
81
|
+
else id=metadata.id;
|
|
82
|
+
}
|
|
83
|
+
if (id.length > 256) { report(file, "Scenario paths must fit within 256 characters."); continue; }
|
|
84
|
+
if (ids.has(id)) { report(file, `Duplicate scenario id ${id}. Use one entrypoint per identity.`); continue; }
|
|
85
|
+
ids.add(id);
|
|
72
86
|
if (metadata?.kind !== undefined) {
|
|
73
87
|
const owner = scoped.inventory.owner(relative);
|
|
74
|
-
if (metadata.kind !== "tokens" || !scoped.definitions.some(entry => entry.unit === owner))
|
|
75
|
-
kind = "tokens";
|
|
88
|
+
if (metadata.kind !== "tokens" || !scoped.definitions.some(entry => entry.unit === owner)) report(file, "Only an owner with registered local tokens may set kind: tokens for a colocated token preview.",entryNode("kind"),{entry:"kind"});
|
|
89
|
+
else kind = "tokens";
|
|
76
90
|
}
|
|
77
91
|
if (!entry) report(file, "Export a default independently mountable scenario entrypoint.");
|
|
78
92
|
const module = checker.getSymbolAtLocation(source);
|
|
@@ -90,12 +104,45 @@ export function inspectScenarios(options, suppliedProgram) {
|
|
|
90
104
|
const fixtureBase = path.basename(file).startsWith("Scenario.") ? path.join(path.dirname(file), "fixture") : file.replace(/\.scenario\.[^.]+$/, ".fixture");
|
|
91
105
|
const fixture = [".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs", ".json"].map(extension => fixtureBase + extension).find(existsSync);
|
|
92
106
|
if (!fixture) report(file, "Keep this scenario's inputs beside it: fixture.ts (directory entry) or <Owner>.fixture.ts (flat entry). An empty fixture is valid for a component without inputs.");
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
function entryNode(field, index) {
|
|
108
|
+
let value = metadataExpression;
|
|
109
|
+
const seen = new Set();
|
|
110
|
+
while (value && !seen.has(value)) {
|
|
111
|
+
seen.add(value);
|
|
112
|
+
if (ts.isSatisfiesExpression(value) || ts.isAsExpression(value) || ts.isParenthesizedExpression(value)) value = value.expression;
|
|
113
|
+
else if (ts.isIdentifier(value)) { let symbol = checker.getSymbolAtLocation(value); if (symbol?.flags & ts.SymbolFlags.Alias) symbol = checker.getAliasedSymbol(symbol); value = symbol?.valueDeclaration?.initializer; }
|
|
114
|
+
else break;
|
|
115
|
+
}
|
|
116
|
+
const property = value && ts.isObjectLiteralExpression(value) && value.properties.find(item => ts.isPropertyAssignment(item) && (ts.isIdentifier(item.name) || ts.isStringLiteralLike(item.name)) && item.name.text === field);
|
|
117
|
+
return index !== undefined && property && ts.isArrayLiteralExpression(property.initializer) ? property.initializer.elements[index] : property?.initializer ?? metadataExpression;
|
|
118
|
+
}
|
|
119
|
+
if (!metadata) {
|
|
120
|
+
report(file, "Scenario metadata is not statically readable. Render coverage is not established until this prerequisite is repaired.", metadataExpression, {coverage:"not-established",owner:scoped.inventory.owner(relative)});
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
for(const field of Object.keys(metadata))if(!["id","title","kind","covers","exercises","tokens","samples","captureTargets"].includes(field))report(file,`Unknown scenario field ${field}.`,entryNode(field),{entry:field});
|
|
124
|
+
if(typeof metadata.title!=="string"||!metadata.title.trim()) {
|
|
125
|
+
report(file,"Scenario title must be a nonempty static string; independent render evidence is retained.",entryNode("title"),{entry:"title"});
|
|
126
|
+
metadata.title=id;
|
|
127
|
+
}
|
|
128
|
+
if(!Array.isArray(metadata.covers)||!metadata.covers.length) {
|
|
129
|
+
report(file,"Scenario covers must be a nonempty static array naming rendered public UI. Coverage is not established until repaired.",entryNode("covers"),{entry:"covers",coverage:"not-established",owner:scoped.inventory.owner(relative)});
|
|
130
|
+
metadata.covers=[];
|
|
131
|
+
}
|
|
132
|
+
if(Object.hasOwn(metadata,"tokens")&&(!Array.isArray(metadata.tokens)||metadata.tokens.some(name=>typeof name!=="string"||!name.startsWith("--")))) {
|
|
133
|
+
report(file,"Scenario tokens must be a static list of real token names.",entryNode("tokens"),{entry:"tokens"});metadata.tokens=[];
|
|
134
|
+
}
|
|
135
|
+
if(kind==="tokens"&&Object.hasOwn(metadata,"tokens")||kind==="tokens"&&!validTokenSamples(metadata.samples)||Object.hasOwn(metadata,"samples")&&!validTokenSamples(metadata.samples)) {
|
|
136
|
+
report(file,"Token scenarios need samples: fixture.samples, a literal list of { token, property, probe } real CSS bindings. Printing values or covers: [] is incomplete.",entryNode("samples"),{entry:"samples"});delete metadata.samples;
|
|
137
|
+
}
|
|
138
|
+
const covers = [];
|
|
139
|
+
for (const [index, name] of metadata.covers.entries()) {
|
|
140
|
+
const api = typeof name === "string" && known.get(name);
|
|
141
|
+
if (!api?.renderable || declarativeParts.some(part=>part.apiName===name)) report(file, `Scenario covers[${index}] ${JSON.stringify(name) ?? "(unresolved)"} ${api ? "is not independently rendered UI. Use exercises for a behavioral or registered declarative API." : "does not name a known public UI export."} Independent valid render evidence is retained.`, entryNode("covers", index), { entry: `covers[${index}]`, ...(typeof name === "string" ? { api: name } : {}), reason: api ? "wrong-api-role" : "unknown-api" });
|
|
142
|
+
else covers.push(name);
|
|
98
143
|
}
|
|
144
|
+
metadata = { ...metadata, covers };
|
|
145
|
+
if (kind === "tokens" && !covers.some(name => ["patterns", "review"].includes(known.get(name)?.kind))) report(file, "Token covers must name the real development review renderer or product pattern consuming samples.", entryNode("covers"));
|
|
99
146
|
// A bad behavioral entry must fail validation without erasing independent
|
|
100
147
|
// render evidence and manufacturing missing-coverage errors for real UI.
|
|
101
148
|
const exercises = [];
|
|
@@ -107,8 +154,8 @@ export function inspectScenarios(options, suppliedProgram) {
|
|
|
107
154
|
if (Object.hasOwn(metadata, "exercises")) {
|
|
108
155
|
if (!Array.isArray(metadata.exercises)) report(file, "Scenario exercises must be a static array naming public behavioral APIs.");
|
|
109
156
|
else for (const [index, name] of metadata.exercises.entries()) {
|
|
110
|
-
if (typeof name !== "string") report(file, `Scenario exercises[${index}] must name a public behavioral API, not ${JSON.stringify(name) ?? "an unresolved value"}
|
|
111
|
-
else if (!behaviorApis.has(name)) report(file, `Scenario exercises[${index}] ${JSON.stringify(name)} is not a recognized public behavioral API. Use a public callable function, hook or controller; rendered UI belongs in covers. Discover the project's exports before declaring exercises
|
|
157
|
+
if (typeof name !== "string") report(file, `Scenario exercises[${index}] must name a public behavioral API, not ${JSON.stringify(name) ?? "an unresolved value"}.`, entryNode("exercises", index), { entry: `exercises[${index}]` });
|
|
158
|
+
else if (!behaviorApis.has(name) && !declarativeParts.some(part=>part.apiName===name)) report(file, `Scenario exercises[${index}] ${JSON.stringify(name)} is not a recognized public behavioral API. Use a public callable function, hook or controller; rendered UI belongs in covers. Discover the project's exports before declaring exercises.`, entryNode("exercises", index), { entry: `exercises[${index}]`, api: name });
|
|
112
159
|
else exercises.push(name);
|
|
113
160
|
}
|
|
114
161
|
}
|
|
@@ -120,16 +167,22 @@ export function inspectScenarios(options, suppliedProgram) {
|
|
|
120
167
|
for (const name of tokens) if (tokenInventory.tokens.find(token => token.name === name)?.owner !== owner) report(file, `Token ${name} needs a preview beside its defining owner. Shared tokens belong under the configured token directory; local tokens stay with their component/pattern.`);
|
|
121
168
|
}
|
|
122
169
|
const stylesheets = kind === "tokens" ? scoped.definitions.filter(entry => entry.unit === scoped.inventory.owner(relative)).map(entry => entry.file) : [];
|
|
123
|
-
|
|
170
|
+
const reachable = new Set(metadata.covers);
|
|
171
|
+
function findBindings(node) { if(ts.isJsxElement(node)||ts.isJsxSelfClosingElement(node))for(const name of bindingTrace.traceRenders(node).rendered)reachable.add(name);ts.forEachChild(node,findBindings); }
|
|
172
|
+
if(bindingTrace) findBindings(source);
|
|
173
|
+
const paintBindings = inspectRuntimeContracts(options,program).bindings.filter(binding=>reachable.has(binding.scope.apiName)).map(binding=>({owner:binding.unit,name:binding.name,scope:binding.scope.selector,prefix:binding.prefix,suffix:binding.suffix,tokens:tokenInventory.tokens.filter(token=>!token.owner||token.owner==="tokens"||token.owner===binding.unit).map(token=>token.name)}));
|
|
174
|
+
const paintAudit=contractDeclarations.some(contract=>contract.paintInputs?.some(input=>reachable.has(input.apiName)));
|
|
175
|
+
scenarios.push({ id, kind, title: metadata.title, ...(fixture ? { fixture: path.relative(root, fixture).replaceAll("\\", "/") } : {}), covers: [...new Set(metadata.covers)], ...(captureTargets ? { captureTargets } : {}), ...(Object.hasOwn(metadata, "exercises") ? { exercises: [...new Set(exercises)] } : {}), tokens, ...(metadata.samples ? { samples: metadata.samples } : {}), ...(stylesheets.length ? { stylesheets } : {}), ...(paintBindings.length?{paintBindings}:{}), ...(paintAudit?{paintAudit:true}:{}), file: path.relative(root, file).replaceAll("\\", "/"), artifact: path.relative(root, scenarioArtifact(root, id)).replaceAll("\\", "/") });
|
|
124
176
|
}
|
|
125
177
|
return { scenarios: scenarios.sort((a, b) => a.id.localeCompare(b.id)), diagnostics };
|
|
126
178
|
}
|
|
127
179
|
|
|
128
180
|
export function inspectScenarioCoverage(options, program, report, advise = () => {}, trace = createScenarioTrace(program, options)) {
|
|
129
181
|
const registry = inspectScenarios(options, program);
|
|
130
|
-
for (const
|
|
182
|
+
for (const {file,rule,message,line,column,...detail} of registry.diagnostics) report(path.join(options.root, file), rule, message, line, column, detail);
|
|
131
183
|
const checker = program.getTypeChecker();
|
|
132
184
|
const ui = publicUi(program, options);
|
|
185
|
+
const parts = inspectRuntimeContracts(options, program).parts;
|
|
133
186
|
function symbolOf(expression) {
|
|
134
187
|
while (expression && (ts.isParenthesizedExpression(expression) || ts.isAsExpression(expression) || ts.isSatisfiesExpression(expression) || ts.isNonNullExpression(expression))) expression = expression.expression;
|
|
135
188
|
if (!expression) return null;
|
|
@@ -167,12 +220,12 @@ export function inspectScenarioCoverage(options, program, report, advise = () =>
|
|
|
167
220
|
|
|
168
221
|
const covered = new Set();
|
|
169
222
|
const coveredTokens = new Set();
|
|
170
|
-
const tokenInventory = inspectTokens(options.root);
|
|
223
|
+
const tokenInventory = inspectTokens(options.root, options);
|
|
171
224
|
const declaredTokens = new Set(tokenInventory.tokens.map(token => token.name));
|
|
172
225
|
for (const scenario of registry.scenarios) {
|
|
173
226
|
const source = program.getSourceFile(path.join(options.root, scenario.file));
|
|
174
227
|
const rendered = new Set();
|
|
175
|
-
const exercised = new Set(), referencedApis = new Set();
|
|
228
|
+
const exercised = new Set(), referencedApis = new Set(), declarative = new Set();
|
|
176
229
|
const referencedTokens = new Set();
|
|
177
230
|
let samplesPassed = false;
|
|
178
231
|
const renderLimits = new Set();
|
|
@@ -229,12 +282,14 @@ export function inspectScenarioCoverage(options, program, report, advise = () =>
|
|
|
229
282
|
ts.forEachChild(node, child => visit(child, reached && (ts.isExportAssignment(node) || ts.isVariableDeclaration(node))));
|
|
230
283
|
}
|
|
231
284
|
for (const node of source.statements) if (ts.isExportAssignment(node) || node.modifiers?.some(item => item.kind === ts.SyntaxKind.DefaultKeyword)) visit(node, true);
|
|
285
|
+
if(parts.some(part=>scenario.exercises?.includes(part.apiName)))for(const name of trace.traceEntry(source).declarative)declarative.add(name);
|
|
232
286
|
for (const name of scenario.covers) {
|
|
233
287
|
if (!rendered.has(name)) report(source.fileName, "scenario-render", `${name} is declared in covers but has no reachable public render reference. Render it directly or through a traced parent with fixture props/defaults selecting that child. Imports, unused JSX and unknown conditional paths do not establish coverage.${renderLimits.size ? ` Source tracing could not establish: ${[...renderLimits].slice(0, 2).join(" ")}` : ""} Review the real scenario in a browser; do not hide a valid public part just to avoid coverage. See ads://guides/scenarios.`);
|
|
234
288
|
else covered.add(name);
|
|
235
289
|
}
|
|
236
290
|
for (const name of scenario.exercises ?? []) {
|
|
237
|
-
if (
|
|
291
|
+
if (parts.some(part=>part.apiName===name) && !declarative.has(name)) report(source.fileName, "scenario-exercise", `${name} is a registered declarative API, but its child does not reach the declared consumer through a rendered parent. Imports, unused children and unrelated parent renders do not establish consumption.`);
|
|
292
|
+
else if (!exercised.has(name) && !referencedApis.has(name)) report(source.fileName, "scenario-exercise", `${name} is declared in exercises but has no reachable behavioral use. Imports and unused adapters do not count.`);
|
|
238
293
|
else if (!exercised.has(name)) advise(source.fileName, "scenario-exercise-review", `${name} is referenced dynamically. Verify its invocation in the registered browser/behavior checks; a reference alone does not prove execution.`);
|
|
239
294
|
}
|
|
240
295
|
for (const name of referencedTokens) if (!declaredTokens.has(name)) report(source.fileName, "token-scenario-reference", `Reachable scenario input references undeclared token ${name}.`);
|
|
@@ -244,7 +299,8 @@ export function inspectScenarioCoverage(options, program, report, advise = () =>
|
|
|
244
299
|
else if (scenario.kind === "tokens" && samplesPassed && declaredTokens.has(name)) coveredTokens.add(name);
|
|
245
300
|
}
|
|
246
301
|
}
|
|
247
|
-
|
|
302
|
+
const blockedOwners=new Set(registry.diagnostics.filter(item=>item.coverage==="not-established").map(item=>item.owner));
|
|
303
|
+
for (const item of ui.filter((item) => item.renderable && !covered.has(item.name) && !blockedOwners.has(scopedTokens(options).inventory.owner(scopedTokens(options).inventory.relative(path.relative(options.root,item.file)))))) report(item.file, "scenario-coverage", `Public UI ${item.name} needs a colocated, registered scenario render reference. Related parts may share a scenario.`);
|
|
248
304
|
for (const token of tokenInventory.tokens.filter(token => !coveredTokens.has(token.name))) {
|
|
249
305
|
const definition = token.definitions[0];
|
|
250
306
|
report(path.join(options.root, definition.file), "token-scenario-coverage", `Token ${token.name} needs a registered scenario with a reachable token fixture reference.`, definition.line, definition.column);
|
package/dist/scoped-tokens.mjs
CHANGED
|
@@ -7,19 +7,24 @@ import { isDevelopmentFile } from "./config.mjs";
|
|
|
7
7
|
import { foundationSelector } from "./foundation.mjs";
|
|
8
8
|
import { isSharedToken, tokenContract } from "./token-policy.mjs";
|
|
9
9
|
import { propertyRegistration } from "./property-registrations.mjs";
|
|
10
|
+
import { inspectUnitContracts } from "./unit-contracts.mjs";
|
|
10
11
|
|
|
11
12
|
/** An explicit owner contract, never a heuristic based on a token's name. */
|
|
12
13
|
export function scopedTokens(options) {
|
|
13
14
|
if (options.scopedTokens) return options.scopedTokens;
|
|
14
|
-
const inventory = inspectUnits(options), definitions = [], diagnostics = [];
|
|
15
|
+
const inventory = inspectUnits(options), definitions = [], declarations = inspectUnitContracts(options), diagnostics = [...declarations.diagnostics];
|
|
15
16
|
const report = (file, message, line = 1) => diagnostics.push({rule:"scoped-token", file, message, line, column:1});
|
|
16
17
|
for (const unit of inventory.units.filter(unit => ["components", "patterns"].includes(unit.kind))) {
|
|
17
|
-
|
|
18
|
+
const inputs = [
|
|
19
|
+
...unit.files.filter(file => /(?:^|\/)tokens\.json$|\.tokens\.json$/.test(file)).map(manifest => ({manifest})),
|
|
20
|
+
...declarations.contracts.filter(entry => entry.unit === unit.id && entry.tokens).map(entry => ({manifest:entry.file, contract:entry.tokens})),
|
|
21
|
+
];
|
|
22
|
+
for (const {manifest, contract} of inputs) {
|
|
18
23
|
const absolute = path.join(options.root, manifest);
|
|
19
24
|
try {
|
|
20
25
|
if (statSync(absolute).size > 65536 || realpathSync(absolute) !== absolute) throw new Error("Token manifests must be ordinary files no larger than 64 KiB.");
|
|
21
|
-
const data = JSON.parse(readFileSync(absolute, "utf8"));
|
|
22
|
-
const stylesheet = manifest.replace(/\.json$/, ".css");
|
|
26
|
+
const data = contract ? {schemaVersion:1, namespace:contract.namespace} : JSON.parse(readFileSync(absolute, "utf8"));
|
|
27
|
+
const stylesheet = contract ? contract.file : manifest.replace(/\.json$/, ".css");
|
|
23
28
|
if (!data || data.schemaVersion !== 1 || Object.keys(data).some(key => !["schemaVersion", "namespace"].includes(key))
|
|
24
29
|
|| typeof data.namespace !== "string" || !/^--[a-z][a-z0-9-]*-$/.test(data.namespace)
|
|
25
30
|
|| /^(?:--ds-|--ux-|--showcase-)/.test(data.namespace) || isSharedToken(data.namespace + "value")
|
|
@@ -355,3 +355,7 @@ rendered check.
|
|
|
355
355
|
|
|
356
356
|
Source credits and retained MIT terms: [README.md](README.md) and
|
|
357
357
|
[LICENSE.upstream](LICENSE.upstream).
|
|
358
|
+
|
|
359
|
+
For static owner contracts, `.ads.` development roles, typed paint/binding APIs,
|
|
360
|
+
declarative evidence, token privacy and guarded migration, read
|
|
361
|
+
[contracts.md](references/contracts.md) when the task touches those contracts.
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# Static owner contracts and development files
|
|
2
|
+
|
|
3
|
+
Use one optional `<Owner>.ads.contract.ts` for owner-local tokens, constrained
|
|
4
|
+
paint inputs, scoped runtime bindings, declarative parts and behavioral checks.
|
|
5
|
+
The import `import type { UnitContract } from "@reqvire-org/ads/contracts"` supplies
|
|
6
|
+
development typing. ADS reads its AST and resolves real symbols without importing
|
|
7
|
+
or executing project modules. It adds no production runtime.
|
|
8
|
+
|
|
9
|
+
## Files and ownership
|
|
10
|
+
|
|
11
|
+
```text
|
|
12
|
+
components/Plot/
|
|
13
|
+
Plot.tsx production API/root
|
|
14
|
+
Series.tsx production declarative API
|
|
15
|
+
renderer.tsx production consumer
|
|
16
|
+
paints.ts production binding producer
|
|
17
|
+
types.ts production prop types
|
|
18
|
+
tokens.css optional production token definitions
|
|
19
|
+
Plot.ads.contract.ts static tool declaration
|
|
20
|
+
Plot.ads.scenario.tsx demonstration
|
|
21
|
+
Plot.ads.fixture.ts demonstration data
|
|
22
|
+
Tokens.ads.scenario.tsx real owner-token samples
|
|
23
|
+
Tokens.ads.fixture.ts sample bindings/probes
|
|
24
|
+
Plot.ads.behavior.spec.ts registered browser checks
|
|
25
|
+
Plot.ads.test.tsx optional unit tests
|
|
26
|
+
Plot.ads.helpers.ts development-only adapters
|
|
27
|
+
README.md API/provenance
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
This is an example, not a required inventory. Use the same roles for patterns,
|
|
31
|
+
controllers and relevant development owners. `review/` remains a separate
|
|
32
|
+
shared development layer; its implementation filenames need no extra marker.
|
|
33
|
+
Runtime components, types and styles cannot import `.ads.` files, tests, fixtures
|
|
34
|
+
or review code through aliases, barrels or helpers. Unknown `.ads.` roles are
|
|
35
|
+
still development-only and are diagnosed. Fixtures/helpers cannot define styles;
|
|
36
|
+
put demo framing in review. Ordinary README/license files retain their names.
|
|
37
|
+
|
|
38
|
+
Scenarios/fixtures/review enter only the selected preview closure. Contracts are
|
|
39
|
+
never executable preview modules; derived audit descriptors are data. Specs run
|
|
40
|
+
only in the configured test runner. Production declaration emission must also
|
|
41
|
+
start from the public runtime entry, rather than include every file under the
|
|
42
|
+
design-system folder. The starter’s `tsconfig.build.json` does this; its normal
|
|
43
|
+
no-emit typecheck still checks development sources. Core import guards protect
|
|
44
|
+
the runtime dependency closure. Review custom emitter configurations through
|
|
45
|
+
the configuration owner before migrating a consuming project. Guards remain tooling. Production prop types
|
|
46
|
+
belong in runtime files, never inferred from a development contract.
|
|
47
|
+
|
|
48
|
+
## A contract uses actual implementation references
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
import type { UnitContract } from "@reqvire-org/ads/contracts";
|
|
52
|
+
import { Plot } from "./Plot";
|
|
53
|
+
import { Series } from "./Series";
|
|
54
|
+
import { Renderer } from "./renderer";
|
|
55
|
+
import { paints } from "./paints";
|
|
56
|
+
|
|
57
|
+
export default {
|
|
58
|
+
schemaVersion: 1,
|
|
59
|
+
tokens: { namespace: "--plot-", stylesheet: "./tokens.css" },
|
|
60
|
+
paintInputs: [
|
|
61
|
+
{ api: Plot, property: "config.*.paint", kind: "paint-reference" },
|
|
62
|
+
{ api: Series, property: "fill", kind: "paint-reference", bindings: ["items"] },
|
|
63
|
+
],
|
|
64
|
+
bindings: {
|
|
65
|
+
items: {
|
|
66
|
+
input: { api: Plot, property: "config" },
|
|
67
|
+
producer: paints,
|
|
68
|
+
valueFrom: "paint",
|
|
69
|
+
variable: "--plot-runtime-${key}",
|
|
70
|
+
scope: { api: Plot, selector: '[data-plot-root=""]' },
|
|
71
|
+
checks: ["isolation"],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
declarativeParts: [
|
|
75
|
+
{ api: Series, parent: Plot, consumer: Renderer, checks: ["isolation"] },
|
|
76
|
+
],
|
|
77
|
+
behavior: {
|
|
78
|
+
subjects: { plot: { api: Plot }, renderer: { implementation: Renderer } },
|
|
79
|
+
guarantees: [{
|
|
80
|
+
id: "scoped-output",
|
|
81
|
+
description: "Changing either instance updates only its own output.",
|
|
82
|
+
subjects: ["plot", "renderer"], checks: ["isolation"],
|
|
83
|
+
}],
|
|
84
|
+
checks: {
|
|
85
|
+
isolation: {
|
|
86
|
+
file: "./Plot.ads.behavior.spec.ts", case: "isolation",
|
|
87
|
+
scenario: "components/Plot", profile: "widget",
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
} satisfies UnitContract;
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Keep contracts small; most owners need none or one section. Supported data are
|
|
95
|
+
literal objects/arrays, static constants/spreads, `as const` and `satisfies`.
|
|
96
|
+
Symbol references resolve through normal imports, aliases and re-exports.
|
|
97
|
+
Calls, getters, methods, computed metadata and environment-dependent expressions
|
|
98
|
+
are rejected. Function references are opaque identities, never executed.
|
|
99
|
+
Private implementation subjects may be internal module exports without becoming
|
|
100
|
+
public barrel exports. Producers/consumers must belong to the same owner. Required
|
|
101
|
+
checks must exist, back named guarantees and use a browser profile with the
|
|
102
|
+
corresponding scenario. A relationship is registration, not a passing guarantee.
|
|
103
|
+
|
|
104
|
+
## Typed paints and runtime bindings
|
|
105
|
+
|
|
106
|
+
Production-facing types can use:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
export type Paint = `var(--${string})` | `url(#${string})` | "none" | "currentColor";
|
|
110
|
+
export interface SeriesProps { fill?: Paint }
|
|
111
|
+
export type Config = Readonly<Record<string, { paint: Paint }>>;
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
ADS follows the actual property path/type, including nested maps. A name such as
|
|
115
|
+
`Paint`, an arbitrary `string`, `any` or a forged metadata role grants no waiver.
|
|
116
|
+
Supported references are a single accessible token/binding `var(--name)`, a local
|
|
117
|
+
SVG paint `url(#id)`, `none` or `currentColor`. Fallbacks, raw colours, external
|
|
118
|
+
URLs and arbitrary CSS are outside this role. Raw domain colours need a separately
|
|
119
|
+
reviewed validated input boundary; they are not implicitly supported here.
|
|
120
|
+
|
|
121
|
+
A runtime family is instance data, not an infinite design-token inventory. Its
|
|
122
|
+
source proof requires the declared config map, exact key/value mapping and actual
|
|
123
|
+
root attachment. Supported producers use `Object.entries(config)` with a loop,
|
|
124
|
+
or `Object.fromEntries(...map(...))`; same-owner helper returns are traceable.
|
|
125
|
+
Guard each key before the write/return using
|
|
126
|
+
`if (!/^[A-Za-z0-9_-]+$/.test(key)) throw new Error("Invalid key")` (a loop may
|
|
127
|
+
`continue`). Return only the computed map. For example:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
export function paints(config: Config) {
|
|
131
|
+
const result: Record<string, string> = {};
|
|
132
|
+
for (const [key, entry] of Object.entries(config)) {
|
|
133
|
+
if (!/^[A-Za-z0-9_-]+$/.test(key)) throw new Error("Invalid key");
|
|
134
|
+
result[`--plot-runtime-${key}`] = entry.paint;
|
|
135
|
+
}
|
|
136
|
+
return result;
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
The runtime root attaches that returned map using `style={paints(config)}` or a
|
|
141
|
+
traceable local/useMemo result and the declared class/exact data attribute.
|
|
142
|
+
Place spreading props before the explicit scope/style so an unknown spread
|
|
143
|
+
cannot replace them. This grants permission to that binding attachment only;
|
|
144
|
+
it does not authorize arbitrary inline styling, CSSProperties APIs or bulk writes.
|
|
145
|
+
Unsupported dynamic producers/attachment paths fail with an analysis diagnostic.
|
|
146
|
+
Keep the implementation project-owned; there is no required ADS binding helper.
|
|
147
|
+
|
|
148
|
+
Families cannot overlap shared tokens, owner-token namespaces, private runtime
|
|
149
|
+
prefixes or other bindings. A broad family such as `--color-${key}` grants no
|
|
150
|
+
shared-token exception. Use a distinct private family where collision freedom
|
|
151
|
+
cannot be established. Registered paint consumers list their allowed binding IDs;
|
|
152
|
+
borrowing a peer's family or private token remains invalid. Bindings are scoped
|
|
153
|
+
to their actual root; portals outside it need deliberate project integration.
|
|
154
|
+
|
|
155
|
+
The App audit checks current mounted instances, accessible values, local SVG
|
|
156
|
+
resources and computed paint-property responses. Visibility and complete rendered
|
|
157
|
+
output still need assertions in the registered browser checks. It rejects missing/duplicate IDs and reports unobserved written values separately. It is bounded to 100 families, 20 instances per family, 100 keys
|
|
158
|
+
and 1000 elements per instance. Exceeding a budget reports limits and complete: false; it does not invalidate a
|
|
159
|
+
working component. An unobserved paint effect also remains unverified until its
|
|
160
|
+
relevant consumer/state is checked. It does
|
|
161
|
+
not prove every future input, visibility state or interaction. In registered
|
|
162
|
+
Playwright checks, use `auditBehaviorPaints(frame)` from
|
|
163
|
+
`@reqvire-org/ads/verification` after updates. Assert `result.ok` and, when claiming complete current-map
|
|
164
|
+
evidence, `result.complete` and the expected `verified` entries.
|
|
165
|
+
Exercise two instances with identical keys/different paints, key updates/removal,
|
|
166
|
+
themes, local SVG IDs and invalid/inaccessible references. Keep normal runner
|
|
167
|
+
profiles; React StrictMode still needs a project development harness.
|
|
168
|
+
|
|
169
|
+
## Rendered and declarative evidence
|
|
170
|
+
|
|
171
|
+
`covers` names rendered public UI. A null-returning declarative part belongs in
|
|
172
|
+
`exercises`, alongside behavioral APIs:
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
export const scenario = {
|
|
176
|
+
title: "Plot", covers: ["Plot"], exercises: ["Series"],
|
|
177
|
+
};
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The real part must reach its registered consumer through a rendered parent.
|
|
181
|
+
ADS resolves source identity comparisons (`child.type === Series`, including a
|
|
182
|
+
traceable comparison helper) and reads of that same child’s props traced from the consumer’s children input.
|
|
183
|
+
A comparison against a decoy object gives no evidence. An import, unrelated parent, unused
|
|
184
|
+
child or metadata claim gives no consumption credit. Unknown render branches are
|
|
185
|
+
conservative. Browser checks must verify that adding/removing/changing the part
|
|
186
|
+
changes output. Source relationships and current browser evidence are separate.
|
|
187
|
+
Do not add fake DOM, hide public parts or rewrite a valid API to appease tracing.
|
|
188
|
+
|
|
189
|
+
Scenario metadata is validated by field/entry with positions and reasons.
|
|
190
|
+
A bad entry still blocks acceptance while valid independent render evidence is
|
|
191
|
+
retained. Unreadable prerequisites report coverage as not established. Inspect
|
|
192
|
+
the exact source diagnostic and bounded results; do not assume every subsequent
|
|
193
|
+
missing preview means its runtime API is invalid.
|
|
194
|
+
|
|
195
|
+
## Local tokens and project consistency
|
|
196
|
+
|
|
197
|
+
`tokens.css` remains optional production design data for component/pattern owners,
|
|
198
|
+
imported by the runtime owner. Controllers stay headless; review uses the shared
|
|
199
|
+
vocabulary and its own development presentation state. Only root/theme definitions belong there. Tokens require real sample
|
|
200
|
+
coverage, including independently built owner token scenarios. One namespace
|
|
201
|
+
cannot be shared by peer components. Private references remain private through
|
|
202
|
+
TS constants, re-exports, helper returns, SVG/paint props and property reads/writes.
|
|
203
|
+
Normal composition of a public component using its own tokens remains valid.
|
|
204
|
+
Source analysis covers supported value flows; arbitrary dynamic code is not
|
|
205
|
+
proof of ownership. CSS variables remain browser-visible; namespacing is an
|
|
206
|
+
architecture boundary, not browser encapsulation.
|
|
207
|
+
|
|
208
|
+
Ownership does not establish a project's visual scale:
|
|
209
|
+
|
|
210
|
+
- A component redefining shared `--space-4` fails.
|
|
211
|
+
- Its private `--plot-gap: 7px` can be valid component geometry.
|
|
212
|
+
- `--plot-gap: var(--space-4)` preserves a shared scale with local meaning.
|
|
213
|
+
|
|
214
|
+
Choose project policy explicitly. A registered project check can use tooling-only
|
|
215
|
+
`inspectTokens(root)` and `inspectTokenOrigins(inventory, name, { stopAt: names })`
|
|
216
|
+
from `@reqvire-org/ads`. `aliasOnly` verifies every theme definition follows only
|
|
217
|
+
aliases to the explicitly selected origins; `literals` and `diagnostics` explain
|
|
218
|
+
violations/limits. Select the exact targets and permitted shared origins. Keep
|
|
219
|
+
intentional geometry exceptions exact with documented reasons. ADS neither
|
|
220
|
+
infers spacing meaning from names nor prescribes a global spacing palette.
|
|
221
|
+
|
|
222
|
+
## Migration and MCP editing
|
|
223
|
+
|
|
224
|
+
Legacy `tokens.json`/`<Owner>.tokens.json` and `behavior.json` remain readable.
|
|
225
|
+
Use either authoring form per section; competing declarations fail. New contracts
|
|
226
|
+
normalize into the same discovery, ownership, source revisions, affected-check
|
|
227
|
+
selection and candidate gates. `ads_units` exposes `contractFile`; `ads_catalog`
|
|
228
|
+
reports constrained paint paths and declarative parent/consumer/check references.
|
|
229
|
+
Use bounded file reads and paged detailed results, not repeated whole-project dumps.
|
|
230
|
+
|
|
231
|
+
Install supporting ADS tooling before migrating a consuming project and reconnect
|
|
232
|
+
MCP before replacing a package used by a running server. Then:
|
|
233
|
+
|
|
234
|
+
1. Discover one owner with `ads_units`.
|
|
235
|
+
2. `ads_prepare_patch({ unit, action: "migrate" })` creates a normal local draft.
|
|
236
|
+
3. Review the converted contract, renamed files/imports and migration notes.
|
|
237
|
+
4. `ads_prepare_patch({ unit, action: "diff", draft })` emits its Git patchFile.
|
|
238
|
+
5. Dry-run/apply with `ads_apply_patch`, or a genuinely coupled transaction.
|
|
239
|
+
|
|
240
|
+
Migration never edits live source or bypasses gates. It retains stable scenario
|
|
241
|
+
IDs and check case IDs. An optional static `scenario.id` preserves a legacy
|
|
242
|
+
identity when a renamed filename would otherwise be ambiguous; it must stay
|
|
243
|
+
inside the owning unit and be unique. Whole-file or unimportable private behavior subjects stay
|
|
244
|
+
in legacy registration with a note until deliberately resolved. Unknown runner
|
|
245
|
+
globs, custom adapters and documentation references require review. Ambiguous
|
|
246
|
+
flat-to-directory renames stay in the legacy form with a migration note so file
|
|
247
|
+
ownership cannot silently change. Preserve
|
|
248
|
+
licenses, pending drafts and unrelated work; keep transactions within their limits.
|
|
249
|
+
There is no alternate JSON contract editor. Do not loosen a guard or make a valid
|
|
250
|
+
API private merely to complete a migration.
|
|
@@ -522,3 +522,7 @@ For visual work, inspect the real project showcase, including keyboard/focus,
|
|
|
522
522
|
relevant pointer/touch behavior, dark theme, and reduced motion as needed.
|
|
523
523
|
A screenshot alone does not prove behavior or animation. Validate public imports,
|
|
524
524
|
CSS/asset resolution, required setup, and local documentation links.
|
|
525
|
+
|
|
526
|
+
For static owner contracts, `.ads.` development roles, typed paint/binding APIs,
|
|
527
|
+
declarative evidence, token privacy and guarded migration, read
|
|
528
|
+
[contracts.md](references/contracts.md) when the task touches those contracts.
|
|
@@ -56,7 +56,7 @@ Its MCP client discovers entries, invokes preview tools and follows notification
|
|
|
56
56
|
There are no required TokensPage/ComponentsPage/PatternsPage/CompositionPage
|
|
57
57
|
files, and no separate project browser app to implement or rebuild.
|
|
58
58
|
|
|
59
|
-
The existing `tokens/Foundation.scenario.tsx` and colocated fixture visually demonstrate all
|
|
59
|
+
The existing `tokens/Foundation.ads.scenario.tsx` and colocated fixture visually demonstrate all
|
|
60
60
|
starter token values. The fixture owns `{ token, property, probe }` bindings;
|
|
61
61
|
the public `TokenPreview` pattern owns their presentation and applies the real CSS
|
|
62
62
|
variables. Add/remove samples alongside token definitions. `ads check` rejects
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { TokenPreview } from "@ds/review";
|
|
2
|
-
import { fixture } from "./Foundation.fixture";
|
|
2
|
+
import { fixture } from "./Foundation.ads.fixture";
|
|
3
3
|
export const scenario = { title: fixture.title, covers: ["TokenPreview"], samples: fixture.samples };
|
|
4
4
|
export default function Scenario() { return <TokenPreview {...fixture} />; }
|
|
@@ -7,6 +7,13 @@
|
|
|
7
7
|
"rootDir": "design-system",
|
|
8
8
|
"outDir": "dist"
|
|
9
9
|
},
|
|
10
|
-
"include": [
|
|
11
|
-
|
|
10
|
+
"include": [
|
|
11
|
+
"design-system/index.ts"
|
|
12
|
+
],
|
|
13
|
+
"exclude": [
|
|
14
|
+
"design-system/showcase",
|
|
15
|
+
"design-system/review",
|
|
16
|
+
"design-system/composition",
|
|
17
|
+
"**/*.ads.*"
|
|
18
|
+
]
|
|
12
19
|
}
|