@getcoherent/cli 0.5.1 → 0.5.3

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.
Files changed (2) hide show
  1. package/dist/index.js +362 -163
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -362,6 +362,7 @@ function createMinimalConfig() {
362
362
  }
363
363
  },
364
364
  settings: {
365
+ initialized: false,
365
366
  appType: "multi-page",
366
367
  framework: "next",
367
368
  typescript: true,
@@ -2427,8 +2428,8 @@ async function createAppRouteGroupLayout(projectPath) {
2427
2428
  // src/commands/chat.ts
2428
2429
  import chalk13 from "chalk";
2429
2430
  import ora2 from "ora";
2430
- import { resolve as resolve9 } from "path";
2431
- import { existsSync as existsSync16, readFileSync as readFileSync10, mkdirSync as mkdirSync6 } from "fs";
2431
+ import { resolve as resolve9, relative as relative2 } from "path";
2432
+ import { existsSync as existsSync16, readFileSync as readFileSync10, mkdirSync as mkdirSync6, readdirSync as readdirSync2 } from "fs";
2432
2433
  import {
2433
2434
  DesignSystemManager as DesignSystemManager7,
2434
2435
  ComponentManager as ComponentManager4,
@@ -4605,6 +4606,26 @@ FEW-SHOT EXAMPLE \u2014 correct stat card in pageCode (follow this pattern exact
4605
4606
  \`\`\`
4606
4607
  Key: CardTitle is text-sm font-medium (NOT text-lg). Metric is text-2xl font-bold. Subtext is text-xs text-muted-foreground. Icon is size-4 text-muted-foreground.
4607
4608
 
4609
+ SURGICAL MODIFICATION RULES (CRITICAL for incremental edits):
4610
+ - When modifying an existing page, return the COMPLETE page code
4611
+ - Change ONLY the specific section, component, or element the user requested
4612
+ - Do NOT modify imports unless the change requires new imports
4613
+ - Do NOT change state variables, event handlers, or data in unrelated sections
4614
+ - Do NOT restyle sections the user did not mention
4615
+ - Preserve all existing className values on unchanged elements
4616
+ - If the user asks to change a "section" or "block", identify it by heading, content, or position
4617
+
4618
+ Component Promotion Rules:
4619
+ - When the user asks to "make X a shared component" or "reuse X across pages":
4620
+ - Use request type "promote-and-link"
4621
+ - Extract the JSX block into a separate component file
4622
+ - Replace inline code with the component import on all specified pages
4623
+
4624
+ Global Component Change Rules:
4625
+ - When the user asks to change "all cards" or "every button" or similar:
4626
+ - If the pattern is already a shared component, modify the shared component file
4627
+ - If the pattern is inline across pages, first promote it to a shared component, then modify it
4628
+
4608
4629
  OPTIONAL UX RECOMMENDATIONS:
4609
4630
  If you see opportunities to improve UX (accessibility, layout, consistency, responsiveness, visual hierarchy), add a short markdown block in "uxRecommendations". Otherwise omit it.
4610
4631
 
@@ -4712,8 +4733,8 @@ async function ensureAuthRouteGroup(projectRoot) {
4712
4733
  const guardPath = join6(projectRoot, "app", "ShowWhenNotAuthRoute.tsx");
4713
4734
  const rootLayoutPath = join6(projectRoot, "app", "layout.tsx");
4714
4735
  if (!existsSync9(authLayoutPath)) {
4715
- const { mkdir: mkdir7 } = await import("fs/promises");
4716
- await mkdir7(join6(projectRoot, "app", "(auth)"), { recursive: true });
4736
+ const { mkdir: mkdir8 } = await import("fs/promises");
4737
+ await mkdir8(join6(projectRoot, "app", "(auth)"), { recursive: true });
4717
4738
  await writeFile2(authLayoutPath, AUTH_LAYOUT, "utf-8");
4718
4739
  }
4719
4740
  if (!existsSync9(guardPath)) {
@@ -5885,7 +5906,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
5885
5906
 
5886
5907
  // src/commands/chat/modification-handler.ts
5887
5908
  import { resolve as resolve7 } from "path";
5888
- import { mkdir as mkdir3 } from "fs/promises";
5909
+ import { mkdir as mkdir4 } from "fs/promises";
5889
5910
  import { dirname as dirname6 } from "path";
5890
5911
  import chalk11 from "chalk";
5891
5912
  import {
@@ -6673,11 +6694,68 @@ function formatIssues(issues) {
6673
6694
  }
6674
6695
  return lines.join("\n");
6675
6696
  }
6697
+ function checkDesignConsistency(code) {
6698
+ const warnings = [];
6699
+ const hexPattern = /\[#[0-9a-fA-F]{3,8}\]/g;
6700
+ for (const match of code.matchAll(hexPattern)) {
6701
+ warnings.push({
6702
+ type: "hardcoded-color",
6703
+ message: `Hardcoded color ${match[0]} \u2014 use a design token (e.g., bg-primary) instead`
6704
+ });
6705
+ }
6706
+ const spacingPattern = /[pm][trblxy]?-\[\d+px\]/g;
6707
+ for (const match of code.matchAll(spacingPattern)) {
6708
+ warnings.push({
6709
+ type: "arbitrary-spacing",
6710
+ message: `Arbitrary spacing ${match[0]} \u2014 use Tailwind spacing scale instead`
6711
+ });
6712
+ }
6713
+ return warnings;
6714
+ }
6715
+ function verifyIncrementalEdit(before, after) {
6716
+ const issues = [];
6717
+ const hookPattern = /\buse[A-Z]\w+\s*\(/;
6718
+ if (hookPattern.test(after) && !after.includes("'use client'") && !after.includes('"use client"')) {
6719
+ issues.push({
6720
+ type: "missing-use-client",
6721
+ message: 'Code uses React hooks but missing "use client" directive'
6722
+ });
6723
+ }
6724
+ if (!after.includes("export default")) {
6725
+ issues.push({
6726
+ type: "missing-default-export",
6727
+ message: "Missing default export \u2014 page component must have a default export"
6728
+ });
6729
+ }
6730
+ const importRegex = /import\s+\{([^}]+)\}\s+from/g;
6731
+ const beforeImports = /* @__PURE__ */ new Set();
6732
+ const afterImports = /* @__PURE__ */ new Set();
6733
+ for (const match of before.matchAll(importRegex)) {
6734
+ match[1].split(",").forEach((s) => beforeImports.add(s.trim()));
6735
+ }
6736
+ for (const match of after.matchAll(importRegex)) {
6737
+ match[1].split(",").forEach((s) => afterImports.add(s.trim()));
6738
+ }
6739
+ for (const symbol of beforeImports) {
6740
+ if (!afterImports.has(symbol) && symbol.length > 0) {
6741
+ const codeWithoutImports = after.replace(/^import\s+.*$/gm, "");
6742
+ const symbolRegex = new RegExp(`\\b${symbol}\\b`);
6743
+ if (symbolRegex.test(codeWithoutImports)) {
6744
+ issues.push({
6745
+ type: "missing-import",
6746
+ symbol,
6747
+ message: `Import for "${symbol}" was removed but symbol is still used in code`
6748
+ });
6749
+ }
6750
+ }
6751
+ }
6752
+ return issues;
6753
+ }
6676
6754
 
6677
6755
  // src/commands/chat/code-generator.ts
6678
6756
  import { resolve as resolve6 } from "path";
6679
6757
  import { existsSync as existsSync14 } from "fs";
6680
- import { mkdir as mkdir2 } from "fs/promises";
6758
+ import { mkdir as mkdir3 } from "fs/promises";
6681
6759
  import { dirname as dirname5 } from "path";
6682
6760
  import {
6683
6761
  ComponentGenerator as ComponentGenerator2,
@@ -6686,6 +6764,39 @@ import {
6686
6764
  } from "@getcoherent/core";
6687
6765
  import { integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout2, generateSharedComponent as generateSharedComponent2 } from "@getcoherent/core";
6688
6766
  import chalk9 from "chalk";
6767
+
6768
+ // src/utils/file-hashes.ts
6769
+ import { createHash } from "crypto";
6770
+ import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir2 } from "fs/promises";
6771
+ import { join as join9 } from "path";
6772
+ var HASHES_FILE = ".coherent/file-hashes.json";
6773
+ async function computeFileHash(filePath) {
6774
+ const content = await readFile5(filePath, "utf-8");
6775
+ return createHash("sha256").update(content).digest("hex");
6776
+ }
6777
+ async function loadHashes(projectRoot) {
6778
+ try {
6779
+ const raw = await readFile5(join9(projectRoot, HASHES_FILE), "utf-8");
6780
+ return JSON.parse(raw);
6781
+ } catch {
6782
+ return {};
6783
+ }
6784
+ }
6785
+ async function saveHashes(projectRoot, hashes) {
6786
+ const dir = join9(projectRoot, ".coherent");
6787
+ await mkdir2(dir, { recursive: true });
6788
+ await writeFile4(join9(projectRoot, HASHES_FILE), JSON.stringify(hashes, null, 2) + "\n");
6789
+ }
6790
+ async function isManuallyEdited(filePath, storedHash) {
6791
+ try {
6792
+ const currentHash = await computeFileHash(filePath);
6793
+ return currentHash !== storedHash;
6794
+ } catch {
6795
+ return false;
6796
+ }
6797
+ }
6798
+
6799
+ // src/commands/chat/code-generator.ts
6689
6800
  async function validateAndFixGeneratedCode(projectRoot, code, options = {}) {
6690
6801
  const fixes = [];
6691
6802
  let fixed = fixEscapedClosingQuotes(code);
@@ -6748,48 +6859,71 @@ async function regeneratePage(pageId, config2, projectRoot) {
6748
6859
  const route = page.route || "/";
6749
6860
  const isAuth = isAuthRoute(route) || isAuthRoute(page.name || page.id || "");
6750
6861
  const filePath = routeToFsPath(projectRoot, route, isAuth);
6751
- await mkdir2(dirname5(filePath), { recursive: true });
6862
+ await mkdir3(dirname5(filePath), { recursive: true });
6752
6863
  await writeFile(filePath, code);
6753
6864
  }
6754
- async function regenerateLayout(config2, projectRoot) {
6755
- const layout = config2.pages[0]?.layout || "centered";
6865
+ async function canOverwriteShared(projectRoot, componentFile, storedHashes) {
6866
+ const filePath = resolve6(projectRoot, componentFile);
6867
+ if (!existsSync14(filePath)) return true;
6868
+ const storedHash = storedHashes[componentFile];
6869
+ if (!storedHash) return true;
6870
+ const edited = await isManuallyEdited(filePath, storedHash);
6871
+ if (edited) {
6872
+ console.log(chalk9.yellow(` \u26A0 Skipping ${componentFile} \u2014 manually edited since last generation`));
6873
+ }
6874
+ return !edited;
6875
+ }
6876
+ async function regenerateLayout(config2, projectRoot, options = { navChanged: false }) {
6756
6877
  const appType = config2.settings.appType || "multi-page";
6757
6878
  const generator = new PageGenerator(config2);
6758
- const code = await generator.generateLayout(layout, appType, { skipNav: true });
6759
- const layoutPath = resolve6(projectRoot, "app", "layout.tsx");
6760
- await writeFile(layoutPath, code);
6879
+ const initialized = config2.settings.initialized !== false;
6880
+ const hashes = options.storedHashes ?? {};
6881
+ if (!initialized) {
6882
+ const layout = config2.pages[0]?.layout || "centered";
6883
+ const code = await generator.generateLayout(layout, appType, { skipNav: true });
6884
+ await writeFile(resolve6(projectRoot, "app", "layout.tsx"), code);
6885
+ }
6761
6886
  if (config2.navigation?.enabled && appType === "multi-page") {
6762
6887
  const navType = config2.navigation.type || "header";
6763
- if (navType === "header" || navType === "both") {
6764
- const headerCode = generator.generateSharedHeaderCode();
6765
- await generateSharedComponent2(projectRoot, {
6766
- name: "Header",
6767
- type: "layout",
6768
- code: headerCode,
6769
- description: "Main site header with navigation and theme toggle",
6770
- usedIn: ["app/layout.tsx"],
6771
- overwrite: true
6772
- });
6773
- }
6774
- const footerCode = generator.generateSharedFooterCode();
6775
- await generateSharedComponent2(projectRoot, {
6776
- name: "Footer",
6777
- type: "layout",
6778
- code: footerCode,
6779
- description: "Site footer",
6780
- usedIn: ["app/layout.tsx"],
6781
- overwrite: true
6782
- });
6783
- if (navType === "sidebar" || navType === "both") {
6784
- const sidebarCode = generator.generateSharedSidebarCode();
6785
- await generateSharedComponent2(projectRoot, {
6786
- name: "Sidebar",
6787
- type: "layout",
6788
- code: sidebarCode,
6789
- description: "Vertical sidebar navigation with collapsible sections",
6790
- usedIn: ["app/(app)/layout.tsx"],
6791
- overwrite: true
6792
- });
6888
+ const shouldRegenShared = !initialized || options.navChanged;
6889
+ if (shouldRegenShared) {
6890
+ if (navType === "header" || navType === "both") {
6891
+ if (await canOverwriteShared(projectRoot, "components/shared/header.tsx", hashes)) {
6892
+ const headerCode = generator.generateSharedHeaderCode();
6893
+ await generateSharedComponent2(projectRoot, {
6894
+ name: "Header",
6895
+ type: "layout",
6896
+ code: headerCode,
6897
+ description: "Main site header with navigation and theme toggle",
6898
+ usedIn: ["app/layout.tsx"],
6899
+ overwrite: true
6900
+ });
6901
+ }
6902
+ }
6903
+ if (await canOverwriteShared(projectRoot, "components/shared/footer.tsx", hashes)) {
6904
+ const footerCode = generator.generateSharedFooterCode();
6905
+ await generateSharedComponent2(projectRoot, {
6906
+ name: "Footer",
6907
+ type: "layout",
6908
+ code: footerCode,
6909
+ description: "Site footer",
6910
+ usedIn: ["app/layout.tsx"],
6911
+ overwrite: true
6912
+ });
6913
+ }
6914
+ if (navType === "sidebar" || navType === "both") {
6915
+ if (await canOverwriteShared(projectRoot, "components/shared/sidebar.tsx", hashes)) {
6916
+ const sidebarCode = generator.generateSharedSidebarCode();
6917
+ await generateSharedComponent2(projectRoot, {
6918
+ name: "Sidebar",
6919
+ type: "layout",
6920
+ code: sidebarCode,
6921
+ description: "Vertical sidebar navigation with collapsible sections",
6922
+ usedIn: ["app/(app)/layout.tsx"],
6923
+ overwrite: true
6924
+ });
6925
+ }
6926
+ }
6793
6927
  }
6794
6928
  }
6795
6929
  try {
@@ -6844,7 +6978,7 @@ export default function AppLayout({
6844
6978
  }
6845
6979
  `;
6846
6980
  }
6847
- async function regenerateFiles(modified, config2, projectRoot) {
6981
+ async function regenerateFiles(modified, config2, projectRoot, options = { navChanged: false }) {
6848
6982
  const componentIds = /* @__PURE__ */ new Set();
6849
6983
  const pageIds = /* @__PURE__ */ new Set();
6850
6984
  for (const item of modified) {
@@ -6855,7 +6989,10 @@ async function regenerateFiles(modified, config2, projectRoot) {
6855
6989
  }
6856
6990
  }
6857
6991
  if (config2.navigation?.enabled && modified.length > 0) {
6858
- await regenerateLayout(config2, projectRoot);
6992
+ await regenerateLayout(config2, projectRoot, {
6993
+ navChanged: options.navChanged,
6994
+ storedHashes: options.storedHashes
6995
+ });
6859
6996
  }
6860
6997
  if (componentIds.size > 0) {
6861
6998
  const twGen = new TailwindConfigGenerator(config2);
@@ -7582,7 +7719,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
7582
7719
  await ensureAuthRouteGroup(projectRoot);
7583
7720
  }
7584
7721
  const filePath = routeToFsPath(projectRoot, route, isAuth);
7585
- await mkdir3(dirname6(filePath), { recursive: true });
7722
+ await mkdir4(dirname6(filePath), { recursive: true });
7586
7723
  const { fixedCode, fixes: postFixes } = await validateAndFixGeneratedCode(projectRoot, finalPageCode, {
7587
7724
  isPage: true
7588
7725
  });
@@ -7672,6 +7809,12 @@ Rules:
7672
7809
  \u{1F50D} Quality check for ${page.name || page.id}:`));
7673
7810
  console.log(chalk11.dim(report));
7674
7811
  }
7812
+ const consistency = checkDesignConsistency(codeToWrite);
7813
+ if (consistency.length > 0) {
7814
+ console.log(chalk11.yellow(`
7815
+ \u{1F3A8} Design consistency for ${page.name || page.id}:`));
7816
+ consistency.forEach((w) => console.log(chalk11.dim(` \u26A0 [${w.type}] ${w.message}`)));
7817
+ }
7675
7818
  }
7676
7819
  }
7677
7820
  return {
@@ -7737,6 +7880,12 @@ ${routeRules}
7737
7880
  ${pagesCtx}`
7738
7881
  );
7739
7882
  if (DEBUG2) console.log(chalk11.dim(` [update-page] AI returned ${resolvedPageCode.length} chars`));
7883
+ const editIssues = verifyIncrementalEdit(currentCode, resolvedPageCode);
7884
+ if (editIssues.length > 0) {
7885
+ console.log(chalk11.yellow(`
7886
+ \u26A0 Incremental edit issues for ${pageDef.name || pageDef.id}:`));
7887
+ editIssues.forEach((issue) => console.log(chalk11.dim(` [${issue.type}] ${issue.message}`)));
7888
+ }
7740
7889
  } else {
7741
7890
  console.log(chalk11.yellow(" \u26A0 AI provider does not support editPageCode"));
7742
7891
  }
@@ -7761,7 +7910,7 @@ ${pagesCtx}`
7761
7910
  if (installed.length > 0) {
7762
7911
  result.modified = [...result.modified, ...installed.map((id) => `component:${id}`)];
7763
7912
  }
7764
- await mkdir3(dirname6(absPath), { recursive: true });
7913
+ await mkdir4(dirname6(absPath), { recursive: true });
7765
7914
  const { fixedCode, fixes: postFixes } = await validateAndFixGeneratedCode(projectRoot, resolvedPageCode, {
7766
7915
  isPage: true
7767
7916
  });
@@ -7823,6 +7972,12 @@ ${pagesCtx}`
7823
7972
  \u{1F50D} Quality check for ${pageDef.name || pageDef.id}:`));
7824
7973
  console.log(chalk11.dim(report));
7825
7974
  }
7975
+ const consistency = checkDesignConsistency(codeToWrite);
7976
+ if (consistency.length > 0) {
7977
+ console.log(chalk11.yellow(`
7978
+ \u{1F3A8} Design consistency for ${pageDef.name || pageDef.id}:`));
7979
+ consistency.forEach((w) => console.log(chalk11.dim(` \u26A0 [${w.type}] ${w.message}`)));
7980
+ }
7826
7981
  } else {
7827
7982
  try {
7828
7983
  let code = await readFile(absPath);
@@ -7879,6 +8034,15 @@ ${pagesCtx}`
7879
8034
  }
7880
8035
  }
7881
8036
 
8037
+ // src/utils/nav-snapshot.ts
8038
+ function takeNavSnapshot(items) {
8039
+ if (!items || items.length === 0) return "[]";
8040
+ return JSON.stringify(items.map((i) => `${i.label}:${i.href}`).sort());
8041
+ }
8042
+ function hasNavChanged(before, after) {
8043
+ return before !== after;
8044
+ }
8045
+
7882
8046
  // src/commands/chat/interactive.ts
7883
8047
  import chalk12 from "chalk";
7884
8048
  import { resolve as resolve8 } from "path";
@@ -8111,6 +8275,7 @@ async function chatCommand(message, options) {
8111
8275
  }
8112
8276
  spinner.text = "Loading design system configuration...";
8113
8277
  }
