@contentful/experience-design-system-cli 2.28.1-dev-build-090b2d1.0 → 2.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.29.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"lint:fix": "nx lint:fix experience-design-system-cli"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
+
"@contentful/experience-design-system-cli-v2": "workspace:*",
|
|
41
42
|
"@contentful/experience-design-system-types": "workspace:*",
|
|
42
43
|
"@segment/analytics-node": "^3.0.0",
|
|
43
44
|
"@vue/compiler-sfc": "^3.5.31",
|
package/dist/src/index.js
CHANGED
|
@@ -46,7 +46,6 @@ function findJsonObjectEnd(line) {
|
|
|
46
46
|
function readToolCallObjects(stdout) {
|
|
47
47
|
const objects = [];
|
|
48
48
|
const warnings = [];
|
|
49
|
-
let hadDroppedLine = false;
|
|
50
49
|
for (const raw of stdout.split("\n")) {
|
|
51
50
|
const line = raw.trim();
|
|
52
51
|
if (!line.startsWith("{"))
|
|
@@ -54,7 +53,6 @@ function readToolCallObjects(stdout) {
|
|
|
54
53
|
const end = findJsonObjectEnd(line);
|
|
55
54
|
if (end === -1) {
|
|
56
55
|
warnings.push(`unparseable line: ${line.slice(0, 120)}`);
|
|
57
|
-
hadDroppedLine = true;
|
|
58
56
|
continue;
|
|
59
57
|
}
|
|
60
58
|
let parsed;
|
|
@@ -62,7 +60,6 @@ function readToolCallObjects(stdout) {
|
|
|
62
60
|
parsed = JSON.parse(line.slice(0, end + 1));
|
|
63
61
|
} catch {
|
|
64
62
|
warnings.push(`unparseable line: ${line.slice(0, 120)}`);
|
|
65
|
-
hadDroppedLine = true;
|
|
66
63
|
continue;
|
|
67
64
|
}
|
|
68
65
|
const trailing = line.slice(end + 1);
|
|
@@ -71,11 +68,9 @@ function readToolCallObjects(stdout) {
|
|
|
71
68
|
}
|
|
72
69
|
if (typeof parsed === "object" && parsed !== null && "tool" in parsed) {
|
|
73
70
|
objects.push(parsed);
|
|
74
|
-
} else {
|
|
75
|
-
hadDroppedLine = true;
|
|
76
71
|
}
|
|
77
72
|
}
|
|
78
|
-
return { objects, warnings
|
|
73
|
+
return { objects, warnings };
|
|
79
74
|
}
|
|
80
75
|
function parseSelectToolCallLines(stdout) {
|
|
81
76
|
const calls = [];
|
|
@@ -106,7 +101,7 @@ function parseSelectToolCallLines(stdout) {
|
|
|
106
101
|
}
|
|
107
102
|
function parseToolCallLines(stdout) {
|
|
108
103
|
const calls = [];
|
|
109
|
-
const { objects, warnings
|
|
104
|
+
const { objects, warnings } = readToolCallObjects(stdout);
|
|
110
105
|
for (const rec of objects) {
|
|
111
106
|
if (!VALID_TOOL_NAMES.has(rec.tool)) {
|
|
112
107
|
warnings.push(`unknown tool: ${String(rec.tool)}`);
|
|
@@ -191,7 +186,7 @@ function parseToolCallLines(stdout) {
|
|
|
191
186
|
calls.push(call);
|
|
192
187
|
}
|
|
193
188
|
}
|
|
194
|
-
return { calls, warnings
|
|
189
|
+
return { calls, warnings };
|
|
195
190
|
}
|
|
196
191
|
function parseTokenToolCallLines(stdout) {
|
|
197
192
|
const calls = [];
|
|
@@ -9579,26 +9574,6 @@ function loadRawComponents(db, sessionId2, allowedNames) {
|
|
|
9579
9574
|
})
|
|
9580
9575
|
);
|
|
9581
9576
|
}
|
|
9582
|
-
function rewrittenSourcePaths(basePath) {
|
|
9583
|
-
for (const [emitted, sources] of TS_ESM_EXTENSION_REWRITES) {
|
|
9584
|
-
if (!basePath.endsWith(emitted)) continue;
|
|
9585
|
-
const stem = basePath.slice(0, -emitted.length);
|
|
9586
|
-
return sources.map((ext) => `${stem}${ext}`);
|
|
9587
|
-
}
|
|
9588
|
-
return [];
|
|
9589
|
-
}
|
|
9590
|
-
function identifiersIn(propTypes) {
|
|
9591
|
-
const names = /* @__PURE__ */ new Set();
|
|
9592
|
-
for (const propType of propTypes) {
|
|
9593
|
-
for (const match2 of propType.matchAll(IDENTIFIER_PATTERN)) names.add(match2[0]);
|
|
9594
|
-
}
|
|
9595
|
-
return [...names];
|
|
9596
|
-
}
|
|
9597
|
-
function declaresAnyType(text, typeNames) {
|
|
9598
|
-
return typeNames.some(
|
|
9599
|
-
(name) => new RegExp(`\\b(?:type|interface|enum|const|class)\\s+${escapeForRegExp(name)}\\b`).test(text)
|
|
9600
|
-
);
|
|
9601
|
-
}
|
|
9602
9577
|
function extractRelativeImportPaths(sourceText) {
|
|
9603
9578
|
const specifiers = /* @__PURE__ */ new Set();
|
|
9604
9579
|
for (const match2 of sourceText.matchAll(RELATIVE_IMPORT_PATTERN)) {
|
|
@@ -9610,7 +9585,6 @@ async function resolveRelativeImport(specifier, fromDir) {
|
|
|
9610
9585
|
const basePath = resolve7(fromDir, specifier);
|
|
9611
9586
|
const candidates = [
|
|
9612
9587
|
basePath,
|
|
9613
|
-
...rewrittenSourcePaths(basePath),
|
|
9614
9588
|
...SIBLING_FILE_EXTENSIONS.map((ext) => `${basePath}${ext}`),
|
|
9615
9589
|
...SIBLING_FILE_EXTENSIONS.map((ext) => resolve7(basePath, `index${ext}`))
|
|
9616
9590
|
];
|
|
@@ -9631,7 +9605,7 @@ async function resolveImportSpecifiers(sourceText, fromDir) {
|
|
|
9631
9605
|
}
|
|
9632
9606
|
return resolvedPaths;
|
|
9633
9607
|
}
|
|
9634
|
-
async function loadSiblingFiles(sourceText, sourcePath, propNames
|
|
9608
|
+
async function loadSiblingFiles(sourceText, sourcePath, propNames) {
|
|
9635
9609
|
const visited = /* @__PURE__ */ new Set([resolve7(sourcePath)]);
|
|
9636
9610
|
const discovered = [];
|
|
9637
9611
|
let frontier = [{ text: sourceText, dir: dirname7(sourcePath) }];
|
|
@@ -9656,16 +9630,9 @@ async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateType
|
|
|
9656
9630
|
}
|
|
9657
9631
|
frontier = nextFrontier;
|
|
9658
9632
|
}
|
|
9659
|
-
const inHand = [sourceText, ...discovered.map((d) => d.content)];
|
|
9660
|
-
const declaredTypeNames = candidateTypeNames.filter((name) => inHand.some((text) => declaresAnyType(text, [name])));
|
|
9661
9633
|
const usesNotShown = /* @__PURE__ */ new Set();
|
|
9662
|
-
const searchNames = [.../* @__PURE__ */ new Set([...propNames, ...declaredTypeNames])];
|
|
9663
|
-
let enlarged = 0;
|
|
9664
9634
|
const siblings = discovered.slice(0, MAX_SIBLING_FILES).map((d) => {
|
|
9665
|
-
const
|
|
9666
|
-
if (enlarge) enlarged++;
|
|
9667
|
-
const budget = enlarge ? MAX_TYPE_DECLARING_SIBLING_CHARS : MAX_SIBLING_SNIPPET_CHARS;
|
|
9668
|
-
const excerpt = excerptAroundNames(d.content, searchNames, budget);
|
|
9635
|
+
const excerpt = excerptAroundNames(d.content, propNames, MAX_SIBLING_SNIPPET_CHARS);
|
|
9669
9636
|
for (const name of excerpt.usesNotShown) usesNotShown.add(name);
|
|
9670
9637
|
return { path: d.path, content: excerpt.content };
|
|
9671
9638
|
});
|
|
@@ -9673,32 +9640,25 @@ async function loadSiblingFiles(sourceText, sourcePath, propNames, candidateType
|
|
|
9673
9640
|
return {
|
|
9674
9641
|
siblings,
|
|
9675
9642
|
truncatedCount,
|
|
9676
|
-
usesNotShown: propNames.filter((name) => usesNotShown.has(name))
|
|
9677
|
-
declaredTypeNames
|
|
9643
|
+
usesNotShown: propNames.filter((name) => usesNotShown.has(name))
|
|
9678
9644
|
};
|
|
9679
9645
|
}
|
|
9680
|
-
async function loadComponentSourceRef(name, sourcePath, propNames = []
|
|
9646
|
+
async function loadComponentSourceRef(name, sourcePath, propNames = []) {
|
|
9681
9647
|
let content = null;
|
|
9682
9648
|
let siblingFiles = [];
|
|
9683
9649
|
let truncatedSiblingCount = 0;
|
|
9684
9650
|
const usesNotShown = /* @__PURE__ */ new Set();
|
|
9685
9651
|
try {
|
|
9686
9652
|
const rawText = await readFile7(sourcePath, "utf8");
|
|
9653
|
+
const mainExcerpt = excerptAroundNames(rawText, propNames, MAX_COMPONENT_SOURCE_CHARS);
|
|
9654
|
+
content = mainExcerpt.content;
|
|
9655
|
+
for (const name2 of mainExcerpt.usesNotShown) usesNotShown.add(name2);
|
|
9687
9656
|
let siblingUsesNotShown;
|
|
9688
|
-
let declaredTypeNames;
|
|
9689
9657
|
({
|
|
9690
9658
|
siblings: siblingFiles,
|
|
9691
9659
|
truncatedCount: truncatedSiblingCount,
|
|
9692
|
-
usesNotShown: siblingUsesNotShown
|
|
9693
|
-
|
|
9694
|
-
} = await loadSiblingFiles(rawText, sourcePath, propNames, identifiersIn(propTypes)));
|
|
9695
|
-
const mainExcerpt = excerptAroundNames(
|
|
9696
|
-
rawText,
|
|
9697
|
-
[.../* @__PURE__ */ new Set([...propNames, ...declaredTypeNames])],
|
|
9698
|
-
MAX_COMPONENT_SOURCE_CHARS
|
|
9699
|
-
);
|
|
9700
|
-
content = mainExcerpt.content;
|
|
9701
|
-
for (const name2 of mainExcerpt.usesNotShown) usesNotShown.add(name2);
|
|
9660
|
+
usesNotShown: siblingUsesNotShown
|
|
9661
|
+
} = await loadSiblingFiles(rawText, sourcePath, propNames));
|
|
9702
9662
|
for (const name2 of siblingUsesNotShown) usesNotShown.add(name2);
|
|
9703
9663
|
} catch {
|
|
9704
9664
|
}
|
|
@@ -9713,18 +9673,13 @@ async function loadComponentSourceRefs(db, sessionId2) {
|
|
|
9713
9673
|
const rows = db.prepare(
|
|
9714
9674
|
`SELECT component_id, name, source, source_path FROM raw_components WHERE session_id = ? AND status = 'generated' ORDER BY rowid`
|
|
9715
9675
|
).all(sessionId2);
|
|
9716
|
-
const
|
|
9717
|
-
`SELECT name
|
|
9676
|
+
const propNamesFor = db.prepare(
|
|
9677
|
+
`SELECT name FROM raw_props WHERE session_id = ? AND component_id = ? ORDER BY position`
|
|
9718
9678
|
);
|
|
9719
9679
|
return Promise.all(
|
|
9720
9680
|
rows.map((r) => {
|
|
9721
|
-
const
|
|
9722
|
-
return loadComponentSourceRef(
|
|
9723
|
-
r.name,
|
|
9724
|
-
r.source_path ?? r.source,
|
|
9725
|
-
props.map((p) => p.name),
|
|
9726
|
-
props.map((p) => p.type)
|
|
9727
|
-
);
|
|
9681
|
+
const propNames = propNamesFor.all(sessionId2, r.component_id).map((p) => p.name);
|
|
9682
|
+
return loadComponentSourceRef(r.name, r.source_path ?? r.source, propNames);
|
|
9728
9683
|
})
|
|
9729
9684
|
);
|
|
9730
9685
|
}
|
|
@@ -10607,7 +10562,7 @@ function lookupSelectCache(db, componentHash, promptHash, cliVersion2) {
|
|
|
10607
10562
|
updatedAt: row.updated_at
|
|
10608
10563
|
};
|
|
10609
10564
|
}
|
|
10610
|
-
var SCHEMA, MAX_COMPONENT_SOURCE_CHARS, MAX_SIBLING_FILES, MAX_SIBLING_SNIPPET_CHARS,
|
|
10565
|
+
var SCHEMA, MAX_COMPONENT_SOURCE_CHARS, MAX_SIBLING_FILES, MAX_SIBLING_SNIPPET_CHARS, MAX_SIBLING_DEPTH, MAX_SIBLING_CANDIDATES_EXPLORED, RELATIVE_IMPORT_PATTERN, SIBLING_FILE_EXTENSIONS, _cliCacheVersionCache;
|
|
10611
10566
|
var init_db = __esm({
|
|
10612
10567
|
"packages/experience-design-system-cli/src/session/db.ts"() {
|
|
10613
10568
|
"use strict";
|
|
@@ -10793,19 +10748,10 @@ CREATE INDEX IF NOT EXISTS idx_scanned_files_session ON scanned_files(session
|
|
|
10793
10748
|
MAX_COMPONENT_SOURCE_CHARS = 8e3;
|
|
10794
10749
|
MAX_SIBLING_FILES = 5;
|
|
10795
10750
|
MAX_SIBLING_SNIPPET_CHARS = 1200;
|
|
10796
|
-
MAX_TYPE_DECLARING_SIBLING_CHARS = 4e3;
|
|
10797
|
-
MAX_ENLARGED_SIBLINGS = 2;
|
|
10798
10751
|
MAX_SIBLING_DEPTH = 2;
|
|
10799
10752
|
MAX_SIBLING_CANDIDATES_EXPLORED = 25;
|
|
10800
10753
|
RELATIVE_IMPORT_PATTERN = /from\s+['"](\.[^'"]+)['"]/g;
|
|
10801
10754
|
SIBLING_FILE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx"];
|
|
10802
|
-
TS_ESM_EXTENSION_REWRITES = [
|
|
10803
|
-
[".js", [".ts", ".tsx", ".js", ".jsx"]],
|
|
10804
|
-
[".jsx", [".tsx", ".jsx"]],
|
|
10805
|
-
[".mjs", [".mts", ".mjs"]],
|
|
10806
|
-
[".cjs", [".cts", ".cjs"]]
|
|
10807
|
-
];
|
|
10808
|
-
IDENTIFIER_PATTERN = /[A-Za-z_$][A-Za-z0-9_$]*/g;
|
|
10809
10755
|
_cliCacheVersionCache = null;
|
|
10810
10756
|
}
|
|
10811
10757
|
});
|
|
@@ -32522,8 +32468,7 @@ async function runOneComponent(agent, model, db, sessionId2, component, tokensIn
|
|
|
32522
32468
|
const sourceRef = await loadComponentSourceRef(
|
|
32523
32469
|
component.name,
|
|
32524
32470
|
component.sourcePath ?? component.source,
|
|
32525
|
-
component.props.map((p) => p.name)
|
|
32526
|
-
component.props.map((p) => p.type)
|
|
32471
|
+
component.props.map((p) => p.name)
|
|
32527
32472
|
);
|
|
32528
32473
|
const prompt2 = await buildPrompt({
|
|
32529
32474
|
skill: "components",
|
|
@@ -32571,16 +32516,11 @@ ${outputBuf}`);
|
|
|
32571
32516
|
lastError = describeAgentFailure(result);
|
|
32572
32517
|
continue;
|
|
32573
32518
|
}
|
|
32574
|
-
const { calls, warnings
|
|
32519
|
+
const { calls, warnings } = parseToolCallLines(result.stdout);
|
|
32575
32520
|
if (calls.length === 0) {
|
|
32576
32521
|
lastError = describeAgentFailure(result);
|
|
32577
32522
|
continue;
|
|
32578
32523
|
}
|
|
32579
|
-
if (hadDroppedLine) {
|
|
32580
|
-
lastError = `agent output dropped at least one tool-call line: ${warnings.join("; ").slice(0, 200)}`;
|
|
32581
|
-
if (attempt < maxAttempts) continue;
|
|
32582
|
-
break;
|
|
32583
|
-
}
|
|
32584
32524
|
const applied = applyToolCalls(db, sessionId2, component.component_id, component.name, calls, warnings);
|
|
32585
32525
|
if (!noCache) {
|
|
32586
32526
|
const inputHash = computeComponentInputHash(component);
|
|
@@ -32769,8 +32709,7 @@ async function runGenerateSkill(skill, opts, verbose = false) {
|
|
|
32769
32709
|
const sampleSourceRef = sampleComponent ? await loadComponentSourceRef(
|
|
32770
32710
|
sampleComponent.name,
|
|
32771
32711
|
sampleComponent.sourcePath ?? sampleComponent.source,
|
|
32772
|
-
sampleComponent.props.map((p) => p.name)
|
|
32773
|
-
sampleComponent.props.map((p) => p.type)
|
|
32712
|
+
sampleComponent.props.map((p) => p.name)
|
|
32774
32713
|
) : void 0;
|
|
32775
32714
|
const prompt2 = await buildPrompt({
|
|
32776
32715
|
skill,
|
|
@@ -32846,11 +32785,6 @@ async function runGenerateSkill(skill, opts, verbose = false) {
|
|
|
32846
32785
|
`Error: all ${componentResults.length} component(s) failed to generate \u2014 see the per-component errors above.`
|
|
32847
32786
|
);
|
|
32848
32787
|
}
|
|
32849
|
-
if (failed.length > 0) {
|
|
32850
|
-
die2(
|
|
32851
|
-
`Error: ${failed.length}/${componentResults.length} component(s) failed to generate \u2014 see the per-component errors above.`
|
|
32852
|
-
);
|
|
32853
|
-
}
|
|
32854
32788
|
} else if (skill === "tokens") {
|
|
32855
32789
|
const noCache = opts.cache === false || process.env.EDS_NO_CACHE === "1";
|
|
32856
32790
|
const tokenInputContent = rawTokensInline ?? "";
|
|
@@ -33478,7 +33412,7 @@ import { basename as basename5, resolve as resolve19 } from "node:path";
|
|
|
33478
33412
|
|
|
33479
33413
|
// packages/experience-design-system-cli/src/print/validate/validators/cdf-validator.ts
|
|
33480
33414
|
import { readFile as readFile19 } from "node:fs/promises";
|
|
33481
|
-
import { validateCDF as validateCDF2
|
|
33415
|
+
import { validateCDF as validateCDF2 } from "@contentful/experience-design-system-types";
|
|
33482
33416
|
function extractValue(input, path) {
|
|
33483
33417
|
if (path === "/") return void 0;
|
|
33484
33418
|
const parts = path.split("/").filter(Boolean);
|
|
@@ -33562,14 +33496,6 @@ async function validateCDFFile(filePath) {
|
|
|
33562
33496
|
}).map((e) => rewriteDiagnostic(e, parsed));
|
|
33563
33497
|
return { valid: false, summary: "", diagnostics };
|
|
33564
33498
|
}
|
|
33565
|
-
const invariantErrors = checkCDFComponentInvariants(cdfResult.components);
|
|
33566
|
-
if (invariantErrors.length > 0) {
|
|
33567
|
-
return {
|
|
33568
|
-
valid: false,
|
|
33569
|
-
summary: "",
|
|
33570
|
-
diagnostics: invariantErrors.map((e) => rewriteDiagnostic(e, parsed))
|
|
33571
|
-
};
|
|
33572
|
-
}
|
|
33573
33499
|
const count = cdfResult.components.length;
|
|
33574
33500
|
return {
|
|
33575
33501
|
valid: true,
|
|
@@ -36898,6 +36824,15 @@ async function runBuild(opts) {
|
|
|
36898
36824
|
child.on("exit", (code) => settle(code ?? 1));
|
|
36899
36825
|
});
|
|
36900
36826
|
}
|
|
36827
|
+
function registerImportV2Command(program) {
|
|
36828
|
+
program.command("importv2").description("Launch the v2 import TUI (experience-design-system-cli-v2)").action(async () => {
|
|
36829
|
+
const { render: render7 } = await import("ink");
|
|
36830
|
+
const { createElement: createElement7 } = await import("react");
|
|
36831
|
+
const { App: App2 } = await import("@contentful/experience-design-system-cli-v2/app");
|
|
36832
|
+
const { waitUntilExit } = render7(createElement7(App2));
|
|
36833
|
+
await waitUntilExit();
|
|
36834
|
+
});
|
|
36835
|
+
}
|
|
36901
36836
|
function registerBuildCommand(program) {
|
|
36902
36837
|
program.command("build").description("Rebuild from source and re-link exo/experiences binaries to this build").action(async () => {
|
|
36903
36838
|
const pkgRoot = resolve26(dirname14(fileURLToPath5(import.meta.url)), "..", "..");
|
|
@@ -36918,6 +36853,7 @@ function createProgram() {
|
|
|
36918
36853
|
registerApplyCommand(program);
|
|
36919
36854
|
registerSessionCommand(program);
|
|
36920
36855
|
registerImportCommand(program);
|
|
36856
|
+
registerImportV2Command(program);
|
|
36921
36857
|
registerSetupCommand(program);
|
|
36922
36858
|
registerRunsCommand(program);
|
|
36923
36859
|
registerBuildCommand(program);
|
package/dist/src/session/db.d.ts
CHANGED
|
@@ -107,7 +107,7 @@ export type RawComponentWithId = RawComponentDefinition & {
|
|
|
107
107
|
component_id: string;
|
|
108
108
|
};
|
|
109
109
|
export declare function loadRawComponents(db: DatabaseSync, sessionId: string, allowedNames?: Set<string>): RawComponentWithId[];
|
|
110
|
-
export declare function loadComponentSourceRef(name: string, sourcePath: string, propNames?: string[]
|
|
110
|
+
export declare function loadComponentSourceRef(name: string, sourcePath: string, propNames?: string[]): Promise<ComponentSourceRef>;
|
|
111
111
|
export declare function loadComponentSourceRefs(db: DatabaseSync, sessionId: string): Promise<ComponentSourceRef[]>;
|
|
112
112
|
export declare function renameEmptySlots(db: DatabaseSync, sessionId: string, componentId: string, componentName: string, slotCount: number): {
|
|
113
113
|
renames: Array<{
|
|
@@ -16,7 +16,6 @@ export interface SourceExcerpt {
|
|
|
16
16
|
*/
|
|
17
17
|
usesNotShown: string[];
|
|
18
18
|
}
|
|
19
|
-
export declare function escapeForRegExp(value: string): string;
|
|
20
19
|
/**
|
|
21
20
|
* Returns `text` whole when it fits `maxChars`. Otherwise returns windows of
|
|
22
21
|
* `contextLines` lines around every line that mentions one of `names`, merged
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@contentful/experience-design-system-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.29.0",
|
|
4
4
|
"description": "Contentful Experiences design system import CLI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
"svelte": "^5.56.4",
|
|
40
40
|
"ts-morph": "^27.0.2",
|
|
41
41
|
"typescript": "^5.9.3",
|
|
42
|
-
"@contentful/experience-design-system-
|
|
42
|
+
"@contentful/experience-design-system-cli-v2": "2.29.0",
|
|
43
|
+
"@contentful/experience-design-system-types": "2.29.0"
|
|
43
44
|
},
|
|
44
45
|
"devDependencies": {
|
|
45
46
|
"@tsconfig/node24": "^24.0.4",
|
|
@@ -51,9 +52,9 @@
|
|
|
51
52
|
"ink-testing-library": "^4.0.0",
|
|
52
53
|
"typescript-eslint": "^8.67.0",
|
|
53
54
|
"vitest": "^4.0.16",
|
|
54
|
-
"@contentful/experience-design-system-
|
|
55
|
-
"@contentful/experience-design-system-
|
|
56
|
-
"@contentful/experience-design-system-generation": "2.
|
|
55
|
+
"@contentful/experience-design-system-client": "2.29.0",
|
|
56
|
+
"@contentful/experience-design-system-extraction": "2.29.0",
|
|
57
|
+
"@contentful/experience-design-system-generation": "2.29.0"
|
|
57
58
|
},
|
|
58
59
|
"repository": {
|
|
59
60
|
"type": "git",
|
|
@@ -91,7 +91,7 @@ Emit one JSON object per line. The CLI parses lines starting with `{`. Lines not
|
|
|
91
91
|
- Every prop in the input must produce exactly one call: `classify_prop` OR `exclude_prop`.
|
|
92
92
|
- Every slot must produce exactly one `classify_slot` call.
|
|
93
93
|
- Emit `classify_component` once at the start (required). The `description` field is **required** — always provide a brief description of the component's purpose.
|
|
94
|
-
- `values` is required for `cdf_type: "enum"` — must be a non-empty string array of the variant names the prop accepts.
|
|
94
|
+
- `values` is required for `cdf_type: "enum"` — must be a non-empty string array of the variant names the prop accepts.
|
|
95
95
|
- **Do NOT include `values` for `cdf_type: "token"`.** The two property types carry different kinds of list. An `enum` prop's list holds *variant names* the component accepts (`"primary"`, `"secondary"`). A `token` prop's list holds *design token paths* (`"color.brand.primary"`), produced by the separate token-mapping step (`$token.allowed`) — never by you. Emitting `values` on a token prop makes the definition invalid.
|
|
96
96
|
- `token_kind` is required for `cdf_type: "token"` — must be a DTCG `$type` string, e.g. `"color"`.
|
|
97
97
|
- `required` must be a JSON boolean (`true`/`false`), not a string.
|
|
@@ -208,14 +208,12 @@ the default "could be overridden" (every default can), or because the tie-break
|
|
|
208
208
|
below exists. Nothing downstream re-derives, corrects, or second-guesses this —
|
|
209
209
|
what you emit is what ships.
|
|
210
210
|
|
|
211
|
-
**Ambiguity resolves to `enum` — but only when Q1–Q3 cannot be answered
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
"string"` per "no legal `enum` with a guessed list" — either way, record the
|
|
218
|
-
ambiguity in `reason`. When the three
|
|
211
|
+
**Ambiguity resolves to `enum` — but only when Q1–Q3 cannot be answered.** The
|
|
212
|
+
tie-break applies solely when the source shown does not let you answer the three
|
|
213
|
+
questions: the property is interpolated in one place and looked up in another,
|
|
214
|
+
the use sits in a file you cannot see, or the source is truncated at the point
|
|
215
|
+
of use (the "uses not shown" line). In those cases emit `enum` (or `string` when
|
|
216
|
+
it has no value set) and record the ambiguity in `reason`. When the three
|
|
219
217
|
questions *can* be answered from the source shown, they are answered and the
|
|
220
218
|
tie-break does not apply: a clear Q1-yes is `enum`, a clear Q2-yes + Q3-yes is
|
|
221
219
|
`token`, and a type annotation does not create ambiguity. An `enum` is delivered
|
|
@@ -299,7 +297,7 @@ A prop with a complex TypeScript type is **not automatically excluded**. Many pr
|
|
|
299
297
|
| Raw type pattern | How to resolve |
|
|
300
298
|
|---|---|
|
|
301
299
|
| `'primary' \| 'secondary' \| 'ghost'` (union of literals) | → `enum`, extract `values` |
|
|
302
|
-
| `HeadingSize` / `ButtonVariant` / any named type that is clearly a finite set of visual options | → `enum
|
|
300
|
+
| `HeadingSize` / `ButtonVariant` / any named type that is clearly a finite set of visual options | → `enum`. **Never invent a value list.** Use only values you can cite from the source shown (the type's own declaration, a lookup table, a default). If the source shown does not enumerate the values, this is the "cannot be answered" case — apply "Ambiguity resolves to `enum`": emit `enum` (or `string` if it has no discoverable value set) and record the missing evidence in `description` (not `reason` — `description` ships to the CDF, `reason` does not), e.g. `"WARNING: values not visible in source — enum values unconfirmed"`. Do not default to a generic guess like `['sm', 'md', 'lg']` or `['primary', 'secondary']` — those are one design system's vocabulary, not a fallback for missing evidence. |
|
|
303
301
|
| `Variant` / `variant` prop | Usually a visual design variant. → `enum`, `cdf_category: "design"`. Same evidence rule as above — cite values from the source shown; do not invent them. |
|
|
304
302
|
| `Section[]` / array of custom items where the structure is unclear | → `exclude_prop` only if the array elements are complex objects with no obvious flat representation. If items are simple (title, label, id), consider representing as `string` (a comma-separated IDs or keys) or note in `description` why. |
|
|
305
303
|
| `ExperienceConfiguration<Variant>` / deep generic | Personalization config — → `exclude_prop`, reason: `"personalization configuration — framework internal"` |
|
|
@@ -366,7 +364,7 @@ as enum so the author picks "primary" and the component receives "primary".
|
|
|
366
364
|
|
|
367
365
|
---
|
|
368
366
|
|
|
369
|
-
## Category
|
|
367
|
+
## Category correction rules
|
|
370
368
|
|
|
371
369
|
The pre-classified `category` is wrong in predictable ways. Correct silently (document in `description`):
|
|
372
370
|
|
|
@@ -377,11 +375,6 @@ The pre-classified `category` is wrong in predictable ways. Correct silently (do
|
|
|
377
375
|
|
|
378
376
|
> **Key question for category**: "Who fills this in?" — A content editor fills in `content`. A designer configures `design`. Neither fills in `state` — it comes from routing, runtime behavior, or component infrastructure.
|
|
379
377
|
|
|
380
|
-
**`required` is also a hint, and it is also wrong.** Verify it against the declaration:
|
|
381
|
-
- A prop with a default value — a `$default`, a destructure default, or a `defaultVariants` entry — is **never** `required: true`. The two together are incoherent.
|
|
382
|
-
- A prop declared `foo?:` is `required: false`, whatever the hint says.
|
|
383
|
-
- Props reached through `VariantProps<typeof x>` or a Radix/`ComponentPropsWithoutRef` base are optional unless the component's own interface redeclares them as required.
|
|
384
|
-
|
|
385
378
|
---
|
|
386
379
|
|
|
387
380
|
## Slot classification
|
|
@@ -469,30 +462,14 @@ footer is supplementary and optional
|
|
|
469
462
|
{"tool":"classify_slot","slot":"footer","required":false,"description":"Optional footer area for actions or metadata","rationale":"Footer is a supplementary region typically used for actions or metadata. Optional because most cards do not need one and the card renders correctly without it."}
|
|
470
463
|
```
|
|
471
464
|
|
|
472
|
-
### Named type (HeadingSize, ButtonVariant, etc.)
|
|
473
|
-
|
|
474
|
-
When a prop has a named TypeScript type that is not inlined as a union literal,
|
|
475
|
-
the value set is whatever that type's declaration says. A type *name* is not
|
|
476
|
-
evidence of its members: `HeadingSize` could be `h1`–`h6`, `sm|md|lg`, or
|
|
477
|
-
`s|m|l|xl`. Guessing picks one design system's vocabulary and is wrong more
|
|
478
|
-
often than it is right, and a wrong name matches no style rule — the author's
|
|
479
|
-
setting silently does nothing.
|
|
480
|
-
|
|
481
|
-
So: look for the declaration in the source shown. If it is there, copy it. If it
|
|
482
|
-
is not, **emit `string`** — `string` is an unrestricted enum, so the author stays
|
|
483
|
-
unrestricted but correct.
|
|
465
|
+
### Named type (HeadingSize, ButtonVariant, etc.)
|
|
484
466
|
|
|
485
|
-
|
|
486
|
-
```
|
|
487
|
-
titleSize has type HeadingSize; HeadingSize is declared in the excerpt as 'sm' | 'md' | 'lg'
|
|
488
|
-
{"tool":"classify_prop","prop":"titleSize","cdf_type":"enum","cdf_category":"design","required":false,"values":["sm","md","lg"],"description":"Heading size","reason":"Copied from the HeadingSize declaration shown in the excerpt."}
|
|
489
|
-
```
|
|
467
|
+
When a prop has a named TypeScript type that is not inlined as a union literal, reason from the prop name and type name to infer the finite value set.
|
|
490
468
|
|
|
491
|
-
Members NOT visible in the source shown — degrade to `string`:
|
|
492
469
|
```
|
|
493
|
-
titleSize has type HeadingSize
|
|
494
|
-
|
|
495
|
-
{"tool":"classify_prop","prop":"titleSize","cdf_type":"
|
|
470
|
+
titleSize has type HeadingSize — this is a named enum controlling heading size
|
|
471
|
+
inferring likely values: ["h1", "h2", "h3", "h4", "h5", "h6"] — documenting inference
|
|
472
|
+
{"tool":"classify_prop","prop":"titleSize","cdf_type":"enum","cdf_category":"design","required":false,"values":["h1","h2","h3","h4","h5","h6"],"description":"Heading level — inferred from HeadingSize type name; actual values may be h1–h6 or sm/md/lg"}
|
|
496
473
|
```
|
|
497
474
|
|
|
498
475
|
### href prop
|
|
@@ -518,7 +495,7 @@ Before emitting any tool calls, verify:
|
|
|
518
495
|
1. Every prop in the input has exactly one `classify_prop` or `exclude_prop` call
|
|
519
496
|
2. Every slot has exactly one `classify_slot` call
|
|
520
497
|
3. `classify_component` is emitted exactly once
|
|
521
|
-
4. Every `cdf_type: "enum"` has a non-empty `values` array
|
|
498
|
+
4. Every `cdf_type: "enum"` has a non-empty `values` array
|
|
522
499
|
5. Every `cdf_type: "token"` has `token_kind` (or a warning in `description` if lookup failed) and does **not** have `values` set
|
|
523
500
|
6. No `cdf_type: "link"` — all href/url props use `string`
|
|
524
501
|
7. `required` values are JSON booleans, not strings
|
|
@@ -530,8 +507,7 @@ Before emitting any tool calls, verify:
|
|
|
530
507
|
13. `rationale.description` follows the same "Description content rules" as `description` — no internal initiative names (`INTEG-*`, `EDSI`, `DSI`, `M1`, `M2`, wave/phase references, etc.).
|
|
531
508
|
14. Every `cdf_type: "token"` prop cites a line that interpolates its value into a style **and** a token reference at that use (Q2 yes, Q3 yes), and carries no `values`. If you cannot cite both, it is `enum` or `string`; if you can, it is `token` — its type annotation is not a reason to emit `string`.
|
|
532
509
|
15. Every prop whose values are friendly names is `enum` with a non-empty `values` array, even when each name resolves to a design token internally; every prop for which Q1–Q3 could not be answered was emitted as `enum` (or `string`) with the ambiguity stated in `reason`, and no answerable prop was called ambiguous.
|
|
533
|
-
16. Every `enum`'s `values` array is copied from evidence in the
|
|
534
|
-
17. No prop has `required: true` alongside a default value (`$default`, a destructure default, or a `defaultVariants` entry) — the two are incoherent. A prop declared `foo?:`, or reached through `VariantProps<typeof x>` or a Radix/`ComponentPropsWithoutRef` base without its own required redeclaration, is `required: false`.
|
|
510
|
+
16. Every `enum`'s `values` array is copied from evidence in the source shown (a type declaration, lookup table, or default) — none of it was invented. Where no evidence exists for the values, the `description` states so (e.g. `"WARNING: values not visible in source"`), not just `reason`.
|
|
535
511
|
|
|
536
512
|
After the run completes, the developer can validate the pipeline output with:
|
|
537
513
|
|
|
@@ -552,6 +528,4 @@ properties — this is never acceptable.
|
|
|
552
528
|
If you are genuinely uncertain about every prop, classify each as:
|
|
553
529
|
{"tool":"classify_prop","prop":"<name>","cdf_type":"string","cdf_category":"content","required":false,"description":"Uncertain classification — review recommended"}
|
|
554
530
|
|
|
555
|
-
An imperfect classification is infinitely better than no classification.
|
|
556
|
-
about **classifying a prop rather than dropping it** — it is not a licence to
|
|
557
|
-
invent a `values` list. The uncertain answer is `cdf_type: "string"`, every time.
|
|
531
|
+
An imperfect classification is infinitely better than no classification.
|