@ikas/code-components-mcp 1.4.0-beta.2 → 1.4.0-beta.4
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/data/framework.json +4 -4
- package/data/section-templates/account-info-section/children/AccountFavorites/ikas-config-snippet.json +3 -3
- package/data/section-templates/account-info-section/ikas-config-snippet.json +5 -5
- package/data/section-templates/category-images-section/ikas-config-snippet.json +1 -1
- package/data/section-templates/category-list-section/ikas-config-snippet.json +3 -3
- package/data/section-templates/component-renderer/ikas-config-snippet.json +3 -3
- package/data/section-templates/features-section/ikas-config-snippet.json +1 -1
- package/data/section-templates/footer-section/ikas-config-snippet.json +1 -1
- package/data/section-templates/header-section/children/Announcements/ikas-config-snippet.json +1 -1
- package/data/section-templates/header-section/children/Navbar/ikas-config-snippet.json +3 -3
- package/data/section-templates/header-section/ikas-config-snippet.json +3 -3
- package/data/section-templates/hero-slider-section/ikas-config-snippet.json +1 -1
- package/data/section-templates/image-handling/ikas-config-snippet.json +13 -13
- package/data/section-templates/navigation/ikas-config-snippet.json +3 -3
- package/data/section-templates/product-detail-section/children/ProductDetailDescription/ikas-config-snippet.json +1 -1
- package/data/section-templates/product-detail-section/children/ProductDetailFeatures/ikas-config-snippet.json +1 -1
- package/data/section-templates/product-detail-section/ikas-config-snippet.json +13 -13
- package/data/section-templates/product-slider-section/ikas-config-snippet.json +3 -3
- package/data/storefront-api.json +1 -1
- package/data/storefront-types.json +1 -1
- package/dist/index.js +178 -60
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -584,7 +584,7 @@ function generateMigrationPlan(theme, projectName, oldSourceDir) {
|
|
|
584
584
|
// Sections queue
|
|
585
585
|
parts.push(`## Sections`);
|
|
586
586
|
parts.push("");
|
|
587
|
-
parts.push(`
|
|
587
|
+
parts.push(`Component **names** are listed below. The CLI auto-generates opaque \`componentId\`s when you run \`config add-component\` — capture each id from the JSON response and use it (or look it up later with \`config list\`) when wiring \`filteredComponentIds\`.`);
|
|
588
588
|
parts.push("");
|
|
589
589
|
const buckets = {
|
|
590
590
|
simple: [],
|
|
@@ -621,10 +621,8 @@ function generateMigrationPlan(theme, projectName, oldSourceDir) {
|
|
|
621
621
|
const itemObj = cd.nestedData?.[0];
|
|
622
622
|
if (itemObj) {
|
|
623
623
|
const childName = itemObj.typescriptName || (itemObj.name ? itemObj.name.replace(/[^a-zA-Z0-9]/g, "") : `${oldName}Item`);
|
|
624
|
-
const childKebab = toKebabCase(childName);
|
|
625
|
-
const childId = `${projectName}-${childKebab}`;
|
|
626
624
|
const fields = (itemObj.nestedData || []).map((f) => f.key || f.name || "?").slice(0, 8);
|
|
627
|
-
children.push({ propName: p.name || "?",
|
|
625
|
+
children.push({ propName: p.name || "?", childName, fields });
|
|
628
626
|
}
|
|
629
627
|
}
|
|
630
628
|
}
|
|
@@ -634,7 +632,7 @@ function generateMigrationPlan(theme, projectName, oldSourceDir) {
|
|
|
634
632
|
if (children.length > 0) {
|
|
635
633
|
parts.push(` - Children:`);
|
|
636
634
|
for (const ch of children) {
|
|
637
|
-
parts.push(` - [ ] \`${ch.
|
|
635
|
+
parts.push(` - [ ] \`${ch.childName}\` — for prop \`${ch.propName}\` — fields: ${ch.fields.join(", ")}`);
|
|
638
636
|
}
|
|
639
637
|
}
|
|
640
638
|
}
|
|
@@ -855,8 +853,8 @@ function generateSectionMigrationPlan(theme, sectionName, projectName, oldSource
|
|
|
855
853
|
parts.push(`# Section Migration Plan: ${oldName}`);
|
|
856
854
|
parts.push("");
|
|
857
855
|
parts.push(`**Old name:** \`${oldName}\` (dir: \`${target.dir || "?"}\`)`);
|
|
858
|
-
parts.push(`**New section
|
|
859
|
-
parts.push(`**
|
|
856
|
+
parts.push(`**New section name:** \`${sectionPascal}\` (the CLI will assign an opaque \`componentId\` at write-time — capture it from \`config add-component\`'s JSON response)`);
|
|
857
|
+
parts.push(`**Migration checkbox label:** \`${sectionId}\` (tracking identifier in \`MIGRATION.md\` only — not the runtime component id)`);
|
|
860
858
|
if (target.isHeader)
|
|
861
859
|
parts.push(`**Flags:** HEADER (\`isHeader: true\`)`);
|
|
862
860
|
if (target.isFooter)
|
|
@@ -961,8 +959,6 @@ function generateSectionMigrationPlan(theme, sectionName, projectName, oldSource
|
|
|
961
959
|
// Child component needed
|
|
962
960
|
const itemObj = cd.nestedData?.[0];
|
|
963
961
|
const childName = itemObj?.typescriptName || (itemObj?.name ? itemObj.name.replace(/[^a-zA-Z0-9]/g, "") : `${sectionPascal}Item`);
|
|
964
|
-
const childKebab = toKebabCase(childName);
|
|
965
|
-
const childId = `${projectName}-${childKebab}`;
|
|
966
962
|
const childProps = [];
|
|
967
963
|
const nestedWarnings = [];
|
|
968
964
|
for (const f of (itemObj?.nestedData || [])) {
|
|
@@ -997,18 +993,17 @@ function generateSectionMigrationPlan(theme, sectionName, projectName, oldSource
|
|
|
997
993
|
}
|
|
998
994
|
children.push({
|
|
999
995
|
propName: oldName,
|
|
1000
|
-
childId,
|
|
1001
996
|
childName,
|
|
1002
997
|
childPropsJson: childProps,
|
|
1003
998
|
customDataName: cd.name || "unnamed",
|
|
1004
999
|
});
|
|
1005
1000
|
newType = "COMPONENT_LIST";
|
|
1006
|
-
notes = `Was CUSTOM → ${cd.type} of ${itemObj?.typescriptName || "object"}. Create child component \`${
|
|
1001
|
+
notes = `Was CUSTOM → ${cd.type} of ${itemObj?.typescriptName || "object"}. Create child component \`${childName}\`; the CLI returns the opaque \`componentId\` in its JSON response — substitute it for the \`<id-of-${childName}>\` placeholder below.`;
|
|
1007
1002
|
const prop = {
|
|
1008
1003
|
name: newName,
|
|
1009
1004
|
displayName: p.displayName || newName,
|
|
1010
1005
|
type: "COMPONENT_LIST",
|
|
1011
|
-
filteredComponentIds: [
|
|
1006
|
+
filteredComponentIds: [`<id-of-${childName}>`],
|
|
1012
1007
|
};
|
|
1013
1008
|
parentPropsJson.push(prop);
|
|
1014
1009
|
}
|
|
@@ -1074,15 +1069,15 @@ function generateSectionMigrationPlan(theme, sectionName, projectName, oldSource
|
|
|
1074
1069
|
parts.push(`Save the returned \`enumId\` values and substitute them in the parent config JSON below.`);
|
|
1075
1070
|
parts.push("");
|
|
1076
1071
|
}
|
|
1077
|
-
// Child CLI commands — dedupe by
|
|
1072
|
+
// Child CLI commands — dedupe by childName (same shape may be referenced by multiple parent props)
|
|
1078
1073
|
const uniqueChildren = new Map();
|
|
1079
1074
|
for (const ch of children) {
|
|
1080
|
-
const existing = uniqueChildren.get(ch.
|
|
1075
|
+
const existing = uniqueChildren.get(ch.childName);
|
|
1081
1076
|
if (existing) {
|
|
1082
1077
|
existing.usedByProps.push(ch.propName);
|
|
1083
1078
|
}
|
|
1084
1079
|
else {
|
|
1085
|
-
uniqueChildren.set(ch.
|
|
1080
|
+
uniqueChildren.set(ch.childName, { child: ch, usedByProps: [ch.propName] });
|
|
1086
1081
|
}
|
|
1087
1082
|
}
|
|
1088
1083
|
if (uniqueChildren.size > 0) {
|
|
@@ -1090,9 +1085,10 @@ function generateSectionMigrationPlan(theme, sectionName, projectName, oldSource
|
|
|
1090
1085
|
parts.push("");
|
|
1091
1086
|
parts.push(`These children are referenced by the parent's COMPONENT_LIST props. Create them before the parent.`);
|
|
1092
1087
|
parts.push(`**Deduped:** run each \`add-component\` command ONCE even if multiple parent props reference the same child.`);
|
|
1088
|
+
parts.push(`**Capture each \`componentId\` from the CLI's JSON response** — you'll substitute these ids for the \`<id-of-{ChildName}>\` placeholders in the parent's \`filteredComponentIds\` below.`);
|
|
1093
1089
|
parts.push("");
|
|
1094
1090
|
for (const { child: ch, usedByProps } of uniqueChildren.values()) {
|
|
1095
|
-
parts.push(`### \`${ch.
|
|
1091
|
+
parts.push(`### \`${ch.childName}\``);
|
|
1096
1092
|
const propsLabel = usedByProps.length > 1
|
|
1097
1093
|
? `Used by parent props: ${usedByProps.map(p => `\`${p}\``).join(", ")} (${usedByProps.length}×)`
|
|
1098
1094
|
: `For parent prop: \`${usedByProps[0]}\``;
|
|
@@ -1101,6 +1097,7 @@ function generateSectionMigrationPlan(theme, sectionName, projectName, oldSource
|
|
|
1101
1097
|
parts.push("");
|
|
1102
1098
|
parts.push(`\`\`\`bash`);
|
|
1103
1099
|
parts.push(`npx ikas-component config add-component --name "${ch.childName}" --type component --props '${JSON.stringify(ch.childPropsJson)}'`);
|
|
1100
|
+
parts.push(`# → { "success": true, "componentId": "<capture-this>", ... }`);
|
|
1104
1101
|
parts.push(`\`\`\``);
|
|
1105
1102
|
parts.push("");
|
|
1106
1103
|
}
|
|
@@ -1116,6 +1113,10 @@ function generateSectionMigrationPlan(theme, sectionName, projectName, oldSource
|
|
|
1116
1113
|
parts.push(`\`\`\``);
|
|
1117
1114
|
parts.push("");
|
|
1118
1115
|
parts.push(`**IMPORTANT:** The CLI auto-generates \`types.ts\`. DO NOT manually create or edit \`types.ts\`.`);
|
|
1116
|
+
if (children.length > 0) {
|
|
1117
|
+
parts.push("");
|
|
1118
|
+
parts.push(`**Before running the command above:** replace each \`<id-of-{ChildName}>\` placeholder in the \`--props\` JSON with the real \`componentId\` captured from the corresponding child's \`add-component\` response (or from \`npx ikas-component config list\`). Component ids are opaque random strings — the CLI will reject any unknown id with a structured error.`);
|
|
1119
|
+
}
|
|
1119
1120
|
parts.push("");
|
|
1120
1121
|
// Implementation guidance
|
|
1121
1122
|
const nextStep = parentStep + 1;
|
|
@@ -1132,7 +1133,7 @@ function generateSectionMigrationPlan(theme, sectionName, projectName, oldSource
|
|
|
1132
1133
|
if (children.length > 0) {
|
|
1133
1134
|
parts.push(`- For COMPONENT_LIST props, use \`<IkasComponentRenderer id="..." components={list as any[]} parentProps={props} />\``);
|
|
1134
1135
|
parts.push(`- **Remember: the parent cannot read child prop values.** Any per-item logic must live in the child component.`);
|
|
1135
|
-
parts.push(`- **Every COMPONENT_LIST slot needs a child component to render individual items.** A product list needs a ProductCard child, a blog list needs a BlogCard child, etc. Check if the child already exists
|
|
1136
|
+
parts.push(`- **Every COMPONENT_LIST slot needs a child component to render individual items.** A product list needs a ProductCard child, a blog list needs a BlogCard child, etc. Check if the child already exists (run \`config list\` to see all components and their opaque ids; reuse the existing id in \`filteredComponentIds\`). If not, create it as a registered component and use the \`componentId\` from the CLI's response.`);
|
|
1136
1137
|
}
|
|
1137
1138
|
// Check if the section itself has data-driven list props (PRODUCT_LIST, BLOG_LIST, CATEGORY_LIST)
|
|
1138
1139
|
const dataListProps = (target.props || []).filter(p => p.type === "PRODUCT_LIST" || p.type === "BLOG_LIST" || p.type === "CATEGORY_LIST" || p.type === "BRAND_LIST");
|
|
@@ -1274,6 +1275,7 @@ function searchFunctions(query) {
|
|
|
1274
1275
|
const scored = storefrontData.functions
|
|
1275
1276
|
.map((fn) => {
|
|
1276
1277
|
const nameScore = matchScore(fn.name, query) * 3;
|
|
1278
|
+
const displayNameScore = fn.displayName ? matchScore(fn.displayName, query) * 3 : 0;
|
|
1277
1279
|
const descScore = matchScore(fn.description, query);
|
|
1278
1280
|
const catScore = fn.categories.some((c) => matchScore(c, query) > 0) ? 5 : 0;
|
|
1279
1281
|
const paramScore = fn.params.some((p) => matchScore(p.name, query) > 0 || matchScore(p.description, query) > 0)
|
|
@@ -1281,7 +1283,10 @@ function searchFunctions(query) {
|
|
|
1281
1283
|
: 0;
|
|
1282
1284
|
const sigScore = matchScore(fn.signature, query) * 2;
|
|
1283
1285
|
const typeScore = fn.parameterTypes?.some((t) => matchScore(t, query) > 0) ? 8 : 0;
|
|
1284
|
-
return {
|
|
1286
|
+
return {
|
|
1287
|
+
fn,
|
|
1288
|
+
score: nameScore + displayNameScore + descScore + catScore + paramScore + sigScore + typeScore,
|
|
1289
|
+
};
|
|
1285
1290
|
})
|
|
1286
1291
|
.filter((item) => item.score > 0)
|
|
1287
1292
|
.sort((a, b) => b.score - a.score);
|
|
@@ -1469,31 +1474,53 @@ server.tool("search_docs", "Search across all ikas storefront API docs, framewor
|
|
|
1469
1474
|
// Tool: get_function_doc
|
|
1470
1475
|
server.tool("get_function_doc", "Get full documentation for a specific storefront API function including signature, parameters, return type, and example.", { name: z.string().describe("Function name (e.g. 'addItemToCart', 'Router.navigate')") }, async ({ name }) => {
|
|
1471
1476
|
const nameLower = name.toLowerCase();
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1477
|
+
// Phase 1: canonical-name match wins. A real function name always outranks
|
|
1478
|
+
// any displayName alias so aliases can never shadow the function they're
|
|
1479
|
+
// named after (e.g. `hasCustomer` the function vs. `hasIkasOrderCustomer`'s
|
|
1480
|
+
// [BP-DISPLAY-NAME: hasCustomer] alias).
|
|
1481
|
+
const byName = storefrontData.functions.find((f) => f.name.toLowerCase() === nameLower);
|
|
1482
|
+
if (byName) {
|
|
1483
|
+
return { content: [{ type: "text", text: formatFunctionDoc(byName) }] };
|
|
1484
|
+
}
|
|
1485
|
+
// Phase 2: fall back to displayName aliases.
|
|
1486
|
+
const byAlias = storefrontData.functions.filter((f) => f.displayName && f.displayName.toLowerCase() === nameLower);
|
|
1487
|
+
if (byAlias.length === 1) {
|
|
1488
|
+
const fn = byAlias[0];
|
|
1489
|
+
const note = `> Note: "${name}" is a display alias for \`${fn.name}\`.\n\n`;
|
|
1490
|
+
return {
|
|
1491
|
+
content: [{ type: "text", text: note + formatFunctionDoc(fn) }],
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
if (byAlias.length > 1) {
|
|
1495
|
+
const lines = [];
|
|
1496
|
+
lines.push(`"${name}" is an ambiguous display alias used by ${byAlias.length} functions. Call \`get_function_doc\` again with the canonical name of the one you want:`);
|
|
1497
|
+
lines.push("");
|
|
1498
|
+
for (const fn of byAlias) {
|
|
1499
|
+
lines.push(formatFunctionSummary(fn));
|
|
1500
|
+
lines.push(` \`${fn.signature}\``);
|
|
1491
1501
|
}
|
|
1502
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
1503
|
+
}
|
|
1504
|
+
// No exact match. Try fuzzy substring match across name + displayName.
|
|
1505
|
+
const matches = storefrontData.functions.filter((f) => f.name.toLowerCase().includes(nameLower) ||
|
|
1506
|
+
(f.displayName && f.displayName.toLowerCase().includes(nameLower)));
|
|
1507
|
+
if (matches.length > 0) {
|
|
1508
|
+
const suggestions = matches.slice(0, 5).map((f) => {
|
|
1509
|
+
const alias = f.displayName && f.displayName !== f.name ? ` (alias: ${f.displayName})` : "";
|
|
1510
|
+
return ` - ${f.name}${alias}`;
|
|
1511
|
+
});
|
|
1492
1512
|
return {
|
|
1493
|
-
content: [
|
|
1513
|
+
content: [
|
|
1514
|
+
{
|
|
1515
|
+
type: "text",
|
|
1516
|
+
text: `Function "${name}" not found. Did you mean:\n${suggestions.join("\n")}`,
|
|
1517
|
+
},
|
|
1518
|
+
],
|
|
1494
1519
|
};
|
|
1495
1520
|
}
|
|
1496
|
-
return {
|
|
1521
|
+
return {
|
|
1522
|
+
content: [{ type: "text", text: `Function "${name}" not found. Use \`list_functions()\` to see all available functions.` }],
|
|
1523
|
+
};
|
|
1497
1524
|
});
|
|
1498
1525
|
// Tool: list_functions
|
|
1499
1526
|
server.tool("list_functions", "List storefront API functions. Without a `category`, returns category names + counts so you can drill in. With a `category`, returns one-line summaries for that category. Use `limit`/`offset` to paginate.", {
|
|
@@ -2072,7 +2099,7 @@ server.tool("get_prop_types", "Get all available ikas.config.json prop types wit
|
|
|
2072
2099
|
const sectionTemplateKeys = sectionTemplateNames.length > 0
|
|
2073
2100
|
? sectionTemplateNames
|
|
2074
2101
|
: null;
|
|
2075
|
-
server.tool("get_section_template", "Get the root files of a starter section template (index.tsx, types.ts, styles.css, ikas-config-snippet.json). Returns ONLY the section's root files plus the NAMES of any children, components, sub-components, utilities, and hooks — their files are NOT included by default. To view one item's full implementation, call `get_section_child(section, name, kind)` where kind is 'children' (default), 'components', or 'sub-components'. To bundle subtrees inline, pass `include`. Call `list_section_types()` for available section types. Use the API patterns shown — create your own JSX structure, CSS class names, and visual design.", {
|
|
2102
|
+
server.tool("get_section_template", "Get the root files of a starter section template (index.tsx, types.ts, styles.css, ikas-config-snippet.json). Returns ONLY the section's root files plus the NAMES of any children, components, sub-components, utilities, and hooks — their files are NOT included by default. To view one item's full implementation, call `get_section_child(section, name, kind)` where kind is 'children' (default), 'components', or 'sub-components'. To bundle subtrees inline, pass `include`. Call `list_section_types()` for available section types. Use the API patterns shown — create your own JSX structure, CSS class names, and visual design. **Container sections** (Header, Footer, ProductDetail, etc.) host child components via a `COMPONENT_LIST` slot — the response emits a complete multi-step Setup Recipe (create children → capture ids → wire `filteredComponentIds` via `config update-prop`). Follow all steps; the parent alone produces an empty section.", {
|
|
2076
2103
|
sectionType: z
|
|
2077
2104
|
.string()
|
|
2078
2105
|
.describe("The section type (call `list_section_types()` for valid values)"),
|
|
@@ -2147,6 +2174,18 @@ server.tool("get_section_template", "Get the root files of a starter section tem
|
|
|
2147
2174
|
`## ${bundle.title} — API Integration Pattern Reference`,
|
|
2148
2175
|
"",
|
|
2149
2176
|
];
|
|
2177
|
+
// Detect container-section pattern (COMPONENT_LIST with `<id-of-X>` placeholders).
|
|
2178
|
+
// Surface this BEFORE every other warning so the LLM cannot miss the wiring requirement.
|
|
2179
|
+
// The full recipe (commands, captured-id placeholders, update-prop call) is appended near
|
|
2180
|
+
// the end of the response in the existing recipe-builder block.
|
|
2181
|
+
{
|
|
2182
|
+
const snippetStrForBanner = bundle.rootFiles["ikas-config-snippet.json"];
|
|
2183
|
+
if (snippetStrForBanner && /<id-of-[A-Za-z0-9_]+>/.test(snippetStrForBanner)) {
|
|
2184
|
+
const childMatches = Array.from(snippetStrForBanner.matchAll(/<id-of-([A-Za-z0-9_]+)>/g));
|
|
2185
|
+
const uniqueChildren = Array.from(new Set(childMatches.map((m) => m[1])));
|
|
2186
|
+
parts.push(`> 🔧 **CONTAINER SECTION — required wiring.** This template hosts ${uniqueChildren.length} child component${uniqueChildren.length === 1 ? "" : "s"} (${uniqueChildren.map((n) => `\`${n}\``).join(", ")}) via a \`COMPONENT_LIST\` slot. Creating the parent alone produces an empty, unusable section. **You MUST follow the full Setup Recipe below**: create each child → capture its \`componentId\` → wire them into the parent's \`filteredComponentIds\` with \`config update-prop\`. Component ids are opaque random strings (e.g. \`7ojrigep-Eml9n5sN3i\`) — they cannot be derived from names, and the CLI rejects unknown ids.`, "");
|
|
2187
|
+
}
|
|
2188
|
+
}
|
|
2150
2189
|
if (anyOutstandingSubtree) {
|
|
2151
2190
|
parts.push("> **PARTIAL TEMPLATE — NOT A COMPLETE IMPLEMENTATION.** The files below are the section's root files only. The section also ships with child components, local components, and/or shared sub-components whose files are **not included** in this response. To view one's full implementation, call `get_section_child(section, name, kind)` for each item you need (kind = `children`, `components`, or `sub-components`). The root `index.tsx` alone is not a complete reference — its imports resolve to files that live in those subtrees.", "");
|
|
2152
2191
|
}
|
|
@@ -2215,7 +2254,10 @@ server.tool("get_section_template", "Get the root files of a starter section tem
|
|
|
2215
2254
|
renderInlineSubtree("children", bundle.childContents);
|
|
2216
2255
|
renderInlineSubtree("components", bundle.componentContents);
|
|
2217
2256
|
renderInlineSubtree("sub-components", bundle.subComponentContents);
|
|
2218
|
-
// Generate a ready-to-run CLI
|
|
2257
|
+
// Generate a ready-to-run CLI recipe from the config snippet. If the parent's
|
|
2258
|
+
// filteredComponentIds reference `<id-of-X>` placeholders, expand into a
|
|
2259
|
+
// multi-step recipe (create children → create parent → wire filteredComponentIds)
|
|
2260
|
+
// so the LLM cannot skip the wiring step.
|
|
2219
2261
|
const configSnippetStr = bundle.rootFiles["ikas-config-snippet.json"];
|
|
2220
2262
|
if (configSnippetStr) {
|
|
2221
2263
|
try {
|
|
@@ -2223,26 +2265,102 @@ server.tool("get_section_template", "Get the root files of a starter section tem
|
|
|
2223
2265
|
const compName = configSnippet.name || normalizedType;
|
|
2224
2266
|
const compType = configSnippet.type || "section";
|
|
2225
2267
|
const propsArr = configSnippet.props || [];
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2268
|
+
// Strip filteredComponentIds for the parent's add-component call (wired
|
|
2269
|
+
// separately below). Preserve name/type/displayName/required so the LLM gets the prop shape.
|
|
2270
|
+
const parentPropsForAdd = propsArr.map((p) => {
|
|
2271
|
+
const out = { name: p.name, type: p.type };
|
|
2272
|
+
if (p.displayName)
|
|
2273
|
+
out.displayName = p.displayName;
|
|
2274
|
+
if (p.required)
|
|
2275
|
+
out.required = true;
|
|
2276
|
+
return out;
|
|
2277
|
+
});
|
|
2278
|
+
const placeholderRe = /^<id-of-(.+)>$/;
|
|
2279
|
+
const slotPlans = [];
|
|
2280
|
+
const allChildNames = new Set();
|
|
2281
|
+
for (const p of propsArr) {
|
|
2282
|
+
if ((p.type === "COMPONENT_LIST" || p.type === "COMPONENT") &&
|
|
2283
|
+
Array.isArray(p.filteredComponentIds)) {
|
|
2284
|
+
const childNames = [];
|
|
2285
|
+
for (const entry of p.filteredComponentIds) {
|
|
2286
|
+
const m = typeof entry === "string" && entry.match(placeholderRe);
|
|
2287
|
+
if (m) {
|
|
2288
|
+
childNames.push(m[1]);
|
|
2289
|
+
allChildNames.add(m[1]);
|
|
2290
|
+
}
|
|
2291
|
+
}
|
|
2292
|
+
if (childNames.length > 0) {
|
|
2293
|
+
slotPlans.push({ propName: p.name, childNames });
|
|
2294
|
+
}
|
|
2295
|
+
}
|
|
2296
|
+
}
|
|
2297
|
+
const childPlans = [];
|
|
2298
|
+
for (const childName of allChildNames) {
|
|
2299
|
+
const childSnippetPath = path.join(SECTION_TEMPLATES_DIR, normalizedType, "children", childName, "ikas-config-snippet.json");
|
|
2300
|
+
let propsJson = "[]";
|
|
2301
|
+
let hasTemplate = false;
|
|
2302
|
+
if (fs.existsSync(childSnippetPath)) {
|
|
2303
|
+
hasTemplate = true;
|
|
2304
|
+
try {
|
|
2305
|
+
const childSnippet = JSON.parse(fs.readFileSync(childSnippetPath, "utf-8"));
|
|
2306
|
+
const childProps = (childSnippet.props || []).map((p) => {
|
|
2307
|
+
const out = { name: p.name, type: p.type };
|
|
2308
|
+
if (p.displayName)
|
|
2309
|
+
out.displayName = p.displayName;
|
|
2310
|
+
if (p.required)
|
|
2311
|
+
out.required = true;
|
|
2312
|
+
return out;
|
|
2313
|
+
});
|
|
2314
|
+
propsJson = JSON.stringify(childProps);
|
|
2315
|
+
}
|
|
2316
|
+
catch {
|
|
2317
|
+
// fall through with []
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
childPlans.push({ name: childName, propsJson, hasTemplate });
|
|
2321
|
+
}
|
|
2322
|
+
const baseFlags = (configSnippet.isHeader ? " --isHeader" : "") +
|
|
2323
|
+
(configSnippet.isFooter ? " --isFooter" : "");
|
|
2324
|
+
const parentPropsJsonStr = parentPropsForAdd.length > 0
|
|
2325
|
+
? ` --props '${JSON.stringify(parentPropsForAdd)}'`
|
|
2326
|
+
: "";
|
|
2327
|
+
if (slotPlans.length > 0) {
|
|
2328
|
+
parts.push("---", "");
|
|
2329
|
+
parts.push(`### Setup Recipe (run in order — ${childPlans.length} child component${childPlans.length === 1 ? "" : "s"} + 1 parent + wiring)`);
|
|
2330
|
+
parts.push("");
|
|
2331
|
+
parts.push(`> ⚠️ **This section is a CONTAINER.** It hosts child components via ${slotPlans.length === 1 ? "a COMPONENT_LIST slot" : "COMPONENT_LIST slots"} (\`${slotPlans.map((s) => s.propName).join("`, `")}\`). Creating the parent alone is **not enough** — you MUST also create the child components and wire their opaque ids into the parent's \`filteredComponentIds\`. Skipping the wiring leaves the slot empty and the section unusable.`, "");
|
|
2332
|
+
parts.push("**Step 1 — Create each child component, and capture its `componentId` from the JSON response:**");
|
|
2333
|
+
parts.push("", "```bash");
|
|
2334
|
+
for (const ch of childPlans) {
|
|
2335
|
+
parts.push(`npx ikas-component config add-component --name "${ch.name}" --type component --props '${ch.propsJson}'`);
|
|
2336
|
+
parts.push(`# → { "success": true, "componentId": "<capture as ${ch.name.toUpperCase()}_ID>", ... }`);
|
|
2337
|
+
if (!ch.hasTemplate) {
|
|
2338
|
+
parts.push(`# (no children/${ch.name}/ template in this section bundle — \`--props '[]'\` is a stub; add real props for ${ch.name} as needed)`);
|
|
2339
|
+
}
|
|
2340
|
+
}
|
|
2341
|
+
parts.push("```", "");
|
|
2342
|
+
parts.push("**Step 2 — Create the parent section (without `filteredComponentIds` yet — those are wired in Step 3):**");
|
|
2343
|
+
parts.push("", "```bash");
|
|
2344
|
+
parts.push(`npx ikas-component config add-component --name "${compName}" --type ${compType}${baseFlags}${parentPropsJsonStr}`);
|
|
2345
|
+
parts.push("```", "");
|
|
2346
|
+
parts.push("**Step 3 — Wire each slot to its allowed children using the ids captured in Step 1:**");
|
|
2347
|
+
parts.push("", "```bash");
|
|
2348
|
+
for (const slot of slotPlans) {
|
|
2349
|
+
const idsArr = slot.childNames.map((n) => `<${n.toUpperCase()}_ID>`);
|
|
2350
|
+
parts.push(`npx ikas-component config update-prop --component "${compName}" --prop ${slot.propName} \\`);
|
|
2351
|
+
parts.push(` --filteredComponentIds '${JSON.stringify(idsArr)}'`);
|
|
2352
|
+
}
|
|
2353
|
+
parts.push("```", "");
|
|
2354
|
+
parts.push("Replace each `<X_ID>` placeholder above with the real `componentId` from the corresponding Step 1 response (or look ids up with `config list`). The CLI rejects unknown ids with a structured error — there is no silent failure mode.");
|
|
2355
|
+
parts.push("");
|
|
2356
|
+
parts.push("**Do NOT manually create or edit `types.ts`** — the CLI commands above regenerate it automatically.");
|
|
2357
|
+
parts.push("");
|
|
2358
|
+
}
|
|
2359
|
+
else {
|
|
2360
|
+
// No child slots — single-step parent command (preserves previous behaviour)
|
|
2361
|
+
const cliCommand = `npx ikas-component config add-component --name "${compName}" --type ${compType}${baseFlags}${parentPropsJsonStr}`;
|
|
2362
|
+
parts.push("---", "", "### CLI Command (run this first)", "", "```bash", cliCommand, "```", "", "**Do NOT manually create or edit `types.ts`** — the CLI command above generates it automatically.", "");
|
|
2244
2363
|
}
|
|
2245
|
-
parts.push("---", "", "### CLI Command (run this first)", "", "```bash", cliCommand, "```", "", "**Do NOT manually create or edit `types.ts`** — the CLI command above generates it automatically.", "");
|
|
2246
2364
|
}
|
|
2247
2365
|
catch {
|
|
2248
2366
|
// ignore parse errors
|