8278
+ const storedHashes = await loadHashes(projectRoot);
8114
8279
  const dsm = new DesignSystemManager7(configPath);
8115
8280
  await dsm.load();
8116
8281
  const cm = new ComponentManager4(config2);
@@ -8461,6 +8626,9 @@ async function chatCommand(message, options) {
8461
8626
  if (DEBUG4) console.log(chalk13.dim("[backup] Created snapshot"));
8462
8627
  } catch {
8463
8628
  }
8629
+ const navBefore = takeNavSnapshot(
8630
+ config2.navigation?.items?.map((i) => ({ label: i.label, href: i.route || `/${i.label.toLowerCase()}` }))
8631
+ );
8464
8632
  spinner.start("Applying modifications...");
8465
8633
  const results = [];
8466
8634
  for (const request of normalizedRequests) {
@@ -8608,6 +8776,10 @@ async function chatCommand(message, options) {
8608
8776
  } catch {
8609
8777
  }
8610
8778
  }
8779
+ if (updatedConfig.settings.initialized === false) {
8780
+ updatedConfig.settings.initialized = true;
8781
+ dsm.updateConfig(updatedConfig);
8782
+ }
8611
8783
  spinner.text = "Saving configuration...";
8612
8784
  await dsm.save();
8613
8785
  spinner.succeed("Configuration saved");
