@getcoherent/cli 0.6.22 → 0.6.24
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/{chunk-VKGZRKWD.js → chunk-2ZL4X4QD.js} +99 -11
- package/dist/{chunk-PVJJ2YXP.js → chunk-IYLHC4RC.js} +29 -1
- package/dist/index.js +28 -10
- package/dist/{plan-generator-ITHYNYJI.js → plan-generator-BHDEJGMY.js} +1 -1
- package/dist/{quality-validator-P572ZUW5.js → quality-validator-G5AE4337.js} +1 -1
- package/package.json +2 -2
|
@@ -703,17 +703,9 @@ async function autoFixCode(code, context) {
|
|
|
703
703
|
};
|
|
704
704
|
fixed = fixed.split("\n").map((line) => {
|
|
705
705
|
let l = line;
|
|
706
|
-
l = l.replace(/<
|
|
707
|
-
l = l.replace(/>
|
|
708
|
-
l = l.replace(/&
|
|
709
|
-
l = l.replace(
|
|
710
|
-
/([\w)\]])\s*<\s*([\w(])/g,
|
|
711
|
-
(m, p1, p2, offset) => isInsideAttrValue(line, offset) ? m : `${p1} < ${p2}`
|
|
712
|
-
);
|
|
713
|
-
l = l.replace(
|
|
714
|
-
/([\w)\]])\s*>\s*([\w(])/g,
|
|
715
|
-
(m, p1, p2, offset) => isInsideAttrValue(line, offset) ? m : `${p1} > ${p2}`
|
|
716
|
-
);
|
|
706
|
+
l = l.replace(/</g, (m, offset) => isInsideAttrValue(line, offset) ? m : "<");
|
|
707
|
+
l = l.replace(/>/g, (m, offset) => isInsideAttrValue(line, offset) ? m : ">");
|
|
708
|
+
l = l.replace(/&/g, (m, offset) => isInsideAttrValue(line, offset) ? m : "&");
|
|
717
709
|
return l;
|
|
718
710
|
}).join("\n");
|
|
719
711
|
if (fixed !== beforeEntityFix) {
|
|
@@ -807,6 +799,97 @@ ${fixed}`;
|
|
|
807
799
|
}
|
|
808
800
|
fixes.push("<button> \u2192 <Button> (with import)");
|
|
809
801
|
}
|
|
802
|
+
const compositeComponents = {
|
|
803
|
+
select: [
|
|
804
|
+
"Select",
|
|
805
|
+
"SelectContent",
|
|
806
|
+
"SelectItem",
|
|
807
|
+
"SelectTrigger",
|
|
808
|
+
"SelectValue",
|
|
809
|
+
"SelectGroup",
|
|
810
|
+
"SelectLabel",
|
|
811
|
+
"SelectSeparator",
|
|
812
|
+
"SelectScrollUpButton",
|
|
813
|
+
"SelectScrollDownButton"
|
|
814
|
+
],
|
|
815
|
+
dialog: [
|
|
816
|
+
"Dialog",
|
|
817
|
+
"DialogContent",
|
|
818
|
+
"DialogDescription",
|
|
819
|
+
"DialogFooter",
|
|
820
|
+
"DialogHeader",
|
|
821
|
+
"DialogTitle",
|
|
822
|
+
"DialogTrigger",
|
|
823
|
+
"DialogClose",
|
|
824
|
+
"DialogOverlay",
|
|
825
|
+
"DialogPortal"
|
|
826
|
+
],
|
|
827
|
+
dropdown_menu: [
|
|
828
|
+
"DropdownMenu",
|
|
829
|
+
"DropdownMenuContent",
|
|
830
|
+
"DropdownMenuItem",
|
|
831
|
+
"DropdownMenuLabel",
|
|
832
|
+
"DropdownMenuSeparator",
|
|
833
|
+
"DropdownMenuTrigger",
|
|
834
|
+
"DropdownMenuCheckboxItem",
|
|
835
|
+
"DropdownMenuGroup",
|
|
836
|
+
"DropdownMenuRadioGroup",
|
|
837
|
+
"DropdownMenuRadioItem",
|
|
838
|
+
"DropdownMenuShortcut",
|
|
839
|
+
"DropdownMenuSub",
|
|
840
|
+
"DropdownMenuSubContent",
|
|
841
|
+
"DropdownMenuSubTrigger"
|
|
842
|
+
],
|
|
843
|
+
table: ["Table", "TableBody", "TableCaption", "TableCell", "TableFooter", "TableHead", "TableHeader", "TableRow"],
|
|
844
|
+
tabs: ["Tabs", "TabsContent", "TabsList", "TabsTrigger"],
|
|
845
|
+
card: ["Card", "CardContent", "CardDescription", "CardFooter", "CardHeader", "CardTitle"],
|
|
846
|
+
alert_dialog: [
|
|
847
|
+
"AlertDialog",
|
|
848
|
+
"AlertDialogAction",
|
|
849
|
+
"AlertDialogCancel",
|
|
850
|
+
"AlertDialogContent",
|
|
851
|
+
"AlertDialogDescription",
|
|
852
|
+
"AlertDialogFooter",
|
|
853
|
+
"AlertDialogHeader",
|
|
854
|
+
"AlertDialogTitle",
|
|
855
|
+
"AlertDialogTrigger"
|
|
856
|
+
],
|
|
857
|
+
popover: ["Popover", "PopoverContent", "PopoverTrigger"],
|
|
858
|
+
command: [
|
|
859
|
+
"Command",
|
|
860
|
+
"CommandDialog",
|
|
861
|
+
"CommandEmpty",
|
|
862
|
+
"CommandGroup",
|
|
863
|
+
"CommandInput",
|
|
864
|
+
"CommandItem",
|
|
865
|
+
"CommandList",
|
|
866
|
+
"CommandSeparator",
|
|
867
|
+
"CommandShortcut"
|
|
868
|
+
],
|
|
869
|
+
form: ["Form", "FormControl", "FormDescription", "FormField", "FormItem", "FormLabel", "FormMessage"]
|
|
870
|
+
};
|
|
871
|
+
const beforeSubImportFix = fixed;
|
|
872
|
+
for (const [uiName, allExports] of Object.entries(compositeComponents)) {
|
|
873
|
+
const importPath = `@/components/ui/${uiName.replace(/_/g, "-")}`;
|
|
874
|
+
const importRe = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*['"]${importPath.replace(/[-/]/g, "\\$&")}['"]`);
|
|
875
|
+
const importMatch = fixed.match(importRe);
|
|
876
|
+
if (!importMatch) continue;
|
|
877
|
+
const imported = new Set(
|
|
878
|
+
importMatch[1].split(",").map((s) => s.trim()).filter(Boolean)
|
|
879
|
+
);
|
|
880
|
+
const usedInCode = allExports.filter((e) => {
|
|
881
|
+
if (imported.has(e)) return false;
|
|
882
|
+
return new RegExp(`<${e}[\\s/>]`).test(fixed) || new RegExp(`</${e}>`).test(fixed);
|
|
883
|
+
});
|
|
884
|
+
if (usedInCode.length > 0) {
|
|
885
|
+
const merged = [...imported, ...usedInCode];
|
|
886
|
+
const newImport = `import { ${merged.join(", ")} } from '${importPath}'`;
|
|
887
|
+
fixed = fixed.replace(importRe, newImport);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
if (fixed !== beforeSubImportFix) {
|
|
891
|
+
fixes.push("added missing sub-imports for composite components");
|
|
892
|
+
}
|
|
810
893
|
const colorMap = {
|
|
811
894
|
"bg-zinc-950": "bg-background",
|
|
812
895
|
"bg-zinc-900": "bg-background",
|
|
@@ -1186,6 +1269,11 @@ ${selectImport}`
|
|
|
1186
1269
|
if (fixed !== beforePlaceholder) {
|
|
1187
1270
|
fixes.push("placeholder content \u2192 contextual content");
|
|
1188
1271
|
}
|
|
1272
|
+
const beforeIconProp = fixed;
|
|
1273
|
+
fixed = fixed.replace(/(\bicon\s*:\s*)React\.ReactNode\b/g, "$1React.ElementType");
|
|
1274
|
+
if (fixed !== beforeIconProp) {
|
|
1275
|
+
fixes.push("icon prop: ReactNode \u2192 ElementType (forwardRef compat)");
|
|
1276
|
+
}
|
|
1189
1277
|
return { code: fixed, fixes };
|
|
1190
1278
|
}
|
|
1191
1279
|
function formatIssues(issues) {
|
|
@@ -236,6 +236,29 @@ function loadPlan(projectRoot) {
|
|
|
236
236
|
function toKebabCase(name) {
|
|
237
237
|
return name.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
|
|
238
238
|
}
|
|
239
|
+
function extractPropsInterface(code, componentName) {
|
|
240
|
+
const interfaceRe = new RegExp(`interface\\s+${componentName}Props\\s*\\{([^}]+)\\}`, "s");
|
|
241
|
+
const match = code.match(interfaceRe);
|
|
242
|
+
if (match) {
|
|
243
|
+
return match[1].split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("//")).join("; ");
|
|
244
|
+
}
|
|
245
|
+
const typeRe = new RegExp(`type\\s+${componentName}Props\\s*=\\s*\\{([^}]+)\\}`, "s");
|
|
246
|
+
const typeMatch = code.match(typeRe);
|
|
247
|
+
if (typeMatch) {
|
|
248
|
+
return typeMatch[1].split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("//")).join("; ");
|
|
249
|
+
}
|
|
250
|
+
return void 0;
|
|
251
|
+
}
|
|
252
|
+
function extractUsageExample(code, componentName) {
|
|
253
|
+
const funcMatch = code.match(new RegExp(`export function ${componentName}\\s*\\(\\{([^}]+)\\}`, "s"));
|
|
254
|
+
if (!funcMatch) return void 0;
|
|
255
|
+
const props = funcMatch[1].split(",").map((p) => p.split(":")[0].trim()).filter(Boolean);
|
|
256
|
+
const example = props.map((p) => {
|
|
257
|
+
if (p.startsWith("...")) return `${p.slice(3)}={{}}`;
|
|
258
|
+
return `${p}={...}`;
|
|
259
|
+
}).join(" ");
|
|
260
|
+
return `<${componentName} ${example} />`;
|
|
261
|
+
}
|
|
239
262
|
async function generateSharedComponentsFromPlan(plan, styleContext, projectRoot, aiProvider) {
|
|
240
263
|
if (plan.sharedComponents.length === 0) return [];
|
|
241
264
|
const componentSpecs = plan.sharedComponents.map(
|
|
@@ -258,6 +281,7 @@ Requirements:
|
|
|
258
281
|
- Use Tailwind CSS classes matching the style context
|
|
259
282
|
- TypeScript with proper props interface
|
|
260
283
|
- Each component is a standalone file
|
|
284
|
+
- Icon props MUST use \`icon: React.ElementType\` (NOT React.ReactNode) and render as \`<Icon className="size-4" />\` where \`const Icon = icon\`. Lucide icons are forwardRef components, not elements.
|
|
261
285
|
|
|
262
286
|
Return JSON with { requests: [{ type: "add-page", changes: { name: "ComponentName", pageCode: "..." } }, ...] }`;
|
|
263
287
|
const results = [];
|
|
@@ -296,6 +320,8 @@ Return JSON with { requests: [{ type: "add-page", changes: { name: "ComponentNam
|
|
|
296
320
|
}
|
|
297
321
|
for (const comp of results) {
|
|
298
322
|
const planned = plan.sharedComponents.find((c) => c.name === comp.name);
|
|
323
|
+
const propsInterface = extractPropsInterface(comp.code, comp.name);
|
|
324
|
+
const usageExample = extractUsageExample(comp.code, comp.name);
|
|
299
325
|
await generateSharedComponent(projectRoot, {
|
|
300
326
|
name: comp.name,
|
|
301
327
|
type: planned?.type ?? "section",
|
|
@@ -303,7 +329,9 @@ Return JSON with { requests: [{ type: "add-page", changes: { name: "ComponentNam
|
|
|
303
329
|
description: planned?.description,
|
|
304
330
|
usedIn: planned?.usedBy ?? [],
|
|
305
331
|
source: "generated",
|
|
306
|
-
overwrite: true
|
|
332
|
+
overwrite: true,
|
|
333
|
+
propsInterface,
|
|
334
|
+
usageExample
|
|
307
335
|
});
|
|
308
336
|
}
|
|
309
337
|
return results;
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
formatIssues,
|
|
8
8
|
validatePageQuality,
|
|
9
9
|
verifyIncrementalEdit
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2ZL4X4QD.js";
|
|
11
11
|
import {
|
|
12
12
|
generateArchitecturePlan,
|
|
13
13
|
getPageGroup,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
routeToKey,
|
|
17
17
|
savePlan,
|
|
18
18
|
updateArchitecturePlan
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-IYLHC4RC.js";
|
|
20
20
|
import {
|
|
21
21
|
CORE_CONSTRAINTS,
|
|
22
22
|
DESIGN_QUALITY,
|
|
@@ -4393,6 +4393,12 @@ PAGE WRAPPER (CRITICAL \u2014 the layout provides width/padding automatically):
|
|
|
4393
4393
|
- ALL app pages must follow this exact same structure so content aligns consistently across pages
|
|
4394
4394
|
- Landing/marketing pages are an exception: they render outside the app layout and should use full-width <section> elements with inner "mx-auto max-w-6xl" for content.
|
|
4395
4395
|
|
|
4396
|
+
ICON PROPS (CRITICAL \u2014 prevents runtime crash):
|
|
4397
|
+
- When passing lucide-react icons as props, the component prop MUST be typed as \`React.ElementType\` (NOT React.ReactNode).
|
|
4398
|
+
- Render icon props as: \`const Icon = props.icon; <Icon className="size-4" />\`
|
|
4399
|
+
- Pass icons as: \`icon={FolderOpen}\` (component reference, not JSX element)
|
|
4400
|
+
- Lucide icons are forwardRef components \u2014 passing them as ReactNode causes "Objects are not valid as a React child" error.
|
|
4401
|
+
|
|
4396
4402
|
PAGE CONTENT (CRITICAL \u2014 prevents empty or duplicate pages):
|
|
4397
4403
|
- Every page MUST have substantial content. NEVER generate a page with only metadata and an empty <main> element.
|
|
4398
4404
|
- NEVER create an inline preview/demo of another page (e.g., embedding a "dashboard view" inside the landing page with a toggle). Each page should be its own route.
|
|
@@ -5096,7 +5102,7 @@ async function warnInlineDuplicates(projectRoot, pageName, route, pageCode, mani
|
|
|
5096
5102
|
if (pageTokens.includes(t)) overlap++;
|
|
5097
5103
|
}
|
|
5098
5104
|
const overlapRatio = sharedTokens.size > 0 ? overlap / sharedTokens.size : 0;
|
|
5099
|
-
if (overlap >=
|
|
5105
|
+
if (overlap >= 25 && overlapRatio >= 0.7) {
|
|
5100
5106
|
console.log(
|
|
5101
5107
|
chalk7.yellow(
|
|
5102
5108
|
`
|
|
@@ -6309,7 +6315,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
6309
6315
|
if (plan && plan.sharedComponents.length > 0) {
|
|
6310
6316
|
spinner.start(`Phase 4.5/6 \u2014 Generating ${plan.sharedComponents.length} shared components from plan...`);
|
|
6311
6317
|
try {
|
|
6312
|
-
const { generateSharedComponentsFromPlan } = await import("./plan-generator-
|
|
6318
|
+
const { generateSharedComponentsFromPlan } = await import("./plan-generator-BHDEJGMY.js");
|
|
6313
6319
|
const generated = await generateSharedComponentsFromPlan(
|
|
6314
6320
|
plan,
|
|
6315
6321
|
styleContext,
|
|
@@ -6769,7 +6775,7 @@ async function regeneratePage(pageId, config2, projectRoot) {
|
|
|
6769
6775
|
const code = await generator.generate(page, appType);
|
|
6770
6776
|
const route = page.route || "/";
|
|
6771
6777
|
const isAuth = isAuthRoute(route) || isAuthRoute(page.name || page.id || "");
|
|
6772
|
-
const { loadPlan: loadPlanForPath } = await import("./plan-generator-
|
|
6778
|
+
const { loadPlan: loadPlanForPath } = await import("./plan-generator-BHDEJGMY.js");
|
|
6773
6779
|
const planForPath = loadPlanForPath(projectRoot);
|
|
6774
6780
|
const filePath = routeToFsPath(projectRoot, route, planForPath || isAuth);
|
|
6775
6781
|
await mkdir3(dirname5(filePath), { recursive: true });
|
|
@@ -7085,7 +7091,17 @@ function extractImportsFrom(code, fromPath) {
|
|
|
7085
7091
|
return [...new Set(results)];
|
|
7086
7092
|
}
|
|
7087
7093
|
function printPostGenerationReport(opts) {
|
|
7088
|
-
const {
|
|
7094
|
+
const {
|
|
7095
|
+
action,
|
|
7096
|
+
pageTitle,
|
|
7097
|
+
filePath,
|
|
7098
|
+
code,
|
|
7099
|
+
route,
|
|
7100
|
+
postFixes = [],
|
|
7101
|
+
layoutShared = [],
|
|
7102
|
+
allShared = [],
|
|
7103
|
+
groupLayout
|
|
7104
|
+
} = opts;
|
|
7089
7105
|
const uiComponents = extractImportsFrom(code, "@/components/ui");
|
|
7090
7106
|
const sharedImportNames = extractImportsFrom(code, "@/components/shared/");
|
|
7091
7107
|
const inCodeShared = allShared.filter((s) => sharedImportNames.some((n) => n === s.name));
|
|
@@ -7102,8 +7118,10 @@ function printPostGenerationReport(opts) {
|
|
|
7102
7118
|
console.log(chalk10.dim(` Shared: ${inCodeShared.map((s) => `${s.id} (${s.name})`).join(", ")}`));
|
|
7103
7119
|
}
|
|
7104
7120
|
const isAuthPage = route && (/^\/(login|signin|signup|register|forgot-password|reset-password)\b/.test(route) || filePath.includes("(auth)"));
|
|
7105
|
-
|
|
7106
|
-
|
|
7121
|
+
const isSidebarPage = groupLayout === "sidebar" || filePath.includes("(app)");
|
|
7122
|
+
const filteredLayout = isAuthPage ? [] : isSidebarPage ? layoutShared.filter((l) => /sidebar/i.test(l.name)) : layoutShared;
|
|
7123
|
+
if (filteredLayout.length > 0) {
|
|
7124
|
+
console.log(chalk10.dim(` Layout: ${filteredLayout.map((l) => `${l.id} (${l.name})`).join(", ")} via layout.tsx`));
|
|
7107
7125
|
}
|
|
7108
7126
|
if (iconCount > 0) {
|
|
7109
7127
|
console.log(chalk10.dim(` Icons: ${iconCount} from lucide-react`));
|
|
@@ -7512,7 +7530,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7512
7530
|
sharedId: resolved.id,
|
|
7513
7531
|
sharedName: resolved.name,
|
|
7514
7532
|
pageTarget,
|
|
7515
|
-
route
|
|
7533
|
+
route,
|
|
7516
7534
|
postFixes: fixes
|
|
7517
7535
|
});
|
|
7518
7536
|
try {
|
|
@@ -8528,7 +8546,7 @@ async function chatCommand(message, options) {
|
|
|
8528
8546
|
spinner.start(`Creating shared component: ${componentName}...`);
|
|
8529
8547
|
const { createAIProvider: createAIProvider2 } = await import("./ai-provider-CGSIYFZT.js");
|
|
8530
8548
|
const { generateSharedComponent: generateSharedComponent7 } = await import("@getcoherent/core");
|
|
8531
|
-
const { autoFixCode: autoFixCode2 } = await import("./quality-validator-
|
|
8549
|
+
const { autoFixCode: autoFixCode2 } = await import("./quality-validator-G5AE4337.js");
|
|
8532
8550
|
const { extractPropsInterface, extractDependencies } = await import("./component-extractor-VYJLT5NR.js");
|
|
8533
8551
|
const aiProvider = await createAIProvider2(provider ?? "auto");
|
|
8534
8552
|
const prompt = `Generate a React component called "${componentName}". Description: ${message}.
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.6.
|
|
6
|
+
"version": "0.6.24",
|
|
7
7
|
"description": "CLI interface for Coherent Design Method",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.js",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"ora": "^7.0.1",
|
|
44
44
|
"prompts": "^2.4.2",
|
|
45
45
|
"zod": "^3.22.4",
|
|
46
|
-
"@getcoherent/core": "0.6.
|
|
46
|
+
"@getcoherent/core": "0.6.24"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/node": "^20.11.0",
|