@getcoherent/cli 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +371 -504
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -601,7 +601,7 @@ function installPackages(projectRoot, packages) {
|
|
|
601
601
|
if (packages.length === 0) return Promise.resolve(true);
|
|
602
602
|
const safe = packages.filter((p) => SAFE_PKG_NAME.test(p));
|
|
603
603
|
if (safe.length === 0) return Promise.resolve(true);
|
|
604
|
-
return new Promise((
|
|
604
|
+
return new Promise((resolve16) => {
|
|
605
605
|
try {
|
|
606
606
|
const hasPnpm = existsSync4(join2(projectRoot, "pnpm-lock.yaml"));
|
|
607
607
|
if (hasPnpm) {
|
|
@@ -612,10 +612,10 @@ function installPackages(projectRoot, packages) {
|
|
|
612
612
|
stdio: "pipe"
|
|
613
613
|
});
|
|
614
614
|
}
|
|
615
|
-
|
|
615
|
+
resolve16(true);
|
|
616
616
|
} catch (e) {
|
|
617
617
|
if (process.env.COHERENT_DEBUG === "1") console.error("Failed to install packages:", e);
|
|
618
|
-
|
|
618
|
+
resolve16(false);
|
|
619
619
|
}
|
|
620
620
|
});
|
|
621
621
|
}
|
|
@@ -1974,7 +1974,7 @@ Run in terminal:
|
|
|
1974
1974
|
## Platform Overlay (DO NOT TOUCH)
|
|
1975
1975
|
|
|
1976
1976
|
The following are dev-only platform features, excluded from production export:
|
|
1977
|
-
- Floating Design System button (in
|
|
1977
|
+
- Floating Design System button (in shared Header component)
|
|
1978
1978
|
- /design-system/* pages
|
|
1979
1979
|
- /api/design-system/* routes
|
|
1980
1980
|
- coherent.components.json
|
|
@@ -2425,8 +2425,8 @@ export default config
|
|
|
2425
2425
|
// src/commands/chat.ts
|
|
2426
2426
|
import chalk13 from "chalk";
|
|
2427
2427
|
import ora2 from "ora";
|
|
2428
|
-
import { resolve as
|
|
2429
|
-
import { existsSync as existsSync16, readFileSync as
|
|
2428
|
+
import { resolve as resolve9 } from "path";
|
|
2429
|
+
import { existsSync as existsSync16, readFileSync as readFileSync10, mkdirSync as mkdirSync6 } from "fs";
|
|
2430
2430
|
import {
|
|
2431
2431
|
DesignSystemManager as DesignSystemManager7,
|
|
2432
2432
|
ComponentManager as ComponentManager4,
|
|
@@ -4480,6 +4480,14 @@ Format:
|
|
|
4480
4480
|
"pageCode": "import { Metadata } from 'next'\\n..."
|
|
4481
4481
|
}
|
|
4482
4482
|
|
|
4483
|
+
LAYOUT CONTRACT (CRITICAL \u2014 prevents duplicate navigation and footer):
|
|
4484
|
+
- The app has a root layout (app/layout.tsx) that renders a shared Header and Footer.
|
|
4485
|
+
- Pages are rendered INSIDE this layout, between the Header and Footer.
|
|
4486
|
+
- NEVER include <header>, <nav>, or <footer> elements in pageCode.
|
|
4487
|
+
- Start page content with <main> or a wrapper <div>. The first visible element should be the page title or hero section.
|
|
4488
|
+
- If the page needs sub-navigation (tabs, breadcrumbs, sidebar nav), use elements like <div role="tablist"> or <aside> \u2014 NOT <header>, <nav>, or <footer>.
|
|
4489
|
+
- Do NOT add any navigation bars, logo headers, site-wide menus, or site footers to pages. The layout provides all of these.
|
|
4490
|
+
|
|
4483
4491
|
pageCode rules (shadcn/ui blocks quality):
|
|
4484
4492
|
- Full Next.js App Router page. Imports from '@/components/ui/...' for registry components.
|
|
4485
4493
|
- Follow ALL design constraints above: text-sm base, semantic colors only, restricted spacing, weight-based hierarchy.
|
|
@@ -5849,14 +5857,14 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
5849
5857
|
import { resolve as resolve7 } from "path";
|
|
5850
5858
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
5851
5859
|
import { dirname as dirname6 } from "path";
|
|
5852
|
-
import
|
|
5860
|
+
import chalk11 from "chalk";
|
|
5853
5861
|
import {
|
|
5854
5862
|
getTemplateForPageType,
|
|
5855
|
-
loadManifest as
|
|
5863
|
+
loadManifest as loadManifest6,
|
|
5856
5864
|
saveManifest,
|
|
5857
5865
|
updateUsedIn,
|
|
5858
5866
|
findSharedComponentByIdOrName,
|
|
5859
|
-
generateSharedComponent as
|
|
5867
|
+
generateSharedComponent as generateSharedComponent3
|
|
5860
5868
|
} from "@getcoherent/core";
|
|
5861
5869
|
|
|
5862
5870
|
// src/utils/quality-validator.ts
|
|
@@ -6530,36 +6538,41 @@ ${fixed}`;
|
|
|
6530
6538
|
} catch {
|
|
6531
6539
|
}
|
|
6532
6540
|
if (lucideExports2) {
|
|
6533
|
-
const
|
|
6541
|
+
const allImportedNames = /* @__PURE__ */ new Set();
|
|
6542
|
+
for (const m of fixed.matchAll(/import\s*\{([^}]+)\}\s*from/g)) {
|
|
6543
|
+
m[1].split(",").map((s) => s.trim()).filter(Boolean).forEach((n) => allImportedNames.add(n));
|
|
6544
|
+
}
|
|
6545
|
+
for (const m of fixed.matchAll(/import\s+([A-Z]\w+)\s+from/g)) {
|
|
6546
|
+
allImportedNames.add(m[1]);
|
|
6547
|
+
}
|
|
6548
|
+
const lucideImported = new Set(
|
|
6534
6549
|
lucideImportMatch2[1].split(",").map((s) => s.trim()).filter(Boolean)
|
|
6535
6550
|
);
|
|
6536
|
-
const jsxIconRefs = [...fixed.matchAll(/<([A-Z][a-zA-Z]*
|
|
6537
|
-
const htmlElements = /* @__PURE__ */ new Set(["Link", "Fragment", "Suspense", "Image"]);
|
|
6551
|
+
const jsxIconRefs = [...new Set([...fixed.matchAll(/<([A-Z][a-zA-Z]*Icon)\s/g)].map((m) => m[1]))];
|
|
6538
6552
|
const missing = [];
|
|
6539
6553
|
for (const ref of jsxIconRefs) {
|
|
6540
|
-
if (
|
|
6554
|
+
if (allImportedNames.has(ref)) continue;
|
|
6541
6555
|
if (fixed.includes(`function ${ref}`) || fixed.includes(`const ${ref}`)) continue;
|
|
6542
|
-
if (/^import\s.*\b${ref}\b/m.test(fixed)) continue;
|
|
6543
6556
|
const baseName = ref.replace(/Icon$/, "");
|
|
6544
6557
|
if (lucideExports2.has(ref)) {
|
|
6545
6558
|
missing.push(ref);
|
|
6546
|
-
|
|
6559
|
+
lucideImported.add(ref);
|
|
6547
6560
|
} else if (lucideExports2.has(baseName)) {
|
|
6548
6561
|
const re = new RegExp(`\\b${ref}\\b`, "g");
|
|
6549
6562
|
fixed = fixed.replace(re, baseName);
|
|
6550
6563
|
missing.push(baseName);
|
|
6551
|
-
|
|
6564
|
+
lucideImported.add(baseName);
|
|
6552
6565
|
fixes.push(`renamed ${ref} \u2192 ${baseName} (lucide-react)`);
|
|
6553
6566
|
} else {
|
|
6554
6567
|
const fallback = "Circle";
|
|
6555
6568
|
const re = new RegExp(`\\b${ref}\\b`, "g");
|
|
6556
6569
|
fixed = fixed.replace(re, fallback);
|
|
6557
|
-
|
|
6570
|
+
lucideImported.add(fallback);
|
|
6558
6571
|
fixes.push(`unknown icon ${ref} \u2192 ${fallback}`);
|
|
6559
6572
|
}
|
|
6560
6573
|
}
|
|
6561
6574
|
if (missing.length > 0) {
|
|
6562
|
-
const allNames = [...
|
|
6575
|
+
const allNames = [...lucideImported];
|
|
6563
6576
|
const origLine = lucideImportMatch2[0];
|
|
6564
6577
|
fixed = fixed.replace(origLine, `import { ${allNames.join(", ")} } from "lucide-react"`);
|
|
6565
6578
|
fixes.push(`added missing lucide imports: ${missing.join(", ")}`);
|
|
@@ -6609,7 +6622,8 @@ import {
|
|
|
6609
6622
|
PageGenerator,
|
|
6610
6623
|
TailwindConfigGenerator
|
|
6611
6624
|
} from "@getcoherent/core";
|
|
6612
|
-
import { integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout2 } from "@getcoherent/core";
|
|
6625
|
+
import { integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout2, loadManifest as loadManifest5, generateSharedComponent as generateSharedComponent2 } from "@getcoherent/core";
|
|
6626
|
+
import chalk9 from "chalk";
|
|
6613
6627
|
async function validateAndFixGeneratedCode(projectRoot, code, options = {}) {
|
|
6614
6628
|
const fixes = [];
|
|
6615
6629
|
let fixed = fixEscapedClosingQuotes(code);
|
|
@@ -6679,18 +6693,49 @@ async function regenerateLayout(config2, projectRoot) {
|
|
|
6679
6693
|
const layout = config2.pages[0]?.layout || "centered";
|
|
6680
6694
|
const appType = config2.settings.appType || "multi-page";
|
|
6681
6695
|
const generator = new PageGenerator(config2);
|
|
6682
|
-
|
|
6683
|
-
|
|
6684
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
6688
|
-
|
|
6696
|
+
let manifest = null;
|
|
6697
|
+
try {
|
|
6698
|
+
manifest = await loadManifest5(projectRoot);
|
|
6699
|
+
} catch {
|
|
6700
|
+
}
|
|
6701
|
+
const hasSharedHeader = manifest?.shared.some((c) => c.type === "layout" && /header|nav/i.test(c.name)) ?? false;
|
|
6702
|
+
const hasSharedFooter = manifest?.shared.some((c) => c.type === "layout" && /footer/i.test(c.name)) ?? false;
|
|
6703
|
+
if (hasSharedHeader) {
|
|
6704
|
+
const code = await generator.generateLayout(layout, appType, { skipNav: true });
|
|
6705
|
+
const layoutPath = resolve6(projectRoot, "app", "layout.tsx");
|
|
6706
|
+
await writeFile(layoutPath, code);
|
|
6707
|
+
} else {
|
|
6708
|
+
const code = await generator.generateLayout(layout, appType, { skipNav: true });
|
|
6709
|
+
const layoutPath = resolve6(projectRoot, "app", "layout.tsx");
|
|
6710
|
+
await writeFile(layoutPath, code);
|
|
6711
|
+
if (config2.navigation?.enabled && appType === "multi-page") {
|
|
6712
|
+
const headerCode = generator.generateSharedHeaderCode();
|
|
6713
|
+
await generateSharedComponent2(projectRoot, {
|
|
6714
|
+
name: "Header",
|
|
6715
|
+
type: "layout",
|
|
6716
|
+
code: headerCode,
|
|
6717
|
+
description: "Main site header with navigation and theme toggle",
|
|
6718
|
+
usedIn: ["app/layout.tsx"]
|
|
6719
|
+
});
|
|
6720
|
+
if (!hasSharedFooter) {
|
|
6721
|
+
const footerCode = generator.generateSharedFooterCode();
|
|
6722
|
+
await generateSharedComponent2(projectRoot, {
|
|
6723
|
+
name: "Footer",
|
|
6724
|
+
type: "layout",
|
|
6725
|
+
code: footerCode,
|
|
6726
|
+
description: "Site footer",
|
|
6727
|
+
usedIn: ["app/layout.tsx"]
|
|
6728
|
+
});
|
|
6729
|
+
}
|
|
6730
|
+
}
|
|
6689
6731
|
}
|
|
6690
6732
|
try {
|
|
6691
6733
|
await integrateSharedLayoutIntoRootLayout2(projectRoot);
|
|
6692
6734
|
await ensureAuthRouteGroup(projectRoot);
|
|
6693
|
-
} catch {
|
|
6735
|
+
} catch (err) {
|
|
6736
|
+
if (process.env.COHERENT_DEBUG === "1") {
|
|
6737
|
+
console.log(chalk9.dim("Layout integration warning:", err));
|
|
6738
|
+
}
|
|
6694
6739
|
}
|
|
6695
6740
|
}
|
|
6696
6741
|
async function regenerateFiles(modified, config2, projectRoot) {
|
|
@@ -6728,8 +6773,39 @@ async function regenerateFiles(modified, config2, projectRoot) {
|
|
|
6728
6773
|
}
|
|
6729
6774
|
}
|
|
6730
6775
|
|
|
6776
|
+
// src/commands/chat/jsx-extractor.ts
|
|
6777
|
+
function extractBalancedTag(source, tagName) {
|
|
6778
|
+
const openRe = new RegExp(`<${tagName}\\b`, "gi");
|
|
6779
|
+
const match = openRe.exec(source);
|
|
6780
|
+
if (!match) return null;
|
|
6781
|
+
const startIdx = match.index;
|
|
6782
|
+
const openTagRe = new RegExp(`<${tagName}\\b`, "gi");
|
|
6783
|
+
const closeTagRe = new RegExp(`</${tagName}>`, "gi");
|
|
6784
|
+
const events = [];
|
|
6785
|
+
let m;
|
|
6786
|
+
openTagRe.lastIndex = startIdx;
|
|
6787
|
+
while ((m = openTagRe.exec(source)) !== null) {
|
|
6788
|
+
events.push({ pos: m.index, type: "open", end: m.index + m[0].length });
|
|
6789
|
+
}
|
|
6790
|
+
closeTagRe.lastIndex = startIdx;
|
|
6791
|
+
while ((m = closeTagRe.exec(source)) !== null) {
|
|
6792
|
+
events.push({ pos: m.index, type: "close", end: m.index + m[0].length });
|
|
6793
|
+
}
|
|
6794
|
+
events.sort((a, b) => a.pos - b.pos);
|
|
6795
|
+
let depth = 0;
|
|
6796
|
+
for (const ev of events) {
|
|
6797
|
+
if (ev.pos < startIdx) continue;
|
|
6798
|
+
if (ev.type === "open") depth++;
|
|
6799
|
+
else {
|
|
6800
|
+
depth--;
|
|
6801
|
+
if (depth === 0) return source.slice(startIdx, ev.end);
|
|
6802
|
+
}
|
|
6803
|
+
}
|
|
6804
|
+
return null;
|
|
6805
|
+
}
|
|
6806
|
+
|
|
6731
6807
|
// src/commands/chat/reporting.ts
|
|
6732
|
-
import
|
|
6808
|
+
import chalk10 from "chalk";
|
|
6733
6809
|
function extractImportsFrom(code, fromPath) {
|
|
6734
6810
|
const escaped = fromPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6735
6811
|
const regex = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*['"\`]${escaped}[^'"\`]*['"\`]`, "g");
|
|
@@ -6751,27 +6827,27 @@ function printPostGenerationReport(opts) {
|
|
|
6751
6827
|
const iconCount = extractImportsFrom(code, "lucide-react").length;
|
|
6752
6828
|
const hasInstalled = postFixes.some((f) => f.startsWith("Installed:"));
|
|
6753
6829
|
const syntaxStatus = postFixes.length > 0 ? postFixes.some((f) => f.includes("metadata")) ? "fixed (escaped metadata quotes) \u2714" : "fixed \u2714" : "valid \u2714";
|
|
6754
|
-
console.log(
|
|
6830
|
+
console.log(chalk10.green(`
|
|
6755
6831
|
\u2705 Page "${pageTitle}" ${action} at ${filePath}
|
|
6756
6832
|
`));
|
|
6757
6833
|
if (uiComponents.length > 0) {
|
|
6758
|
-
console.log(
|
|
6834
|
+
console.log(chalk10.dim(` Components: ${uiComponents.join(", ")} (from @/components/ui)`));
|
|
6759
6835
|
}
|
|
6760
6836
|
if (inCodeShared.length > 0) {
|
|
6761
|
-
console.log(
|
|
6837
|
+
console.log(chalk10.dim(` Shared: ${inCodeShared.map((s) => `${s.id} (${s.name})`).join(", ")}`));
|
|
6762
6838
|
}
|
|
6763
6839
|
if (layoutShared.length > 0) {
|
|
6764
|
-
console.log(
|
|
6840
|
+
console.log(chalk10.dim(` Layout: ${layoutShared.map((l) => `${l.id} (${l.name})`).join(", ")} via layout.tsx`));
|
|
6765
6841
|
}
|
|
6766
6842
|
if (iconCount > 0) {
|
|
6767
|
-
console.log(
|
|
6843
|
+
console.log(chalk10.dim(` Icons: ${iconCount} from lucide-react`));
|
|
6768
6844
|
}
|
|
6769
6845
|
if (hasInstalled) {
|
|
6770
|
-
console.log(
|
|
6846
|
+
console.log(chalk10.dim(" Dependencies: installed \u2714"));
|
|
6771
6847
|
}
|
|
6772
|
-
console.log(
|
|
6848
|
+
console.log(chalk10.dim(` Syntax: ${syntaxStatus}`));
|
|
6773
6849
|
if (route) {
|
|
6774
|
-
console.log(
|
|
6850
|
+
console.log(chalk10.cyan(`
|
|
6775
6851
|
Preview: http://localhost:3000${route}`));
|
|
6776
6852
|
}
|
|
6777
6853
|
console.log("");
|
|
@@ -6779,35 +6855,35 @@ function printPostGenerationReport(opts) {
|
|
|
6779
6855
|
function printSharedComponentReport(opts) {
|
|
6780
6856
|
const { id, name, file, instruction, postFixes = [] } = opts;
|
|
6781
6857
|
const syntaxStatus = postFixes.length > 0 ? "fixed \u2714" : "valid \u2714";
|
|
6782
|
-
console.log(
|
|
6858
|
+
console.log(chalk10.green(`
|
|
6783
6859
|
\u2705 Updated ${id} (${name}) at ${file}
|
|
6784
6860
|
`));
|
|
6785
6861
|
if (instruction) {
|
|
6786
6862
|
const snippet = instruction.length > 60 ? instruction.slice(0, 57) + "..." : instruction;
|
|
6787
|
-
console.log(
|
|
6863
|
+
console.log(chalk10.dim(` Changed: ${snippet}`));
|
|
6788
6864
|
}
|
|
6789
|
-
console.log(
|
|
6790
|
-
console.log(
|
|
6865
|
+
console.log(chalk10.dim(" Affects: all pages via layout.tsx"));
|
|
6866
|
+
console.log(chalk10.dim(` Syntax: ${syntaxStatus}`));
|
|
6791
6867
|
console.log("");
|
|
6792
6868
|
}
|
|
6793
6869
|
function printLinkSharedReport(opts) {
|
|
6794
6870
|
const { sharedId, sharedName, pageTarget, route, postFixes = [] } = opts;
|
|
6795
6871
|
const syntaxStatus = postFixes.length > 0 ? "fixed \u2714" : "valid \u2714";
|
|
6796
|
-
console.log(
|
|
6872
|
+
console.log(chalk10.green(`
|
|
6797
6873
|
\u2705 Linked ${sharedId} (${sharedName}) to page "${pageTarget}"
|
|
6798
6874
|
`));
|
|
6799
|
-
console.log(
|
|
6800
|
-
console.log(
|
|
6875
|
+
console.log(chalk10.dim(` Syntax: ${syntaxStatus}`));
|
|
6876
|
+
console.log(chalk10.cyan(` Preview: http://localhost:3000${route}`));
|
|
6801
6877
|
console.log("");
|
|
6802
6878
|
}
|
|
6803
6879
|
function printPromoteAndLinkReport(opts) {
|
|
6804
6880
|
const { id, name, file, usedInFiles, postFixes = [] } = opts;
|
|
6805
6881
|
const syntaxStatus = postFixes.length > 0 ? "fixed \u2714" : "valid \u2714";
|
|
6806
|
-
console.log(
|
|
6882
|
+
console.log(chalk10.green(`
|
|
6807
6883
|
\u2705 Created ${id} (${name}) at ${file}
|
|
6808
6884
|
`));
|
|
6809
|
-
console.log(
|
|
6810
|
-
console.log(
|
|
6885
|
+
console.log(chalk10.dim(` Linked to: ${usedInFiles.length} page(s)`));
|
|
6886
|
+
console.log(chalk10.dim(` Syntax: ${syntaxStatus}`));
|
|
6811
6887
|
console.log("");
|
|
6812
6888
|
}
|
|
6813
6889
|
function showPreview(requests, results, config2, preflightInstalledNames) {
|
|
@@ -6825,23 +6901,23 @@ function showPreview(requests, results, config2, preflightInstalledNames) {
|
|
|
6825
6901
|
const modifiedSharedComponents = successfulPairs.filter(({ request }) => request.type === "modify-layout-block");
|
|
6826
6902
|
const modifiedPages = successfulPairs.filter(({ request }) => request.type === "update-page");
|
|
6827
6903
|
const tokenChanges = successfulPairs.filter(({ request }) => request.type === "update-token");
|
|
6828
|
-
console.log(
|
|
6904
|
+
console.log(chalk10.bold.cyan("\n\u{1F4CB} Changes Applied:\n"));
|
|
6829
6905
|
if (preflightInstalledNames && preflightInstalledNames.length > 0) {
|
|
6830
|
-
console.log(
|
|
6906
|
+
console.log(chalk10.cyan("\u{1F50D} Pre-flight check: Installed missing components:"));
|
|
6831
6907
|
preflightInstalledNames.forEach((name) => {
|
|
6832
|
-
console.log(
|
|
6908
|
+
console.log(chalk10.green(` \u2728 Auto-installed ${name}`));
|
|
6833
6909
|
});
|
|
6834
6910
|
console.log("");
|
|
6835
6911
|
}
|
|
6836
6912
|
if (addedComponents.length > 0) {
|
|
6837
6913
|
const names = addedComponents.map(({ request }) => request.changes.name).filter(Boolean);
|
|
6838
|
-
console.log(
|
|
6839
|
-
console.log(
|
|
6914
|
+
console.log(chalk10.green("\u{1F4E6} Components:"));
|
|
6915
|
+
console.log(chalk10.white(` \u2728 Auto-installed: ${names.join(", ")}`));
|
|
6840
6916
|
}
|
|
6841
6917
|
if (customComponents.length > 0) {
|
|
6842
6918
|
const names = customComponents.map(({ request }) => request.changes.name).filter(Boolean);
|
|
6843
|
-
if (addedComponents.length === 0) console.log(
|
|
6844
|
-
console.log(
|
|
6919
|
+
if (addedComponents.length === 0) console.log(chalk10.green("\u{1F4E6} Components:"));
|
|
6920
|
+
console.log(chalk10.white(` \u2728 Created: ${names.join(", ")}`));
|
|
6845
6921
|
}
|
|
6846
6922
|
const usedComponentIds = /* @__PURE__ */ new Set();
|
|
6847
6923
|
addedPages.forEach(({ request }) => {
|
|
@@ -6856,71 +6932,71 @@ function showPreview(requests, results, config2, preflightInstalledNames) {
|
|
|
6856
6932
|
]);
|
|
6857
6933
|
const reusedIds = [...usedComponentIds].filter((id) => !newComponentIds.has(id));
|
|
6858
6934
|
if (reusedIds.length > 0) {
|
|
6859
|
-
if (addedComponents.length === 0 && customComponents.length === 0) console.log(
|
|
6860
|
-
console.log(
|
|
6935
|
+
if (addedComponents.length === 0 && customComponents.length === 0) console.log(chalk10.green("\u{1F4E6} Components:"));
|
|
6936
|
+
console.log(chalk10.white(` \u{1F504} Reused: ${reusedIds.join(", ")}`));
|
|
6861
6937
|
}
|
|
6862
6938
|
if (addedComponents.length > 0 || customComponents.length > 0 || reusedIds.length > 0) {
|
|
6863
6939
|
console.log("");
|
|
6864
6940
|
}
|
|
6865
6941
|
if (addedPages.length > 0) {
|
|
6866
|
-
console.log(
|
|
6942
|
+
console.log(chalk10.green("\u{1F4C4} Pages Created:"));
|
|
6867
6943
|
addedPages.forEach(({ request }) => {
|
|
6868
6944
|
const page = request.changes;
|
|
6869
6945
|
const route = page.route || "/";
|
|
6870
|
-
console.log(
|
|
6871
|
-
console.log(
|
|
6872
|
-
console.log(
|
|
6946
|
+
console.log(chalk10.white(` \u2728 ${page.name || "Page"}`));
|
|
6947
|
+
console.log(chalk10.gray(` Route: ${route}`));
|
|
6948
|
+
console.log(chalk10.gray(` Sections: ${page.sections?.length ?? 0}`));
|
|
6873
6949
|
});
|
|
6874
6950
|
console.log("");
|
|
6875
6951
|
}
|
|
6876
6952
|
if (modifiedComponents.length > 0 || modifiedSharedComponents.length > 0 || modifiedPages.length > 0 || tokenChanges.length > 0) {
|
|
6877
|
-
console.log(
|
|
6953
|
+
console.log(chalk10.yellow("\u{1F527} Modified:"));
|
|
6878
6954
|
modifiedComponents.forEach(({ result }) => {
|
|
6879
|
-
console.log(
|
|
6955
|
+
console.log(chalk10.white(` \u2022 ${result.message}`));
|
|
6880
6956
|
});
|
|
6881
6957
|
modifiedSharedComponents.forEach(({ result }) => {
|
|
6882
|
-
console.log(
|
|
6958
|
+
console.log(chalk10.white(` \u2022 ${result.message}`));
|
|
6883
6959
|
});
|
|
6884
6960
|
modifiedPages.forEach(({ result }) => {
|
|
6885
|
-
console.log(
|
|
6961
|
+
console.log(chalk10.white(` \u2022 ${result.message}`));
|
|
6886
6962
|
});
|
|
6887
6963
|
tokenChanges.forEach(({ result }) => {
|
|
6888
|
-
console.log(
|
|
6964
|
+
console.log(chalk10.white(` \u2022 ${result.message}`));
|
|
6889
6965
|
});
|
|
6890
6966
|
console.log("");
|
|
6891
6967
|
}
|
|
6892
6968
|
if (failedPairs.length > 0) {
|
|
6893
|
-
console.log(
|
|
6969
|
+
console.log(chalk10.red("\u274C Failed modifications:"));
|
|
6894
6970
|
failedPairs.forEach(({ result }) => {
|
|
6895
|
-
console.log(
|
|
6971
|
+
console.log(chalk10.gray(` \u2716 ${result.message}`));
|
|
6896
6972
|
});
|
|
6897
6973
|
console.log("");
|
|
6898
6974
|
}
|
|
6899
6975
|
const successCount = successfulPairs.length;
|
|
6900
6976
|
const totalCount = results.length;
|
|
6901
6977
|
if (successCount === totalCount) {
|
|
6902
|
-
console.log(
|
|
6978
|
+
console.log(chalk10.green.bold(`\u2705 Success! ${successCount} modification(s) applied
|
|
6903
6979
|
`));
|
|
6904
6980
|
} else {
|
|
6905
|
-
console.log(
|
|
6981
|
+
console.log(chalk10.yellow.bold(`\u26A0\uFE0F Partial success: ${successCount}/${totalCount} modification(s) applied
|
|
6906
6982
|
`));
|
|
6907
6983
|
}
|
|
6908
6984
|
if (addedPages.length > 0) {
|
|
6909
6985
|
const firstPage = addedPages[0].request.changes;
|
|
6910
6986
|
const route = firstPage?.route || "/";
|
|
6911
|
-
console.log(
|
|
6912
|
-
console.log(
|
|
6913
|
-
console.log(
|
|
6914
|
-
console.log(
|
|
6987
|
+
console.log(chalk10.cyan("\u{1F680} What's next:\n"));
|
|
6988
|
+
console.log(chalk10.white(" \u{1F4FA} View in browser:"));
|
|
6989
|
+
console.log(chalk10.cyan(" coherent preview"));
|
|
6990
|
+
console.log(chalk10.gray(` \u2192 Opens http://localhost:3000${route}
|
|
6915
6991
|
`));
|
|
6916
|
-
console.log(
|
|
6917
|
-
console.log(
|
|
6918
|
-
console.log(
|
|
6992
|
+
console.log(chalk10.white(" \u{1F3A8} Customize:"));
|
|
6993
|
+
console.log(chalk10.cyan(' coherent chat "make buttons rounded"'));
|
|
6994
|
+
console.log(chalk10.cyan(` coherent chat "add hero section to ${firstPage?.name ?? "page"}"`));
|
|
6919
6995
|
console.log("");
|
|
6920
6996
|
} else if (successCount > 0) {
|
|
6921
|
-
console.log(
|
|
6922
|
-
console.log(
|
|
6923
|
-
console.log(
|
|
6997
|
+
console.log(chalk10.cyan("\u{1F680} What's next:\n"));
|
|
6998
|
+
console.log(chalk10.white(" \u{1F4FA} Preview changes:"));
|
|
6999
|
+
console.log(chalk10.cyan(" coherent preview\n"));
|
|
6924
7000
|
}
|
|
6925
7001
|
}
|
|
6926
7002
|
function getChangeDescription(request, config2) {
|
|
@@ -6962,6 +7038,24 @@ function getChangeDescription(request, config2) {
|
|
|
6962
7038
|
|
|
6963
7039
|
// src/commands/chat/modification-handler.ts
|
|
6964
7040
|
var DEBUG2 = process.env.COHERENT_DEBUG === "1";
|
|
7041
|
+
function stripInlineLayoutElements(code) {
|
|
7042
|
+
let result = code;
|
|
7043
|
+
const stripped = [];
|
|
7044
|
+
const headerBlock = extractBalancedTag(result, "header");
|
|
7045
|
+
if (headerBlock) {
|
|
7046
|
+
result = result.replace(headerBlock, "");
|
|
7047
|
+
stripped.push("header");
|
|
7048
|
+
}
|
|
7049
|
+
const footerBlock = extractBalancedTag(result, "footer");
|
|
7050
|
+
if (footerBlock) {
|
|
7051
|
+
result = result.replace(footerBlock, "");
|
|
7052
|
+
stripped.push("footer");
|
|
7053
|
+
}
|
|
7054
|
+
if (stripped.length > 0) {
|
|
7055
|
+
result = result.replace(/\n{3,}/g, "\n\n");
|
|
7056
|
+
}
|
|
7057
|
+
return { code: result, stripped };
|
|
7058
|
+
}
|
|
6965
7059
|
async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider, originalMessage) {
|
|
6966
7060
|
switch (request.type) {
|
|
6967
7061
|
case "modify-layout-block": {
|
|
@@ -7000,8 +7094,8 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7000
7094
|
const newCode = await ai.editSharedComponentCode(currentCode, instruction, resolved.name);
|
|
7001
7095
|
const { fixedCode, fixes } = await validateAndFixGeneratedCode(projectRoot, newCode, { isPage: false });
|
|
7002
7096
|
if (fixes.length > 0) {
|
|
7003
|
-
console.log(
|
|
7004
|
-
fixes.forEach((f) => console.log(
|
|
7097
|
+
console.log(chalk11.dim(" \u{1F527} Post-generation fixes:"));
|
|
7098
|
+
fixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
7005
7099
|
}
|
|
7006
7100
|
await writeFile(fullPath, fixedCode);
|
|
7007
7101
|
printSharedComponentReport({
|
|
@@ -7074,11 +7168,11 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7074
7168
|
const newPageCode = await ai.replaceInlineWithShared(pageCode, sharedCode, resolved.name, changes?.blockHint);
|
|
7075
7169
|
const { fixedCode, fixes } = await validateAndFixGeneratedCode(projectRoot, newPageCode, { isPage: true });
|
|
7076
7170
|
if (fixes.length > 0) {
|
|
7077
|
-
console.log(
|
|
7078
|
-
fixes.forEach((f) => console.log(
|
|
7171
|
+
console.log(chalk11.dim(" \u{1F527} Post-generation fixes:"));
|
|
7172
|
+
fixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
7079
7173
|
}
|
|
7080
7174
|
await writeFile(pageFilePath, fixedCode);
|
|
7081
|
-
const manifest = await
|
|
7175
|
+
const manifest = await loadManifest6(projectRoot);
|
|
7082
7176
|
const usedIn = manifest.shared.find((e) => e.id === resolved.id)?.usedIn ?? [];
|
|
7083
7177
|
const routePath = route.replace(/^\//, "");
|
|
7084
7178
|
const filePathRel = routePath ? `app/${routePath}/page.tsx` : "app/page.tsx";
|
|
@@ -7148,7 +7242,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7148
7242
|
};
|
|
7149
7243
|
}
|
|
7150
7244
|
const extractedCode = await ai.extractBlockAsComponent(sourceCode, blockHint, componentName);
|
|
7151
|
-
const created = await
|
|
7245
|
+
const created = await generateSharedComponent3(projectRoot, {
|
|
7152
7246
|
name: componentName,
|
|
7153
7247
|
type: "section",
|
|
7154
7248
|
code: extractedCode,
|
|
@@ -7177,13 +7271,13 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7177
7271
|
const newCode = await ai.replaceInlineWithShared(linkPageCode, sharedCode, created.name, blockHint);
|
|
7178
7272
|
const { fixedCode, fixes } = await validateAndFixGeneratedCode(projectRoot, newCode, { isPage: true });
|
|
7179
7273
|
if (fixes.length > 0) {
|
|
7180
|
-
console.log(
|
|
7181
|
-
fixes.forEach((f) => console.log(
|
|
7274
|
+
console.log(chalk11.dim(" \u{1F527} Post-generation fixes:"));
|
|
7275
|
+
fixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
7182
7276
|
}
|
|
7183
7277
|
await writeFile(fullPath, fixedCode);
|
|
7184
7278
|
usedInFiles.push(relPath);
|
|
7185
7279
|
}
|
|
7186
|
-
const manifest = await
|
|
7280
|
+
const manifest = await loadManifest6(projectRoot);
|
|
7187
7281
|
const nextManifest = updateUsedIn(manifest, created.id, usedInFiles);
|
|
7188
7282
|
await saveManifest(projectRoot, nextManifest);
|
|
7189
7283
|
printPromoteAndLinkReport({
|
|
@@ -7272,7 +7366,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7272
7366
|
const aiPageCode = typeof page.pageCode === "string" && page.pageCode.trim() !== "" ? page.pageCode : void 0;
|
|
7273
7367
|
if (aiPageCode) {
|
|
7274
7368
|
finalPageCode = aiPageCode;
|
|
7275
|
-
if (DEBUG2) console.log(
|
|
7369
|
+
if (DEBUG2) console.log(chalk11.dim(` [pageCode] Using AI-generated pageCode (user content priority)`));
|
|
7276
7370
|
} else if (page.pageType && page.structuredContent) {
|
|
7277
7371
|
const templateFn = getTemplateForPageType(page.pageType);
|
|
7278
7372
|
if (templateFn) {
|
|
@@ -7283,9 +7377,9 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7283
7377
|
pageName
|
|
7284
7378
|
};
|
|
7285
7379
|
finalPageCode = templateFn(page.structuredContent, opts);
|
|
7286
|
-
if (DEBUG2) console.log(
|
|
7380
|
+
if (DEBUG2) console.log(chalk11.dim(` [template] Used "${page.pageType}" template (no pageCode provided)`));
|
|
7287
7381
|
} catch {
|
|
7288
|
-
if (DEBUG2) console.log(
|
|
7382
|
+
if (DEBUG2) console.log(chalk11.dim(` [template] Failed for "${page.pageType}"`));
|
|
7289
7383
|
}
|
|
7290
7384
|
}
|
|
7291
7385
|
}
|
|
@@ -7328,10 +7422,13 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7328
7422
|
let codeToWrite = fixedCode;
|
|
7329
7423
|
const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite);
|
|
7330
7424
|
codeToWrite = autoFixed;
|
|
7425
|
+
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
7426
|
+
codeToWrite = layoutStripped;
|
|
7331
7427
|
const allFixes = [...postFixes, ...autoFixes];
|
|
7428
|
+
if (stripped.length > 0) allFixes.push(`stripped inline ${stripped.join(", ")} (layout owns these)`);
|
|
7332
7429
|
if (allFixes.length > 0) {
|
|
7333
|
-
console.log(
|
|
7334
|
-
allFixes.forEach((f) => console.log(
|
|
7430
|
+
console.log(chalk11.dim(" \u{1F527} Post-generation fixes:"));
|
|
7431
|
+
allFixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
7335
7432
|
}
|
|
7336
7433
|
await writeFile(filePath, codeToWrite);
|
|
7337
7434
|
const pageIdx = dsm.getConfig().pages.findIndex((p) => p.id === page.id);
|
|
@@ -7342,7 +7439,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7342
7439
|
cm.updateConfig(cfg);
|
|
7343
7440
|
pm.updateConfig(cfg);
|
|
7344
7441
|
}
|
|
7345
|
-
const manifestForAudit = await
|
|
7442
|
+
const manifestForAudit = await loadManifest6(projectRoot);
|
|
7346
7443
|
await warnInlineDuplicates(projectRoot, page.name || page.id || route.slice(1), codeToWrite, manifestForAudit);
|
|
7347
7444
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
7348
7445
|
printPostGenerationReport({
|
|
@@ -7361,7 +7458,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7361
7458
|
const errors = issues.filter((i) => i.severity === "error");
|
|
7362
7459
|
if (errors.length >= 5 && aiProvider) {
|
|
7363
7460
|
console.log(
|
|
7364
|
-
|
|
7461
|
+
chalk11.yellow(`
|
|
7365
7462
|
\u{1F504} ${errors.length} quality errors \u2014 attempting AI fix for ${page.name || page.id}...`)
|
|
7366
7463
|
);
|
|
7367
7464
|
try {
|
|
@@ -7383,7 +7480,7 @@ Rules:
|
|
|
7383
7480
|
if (recheckErrors.length < errors.length) {
|
|
7384
7481
|
codeToWrite = fixedCode2;
|
|
7385
7482
|
await writeFile(filePath, codeToWrite);
|
|
7386
|
-
console.log(
|
|
7483
|
+
console.log(chalk11.green(` \u2714 Quality fix: ${errors.length} \u2192 ${recheckErrors.length} errors`));
|
|
7387
7484
|
}
|
|
7388
7485
|
}
|
|
7389
7486
|
}
|
|
@@ -7392,9 +7489,9 @@ Rules:
|
|
|
7392
7489
|
}
|
|
7393
7490
|
const report = formatIssues(issues);
|
|
7394
7491
|
if (report) {
|
|
7395
|
-
console.log(
|
|
7492
|
+
console.log(chalk11.yellow(`
|
|
7396
7493
|
\u{1F50D} Quality check for ${page.name || page.id}:`));
|
|
7397
|
-
console.log(
|
|
7494
|
+
console.log(chalk11.dim(report));
|
|
7398
7495
|
}
|
|
7399
7496
|
}
|
|
7400
7497
|
}
|
|
@@ -7409,9 +7506,9 @@ Rules:
|
|
|
7409
7506
|
const changes = request.changes;
|
|
7410
7507
|
const instruction = originalMessage || (typeof changes?.instruction === "string" ? changes.instruction : void 0);
|
|
7411
7508
|
let resolvedPageCode = typeof changes?.pageCode === "string" && changes.pageCode.trim() !== "" ? changes.pageCode : void 0;
|
|
7412
|
-
if (DEBUG2 && instruction) console.log(
|
|
7509
|
+
if (DEBUG2 && instruction) console.log(chalk11.dim(` [update-page] instruction: ${instruction.slice(0, 120)}...`));
|
|
7413
7510
|
if (DEBUG2 && resolvedPageCode)
|
|
7414
|
-
console.log(
|
|
7511
|
+
console.log(chalk11.dim(` [update-page] has pageCode (${resolvedPageCode.length} chars)`));
|
|
7415
7512
|
const configChanges = { ...changes };
|
|
7416
7513
|
delete configChanges.pageCode;
|
|
7417
7514
|
delete configChanges.pageType;
|
|
@@ -7435,12 +7532,12 @@ Rules:
|
|
|
7435
7532
|
try {
|
|
7436
7533
|
currentCode = await readFile(absPath);
|
|
7437
7534
|
} catch {
|
|
7438
|
-
if (DEBUG2) console.log(
|
|
7535
|
+
if (DEBUG2) console.log(chalk11.dim(` [update-page] Could not read current file at ${absPath}`));
|
|
7439
7536
|
}
|
|
7440
7537
|
if (currentCode) {
|
|
7441
7538
|
const ai = await createAIProvider(aiProvider ?? "auto");
|
|
7442
7539
|
if (ai.editPageCode) {
|
|
7443
|
-
console.log(
|
|
7540
|
+
console.log(chalk11.dim(" \u270F\uFE0F Applying changes to existing page..."));
|
|
7444
7541
|
const coreRules = CORE_CONSTRAINTS;
|
|
7445
7542
|
const qualityRules = DESIGN_QUALITY;
|
|
7446
7543
|
const contextualRules = selectContextualRules(instruction);
|
|
@@ -7460,9 +7557,9 @@ ${contextualRules}
|
|
|
7460
7557
|
${routeRules}
|
|
7461
7558
|
${pagesCtx}`
|
|
7462
7559
|
);
|
|
7463
|
-
if (DEBUG2) console.log(
|
|
7560
|
+
if (DEBUG2) console.log(chalk11.dim(` [update-page] AI returned ${resolvedPageCode.length} chars`));
|
|
7464
7561
|
} else {
|
|
7465
|
-
console.log(
|
|
7562
|
+
console.log(chalk11.yellow(" \u26A0 AI provider does not support editPageCode"));
|
|
7466
7563
|
}
|
|
7467
7564
|
}
|
|
7468
7565
|
}
|
|
@@ -7492,10 +7589,13 @@ ${pagesCtx}`
|
|
|
7492
7589
|
let codeToWrite = fixedCode;
|
|
7493
7590
|
const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite);
|
|
7494
7591
|
codeToWrite = autoFixed;
|
|
7592
|
+
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
7593
|
+
codeToWrite = layoutStripped;
|
|
7495
7594
|
const allFixes = [...postFixes, ...autoFixes];
|
|
7595
|
+
if (stripped.length > 0) allFixes.push(`stripped inline ${stripped.join(", ")} (layout owns these)`);
|
|
7496
7596
|
if (allFixes.length > 0) {
|
|
7497
|
-
console.log(
|
|
7498
|
-
allFixes.forEach((f) => console.log(
|
|
7597
|
+
console.log(chalk11.dim(" \u{1F527} Post-generation fixes:"));
|
|
7598
|
+
allFixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
7499
7599
|
}
|
|
7500
7600
|
await writeFile(absPath, codeToWrite);
|
|
7501
7601
|
const updatePageIdx = dsm.getConfig().pages.findIndex((p) => p.id === pageDef.id);
|
|
@@ -7506,7 +7606,7 @@ ${pagesCtx}`
|
|
|
7506
7606
|
cm.updateConfig(cfg);
|
|
7507
7607
|
pm.updateConfig(cfg);
|
|
7508
7608
|
}
|
|
7509
|
-
const manifestForAudit = await
|
|
7609
|
+
const manifestForAudit = await loadManifest6(projectRoot);
|
|
7510
7610
|
await warnInlineDuplicates(
|
|
7511
7611
|
projectRoot,
|
|
7512
7612
|
pageDef.name || pageDef.id || route.slice(1),
|
|
@@ -7528,9 +7628,9 @@ ${pagesCtx}`
|
|
|
7528
7628
|
const issues = validatePageQuality(codeToWrite);
|
|
7529
7629
|
const report = formatIssues(issues);
|
|
7530
7630
|
if (report) {
|
|
7531
|
-
console.log(
|
|
7631
|
+
console.log(chalk11.yellow(`
|
|
7532
7632
|
\u{1F50D} Quality check for ${pageDef.name || pageDef.id}:`));
|
|
7533
|
-
console.log(
|
|
7633
|
+
console.log(chalk11.dim(report));
|
|
7534
7634
|
}
|
|
7535
7635
|
} else {
|
|
7536
7636
|
try {
|
|
@@ -7539,11 +7639,11 @@ ${pagesCtx}`
|
|
|
7539
7639
|
if (fixes.length > 0) {
|
|
7540
7640
|
code = fixed;
|
|
7541
7641
|
await writeFile(absPath, code);
|
|
7542
|
-
console.log(
|
|
7543
|
-
fixes.forEach((f) => console.log(
|
|
7642
|
+
console.log(chalk11.dim(" \u{1F527} Auto-fixes applied:"));
|
|
7643
|
+
fixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
7544
7644
|
}
|
|
7545
7645
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
7546
|
-
const manifest = await
|
|
7646
|
+
const manifest = await loadManifest6(projectRoot);
|
|
7547
7647
|
printPostGenerationReport({
|
|
7548
7648
|
action: "updated",
|
|
7549
7649
|
pageTitle: pageDef.name || pageDef.id || "Page",
|
|
@@ -7557,9 +7657,9 @@ ${pagesCtx}`
|
|
|
7557
7657
|
const issues = validatePageQuality(code);
|
|
7558
7658
|
const report = formatIssues(issues);
|
|
7559
7659
|
if (report) {
|
|
7560
|
-
console.log(
|
|
7660
|
+
console.log(chalk11.yellow(`
|
|
7561
7661
|
\u{1F50D} Quality check for ${pageDef.name || pageDef.id}:`));
|
|
7562
|
-
console.log(
|
|
7662
|
+
console.log(chalk11.dim(report));
|
|
7563
7663
|
}
|
|
7564
7664
|
} catch {
|
|
7565
7665
|
}
|
|
@@ -7588,236 +7688,10 @@ ${pagesCtx}`
|
|
|
7588
7688
|
}
|
|
7589
7689
|
}
|
|
7590
7690
|
|
|
7591
|
-
// src/commands/chat/layout-extractor.ts
|
|
7592
|
-
import { readFileSync as readFileSync9, readdirSync as readdirSync2 } from "fs";
|
|
7593
|
-
import { join as join9, resolve as resolve8 } from "path";
|
|
7594
|
-
import chalk11 from "chalk";
|
|
7595
|
-
import { loadManifest as loadManifest6, generateSharedComponent as generateSharedComponent3, integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout3 } from "@getcoherent/core";
|
|
7596
|
-
|
|
7597
|
-
// src/commands/chat/jsx-extractor.ts
|
|
7598
|
-
function extractBalancedTag(source, tagName) {
|
|
7599
|
-
const openRe = new RegExp(`<${tagName}\\b`, "gi");
|
|
7600
|
-
const match = openRe.exec(source);
|
|
7601
|
-
if (!match) return null;
|
|
7602
|
-
const startIdx = match.index;
|
|
7603
|
-
const openTagRe = new RegExp(`<${tagName}\\b`, "gi");
|
|
7604
|
-
const closeTagRe = new RegExp(`</${tagName}>`, "gi");
|
|
7605
|
-
const events = [];
|
|
7606
|
-
let m;
|
|
7607
|
-
openTagRe.lastIndex = startIdx;
|
|
7608
|
-
while ((m = openTagRe.exec(source)) !== null) {
|
|
7609
|
-
events.push({ pos: m.index, type: "open", end: m.index + m[0].length });
|
|
7610
|
-
}
|
|
7611
|
-
closeTagRe.lastIndex = startIdx;
|
|
7612
|
-
while ((m = closeTagRe.exec(source)) !== null) {
|
|
7613
|
-
events.push({ pos: m.index, type: "close", end: m.index + m[0].length });
|
|
7614
|
-
}
|
|
7615
|
-
events.sort((a, b) => a.pos - b.pos);
|
|
7616
|
-
let depth = 0;
|
|
7617
|
-
for (const ev of events) {
|
|
7618
|
-
if (ev.pos < startIdx) continue;
|
|
7619
|
-
if (ev.type === "open") depth++;
|
|
7620
|
-
else {
|
|
7621
|
-
depth--;
|
|
7622
|
-
if (depth === 0) return source.slice(startIdx, ev.end);
|
|
7623
|
-
}
|
|
7624
|
-
}
|
|
7625
|
-
return null;
|
|
7626
|
-
}
|
|
7627
|
-
function extractRelevantImports(fullSource, jsxBlock) {
|
|
7628
|
-
const importLines = [];
|
|
7629
|
-
const importRe = /^import\s+.*from\s+['"][^'"]+['"];?\s*$/gm;
|
|
7630
|
-
let m;
|
|
7631
|
-
while ((m = importRe.exec(fullSource)) !== null) {
|
|
7632
|
-
const line = m[0];
|
|
7633
|
-
const namesMatch = line.match(/import\s*\{([^}]+)\}/);
|
|
7634
|
-
if (namesMatch) {
|
|
7635
|
-
const names = namesMatch[1].split(",").map(
|
|
7636
|
-
(n) => n.trim().split(/\s+as\s+/).pop().trim()
|
|
7637
|
-
);
|
|
7638
|
-
if (names.some((name) => jsxBlock.includes(name))) {
|
|
7639
|
-
importLines.push(line);
|
|
7640
|
-
}
|
|
7641
|
-
}
|
|
7642
|
-
const defaultMatch = line.match(/import\s+(\w+)\s+from/);
|
|
7643
|
-
if (defaultMatch && jsxBlock.includes(defaultMatch[1])) {
|
|
7644
|
-
importLines.push(line);
|
|
7645
|
-
}
|
|
7646
|
-
}
|
|
7647
|
-
return [...new Set(importLines)];
|
|
7648
|
-
}
|
|
7649
|
-
function extractStateHooks(fullSource, jsxBlock) {
|
|
7650
|
-
const hooks = [];
|
|
7651
|
-
const stateRe = /const\s+\[(\w+),\s*(\w+)\]\s*=\s*useState\b[^)]*\)/g;
|
|
7652
|
-
let m;
|
|
7653
|
-
while ((m = stateRe.exec(fullSource)) !== null) {
|
|
7654
|
-
const [fullMatch, getter, setter] = m;
|
|
7655
|
-
if (jsxBlock.includes(getter) || jsxBlock.includes(setter)) {
|
|
7656
|
-
hooks.push(fullMatch);
|
|
7657
|
-
}
|
|
7658
|
-
}
|
|
7659
|
-
return hooks;
|
|
7660
|
-
}
|
|
7661
|
-
function addActiveNavToHeader(code) {
|
|
7662
|
-
let result = code;
|
|
7663
|
-
if (!result.includes("usePathname")) {
|
|
7664
|
-
if (result.includes("from 'next/navigation'")) {
|
|
7665
|
-
result = result.replace(
|
|
7666
|
-
/import\s*\{([^}]+)\}\s*from\s*'next\/navigation'/,
|
|
7667
|
-
(_, names) => `import { ${names.trim()}, usePathname } from 'next/navigation'`
|
|
7668
|
-
);
|
|
7669
|
-
} else {
|
|
7670
|
-
result = result.replace(
|
|
7671
|
-
"export function Header()",
|
|
7672
|
-
"import { usePathname } from 'next/navigation'\n\nexport function Header()"
|
|
7673
|
-
);
|
|
7674
|
-
}
|
|
7675
|
-
}
|
|
7676
|
-
if (!result.includes("const pathname")) {
|
|
7677
|
-
result = result.replace(
|
|
7678
|
-
/export function Header\(\)\s*\{/,
|
|
7679
|
-
"export function Header() {\n const pathname = usePathname()"
|
|
7680
|
-
);
|
|
7681
|
-
}
|
|
7682
|
-
result = result.replace(
|
|
7683
|
-
/<Link\s+href="(\/[^"]*?)"\s+className="([^"]*?)(?:text-foreground|text-muted-foreground(?:\s+hover:text-foreground)?(?:\s+transition-colors)?)([^"]*?)">/g,
|
|
7684
|
-
(_, href, before, after) => {
|
|
7685
|
-
const base = before.trim();
|
|
7686
|
-
const trail = after.trim();
|
|
7687
|
-
const staticParts = [base, trail].filter(Boolean).join(" ");
|
|
7688
|
-
const space = staticParts ? " " : "";
|
|
7689
|
-
return `<Link href="${href}" className={\`${staticParts}${space}\${pathname === '${href}' ? 'text-foreground font-medium' : 'text-muted-foreground hover:text-foreground transition-colors'}\`}>`;
|
|
7690
|
-
}
|
|
7691
|
-
);
|
|
7692
|
-
return result;
|
|
7693
|
-
}
|
|
7694
|
-
|
|
7695
|
-
// src/commands/chat/layout-extractor.ts
|
|
7696
|
-
function findAllPageFiles(dir) {
|
|
7697
|
-
const results = [];
|
|
7698
|
-
try {
|
|
7699
|
-
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
7700
|
-
const full = join9(dir, entry.name);
|
|
7701
|
-
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".next" && entry.name !== "design-system") {
|
|
7702
|
-
results.push(...findAllPageFiles(full));
|
|
7703
|
-
} else if (entry.name === "page.tsx" || entry.name === "page.jsx") {
|
|
7704
|
-
results.push(full);
|
|
7705
|
-
}
|
|
7706
|
-
}
|
|
7707
|
-
} catch {
|
|
7708
|
-
}
|
|
7709
|
-
return results;
|
|
7710
|
-
}
|
|
7711
|
-
async function extractAndShareLayoutComponents(projectRoot, generatedPageFiles) {
|
|
7712
|
-
const manifest = await loadManifest6(projectRoot);
|
|
7713
|
-
const hasSharedHeader = manifest.shared.some((c) => c.type === "layout" && /header|nav/i.test(c.name));
|
|
7714
|
-
const hasSharedFooter = manifest.shared.some((c) => c.type === "layout" && /footer/i.test(c.name));
|
|
7715
|
-
if (hasSharedHeader && hasSharedFooter) return false;
|
|
7716
|
-
let sourceCode = "";
|
|
7717
|
-
for (const file of generatedPageFiles) {
|
|
7718
|
-
try {
|
|
7719
|
-
const code = readFileSync9(file, "utf-8");
|
|
7720
|
-
if (code.includes("<header") || code.includes("<footer") || code.includes("<nav")) {
|
|
7721
|
-
sourceCode = code;
|
|
7722
|
-
break;
|
|
7723
|
-
}
|
|
7724
|
-
} catch {
|
|
7725
|
-
continue;
|
|
7726
|
-
}
|
|
7727
|
-
}
|
|
7728
|
-
if (!sourceCode) return false;
|
|
7729
|
-
let extracted = false;
|
|
7730
|
-
if (!hasSharedHeader) {
|
|
7731
|
-
let headerJsx = extractBalancedTag(sourceCode, "header");
|
|
7732
|
-
if (!headerJsx) headerJsx = extractBalancedTag(sourceCode, "nav");
|
|
7733
|
-
if (headerJsx) {
|
|
7734
|
-
const imports = extractRelevantImports(sourceCode, headerJsx);
|
|
7735
|
-
const importBlock = imports.length > 0 ? imports.join("\n") + "\n" : "import Link from 'next/link'\n";
|
|
7736
|
-
const stateHooks = extractStateHooks(sourceCode, headerJsx);
|
|
7737
|
-
const needsReactImport = stateHooks.length > 0 && !importBlock.includes("from 'react'");
|
|
7738
|
-
const reactImport = needsReactImport ? "import { useState } from 'react'\n" : "";
|
|
7739
|
-
const stateBlock = stateHooks.length > 0 ? " " + stateHooks.join("\n ") + "\n" : "";
|
|
7740
|
-
const returnIndent = stateBlock ? " " : " ";
|
|
7741
|
-
let headerComponent = `'use client'
|
|
7742
|
-
|
|
7743
|
-
${reactImport}${importBlock}
|
|
7744
|
-
export function Header() {
|
|
7745
|
-
${stateBlock}${returnIndent}return (
|
|
7746
|
-
${headerJsx}
|
|
7747
|
-
)
|
|
7748
|
-
}
|
|
7749
|
-
`;
|
|
7750
|
-
headerComponent = addActiveNavToHeader(headerComponent);
|
|
7751
|
-
await generateSharedComponent3(projectRoot, {
|
|
7752
|
-
name: "Header",
|
|
7753
|
-
type: "layout",
|
|
7754
|
-
code: headerComponent,
|
|
7755
|
-
description: "Main site header/navigation",
|
|
7756
|
-
usedIn: ["app/layout.tsx"]
|
|
7757
|
-
});
|
|
7758
|
-
extracted = true;
|
|
7759
|
-
}
|
|
7760
|
-
}
|
|
7761
|
-
if (!hasSharedFooter) {
|
|
7762
|
-
const footerJsx = extractBalancedTag(sourceCode, "footer");
|
|
7763
|
-
if (footerJsx) {
|
|
7764
|
-
const imports = extractRelevantImports(sourceCode, footerJsx);
|
|
7765
|
-
const importBlock = imports.length > 0 ? imports.join("\n") + "\n" : "import Link from 'next/link'\n";
|
|
7766
|
-
const stateHooks = extractStateHooks(sourceCode, footerJsx);
|
|
7767
|
-
const needsReactImport = stateHooks.length > 0 && !importBlock.includes("from 'react'");
|
|
7768
|
-
const reactImport = needsReactImport ? "import { useState } from 'react'\n" : "";
|
|
7769
|
-
const stateBlock = stateHooks.length > 0 ? " " + stateHooks.join("\n ") + "\n" : "";
|
|
7770
|
-
const returnIndent = stateBlock ? " " : " ";
|
|
7771
|
-
const footerComponent = `'use client'
|
|
7772
|
-
|
|
7773
|
-
${reactImport}${importBlock}
|
|
7774
|
-
export function Footer() {
|
|
7775
|
-
${stateBlock}${returnIndent}return (
|
|
7776
|
-
${footerJsx}
|
|
7777
|
-
)
|
|
7778
|
-
}
|
|
7779
|
-
`;
|
|
7780
|
-
await generateSharedComponent3(projectRoot, {
|
|
7781
|
-
name: "Footer",
|
|
7782
|
-
type: "layout",
|
|
7783
|
-
code: footerComponent,
|
|
7784
|
-
description: "Site footer",
|
|
7785
|
-
usedIn: ["app/layout.tsx"]
|
|
7786
|
-
});
|
|
7787
|
-
extracted = true;
|
|
7788
|
-
}
|
|
7789
|
-
}
|
|
7790
|
-
if (!extracted) return false;
|
|
7791
|
-
await integrateSharedLayoutIntoRootLayout3(projectRoot);
|
|
7792
|
-
await ensureAuthRouteGroup(projectRoot);
|
|
7793
|
-
const allPageFiles = /* @__PURE__ */ new Set([...generatedPageFiles, ...findAllPageFiles(resolve8(projectRoot, "app"))]);
|
|
7794
|
-
for (const file of allPageFiles) {
|
|
7795
|
-
try {
|
|
7796
|
-
let code = await readFile(file);
|
|
7797
|
-
const original = code;
|
|
7798
|
-
const headerBlock = extractBalancedTag(code, "header");
|
|
7799
|
-
if (headerBlock) {
|
|
7800
|
-
code = code.replace(headerBlock, "");
|
|
7801
|
-
} else {
|
|
7802
|
-
const navBlock = extractBalancedTag(code, "nav");
|
|
7803
|
-
if (navBlock) code = code.replace(navBlock, "");
|
|
7804
|
-
}
|
|
7805
|
-
const footerBlock = extractBalancedTag(code, "footer");
|
|
7806
|
-
if (footerBlock) code = code.replace(footerBlock, "");
|
|
7807
|
-
code = code.replace(/\n{3,}/g, "\n\n");
|
|
7808
|
-
if (code !== original) await writeFile(file, code);
|
|
7809
|
-
} catch {
|
|
7810
|
-
continue;
|
|
7811
|
-
}
|
|
7812
|
-
}
|
|
7813
|
-
console.log(chalk11.cyan(" \u{1F517} Extracted Header and Footer as shared components (all pages via layout)"));
|
|
7814
|
-
return true;
|
|
7815
|
-
}
|
|
7816
|
-
|
|
7817
7691
|
// src/commands/chat/interactive.ts
|
|
7818
7692
|
import chalk12 from "chalk";
|
|
7819
|
-
import { resolve as
|
|
7820
|
-
import { existsSync as existsSync15, readFileSync as
|
|
7693
|
+
import { resolve as resolve8 } from "path";
|
|
7694
|
+
import { existsSync as existsSync15, readFileSync as readFileSync9, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
7821
7695
|
import { DesignSystemManager as DesignSystemManager6, ComponentManager as ComponentManager3, loadManifest as loadManifest7 } from "@getcoherent/core";
|
|
7822
7696
|
var DEBUG3 = process.env.COHERENT_DEBUG === "1";
|
|
7823
7697
|
async function interactiveChat(options, chatCommandFn) {
|
|
@@ -7837,13 +7711,13 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
7837
7711
|
\u274C Invalid provider: ${options.provider}`));
|
|
7838
7712
|
process.exit(1);
|
|
7839
7713
|
}
|
|
7840
|
-
const historyDir =
|
|
7841
|
-
const historyFile =
|
|
7714
|
+
const historyDir = resolve8(homedir2(), ".coherent");
|
|
7715
|
+
const historyFile = resolve8(historyDir, "history");
|
|
7842
7716
|
let history = [];
|
|
7843
7717
|
try {
|
|
7844
7718
|
mkdirSync5(historyDir, { recursive: true });
|
|
7845
7719
|
if (existsSync15(historyFile)) {
|
|
7846
|
-
history =
|
|
7720
|
+
history = readFileSync9(historyFile, "utf-8").split("\n").filter(Boolean).slice(-200);
|
|
7847
7721
|
}
|
|
7848
7722
|
} catch (e) {
|
|
7849
7723
|
if (DEBUG3) console.error("Failed to load REPL history:", e);
|
|
@@ -8067,7 +7941,7 @@ async function chatCommand(message, options) {
|
|
|
8067
7941
|
}
|
|
8068
7942
|
if (/switch to light mode|default to light|make.*light.*(default|theme)|light theme/i.test(message)) {
|
|
8069
7943
|
spinner.start("Setting default theme to light...");
|
|
8070
|
-
const layoutPath =
|
|
7944
|
+
const layoutPath = resolve9(projectRoot, "app/layout.tsx");
|
|
8071
7945
|
try {
|
|
8072
7946
|
let layout = await readFile(layoutPath);
|
|
8073
7947
|
layout = layout.replace(/className="dark"/, "");
|
|
@@ -8106,7 +7980,7 @@ async function chatCommand(message, options) {
|
|
|
8106
7980
|
spinner.start("Parsing your request...");
|
|
8107
7981
|
let manifest = await loadManifest8(project.root);
|
|
8108
7982
|
const validShared = manifest.shared.filter((s) => {
|
|
8109
|
-
const fp =
|
|
7983
|
+
const fp = resolve9(project.root, s.file);
|
|
8110
7984
|
return existsSync16(fp);
|
|
8111
7985
|
});
|
|
8112
7986
|
if (validShared.length !== manifest.shared.length) {
|
|
@@ -8402,21 +8276,6 @@ async function chatCommand(message, options) {
|
|
|
8402
8276
|
const result = await applyModification(request, dsm, cm, pm, projectRoot, provider, message);
|
|
8403
8277
|
results.push(result);
|
|
8404
8278
|
}
|
|
8405
|
-
const anyPageGenerated = normalizedRequests.some(
|
|
8406
|
-
(req, i) => (req.type === "add-page" || req.type === "update-page") && results[i]?.success
|
|
8407
|
-
);
|
|
8408
|
-
if (anyPageGenerated) {
|
|
8409
|
-
const generatedPageFiles = normalizedRequests.filter((req, i) => (req.type === "add-page" || req.type === "update-page") && results[i]?.success).map((req) => {
|
|
8410
|
-
const page = req.changes;
|
|
8411
|
-
const route = page.route || `/${page.id || "page"}`;
|
|
8412
|
-
return routeToFsPath(projectRoot, route, isAuthRoute(route));
|
|
8413
|
-
}).filter((f) => existsSync16(f));
|
|
8414
|
-
try {
|
|
8415
|
-
await extractAndShareLayoutComponents(projectRoot, generatedPageFiles);
|
|
8416
|
-
} catch (err) {
|
|
8417
|
-
if (DEBUG4) console.log(chalk13.dim("Shared layout extraction failed:", err));
|
|
8418
|
-
}
|
|
8419
|
-
}
|
|
8420
8279
|
const currentConfig = dsm.getConfig();
|
|
8421
8280
|
const autoScaffoldEnabled = currentConfig.settings.autoScaffold === true;
|
|
8422
8281
|
const scaffoldedPages = [];
|
|
@@ -8430,7 +8289,7 @@ async function chatCommand(message, options) {
|
|
|
8430
8289
|
let pageCode = "";
|
|
8431
8290
|
if (existsSync16(pageFilePath)) {
|
|
8432
8291
|
try {
|
|
8433
|
-
pageCode =
|
|
8292
|
+
pageCode = readFileSync10(pageFilePath, "utf-8");
|
|
8434
8293
|
} catch {
|
|
8435
8294
|
}
|
|
8436
8295
|
}
|
|
@@ -8507,7 +8366,7 @@ async function chatCommand(message, options) {
|
|
|
8507
8366
|
const isAuth = isAuthRoute(linkedRoute);
|
|
8508
8367
|
const filePath = routeToFsPath(projectRoot, linkedRoute, isAuth);
|
|
8509
8368
|
if (isAuth) await ensureAuthRouteGroup(projectRoot);
|
|
8510
|
-
const dir =
|
|
8369
|
+
const dir = resolve9(filePath, "..");
|
|
8511
8370
|
if (!existsSync16(dir)) {
|
|
8512
8371
|
mkdirSync6(dir, { recursive: true });
|
|
8513
8372
|
}
|
|
@@ -8543,7 +8402,7 @@ async function chatCommand(message, options) {
|
|
|
8543
8402
|
dsm.updateConfig(latestConfig);
|
|
8544
8403
|
if (DEBUG4) console.log(chalk13.dim(` [theme] Set defaultMode to "${targetMode}"`));
|
|
8545
8404
|
}
|
|
8546
|
-
const layoutPath =
|
|
8405
|
+
const layoutPath = resolve9(projectRoot, "app", "layout.tsx");
|
|
8547
8406
|
try {
|
|
8548
8407
|
let layoutCode = await readFile(layoutPath);
|
|
8549
8408
|
if (targetMode === "dark" && !layoutCode.includes('className="dark"')) {
|
|
@@ -8598,7 +8457,7 @@ async function chatCommand(message, options) {
|
|
|
8598
8457
|
console.log("");
|
|
8599
8458
|
}
|
|
8600
8459
|
if (uxRecommendations) {
|
|
8601
|
-
const recPath =
|
|
8460
|
+
const recPath = resolve9(projectRoot, "recommendations.md");
|
|
8602
8461
|
const section = `
|
|
8603
8462
|
|
|
8604
8463
|
---
|
|
@@ -8682,19 +8541,19 @@ ${uxRecommendations}
|
|
|
8682
8541
|
import chalk14 from "chalk";
|
|
8683
8542
|
import ora3 from "ora";
|
|
8684
8543
|
import { spawn } from "child_process";
|
|
8685
|
-
import { existsSync as existsSync19, rmSync as rmSync3, readFileSync as
|
|
8686
|
-
import { resolve as
|
|
8544
|
+
import { existsSync as existsSync19, rmSync as rmSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync10 } from "fs";
|
|
8545
|
+
import { resolve as resolve10, join as join11 } from "path";
|
|
8687
8546
|
import { readdir as readdir2 } from "fs/promises";
|
|
8688
8547
|
import { DesignSystemManager as DesignSystemManager8, ComponentGenerator as ComponentGenerator3 } from "@getcoherent/core";
|
|
8689
8548
|
|
|
8690
8549
|
// src/utils/file-watcher.ts
|
|
8691
|
-
import { readFileSync as
|
|
8692
|
-
import { relative as relative3, join as
|
|
8550
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync18 } from "fs";
|
|
8551
|
+
import { relative as relative3, join as join10 } from "path";
|
|
8693
8552
|
import { loadManifest as loadManifest9, saveManifest as saveManifest3 } from "@getcoherent/core";
|
|
8694
8553
|
|
|
8695
8554
|
// src/utils/component-integrity.ts
|
|
8696
|
-
import { existsSync as existsSync17, readFileSync as
|
|
8697
|
-
import { join as
|
|
8555
|
+
import { existsSync as existsSync17, readFileSync as readFileSync11, readdirSync as readdirSync2 } from "fs";
|
|
8556
|
+
import { join as join9, relative as relative2 } from "path";
|
|
8698
8557
|
function extractExportedComponentNames(code) {
|
|
8699
8558
|
const names = [];
|
|
8700
8559
|
let m;
|
|
@@ -8720,14 +8579,14 @@ function arraysEqual(a, b) {
|
|
|
8720
8579
|
}
|
|
8721
8580
|
function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
8722
8581
|
const results = [];
|
|
8723
|
-
const appDir =
|
|
8582
|
+
const appDir = join9(projectRoot, "app");
|
|
8724
8583
|
if (!existsSync17(appDir)) return results;
|
|
8725
8584
|
const pageFiles = collectFiles(appDir, (name) => name === "page.tsx" || name === "page.jsx");
|
|
8726
8585
|
const componentImportPath = componentFile.replace(/\.tsx$/, "").replace(/\.jsx$/, "");
|
|
8727
8586
|
for (const absPath of pageFiles) {
|
|
8728
8587
|
if (absPath.includes("design-system")) continue;
|
|
8729
8588
|
try {
|
|
8730
|
-
const code =
|
|
8589
|
+
const code = readFileSync11(absPath, "utf-8");
|
|
8731
8590
|
const hasNamedImport = new RegExp(`import\\s+\\{[^}]*\\b${componentName}\\b[^}]*\\}\\s+from\\s+['"]`).test(code);
|
|
8732
8591
|
const hasDefaultImport = new RegExp(`import\\s+${componentName}\\s+from\\s+['"]`).test(code);
|
|
8733
8592
|
const hasPathImport = code.includes(`@/${componentImportPath}`);
|
|
@@ -8740,10 +8599,10 @@ function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
|
8740
8599
|
return results;
|
|
8741
8600
|
}
|
|
8742
8601
|
function isUsedInLayout(projectRoot, componentName) {
|
|
8743
|
-
const layoutPath =
|
|
8602
|
+
const layoutPath = join9(projectRoot, "app", "layout.tsx");
|
|
8744
8603
|
if (!existsSync17(layoutPath)) return false;
|
|
8745
8604
|
try {
|
|
8746
|
-
const code =
|
|
8605
|
+
const code = readFileSync11(layoutPath, "utf-8");
|
|
8747
8606
|
return code.includes(componentName);
|
|
8748
8607
|
} catch {
|
|
8749
8608
|
return false;
|
|
@@ -8751,7 +8610,7 @@ function isUsedInLayout(projectRoot, componentName) {
|
|
|
8751
8610
|
}
|
|
8752
8611
|
function findUnregisteredComponents(projectRoot, manifest) {
|
|
8753
8612
|
const results = [];
|
|
8754
|
-
const componentsDir =
|
|
8613
|
+
const componentsDir = join9(projectRoot, "components");
|
|
8755
8614
|
if (!existsSync17(componentsDir)) return results;
|
|
8756
8615
|
const registeredFiles = new Set(manifest.shared.map((s) => s.file));
|
|
8757
8616
|
const registeredNames = new Set(manifest.shared.map((s) => s.name));
|
|
@@ -8764,7 +8623,7 @@ function findUnregisteredComponents(projectRoot, manifest) {
|
|
|
8764
8623
|
const relFile = relative2(projectRoot, absPath);
|
|
8765
8624
|
if (registeredFiles.has(relFile)) continue;
|
|
8766
8625
|
try {
|
|
8767
|
-
const code =
|
|
8626
|
+
const code = readFileSync11(absPath, "utf-8");
|
|
8768
8627
|
const exports = extractExportedComponentNames(code);
|
|
8769
8628
|
for (const name of exports) {
|
|
8770
8629
|
if (registeredNames.has(name)) continue;
|
|
@@ -8779,14 +8638,14 @@ function findUnregisteredComponents(projectRoot, manifest) {
|
|
|
8779
8638
|
}
|
|
8780
8639
|
function findInlineDuplicates(projectRoot, manifest) {
|
|
8781
8640
|
const results = [];
|
|
8782
|
-
const appDir =
|
|
8641
|
+
const appDir = join9(projectRoot, "app");
|
|
8783
8642
|
if (!existsSync17(appDir)) return results;
|
|
8784
8643
|
const pageFiles = collectFiles(appDir, (name) => name === "page.tsx" || name === "page.jsx");
|
|
8785
8644
|
for (const absPath of pageFiles) {
|
|
8786
8645
|
if (absPath.includes("design-system")) continue;
|
|
8787
8646
|
let code;
|
|
8788
8647
|
try {
|
|
8789
|
-
code =
|
|
8648
|
+
code = readFileSync11(absPath, "utf-8");
|
|
8790
8649
|
} catch {
|
|
8791
8650
|
continue;
|
|
8792
8651
|
}
|
|
@@ -8809,7 +8668,7 @@ function findInlineDuplicates(projectRoot, manifest) {
|
|
|
8809
8668
|
return results;
|
|
8810
8669
|
}
|
|
8811
8670
|
function findComponentFileByExportName(projectRoot, componentName) {
|
|
8812
|
-
const componentsDir =
|
|
8671
|
+
const componentsDir = join9(projectRoot, "components");
|
|
8813
8672
|
if (!existsSync17(componentsDir)) return null;
|
|
8814
8673
|
const files = collectFiles(
|
|
8815
8674
|
componentsDir,
|
|
@@ -8818,7 +8677,7 @@ function findComponentFileByExportName(projectRoot, componentName) {
|
|
|
8818
8677
|
);
|
|
8819
8678
|
for (const absPath of files) {
|
|
8820
8679
|
try {
|
|
8821
|
-
const code =
|
|
8680
|
+
const code = readFileSync11(absPath, "utf-8");
|
|
8822
8681
|
const exports = extractExportedComponentNames(code);
|
|
8823
8682
|
if (exports.includes(componentName)) {
|
|
8824
8683
|
return relative2(projectRoot, absPath);
|
|
@@ -8831,7 +8690,7 @@ function findComponentFileByExportName(projectRoot, componentName) {
|
|
|
8831
8690
|
function removeOrphanedEntries(projectRoot, manifest) {
|
|
8832
8691
|
const removed = [];
|
|
8833
8692
|
const valid = manifest.shared.filter((entry) => {
|
|
8834
|
-
const filePath =
|
|
8693
|
+
const filePath = join9(projectRoot, entry.file);
|
|
8835
8694
|
if (existsSync17(filePath)) return true;
|
|
8836
8695
|
removed.push({ id: entry.id, name: entry.name });
|
|
8837
8696
|
return false;
|
|
@@ -8850,7 +8709,7 @@ function reconcileComponents(projectRoot, manifest) {
|
|
|
8850
8709
|
};
|
|
8851
8710
|
const m = { ...manifest, shared: [...manifest.shared], nextId: manifest.nextId };
|
|
8852
8711
|
m.shared = m.shared.filter((entry) => {
|
|
8853
|
-
const filePath =
|
|
8712
|
+
const filePath = join9(projectRoot, entry.file);
|
|
8854
8713
|
if (!existsSync17(filePath)) {
|
|
8855
8714
|
const newPath = findComponentFileByExportName(projectRoot, entry.name);
|
|
8856
8715
|
if (newPath) {
|
|
@@ -8863,7 +8722,7 @@ function reconcileComponents(projectRoot, manifest) {
|
|
|
8863
8722
|
}
|
|
8864
8723
|
let code;
|
|
8865
8724
|
try {
|
|
8866
|
-
code =
|
|
8725
|
+
code = readFileSync11(join9(projectRoot, entry.file), "utf-8");
|
|
8867
8726
|
} catch {
|
|
8868
8727
|
return true;
|
|
8869
8728
|
}
|
|
@@ -8940,12 +8799,12 @@ function collectFiles(dir, filter, skipDirs = []) {
|
|
|
8940
8799
|
function walk(d) {
|
|
8941
8800
|
let entries;
|
|
8942
8801
|
try {
|
|
8943
|
-
entries =
|
|
8802
|
+
entries = readdirSync2(d, { withFileTypes: true });
|
|
8944
8803
|
} catch {
|
|
8945
8804
|
return;
|
|
8946
8805
|
}
|
|
8947
8806
|
for (const e of entries) {
|
|
8948
|
-
const full =
|
|
8807
|
+
const full = join9(d, e.name);
|
|
8949
8808
|
if (e.isDirectory()) {
|
|
8950
8809
|
if (skipDirs.includes(e.name) || e.name.startsWith(".")) continue;
|
|
8951
8810
|
walk(full);
|
|
@@ -8984,9 +8843,9 @@ function findInlineDuplicatesOfShared(content, manifest) {
|
|
|
8984
8843
|
}
|
|
8985
8844
|
function getWatcherConfig(projectRoot) {
|
|
8986
8845
|
try {
|
|
8987
|
-
const pkgPath =
|
|
8846
|
+
const pkgPath = join10(projectRoot, "package.json");
|
|
8988
8847
|
if (!existsSync18(pkgPath)) return defaultWatcherConfig();
|
|
8989
|
-
const pkg = JSON.parse(
|
|
8848
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
8990
8849
|
const c = pkg?.coherent?.watcher ?? {};
|
|
8991
8850
|
return {
|
|
8992
8851
|
enabled: c.enabled !== false,
|
|
@@ -9014,7 +8873,7 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
9014
8873
|
if (relativePath.includes("node_modules") || relativePath.includes(".next")) return;
|
|
9015
8874
|
let content;
|
|
9016
8875
|
try {
|
|
9017
|
-
content =
|
|
8876
|
+
content = readFileSync12(filePath, "utf-8");
|
|
9018
8877
|
} catch {
|
|
9019
8878
|
return;
|
|
9020
8879
|
}
|
|
@@ -9087,7 +8946,7 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
9087
8946
|
const manifest = await loadManifest9(projectRoot);
|
|
9088
8947
|
const alreadyRegistered = manifest.shared.some((s) => s.file === relativePath);
|
|
9089
8948
|
if (alreadyRegistered) return;
|
|
9090
|
-
const code =
|
|
8949
|
+
const code = readFileSync12(filePath, "utf-8");
|
|
9091
8950
|
const exports = extractExportedComponentNames(code);
|
|
9092
8951
|
if (exports.length > 0) {
|
|
9093
8952
|
const alreadyByName = exports.every((n) => manifest.shared.some((s) => s.name === n));
|
|
@@ -9113,8 +8972,8 @@ function startFileWatcher(projectRoot) {
|
|
|
9113
8972
|
let watcher = null;
|
|
9114
8973
|
let manifestWatcher = null;
|
|
9115
8974
|
import("chokidar").then((chokidar) => {
|
|
9116
|
-
const appGlob =
|
|
9117
|
-
const compGlob =
|
|
8975
|
+
const appGlob = join10(projectRoot, "app", "**", "*.tsx");
|
|
8976
|
+
const compGlob = join10(projectRoot, "components", "**", "*.tsx");
|
|
9118
8977
|
watcher = chokidar.default.watch([appGlob, compGlob], {
|
|
9119
8978
|
ignoreInitial: true,
|
|
9120
8979
|
awaitWriteFinish: { stabilityThreshold: 500 }
|
|
@@ -9126,7 +8985,7 @@ function startFileWatcher(projectRoot) {
|
|
|
9126
8985
|
});
|
|
9127
8986
|
watcher.on("unlink", (fp) => handleFileDelete(projectRoot, fp));
|
|
9128
8987
|
});
|
|
9129
|
-
const manifestPath =
|
|
8988
|
+
const manifestPath = join10(projectRoot, "coherent.components.json");
|
|
9130
8989
|
if (existsSync18(manifestPath)) {
|
|
9131
8990
|
import("chokidar").then((chokidar) => {
|
|
9132
8991
|
manifestWatcher = chokidar.default.watch(manifestPath, { ignoreInitial: true });
|
|
@@ -9141,7 +9000,7 @@ function startFileWatcher(projectRoot) {
|
|
|
9141
9000
|
|
|
9142
9001
|
// src/commands/preview.ts
|
|
9143
9002
|
function getPackageManager(projectRoot) {
|
|
9144
|
-
const hasPnpm = existsSync19(
|
|
9003
|
+
const hasPnpm = existsSync19(resolve10(projectRoot, "pnpm-lock.yaml"));
|
|
9145
9004
|
return hasPnpm ? "pnpm" : "npm";
|
|
9146
9005
|
}
|
|
9147
9006
|
function runInstall(projectRoot) {
|
|
@@ -9163,8 +9022,8 @@ function runInstall(projectRoot) {
|
|
|
9163
9022
|
});
|
|
9164
9023
|
}
|
|
9165
9024
|
function checkProjectInitialized(projectRoot) {
|
|
9166
|
-
const configPath =
|
|
9167
|
-
const packageJsonPath =
|
|
9025
|
+
const configPath = resolve10(projectRoot, "design-system.config.ts");
|
|
9026
|
+
const packageJsonPath = resolve10(projectRoot, "package.json");
|
|
9168
9027
|
if (!existsSync19(configPath)) {
|
|
9169
9028
|
return false;
|
|
9170
9029
|
}
|
|
@@ -9174,11 +9033,11 @@ function checkProjectInitialized(projectRoot) {
|
|
|
9174
9033
|
return true;
|
|
9175
9034
|
}
|
|
9176
9035
|
function checkDependenciesInstalled(projectRoot) {
|
|
9177
|
-
const nodeModulesPath =
|
|
9036
|
+
const nodeModulesPath = resolve10(projectRoot, "node_modules");
|
|
9178
9037
|
return existsSync19(nodeModulesPath);
|
|
9179
9038
|
}
|
|
9180
9039
|
function clearStaleCache(projectRoot) {
|
|
9181
|
-
const nextDir =
|
|
9040
|
+
const nextDir = join11(projectRoot, ".next");
|
|
9182
9041
|
if (existsSync19(nextDir)) {
|
|
9183
9042
|
rmSync3(nextDir, { recursive: true, force: true });
|
|
9184
9043
|
console.log(chalk14.dim(" \u2714 Cleared stale build cache"));
|
|
@@ -9199,7 +9058,7 @@ async function listPageFiles(appDir) {
|
|
|
9199
9058
|
async function walk(dir) {
|
|
9200
9059
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
9201
9060
|
for (const e of entries) {
|
|
9202
|
-
const full =
|
|
9061
|
+
const full = join11(dir, e.name);
|
|
9203
9062
|
if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "api" && e.name !== "design-system") await walk(full);
|
|
9204
9063
|
else if (e.isFile() && e.name === "page.tsx") out.push(full);
|
|
9205
9064
|
}
|
|
@@ -9208,10 +9067,10 @@ async function listPageFiles(appDir) {
|
|
|
9208
9067
|
return out;
|
|
9209
9068
|
}
|
|
9210
9069
|
async function validateSyntax(projectRoot) {
|
|
9211
|
-
const appDir =
|
|
9070
|
+
const appDir = join11(projectRoot, "app");
|
|
9212
9071
|
const pages = await listPageFiles(appDir);
|
|
9213
9072
|
for (const file of pages) {
|
|
9214
|
-
const content =
|
|
9073
|
+
const content = readFileSync13(file, "utf-8");
|
|
9215
9074
|
const fixed = fixUnescapedLtInJsx(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)));
|
|
9216
9075
|
if (fixed !== content) {
|
|
9217
9076
|
writeFileSync10(file, fixed, "utf-8");
|
|
@@ -9220,13 +9079,13 @@ async function validateSyntax(projectRoot) {
|
|
|
9220
9079
|
}
|
|
9221
9080
|
}
|
|
9222
9081
|
async function fixMissingComponentExports(projectRoot) {
|
|
9223
|
-
const appDir =
|
|
9224
|
-
const uiDir =
|
|
9082
|
+
const appDir = join11(projectRoot, "app");
|
|
9083
|
+
const uiDir = join11(projectRoot, "components", "ui");
|
|
9225
9084
|
if (!existsSync19(appDir) || !existsSync19(uiDir)) return;
|
|
9226
9085
|
const pages = await listPageFiles(appDir);
|
|
9227
9086
|
const neededExports = /* @__PURE__ */ new Map();
|
|
9228
9087
|
for (const file of pages) {
|
|
9229
|
-
const content =
|
|
9088
|
+
const content = readFileSync13(file, "utf-8");
|
|
9230
9089
|
const importRe = /import\s*\{([^}]+)\}\s*from\s*['"]@\/components\/ui\/([^'"]+)['"]/g;
|
|
9231
9090
|
let m;
|
|
9232
9091
|
while ((m = importRe.exec(content)) !== null) {
|
|
@@ -9236,7 +9095,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
9236
9095
|
for (const name of names) neededExports.get(componentId).add(name);
|
|
9237
9096
|
}
|
|
9238
9097
|
}
|
|
9239
|
-
const configPath =
|
|
9098
|
+
const configPath = join11(projectRoot, "design-system.config.ts");
|
|
9240
9099
|
let config2 = null;
|
|
9241
9100
|
try {
|
|
9242
9101
|
const mgr = new DesignSystemManager8(configPath);
|
|
@@ -9245,7 +9104,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
9245
9104
|
}
|
|
9246
9105
|
const generator = new ComponentGenerator3(config2 || { components: [], pages: [], tokens: {} });
|
|
9247
9106
|
for (const [componentId, needed] of neededExports) {
|
|
9248
|
-
const componentFile =
|
|
9107
|
+
const componentFile = join11(uiDir, `${componentId}.tsx`);
|
|
9249
9108
|
const def = getShadcnComponent(componentId);
|
|
9250
9109
|
if (!existsSync19(componentFile)) {
|
|
9251
9110
|
if (!def) continue;
|
|
@@ -9259,7 +9118,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
9259
9118
|
}
|
|
9260
9119
|
continue;
|
|
9261
9120
|
}
|
|
9262
|
-
const content =
|
|
9121
|
+
const content = readFileSync13(componentFile, "utf-8");
|
|
9263
9122
|
const exportRe = /export\s+(?:const|function|class)\s+(\w+)|export\s*\{([^}]+)\}/g;
|
|
9264
9123
|
const existingExports = /* @__PURE__ */ new Set();
|
|
9265
9124
|
let em;
|
|
@@ -9282,7 +9141,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
9282
9141
|
}
|
|
9283
9142
|
}
|
|
9284
9143
|
async function backfillPageAnalysis(projectRoot) {
|
|
9285
|
-
const configPath =
|
|
9144
|
+
const configPath = join11(projectRoot, "design-system.config.ts");
|
|
9286
9145
|
if (!existsSync19(configPath)) return;
|
|
9287
9146
|
try {
|
|
9288
9147
|
const mgr = new DesignSystemManager8(configPath);
|
|
@@ -9294,14 +9153,14 @@ async function backfillPageAnalysis(projectRoot) {
|
|
|
9294
9153
|
const isAuth = route.includes("login") || route.includes("register") || route.includes("signup") || route.includes("sign-up");
|
|
9295
9154
|
let filePath;
|
|
9296
9155
|
if (route === "/") {
|
|
9297
|
-
filePath =
|
|
9156
|
+
filePath = join11(projectRoot, "app", "page.tsx");
|
|
9298
9157
|
} else if (isAuth) {
|
|
9299
|
-
filePath =
|
|
9158
|
+
filePath = join11(projectRoot, "app", "(auth)", route.slice(1), "page.tsx");
|
|
9300
9159
|
} else {
|
|
9301
|
-
filePath =
|
|
9160
|
+
filePath = join11(projectRoot, "app", route.slice(1), "page.tsx");
|
|
9302
9161
|
}
|
|
9303
9162
|
if (!existsSync19(filePath)) continue;
|
|
9304
|
-
const code =
|
|
9163
|
+
const code = readFileSync13(filePath, "utf-8");
|
|
9305
9164
|
if (code.length < 50) continue;
|
|
9306
9165
|
page.pageAnalysis = analyzePageCode(code);
|
|
9307
9166
|
changed = true;
|
|
@@ -9328,7 +9187,7 @@ async function autoInstallShadcnComponent(componentId, projectRoot) {
|
|
|
9328
9187
|
const def = getShadcnComponent(componentId);
|
|
9329
9188
|
if (!def) return false;
|
|
9330
9189
|
try {
|
|
9331
|
-
const configPath =
|
|
9190
|
+
const configPath = join11(projectRoot, "design-system.config.ts");
|
|
9332
9191
|
let config2 = null;
|
|
9333
9192
|
try {
|
|
9334
9193
|
const mgr = new DesignSystemManager8(configPath);
|
|
@@ -9337,10 +9196,10 @@ async function autoInstallShadcnComponent(componentId, projectRoot) {
|
|
|
9337
9196
|
}
|
|
9338
9197
|
const generator = new ComponentGenerator3(config2 || { components: [], pages: [], tokens: {} });
|
|
9339
9198
|
const code = await generator.generate(def);
|
|
9340
|
-
const uiDir =
|
|
9199
|
+
const uiDir = join11(projectRoot, "components", "ui");
|
|
9341
9200
|
const { mkdirSync: mkdirSync9 } = await import("fs");
|
|
9342
9201
|
mkdirSync9(uiDir, { recursive: true });
|
|
9343
|
-
writeFileSync10(
|
|
9202
|
+
writeFileSync10(join11(uiDir, `${componentId}.tsx`), code, "utf-8");
|
|
9344
9203
|
return true;
|
|
9345
9204
|
} catch {
|
|
9346
9205
|
return false;
|
|
@@ -9463,12 +9322,12 @@ async function openBrowser(url) {
|
|
|
9463
9322
|
}
|
|
9464
9323
|
}
|
|
9465
9324
|
function startDevServer(projectRoot) {
|
|
9466
|
-
const packageJsonPath =
|
|
9325
|
+
const packageJsonPath = resolve10(projectRoot, "package.json");
|
|
9467
9326
|
if (!existsSync19(packageJsonPath)) {
|
|
9468
9327
|
throw new Error('package.json not found. Run "coherent init" first.');
|
|
9469
9328
|
}
|
|
9470
|
-
const hasPnpm = existsSync19(
|
|
9471
|
-
const hasNpm = existsSync19(
|
|
9329
|
+
const hasPnpm = existsSync19(resolve10(projectRoot, "pnpm-lock.yaml"));
|
|
9330
|
+
const hasNpm = existsSync19(resolve10(projectRoot, "package-lock.json"));
|
|
9472
9331
|
const command = hasPnpm ? "pnpm" : hasNpm ? "npm" : "npx";
|
|
9473
9332
|
const args = hasPnpm ? ["dev", "--turbo"] : hasNpm ? ["run", "dev", "--", "--turbo"] : ["next", "dev", "--turbo"];
|
|
9474
9333
|
const child = spawn(command, args, {
|
|
@@ -9515,7 +9374,7 @@ async function previewCommand() {
|
|
|
9515
9374
|
if (needsGlobalsFix(projectRoot)) {
|
|
9516
9375
|
spinner.text = "Fixing globals.css...";
|
|
9517
9376
|
try {
|
|
9518
|
-
const dsm = new DesignSystemManager8(
|
|
9377
|
+
const dsm = new DesignSystemManager8(resolve10(projectRoot, "design-system.config.ts"));
|
|
9519
9378
|
await dsm.load();
|
|
9520
9379
|
const config2 = dsm.getConfig();
|
|
9521
9380
|
fixGlobalsCss(projectRoot, config2);
|
|
@@ -9554,8 +9413,8 @@ async function previewCommand() {
|
|
|
9554
9413
|
import chalk15 from "chalk";
|
|
9555
9414
|
import ora4 from "ora";
|
|
9556
9415
|
import { spawn as spawn2 } from "child_process";
|
|
9557
|
-
import { existsSync as existsSync20, rmSync as rmSync4, readdirSync as
|
|
9558
|
-
import { resolve as
|
|
9416
|
+
import { existsSync as existsSync20, rmSync as rmSync4, readdirSync as readdirSync3 } from "fs";
|
|
9417
|
+
import { resolve as resolve11, join as join12, dirname as dirname7 } from "path";
|
|
9559
9418
|
import { readdir as readdir3, readFile as readFile5, writeFile as writeFile4, mkdir as mkdir4, copyFile as copyFile2 } from "fs/promises";
|
|
9560
9419
|
var COPY_EXCLUDE = /* @__PURE__ */ new Set([
|
|
9561
9420
|
"node_modules",
|
|
@@ -9577,8 +9436,8 @@ async function copyDir(src, dest) {
|
|
|
9577
9436
|
await mkdir4(dest, { recursive: true });
|
|
9578
9437
|
const entries = await readdir3(src, { withFileTypes: true });
|
|
9579
9438
|
for (const e of entries) {
|
|
9580
|
-
const srcPath =
|
|
9581
|
-
const destPath =
|
|
9439
|
+
const srcPath = join12(src, e.name);
|
|
9440
|
+
const destPath = join12(dest, e.name);
|
|
9582
9441
|
if (COPY_EXCLUDE.has(e.name)) continue;
|
|
9583
9442
|
if (e.isDirectory()) {
|
|
9584
9443
|
await copyDir(srcPath, destPath);
|
|
@@ -9589,16 +9448,16 @@ async function copyDir(src, dest) {
|
|
|
9589
9448
|
}
|
|
9590
9449
|
}
|
|
9591
9450
|
function checkProjectInitialized2(projectRoot) {
|
|
9592
|
-
return existsSync20(
|
|
9451
|
+
return existsSync20(resolve11(projectRoot, "design-system.config.ts")) && existsSync20(resolve11(projectRoot, "package.json"));
|
|
9593
9452
|
}
|
|
9594
9453
|
function getPackageManager2(projectRoot) {
|
|
9595
|
-
if (existsSync20(
|
|
9596
|
-
if (existsSync20(
|
|
9454
|
+
if (existsSync20(resolve11(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
9455
|
+
if (existsSync20(resolve11(projectRoot, "package-lock.json"))) return "npm";
|
|
9597
9456
|
return "npx";
|
|
9598
9457
|
}
|
|
9599
9458
|
async function patchNextConfigForExport(outRoot) {
|
|
9600
9459
|
for (const name of ["next.config.ts", "next.config.mjs", "next.config.js"]) {
|
|
9601
|
-
const p =
|
|
9460
|
+
const p = join12(outRoot, name);
|
|
9602
9461
|
if (!existsSync20(p)) continue;
|
|
9603
9462
|
let content = await readFile5(p, "utf-8");
|
|
9604
9463
|
if (content.includes("ignoreDuringBuilds")) return;
|
|
@@ -9614,9 +9473,9 @@ async function buildProduction(projectRoot) {
|
|
|
9614
9473
|
const pm = getPackageManager2(projectRoot);
|
|
9615
9474
|
const command = pm === "pnpm" ? "pnpm" : pm === "npm" ? "npm" : "npx";
|
|
9616
9475
|
const args = pm === "npx" ? ["next", "build"] : ["run", "build"];
|
|
9617
|
-
return new Promise((
|
|
9476
|
+
return new Promise((resolve16, reject) => {
|
|
9618
9477
|
const child = spawn2(command, args, { cwd: projectRoot, stdio: "inherit", shell: true });
|
|
9619
|
-
child.on("exit", (code) => code === 0 ?
|
|
9478
|
+
child.on("exit", (code) => code === 0 ? resolve16() : reject(new Error(`Build failed with exit code ${code}`)));
|
|
9620
9479
|
child.on("error", (error) => reject(new Error(`Failed to start build: ${error.message}`)));
|
|
9621
9480
|
});
|
|
9622
9481
|
}
|
|
@@ -9640,7 +9499,7 @@ EXPOSE 3000
|
|
|
9640
9499
|
\`\`\`
|
|
9641
9500
|
`;
|
|
9642
9501
|
async function ensureReadmeDeploySection(outRoot) {
|
|
9643
|
-
const readmePath =
|
|
9502
|
+
const readmePath = join12(outRoot, "README.md");
|
|
9644
9503
|
if (!existsSync20(readmePath)) return;
|
|
9645
9504
|
try {
|
|
9646
9505
|
let content = await readFile5(readmePath, "utf-8");
|
|
@@ -9660,22 +9519,22 @@ async function countPages(outRoot) {
|
|
|
9660
9519
|
return;
|
|
9661
9520
|
}
|
|
9662
9521
|
for (const e of entries) {
|
|
9663
|
-
const full =
|
|
9522
|
+
const full = join12(dir, e.name);
|
|
9664
9523
|
if (e.isFile() && e.name === "page.tsx") n++;
|
|
9665
9524
|
else if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "api") await walk(full);
|
|
9666
9525
|
}
|
|
9667
9526
|
}
|
|
9668
|
-
const appDir =
|
|
9527
|
+
const appDir = join12(outRoot, "app");
|
|
9669
9528
|
if (existsSync20(appDir)) await walk(appDir);
|
|
9670
9529
|
return n;
|
|
9671
9530
|
}
|
|
9672
9531
|
function countComponents(outRoot) {
|
|
9673
9532
|
let n = 0;
|
|
9674
9533
|
for (const sub of ["ui", "shared"]) {
|
|
9675
|
-
const dir =
|
|
9534
|
+
const dir = join12(outRoot, "components", sub);
|
|
9676
9535
|
if (!existsSync20(dir)) continue;
|
|
9677
9536
|
try {
|
|
9678
|
-
n +=
|
|
9537
|
+
n += readdirSync3(dir).filter((f) => f.endsWith(".tsx") || f.endsWith(".jsx")).length;
|
|
9679
9538
|
} catch {
|
|
9680
9539
|
}
|
|
9681
9540
|
}
|
|
@@ -9688,7 +9547,7 @@ async function collectImportedPackages2(dir, extensions) {
|
|
|
9688
9547
|
async function walk(d) {
|
|
9689
9548
|
const entries = await readdir3(d, { withFileTypes: true });
|
|
9690
9549
|
for (const e of entries) {
|
|
9691
|
-
const full =
|
|
9550
|
+
const full = join12(d, e.name);
|
|
9692
9551
|
if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules") {
|
|
9693
9552
|
await walk(full);
|
|
9694
9553
|
continue;
|
|
@@ -9711,7 +9570,7 @@ async function collectImportedPackages2(dir, extensions) {
|
|
|
9711
9570
|
return packages;
|
|
9712
9571
|
}
|
|
9713
9572
|
async function findMissingDepsInExport(outRoot) {
|
|
9714
|
-
const pkgPath =
|
|
9573
|
+
const pkgPath = join12(outRoot, "package.json");
|
|
9715
9574
|
if (!existsSync20(pkgPath)) return [];
|
|
9716
9575
|
let pkg;
|
|
9717
9576
|
try {
|
|
@@ -9720,7 +9579,7 @@ async function findMissingDepsInExport(outRoot) {
|
|
|
9720
9579
|
return [];
|
|
9721
9580
|
}
|
|
9722
9581
|
const inDeps = /* @__PURE__ */ new Set([...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})]);
|
|
9723
|
-
const codeDirs = [
|
|
9582
|
+
const codeDirs = [join12(outRoot, "app"), join12(outRoot, "components")];
|
|
9724
9583
|
const extensions = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx"]);
|
|
9725
9584
|
const imported = /* @__PURE__ */ new Set();
|
|
9726
9585
|
for (const dir of codeDirs) {
|
|
@@ -9732,25 +9591,33 @@ async function findMissingDepsInExport(outRoot) {
|
|
|
9732
9591
|
async function stripCoherentArtifacts(outputDir) {
|
|
9733
9592
|
const removed = [];
|
|
9734
9593
|
for (const p of ["app/design-system", "app/api/design-system"]) {
|
|
9735
|
-
const full =
|
|
9594
|
+
const full = join12(outputDir, p);
|
|
9736
9595
|
if (existsSync20(full)) {
|
|
9737
9596
|
rmSync4(full, { recursive: true, force: true });
|
|
9738
9597
|
removed.push(p);
|
|
9739
9598
|
}
|
|
9740
9599
|
}
|
|
9741
|
-
const appNavPath =
|
|
9600
|
+
const appNavPath = join12(outputDir, "app", "AppNav.tsx");
|
|
9742
9601
|
if (existsSync20(appNavPath)) {
|
|
9743
9602
|
rmSync4(appNavPath, { force: true });
|
|
9744
9603
|
removed.push("app/AppNav.tsx");
|
|
9745
9604
|
}
|
|
9746
|
-
const layoutPath =
|
|
9605
|
+
const layoutPath = join12(outputDir, "app", "layout.tsx");
|
|
9747
9606
|
if (existsSync20(layoutPath)) {
|
|
9748
9607
|
let layout = await readFile5(layoutPath, "utf-8");
|
|
9749
9608
|
layout = layout.replace(/import\s*\{?\s*AppNav\s*\}?\s*from\s*['"][^'"]+['"]\s*\n?/g, "");
|
|
9750
9609
|
layout = layout.replace(/\s*<AppNav\s*\/?\s*>\s*/g, "\n");
|
|
9751
9610
|
await writeFile4(layoutPath, layout, "utf-8");
|
|
9752
9611
|
}
|
|
9753
|
-
const
|
|
9612
|
+
const sharedHeaderPath = join12(outputDir, "components", "shared", "header.tsx");
|
|
9613
|
+
if (existsSync20(sharedHeaderPath)) {
|
|
9614
|
+
let header = await readFile5(sharedHeaderPath, "utf-8");
|
|
9615
|
+
header = header.replace(/<Link\s[^>]*href="\/design-system"[^>]*>[\s\S]*?<\/Link>/g, "");
|
|
9616
|
+
header = header.replace(/\n\s*<>\s*\n/, "\n");
|
|
9617
|
+
header = header.replace(/\n\s*<\/>\s*\n/, "\n");
|
|
9618
|
+
await writeFile4(sharedHeaderPath, header, "utf-8");
|
|
9619
|
+
}
|
|
9620
|
+
const guardPath = join12(outputDir, "app", "ShowWhenNotAuthRoute.tsx");
|
|
9754
9621
|
if (existsSync20(guardPath)) {
|
|
9755
9622
|
let guard = await readFile5(guardPath, "utf-8");
|
|
9756
9623
|
guard = guard.replace(/['"],?\s*'\/design-system['"],?\s*/g, "");
|
|
@@ -9779,14 +9646,14 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
9779
9646
|
".env.local",
|
|
9780
9647
|
"recommendations.md"
|
|
9781
9648
|
]) {
|
|
9782
|
-
const full =
|
|
9649
|
+
const full = join12(outputDir, name);
|
|
9783
9650
|
if (existsSync20(full)) {
|
|
9784
9651
|
rmSync4(full, { force: true });
|
|
9785
9652
|
removed.push(name);
|
|
9786
9653
|
}
|
|
9787
9654
|
}
|
|
9788
9655
|
for (const dir of [".claude", ".coherent"]) {
|
|
9789
|
-
const full =
|
|
9656
|
+
const full = join12(outputDir, dir);
|
|
9790
9657
|
if (existsSync20(full)) {
|
|
9791
9658
|
rmSync4(full, { recursive: true, force: true });
|
|
9792
9659
|
removed.push(dir + "/");
|
|
@@ -9795,7 +9662,7 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
9795
9662
|
return removed;
|
|
9796
9663
|
}
|
|
9797
9664
|
async function exportCommand(options = {}) {
|
|
9798
|
-
const outputDir =
|
|
9665
|
+
const outputDir = resolve11(process.cwd(), options.output ?? "./export");
|
|
9799
9666
|
const doBuild = options.build !== false;
|
|
9800
9667
|
const keepDs = options.keepDs === true;
|
|
9801
9668
|
const spinner = ora4("Preparing export...").start();
|
|
@@ -9986,8 +9853,8 @@ async function regenerateDocsCommand() {
|
|
|
9986
9853
|
|
|
9987
9854
|
// src/commands/fix.ts
|
|
9988
9855
|
import chalk18 from "chalk";
|
|
9989
|
-
import { readdirSync as
|
|
9990
|
-
import { resolve as
|
|
9856
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync14, existsSync as existsSync21, writeFileSync as writeFileSync11, rmSync as rmSync5, mkdirSync as mkdirSync7 } from "fs";
|
|
9857
|
+
import { resolve as resolve12, join as join13 } from "path";
|
|
9991
9858
|
import {
|
|
9992
9859
|
DesignSystemManager as DesignSystemManager11,
|
|
9993
9860
|
ComponentManager as ComponentManager5,
|
|
@@ -10011,9 +9878,9 @@ function extractComponentIdsFromCode2(code) {
|
|
|
10011
9878
|
function listTsxFiles(dir) {
|
|
10012
9879
|
const files = [];
|
|
10013
9880
|
try {
|
|
10014
|
-
const entries =
|
|
9881
|
+
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
10015
9882
|
for (const e of entries) {
|
|
10016
|
-
const full =
|
|
9883
|
+
const full = join13(dir, e.name);
|
|
10017
9884
|
if (e.isDirectory() && e.name !== "node_modules" && !e.name.startsWith(".")) {
|
|
10018
9885
|
files.push(...listTsxFiles(full));
|
|
10019
9886
|
} else if (e.isFile() && e.name.endsWith(".tsx")) {
|
|
@@ -10042,7 +9909,7 @@ async function fixCommand(opts = {}) {
|
|
|
10042
9909
|
console.log(chalk18.cyan("\ncoherent fix\n"));
|
|
10043
9910
|
}
|
|
10044
9911
|
if (!skipCache) {
|
|
10045
|
-
const nextDir =
|
|
9912
|
+
const nextDir = join13(projectRoot, ".next");
|
|
10046
9913
|
if (existsSync21(nextDir)) {
|
|
10047
9914
|
if (!dryRun) rmSync5(nextDir, { recursive: true, force: true });
|
|
10048
9915
|
fixes.push("Cleared build cache");
|
|
@@ -10065,12 +9932,12 @@ async function fixCommand(opts = {}) {
|
|
|
10065
9932
|
}
|
|
10066
9933
|
}
|
|
10067
9934
|
}
|
|
10068
|
-
const appDir =
|
|
9935
|
+
const appDir = resolve12(projectRoot, "app");
|
|
10069
9936
|
const allTsxFiles = listTsxFiles(appDir);
|
|
10070
|
-
const componentsTsxFiles = listTsxFiles(
|
|
9937
|
+
const componentsTsxFiles = listTsxFiles(resolve12(projectRoot, "components"));
|
|
10071
9938
|
const allComponentIds = /* @__PURE__ */ new Set();
|
|
10072
9939
|
for (const file of [...allTsxFiles, ...componentsTsxFiles]) {
|
|
10073
|
-
const content =
|
|
9940
|
+
const content = readFileSync14(file, "utf-8");
|
|
10074
9941
|
extractComponentIdsFromCode2(content).forEach((id) => allComponentIds.add(id));
|
|
10075
9942
|
}
|
|
10076
9943
|
let dsm = null;
|
|
@@ -10089,7 +9956,7 @@ async function fixCommand(opts = {}) {
|
|
|
10089
9956
|
missingComponents.push(id);
|
|
10090
9957
|
} else {
|
|
10091
9958
|
const fileName = toKebabCase(id) + ".tsx";
|
|
10092
|
-
const filePath =
|
|
9959
|
+
const filePath = resolve12(projectRoot, "components", "ui", fileName);
|
|
10093
9960
|
if (!existsSync21(filePath)) missingFiles.push(id);
|
|
10094
9961
|
}
|
|
10095
9962
|
}
|
|
@@ -10117,8 +9984,8 @@ async function fixCommand(opts = {}) {
|
|
|
10117
9984
|
const generator = new ComponentGenerator4(updatedConfig);
|
|
10118
9985
|
const code = await generator.generate(component);
|
|
10119
9986
|
const fileName = toKebabCase(component.name) + ".tsx";
|
|
10120
|
-
const filePath =
|
|
10121
|
-
mkdirSync7(
|
|
9987
|
+
const filePath = resolve12(projectRoot, "components", "ui", fileName);
|
|
9988
|
+
mkdirSync7(resolve12(projectRoot, "components", "ui"), { recursive: true });
|
|
10122
9989
|
await writeFile(filePath, code);
|
|
10123
9990
|
installed++;
|
|
10124
9991
|
}
|
|
@@ -10139,7 +10006,7 @@ async function fixCommand(opts = {}) {
|
|
|
10139
10006
|
const userTsxFiles = allTsxFiles.filter((f) => !f.includes("/design-system/"));
|
|
10140
10007
|
let syntaxFixed = 0;
|
|
10141
10008
|
for (const file of userTsxFiles) {
|
|
10142
|
-
const content =
|
|
10009
|
+
const content = readFileSync14(file, "utf-8");
|
|
10143
10010
|
const fixed = fixUnescapedLtInJsx(
|
|
10144
10011
|
fixEscapedClosingQuotes(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)))
|
|
10145
10012
|
);
|
|
@@ -10157,7 +10024,7 @@ async function fixCommand(opts = {}) {
|
|
|
10157
10024
|
let qualityFixCount = 0;
|
|
10158
10025
|
const qualityFixDetails = [];
|
|
10159
10026
|
for (const file of userTsxFiles) {
|
|
10160
|
-
const content =
|
|
10027
|
+
const content = readFileSync14(file, "utf-8");
|
|
10161
10028
|
const { code: autoFixed, fixes: fileFixes } = await autoFixCode(content);
|
|
10162
10029
|
if (autoFixed !== content) {
|
|
10163
10030
|
if (!dryRun) writeFileSync11(file, autoFixed, "utf-8");
|
|
@@ -10176,7 +10043,7 @@ async function fixCommand(opts = {}) {
|
|
|
10176
10043
|
let totalWarnings = 0;
|
|
10177
10044
|
const fileIssues = [];
|
|
10178
10045
|
for (const file of allTsxFiles) {
|
|
10179
|
-
const code = dryRun ?
|
|
10046
|
+
const code = dryRun ? readFileSync14(file, "utf-8") : readFileSync14(file, "utf-8");
|
|
10180
10047
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
10181
10048
|
const baseName = file.split("/").pop() || "";
|
|
10182
10049
|
const isAuthPage = relativePath.includes("(auth)");
|
|
@@ -10298,16 +10165,16 @@ async function fixCommand(opts = {}) {
|
|
|
10298
10165
|
|
|
10299
10166
|
// src/commands/check.ts
|
|
10300
10167
|
import chalk19 from "chalk";
|
|
10301
|
-
import { resolve as
|
|
10302
|
-
import { readdirSync as
|
|
10168
|
+
import { resolve as resolve13 } from "path";
|
|
10169
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync15, statSync as statSync2, existsSync as existsSync22 } from "fs";
|
|
10303
10170
|
import { loadManifest as loadManifest11 } from "@getcoherent/core";
|
|
10304
10171
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", "design-system"]);
|
|
10305
10172
|
function findTsxFiles(dir) {
|
|
10306
10173
|
const results = [];
|
|
10307
10174
|
try {
|
|
10308
|
-
const entries =
|
|
10175
|
+
const entries = readdirSync5(dir);
|
|
10309
10176
|
for (const entry of entries) {
|
|
10310
|
-
const full =
|
|
10177
|
+
const full = resolve13(dir, entry);
|
|
10311
10178
|
const stat = statSync2(full);
|
|
10312
10179
|
if (stat.isDirectory() && !entry.startsWith(".") && !EXCLUDED_DIRS.has(entry)) {
|
|
10313
10180
|
results.push(...findTsxFiles(full));
|
|
@@ -10342,7 +10209,7 @@ async function checkCommand(opts = {}) {
|
|
|
10342
10209
|
} catch {
|
|
10343
10210
|
}
|
|
10344
10211
|
if (!skipPages) {
|
|
10345
|
-
const appDir =
|
|
10212
|
+
const appDir = resolve13(projectRoot, "app");
|
|
10346
10213
|
const files = findTsxFiles(appDir);
|
|
10347
10214
|
result.pages.total = files.length;
|
|
10348
10215
|
if (!opts.json) console.log(chalk19.cyan("\n \u{1F4C4} Pages") + chalk19.dim(` (${files.length} scanned)
|
|
@@ -10356,7 +10223,7 @@ async function checkCommand(opts = {}) {
|
|
|
10356
10223
|
"NATIVE_TABLE"
|
|
10357
10224
|
]);
|
|
10358
10225
|
for (const file of files) {
|
|
10359
|
-
const code =
|
|
10226
|
+
const code = readFileSync15(file, "utf-8");
|
|
10360
10227
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
10361
10228
|
const baseName = file.split("/").pop() || "";
|
|
10362
10229
|
const isAuthPage = relativePath.includes("(auth)");
|
|
@@ -10398,7 +10265,7 @@ async function checkCommand(opts = {}) {
|
|
|
10398
10265
|
routeSet.add("/");
|
|
10399
10266
|
routeSet.add("#");
|
|
10400
10267
|
for (const file of files) {
|
|
10401
|
-
const code =
|
|
10268
|
+
const code = readFileSync15(file, "utf-8");
|
|
10402
10269
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
10403
10270
|
const lines = code.split("\n");
|
|
10404
10271
|
const linkHrefRe = /href\s*=\s*["'](\/[a-z0-9/-]*)["']/gi;
|
|
@@ -10430,7 +10297,7 @@ async function checkCommand(opts = {}) {
|
|
|
10430
10297
|
const manifest = await loadManifest11(project.root);
|
|
10431
10298
|
if (manifest.shared.length > 0) {
|
|
10432
10299
|
for (const entry of manifest.shared) {
|
|
10433
|
-
const fullPath =
|
|
10300
|
+
const fullPath = resolve13(project.root, entry.file);
|
|
10434
10301
|
if (!existsSync22(fullPath)) {
|
|
10435
10302
|
result.pages.withErrors++;
|
|
10436
10303
|
if (!opts.json) console.log(chalk19.red(`
|
|
@@ -10455,7 +10322,7 @@ async function checkCommand(opts = {}) {
|
|
|
10455
10322
|
let _staleUsedIn = 0;
|
|
10456
10323
|
let _nameMismatch = 0;
|
|
10457
10324
|
for (const entry of manifest.shared) {
|
|
10458
|
-
const filePath =
|
|
10325
|
+
const filePath = resolve13(projectRoot, entry.file);
|
|
10459
10326
|
const fileExists = existsSync22(filePath);
|
|
10460
10327
|
if (!fileExists) {
|
|
10461
10328
|
_orphaned++;
|
|
@@ -10466,7 +10333,7 @@ async function checkCommand(opts = {}) {
|
|
|
10466
10333
|
continue;
|
|
10467
10334
|
}
|
|
10468
10335
|
try {
|
|
10469
|
-
const code =
|
|
10336
|
+
const code = readFileSync15(filePath, "utf-8");
|
|
10470
10337
|
const actualExports = extractExportedComponentNames(code);
|
|
10471
10338
|
if (actualExports.length > 0 && !actualExports.includes(entry.name)) {
|
|
10472
10339
|
_nameMismatch++;
|
|
@@ -10534,7 +10401,7 @@ async function checkCommand(opts = {}) {
|
|
|
10534
10401
|
id: e.id,
|
|
10535
10402
|
name: e.name,
|
|
10536
10403
|
type: e.type,
|
|
10537
|
-
status: existsSync22(
|
|
10404
|
+
status: existsSync22(resolve13(projectRoot, e.file)) ? "ok" : "unused",
|
|
10538
10405
|
message: "",
|
|
10539
10406
|
suggestions: void 0
|
|
10540
10407
|
}))
|
|
@@ -10627,14 +10494,14 @@ import {
|
|
|
10627
10494
|
ComponentManager as ComponentManager6,
|
|
10628
10495
|
loadManifest as loadManifest12,
|
|
10629
10496
|
generateSharedComponent as generateSharedComponent4,
|
|
10630
|
-
integrateSharedLayoutIntoRootLayout as
|
|
10497
|
+
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout3
|
|
10631
10498
|
} from "@getcoherent/core";
|
|
10632
10499
|
import { existsSync as existsSync23 } from "fs";
|
|
10633
|
-
import { resolve as
|
|
10500
|
+
import { resolve as resolve14 } from "path";
|
|
10634
10501
|
|
|
10635
10502
|
// src/utils/ds-files.ts
|
|
10636
10503
|
import { mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
|
|
10637
|
-
import { join as
|
|
10504
|
+
import { join as join14, dirname as dirname8 } from "path";
|
|
10638
10505
|
import { DesignSystemGenerator } from "@getcoherent/core";
|
|
10639
10506
|
var SHARED_DS_KEYS = [
|
|
10640
10507
|
"app/design-system/shared/page.tsx",
|
|
@@ -10648,7 +10515,7 @@ async function writeDesignSystemFiles(projectRoot, config2, options) {
|
|
|
10648
10515
|
const toWrite = options?.sharedOnly ? new Map([...files].filter(([path3]) => SHARED_DS_KEYS.includes(path3))) : files;
|
|
10649
10516
|
const written = [];
|
|
10650
10517
|
for (const [relativePath, content] of toWrite) {
|
|
10651
|
-
const fullPath =
|
|
10518
|
+
const fullPath = join14(projectRoot, relativePath);
|
|
10652
10519
|
await mkdir5(dirname8(fullPath), { recursive: true });
|
|
10653
10520
|
await writeFile5(fullPath, content, "utf-8");
|
|
10654
10521
|
written.push(relativePath);
|
|
@@ -10757,10 +10624,10 @@ function createComponentsCommand() {
|
|
|
10757
10624
|
\u2705 Created ${result.id} (${result.name}) at ${result.file}
|
|
10758
10625
|
`));
|
|
10759
10626
|
if (type === "layout") {
|
|
10760
|
-
const updated = await
|
|
10627
|
+
const updated = await integrateSharedLayoutIntoRootLayout3(project.root);
|
|
10761
10628
|
if (updated) console.log(chalk25.cyan(" Updated app/layout.tsx to use shared layout components.\n"));
|
|
10762
10629
|
}
|
|
10763
|
-
const sharedPagePath =
|
|
10630
|
+
const sharedPagePath = resolve14(project.root, "app/design-system/shared/page.tsx");
|
|
10764
10631
|
if (!existsSync23(sharedPagePath)) {
|
|
10765
10632
|
try {
|
|
10766
10633
|
const dsm = new DesignSystemManager12(project.configPath);
|
|
@@ -10787,7 +10654,7 @@ function createComponentsCommand() {
|
|
|
10787
10654
|
import chalk26 from "chalk";
|
|
10788
10655
|
import ora6 from "ora";
|
|
10789
10656
|
import { writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
|
|
10790
|
-
import { resolve as
|
|
10657
|
+
import { resolve as resolve15, join as join15, dirname as dirname9 } from "path";
|
|
10791
10658
|
import { existsSync as existsSync24 } from "fs";
|
|
10792
10659
|
import {
|
|
10793
10660
|
FigmaClient,
|
|
@@ -10800,7 +10667,7 @@ import {
|
|
|
10800
10667
|
setSharedMapping,
|
|
10801
10668
|
generateSharedComponent as generateSharedComponent5,
|
|
10802
10669
|
generatePagesFromFigma,
|
|
10803
|
-
integrateSharedLayoutIntoRootLayout as
|
|
10670
|
+
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout4,
|
|
10804
10671
|
DesignSystemManager as DesignSystemManager13,
|
|
10805
10672
|
validateConfig,
|
|
10806
10673
|
FRAMEWORK_VERSIONS as FRAMEWORK_VERSIONS2,
|
|
@@ -10935,7 +10802,7 @@ async function importFigmaAction(urlOrKey, opts) {
|
|
|
10935
10802
|
stats.filesWritten.push(filePath);
|
|
10936
10803
|
return;
|
|
10937
10804
|
}
|
|
10938
|
-
const fullPath =
|
|
10805
|
+
const fullPath = join15(projectRoot, filePath);
|
|
10939
10806
|
await mkdir6(dirname9(fullPath), { recursive: true });
|
|
10940
10807
|
await writeFile6(fullPath, content, "utf-8");
|
|
10941
10808
|
stats.filesWritten.push(filePath);
|
|
@@ -11012,7 +10879,7 @@ async function importFigmaAction(urlOrKey, opts) {
|
|
|
11012
10879
|
if (dryRun) stats.filesWritten.push(FIGMA_COMPONENT_MAP_FILENAME);
|
|
11013
10880
|
else
|
|
11014
10881
|
await writeFile6(
|
|
11015
|
-
|
|
10882
|
+
resolve15(projectRoot, FIGMA_COMPONENT_MAP_FILENAME),
|
|
11016
10883
|
JSON.stringify(componentMapObj, null, 2),
|
|
11017
10884
|
"utf-8"
|
|
11018
10885
|
);
|
|
@@ -11035,7 +10902,7 @@ async function importFigmaAction(urlOrKey, opts) {
|
|
|
11035
10902
|
const fullConfig = buildFigmaImportConfig(mergedConfig, pageDefs, intermediate.fileName);
|
|
11036
10903
|
if (!dryRun) {
|
|
11037
10904
|
spinner.start("Updating design-system.config.ts...");
|
|
11038
|
-
const configPath =
|
|
10905
|
+
const configPath = resolve15(projectRoot, DESIGN_SYSTEM_CONFIG_PATH);
|
|
11039
10906
|
const dsm = new DesignSystemManager13(configPath);
|
|
11040
10907
|
if (existsSync24(configPath)) {
|
|
11041
10908
|
await dsm.load();
|
|
@@ -11066,7 +10933,7 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
|
|
|
11066
10933
|
stats.configUpdated = true;
|
|
11067
10934
|
spinner.succeed("design-system.config.ts updated");
|
|
11068
10935
|
spinner.start("Ensuring root layout...");
|
|
11069
|
-
const layoutPath =
|
|
10936
|
+
const layoutPath = join15(projectRoot, "app/layout.tsx");
|
|
11070
10937
|
if (!existsSync24(layoutPath)) {
|
|
11071
10938
|
await mkdir6(dirname9(layoutPath), { recursive: true });
|
|
11072
10939
|
await writeFile6(layoutPath, MINIMAL_ROOT_LAYOUT, "utf-8");
|
|
@@ -11074,7 +10941,7 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
|
|
|
11074
10941
|
}
|
|
11075
10942
|
spinner.succeed("Root layout OK");
|
|
11076
10943
|
spinner.start("Integrating shared layout (Header/Footer)...");
|
|
11077
|
-
const layoutIntegrated = await
|
|
10944
|
+
const layoutIntegrated = await integrateSharedLayoutIntoRootLayout4(projectRoot);
|
|
11078
10945
|
stats.layoutIntegrated = layoutIntegrated;
|
|
11079
10946
|
spinner.succeed(layoutIntegrated ? "Layout components wired" : "No layout components to wire");
|
|
11080
10947
|
spinner.start("Generating Design System viewer...");
|
|
@@ -11157,8 +11024,8 @@ async function dsRegenerateCommand() {
|
|
|
11157
11024
|
// src/commands/update.ts
|
|
11158
11025
|
import chalk28 from "chalk";
|
|
11159
11026
|
import ora8 from "ora";
|
|
11160
|
-
import { readFileSync as
|
|
11161
|
-
import { join as
|
|
11027
|
+
import { readFileSync as readFileSync16, existsSync as existsSync25 } from "fs";
|
|
11028
|
+
import { join as join16 } from "path";
|
|
11162
11029
|
import { DesignSystemManager as DesignSystemManager15, CLI_VERSION as CLI_VERSION4 } from "@getcoherent/core";
|
|
11163
11030
|
|
|
11164
11031
|
// src/utils/migrations.ts
|
|
@@ -11327,20 +11194,20 @@ var EXPECTED_CSS_VARS = [
|
|
|
11327
11194
|
"--sidebar-ring"
|
|
11328
11195
|
];
|
|
11329
11196
|
function checkMissingCssVars(projectRoot) {
|
|
11330
|
-
const globalsPath =
|
|
11197
|
+
const globalsPath = join16(projectRoot, "app", "globals.css");
|
|
11331
11198
|
if (!existsSync25(globalsPath)) return [];
|
|
11332
11199
|
try {
|
|
11333
|
-
const content =
|
|
11200
|
+
const content = readFileSync16(globalsPath, "utf-8");
|
|
11334
11201
|
return EXPECTED_CSS_VARS.filter((v) => !content.includes(v));
|
|
11335
11202
|
} catch {
|
|
11336
11203
|
return [];
|
|
11337
11204
|
}
|
|
11338
11205
|
}
|
|
11339
11206
|
function patchGlobalsCss(projectRoot, missingVars) {
|
|
11340
|
-
const globalsPath =
|
|
11207
|
+
const globalsPath = join16(projectRoot, "app", "globals.css");
|
|
11341
11208
|
if (!existsSync25(globalsPath) || missingVars.length === 0) return;
|
|
11342
11209
|
const { writeFileSync: writeFileSync13 } = __require("fs");
|
|
11343
|
-
let content =
|
|
11210
|
+
let content = readFileSync16(globalsPath, "utf-8");
|
|
11344
11211
|
const defaultValues = {
|
|
11345
11212
|
"--chart-1": "220 70% 50%",
|
|
11346
11213
|
"--chart-2": "160 60% 45%",
|
|
@@ -11418,26 +11285,26 @@ async function undoCommand(options) {
|
|
|
11418
11285
|
// src/commands/sync.ts
|
|
11419
11286
|
import chalk30 from "chalk";
|
|
11420
11287
|
import ora9 from "ora";
|
|
11421
|
-
import { existsSync as existsSync26, readFileSync as
|
|
11422
|
-
import { join as
|
|
11288
|
+
import { existsSync as existsSync26, readFileSync as readFileSync17 } from "fs";
|
|
11289
|
+
import { join as join17, relative as relative4, dirname as dirname10 } from "path";
|
|
11423
11290
|
import { readdir as readdir4, readFile as readFile6 } from "fs/promises";
|
|
11424
11291
|
import { DesignSystemManager as DesignSystemManager16 } from "@getcoherent/core";
|
|
11425
11292
|
import { loadManifest as loadManifest13, saveManifest as saveManifest5, findSharedComponent } from "@getcoherent/core";
|
|
11426
11293
|
function extractTokensFromProject(projectRoot) {
|
|
11427
11294
|
const lightColors = {};
|
|
11428
11295
|
const darkColors = {};
|
|
11429
|
-
const globalsPath =
|
|
11296
|
+
const globalsPath = join17(projectRoot, "app", "globals.css");
|
|
11430
11297
|
if (existsSync26(globalsPath)) {
|
|
11431
|
-
const css =
|
|
11298
|
+
const css = readFileSync17(globalsPath, "utf-8");
|
|
11432
11299
|
const rootMatch = css.match(/:root\s*\{([^}]+)\}/s);
|
|
11433
11300
|
if (rootMatch) parseVarsInto(rootMatch[1], lightColors);
|
|
11434
11301
|
const darkMatch = css.match(/\.dark\s*\{([^}]+)\}/s);
|
|
11435
11302
|
if (darkMatch) parseVarsInto(darkMatch[1], darkColors);
|
|
11436
11303
|
}
|
|
11437
|
-
const layoutPath =
|
|
11304
|
+
const layoutPath = join17(projectRoot, "app", "layout.tsx");
|
|
11438
11305
|
let layoutCode = "";
|
|
11439
11306
|
if (existsSync26(layoutPath)) {
|
|
11440
|
-
layoutCode =
|
|
11307
|
+
layoutCode = readFileSync17(layoutPath, "utf-8");
|
|
11441
11308
|
const rootInline = layoutCode.match(/:root\s*\{([^}]+)\}/s);
|
|
11442
11309
|
if (rootInline && Object.keys(lightColors).length === 0) {
|
|
11443
11310
|
parseVarsInto(rootInline[1], lightColors);
|
|
@@ -11455,7 +11322,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
11455
11322
|
defaultMode = "dark";
|
|
11456
11323
|
}
|
|
11457
11324
|
let radius;
|
|
11458
|
-
const allCss = [existsSync26(globalsPath) ?
|
|
11325
|
+
const allCss = [existsSync26(globalsPath) ? readFileSync17(globalsPath, "utf-8") : "", layoutCode].join("\n");
|
|
11459
11326
|
const radiusMatch = allCss.match(/--radius:\s*([^;]+);/);
|
|
11460
11327
|
if (radiusMatch) radius = radiusMatch[1].trim();
|
|
11461
11328
|
return {
|
|
@@ -11478,7 +11345,7 @@ function parseVarsInto(block, target) {
|
|
|
11478
11345
|
}
|
|
11479
11346
|
async function detectCustomComponents(projectRoot, allPageCode) {
|
|
11480
11347
|
const results = [];
|
|
11481
|
-
const componentsDir =
|
|
11348
|
+
const componentsDir = join17(projectRoot, "components");
|
|
11482
11349
|
if (!existsSync26(componentsDir)) return results;
|
|
11483
11350
|
const files = [];
|
|
11484
11351
|
await walkForTsx(componentsDir, files, ["ui"]);
|
|
@@ -11506,7 +11373,7 @@ async function walkForTsx(dir, files, skipDirs) {
|
|
|
11506
11373
|
return;
|
|
11507
11374
|
}
|
|
11508
11375
|
for (const e of entries) {
|
|
11509
|
-
const full =
|
|
11376
|
+
const full = join17(dir, e.name);
|
|
11510
11377
|
if (e.isDirectory()) {
|
|
11511
11378
|
if (skipDirs.includes(e.name) || e.name.startsWith(".")) continue;
|
|
11512
11379
|
await walkForTsx(full, files, skipDirs);
|
|
@@ -11580,7 +11447,7 @@ async function discoverPages(appDir) {
|
|
|
11580
11447
|
return;
|
|
11581
11448
|
}
|
|
11582
11449
|
for (const entry of entries) {
|
|
11583
|
-
const full =
|
|
11450
|
+
const full = join17(dir, entry.name);
|
|
11584
11451
|
if (entry.isDirectory()) {
|
|
11585
11452
|
if (["design-system", "api", "_not-found"].includes(entry.name)) continue;
|
|
11586
11453
|
if (entry.name.startsWith(".")) continue;
|
|
@@ -11657,7 +11524,7 @@ async function syncCommand(options = {}) {
|
|
|
11657
11524
|
if (dryRun) console.log(chalk30.yellow(" [dry-run] No files will be written\n"));
|
|
11658
11525
|
const spinner = ora9("Scanning project files...").start();
|
|
11659
11526
|
try {
|
|
11660
|
-
const appDir =
|
|
11527
|
+
const appDir = join17(project.root, "app");
|
|
11661
11528
|
if (!existsSync26(appDir)) {
|
|
11662
11529
|
spinner.fail("No app/ directory found");
|
|
11663
11530
|
process.exit(1);
|
|
@@ -11883,20 +11750,20 @@ async function syncCommand(options = {}) {
|
|
|
11883
11750
|
}
|
|
11884
11751
|
|
|
11885
11752
|
// src/utils/update-notifier.ts
|
|
11886
|
-
import { existsSync as existsSync27, mkdirSync as mkdirSync8, readFileSync as
|
|
11887
|
-
import { join as
|
|
11753
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync8, readFileSync as readFileSync18, writeFileSync as writeFileSync12 } from "fs";
|
|
11754
|
+
import { join as join18 } from "path";
|
|
11888
11755
|
import { homedir } from "os";
|
|
11889
11756
|
import chalk31 from "chalk";
|
|
11890
11757
|
import { CLI_VERSION as CLI_VERSION5 } from "@getcoherent/core";
|
|
11891
11758
|
var DEBUG5 = process.env.COHERENT_DEBUG === "1";
|
|
11892
11759
|
var PACKAGE_NAME = "@getcoherent/cli";
|
|
11893
|
-
var CACHE_DIR =
|
|
11894
|
-
var CACHE_FILE =
|
|
11760
|
+
var CACHE_DIR = join18(homedir(), ".coherent");
|
|
11761
|
+
var CACHE_FILE = join18(CACHE_DIR, "update-check.json");
|
|
11895
11762
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
11896
11763
|
function readCache() {
|
|
11897
11764
|
try {
|
|
11898
11765
|
if (!existsSync27(CACHE_FILE)) return null;
|
|
11899
|
-
const raw =
|
|
11766
|
+
const raw = readFileSync18(CACHE_FILE, "utf-8");
|
|
11900
11767
|
return JSON.parse(raw);
|
|
11901
11768
|
} catch (e) {
|
|
11902
11769
|
if (DEBUG5) console.error("Failed to read update cache:", e);
|