@@ -8617,15 +8789,42 @@ async function chatCommand(message, options) {
8617
8789
  scaffoldedPages.forEach(({ route }) => {
8618
8790
  allModified.add(`page:${route.slice(1) || "home"}`);
8619
8791
  });
8792
+ const navAfter = takeNavSnapshot(
8793
+ updatedConfig.navigation?.items?.map((i) => ({
8794
+ label: i.label,
8795
+ href: i.route || `/${i.label.toLowerCase()}`
8796
+ }))
8797
+ );
8798
+ const navChanged = hasNavChanged(navBefore, navAfter);
8620
8799
  if (allModified.size > 0) {
8621
8800
  spinner.start("Regenerating affected files...");
8622
- await regenerateFiles(Array.from(allModified), updatedConfig, projectRoot);
8801
+ await regenerateFiles(Array.from(allModified), updatedConfig, projectRoot, { navChanged, storedHashes });
8623
8802
  spinner.succeed("Files regenerated");
8624
8803
  }
8625
8804
  try {
8626
8805
  fixGlobalsCss(projectRoot, updatedConfig);
8627
8806
  } catch {
8628
8807
  }
8808
+ try {
8809
+ const updatedHashes = { ...storedHashes };
8810
+ const sharedDir = resolve9(projectRoot, "components", "shared");
8811
+ const layoutFile = resolve9(projectRoot, "app", "layout.tsx");
8812
+ const filesToHash = [layoutFile];
8813
+ if (existsSync16(sharedDir)) {
8814
+ for (const f of readdirSync2(sharedDir)) {
8815
+ if (f.endsWith(".tsx")) filesToHash.push(resolve9(sharedDir, f));
8816
+ }
8817
+ }
8818
+ for (const filePath of filesToHash) {
8819
+ if (existsSync16(filePath)) {
8820
+ const rel = relative2(projectRoot, filePath);
8821
+ updatedHashes[rel] = await computeFileHash(filePath);
8822
+ }
8823
+ }
8824
+ await saveHashes(projectRoot, updatedHashes);
8825
+ } catch {
8826
+ if (DEBUG4) console.log(chalk13.dim("[hashes] Could not save file hashes"));
8827
+ }
8629
8828
  const successfulPairs = normalizedRequests.map((request, index) => ({ request, result: results[index] })).filter(({ result }) => result.success);
8630
8829
  if (successfulPairs.length > 0) {
8631
8830
  const changes = successfulPairs.map(({ request }) => ({
@@ -8733,18 +8932,18 @@ import chalk14 from "chalk";
8733
8932
  import ora3 from "ora";
8734
8933
  import { spawn } from "child_process";
8735
8934
  import { existsSync as existsSync19, rmSync as rmSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync10 } from "fs";
8736
- import { resolve as resolve10, join as join11 } from "path";
8935
+ import { resolve as resolve10, join as join12 } from "path";
8737
8936
  import { readdir as readdir2 } from "fs/promises";
8738
8937
  import { DesignSystemManager as DesignSystemManager8, ComponentGenerator as ComponentGenerator3 } from "@getcoherent/core";
8739
8938
 
8740
8939
  // src/utils/file-watcher.ts
8741
8940
  import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync18 } from "fs";
8742
- import { relative as relative3, join as join10 } from "path";
8941
+ import { relative as relative4, join as join11 } from "path";
8743
8942
  import { loadManifest as loadManifest8, saveManifest as saveManifest3 } from "@getcoherent/core";
8744
8943
 
8745
8944
  // src/utils/component-integrity.ts
8746
- import { existsSync as existsSync17, readFileSync as readFileSync11, readdirSync as readdirSync2 } from "fs";
8747
- import { join as join9, relative as relative2 } from "path";
8945
+ import { existsSync as existsSync17, readFileSync as readFileSync11, readdirSync as readdirSync3 } from "fs";
8946
+ import { join as join10, relative as relative3 } from "path";
8748
8947
  function extractExportedComponentNames(code) {
8749
8948
  const names = [];
8750
8949
  let m;
@@ -8770,7 +8969,7 @@ function arraysEqual(a, b) {
8770
8969
  }
8771
8970
  function findPagesImporting(projectRoot, componentName, componentFile) {
8772
8971
  const results = [];
8773
- const appDir = join9(projectRoot, "app");
8972
+ const appDir = join10(projectRoot, "app");
8774
8973
  if (!existsSync17(appDir)) return results;
8775
8974
  const pageFiles = collectFiles(appDir, (name) => name === "page.tsx" || name === "page.jsx");
8776
8975
  const componentImportPath = componentFile.replace(/\.tsx$/, "").replace(/\.jsx$/, "");
@@ -8782,7 +8981,7 @@ function findPagesImporting(projectRoot, componentName, componentFile) {
8782
8981
  const hasDefaultImport = new RegExp(`import\\s+${componentName}\\s+from\\s+['"]`).test(code);
8783
8982
  const hasPathImport = code.includes(`@/${componentImportPath}`);
8784
8983
  if (hasNamedImport || hasDefaultImport || hasPathImport) {
8785
- results.push(relative2(projectRoot, absPath));
8984
+ results.push(relative3(projectRoot, absPath));
8786
8985
  }
8787
8986
  } catch {
8788
8987
  }
@@ -8790,7 +8989,7 @@ function findPagesImporting(projectRoot, componentName, componentFile) {
8790
8989
  return results;
8791
8990
  }
8792
8991
  function isUsedInLayout(projectRoot, componentName) {
8793
- const layoutPath = join9(projectRoot, "app", "layout.tsx");
8992
+ const layoutPath = join10(projectRoot, "app", "layout.tsx");
8794
8993
  if (!existsSync17(layoutPath)) return false;
8795
8994
  try {
8796
8995
  const code = readFileSync11(layoutPath, "utf-8");
@@ -8801,7 +9000,7 @@ function isUsedInLayout(projectRoot, componentName) {
8801
9000
  }
8802
9001
  function findUnregisteredComponents(projectRoot, manifest) {
8803
9002
  const results = [];
8804
- const componentsDir = join9(projectRoot, "components");
9003
+ const componentsDir = join10(projectRoot, "components");
8805
9004
  if (!existsSync17(componentsDir)) return results;
8806
9005
  const registeredFiles = new Set(manifest.shared.map((s) => s.file));
8807
9006
  const registeredNames = new Set(manifest.shared.map((s) => s.name));
@@ -8811,7 +9010,7 @@ function findUnregisteredComponents(projectRoot, manifest) {
8811
9010
  ["ui", "node_modules"]
8812
9011
  );
8813
9012
  for (const absPath of files) {
8814
- const relFile = relative2(projectRoot, absPath);
9013
+ const relFile = relative3(projectRoot, absPath);
8815
9014
  if (registeredFiles.has(relFile)) continue;
8816
9015
  try {
8817
9016
  const code = readFileSync11(absPath, "utf-8");
@@ -8829,7 +9028,7 @@ function findUnregisteredComponents(projectRoot, manifest) {
8829
9028
  }
8830
9029
  function findInlineDuplicates(projectRoot, manifest) {
8831
9030
  const results = [];
8832
- const appDir = join9(projectRoot, "app");
9031
+ const appDir = join10(projectRoot, "app");
8833
9032
  if (!existsSync17(appDir)) return results;
8834
9033
  const pageFiles = collectFiles(appDir, (name) => name === "page.tsx" || name === "page.jsx");
8835
9034
  for (const absPath of pageFiles) {
@@ -8840,7 +9039,7 @@ function findInlineDuplicates(projectRoot, manifest) {
8840
9039
  } catch {
8841
9040
  continue;
8842
9041
  }
8843
- const relPath = relative2(projectRoot, absPath);
9042
+ const relPath = relative3(projectRoot, absPath);
8844
9043
  for (const shared of manifest.shared) {
8845
9044
  const importPath = shared.file.replace(/\.tsx$/, "").replace(/\.jsx$/, "");
8846
9045
  const isImported = code.includes(`@/${importPath}`) || code.includes(`from './${importPath}'`) || code.includes(`from "../${importPath}"`);
@@ -8859,7 +9058,7 @@ function findInlineDuplicates(projectRoot, manifest) {
8859
9058
  return results;
8860
9059
  }
8861
9060
  function findComponentFileByExportName(projectRoot, componentName) {
8862
- const componentsDir = join9(projectRoot, "components");
9061
+ const componentsDir = join10(projectRoot, "components");
8863
9062
  if (!existsSync17(componentsDir)) return null;
8864
9063
  const files = collectFiles(
8865
9064
  componentsDir,
@@ -8871,7 +9070,7 @@ function findComponentFileByExportName(projectRoot, componentName) {
8871
9070
  const code = readFileSync11(absPath, "utf-8");
8872
9071
  const exports = extractExportedComponentNames(code);
8873
9072
  if (exports.includes(componentName)) {
8874
- return relative2(projectRoot, absPath);
9073
+ return relative3(projectRoot, absPath);
8875
9074
  }
8876
9075
  } catch {
8877
9076
  }
@@ -8881,7 +9080,7 @@ function findComponentFileByExportName(projectRoot, componentName) {
8881
9080
  function removeOrphanedEntries(projectRoot, manifest) {
8882
9081
  const removed = [];
8883
9082
  const valid = manifest.shared.filter((entry) => {
8884
- const filePath = join9(projectRoot, entry.file);
9083
+ const filePath = join10(projectRoot, entry.file);
8885
9084
  if (existsSync17(filePath)) return true;
8886
9085
  removed.push({ id: entry.id, name: entry.name });
8887
9086
  return false;
@@ -8900,7 +9099,7 @@ function reconcileComponents(projectRoot, manifest) {
8900
9099
  };
8901
9100
  const m = { ...manifest, shared: [...manifest.shared], nextId: manifest.nextId };
8902
9101
  m.shared = m.shared.filter((entry) => {
8903
- const filePath = join9(projectRoot, entry.file);
9102
+ const filePath = join10(projectRoot, entry.file);
8904
9103
  if (!existsSync17(filePath)) {
8905
9104
  const newPath = findComponentFileByExportName(projectRoot, entry.name);
8906
9105
  if (newPath) {
@@ -8913,7 +9112,7 @@ function reconcileComponents(projectRoot, manifest) {
8913
9112
  }
8914
9113
  let code;
8915
9114
  try {
8916
- code = readFileSync11(join9(projectRoot, entry.file), "utf-8");
9115
+ code = readFileSync11(join10(projectRoot, entry.file), "utf-8");
8917
9116
  } catch {
8918
9117
  return true;
8919
9118
  }
@@ -8990,12 +9189,12 @@ function collectFiles(dir, filter, skipDirs = []) {
8990
9189
  function walk(d) {
8991
9190
  let entries;
8992
9191
  try {
8993
- entries = readdirSync2(d, { withFileTypes: true });
9192
+ entries = readdirSync3(d, { withFileTypes: true });
8994
9193
  } catch {
8995
9194
  return;
8996
9195
  }
8997
9196
  for (const e of entries) {
8998
- const full = join9(d, e.name);
9197
+ const full = join10(d, e.name);
8999
9198
  if (e.isDirectory()) {
9000
9199
  if (skipDirs.includes(e.name) || e.name.startsWith(".")) continue;
9001
9200
  walk(full);
@@ -9034,7 +9233,7 @@ function findInlineDuplicatesOfShared(content, manifest) {
9034
9233
  }
9035
9234
  function getWatcherConfig(projectRoot) {
9036
9235
  try {
9037
- const pkgPath = join10(projectRoot, "package.json");
9236
+ const pkgPath = join11(projectRoot, "package.json");
9038
9237
  if (!existsSync18(pkgPath)) return defaultWatcherConfig();
9039
9238
  const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
9040
9239
  const c = pkg?.coherent?.watcher ?? {};
@@ -9059,7 +9258,7 @@ function defaultWatcherConfig() {
9059
9258
  };
9060
9259
  }
9061
9260
  async function handleFileChange(projectRoot, filePath) {
9062
- const relativePath = relative3(projectRoot, filePath).replace(/\\/g, "/");
9261
+ const relativePath = relative4(projectRoot, filePath).replace(/\\/g, "/");
9063
9262
  if (!relativePath.endsWith(".tsx") && !relativePath.endsWith(".ts")) return;
9064
9263
  if (relativePath.includes("node_modules") || relativePath.includes(".next")) return;
9065
9264
  let content;
@@ -9109,7 +9308,7 @@ async function handleFileChange(projectRoot, filePath) {
9109
9308
  }
9110
9309
  }
9111
9310
  async function handleFileDelete(projectRoot, filePath) {
9112
- const relativePath = relative3(projectRoot, filePath).replace(/\\/g, "/");
9311
+ const relativePath = relative4(projectRoot, filePath).replace(/\\/g, "/");
9113
9312
  if (!relativePath.startsWith("components/") || relativePath.startsWith("components/ui/")) return;
9114
9313
  try {
9115
9314
  const chalk32 = (await import("chalk")).default;
@@ -9129,7 +9328,7 @@ async function handleFileDelete(projectRoot, filePath) {
9129
9328
  }
9130
9329
  }
9131
9330
  async function detectNewComponent(projectRoot, filePath) {
9132
- const relativePath = relative3(projectRoot, filePath).replace(/\\/g, "/");
9331
+ const relativePath = relative4(projectRoot, filePath).replace(/\\/g, "/");
9133
9332
  if (!relativePath.startsWith("components/") || relativePath.startsWith("components/ui/")) return;
9134
9333
  if (!relativePath.endsWith(".tsx") && !relativePath.endsWith(".jsx")) return;
9135
9334
  try {
@@ -9163,8 +9362,8 @@ function startFileWatcher(projectRoot) {
9163
9362
  let watcher = null;
9164
9363
  let manifestWatcher = null;
9165
9364
  import("chokidar").then((chokidar) => {
9166
- const appGlob = join10(projectRoot, "app", "**", "*.tsx");
9167
- const compGlob = join10(projectRoot, "components", "**", "*.tsx");
9365
+ const appGlob = join11(projectRoot, "app", "**", "*.tsx");
9366
+ const compGlob = join11(projectRoot, "components", "**", "*.tsx");
9168
9367
  watcher = chokidar.default.watch([appGlob, compGlob], {
9169
9368
  ignoreInitial: true,
9170
9369
  awaitWriteFinish: { stabilityThreshold: 500 }
@@ -9176,7 +9375,7 @@ function startFileWatcher(projectRoot) {
9176
9375
  });
9177
9376
  watcher.on("unlink", (fp) => handleFileDelete(projectRoot, fp));
9178
9377
  });
9179
- const manifestPath = join10(projectRoot, "coherent.components.json");
9378
+ const manifestPath = join11(projectRoot, "coherent.components.json");
9180
9379
  if (existsSync18(manifestPath)) {
9181
9380
  import("chokidar").then((chokidar) => {
9182
9381
  manifestWatcher = chokidar.default.watch(manifestPath, { ignoreInitial: true });
@@ -9228,7 +9427,7 @@ function checkDependenciesInstalled(projectRoot) {
9228
9427
  return existsSync19(nodeModulesPath);
9229
9428
  }
9230
9429
  function clearStaleCache(projectRoot) {
9231
- const nextDir = join11(projectRoot, ".next");
9430
+ const nextDir = join12(projectRoot, ".next");
9232
9431
  if (existsSync19(nextDir)) {
9233
9432
  rmSync3(nextDir, { recursive: true, force: true });
9234
9433
  console.log(chalk14.dim(" \u2714 Cleared stale build cache"));
@@ -9249,7 +9448,7 @@ async function listPageFiles(appDir) {
9249
9448
  async function walk(dir) {
9250
9449
  const entries = await readdir2(dir, { withFileTypes: true });
9251
9450
  for (const e of entries) {
9252
- const full = join11(dir, e.name);
9451
+ const full = join12(dir, e.name);
9253
9452
  if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "api" && e.name !== "design-system") await walk(full);
9254
9453
  else if (e.isFile() && e.name === "page.tsx") out.push(full);
9255
9454
  }
@@ -9258,7 +9457,7 @@ async function listPageFiles(appDir) {
9258
9457
  return out;
9259
9458
  }
9260
9459
  async function validateSyntax(projectRoot) {
9261
- const appDir = join11(projectRoot, "app");
9460
+ const appDir = join12(projectRoot, "app");
9262
9461
  const pages = await listPageFiles(appDir);
9263
9462
  for (const file of pages) {
9264
9463
  const content = readFileSync13(file, "utf-8");
@@ -9270,8 +9469,8 @@ async function validateSyntax(projectRoot) {
9270
9469
  }
9271
9470
  }
9272
9471
  async function fixMissingComponentExports(projectRoot) {
9273
- const appDir = join11(projectRoot, "app");
9274
- const uiDir = join11(projectRoot, "components", "ui");
9472
+ const appDir = join12(projectRoot, "app");
9473
+ const uiDir = join12(projectRoot, "components", "ui");
9275
9474
  if (!existsSync19(appDir) || !existsSync19(uiDir)) return;
9276
9475
  const pages = await listPageFiles(appDir);
9277
9476
  const neededExports = /* @__PURE__ */ new Map();
@@ -9286,7 +9485,7 @@ async function fixMissingComponentExports(projectRoot) {
9286
9485
  for (const name of names) neededExports.get(componentId).add(name);
9287
9486
  }
9288
9487
  }
9289
- const configPath = join11(projectRoot, "design-system.config.ts");
9488
+ const configPath = join12(projectRoot, "design-system.config.ts");
9290
9489
  let config2 = null;
9291
9490
  try {
9292
9491
  const mgr = new DesignSystemManager8(configPath);
@@ -9295,7 +9494,7 @@ async function fixMissingComponentExports(projectRoot) {
9295
9494
  }
9296
9495
  const generator = new ComponentGenerator3(config2 || { components: [], pages: [], tokens: {} });
9297
9496
  for (const [componentId, needed] of neededExports) {
9298
- const componentFile = join11(uiDir, `${componentId}.tsx`);
9497
+ const componentFile = join12(uiDir, `${componentId}.tsx`);
9299
9498
  const def = getShadcnComponent(componentId);
9300
9499
  if (!existsSync19(componentFile)) {
9301
9500
  if (!def) continue;
@@ -9332,7 +9531,7 @@ async function fixMissingComponentExports(projectRoot) {
9332
9531
  }
9333
9532
  }
9334
9533
  async function backfillPageAnalysis(projectRoot) {
9335
- const configPath = join11(projectRoot, "design-system.config.ts");
9534
+ const configPath = join12(projectRoot, "design-system.config.ts");
9336
9535
  if (!existsSync19(configPath)) return;
9337
9536
  try {
9338
9537
  const mgr = new DesignSystemManager8(configPath);
@@ -9344,11 +9543,11 @@ async function backfillPageAnalysis(projectRoot) {
9344
9543
  const isAuth = route.includes("login") || route.includes("register") || route.includes("signup") || route.includes("sign-up");
9345
9544
  let filePath;
9346
9545
  if (route === "/") {
9347
- filePath = join11(projectRoot, "app", "page.tsx");
9546
+ filePath = join12(projectRoot, "app", "page.tsx");
9348
9547
  } else if (isAuth) {
9349
- filePath = join11(projectRoot, "app", "(auth)", route.slice(1), "page.tsx");
9548
+ filePath = join12(projectRoot, "app", "(auth)", route.slice(1), "page.tsx");
9350
9549
  } else {
9351
- filePath = join11(projectRoot, "app", route.slice(1), "page.tsx");
9550
+ filePath = join12(projectRoot, "app", route.slice(1), "page.tsx");
9352
9551
  }
9353
9552
  if (!existsSync19(filePath)) continue;
9354
9553
  const code = readFileSync13(filePath, "utf-8");
@@ -9378,7 +9577,7 @@ async function autoInstallShadcnComponent(componentId, projectRoot) {
9378
9577
  const def = getShadcnComponent(componentId);
9379
9578
  if (!def) return false;
9380
9579
  try {
9381
- const configPath = join11(projectRoot, "design-system.config.ts");
9580
+ const configPath = join12(projectRoot, "design-system.config.ts");
9382
9581
  let config2 = null;
9383
9582
  try {
9384
9583
  const mgr = new DesignSystemManager8(configPath);
@@ -9387,10 +9586,10 @@ async function autoInstallShadcnComponent(componentId, projectRoot) {
9387
9586
  }
9388
9587
  const generator = new ComponentGenerator3(config2 || { components: [], pages: [], tokens: {} });
9389
9588
  const code = await generator.generate(def);
9390
- const uiDir = join11(projectRoot, "components", "ui");
9589
+ const uiDir = join12(projectRoot, "components", "ui");
9391
9590
  const { mkdirSync: mkdirSync9 } = await import("fs");
9392
9591
  mkdirSync9(uiDir, { recursive: true });
9393
- writeFileSync10(join11(uiDir, `${componentId}.tsx`), code, "utf-8");
9592
+ writeFileSync10(join12(uiDir, `${componentId}.tsx`), code, "utf-8");
9394
9593
  return true;
9395
9594
  } catch {
9396
9595
  return false;
@@ -9604,9 +9803,9 @@ async function previewCommand() {
9604
9803
  import chalk15 from "chalk";
9605
9804
  import ora4 from "ora";
9606
9805
  import { spawn as spawn2 } from "child_process";
9607
- import { existsSync as existsSync20, rmSync as rmSync4, readdirSync as readdirSync3 } from "fs";
9608
- import { resolve as resolve11, join as join12, dirname as dirname7 } from "path";
9609
- import { readdir as readdir3, readFile as readFile5, writeFile as writeFile4, mkdir as mkdir4, copyFile as copyFile2 } from "fs/promises";
9806
+ import { existsSync as existsSync20, rmSync as rmSync4, readdirSync as readdirSync4 } from "fs";
9807
+ import { resolve as resolve11, join as join13, dirname as dirname7 } from "path";
9808
+ import { readdir as readdir3, readFile as readFile6, writeFile as writeFile5, mkdir as mkdir5, copyFile as copyFile2 } from "fs/promises";
9610
9809
  var COPY_EXCLUDE = /* @__PURE__ */ new Set([
9611
9810
  "node_modules",
9612
9811
  ".next",
@@ -9624,16 +9823,16 @@ var COPY_EXCLUDE = /* @__PURE__ */ new Set([
9624
9823
  ".env.local"
9625
9824
  ]);
9626
9825
  async function copyDir(src, dest) {
9627
- await mkdir4(dest, { recursive: true });
9826
+ await mkdir5(dest, { recursive: true });
9628
9827
  const entries = await readdir3(src, { withFileTypes: true });
9629
9828
  for (const e of entries) {
9630
- const srcPath = join12(src, e.name);
9631
- const destPath = join12(dest, e.name);
9829
+ const srcPath = join13(src, e.name);
9830
+ const destPath = join13(dest, e.name);
9632
9831
  if (COPY_EXCLUDE.has(e.name)) continue;
9633
9832
  if (e.isDirectory()) {
9634
9833
  await copyDir(srcPath, destPath);
9635
9834
  } else {
9636
- await mkdir4(dirname7(destPath), { recursive: true });
9835
+ await mkdir5(dirname7(destPath), { recursive: true });
9637
9836
  await copyFile2(srcPath, destPath);
9638
9837
  }
9639
9838
  }
@@ -9648,15 +9847,15 @@ function getPackageManager2(projectRoot) {
9648
9847
  }
9649
9848
  async function patchNextConfigForExport(outRoot) {
9650
9849
  for (const name of ["next.config.ts", "next.config.mjs", "next.config.js"]) {
9651
- const p = join12(outRoot, name);
9850
+ const p = join13(outRoot, name);
9652
9851
  if (!existsSync20(p)) continue;
9653
- let content = await readFile5(p, "utf-8");
9852
+ let content = await readFile6(p, "utf-8");
9654
9853
  if (content.includes("ignoreDuringBuilds")) return;
9655
9854
  content = content.replace(
9656
9855
  /(const\s+nextConfig\s*(?::\s*\w+)?\s*=\s*\{)/,
9657
9856
  "$1\n eslint: { ignoreDuringBuilds: true },\n typescript: { ignoreBuildErrors: true },"
9658
9857
  );
9659
- await writeFile4(p, content, "utf-8");
9858
+ await writeFile5(p, content, "utf-8");
9660
9859
  return;
9661
9860
  }
9662
9861
  }
@@ -9690,13 +9889,13 @@ EXPOSE 3000
9690
9889
  \`\`\`
9691
9890
  `;
9692
9891
  async function ensureReadmeDeploySection(outRoot) {
9693
- const readmePath = join12(outRoot, "README.md");
9892
+ const readmePath = join13(outRoot, "README.md");
9694
9893
  if (!existsSync20(readmePath)) return;
9695
9894
  try {
9696
- let content = await readFile5(readmePath, "utf-8");
9895
+ let content = await readFile6(readmePath, "utf-8");
9697
9896
  if (/##\s+Deploy\b/m.test(content)) return;
9698
9897
  content = content.trimEnd() + DEPLOY_SECTION + "\n";
9699
- await writeFile4(readmePath, content);
9898
+ await writeFile5(readmePath, content);
9700
9899
  } catch {
9701
9900
  }
9702
9901
  }
@@ -9710,22 +9909,22 @@ async function countPages(outRoot) {
9710
9909
  return;
9711
9910
  }
9712
9911
  for (const e of entries) {
9713
- const full = join12(dir, e.name);
9912
+ const full = join13(dir, e.name);
9714
9913
  if (e.isFile() && e.name === "page.tsx") n++;
9715
9914
  else if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "api") await walk(full);
9716
9915
  }
9717
9916
  }
9718
- const appDir = join12(outRoot, "app");
9917
+ const appDir = join13(outRoot, "app");
9719
9918
  if (existsSync20(appDir)) await walk(appDir);
9720
9919
  return n;
9721
9920
  }
9722
9921
  function countComponents(outRoot) {
9723
9922
  let n = 0;
9724
9923
  for (const sub of ["ui", "shared"]) {
9725
- const dir = join12(outRoot, "components", sub);
9924
+ const dir = join13(outRoot, "components", sub);
9726
9925
  if (!existsSync20(dir)) continue;
9727
9926
  try {
9728
- n += readdirSync3(dir).filter((f) => f.endsWith(".tsx") || f.endsWith(".jsx")).length;
9927
+ n += readdirSync4(dir).filter((f) => f.endsWith(".tsx") || f.endsWith(".jsx")).length;
9729
9928
  } catch {
9730
9929
  }
9731
9930
  }
@@ -9738,7 +9937,7 @@ async function collectImportedPackages2(dir, extensions) {
9738
9937
  async function walk(d) {
9739
9938
  const entries = await readdir3(d, { withFileTypes: true });
9740
9939
  for (const e of entries) {
9741
- const full = join12(d, e.name);
9940
+ const full = join13(d, e.name);
9742
9941
  if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules") {
9743
9942
  await walk(full);
9744
9943
  continue;
@@ -9746,7 +9945,7 @@ async function collectImportedPackages2(dir, extensions) {
9746
9945
  if (!e.isFile()) continue;
9747
9946
  const ext = e.name.replace(/^.*\./, "");
9748
9947
  if (!extensions.has(ext)) continue;
9749
- const content = await readFile5(full, "utf-8").catch(() => "");
9948
+ const content = await readFile6(full, "utf-8").catch(() => "");
9750
9949
  let m;
9751
9950
  IMPORT_FROM_REGEX2.lastIndex = 0;
9752
9951
  while ((m = IMPORT_FROM_REGEX2.exec(content)) !== null) {
@@ -9761,16 +9960,16 @@ async function collectImportedPackages2(dir, extensions) {
9761
9960
  return packages;
9762
9961
  }
9763
9962
  async function findMissingDepsInExport(outRoot) {
9764
- const pkgPath = join12(outRoot, "package.json");
9963
+ const pkgPath = join13(outRoot, "package.json");
9765
9964
  if (!existsSync20(pkgPath)) return [];
9766
9965
  let pkg;
9767
9966
  try {
9768
- pkg = JSON.parse(await readFile5(pkgPath, "utf-8"));
9967
+ pkg = JSON.parse(await readFile6(pkgPath, "utf-8"));
9769
9968
  } catch {
9770
9969
  return [];
9771
9970
  }
9772
9971
  const inDeps = /* @__PURE__ */ new Set([...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})]);
9773
- const codeDirs = [join12(outRoot, "app"), join12(outRoot, "components")];
9972
+ const codeDirs = [join13(outRoot, "app"), join13(outRoot, "components")];
9774
9973
  const extensions = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx"]);
9775
9974
  const imported = /* @__PURE__ */ new Set();
9776
9975
  for (const dir of codeDirs) {
@@ -9782,35 +9981,35 @@ async function findMissingDepsInExport(outRoot) {
9782
9981
  async function stripCoherentArtifacts(outputDir) {
9783
9982
  const removed = [];
9784
9983
  for (const p of ["app/design-system", "app/api/design-system"]) {
9785
- const full = join12(outputDir, p);
9984
+ const full = join13(outputDir, p);
9786
9985
  if (existsSync20(full)) {
9787
9986
  rmSync4(full, { recursive: true, force: true });
9788
9987
  removed.push(p);
9789
9988
  }
9790
9989
  }
9791
- const appNavPath = join12(outputDir, "app", "AppNav.tsx");
9990
+ const appNavPath = join13(outputDir, "app", "AppNav.tsx");
9792
9991
  if (existsSync20(appNavPath)) {
9793
9992
  rmSync4(appNavPath, { force: true });
9794
9993
  removed.push("app/AppNav.tsx");
9795
9994
  }
9796
- const layoutPath = join12(outputDir, "app", "layout.tsx");
9995
+ const layoutPath = join13(outputDir, "app", "layout.tsx");
9797
9996
  if (existsSync20(layoutPath)) {
9798
- let layout = await readFile5(layoutPath, "utf-8");
9997
+ let layout = await readFile6(layoutPath, "utf-8");
9799
9998
  layout = layout.replace(/import\s*\{?\s*AppNav\s*\}?\s*from\s*['"][^'"]+['"]\s*\n?/g, "");
9800
9999
  layout = layout.replace(/\s*<AppNav\s*\/?\s*>\s*/g, "\n");
9801
- await writeFile4(layoutPath, layout, "utf-8");
10000
+ await writeFile5(layoutPath, layout, "utf-8");
9802
10001
  }
9803
- const sharedHeaderPath = join12(outputDir, "components", "shared", "header.tsx");
10002
+ const sharedHeaderPath = join13(outputDir, "components", "shared", "header.tsx");
9804
10003
  if (existsSync20(sharedHeaderPath)) {
9805
- let header = await readFile5(sharedHeaderPath, "utf-8");
10004
+ let header = await readFile6(sharedHeaderPath, "utf-8");
9806
10005
  header = header.replace(/<Link\s[^>]*href="\/design-system"[^>]*>[\s\S]*?<\/Link>/g, "");
9807
10006
  header = header.replace(/\n\s*<>\s*\n/, "\n");
9808
10007
  header = header.replace(/\n\s*<\/>\s*\n/, "\n");
9809
- await writeFile4(sharedHeaderPath, header, "utf-8");
10008
+ await writeFile5(sharedHeaderPath, header, "utf-8");
9810
10009
  }
9811
- const guardPath = join12(outputDir, "app", "ShowWhenNotAuthRoute.tsx");
10010
+ const guardPath = join13(outputDir, "app", "ShowWhenNotAuthRoute.tsx");
9812
10011
  if (existsSync20(guardPath)) {
9813
- let guard = await readFile5(guardPath, "utf-8");
10012
+ let guard = await readFile6(guardPath, "utf-8");
9814
10013
  guard = guard.replace(/['"],?\s*'\/design-system['"],?\s*/g, "");
9815
10014
  const pathsMatch = guard.match(/HIDDEN_PATHS\s*=\s*\[([^\]]*)\]/);
9816
10015
  const remaining = pathsMatch ? pathsMatch[1].replace(/['"]/g, "").split(",").map((s) => s.trim()).filter(Boolean) : [];
@@ -9818,14 +10017,14 @@ async function stripCoherentArtifacts(outputDir) {
9818
10017
  rmSync4(guardPath, { force: true });
9819
10018
  removed.push("app/ShowWhenNotAuthRoute.tsx");
9820
10019
  if (existsSync20(layoutPath)) {
9821
- let layout = await readFile5(layoutPath, "utf-8");
10020
+ let layout = await readFile6(layoutPath, "utf-8");
9822
10021
  layout = layout.replace(/import\s+\w+\s+from\s*['"]\.\/ShowWhenNotAuthRoute['"]\s*\n?/g, "");
9823
10022
  layout = layout.replace(/\s*<ShowWhenNotAuthRoute>\s*\n?/g, "\n");
9824
10023
  layout = layout.replace(/\s*<\/ShowWhenNotAuthRoute>\s*\n?/g, "\n");
9825
- await writeFile4(layoutPath, layout, "utf-8");
10024
+ await writeFile5(layoutPath, layout, "utf-8");
9826
10025
  }
9827
10026
  } else {
9828
- await writeFile4(guardPath, guard, "utf-8");
10027
+ await writeFile5(guardPath, guard, "utf-8");
9829
10028
  }
9830
10029
  }
9831
10030
  for (const name of [
@@ -9837,14 +10036,14 @@ async function stripCoherentArtifacts(outputDir) {
9837
10036
  ".env.local",
9838
10037
  "recommendations.md"
9839
10038
  ]) {
9840
- const full = join12(outputDir, name);
10039
+ const full = join13(outputDir, name);
9841
10040
  if (existsSync20(full)) {
9842
10041
  rmSync4(full, { force: true });
9843
10042
  removed.push(name);
9844
10043
  }
9845
10044
  }
9846
10045
  for (const dir of [".claude", ".coherent"]) {
9847
- const full = join12(outputDir, dir);
10046
+ const full = join13(outputDir, dir);
9848
10047
  if (existsSync20(full)) {
9849
10048
  rmSync4(full, { recursive: true, force: true });
9850
10049
  removed.push(dir + "/");
@@ -10044,8 +10243,8 @@ async function regenerateDocsCommand() {
10044
10243
 
10045
10244
  // src/commands/fix.ts
10046
10245
  import chalk18 from "chalk";
10047
- import { readdirSync as readdirSync4, readFileSync as readFileSync14, existsSync as existsSync21, writeFileSync as writeFileSync11, rmSync as rmSync5, mkdirSync as mkdirSync7 } from "fs";
10048
- import { resolve as resolve12, join as join13 } from "path";
10246
+ import { readdirSync as readdirSync5, readFileSync as readFileSync14, existsSync as existsSync21, writeFileSync as writeFileSync11, rmSync as rmSync5, mkdirSync as mkdirSync7 } from "fs";
10247
+ import { resolve as resolve12, join as join14 } from "path";
10049
10248
  import {
10050
10249
  DesignSystemManager as DesignSystemManager11,
10051
10250
  ComponentManager as ComponentManager5,
@@ -10069,9 +10268,9 @@ function extractComponentIdsFromCode2(code) {
10069
10268
  function listTsxFiles(dir) {
10070
10269
  const files = [];
10071
10270
  try {
10072
- const entries = readdirSync4(dir, { withFileTypes: true });
10271
+ const entries = readdirSync5(dir, { withFileTypes: true });
10073
10272
  for (const e of entries) {
10074
- const full = join13(dir, e.name);
10273
+ const full = join14(dir, e.name);
10075
10274
  if (e.isDirectory() && e.name !== "node_modules" && !e.name.startsWith(".")) {
10076
10275
  files.push(...listTsxFiles(full));
10077
10276
  } else if (e.isFile() && e.name.endsWith(".tsx")) {
@@ -10100,7 +10299,7 @@ async function fixCommand(opts = {}) {
10100
10299
  console.log(chalk18.cyan("\ncoherent fix\n"));
10101
10300
  }
10102
10301
  if (!skipCache) {
10103
- const nextDir = join13(projectRoot, ".next");
10302
+ const nextDir = join14(projectRoot, ".next");
10104
10303
  if (existsSync21(nextDir)) {
10105
10304
  if (!dryRun) rmSync5(nextDir, { recursive: true, force: true });
10106
10305
  fixes.push("Cleared build cache");
@@ -10357,13 +10556,13 @@ async function fixCommand(opts = {}) {
10357
10556
  // src/commands/check.ts
10358
10557
  import chalk19 from "chalk";
10359
10558
  import { resolve as resolve13 } from "path";
10360
- import { readdirSync as readdirSync5, readFileSync as readFileSync15, statSync as statSync2, existsSync as existsSync22 } from "fs";
10559
+ import { readdirSync as readdirSync6, readFileSync as readFileSync15, statSync as statSync2, existsSync as existsSync22 } from "fs";
10361
10560
  import { loadManifest as loadManifest10 } from "@getcoherent/core";
10362
10561
  var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", "design-system"]);
10363
10562
  function findTsxFiles(dir) {
10364
10563
  const results = [];
10365
10564
  try {
10366
- const entries = readdirSync5(dir);
10565
+ const entries = readdirSync6(dir);
10367
10566
  for (const entry of entries) {
10368
10567
  const full = resolve13(dir, entry);
10369
10568
  const stat = statSync2(full);
@@ -10691,8 +10890,8 @@ import { existsSync as existsSync23 } from "fs";
10691
10890
  import { resolve as resolve14 } from "path";
10692
10891
 
10693
10892
  // src/utils/ds-files.ts
10694
- import { mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
10695
- import { join as join14, dirname as dirname8 } from "path";
10893
+ import { mkdir as mkdir6, writeFile as writeFile6 } from "fs/promises";
10894
+ import { join as join15, dirname as dirname8 } from "path";
10696
10895
  import { DesignSystemGenerator } from "@getcoherent/core";
10697
10896
  var SHARED_DS_KEYS = [
10698
10897
  "app/design-system/shared/page.tsx",
@@ -10706,9 +10905,9 @@ async function writeDesignSystemFiles(projectRoot, config2, options) {
10706
10905
  const toWrite = options?.sharedOnly ? new Map([...files].filter(([path3]) => SHARED_DS_KEYS.includes(path3))) : files;
10707
10906
  const written = [];
10708
10907
  for (const [relativePath, content] of toWrite) {
10709
- const fullPath = join14(projectRoot, relativePath);
10710
- await mkdir5(dirname8(fullPath), { recursive: true });
10711
- await writeFile5(fullPath, content, "utf-8");
10908
+ const fullPath = join15(projectRoot, relativePath);
10909
+ await mkdir6(dirname8(fullPath), { recursive: true });
10910
+ await writeFile6(fullPath, content, "utf-8");
10712
10911
  written.push(relativePath);
10713
10912
  }
10714
10913
  return written;
@@ -10844,8 +11043,8 @@ function createComponentsCommand() {
10844
11043
  // src/commands/import-cmd.ts
10845
11044
  import chalk26 from "chalk";
10846
11045
  import ora6 from "ora";
10847
- import { writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
10848
- import { resolve as resolve15, join as join15, dirname as dirname9 } from "path";
11046
+ import { writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
11047
+ import { resolve as resolve15, join as join16, dirname as dirname9 } from "path";
10849
11048
  import { existsSync as existsSync24 } from "fs";
10850
11049
  import {
10851
11050
  FigmaClient,
@@ -10993,9 +11192,9 @@ async function importFigmaAction(urlOrKey, opts) {
10993
11192
  stats.filesWritten.push(filePath);
10994
11193
  return;
10995
11194
  }
10996
- const fullPath = join15(projectRoot, filePath);
10997
- await mkdir6(dirname9(fullPath), { recursive: true });
10998
- await writeFile6(fullPath, content, "utf-8");
11195
+ const fullPath = join16(projectRoot, filePath);
11196
+ await mkdir7(dirname9(fullPath), { recursive: true });
11197
+ await writeFile7(fullPath, content, "utf-8");
10999
11198
  stats.filesWritten.push(filePath);
11000
11199
  };
11001
11200
  try {
@@ -11069,7 +11268,7 @@ async function importFigmaAction(urlOrKey, opts) {
11069
11268
  });
11070
11269
  if (dryRun) stats.filesWritten.push(FIGMA_COMPONENT_MAP_FILENAME);
11071
11270
  else
11072
- await writeFile6(
11271
+ await writeFile7(
11073
11272
  resolve15(projectRoot, FIGMA_COMPONENT_MAP_FILENAME),
11074
11273
  JSON.stringify(componentMapObj, null, 2),
11075
11274
  "utf-8"
@@ -11109,7 +11308,7 @@ async function importFigmaAction(urlOrKey, opts) {
11109
11308
  });
11110
11309
  await dsm.save();
11111
11310
  } else {
11112
- await writeFile6(
11311
+ await writeFile7(
11113
11312
  configPath,
11114
11313
  `/**
11115
11314
  * Design System Configuration
@@ -11124,10 +11323,10 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
11124
11323
  stats.configUpdated = true;
11125
11324
  spinner.succeed("design-system.config.ts updated");
11126
11325
  spinner.start("Ensuring root layout...");
11127
- const layoutPath = join15(projectRoot, "app/layout.tsx");
11326
+ const layoutPath = join16(projectRoot, "app/layout.tsx");
11128
11327
  if (!existsSync24(layoutPath)) {
11129
- await mkdir6(dirname9(layoutPath), { recursive: true });
11130
- await writeFile6(layoutPath, MINIMAL_ROOT_LAYOUT, "utf-8");
11328
+ await mkdir7(dirname9(layoutPath), { recursive: true });
11329
+ await writeFile7(layoutPath, MINIMAL_ROOT_LAYOUT, "utf-8");
11131
11330
  stats.filesWritten.push("app/layout.tsx");
11132
11331
  }
11133
11332
  spinner.succeed("Root layout OK");
@@ -11216,7 +11415,7 @@ async function dsRegenerateCommand() {
11216
11415
  import chalk28 from "chalk";
11217
11416
  import ora8 from "ora";
11218
11417
  import { readFileSync as readFileSync16, existsSync as existsSync25 } from "fs";
11219
- import { join as join16 } from "path";
11418
+ import { join as join17 } from "path";
11220
11419
  import { DesignSystemManager as DesignSystemManager15, CLI_VERSION as CLI_VERSION4 } from "@getcoherent/core";
11221
11420
 
11222
11421
  // src/utils/migrations.ts
@@ -11385,7 +11584,7 @@ var EXPECTED_CSS_VARS = [
11385
11584
  "--sidebar-ring"
11386
11585
  ];
11387
11586
  function checkMissingCssVars(projectRoot) {
11388
- const globalsPath = join16(projectRoot, "app", "globals.css");
11587
+ const globalsPath = join17(projectRoot, "app", "globals.css");
11389
11588
  if (!existsSync25(globalsPath)) return [];
11390
11589
  try {
11391
11590
  const content = readFileSync16(globalsPath, "utf-8");
@@ -11395,7 +11594,7 @@ function checkMissingCssVars(projectRoot) {
11395
11594
  }
11396
11595
  }
11397
11596
  function patchGlobalsCss(projectRoot, missingVars) {
11398
- const globalsPath = join16(projectRoot, "app", "globals.css");
11597
+ const globalsPath = join17(projectRoot, "app", "globals.css");
11399
11598
  if (!existsSync25(globalsPath) || missingVars.length === 0) return;
11400
11599
  const { writeFileSync: writeFileSync13 } = __require("fs");
11401
11600
  let content = readFileSync16(globalsPath, "utf-8");
@@ -11477,14 +11676,14 @@ async function undoCommand(options) {
11477
11676
  import chalk30 from "chalk";
11478
11677
  import ora9 from "ora";
11479
11678
  import { existsSync as existsSync26, readFileSync as readFileSync17 } from "fs";
11480
- import { join as join17, relative as relative4, dirname as dirname10 } from "path";
11481
- import { readdir as readdir4, readFile as readFile6 } from "fs/promises";
11679
+ import { join as join18, relative as relative5, dirname as dirname10 } from "path";
11680
+ import { readdir as readdir4, readFile as readFile7 } from "fs/promises";
11482
11681
  import { DesignSystemManager as DesignSystemManager16 } from "@getcoherent/core";
11483
11682
  import { loadManifest as loadManifest12, saveManifest as saveManifest5, findSharedComponent } from "@getcoherent/core";
11484
11683
  function extractTokensFromProject(projectRoot) {
11485
11684
  const lightColors = {};
11486
11685
  const darkColors = {};
11487
- const globalsPath = join17(projectRoot, "app", "globals.css");
11686
+ const globalsPath = join18(projectRoot, "app", "globals.css");
11488
11687
  if (existsSync26(globalsPath)) {
11489
11688
  const css = readFileSync17(globalsPath, "utf-8");
11490
11689
  const rootMatch = css.match(/:root\s*\{([^}]+)\}/s);
@@ -11492,7 +11691,7 @@ function extractTokensFromProject(projectRoot) {
11492
11691
  const darkMatch = css.match(/\.dark\s*\{([^}]+)\}/s);
11493
11692
  if (darkMatch) parseVarsInto(darkMatch[1], darkColors);
11494
11693
  }
11495
- const layoutPath = join17(projectRoot, "app", "layout.tsx");
11694
+ const layoutPath = join18(projectRoot, "app", "layout.tsx");
11496
11695
  let layoutCode = "";
11497
11696
  if (existsSync26(layoutPath)) {
11498
11697
  layoutCode = readFileSync17(layoutPath, "utf-8");
@@ -11536,14 +11735,14 @@ function parseVarsInto(block, target) {
11536
11735
  }
11537
11736
  async function detectCustomComponents(projectRoot, allPageCode) {
11538
11737
  const results = [];
11539
- const componentsDir = join17(projectRoot, "components");
11738
+ const componentsDir = join18(projectRoot, "components");
11540
11739
  if (!existsSync26(componentsDir)) return results;
11541
11740
  const files = [];
11542
11741
  await walkForTsx(componentsDir, files, ["ui"]);
11543
11742
  const fileResults = await Promise.all(
11544
11743
  files.map(async (filePath) => {
11545
- const code = await readFile6(filePath, "utf-8");
11546
- const relFile = relative4(projectRoot, filePath);
11744
+ const code = await readFile7(filePath, "utf-8");
11745
+ const relFile = relative5(projectRoot, filePath);
11547
11746
  const exportedNames = extractExportedComponentNames2(code);
11548
11747
  return exportedNames.map((name) => ({
11549
11748
  name,
@@ -11564,7 +11763,7 @@ async function walkForTsx(dir, files, skipDirs) {
11564
11763
  return;
11565
11764
  }
11566
11765
  for (const e of entries) {
11567
- const full = join17(dir, e.name);
11766
+ const full = join18(dir, e.name);
11568
11767
  if (e.isDirectory()) {
11569
11768
  if (skipDirs.includes(e.name) || e.name.startsWith(".")) continue;
11570
11769
  await walkForTsx(full, files, skipDirs);
@@ -11638,14 +11837,14 @@ async function discoverPages(appDir) {
11638
11837
  return;
11639
11838
  }
11640
11839
  for (const entry of entries) {
11641
- const full = join17(dir, entry.name);
11840
+ const full = join18(dir, entry.name);
11642
11841
  if (entry.isDirectory()) {
11643
11842
  if (["design-system", "api", "_not-found"].includes(entry.name)) continue;
11644
11843
  if (entry.name.startsWith(".")) continue;
11645
11844
  await walk(full);
11646
11845
  } else if (entry.name === "page.tsx" || entry.name === "page.jsx") {
11647
- const code = await readFile6(full, "utf-8");
11648
- const routeDir = dirname10(relative4(appDir, full));
11846
+ const code = await readFile7(full, "utf-8");
11847
+ const routeDir = dirname10(relative5(appDir, full));
11649
11848
  let route = routeDir === "." ? "/" : "/" + routeDir;
11650
11849
  route = route.replace(/\/\([^)]+\)/g, "");
11651
11850
  if (!route.startsWith("/")) route = "/" + route;
@@ -11715,7 +11914,7 @@ async function syncCommand(options = {}) {
11715
11914
  if (dryRun) console.log(chalk30.yellow(" [dry-run] No files will be written\n"));
11716
11915
  const spinner = ora9("Scanning project files...").start();
11717
11916
  try {
11718
- const appDir = join17(project.root, "app");
11917
+ const appDir = join18(project.root, "app");
11719
11918
  if (!existsSync26(appDir)) {
11720
11919
  spinner.fail("No app/ directory found");
11721
11920
  process.exit(1);
@@ -11942,14 +12141,14 @@ async function syncCommand(options = {}) {
11942
12141
 
11943
12142
  // src/utils/update-notifier.ts
11944
12143
  import { existsSync as existsSync27, mkdirSync as mkdirSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "fs";
11945
- import { join as join18 } from "path";
12144
+ import { join as join19 } from "path";
11946
12145
  import { homedir } from "os";
11947
12146
  import chalk31 from "chalk";
11948
12147
  import { CLI_VERSION as CLI_VERSION5 } from "@getcoherent/core";
11949
12148
  var DEBUG5 = process.env.COHERENT_DEBUG === "1";
11950
12149
  var PACKAGE_NAME = "@getcoherent/cli";
11951
- var CACHE_DIR = join18(homedir(), ".coherent");
11952
- var CACHE_FILE = join18(CACHE_DIR, "update-check.json");
12150
+ var CACHE_DIR = join19(homedir(), ".coherent");
12151
+ var CACHE_FILE = join19(CACHE_DIR, "update-check.json");
11953
12152
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
11954
12153
  function readCache() {
11955
12154
  try {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "0.5.1",
6
+ "version": "0.5.3",
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.5.1"
46
+ "@getcoherent/core": "0.5.3"
47
47
  },
48
48
  "devDependencies": {
49
49
  "@types/node": "^20.11.0",