@getcoherent/cli 0.6.23 → 0.6.25

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.
@@ -679,6 +679,10 @@ function replaceRawColors(classes, colorMap) {
679
679
  async function autoFixCode(code, context) {
680
680
  const fixes = [];
681
681
  let fixed = code;
682
+ if (!fixed.includes("\n") && fixed.includes("\\n")) {
683
+ fixed = fixed.replace(/\\n/g, "\n");
684
+ fixes.push("unescaped literal \\n to real newlines");
685
+ }
682
686
  const beforeQuoteFix = fixed;
683
687
  fixed = fixed.replace(/\\'(\s*[}\],])/g, "'$1");
684
688
  fixed = fixed.replace(/(:\s*'.+)\\'(\s*)$/gm, "$1'$2");
@@ -703,17 +707,9 @@ async function autoFixCode(code, context) {
703
707
  };
704
708
  fixed = fixed.split("\n").map((line) => {
705
709
  let l = line;
706
- l = l.replace(/&lt;=/g, (m, offset) => isInsideAttrValue(line, offset) ? m : "<=");
707
- l = l.replace(/&gt;=/g, (m, offset) => isInsideAttrValue(line, offset) ? m : ">=");
708
- l = l.replace(/&amp;&amp;/g, (m, offset) => isInsideAttrValue(line, offset) ? m : "&&");
709
- l = l.replace(
710
- /([\w)\]])\s*&lt;\s*([\w(])/g,
711
- (m, p1, p2, offset) => isInsideAttrValue(line, offset) ? m : `${p1} < ${p2}`
712
- );
713
- l = l.replace(
714
- /([\w)\]])\s*&gt;\s*([\w(])/g,
715
- (m, p1, p2, offset) => isInsideAttrValue(line, offset) ? m : `${p1} > ${p2}`
716
- );
710
+ l = l.replace(/&lt;/g, (m, offset) => isInsideAttrValue(line, offset) ? m : "<");
711
+ l = l.replace(/&gt;/g, (m, offset) => isInsideAttrValue(line, offset) ? m : ">");
712
+ l = l.replace(/&amp;/g, (m, offset) => isInsideAttrValue(line, offset) ? m : "&");
717
713
  return l;
718
714
  }).join("\n");
719
715
  if (fixed !== beforeEntityFix) {
@@ -807,6 +803,123 @@ ${fixed}`;
807
803
  }
808
804
  fixes.push("<button> \u2192 <Button> (with import)");
809
805
  }
806
+ if (/<input\b[^>]*(?:\/>|>)/i.test(fixed) && !fixed.includes('type="hidden"')) {
807
+ const inputLines = fixed.split("\n");
808
+ let hasReplacedInput = false;
809
+ for (let i = 0; i < inputLines.length; i++) {
810
+ if (!/<input\b/i.test(inputLines[i])) continue;
811
+ if (inputLines[i].includes('type="hidden"') || inputLines[i].includes("type='hidden'")) continue;
812
+ inputLines[i] = inputLines[i].replace(/<input\b/gi, "<Input");
813
+ hasReplacedInput = true;
814
+ }
815
+ if (hasReplacedInput) {
816
+ fixed = inputLines.join("\n");
817
+ const hasInputImport = /import\s.*\bInput\b.*from\s+['"]@\/components\/ui\/input['"]/.test(fixed);
818
+ if (!hasInputImport) {
819
+ const lastImportIdx = fixed.lastIndexOf("\nimport ");
820
+ if (lastImportIdx !== -1) {
821
+ const lineEnd = fixed.indexOf("\n", lastImportIdx + 1);
822
+ fixed = fixed.slice(0, lineEnd + 1) + "import { Input } from '@/components/ui/input'\n" + fixed.slice(lineEnd + 1);
823
+ } else {
824
+ const hasUseClient2 = /^['"]use client['"]/.test(fixed.trim());
825
+ const insertAfter2 = hasUseClient2 ? fixed.indexOf("\n") + 1 : 0;
826
+ fixed = fixed.slice(0, insertAfter2) + "import { Input } from '@/components/ui/input'\n" + fixed.slice(insertAfter2);
827
+ }
828
+ }
829
+ fixes.push("<input> \u2192 <Input> (with import)");
830
+ }
831
+ }
832
+ const compositeComponents = {
833
+ select: [
834
+ "Select",
835
+ "SelectContent",
836
+ "SelectItem",
837
+ "SelectTrigger",
838
+ "SelectValue",
839
+ "SelectGroup",
840
+ "SelectLabel",
841
+ "SelectSeparator",
842
+ "SelectScrollUpButton",
843
+ "SelectScrollDownButton"
844
+ ],
845
+ dialog: [
846
+ "Dialog",
847
+ "DialogContent",
848
+ "DialogDescription",
849
+ "DialogFooter",
850
+ "DialogHeader",
851
+ "DialogTitle",
852
+ "DialogTrigger",
853
+ "DialogClose",
854
+ "DialogOverlay",
855
+ "DialogPortal"
856
+ ],
857
+ dropdown_menu: [
858
+ "DropdownMenu",
859
+ "DropdownMenuContent",
860
+ "DropdownMenuItem",
861
+ "DropdownMenuLabel",
862
+ "DropdownMenuSeparator",
863
+ "DropdownMenuTrigger",
864
+ "DropdownMenuCheckboxItem",
865
+ "DropdownMenuGroup",
866
+ "DropdownMenuRadioGroup",
867
+ "DropdownMenuRadioItem",
868
+ "DropdownMenuShortcut",
869
+ "DropdownMenuSub",
870
+ "DropdownMenuSubContent",
871
+ "DropdownMenuSubTrigger"
872
+ ],
873
+ table: ["Table", "TableBody", "TableCaption", "TableCell", "TableFooter", "TableHead", "TableHeader", "TableRow"],
874
+ tabs: ["Tabs", "TabsContent", "TabsList", "TabsTrigger"],
875
+ card: ["Card", "CardContent", "CardDescription", "CardFooter", "CardHeader", "CardTitle"],
876
+ alert_dialog: [
877
+ "AlertDialog",
878
+ "AlertDialogAction",
879
+ "AlertDialogCancel",
880
+ "AlertDialogContent",
881
+ "AlertDialogDescription",
882
+ "AlertDialogFooter",
883
+ "AlertDialogHeader",
884
+ "AlertDialogTitle",
885
+ "AlertDialogTrigger"
886
+ ],
887
+ popover: ["Popover", "PopoverContent", "PopoverTrigger"],
888
+ command: [
889
+ "Command",
890
+ "CommandDialog",
891
+ "CommandEmpty",
892
+ "CommandGroup",
893
+ "CommandInput",
894
+ "CommandItem",
895
+ "CommandList",
896
+ "CommandSeparator",
897
+ "CommandShortcut"
898
+ ],
899
+ form: ["Form", "FormControl", "FormDescription", "FormField", "FormItem", "FormLabel", "FormMessage"]
900
+ };
901
+ const beforeSubImportFix = fixed;
902
+ for (const [uiName, allExports] of Object.entries(compositeComponents)) {
903
+ const importPath = `@/components/ui/${uiName.replace(/_/g, "-")}`;
904
+ const importRe = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*['"]${importPath.replace(/[-/]/g, "\\$&")}['"]`);
905
+ const importMatch = fixed.match(importRe);
906
+ if (!importMatch) continue;
907
+ const imported = new Set(
908
+ importMatch[1].split(",").map((s) => s.trim()).filter(Boolean)
909
+ );
910
+ const usedInCode = allExports.filter((e) => {
911
+ if (imported.has(e)) return false;
912
+ return new RegExp(`<${e}[\\s/>]`).test(fixed) || new RegExp(`</${e}>`).test(fixed);
913
+ });
914
+ if (usedInCode.length > 0) {
915
+ const merged = [...imported, ...usedInCode];
916
+ const newImport = `import { ${merged.join(", ")} } from '${importPath}'`;
917
+ fixed = fixed.replace(importRe, newImport);
918
+ }
919
+ }
920
+ if (fixed !== beforeSubImportFix) {
921
+ fixes.push("added missing sub-imports for composite components");
922
+ }
810
923
  const colorMap = {
811
924
  "bg-zinc-950": "bg-background",
812
925
  "bg-zinc-900": "bg-background",
@@ -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(
@@ -297,6 +320,8 @@ Return JSON with { requests: [{ type: "add-page", changes: { name: "ComponentNam
297
320
  }
298
321
  for (const comp of results) {
299
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);
300
325
  await generateSharedComponent(projectRoot, {
301
326
  name: comp.name,
302
327
  type: planned?.type ?? "section",
@@ -304,7 +329,9 @@ Return JSON with { requests: [{ type: "add-page", changes: { name: "ComponentNam
304
329
  description: planned?.description,
305
330
  usedIn: planned?.usedBy ?? [],
306
331
  source: "generated",
307
- overwrite: true
332
+ overwrite: true,
333
+ propsInterface,
334
+ usageExample
308
335
  });
309
336
  }
310
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-SKQRPBPF.js";
10
+ } from "./chunk-H644LLXJ.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-FAV3UHFM.js";
19
+ } from "./chunk-IYLHC4RC.js";
20
20
  import {
21
21
  CORE_CONSTRAINTS,
22
22
  DESIGN_QUALITY,
@@ -6315,7 +6315,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
6315
6315
  if (plan && plan.sharedComponents.length > 0) {
6316
6316
  spinner.start(`Phase 4.5/6 \u2014 Generating ${plan.sharedComponents.length} shared components from plan...`);
6317
6317
  try {
6318
- const { generateSharedComponentsFromPlan } = await import("./plan-generator-5I6BPEVL.js");
6318
+ const { generateSharedComponentsFromPlan } = await import("./plan-generator-BHDEJGMY.js");
6319
6319
  const generated = await generateSharedComponentsFromPlan(
6320
6320
  plan,
6321
6321
  styleContext,
@@ -6775,7 +6775,7 @@ async function regeneratePage(pageId, config2, projectRoot) {
6775
6775
  const code = await generator.generate(page, appType);
6776
6776
  const route = page.route || "/";
6777
6777
  const isAuth = isAuthRoute(route) || isAuthRoute(page.name || page.id || "");
6778
- const { loadPlan: loadPlanForPath } = await import("./plan-generator-5I6BPEVL.js");
6778
+ const { loadPlan: loadPlanForPath } = await import("./plan-generator-BHDEJGMY.js");
6779
6779
  const planForPath = loadPlanForPath(projectRoot);
6780
6780
  const filePath = routeToFsPath(projectRoot, route, planForPath || isAuth);
6781
6781
  await mkdir3(dirname5(filePath), { recursive: true });
@@ -7091,7 +7091,17 @@ function extractImportsFrom(code, fromPath) {
7091
7091
  return [...new Set(results)];
7092
7092
  }
7093
7093
  function printPostGenerationReport(opts) {
7094
- const { action, pageTitle, filePath, code, route, postFixes = [], layoutShared = [], allShared = [] } = opts;
7094
+ const {
7095
+ action,
7096
+ pageTitle,
7097
+ filePath,
7098
+ code,
7099
+ route,
7100
+ postFixes = [],
7101
+ layoutShared = [],
7102
+ allShared = [],
7103
+ groupLayout
7104
+ } = opts;
7095
7105
  const uiComponents = extractImportsFrom(code, "@/components/ui");
7096
7106
  const sharedImportNames = extractImportsFrom(code, "@/components/shared/");
7097
7107
  const inCodeShared = allShared.filter((s) => sharedImportNames.some((n) => n === s.name));
@@ -7108,8 +7118,10 @@ function printPostGenerationReport(opts) {
7108
7118
  console.log(chalk10.dim(` Shared: ${inCodeShared.map((s) => `${s.id} (${s.name})`).join(", ")}`));
7109
7119
  }
7110
7120
  const isAuthPage = route && (/^\/(login|signin|signup|register|forgot-password|reset-password)\b/.test(route) || filePath.includes("(auth)"));
7111
- if (layoutShared.length > 0 && !isAuthPage) {
7112
- console.log(chalk10.dim(` Layout: ${layoutShared.map((l) => `${l.id} (${l.name})`).join(", ")} via layout.tsx`));
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`));
7113
7125
  }
7114
7126
  if (iconCount > 0) {
7115
7127
  console.log(chalk10.dim(` Icons: ${iconCount} from lucide-react`));
@@ -8534,7 +8546,7 @@ async function chatCommand(message, options) {
8534
8546
  spinner.start(`Creating shared component: ${componentName}...`);
8535
8547
  const { createAIProvider: createAIProvider2 } = await import("./ai-provider-CGSIYFZT.js");
8536
8548
  const { generateSharedComponent: generateSharedComponent7 } = await import("@getcoherent/core");
8537
- const { autoFixCode: autoFixCode2 } = await import("./quality-validator-OX25OLIR.js");
8549
+ const { autoFixCode: autoFixCode2 } = await import("./quality-validator-VDQXXDAV.js");
8538
8550
  const { extractPropsInterface, extractDependencies } = await import("./component-extractor-VYJLT5NR.js");
8539
8551
  const aiProvider = await createAIProvider2(provider ?? "auto");
8540
8552
  const prompt = `Generate a React component called "${componentName}". Description: ${message}.
@@ -9001,9 +9013,10 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
9001
9013
  }
9002
9014
  }
9003
9015
  try {
9004
- const { validateReuse } = await import("./reuse-validator-HC4LZEKF.js");
9016
+ const { validateReuse } = await import("./reuse-validator-XR2ZEYC4.js");
9005
9017
  const { inferPageTypeFromRoute: inferPageTypeFromRoute2 } = await import("./design-constraints-EIP2XM7T.js");
9006
9018
  const manifest2 = await loadManifest8(projectRoot);
9019
+ const reuseplan = projectRoot ? loadPlan(projectRoot) : null;
9007
9020
  if (manifest2.shared.length > 0) {
9008
9021
  for (const request of normalizedRequests) {
9009
9022
  if (request.type !== "add-page") continue;
@@ -9012,7 +9025,8 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
9012
9025
  if (!pageCode) continue;
9013
9026
  const route = changes.route || "";
9014
9027
  const pageType = inferPageTypeFromRoute2(route);
9015
- const warnings = validateReuse(manifest2, pageCode, pageType);
9028
+ const planned = reuseplan ? new Set(reuseplan.sharedComponents.filter((c) => c.usedBy.includes(route)).map((c) => c.name)) : void 0;
9029
+ const warnings = validateReuse(manifest2, pageCode, pageType, void 0, planned);
9016
9030
  for (const w of warnings) {
9017
9031
  console.log(chalk13.yellow(` \u26A0 ${w.message}`));
9018
9032
  }
@@ -11400,7 +11414,7 @@ async function checkCommand(opts = {}) {
11400
11414
  }
11401
11415
  if (!skipShared) {
11402
11416
  try {
11403
- const { validateReuse } = await import("./reuse-validator-HC4LZEKF.js");
11417
+ const { validateReuse } = await import("./reuse-validator-XR2ZEYC4.js");
11404
11418
  const { inferPageTypeFromRoute: inferPageTypeFromRoute2 } = await import("./design-constraints-EIP2XM7T.js");
11405
11419
  const manifest = await loadManifest11(projectRoot);
11406
11420
  const appDir = resolve14(projectRoot, "app");
@@ -11,7 +11,7 @@ import {
11
11
  routeToKey,
12
12
  savePlan,
13
13
  updateArchitecturePlan
14
- } from "./chunk-FAV3UHFM.js";
14
+ } from "./chunk-IYLHC4RC.js";
15
15
  import "./chunk-5AHG4NNX.js";
16
16
  import "./chunk-3RG5ZIWI.js";
17
17
  export {
@@ -4,7 +4,7 @@ import {
4
4
  formatIssues,
5
5
  validatePageQuality,
6
6
  verifyIncrementalEdit
7
- } from "./chunk-SKQRPBPF.js";
7
+ } from "./chunk-H644LLXJ.js";
8
8
  import "./chunk-3RG5ZIWI.js";
9
9
  export {
10
10
  autoFixCode,
@@ -6,11 +6,12 @@ var RELEVANT_TYPES = {
6
6
  auth: /* @__PURE__ */ new Set(["form", "feedback"]),
7
7
  marketing: /* @__PURE__ */ new Set(["section", "layout"])
8
8
  };
9
- function validateReuse(manifest, generatedCode, pageType, newComponents) {
9
+ function validateReuse(manifest, generatedCode, pageType, newComponents, plannedComponentNames) {
10
10
  const warnings = [];
11
11
  const relevantTypes = RELEVANT_TYPES[pageType] || RELEVANT_TYPES.app;
12
12
  for (const comp of manifest.shared) {
13
13
  if (!relevantTypes.has(comp.type)) continue;
14
+ if (plannedComponentNames && !plannedComponentNames.has(comp.name)) continue;
14
15
  const isImported = generatedCode.includes(`from '@/components/shared/`) && (generatedCode.includes(`{ ${comp.name} }`) || generatedCode.includes(`{ ${comp.name},`));
15
16
  if (!isImported) {
16
17
  warnings.push({
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.6.23",
6
+ "version": "0.6.25",
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.23"
46
+ "@getcoherent/core": "0.6.25"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^20.11.0",