@getcoherent/cli 0.2.4 → 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 +357 -495
- 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
|
|
@@ -6614,7 +6622,8 @@ import {
|
|
|
6614
6622
|
PageGenerator,
|
|
6615
6623
|
TailwindConfigGenerator
|
|
6616
6624
|
} from "@getcoherent/core";
|
|
6617
|
-
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";
|
|
6618
6627
|
async function validateAndFixGeneratedCode(projectRoot, code, options = {}) {
|
|
6619
6628
|
const fixes = [];
|
|
6620
6629
|
let fixed = fixEscapedClosingQuotes(code);
|
|
@@ -6684,18 +6693,49 @@ async function regenerateLayout(config2, projectRoot) {
|
|
|
6684
6693
|
const layout = config2.pages[0]?.layout || "centered";
|
|
6685
6694
|
const appType = config2.settings.appType || "multi-page";
|
|
6686
6695
|
const generator = new PageGenerator(config2);
|
|
6687
|
-
|
|
6688
|
-
|
|
6689
|
-
|
|
6690
|
-
|
|
6691
|
-
|
|
6692
|
-
|
|
6693
|
-
|
|
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
|
+
}
|
|
6694
6731
|
}
|
|
6695
6732
|
try {
|
|
6696
6733
|
await integrateSharedLayoutIntoRootLayout2(projectRoot);
|
|
6697
6734
|
await ensureAuthRouteGroup(projectRoot);
|
|
6698
|
-
} catch {
|
|
6735
|
+
} catch (err) {
|
|
6736
|
+
if (process.env.COHERENT_DEBUG === "1") {
|
|
6737
|
+
console.log(chalk9.dim("Layout integration warning:", err));
|
|
6738
|
+
}
|
|
6699
6739
|
}
|
|
6700
6740
|
}
|
|
6701
6741
|
async function regenerateFiles(modified, config2, projectRoot) {
|
|
@@ -6733,8 +6773,39 @@ async function regenerateFiles(modified, config2, projectRoot) {
|
|
|
6733
6773
|
}
|
|
6734
6774
|
}
|
|
6735
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
|
+
|
|
6736
6807
|
// src/commands/chat/reporting.ts
|
|
6737
|
-
import
|
|
6808
|
+
import chalk10 from "chalk";
|
|
6738
6809
|
function extractImportsFrom(code, fromPath) {
|
|
6739
6810
|
const escaped = fromPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
6740
6811
|
const regex = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*['"\`]${escaped}[^'"\`]*['"\`]`, "g");
|
|
@@ -6756,27 +6827,27 @@ function printPostGenerationReport(opts) {
|
|
|
6756
6827
|
const iconCount = extractImportsFrom(code, "lucide-react").length;
|
|
6757
6828
|
const hasInstalled = postFixes.some((f) => f.startsWith("Installed:"));
|
|
6758
6829
|
const syntaxStatus = postFixes.length > 0 ? postFixes.some((f) => f.includes("metadata")) ? "fixed (escaped metadata quotes) \u2714" : "fixed \u2714" : "valid \u2714";
|
|
6759
|
-
console.log(
|
|
6830
|
+
console.log(chalk10.green(`
|
|
6760
6831
|
\u2705 Page "${pageTitle}" ${action} at ${filePath}
|
|
6761
6832
|
`));
|
|
6762
6833
|
if (uiComponents.length > 0) {
|
|
6763
|
-
console.log(
|
|
6834
|
+
console.log(chalk10.dim(` Components: ${uiComponents.join(", ")} (from @/components/ui)`));
|
|
6764
6835
|
}
|
|
6765
6836
|
if (inCodeShared.length > 0) {
|
|
6766
|
-
console.log(
|
|
6837
|
+
console.log(chalk10.dim(` Shared: ${inCodeShared.map((s) => `${s.id} (${s.name})`).join(", ")}`));
|
|
6767
6838
|
}
|
|
6768
6839
|
if (layoutShared.length > 0) {
|
|
6769
|
-
console.log(
|
|
6840
|
+
console.log(chalk10.dim(` Layout: ${layoutShared.map((l) => `${l.id} (${l.name})`).join(", ")} via layout.tsx`));
|
|
6770
6841
|
}
|
|
6771
6842
|
if (iconCount > 0) {
|
|
6772
|
-
console.log(
|
|
6843
|
+
console.log(chalk10.dim(` Icons: ${iconCount} from lucide-react`));
|
|
6773
6844
|
}
|
|
6774
6845
|
if (hasInstalled) {
|
|
6775
|
-
console.log(
|
|
6846
|
+
console.log(chalk10.dim(" Dependencies: installed \u2714"));
|
|
6776
6847
|
}
|
|
6777
|
-
console.log(
|
|
6848
|
+
console.log(chalk10.dim(` Syntax: ${syntaxStatus}`));
|
|
6778
6849
|
if (route) {
|
|
6779
|
-
console.log(
|
|
6850
|
+
console.log(chalk10.cyan(`
|
|
6780
6851
|
Preview: http://localhost:3000${route}`));
|
|
6781
6852
|
}
|
|
6782
6853
|
console.log("");
|
|
@@ -6784,35 +6855,35 @@ function printPostGenerationReport(opts) {
|
|
|
6784
6855
|
function printSharedComponentReport(opts) {
|
|
6785
6856
|
const { id, name, file, instruction, postFixes = [] } = opts;
|
|
6786
6857
|
const syntaxStatus = postFixes.length > 0 ? "fixed \u2714" : "valid \u2714";
|
|
6787
|
-
console.log(
|
|
6858
|
+
console.log(chalk10.green(`
|
|
6788
6859
|
\u2705 Updated ${id} (${name}) at ${file}
|
|
6789
6860
|
`));
|
|
6790
6861
|
if (instruction) {
|
|
6791
6862
|
const snippet = instruction.length > 60 ? instruction.slice(0, 57) + "..." : instruction;
|
|
6792
|
-
console.log(
|
|
6863
|
+
console.log(chalk10.dim(` Changed: ${snippet}`));
|
|
6793
6864
|
}
|
|
6794
|
-
console.log(
|
|
6795
|
-
console.log(
|
|
6865
|
+
console.log(chalk10.dim(" Affects: all pages via layout.tsx"));
|
|
6866
|
+
console.log(chalk10.dim(` Syntax: ${syntaxStatus}`));
|
|
6796
6867
|
console.log("");
|
|
6797
6868
|
}
|
|
6798
6869
|
function printLinkSharedReport(opts) {
|
|
6799
6870
|
const { sharedId, sharedName, pageTarget, route, postFixes = [] } = opts;
|
|
6800
6871
|
const syntaxStatus = postFixes.length > 0 ? "fixed \u2714" : "valid \u2714";
|
|
6801
|
-
console.log(
|
|
6872
|
+
console.log(chalk10.green(`
|
|
6802
6873
|
\u2705 Linked ${sharedId} (${sharedName}) to page "${pageTarget}"
|
|
6803
6874
|
`));
|
|
6804
|
-
console.log(
|
|
6805
|
-
console.log(
|
|
6875
|
+
console.log(chalk10.dim(` Syntax: ${syntaxStatus}`));
|
|
6876
|
+
console.log(chalk10.cyan(` Preview: http://localhost:3000${route}`));
|
|
6806
6877
|
console.log("");
|
|
6807
6878
|
}
|
|
6808
6879
|
function printPromoteAndLinkReport(opts) {
|
|
6809
6880
|
const { id, name, file, usedInFiles, postFixes = [] } = opts;
|
|
6810
6881
|
const syntaxStatus = postFixes.length > 0 ? "fixed \u2714" : "valid \u2714";
|
|
6811
|
-
console.log(
|
|
6882
|
+
console.log(chalk10.green(`
|
|
6812
6883
|
\u2705 Created ${id} (${name}) at ${file}
|
|
6813
6884
|
`));
|
|
6814
|
-
console.log(
|
|
6815
|
-
console.log(
|
|
6885
|
+
console.log(chalk10.dim(` Linked to: ${usedInFiles.length} page(s)`));
|
|
6886
|
+
console.log(chalk10.dim(` Syntax: ${syntaxStatus}`));
|
|
6816
6887
|
console.log("");
|
|
6817
6888
|
}
|
|
6818
6889
|
function showPreview(requests, results, config2, preflightInstalledNames) {
|
|
@@ -6830,23 +6901,23 @@ function showPreview(requests, results, config2, preflightInstalledNames) {
|
|
|
6830
6901
|
const modifiedSharedComponents = successfulPairs.filter(({ request }) => request.type === "modify-layout-block");
|
|
6831
6902
|
const modifiedPages = successfulPairs.filter(({ request }) => request.type === "update-page");
|
|
6832
6903
|
const tokenChanges = successfulPairs.filter(({ request }) => request.type === "update-token");
|
|
6833
|
-
console.log(
|
|
6904
|
+
console.log(chalk10.bold.cyan("\n\u{1F4CB} Changes Applied:\n"));
|
|
6834
6905
|
if (preflightInstalledNames && preflightInstalledNames.length > 0) {
|
|
6835
|
-
console.log(
|
|
6906
|
+
console.log(chalk10.cyan("\u{1F50D} Pre-flight check: Installed missing components:"));
|
|
6836
6907
|
preflightInstalledNames.forEach((name) => {
|
|
6837
|
-
console.log(
|
|
6908
|
+
console.log(chalk10.green(` \u2728 Auto-installed ${name}`));
|
|
6838
6909
|
});
|
|
6839
6910
|
console.log("");
|
|
6840
6911
|
}
|
|
6841
6912
|
if (addedComponents.length > 0) {
|
|
6842
6913
|
const names = addedComponents.map(({ request }) => request.changes.name).filter(Boolean);
|
|
6843
|
-
console.log(
|
|
6844
|
-
console.log(
|
|
6914
|
+
console.log(chalk10.green("\u{1F4E6} Components:"));
|
|
6915
|
+
console.log(chalk10.white(` \u2728 Auto-installed: ${names.join(", ")}`));
|
|
6845
6916
|
}
|
|
6846
6917
|
if (customComponents.length > 0) {
|
|
6847
6918
|
const names = customComponents.map(({ request }) => request.changes.name).filter(Boolean);
|
|
6848
|
-
if (addedComponents.length === 0) console.log(
|
|
6849
|
-
console.log(
|
|
6919
|
+
if (addedComponents.length === 0) console.log(chalk10.green("\u{1F4E6} Components:"));
|
|
6920
|
+
console.log(chalk10.white(` \u2728 Created: ${names.join(", ")}`));
|
|
6850
6921
|
}
|
|
6851
6922
|
const usedComponentIds = /* @__PURE__ */ new Set();
|
|
6852
6923
|
addedPages.forEach(({ request }) => {
|
|
@@ -6861,71 +6932,71 @@ function showPreview(requests, results, config2, preflightInstalledNames) {
|
|
|
6861
6932
|
]);
|
|
6862
6933
|
const reusedIds = [...usedComponentIds].filter((id) => !newComponentIds.has(id));
|
|
6863
6934
|
if (reusedIds.length > 0) {
|
|
6864
|
-
if (addedComponents.length === 0 && customComponents.length === 0) console.log(
|
|
6865
|
-
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(", ")}`));
|
|
6866
6937
|
}
|
|
6867
6938
|
if (addedComponents.length > 0 || customComponents.length > 0 || reusedIds.length > 0) {
|
|
6868
6939
|
console.log("");
|
|
6869
6940
|
}
|
|
6870
6941
|
if (addedPages.length > 0) {
|
|
6871
|
-
console.log(
|
|
6942
|
+
console.log(chalk10.green("\u{1F4C4} Pages Created:"));
|
|
6872
6943
|
addedPages.forEach(({ request }) => {
|
|
6873
6944
|
const page = request.changes;
|
|
6874
6945
|
const route = page.route || "/";
|
|
6875
|
-
console.log(
|
|
6876
|
-
console.log(
|
|
6877
|
-
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}`));
|
|
6878
6949
|
});
|
|
6879
6950
|
console.log("");
|
|
6880
6951
|
}
|
|
6881
6952
|
if (modifiedComponents.length > 0 || modifiedSharedComponents.length > 0 || modifiedPages.length > 0 || tokenChanges.length > 0) {
|
|
6882
|
-
console.log(
|
|
6953
|
+
console.log(chalk10.yellow("\u{1F527} Modified:"));
|
|
6883
6954
|
modifiedComponents.forEach(({ result }) => {
|
|
6884
|
-
console.log(
|
|
6955
|
+
console.log(chalk10.white(` \u2022 ${result.message}`));
|
|
6885
6956
|
});
|
|
6886
6957
|
modifiedSharedComponents.forEach(({ result }) => {
|
|
6887
|
-
console.log(
|
|
6958
|
+
console.log(chalk10.white(` \u2022 ${result.message}`));
|
|
6888
6959
|
});
|
|
6889
6960
|
modifiedPages.forEach(({ result }) => {
|
|
6890
|
-
console.log(
|
|
6961
|
+
console.log(chalk10.white(` \u2022 ${result.message}`));
|
|
6891
6962
|
});
|
|
6892
6963
|
tokenChanges.forEach(({ result }) => {
|
|
6893
|
-
console.log(
|
|
6964
|
+
console.log(chalk10.white(` \u2022 ${result.message}`));
|
|
6894
6965
|
});
|
|
6895
6966
|
console.log("");
|
|
6896
6967
|
}
|
|
6897
6968
|
if (failedPairs.length > 0) {
|
|
6898
|
-
console.log(
|
|
6969
|
+
console.log(chalk10.red("\u274C Failed modifications:"));
|
|
6899
6970
|
failedPairs.forEach(({ result }) => {
|
|
6900
|
-
console.log(
|
|
6971
|
+
console.log(chalk10.gray(` \u2716 ${result.message}`));
|
|
6901
6972
|
});
|
|
6902
6973
|
console.log("");
|
|
6903
6974
|
}
|
|
6904
6975
|
const successCount = successfulPairs.length;
|
|
6905
6976
|
const totalCount = results.length;
|
|
6906
6977
|
if (successCount === totalCount) {
|
|
6907
|
-
console.log(
|
|
6978
|
+
console.log(chalk10.green.bold(`\u2705 Success! ${successCount} modification(s) applied
|
|
6908
6979
|
`));
|
|
6909
6980
|
} else {
|
|
6910
|
-
console.log(
|
|
6981
|
+
console.log(chalk10.yellow.bold(`\u26A0\uFE0F Partial success: ${successCount}/${totalCount} modification(s) applied
|
|
6911
6982
|
`));
|
|
6912
6983
|
}
|
|
6913
6984
|
if (addedPages.length > 0) {
|
|
6914
6985
|
const firstPage = addedPages[0].request.changes;
|
|
6915
6986
|
const route = firstPage?.route || "/";
|
|
6916
|
-
console.log(
|
|
6917
|
-
console.log(
|
|
6918
|
-
console.log(
|
|
6919
|
-
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}
|
|
6920
6991
|
`));
|
|
6921
|
-
console.log(
|
|
6922
|
-
console.log(
|
|
6923
|
-
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"}"`));
|
|
6924
6995
|
console.log("");
|
|
6925
6996
|
} else if (successCount > 0) {
|
|
6926
|
-
console.log(
|
|
6927
|
-
console.log(
|
|
6928
|
-
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"));
|
|
6929
7000
|
}
|
|
6930
7001
|
}
|
|
6931
7002
|
function getChangeDescription(request, config2) {
|
|
@@ -6967,6 +7038,24 @@ function getChangeDescription(request, config2) {
|
|
|
6967
7038
|
|
|
6968
7039
|
// src/commands/chat/modification-handler.ts
|
|
6969
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
|
+
}
|
|
6970
7059
|
async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider, originalMessage) {
|
|
6971
7060
|
switch (request.type) {
|
|
6972
7061
|
case "modify-layout-block": {
|
|
@@ -7005,8 +7094,8 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7005
7094
|
const newCode = await ai.editSharedComponentCode(currentCode, instruction, resolved.name);
|
|
7006
7095
|
const { fixedCode, fixes } = await validateAndFixGeneratedCode(projectRoot, newCode, { isPage: false });
|
|
7007
7096
|
if (fixes.length > 0) {
|
|
7008
|
-
console.log(
|
|
7009
|
-
fixes.forEach((f) => console.log(
|
|
7097
|
+
console.log(chalk11.dim(" \u{1F527} Post-generation fixes:"));
|
|
7098
|
+
fixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
7010
7099
|
}
|
|
7011
7100
|
await writeFile(fullPath, fixedCode);
|
|
7012
7101
|
printSharedComponentReport({
|
|
@@ -7079,11 +7168,11 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7079
7168
|
const newPageCode = await ai.replaceInlineWithShared(pageCode, sharedCode, resolved.name, changes?.blockHint);
|
|
7080
7169
|
const { fixedCode, fixes } = await validateAndFixGeneratedCode(projectRoot, newPageCode, { isPage: true });
|
|
7081
7170
|
if (fixes.length > 0) {
|
|
7082
|
-
console.log(
|
|
7083
|
-
fixes.forEach((f) => console.log(
|
|
7171
|
+
console.log(chalk11.dim(" \u{1F527} Post-generation fixes:"));
|
|
7172
|
+
fixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
7084
7173
|
}
|
|
7085
7174
|
await writeFile(pageFilePath, fixedCode);
|
|
7086
|
-
const manifest = await
|
|
7175
|
+
const manifest = await loadManifest6(projectRoot);
|
|
7087
7176
|
const usedIn = manifest.shared.find((e) => e.id === resolved.id)?.usedIn ?? [];
|
|
7088
7177
|
const routePath = route.replace(/^\//, "");
|
|
7089
7178
|
const filePathRel = routePath ? `app/${routePath}/page.tsx` : "app/page.tsx";
|
|
@@ -7153,7 +7242,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7153
7242
|
};
|
|
7154
7243
|
}
|
|
7155
7244
|
const extractedCode = await ai.extractBlockAsComponent(sourceCode, blockHint, componentName);
|
|
7156
|
-
const created = await
|
|
7245
|
+
const created = await generateSharedComponent3(projectRoot, {
|
|
7157
7246
|
name: componentName,
|
|
7158
7247
|
type: "section",
|
|
7159
7248
|
code: extractedCode,
|
|
@@ -7182,13 +7271,13 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7182
7271
|
const newCode = await ai.replaceInlineWithShared(linkPageCode, sharedCode, created.name, blockHint);
|
|
7183
7272
|
const { fixedCode, fixes } = await validateAndFixGeneratedCode(projectRoot, newCode, { isPage: true });
|
|
7184
7273
|
if (fixes.length > 0) {
|
|
7185
|
-
console.log(
|
|
7186
|
-
fixes.forEach((f) => console.log(
|
|
7274
|
+
console.log(chalk11.dim(" \u{1F527} Post-generation fixes:"));
|
|
7275
|
+
fixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
7187
7276
|
}
|
|
7188
7277
|
await writeFile(fullPath, fixedCode);
|
|
7189
7278
|
usedInFiles.push(relPath);
|
|
7190
7279
|
}
|
|
7191
|
-
const manifest = await
|
|
7280
|
+
const manifest = await loadManifest6(projectRoot);
|
|
7192
7281
|
const nextManifest = updateUsedIn(manifest, created.id, usedInFiles);
|
|
7193
7282
|
await saveManifest(projectRoot, nextManifest);
|
|
7194
7283
|
printPromoteAndLinkReport({
|
|
@@ -7277,7 +7366,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7277
7366
|
const aiPageCode = typeof page.pageCode === "string" && page.pageCode.trim() !== "" ? page.pageCode : void 0;
|
|
7278
7367
|
if (aiPageCode) {
|
|
7279
7368
|
finalPageCode = aiPageCode;
|
|
7280
|
-
if (DEBUG2) console.log(
|
|
7369
|
+
if (DEBUG2) console.log(chalk11.dim(` [pageCode] Using AI-generated pageCode (user content priority)`));
|
|
7281
7370
|
} else if (page.pageType && page.structuredContent) {
|
|
7282
7371
|
const templateFn = getTemplateForPageType(page.pageType);
|
|
7283
7372
|
if (templateFn) {
|
|
@@ -7288,9 +7377,9 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7288
7377
|
pageName
|
|
7289
7378
|
};
|
|
7290
7379
|
finalPageCode = templateFn(page.structuredContent, opts);
|
|
7291
|
-
if (DEBUG2) console.log(
|
|
7380
|
+
if (DEBUG2) console.log(chalk11.dim(` [template] Used "${page.pageType}" template (no pageCode provided)`));
|
|
7292
7381
|
} catch {
|
|
7293
|
-
if (DEBUG2) console.log(
|
|
7382
|
+
if (DEBUG2) console.log(chalk11.dim(` [template] Failed for "${page.pageType}"`));
|
|
7294
7383
|
}
|
|
7295
7384
|
}
|
|
7296
7385
|
}
|
|
@@ -7333,10 +7422,13 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7333
7422
|
let codeToWrite = fixedCode;
|
|
7334
7423
|
const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite);
|
|
7335
7424
|
codeToWrite = autoFixed;
|
|
7425
|
+
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
7426
|
+
codeToWrite = layoutStripped;
|
|
7336
7427
|
const allFixes = [...postFixes, ...autoFixes];
|
|
7428
|
+
if (stripped.length > 0) allFixes.push(`stripped inline ${stripped.join(", ")} (layout owns these)`);
|
|
7337
7429
|
if (allFixes.length > 0) {
|
|
7338
|
-
console.log(
|
|
7339
|
-
allFixes.forEach((f) => console.log(
|
|
7430
|
+
console.log(chalk11.dim(" \u{1F527} Post-generation fixes:"));
|
|
7431
|
+
allFixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
7340
7432
|
}
|
|
7341
7433
|
await writeFile(filePath, codeToWrite);
|
|
7342
7434
|
const pageIdx = dsm.getConfig().pages.findIndex((p) => p.id === page.id);
|
|
@@ -7347,7 +7439,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7347
7439
|
cm.updateConfig(cfg);
|
|
7348
7440
|
pm.updateConfig(cfg);
|
|
7349
7441
|
}
|
|
7350
|
-
const manifestForAudit = await
|
|
7442
|
+
const manifestForAudit = await loadManifest6(projectRoot);
|
|
7351
7443
|
await warnInlineDuplicates(projectRoot, page.name || page.id || route.slice(1), codeToWrite, manifestForAudit);
|
|
7352
7444
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
7353
7445
|
printPostGenerationReport({
|
|
@@ -7366,7 +7458,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7366
7458
|
const errors = issues.filter((i) => i.severity === "error");
|
|
7367
7459
|
if (errors.length >= 5 && aiProvider) {
|
|
7368
7460
|
console.log(
|
|
7369
|
-
|
|
7461
|
+
chalk11.yellow(`
|
|
7370
7462
|
\u{1F504} ${errors.length} quality errors \u2014 attempting AI fix for ${page.name || page.id}...`)
|
|
7371
7463
|
);
|
|
7372
7464
|
try {
|
|
@@ -7388,7 +7480,7 @@ Rules:
|
|
|
7388
7480
|
if (recheckErrors.length < errors.length) {
|
|
7389
7481
|
codeToWrite = fixedCode2;
|
|
7390
7482
|
await writeFile(filePath, codeToWrite);
|
|
7391
|
-
console.log(
|
|
7483
|
+
console.log(chalk11.green(` \u2714 Quality fix: ${errors.length} \u2192 ${recheckErrors.length} errors`));
|
|
7392
7484
|
}
|
|
7393
7485
|
}
|
|
7394
7486
|
}
|
|
@@ -7397,9 +7489,9 @@ Rules:
|
|
|
7397
7489
|
}
|
|
7398
7490
|
const report = formatIssues(issues);
|
|
7399
7491
|
if (report) {
|
|
7400
|
-
console.log(
|
|
7492
|
+
console.log(chalk11.yellow(`
|
|
7401
7493
|
\u{1F50D} Quality check for ${page.name || page.id}:`));
|
|
7402
|
-
console.log(
|
|
7494
|
+
console.log(chalk11.dim(report));
|
|
7403
7495
|
}
|
|
7404
7496
|
}
|
|
7405
7497
|
}
|
|
@@ -7414,9 +7506,9 @@ Rules:
|
|
|
7414
7506
|
const changes = request.changes;
|
|
7415
7507
|
const instruction = originalMessage || (typeof changes?.instruction === "string" ? changes.instruction : void 0);
|
|
7416
7508
|
let resolvedPageCode = typeof changes?.pageCode === "string" && changes.pageCode.trim() !== "" ? changes.pageCode : void 0;
|
|
7417
|
-
if (DEBUG2 && instruction) console.log(
|
|
7509
|
+
if (DEBUG2 && instruction) console.log(chalk11.dim(` [update-page] instruction: ${instruction.slice(0, 120)}...`));
|
|
7418
7510
|
if (DEBUG2 && resolvedPageCode)
|
|
7419
|
-
console.log(
|
|
7511
|
+
console.log(chalk11.dim(` [update-page] has pageCode (${resolvedPageCode.length} chars)`));
|
|
7420
7512
|
const configChanges = { ...changes };
|
|
7421
7513
|
delete configChanges.pageCode;
|
|
7422
7514
|
delete configChanges.pageType;
|
|
@@ -7440,12 +7532,12 @@ Rules:
|
|
|
7440
7532
|
try {
|
|
7441
7533
|
currentCode = await readFile(absPath);
|
|
7442
7534
|
} catch {
|
|
7443
|
-
if (DEBUG2) console.log(
|
|
7535
|
+
if (DEBUG2) console.log(chalk11.dim(` [update-page] Could not read current file at ${absPath}`));
|
|
7444
7536
|
}
|
|
7445
7537
|
if (currentCode) {
|
|
7446
7538
|
const ai = await createAIProvider(aiProvider ?? "auto");
|
|
7447
7539
|
if (ai.editPageCode) {
|
|
7448
|
-
console.log(
|
|
7540
|
+
console.log(chalk11.dim(" \u270F\uFE0F Applying changes to existing page..."));
|
|
7449
7541
|
const coreRules = CORE_CONSTRAINTS;
|
|
7450
7542
|
const qualityRules = DESIGN_QUALITY;
|
|
7451
7543
|
const contextualRules = selectContextualRules(instruction);
|
|
@@ -7465,9 +7557,9 @@ ${contextualRules}
|
|
|
7465
7557
|
${routeRules}
|
|
7466
7558
|
${pagesCtx}`
|
|
7467
7559
|
);
|
|
7468
|
-
if (DEBUG2) console.log(
|
|
7560
|
+
if (DEBUG2) console.log(chalk11.dim(` [update-page] AI returned ${resolvedPageCode.length} chars`));
|
|
7469
7561
|
} else {
|
|
7470
|
-
console.log(
|
|
7562
|
+
console.log(chalk11.yellow(" \u26A0 AI provider does not support editPageCode"));
|
|
7471
7563
|
}
|
|
7472
7564
|
}
|
|
7473
7565
|
}
|
|
@@ -7497,10 +7589,13 @@ ${pagesCtx}`
|
|
|
7497
7589
|
let codeToWrite = fixedCode;
|
|
7498
7590
|
const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite);
|
|
7499
7591
|
codeToWrite = autoFixed;
|
|
7592
|
+
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
7593
|
+
codeToWrite = layoutStripped;
|
|
7500
7594
|
const allFixes = [...postFixes, ...autoFixes];
|
|
7595
|
+
if (stripped.length > 0) allFixes.push(`stripped inline ${stripped.join(", ")} (layout owns these)`);
|
|
7501
7596
|
if (allFixes.length > 0) {
|
|
7502
|
-
console.log(
|
|
7503
|
-
allFixes.forEach((f) => console.log(
|
|
7597
|
+
console.log(chalk11.dim(" \u{1F527} Post-generation fixes:"));
|
|
7598
|
+
allFixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
7504
7599
|
}
|
|
7505
7600
|
await writeFile(absPath, codeToWrite);
|
|
7506
7601
|
const updatePageIdx = dsm.getConfig().pages.findIndex((p) => p.id === pageDef.id);
|
|
@@ -7511,7 +7606,7 @@ ${pagesCtx}`
|
|
|
7511
7606
|
cm.updateConfig(cfg);
|
|
7512
7607
|
pm.updateConfig(cfg);
|
|
7513
7608
|
}
|
|
7514
|
-
const manifestForAudit = await
|
|
7609
|
+
const manifestForAudit = await loadManifest6(projectRoot);
|
|
7515
7610
|
await warnInlineDuplicates(
|
|
7516
7611
|
projectRoot,
|
|
7517
7612
|
pageDef.name || pageDef.id || route.slice(1),
|
|
@@ -7533,9 +7628,9 @@ ${pagesCtx}`
|
|
|
7533
7628
|
const issues = validatePageQuality(codeToWrite);
|
|
7534
7629
|
const report = formatIssues(issues);
|
|
7535
7630
|
if (report) {
|
|
7536
|
-
console.log(
|
|
7631
|
+
console.log(chalk11.yellow(`
|
|
7537
7632
|
\u{1F50D} Quality check for ${pageDef.name || pageDef.id}:`));
|
|
7538
|
-
console.log(
|
|
7633
|
+
console.log(chalk11.dim(report));
|
|
7539
7634
|
}
|
|
7540
7635
|
} else {
|
|
7541
7636
|
try {
|
|
@@ -7544,11 +7639,11 @@ ${pagesCtx}`
|
|
|
7544
7639
|
if (fixes.length > 0) {
|
|
7545
7640
|
code = fixed;
|
|
7546
7641
|
await writeFile(absPath, code);
|
|
7547
|
-
console.log(
|
|
7548
|
-
fixes.forEach((f) => console.log(
|
|
7642
|
+
console.log(chalk11.dim(" \u{1F527} Auto-fixes applied:"));
|
|
7643
|
+
fixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
7549
7644
|
}
|
|
7550
7645
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
7551
|
-
const manifest = await
|
|
7646
|
+
const manifest = await loadManifest6(projectRoot);
|
|
7552
7647
|
printPostGenerationReport({
|
|
7553
7648
|
action: "updated",
|
|
7554
7649
|
pageTitle: pageDef.name || pageDef.id || "Page",
|
|
@@ -7562,9 +7657,9 @@ ${pagesCtx}`
|
|
|
7562
7657
|
const issues = validatePageQuality(code);
|
|
7563
7658
|
const report = formatIssues(issues);
|
|
7564
7659
|
if (report) {
|
|
7565
|
-
console.log(
|
|
7660
|
+
console.log(chalk11.yellow(`
|
|
7566
7661
|
\u{1F50D} Quality check for ${pageDef.name || pageDef.id}:`));
|
|
7567
|
-
console.log(
|
|
7662
|
+
console.log(chalk11.dim(report));
|
|
7568
7663
|
}
|
|
7569
7664
|
} catch {
|
|
7570
7665
|
}
|
|
@@ -7593,236 +7688,10 @@ ${pagesCtx}`
|
|
|
7593
7688
|
}
|
|
7594
7689
|
}
|
|
7595
7690
|
|
|
7596
|
-
// src/commands/chat/layout-extractor.ts
|
|
7597
|
-
import { readFileSync as readFileSync9, readdirSync as readdirSync2 } from "fs";
|
|
7598
|
-
import { join as join9, resolve as resolve8 } from "path";
|
|
7599
|
-
import chalk11 from "chalk";
|
|
7600
|
-
import { loadManifest as loadManifest6, generateSharedComponent as generateSharedComponent3, integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout3 } from "@getcoherent/core";
|
|
7601
|
-
|
|
7602
|
-
// src/commands/chat/jsx-extractor.ts
|
|
7603
|
-
function extractBalancedTag(source, tagName) {
|
|
7604
|
-
const openRe = new RegExp(`<${tagName}\\b`, "gi");
|
|
7605
|
-
const match = openRe.exec(source);
|
|
7606
|
-
if (!match) return null;
|
|
7607
|
-
const startIdx = match.index;
|
|
7608
|
-
const openTagRe = new RegExp(`<${tagName}\\b`, "gi");
|
|
7609
|
-
const closeTagRe = new RegExp(`</${tagName}>`, "gi");
|
|
7610
|
-
const events = [];
|
|
7611
|
-
let m;
|
|
7612
|
-
openTagRe.lastIndex = startIdx;
|
|
7613
|
-
while ((m = openTagRe.exec(source)) !== null) {
|
|
7614
|
-
events.push({ pos: m.index, type: "open", end: m.index + m[0].length });
|
|
7615
|
-
}
|
|
7616
|
-
closeTagRe.lastIndex = startIdx;
|
|
7617
|
-
while ((m = closeTagRe.exec(source)) !== null) {
|
|
7618
|
-
events.push({ pos: m.index, type: "close", end: m.index + m[0].length });
|
|
7619
|
-
}
|
|
7620
|
-
events.sort((a, b) => a.pos - b.pos);
|
|
7621
|
-
let depth = 0;
|
|
7622
|
-
for (const ev of events) {
|
|
7623
|
-
if (ev.pos < startIdx) continue;
|
|
7624
|
-
if (ev.type === "open") depth++;
|
|
7625
|
-
else {
|
|
7626
|
-
depth--;
|
|
7627
|
-
if (depth === 0) return source.slice(startIdx, ev.end);
|
|
7628
|
-
}
|
|
7629
|
-
}
|
|
7630
|
-
return null;
|
|
7631
|
-
}
|
|
7632
|
-
function extractRelevantImports(fullSource, jsxBlock) {
|
|
7633
|
-
const importLines = [];
|
|
7634
|
-
const importRe = /^import\s+.*from\s+['"][^'"]+['"];?\s*$/gm;
|
|
7635
|
-
let m;
|
|
7636
|
-
while ((m = importRe.exec(fullSource)) !== null) {
|
|
7637
|
-
const line = m[0];
|
|
7638
|
-
const namesMatch = line.match(/import\s*\{([^}]+)\}/);
|
|
7639
|
-
if (namesMatch) {
|
|
7640
|
-
const names = namesMatch[1].split(",").map(
|
|
7641
|
-
(n) => n.trim().split(/\s+as\s+/).pop().trim()
|
|
7642
|
-
);
|
|
7643
|
-
if (names.some((name) => jsxBlock.includes(name))) {
|
|
7644
|
-
importLines.push(line);
|
|
7645
|
-
}
|
|
7646
|
-
}
|
|
7647
|
-
const defaultMatch = line.match(/import\s+(\w+)\s+from/);
|
|
7648
|
-
if (defaultMatch && jsxBlock.includes(defaultMatch[1])) {
|
|
7649
|
-
importLines.push(line);
|
|
7650
|
-
}
|
|
7651
|
-
}
|
|
7652
|
-
return [...new Set(importLines)];
|
|
7653
|
-
}
|
|
7654
|
-
function extractStateHooks(fullSource, jsxBlock) {
|
|
7655
|
-
const hooks = [];
|
|
7656
|
-
const stateRe = /const\s+\[(\w+),\s*(\w+)\]\s*=\s*useState\b[^)]*\)/g;
|
|
7657
|
-
let m;
|
|
7658
|
-
while ((m = stateRe.exec(fullSource)) !== null) {
|
|
7659
|
-
const [fullMatch, getter, setter] = m;
|
|
7660
|
-
if (jsxBlock.includes(getter) || jsxBlock.includes(setter)) {
|
|
7661
|
-
hooks.push(fullMatch);
|
|
7662
|
-
}
|
|
7663
|
-
}
|
|
7664
|
-
return hooks;
|
|
7665
|
-
}
|
|
7666
|
-
function addActiveNavToHeader(code) {
|
|
7667
|
-
let result = code;
|
|
7668
|
-
if (!result.includes("usePathname")) {
|
|
7669
|
-
if (result.includes("from 'next/navigation'")) {
|
|
7670
|
-
result = result.replace(
|
|
7671
|
-
/import\s*\{([^}]+)\}\s*from\s*'next\/navigation'/,
|
|
7672
|
-
(_, names) => `import { ${names.trim()}, usePathname } from 'next/navigation'`
|
|
7673
|
-
);
|
|
7674
|
-
} else {
|
|
7675
|
-
result = result.replace(
|
|
7676
|
-
"export function Header()",
|
|
7677
|
-
"import { usePathname } from 'next/navigation'\n\nexport function Header()"
|
|
7678
|
-
);
|
|
7679
|
-
}
|
|
7680
|
-
}
|
|
7681
|
-
if (!result.includes("const pathname")) {
|
|
7682
|
-
result = result.replace(
|
|
7683
|
-
/export function Header\(\)\s*\{/,
|
|
7684
|
-
"export function Header() {\n const pathname = usePathname()"
|
|
7685
|
-
);
|
|
7686
|
-
}
|
|
7687
|
-
result = result.replace(
|
|
7688
|
-
/<Link\s+href="(\/[^"]*?)"\s+className="([^"]*?)(?:text-foreground|text-muted-foreground(?:\s+hover:text-foreground)?(?:\s+transition-colors)?)([^"]*?)">/g,
|
|
7689
|
-
(_, href, before, after) => {
|
|
7690
|
-
const base = before.trim();
|
|
7691
|
-
const trail = after.trim();
|
|
7692
|
-
const staticParts = [base, trail].filter(Boolean).join(" ");
|
|
7693
|
-
const space = staticParts ? " " : "";
|
|
7694
|
-
return `<Link href="${href}" className={\`${staticParts}${space}\${pathname === '${href}' ? 'text-foreground font-medium' : 'text-muted-foreground hover:text-foreground transition-colors'}\`}>`;
|
|
7695
|
-
}
|
|
7696
|
-
);
|
|
7697
|
-
return result;
|
|
7698
|
-
}
|
|
7699
|
-
|
|
7700
|
-
// src/commands/chat/layout-extractor.ts
|
|
7701
|
-
function findAllPageFiles(dir) {
|
|
7702
|
-
const results = [];
|
|
7703
|
-
try {
|
|
7704
|
-
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
7705
|
-
const full = join9(dir, entry.name);
|
|
7706
|
-
if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".next" && entry.name !== "design-system") {
|
|
7707
|
-
results.push(...findAllPageFiles(full));
|
|
7708
|
-
} else if (entry.name === "page.tsx" || entry.name === "page.jsx") {
|
|
7709
|
-
results.push(full);
|
|
7710
|
-
}
|
|
7711
|
-
}
|
|
7712
|
-
} catch {
|
|
7713
|
-
}
|
|
7714
|
-
return results;
|
|
7715
|
-
}
|
|
7716
|
-
async function extractAndShareLayoutComponents(projectRoot, generatedPageFiles) {
|
|
7717
|
-
const manifest = await loadManifest6(projectRoot);
|
|
7718
|
-
const hasSharedHeader = manifest.shared.some((c) => c.type === "layout" && /header|nav/i.test(c.name));
|
|
7719
|
-
const hasSharedFooter = manifest.shared.some((c) => c.type === "layout" && /footer/i.test(c.name));
|
|
7720
|
-
if (hasSharedHeader && hasSharedFooter) return false;
|
|
7721
|
-
let sourceCode = "";
|
|
7722
|
-
for (const file of generatedPageFiles) {
|
|
7723
|
-
try {
|
|
7724
|
-
const code = readFileSync9(file, "utf-8");
|
|
7725
|
-
if (code.includes("<header") || code.includes("<footer") || code.includes("<nav")) {
|
|
7726
|
-
sourceCode = code;
|
|
7727
|
-
break;
|
|
7728
|
-
}
|
|
7729
|
-
} catch {
|
|
7730
|
-
continue;
|
|
7731
|
-
}
|
|
7732
|
-
}
|
|
7733
|
-
if (!sourceCode) return false;
|
|
7734
|
-
let extracted = false;
|
|
7735
|
-
if (!hasSharedHeader) {
|
|
7736
|
-
let headerJsx = extractBalancedTag(sourceCode, "header");
|
|
7737
|
-
if (!headerJsx) headerJsx = extractBalancedTag(sourceCode, "nav");
|
|
7738
|
-
if (headerJsx) {
|
|
7739
|
-
const imports = extractRelevantImports(sourceCode, headerJsx);
|
|
7740
|
-
const importBlock = imports.length > 0 ? imports.join("\n") + "\n" : "import Link from 'next/link'\n";
|
|
7741
|
-
const stateHooks = extractStateHooks(sourceCode, headerJsx);
|
|
7742
|
-
const needsReactImport = stateHooks.length > 0 && !importBlock.includes("from 'react'");
|
|
7743
|
-
const reactImport = needsReactImport ? "import { useState } from 'react'\n" : "";
|
|
7744
|
-
const stateBlock = stateHooks.length > 0 ? " " + stateHooks.join("\n ") + "\n" : "";
|
|
7745
|
-
const returnIndent = stateBlock ? " " : " ";
|
|
7746
|
-
let headerComponent = `'use client'
|
|
7747
|
-
|
|
7748
|
-
${reactImport}${importBlock}
|
|
7749
|
-
export function Header() {
|
|
7750
|
-
${stateBlock}${returnIndent}return (
|
|
7751
|
-
${headerJsx}
|
|
7752
|
-
)
|
|
7753
|
-
}
|
|
7754
|
-
`;
|
|
7755
|
-
headerComponent = addActiveNavToHeader(headerComponent);
|
|
7756
|
-
await generateSharedComponent3(projectRoot, {
|
|
7757
|
-
name: "Header",
|
|
7758
|
-
type: "layout",
|
|
7759
|
-
code: headerComponent,
|
|
7760
|
-
description: "Main site header/navigation",
|
|
7761
|
-
usedIn: ["app/layout.tsx"]
|
|
7762
|
-
});
|
|
7763
|
-
extracted = true;
|
|
7764
|
-
}
|
|
7765
|
-
}
|
|
7766
|
-
if (!hasSharedFooter) {
|
|
7767
|
-
const footerJsx = extractBalancedTag(sourceCode, "footer");
|
|
7768
|
-
if (footerJsx) {
|
|
7769
|
-
const imports = extractRelevantImports(sourceCode, footerJsx);
|
|
7770
|
-
const importBlock = imports.length > 0 ? imports.join("\n") + "\n" : "import Link from 'next/link'\n";
|
|
7771
|
-
const stateHooks = extractStateHooks(sourceCode, footerJsx);
|
|
7772
|
-
const needsReactImport = stateHooks.length > 0 && !importBlock.includes("from 'react'");
|
|
7773
|
-
const reactImport = needsReactImport ? "import { useState } from 'react'\n" : "";
|
|
7774
|
-
const stateBlock = stateHooks.length > 0 ? " " + stateHooks.join("\n ") + "\n" : "";
|
|
7775
|
-
const returnIndent = stateBlock ? " " : " ";
|
|
7776
|
-
const footerComponent = `'use client'
|
|
7777
|
-
|
|
7778
|
-
${reactImport}${importBlock}
|
|
7779
|
-
export function Footer() {
|
|
7780
|
-
${stateBlock}${returnIndent}return (
|
|
7781
|
-
${footerJsx}
|
|
7782
|
-
)
|
|
7783
|
-
}
|
|
7784
|
-
`;
|
|
7785
|
-
await generateSharedComponent3(projectRoot, {
|
|
7786
|
-
name: "Footer",
|
|
7787
|
-
type: "layout",
|
|
7788
|
-
code: footerComponent,
|
|
7789
|
-
description: "Site footer",
|
|
7790
|
-
usedIn: ["app/layout.tsx"]
|
|
7791
|
-
});
|
|
7792
|
-
extracted = true;
|
|
7793
|
-
}
|
|
7794
|
-
}
|
|
7795
|
-
if (!extracted) return false;
|
|
7796
|
-
await integrateSharedLayoutIntoRootLayout3(projectRoot);
|
|
7797
|
-
await ensureAuthRouteGroup(projectRoot);
|
|
7798
|
-
const allPageFiles = /* @__PURE__ */ new Set([...generatedPageFiles, ...findAllPageFiles(resolve8(projectRoot, "app"))]);
|
|
7799
|
-
for (const file of allPageFiles) {
|
|
7800
|
-
try {
|
|
7801
|
-
let code = await readFile(file);
|
|
7802
|
-
const original = code;
|
|
7803
|
-
const headerBlock = extractBalancedTag(code, "header");
|
|
7804
|
-
if (headerBlock) {
|
|
7805
|
-
code = code.replace(headerBlock, "");
|
|
7806
|
-
} else {
|
|
7807
|
-
const navBlock = extractBalancedTag(code, "nav");
|
|
7808
|
-
if (navBlock) code = code.replace(navBlock, "");
|
|
7809
|
-
}
|
|
7810
|
-
const footerBlock = extractBalancedTag(code, "footer");
|
|
7811
|
-
if (footerBlock) code = code.replace(footerBlock, "");
|
|
7812
|
-
code = code.replace(/\n{3,}/g, "\n\n");
|
|
7813
|
-
if (code !== original) await writeFile(file, code);
|
|
7814
|
-
} catch {
|
|
7815
|
-
continue;
|
|
7816
|
-
}
|
|
7817
|
-
}
|
|
7818
|
-
console.log(chalk11.cyan(" \u{1F517} Extracted Header and Footer as shared components (all pages via layout)"));
|
|
7819
|
-
return true;
|
|
7820
|
-
}
|
|
7821
|
-
|
|
7822
7691
|
// src/commands/chat/interactive.ts
|
|
7823
7692
|
import chalk12 from "chalk";
|
|
7824
|
-
import { resolve as
|
|
7825
|
-
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";
|
|
7826
7695
|
import { DesignSystemManager as DesignSystemManager6, ComponentManager as ComponentManager3, loadManifest as loadManifest7 } from "@getcoherent/core";
|
|
7827
7696
|
var DEBUG3 = process.env.COHERENT_DEBUG === "1";
|
|
7828
7697
|
async function interactiveChat(options, chatCommandFn) {
|
|
@@ -7842,13 +7711,13 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
7842
7711
|
\u274C Invalid provider: ${options.provider}`));
|
|
7843
7712
|
process.exit(1);
|
|
7844
7713
|
}
|
|
7845
|
-
const historyDir =
|
|
7846
|
-
const historyFile =
|
|
7714
|
+
const historyDir = resolve8(homedir2(), ".coherent");
|
|
7715
|
+
const historyFile = resolve8(historyDir, "history");
|
|
7847
7716
|
let history = [];
|
|
7848
7717
|
try {
|
|
7849
7718
|
mkdirSync5(historyDir, { recursive: true });
|
|
7850
7719
|
if (existsSync15(historyFile)) {
|
|
7851
|
-
history =
|
|
7720
|
+
history = readFileSync9(historyFile, "utf-8").split("\n").filter(Boolean).slice(-200);
|
|
7852
7721
|
}
|
|
7853
7722
|
} catch (e) {
|
|
7854
7723
|
if (DEBUG3) console.error("Failed to load REPL history:", e);
|
|
@@ -8072,7 +7941,7 @@ async function chatCommand(message, options) {
|
|
|
8072
7941
|
}
|
|
8073
7942
|
if (/switch to light mode|default to light|make.*light.*(default|theme)|light theme/i.test(message)) {
|
|
8074
7943
|
spinner.start("Setting default theme to light...");
|
|
8075
|
-
const layoutPath =
|
|
7944
|
+
const layoutPath = resolve9(projectRoot, "app/layout.tsx");
|
|
8076
7945
|
try {
|
|
8077
7946
|
let layout = await readFile(layoutPath);
|
|
8078
7947
|
layout = layout.replace(/className="dark"/, "");
|
|
@@ -8111,7 +7980,7 @@ async function chatCommand(message, options) {
|
|
|
8111
7980
|
spinner.start("Parsing your request...");
|
|
8112
7981
|
let manifest = await loadManifest8(project.root);
|
|
8113
7982
|
const validShared = manifest.shared.filter((s) => {
|
|
8114
|
-
const fp =
|
|
7983
|
+
const fp = resolve9(project.root, s.file);
|
|
8115
7984
|
return existsSync16(fp);
|
|
8116
7985
|
});
|
|
8117
7986
|
if (validShared.length !== manifest.shared.length) {
|
|
@@ -8407,21 +8276,6 @@ async function chatCommand(message, options) {
|
|
|
8407
8276
|
const result = await applyModification(request, dsm, cm, pm, projectRoot, provider, message);
|
|
8408
8277
|
results.push(result);
|
|
8409
8278
|
}
|
|
8410
|
-
const anyPageGenerated = normalizedRequests.some(
|
|
8411
|
-
(req, i) => (req.type === "add-page" || req.type === "update-page") && results[i]?.success
|
|
8412
|
-
);
|
|
8413
|
-
if (anyPageGenerated) {
|
|
8414
|
-
const generatedPageFiles = normalizedRequests.filter((req, i) => (req.type === "add-page" || req.type === "update-page") && results[i]?.success).map((req) => {
|
|
8415
|
-
const page = req.changes;
|
|
8416
|
-
const route = page.route || `/${page.id || "page"}`;
|
|
8417
|
-
return routeToFsPath(projectRoot, route, isAuthRoute(route));
|
|
8418
|
-
}).filter((f) => existsSync16(f));
|
|
8419
|
-
try {
|
|
8420
|
-
await extractAndShareLayoutComponents(projectRoot, generatedPageFiles);
|
|
8421
|
-
} catch (err) {
|
|
8422
|
-
if (DEBUG4) console.log(chalk13.dim("Shared layout extraction failed:", err));
|
|
8423
|
-
}
|
|
8424
|
-
}
|
|
8425
8279
|
const currentConfig = dsm.getConfig();
|
|
8426
8280
|
const autoScaffoldEnabled = currentConfig.settings.autoScaffold === true;
|
|
8427
8281
|
const scaffoldedPages = [];
|
|
@@ -8435,7 +8289,7 @@ async function chatCommand(message, options) {
|
|
|
8435
8289
|
let pageCode = "";
|
|
8436
8290
|
if (existsSync16(pageFilePath)) {
|
|
8437
8291
|
try {
|
|
8438
|
-
pageCode =
|
|
8292
|
+
pageCode = readFileSync10(pageFilePath, "utf-8");
|
|
8439
8293
|
} catch {
|
|
8440
8294
|
}
|
|
8441
8295
|
}
|
|
@@ -8512,7 +8366,7 @@ async function chatCommand(message, options) {
|
|
|
8512
8366
|
const isAuth = isAuthRoute(linkedRoute);
|
|
8513
8367
|
const filePath = routeToFsPath(projectRoot, linkedRoute, isAuth);
|
|
8514
8368
|
if (isAuth) await ensureAuthRouteGroup(projectRoot);
|
|
8515
|
-
const dir =
|
|
8369
|
+
const dir = resolve9(filePath, "..");
|
|
8516
8370
|
if (!existsSync16(dir)) {
|
|
8517
8371
|
mkdirSync6(dir, { recursive: true });
|
|
8518
8372
|
}
|
|
@@ -8548,7 +8402,7 @@ async function chatCommand(message, options) {
|
|
|
8548
8402
|
dsm.updateConfig(latestConfig);
|
|
8549
8403
|
if (DEBUG4) console.log(chalk13.dim(` [theme] Set defaultMode to "${targetMode}"`));
|
|
8550
8404
|
}
|
|
8551
|
-
const layoutPath =
|
|
8405
|
+
const layoutPath = resolve9(projectRoot, "app", "layout.tsx");
|
|
8552
8406
|
try {
|
|
8553
8407
|
let layoutCode = await readFile(layoutPath);
|
|
8554
8408
|
if (targetMode === "dark" && !layoutCode.includes('className="dark"')) {
|
|
@@ -8603,7 +8457,7 @@ async function chatCommand(message, options) {
|
|
|
8603
8457
|
console.log("");
|
|
8604
8458
|
}
|
|
8605
8459
|
if (uxRecommendations) {
|
|
8606
|
-
const recPath =
|
|
8460
|
+
const recPath = resolve9(projectRoot, "recommendations.md");
|
|
8607
8461
|
const section = `
|
|
8608
8462
|
|
|
8609
8463
|
---
|
|
@@ -8687,19 +8541,19 @@ ${uxRecommendations}
|
|
|
8687
8541
|
import chalk14 from "chalk";
|
|
8688
8542
|
import ora3 from "ora";
|
|
8689
8543
|
import { spawn } from "child_process";
|
|
8690
|
-
import { existsSync as existsSync19, rmSync as rmSync3, readFileSync as
|
|
8691
|
-
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";
|
|
8692
8546
|
import { readdir as readdir2 } from "fs/promises";
|
|
8693
8547
|
import { DesignSystemManager as DesignSystemManager8, ComponentGenerator as ComponentGenerator3 } from "@getcoherent/core";
|
|
8694
8548
|
|
|
8695
8549
|
// src/utils/file-watcher.ts
|
|
8696
|
-
import { readFileSync as
|
|
8697
|
-
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";
|
|
8698
8552
|
import { loadManifest as loadManifest9, saveManifest as saveManifest3 } from "@getcoherent/core";
|
|
8699
8553
|
|
|
8700
8554
|
// src/utils/component-integrity.ts
|
|
8701
|
-
import { existsSync as existsSync17, readFileSync as
|
|
8702
|
-
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";
|
|
8703
8557
|
function extractExportedComponentNames(code) {
|
|
8704
8558
|
const names = [];
|
|
8705
8559
|
let m;
|
|
@@ -8725,14 +8579,14 @@ function arraysEqual(a, b) {
|
|
|
8725
8579
|
}
|
|
8726
8580
|
function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
8727
8581
|
const results = [];
|
|
8728
|
-
const appDir =
|
|
8582
|
+
const appDir = join9(projectRoot, "app");
|
|
8729
8583
|
if (!existsSync17(appDir)) return results;
|
|
8730
8584
|
const pageFiles = collectFiles(appDir, (name) => name === "page.tsx" || name === "page.jsx");
|
|
8731
8585
|
const componentImportPath = componentFile.replace(/\.tsx$/, "").replace(/\.jsx$/, "");
|
|
8732
8586
|
for (const absPath of pageFiles) {
|
|
8733
8587
|
if (absPath.includes("design-system")) continue;
|
|
8734
8588
|
try {
|
|
8735
|
-
const code =
|
|
8589
|
+
const code = readFileSync11(absPath, "utf-8");
|
|
8736
8590
|
const hasNamedImport = new RegExp(`import\\s+\\{[^}]*\\b${componentName}\\b[^}]*\\}\\s+from\\s+['"]`).test(code);
|
|
8737
8591
|
const hasDefaultImport = new RegExp(`import\\s+${componentName}\\s+from\\s+['"]`).test(code);
|
|
8738
8592
|
const hasPathImport = code.includes(`@/${componentImportPath}`);
|
|
@@ -8745,10 +8599,10 @@ function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
|
8745
8599
|
return results;
|
|
8746
8600
|
}
|
|
8747
8601
|
function isUsedInLayout(projectRoot, componentName) {
|
|
8748
|
-
const layoutPath =
|
|
8602
|
+
const layoutPath = join9(projectRoot, "app", "layout.tsx");
|
|
8749
8603
|
if (!existsSync17(layoutPath)) return false;
|
|
8750
8604
|
try {
|
|
8751
|
-
const code =
|
|
8605
|
+
const code = readFileSync11(layoutPath, "utf-8");
|
|
8752
8606
|
return code.includes(componentName);
|
|
8753
8607
|
} catch {
|
|
8754
8608
|
return false;
|
|
@@ -8756,7 +8610,7 @@ function isUsedInLayout(projectRoot, componentName) {
|
|
|
8756
8610
|
}
|
|
8757
8611
|
function findUnregisteredComponents(projectRoot, manifest) {
|
|
8758
8612
|
const results = [];
|
|
8759
|
-
const componentsDir =
|
|
8613
|
+
const componentsDir = join9(projectRoot, "components");
|
|
8760
8614
|
if (!existsSync17(componentsDir)) return results;
|
|
8761
8615
|
const registeredFiles = new Set(manifest.shared.map((s) => s.file));
|
|
8762
8616
|
const registeredNames = new Set(manifest.shared.map((s) => s.name));
|
|
@@ -8769,7 +8623,7 @@ function findUnregisteredComponents(projectRoot, manifest) {
|
|
|
8769
8623
|
const relFile = relative2(projectRoot, absPath);
|
|
8770
8624
|
if (registeredFiles.has(relFile)) continue;
|
|
8771
8625
|
try {
|
|
8772
|
-
const code =
|
|
8626
|
+
const code = readFileSync11(absPath, "utf-8");
|
|
8773
8627
|
const exports = extractExportedComponentNames(code);
|
|
8774
8628
|
for (const name of exports) {
|
|
8775
8629
|
if (registeredNames.has(name)) continue;
|
|
@@ -8784,14 +8638,14 @@ function findUnregisteredComponents(projectRoot, manifest) {
|
|
|
8784
8638
|
}
|
|
8785
8639
|
function findInlineDuplicates(projectRoot, manifest) {
|
|
8786
8640
|
const results = [];
|
|
8787
|
-
const appDir =
|
|
8641
|
+
const appDir = join9(projectRoot, "app");
|
|
8788
8642
|
if (!existsSync17(appDir)) return results;
|
|
8789
8643
|
const pageFiles = collectFiles(appDir, (name) => name === "page.tsx" || name === "page.jsx");
|
|
8790
8644
|
for (const absPath of pageFiles) {
|
|
8791
8645
|
if (absPath.includes("design-system")) continue;
|
|
8792
8646
|
let code;
|
|
8793
8647
|
try {
|
|
8794
|
-
code =
|
|
8648
|
+
code = readFileSync11(absPath, "utf-8");
|
|
8795
8649
|
} catch {
|
|
8796
8650
|
continue;
|
|
8797
8651
|
}
|
|
@@ -8814,7 +8668,7 @@ function findInlineDuplicates(projectRoot, manifest) {
|
|
|
8814
8668
|
return results;
|
|
8815
8669
|
}
|
|
8816
8670
|
function findComponentFileByExportName(projectRoot, componentName) {
|
|
8817
|
-
const componentsDir =
|
|
8671
|
+
const componentsDir = join9(projectRoot, "components");
|
|
8818
8672
|
if (!existsSync17(componentsDir)) return null;
|
|
8819
8673
|
const files = collectFiles(
|
|
8820
8674
|
componentsDir,
|
|
@@ -8823,7 +8677,7 @@ function findComponentFileByExportName(projectRoot, componentName) {
|
|
|
8823
8677
|
);
|
|
8824
8678
|
for (const absPath of files) {
|
|
8825
8679
|
try {
|
|
8826
|
-
const code =
|
|
8680
|
+
const code = readFileSync11(absPath, "utf-8");
|
|
8827
8681
|
const exports = extractExportedComponentNames(code);
|
|
8828
8682
|
if (exports.includes(componentName)) {
|
|
8829
8683
|
return relative2(projectRoot, absPath);
|
|
@@ -8836,7 +8690,7 @@ function findComponentFileByExportName(projectRoot, componentName) {
|
|
|
8836
8690
|
function removeOrphanedEntries(projectRoot, manifest) {
|
|
8837
8691
|
const removed = [];
|
|
8838
8692
|
const valid = manifest.shared.filter((entry) => {
|
|
8839
|
-
const filePath =
|
|
8693
|
+
const filePath = join9(projectRoot, entry.file);
|
|
8840
8694
|
if (existsSync17(filePath)) return true;
|
|
8841
8695
|
removed.push({ id: entry.id, name: entry.name });
|
|
8842
8696
|
return false;
|
|
@@ -8855,7 +8709,7 @@ function reconcileComponents(projectRoot, manifest) {
|
|
|
8855
8709
|
};
|
|
8856
8710
|
const m = { ...manifest, shared: [...manifest.shared], nextId: manifest.nextId };
|
|
8857
8711
|
m.shared = m.shared.filter((entry) => {
|
|
8858
|
-
const filePath =
|
|
8712
|
+
const filePath = join9(projectRoot, entry.file);
|
|
8859
8713
|
if (!existsSync17(filePath)) {
|
|
8860
8714
|
const newPath = findComponentFileByExportName(projectRoot, entry.name);
|
|
8861
8715
|
if (newPath) {
|
|
@@ -8868,7 +8722,7 @@ function reconcileComponents(projectRoot, manifest) {
|
|
|
8868
8722
|
}
|
|
8869
8723
|
let code;
|
|
8870
8724
|
try {
|
|
8871
|
-
code =
|
|
8725
|
+
code = readFileSync11(join9(projectRoot, entry.file), "utf-8");
|
|
8872
8726
|
} catch {
|
|
8873
8727
|
return true;
|
|
8874
8728
|
}
|
|
@@ -8945,12 +8799,12 @@ function collectFiles(dir, filter, skipDirs = []) {
|
|
|
8945
8799
|
function walk(d) {
|
|
8946
8800
|
let entries;
|
|
8947
8801
|
try {
|
|
8948
|
-
entries =
|
|
8802
|
+
entries = readdirSync2(d, { withFileTypes: true });
|
|
8949
8803
|
} catch {
|
|
8950
8804
|
return;
|
|
8951
8805
|
}
|
|
8952
8806
|
for (const e of entries) {
|
|
8953
|
-
const full =
|
|
8807
|
+
const full = join9(d, e.name);
|
|
8954
8808
|
if (e.isDirectory()) {
|
|
8955
8809
|
if (skipDirs.includes(e.name) || e.name.startsWith(".")) continue;
|
|
8956
8810
|
walk(full);
|
|
@@ -8989,9 +8843,9 @@ function findInlineDuplicatesOfShared(content, manifest) {
|
|
|
8989
8843
|
}
|
|
8990
8844
|
function getWatcherConfig(projectRoot) {
|
|
8991
8845
|
try {
|
|
8992
|
-
const pkgPath =
|
|
8846
|
+
const pkgPath = join10(projectRoot, "package.json");
|
|
8993
8847
|
if (!existsSync18(pkgPath)) return defaultWatcherConfig();
|
|
8994
|
-
const pkg = JSON.parse(
|
|
8848
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
8995
8849
|
const c = pkg?.coherent?.watcher ?? {};
|
|
8996
8850
|
return {
|
|
8997
8851
|
enabled: c.enabled !== false,
|
|
@@ -9019,7 +8873,7 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
9019
8873
|
if (relativePath.includes("node_modules") || relativePath.includes(".next")) return;
|
|
9020
8874
|
let content;
|
|
9021
8875
|
try {
|
|
9022
|
-
content =
|
|
8876
|
+
content = readFileSync12(filePath, "utf-8");
|
|
9023
8877
|
} catch {
|
|
9024
8878
|
return;
|
|
9025
8879
|
}
|
|
@@ -9092,7 +8946,7 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
9092
8946
|
const manifest = await loadManifest9(projectRoot);
|
|
9093
8947
|
const alreadyRegistered = manifest.shared.some((s) => s.file === relativePath);
|
|
9094
8948
|
if (alreadyRegistered) return;
|
|
9095
|
-
const code =
|
|
8949
|
+
const code = readFileSync12(filePath, "utf-8");
|
|
9096
8950
|
const exports = extractExportedComponentNames(code);
|
|
9097
8951
|
if (exports.length > 0) {
|
|
9098
8952
|
const alreadyByName = exports.every((n) => manifest.shared.some((s) => s.name === n));
|
|
@@ -9118,8 +8972,8 @@ function startFileWatcher(projectRoot) {
|
|
|
9118
8972
|
let watcher = null;
|
|
9119
8973
|
let manifestWatcher = null;
|
|
9120
8974
|
import("chokidar").then((chokidar) => {
|
|
9121
|
-
const appGlob =
|
|
9122
|
-
const compGlob =
|
|
8975
|
+
const appGlob = join10(projectRoot, "app", "**", "*.tsx");
|
|
8976
|
+
const compGlob = join10(projectRoot, "components", "**", "*.tsx");
|
|
9123
8977
|
watcher = chokidar.default.watch([appGlob, compGlob], {
|
|
9124
8978
|
ignoreInitial: true,
|
|
9125
8979
|
awaitWriteFinish: { stabilityThreshold: 500 }
|
|
@@ -9131,7 +8985,7 @@ function startFileWatcher(projectRoot) {
|
|
|
9131
8985
|
});
|
|
9132
8986
|
watcher.on("unlink", (fp) => handleFileDelete(projectRoot, fp));
|
|
9133
8987
|
});
|
|
9134
|
-
const manifestPath =
|
|
8988
|
+
const manifestPath = join10(projectRoot, "coherent.components.json");
|
|
9135
8989
|
if (existsSync18(manifestPath)) {
|
|
9136
8990
|
import("chokidar").then((chokidar) => {
|
|
9137
8991
|
manifestWatcher = chokidar.default.watch(manifestPath, { ignoreInitial: true });
|
|
@@ -9146,7 +9000,7 @@ function startFileWatcher(projectRoot) {
|
|
|
9146
9000
|
|
|
9147
9001
|
// src/commands/preview.ts
|
|
9148
9002
|
function getPackageManager(projectRoot) {
|
|
9149
|
-
const hasPnpm = existsSync19(
|
|
9003
|
+
const hasPnpm = existsSync19(resolve10(projectRoot, "pnpm-lock.yaml"));
|
|
9150
9004
|
return hasPnpm ? "pnpm" : "npm";
|
|
9151
9005
|
}
|
|
9152
9006
|
function runInstall(projectRoot) {
|
|
@@ -9168,8 +9022,8 @@ function runInstall(projectRoot) {
|
|
|
9168
9022
|
});
|
|
9169
9023
|
}
|
|
9170
9024
|
function checkProjectInitialized(projectRoot) {
|
|
9171
|
-
const configPath =
|
|
9172
|
-
const packageJsonPath =
|
|
9025
|
+
const configPath = resolve10(projectRoot, "design-system.config.ts");
|
|
9026
|
+
const packageJsonPath = resolve10(projectRoot, "package.json");
|
|
9173
9027
|
if (!existsSync19(configPath)) {
|
|
9174
9028
|
return false;
|
|
9175
9029
|
}
|
|
@@ -9179,11 +9033,11 @@ function checkProjectInitialized(projectRoot) {
|
|
|
9179
9033
|
return true;
|
|
9180
9034
|
}
|
|
9181
9035
|
function checkDependenciesInstalled(projectRoot) {
|
|
9182
|
-
const nodeModulesPath =
|
|
9036
|
+
const nodeModulesPath = resolve10(projectRoot, "node_modules");
|
|
9183
9037
|
return existsSync19(nodeModulesPath);
|
|
9184
9038
|
}
|
|
9185
9039
|
function clearStaleCache(projectRoot) {
|
|
9186
|
-
const nextDir =
|
|
9040
|
+
const nextDir = join11(projectRoot, ".next");
|
|
9187
9041
|
if (existsSync19(nextDir)) {
|
|
9188
9042
|
rmSync3(nextDir, { recursive: true, force: true });
|
|
9189
9043
|
console.log(chalk14.dim(" \u2714 Cleared stale build cache"));
|
|
@@ -9204,7 +9058,7 @@ async function listPageFiles(appDir) {
|
|
|
9204
9058
|
async function walk(dir) {
|
|
9205
9059
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
9206
9060
|
for (const e of entries) {
|
|
9207
|
-
const full =
|
|
9061
|
+
const full = join11(dir, e.name);
|
|
9208
9062
|
if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "api" && e.name !== "design-system") await walk(full);
|
|
9209
9063
|
else if (e.isFile() && e.name === "page.tsx") out.push(full);
|
|
9210
9064
|
}
|
|
@@ -9213,10 +9067,10 @@ async function listPageFiles(appDir) {
|
|
|
9213
9067
|
return out;
|
|
9214
9068
|
}
|
|
9215
9069
|
async function validateSyntax(projectRoot) {
|
|
9216
|
-
const appDir =
|
|
9070
|
+
const appDir = join11(projectRoot, "app");
|
|
9217
9071
|
const pages = await listPageFiles(appDir);
|
|
9218
9072
|
for (const file of pages) {
|
|
9219
|
-
const content =
|
|
9073
|
+
const content = readFileSync13(file, "utf-8");
|
|
9220
9074
|
const fixed = fixUnescapedLtInJsx(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)));
|
|
9221
9075
|
if (fixed !== content) {
|
|
9222
9076
|
writeFileSync10(file, fixed, "utf-8");
|
|
@@ -9225,13 +9079,13 @@ async function validateSyntax(projectRoot) {
|
|
|
9225
9079
|
}
|
|
9226
9080
|
}
|
|
9227
9081
|
async function fixMissingComponentExports(projectRoot) {
|
|
9228
|
-
const appDir =
|
|
9229
|
-
const uiDir =
|
|
9082
|
+
const appDir = join11(projectRoot, "app");
|
|
9083
|
+
const uiDir = join11(projectRoot, "components", "ui");
|
|
9230
9084
|
if (!existsSync19(appDir) || !existsSync19(uiDir)) return;
|
|
9231
9085
|
const pages = await listPageFiles(appDir);
|
|
9232
9086
|
const neededExports = /* @__PURE__ */ new Map();
|
|
9233
9087
|
for (const file of pages) {
|
|
9234
|
-
const content =
|
|
9088
|
+
const content = readFileSync13(file, "utf-8");
|
|
9235
9089
|
const importRe = /import\s*\{([^}]+)\}\s*from\s*['"]@\/components\/ui\/([^'"]+)['"]/g;
|
|
9236
9090
|
let m;
|
|
9237
9091
|
while ((m = importRe.exec(content)) !== null) {
|
|
@@ -9241,7 +9095,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
9241
9095
|
for (const name of names) neededExports.get(componentId).add(name);
|
|
9242
9096
|
}
|
|
9243
9097
|
}
|
|
9244
|
-
const configPath =
|
|
9098
|
+
const configPath = join11(projectRoot, "design-system.config.ts");
|
|
9245
9099
|
let config2 = null;
|
|
9246
9100
|
try {
|
|
9247
9101
|
const mgr = new DesignSystemManager8(configPath);
|
|
@@ -9250,7 +9104,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
9250
9104
|
}
|
|
9251
9105
|
const generator = new ComponentGenerator3(config2 || { components: [], pages: [], tokens: {} });
|
|
9252
9106
|
for (const [componentId, needed] of neededExports) {
|
|
9253
|
-
const componentFile =
|
|
9107
|
+
const componentFile = join11(uiDir, `${componentId}.tsx`);
|
|
9254
9108
|
const def = getShadcnComponent(componentId);
|
|
9255
9109
|
if (!existsSync19(componentFile)) {
|
|
9256
9110
|
if (!def) continue;
|
|
@@ -9264,7 +9118,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
9264
9118
|
}
|
|
9265
9119
|
continue;
|
|
9266
9120
|
}
|
|
9267
|
-
const content =
|
|
9121
|
+
const content = readFileSync13(componentFile, "utf-8");
|
|
9268
9122
|
const exportRe = /export\s+(?:const|function|class)\s+(\w+)|export\s*\{([^}]+)\}/g;
|
|
9269
9123
|
const existingExports = /* @__PURE__ */ new Set();
|
|
9270
9124
|
let em;
|
|
@@ -9287,7 +9141,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
9287
9141
|
}
|
|
9288
9142
|
}
|
|
9289
9143
|
async function backfillPageAnalysis(projectRoot) {
|
|
9290
|
-
const configPath =
|
|
9144
|
+
const configPath = join11(projectRoot, "design-system.config.ts");
|
|
9291
9145
|
if (!existsSync19(configPath)) return;
|
|
9292
9146
|
try {
|
|
9293
9147
|
const mgr = new DesignSystemManager8(configPath);
|
|
@@ -9299,14 +9153,14 @@ async function backfillPageAnalysis(projectRoot) {
|
|
|
9299
9153
|
const isAuth = route.includes("login") || route.includes("register") || route.includes("signup") || route.includes("sign-up");
|
|
9300
9154
|
let filePath;
|
|
9301
9155
|
if (route === "/") {
|
|
9302
|
-
filePath =
|
|
9156
|
+
filePath = join11(projectRoot, "app", "page.tsx");
|
|
9303
9157
|
} else if (isAuth) {
|
|
9304
|
-
filePath =
|
|
9158
|
+
filePath = join11(projectRoot, "app", "(auth)", route.slice(1), "page.tsx");
|
|
9305
9159
|
} else {
|
|
9306
|
-
filePath =
|
|
9160
|
+
filePath = join11(projectRoot, "app", route.slice(1), "page.tsx");
|
|
9307
9161
|
}
|
|
9308
9162
|
if (!existsSync19(filePath)) continue;
|
|
9309
|
-
const code =
|
|
9163
|
+
const code = readFileSync13(filePath, "utf-8");
|
|
9310
9164
|
if (code.length < 50) continue;
|
|
9311
9165
|
page.pageAnalysis = analyzePageCode(code);
|
|
9312
9166
|
changed = true;
|
|
@@ -9333,7 +9187,7 @@ async function autoInstallShadcnComponent(componentId, projectRoot) {
|
|
|
9333
9187
|
const def = getShadcnComponent(componentId);
|
|
9334
9188
|
if (!def) return false;
|
|
9335
9189
|
try {
|
|
9336
|
-
const configPath =
|
|
9190
|
+
const configPath = join11(projectRoot, "design-system.config.ts");
|
|
9337
9191
|
let config2 = null;
|
|
9338
9192
|
try {
|
|
9339
9193
|
const mgr = new DesignSystemManager8(configPath);
|
|
@@ -9342,10 +9196,10 @@ async function autoInstallShadcnComponent(componentId, projectRoot) {
|
|
|
9342
9196
|
}
|
|
9343
9197
|
const generator = new ComponentGenerator3(config2 || { components: [], pages: [], tokens: {} });
|
|
9344
9198
|
const code = await generator.generate(def);
|
|
9345
|
-
const uiDir =
|
|
9199
|
+
const uiDir = join11(projectRoot, "components", "ui");
|
|
9346
9200
|
const { mkdirSync: mkdirSync9 } = await import("fs");
|
|
9347
9201
|
mkdirSync9(uiDir, { recursive: true });
|
|
9348
|
-
writeFileSync10(
|
|
9202
|
+
writeFileSync10(join11(uiDir, `${componentId}.tsx`), code, "utf-8");
|
|
9349
9203
|
return true;
|
|
9350
9204
|
} catch {
|
|
9351
9205
|
return false;
|
|
@@ -9468,12 +9322,12 @@ async function openBrowser(url) {
|
|
|
9468
9322
|
}
|
|
9469
9323
|
}
|
|
9470
9324
|
function startDevServer(projectRoot) {
|
|
9471
|
-
const packageJsonPath =
|
|
9325
|
+
const packageJsonPath = resolve10(projectRoot, "package.json");
|
|
9472
9326
|
if (!existsSync19(packageJsonPath)) {
|
|
9473
9327
|
throw new Error('package.json not found. Run "coherent init" first.');
|
|
9474
9328
|
}
|
|
9475
|
-
const hasPnpm = existsSync19(
|
|
9476
|
-
const hasNpm = existsSync19(
|
|
9329
|
+
const hasPnpm = existsSync19(resolve10(projectRoot, "pnpm-lock.yaml"));
|
|
9330
|
+
const hasNpm = existsSync19(resolve10(projectRoot, "package-lock.json"));
|
|
9477
9331
|
const command = hasPnpm ? "pnpm" : hasNpm ? "npm" : "npx";
|
|
9478
9332
|
const args = hasPnpm ? ["dev", "--turbo"] : hasNpm ? ["run", "dev", "--", "--turbo"] : ["next", "dev", "--turbo"];
|
|
9479
9333
|
const child = spawn(command, args, {
|
|
@@ -9520,7 +9374,7 @@ async function previewCommand() {
|
|
|
9520
9374
|
if (needsGlobalsFix(projectRoot)) {
|
|
9521
9375
|
spinner.text = "Fixing globals.css...";
|
|
9522
9376
|
try {
|
|
9523
|
-
const dsm = new DesignSystemManager8(
|
|
9377
|
+
const dsm = new DesignSystemManager8(resolve10(projectRoot, "design-system.config.ts"));
|
|
9524
9378
|
await dsm.load();
|
|
9525
9379
|
const config2 = dsm.getConfig();
|
|
9526
9380
|
fixGlobalsCss(projectRoot, config2);
|
|
@@ -9559,8 +9413,8 @@ async function previewCommand() {
|
|
|
9559
9413
|
import chalk15 from "chalk";
|
|
9560
9414
|
import ora4 from "ora";
|
|
9561
9415
|
import { spawn as spawn2 } from "child_process";
|
|
9562
|
-
import { existsSync as existsSync20, rmSync as rmSync4, readdirSync as
|
|
9563
|
-
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";
|
|
9564
9418
|
import { readdir as readdir3, readFile as readFile5, writeFile as writeFile4, mkdir as mkdir4, copyFile as copyFile2 } from "fs/promises";
|
|
9565
9419
|
var COPY_EXCLUDE = /* @__PURE__ */ new Set([
|
|
9566
9420
|
"node_modules",
|
|
@@ -9582,8 +9436,8 @@ async function copyDir(src, dest) {
|
|
|
9582
9436
|
await mkdir4(dest, { recursive: true });
|
|
9583
9437
|
const entries = await readdir3(src, { withFileTypes: true });
|
|
9584
9438
|
for (const e of entries) {
|
|
9585
|
-
const srcPath =
|
|
9586
|
-
const destPath =
|
|
9439
|
+
const srcPath = join12(src, e.name);
|
|
9440
|
+
const destPath = join12(dest, e.name);
|
|
9587
9441
|
if (COPY_EXCLUDE.has(e.name)) continue;
|
|
9588
9442
|
if (e.isDirectory()) {
|
|
9589
9443
|
await copyDir(srcPath, destPath);
|
|
@@ -9594,16 +9448,16 @@ async function copyDir(src, dest) {
|
|
|
9594
9448
|
}
|
|
9595
9449
|
}
|
|
9596
9450
|
function checkProjectInitialized2(projectRoot) {
|
|
9597
|
-
return existsSync20(
|
|
9451
|
+
return existsSync20(resolve11(projectRoot, "design-system.config.ts")) && existsSync20(resolve11(projectRoot, "package.json"));
|
|
9598
9452
|
}
|
|
9599
9453
|
function getPackageManager2(projectRoot) {
|
|
9600
|
-
if (existsSync20(
|
|
9601
|
-
if (existsSync20(
|
|
9454
|
+
if (existsSync20(resolve11(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
9455
|
+
if (existsSync20(resolve11(projectRoot, "package-lock.json"))) return "npm";
|
|
9602
9456
|
return "npx";
|
|
9603
9457
|
}
|
|
9604
9458
|
async function patchNextConfigForExport(outRoot) {
|
|
9605
9459
|
for (const name of ["next.config.ts", "next.config.mjs", "next.config.js"]) {
|
|
9606
|
-
const p =
|
|
9460
|
+
const p = join12(outRoot, name);
|
|
9607
9461
|
if (!existsSync20(p)) continue;
|
|
9608
9462
|
let content = await readFile5(p, "utf-8");
|
|
9609
9463
|
if (content.includes("ignoreDuringBuilds")) return;
|
|
@@ -9619,9 +9473,9 @@ async function buildProduction(projectRoot) {
|
|
|
9619
9473
|
const pm = getPackageManager2(projectRoot);
|
|
9620
9474
|
const command = pm === "pnpm" ? "pnpm" : pm === "npm" ? "npm" : "npx";
|
|
9621
9475
|
const args = pm === "npx" ? ["next", "build"] : ["run", "build"];
|
|
9622
|
-
return new Promise((
|
|
9476
|
+
return new Promise((resolve16, reject) => {
|
|
9623
9477
|
const child = spawn2(command, args, { cwd: projectRoot, stdio: "inherit", shell: true });
|
|
9624
|
-
child.on("exit", (code) => code === 0 ?
|
|
9478
|
+
child.on("exit", (code) => code === 0 ? resolve16() : reject(new Error(`Build failed with exit code ${code}`)));
|
|
9625
9479
|
child.on("error", (error) => reject(new Error(`Failed to start build: ${error.message}`)));
|
|
9626
9480
|
});
|
|
9627
9481
|
}
|
|
@@ -9645,7 +9499,7 @@ EXPOSE 3000
|
|
|
9645
9499
|
\`\`\`
|
|
9646
9500
|
`;
|
|
9647
9501
|
async function ensureReadmeDeploySection(outRoot) {
|
|
9648
|
-
const readmePath =
|
|
9502
|
+
const readmePath = join12(outRoot, "README.md");
|
|
9649
9503
|
if (!existsSync20(readmePath)) return;
|
|
9650
9504
|
try {
|
|
9651
9505
|
let content = await readFile5(readmePath, "utf-8");
|
|
@@ -9665,22 +9519,22 @@ async function countPages(outRoot) {
|
|
|
9665
9519
|
return;
|
|
9666
9520
|
}
|
|
9667
9521
|
for (const e of entries) {
|
|
9668
|
-
const full =
|
|
9522
|
+
const full = join12(dir, e.name);
|
|
9669
9523
|
if (e.isFile() && e.name === "page.tsx") n++;
|
|
9670
9524
|
else if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "api") await walk(full);
|
|
9671
9525
|
}
|
|
9672
9526
|
}
|
|
9673
|
-
const appDir =
|
|
9527
|
+
const appDir = join12(outRoot, "app");
|
|
9674
9528
|
if (existsSync20(appDir)) await walk(appDir);
|
|
9675
9529
|
return n;
|
|
9676
9530
|
}
|
|
9677
9531
|
function countComponents(outRoot) {
|
|
9678
9532
|
let n = 0;
|
|
9679
9533
|
for (const sub of ["ui", "shared"]) {
|
|
9680
|
-
const dir =
|
|
9534
|
+
const dir = join12(outRoot, "components", sub);
|
|
9681
9535
|
if (!existsSync20(dir)) continue;
|
|
9682
9536
|
try {
|
|
9683
|
-
n +=
|
|
9537
|
+
n += readdirSync3(dir).filter((f) => f.endsWith(".tsx") || f.endsWith(".jsx")).length;
|
|
9684
9538
|
} catch {
|
|
9685
9539
|
}
|
|
9686
9540
|
}
|
|
@@ -9693,7 +9547,7 @@ async function collectImportedPackages2(dir, extensions) {
|
|
|
9693
9547
|
async function walk(d) {
|
|
9694
9548
|
const entries = await readdir3(d, { withFileTypes: true });
|
|
9695
9549
|
for (const e of entries) {
|
|
9696
|
-
const full =
|
|
9550
|
+
const full = join12(d, e.name);
|
|
9697
9551
|
if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules") {
|
|
9698
9552
|
await walk(full);
|
|
9699
9553
|
continue;
|
|
@@ -9716,7 +9570,7 @@ async function collectImportedPackages2(dir, extensions) {
|
|
|
9716
9570
|
return packages;
|
|
9717
9571
|
}
|
|
9718
9572
|
async function findMissingDepsInExport(outRoot) {
|
|
9719
|
-
const pkgPath =
|
|
9573
|
+
const pkgPath = join12(outRoot, "package.json");
|
|
9720
9574
|
if (!existsSync20(pkgPath)) return [];
|
|
9721
9575
|
let pkg;
|
|
9722
9576
|
try {
|
|
@@ -9725,7 +9579,7 @@ async function findMissingDepsInExport(outRoot) {
|
|
|
9725
9579
|
return [];
|
|
9726
9580
|
}
|
|
9727
9581
|
const inDeps = /* @__PURE__ */ new Set([...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})]);
|
|
9728
|
-
const codeDirs = [
|
|
9582
|
+
const codeDirs = [join12(outRoot, "app"), join12(outRoot, "components")];
|
|
9729
9583
|
const extensions = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx"]);
|
|
9730
9584
|
const imported = /* @__PURE__ */ new Set();
|
|
9731
9585
|
for (const dir of codeDirs) {
|
|
@@ -9737,25 +9591,33 @@ async function findMissingDepsInExport(outRoot) {
|
|
|
9737
9591
|
async function stripCoherentArtifacts(outputDir) {
|
|
9738
9592
|
const removed = [];
|
|
9739
9593
|
for (const p of ["app/design-system", "app/api/design-system"]) {
|
|
9740
|
-
const full =
|
|
9594
|
+
const full = join12(outputDir, p);
|
|
9741
9595
|
if (existsSync20(full)) {
|
|
9742
9596
|
rmSync4(full, { recursive: true, force: true });
|
|
9743
9597
|
removed.push(p);
|
|
9744
9598
|
}
|
|
9745
9599
|
}
|
|
9746
|
-
const appNavPath =
|
|
9600
|
+
const appNavPath = join12(outputDir, "app", "AppNav.tsx");
|
|
9747
9601
|
if (existsSync20(appNavPath)) {
|
|
9748
9602
|
rmSync4(appNavPath, { force: true });
|
|
9749
9603
|
removed.push("app/AppNav.tsx");
|
|
9750
9604
|
}
|
|
9751
|
-
const layoutPath =
|
|
9605
|
+
const layoutPath = join12(outputDir, "app", "layout.tsx");
|
|
9752
9606
|
if (existsSync20(layoutPath)) {
|
|
9753
9607
|
let layout = await readFile5(layoutPath, "utf-8");
|
|
9754
9608
|
layout = layout.replace(/import\s*\{?\s*AppNav\s*\}?\s*from\s*['"][^'"]+['"]\s*\n?/g, "");
|
|
9755
9609
|
layout = layout.replace(/\s*<AppNav\s*\/?\s*>\s*/g, "\n");
|
|
9756
9610
|
await writeFile4(layoutPath, layout, "utf-8");
|
|
9757
9611
|
}
|
|
9758
|
-
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");
|
|
9759
9621
|
if (existsSync20(guardPath)) {
|
|
9760
9622
|
let guard = await readFile5(guardPath, "utf-8");
|
|
9761
9623
|
guard = guard.replace(/['"],?\s*'\/design-system['"],?\s*/g, "");
|
|
@@ -9784,14 +9646,14 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
9784
9646
|
".env.local",
|
|
9785
9647
|
"recommendations.md"
|
|
9786
9648
|
]) {
|
|
9787
|
-
const full =
|
|
9649
|
+
const full = join12(outputDir, name);
|
|
9788
9650
|
if (existsSync20(full)) {
|
|
9789
9651
|
rmSync4(full, { force: true });
|
|
9790
9652
|
removed.push(name);
|
|
9791
9653
|
}
|
|
9792
9654
|
}
|
|
9793
9655
|
for (const dir of [".claude", ".coherent"]) {
|
|
9794
|
-
const full =
|
|
9656
|
+
const full = join12(outputDir, dir);
|
|
9795
9657
|
if (existsSync20(full)) {
|
|
9796
9658
|
rmSync4(full, { recursive: true, force: true });
|
|
9797
9659
|
removed.push(dir + "/");
|
|
@@ -9800,7 +9662,7 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
9800
9662
|
return removed;
|
|
9801
9663
|
}
|
|
9802
9664
|
async function exportCommand(options = {}) {
|
|
9803
|
-
const outputDir =
|
|
9665
|
+
const outputDir = resolve11(process.cwd(), options.output ?? "./export");
|
|
9804
9666
|
const doBuild = options.build !== false;
|
|
9805
9667
|
const keepDs = options.keepDs === true;
|
|
9806
9668
|
const spinner = ora4("Preparing export...").start();
|
|
@@ -9991,8 +9853,8 @@ async function regenerateDocsCommand() {
|
|
|
9991
9853
|
|
|
9992
9854
|
// src/commands/fix.ts
|
|
9993
9855
|
import chalk18 from "chalk";
|
|
9994
|
-
import { readdirSync as
|
|
9995
|
-
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";
|
|
9996
9858
|
import {
|
|
9997
9859
|
DesignSystemManager as DesignSystemManager11,
|
|
9998
9860
|
ComponentManager as ComponentManager5,
|
|
@@ -10016,9 +9878,9 @@ function extractComponentIdsFromCode2(code) {
|
|
|
10016
9878
|
function listTsxFiles(dir) {
|
|
10017
9879
|
const files = [];
|
|
10018
9880
|
try {
|
|
10019
|
-
const entries =
|
|
9881
|
+
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
10020
9882
|
for (const e of entries) {
|
|
10021
|
-
const full =
|
|
9883
|
+
const full = join13(dir, e.name);
|
|
10022
9884
|
if (e.isDirectory() && e.name !== "node_modules" && !e.name.startsWith(".")) {
|
|
10023
9885
|
files.push(...listTsxFiles(full));
|
|
10024
9886
|
} else if (e.isFile() && e.name.endsWith(".tsx")) {
|
|
@@ -10047,7 +9909,7 @@ async function fixCommand(opts = {}) {
|
|
|
10047
9909
|
console.log(chalk18.cyan("\ncoherent fix\n"));
|
|
10048
9910
|
}
|
|
10049
9911
|
if (!skipCache) {
|
|
10050
|
-
const nextDir =
|
|
9912
|
+
const nextDir = join13(projectRoot, ".next");
|
|
10051
9913
|
if (existsSync21(nextDir)) {
|
|
10052
9914
|
if (!dryRun) rmSync5(nextDir, { recursive: true, force: true });
|
|
10053
9915
|
fixes.push("Cleared build cache");
|
|
@@ -10070,12 +9932,12 @@ async function fixCommand(opts = {}) {
|
|
|
10070
9932
|
}
|
|
10071
9933
|
}
|
|
10072
9934
|
}
|
|
10073
|
-
const appDir =
|
|
9935
|
+
const appDir = resolve12(projectRoot, "app");
|
|
10074
9936
|
const allTsxFiles = listTsxFiles(appDir);
|
|
10075
|
-
const componentsTsxFiles = listTsxFiles(
|
|
9937
|
+
const componentsTsxFiles = listTsxFiles(resolve12(projectRoot, "components"));
|
|
10076
9938
|
const allComponentIds = /* @__PURE__ */ new Set();
|
|
10077
9939
|
for (const file of [...allTsxFiles, ...componentsTsxFiles]) {
|
|
10078
|
-
const content =
|
|
9940
|
+
const content = readFileSync14(file, "utf-8");
|
|
10079
9941
|
extractComponentIdsFromCode2(content).forEach((id) => allComponentIds.add(id));
|
|
10080
9942
|
}
|
|
10081
9943
|
let dsm = null;
|
|
@@ -10094,7 +9956,7 @@ async function fixCommand(opts = {}) {
|
|
|
10094
9956
|
missingComponents.push(id);
|
|
10095
9957
|
} else {
|
|
10096
9958
|
const fileName = toKebabCase(id) + ".tsx";
|
|
10097
|
-
const filePath =
|
|
9959
|
+
const filePath = resolve12(projectRoot, "components", "ui", fileName);
|
|
10098
9960
|
if (!existsSync21(filePath)) missingFiles.push(id);
|
|
10099
9961
|
}
|
|
10100
9962
|
}
|
|
@@ -10122,8 +9984,8 @@ async function fixCommand(opts = {}) {
|
|
|
10122
9984
|
const generator = new ComponentGenerator4(updatedConfig);
|
|
10123
9985
|
const code = await generator.generate(component);
|
|
10124
9986
|
const fileName = toKebabCase(component.name) + ".tsx";
|
|
10125
|
-
const filePath =
|
|
10126
|
-
mkdirSync7(
|
|
9987
|
+
const filePath = resolve12(projectRoot, "components", "ui", fileName);
|
|
9988
|
+
mkdirSync7(resolve12(projectRoot, "components", "ui"), { recursive: true });
|
|
10127
9989
|
await writeFile(filePath, code);
|
|
10128
9990
|
installed++;
|
|
10129
9991
|
}
|
|
@@ -10144,7 +10006,7 @@ async function fixCommand(opts = {}) {
|
|
|
10144
10006
|
const userTsxFiles = allTsxFiles.filter((f) => !f.includes("/design-system/"));
|
|
10145
10007
|
let syntaxFixed = 0;
|
|
10146
10008
|
for (const file of userTsxFiles) {
|
|
10147
|
-
const content =
|
|
10009
|
+
const content = readFileSync14(file, "utf-8");
|
|
10148
10010
|
const fixed = fixUnescapedLtInJsx(
|
|
10149
10011
|
fixEscapedClosingQuotes(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)))
|
|
10150
10012
|
);
|
|
@@ -10162,7 +10024,7 @@ async function fixCommand(opts = {}) {
|
|
|
10162
10024
|
let qualityFixCount = 0;
|
|
10163
10025
|
const qualityFixDetails = [];
|
|
10164
10026
|
for (const file of userTsxFiles) {
|
|
10165
|
-
const content =
|
|
10027
|
+
const content = readFileSync14(file, "utf-8");
|
|
10166
10028
|
const { code: autoFixed, fixes: fileFixes } = await autoFixCode(content);
|
|
10167
10029
|
if (autoFixed !== content) {
|
|
10168
10030
|
if (!dryRun) writeFileSync11(file, autoFixed, "utf-8");
|
|
@@ -10181,7 +10043,7 @@ async function fixCommand(opts = {}) {
|
|
|
10181
10043
|
let totalWarnings = 0;
|
|
10182
10044
|
const fileIssues = [];
|
|
10183
10045
|
for (const file of allTsxFiles) {
|
|
10184
|
-
const code = dryRun ?
|
|
10046
|
+
const code = dryRun ? readFileSync14(file, "utf-8") : readFileSync14(file, "utf-8");
|
|
10185
10047
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
10186
10048
|
const baseName = file.split("/").pop() || "";
|
|
10187
10049
|
const isAuthPage = relativePath.includes("(auth)");
|
|
@@ -10303,16 +10165,16 @@ async function fixCommand(opts = {}) {
|
|
|
10303
10165
|
|
|
10304
10166
|
// src/commands/check.ts
|
|
10305
10167
|
import chalk19 from "chalk";
|
|
10306
|
-
import { resolve as
|
|
10307
|
-
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";
|
|
10308
10170
|
import { loadManifest as loadManifest11 } from "@getcoherent/core";
|
|
10309
10171
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", "design-system"]);
|
|
10310
10172
|
function findTsxFiles(dir) {
|
|
10311
10173
|
const results = [];
|
|
10312
10174
|
try {
|
|
10313
|
-
const entries =
|
|
10175
|
+
const entries = readdirSync5(dir);
|
|
10314
10176
|
for (const entry of entries) {
|
|
10315
|
-
const full =
|
|
10177
|
+
const full = resolve13(dir, entry);
|
|
10316
10178
|
const stat = statSync2(full);
|
|
10317
10179
|
if (stat.isDirectory() && !entry.startsWith(".") && !EXCLUDED_DIRS.has(entry)) {
|
|
10318
10180
|
results.push(...findTsxFiles(full));
|
|
@@ -10347,7 +10209,7 @@ async function checkCommand(opts = {}) {
|
|
|
10347
10209
|
} catch {
|
|
10348
10210
|
}
|
|
10349
10211
|
if (!skipPages) {
|
|
10350
|
-
const appDir =
|
|
10212
|
+
const appDir = resolve13(projectRoot, "app");
|
|
10351
10213
|
const files = findTsxFiles(appDir);
|
|
10352
10214
|
result.pages.total = files.length;
|
|
10353
10215
|
if (!opts.json) console.log(chalk19.cyan("\n \u{1F4C4} Pages") + chalk19.dim(` (${files.length} scanned)
|
|
@@ -10361,7 +10223,7 @@ async function checkCommand(opts = {}) {
|
|
|
10361
10223
|
"NATIVE_TABLE"
|
|
10362
10224
|
]);
|
|
10363
10225
|
for (const file of files) {
|
|
10364
|
-
const code =
|
|
10226
|
+
const code = readFileSync15(file, "utf-8");
|
|
10365
10227
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
10366
10228
|
const baseName = file.split("/").pop() || "";
|
|
10367
10229
|
const isAuthPage = relativePath.includes("(auth)");
|
|
@@ -10403,7 +10265,7 @@ async function checkCommand(opts = {}) {
|
|
|
10403
10265
|
routeSet.add("/");
|
|
10404
10266
|
routeSet.add("#");
|
|
10405
10267
|
for (const file of files) {
|
|
10406
|
-
const code =
|
|
10268
|
+
const code = readFileSync15(file, "utf-8");
|
|
10407
10269
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
10408
10270
|
const lines = code.split("\n");
|
|
10409
10271
|
const linkHrefRe = /href\s*=\s*["'](\/[a-z0-9/-]*)["']/gi;
|
|
@@ -10435,7 +10297,7 @@ async function checkCommand(opts = {}) {
|
|
|
10435
10297
|
const manifest = await loadManifest11(project.root);
|
|
10436
10298
|
if (manifest.shared.length > 0) {
|
|
10437
10299
|
for (const entry of manifest.shared) {
|
|
10438
|
-
const fullPath =
|
|
10300
|
+
const fullPath = resolve13(project.root, entry.file);
|
|
10439
10301
|
if (!existsSync22(fullPath)) {
|
|
10440
10302
|
result.pages.withErrors++;
|
|
10441
10303
|
if (!opts.json) console.log(chalk19.red(`
|
|
@@ -10460,7 +10322,7 @@ async function checkCommand(opts = {}) {
|
|
|
10460
10322
|
let _staleUsedIn = 0;
|
|
10461
10323
|
let _nameMismatch = 0;
|
|
10462
10324
|
for (const entry of manifest.shared) {
|
|
10463
|
-
const filePath =
|
|
10325
|
+
const filePath = resolve13(projectRoot, entry.file);
|
|
10464
10326
|
const fileExists = existsSync22(filePath);
|
|
10465
10327
|
if (!fileExists) {
|
|
10466
10328
|
_orphaned++;
|
|
@@ -10471,7 +10333,7 @@ async function checkCommand(opts = {}) {
|
|
|
10471
10333
|
continue;
|
|
10472
10334
|
}
|
|
10473
10335
|
try {
|
|
10474
|
-
const code =
|
|
10336
|
+
const code = readFileSync15(filePath, "utf-8");
|
|
10475
10337
|
const actualExports = extractExportedComponentNames(code);
|
|
10476
10338
|
if (actualExports.length > 0 && !actualExports.includes(entry.name)) {
|
|
10477
10339
|
_nameMismatch++;
|
|
@@ -10539,7 +10401,7 @@ async function checkCommand(opts = {}) {
|
|
|
10539
10401
|
id: e.id,
|
|
10540
10402
|
name: e.name,
|
|
10541
10403
|
type: e.type,
|
|
10542
|
-
status: existsSync22(
|
|
10404
|
+
status: existsSync22(resolve13(projectRoot, e.file)) ? "ok" : "unused",
|
|
10543
10405
|
message: "",
|
|
10544
10406
|
suggestions: void 0
|
|
10545
10407
|
}))
|
|
@@ -10632,14 +10494,14 @@ import {
|
|
|
10632
10494
|
ComponentManager as ComponentManager6,
|
|
10633
10495
|
loadManifest as loadManifest12,
|
|
10634
10496
|
generateSharedComponent as generateSharedComponent4,
|
|
10635
|
-
integrateSharedLayoutIntoRootLayout as
|
|
10497
|
+
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout3
|
|
10636
10498
|
} from "@getcoherent/core";
|
|
10637
10499
|
import { existsSync as existsSync23 } from "fs";
|
|
10638
|
-
import { resolve as
|
|
10500
|
+
import { resolve as resolve14 } from "path";
|
|
10639
10501
|
|
|
10640
10502
|
// src/utils/ds-files.ts
|
|
10641
10503
|
import { mkdir as mkdir5, writeFile as writeFile5 } from "fs/promises";
|
|
10642
|
-
import { join as
|
|
10504
|
+
import { join as join14, dirname as dirname8 } from "path";
|
|
10643
10505
|
import { DesignSystemGenerator } from "@getcoherent/core";
|
|
10644
10506
|
var SHARED_DS_KEYS = [
|
|
10645
10507
|
"app/design-system/shared/page.tsx",
|
|
@@ -10653,7 +10515,7 @@ async function writeDesignSystemFiles(projectRoot, config2, options) {
|
|
|
10653
10515
|
const toWrite = options?.sharedOnly ? new Map([...files].filter(([path3]) => SHARED_DS_KEYS.includes(path3))) : files;
|
|
10654
10516
|
const written = [];
|
|
10655
10517
|
for (const [relativePath, content] of toWrite) {
|
|
10656
|
-
const fullPath =
|
|
10518
|
+
const fullPath = join14(projectRoot, relativePath);
|
|
10657
10519
|
await mkdir5(dirname8(fullPath), { recursive: true });
|
|
10658
10520
|
await writeFile5(fullPath, content, "utf-8");
|
|
10659
10521
|
written.push(relativePath);
|
|
@@ -10762,10 +10624,10 @@ function createComponentsCommand() {
|
|
|
10762
10624
|
\u2705 Created ${result.id} (${result.name}) at ${result.file}
|
|
10763
10625
|
`));
|
|
10764
10626
|
if (type === "layout") {
|
|
10765
|
-
const updated = await
|
|
10627
|
+
const updated = await integrateSharedLayoutIntoRootLayout3(project.root);
|
|
10766
10628
|
if (updated) console.log(chalk25.cyan(" Updated app/layout.tsx to use shared layout components.\n"));
|
|
10767
10629
|
}
|
|
10768
|
-
const sharedPagePath =
|
|
10630
|
+
const sharedPagePath = resolve14(project.root, "app/design-system/shared/page.tsx");
|
|
10769
10631
|
if (!existsSync23(sharedPagePath)) {
|
|
10770
10632
|
try {
|
|
10771
10633
|
const dsm = new DesignSystemManager12(project.configPath);
|
|
@@ -10792,7 +10654,7 @@ function createComponentsCommand() {
|
|
|
10792
10654
|
import chalk26 from "chalk";
|
|
10793
10655
|
import ora6 from "ora";
|
|
10794
10656
|
import { writeFile as writeFile6, mkdir as mkdir6 } from "fs/promises";
|
|
10795
|
-
import { resolve as
|
|
10657
|
+
import { resolve as resolve15, join as join15, dirname as dirname9 } from "path";
|
|
10796
10658
|
import { existsSync as existsSync24 } from "fs";
|
|
10797
10659
|
import {
|
|
10798
10660
|
FigmaClient,
|
|
@@ -10805,7 +10667,7 @@ import {
|
|
|
10805
10667
|
setSharedMapping,
|
|
10806
10668
|
generateSharedComponent as generateSharedComponent5,
|
|
10807
10669
|
generatePagesFromFigma,
|
|
10808
|
-
integrateSharedLayoutIntoRootLayout as
|
|
10670
|
+
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout4,
|
|
10809
10671
|
DesignSystemManager as DesignSystemManager13,
|
|
10810
10672
|
validateConfig,
|
|
10811
10673
|
FRAMEWORK_VERSIONS as FRAMEWORK_VERSIONS2,
|
|
@@ -10940,7 +10802,7 @@ async function importFigmaAction(urlOrKey, opts) {
|
|
|
10940
10802
|
stats.filesWritten.push(filePath);
|
|
10941
10803
|
return;
|
|
10942
10804
|
}
|
|
10943
|
-
const fullPath =
|
|
10805
|
+
const fullPath = join15(projectRoot, filePath);
|
|
10944
10806
|
await mkdir6(dirname9(fullPath), { recursive: true });
|
|
10945
10807
|
await writeFile6(fullPath, content, "utf-8");
|
|
10946
10808
|
stats.filesWritten.push(filePath);
|
|
@@ -11017,7 +10879,7 @@ async function importFigmaAction(urlOrKey, opts) {
|
|
|
11017
10879
|
if (dryRun) stats.filesWritten.push(FIGMA_COMPONENT_MAP_FILENAME);
|
|
11018
10880
|
else
|
|
11019
10881
|
await writeFile6(
|
|
11020
|
-
|
|
10882
|
+
resolve15(projectRoot, FIGMA_COMPONENT_MAP_FILENAME),
|
|
11021
10883
|
JSON.stringify(componentMapObj, null, 2),
|
|
11022
10884
|
"utf-8"
|
|
11023
10885
|
);
|
|
@@ -11040,7 +10902,7 @@ async function importFigmaAction(urlOrKey, opts) {
|
|
|
11040
10902
|
const fullConfig = buildFigmaImportConfig(mergedConfig, pageDefs, intermediate.fileName);
|
|
11041
10903
|
if (!dryRun) {
|
|
11042
10904
|
spinner.start("Updating design-system.config.ts...");
|
|
11043
|
-
const configPath =
|
|
10905
|
+
const configPath = resolve15(projectRoot, DESIGN_SYSTEM_CONFIG_PATH);
|
|
11044
10906
|
const dsm = new DesignSystemManager13(configPath);
|
|
11045
10907
|
if (existsSync24(configPath)) {
|
|
11046
10908
|
await dsm.load();
|
|
@@ -11071,7 +10933,7 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
|
|
|
11071
10933
|
stats.configUpdated = true;
|
|
11072
10934
|
spinner.succeed("design-system.config.ts updated");
|
|
11073
10935
|
spinner.start("Ensuring root layout...");
|
|
11074
|
-
const layoutPath =
|
|
10936
|
+
const layoutPath = join15(projectRoot, "app/layout.tsx");
|
|
11075
10937
|
if (!existsSync24(layoutPath)) {
|
|
11076
10938
|
await mkdir6(dirname9(layoutPath), { recursive: true });
|
|
11077
10939
|
await writeFile6(layoutPath, MINIMAL_ROOT_LAYOUT, "utf-8");
|
|
@@ -11079,7 +10941,7 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
|
|
|
11079
10941
|
}
|
|
11080
10942
|
spinner.succeed("Root layout OK");
|
|
11081
10943
|
spinner.start("Integrating shared layout (Header/Footer)...");
|
|
11082
|
-
const layoutIntegrated = await
|
|
10944
|
+
const layoutIntegrated = await integrateSharedLayoutIntoRootLayout4(projectRoot);
|
|
11083
10945
|
stats.layoutIntegrated = layoutIntegrated;
|
|
11084
10946
|
spinner.succeed(layoutIntegrated ? "Layout components wired" : "No layout components to wire");
|
|
11085
10947
|
spinner.start("Generating Design System viewer...");
|
|
@@ -11162,8 +11024,8 @@ async function dsRegenerateCommand() {
|
|
|
11162
11024
|
// src/commands/update.ts
|
|
11163
11025
|
import chalk28 from "chalk";
|
|
11164
11026
|
import ora8 from "ora";
|
|
11165
|
-
import { readFileSync as
|
|
11166
|
-
import { join as
|
|
11027
|
+
import { readFileSync as readFileSync16, existsSync as existsSync25 } from "fs";
|
|
11028
|
+
import { join as join16 } from "path";
|
|
11167
11029
|
import { DesignSystemManager as DesignSystemManager15, CLI_VERSION as CLI_VERSION4 } from "@getcoherent/core";
|
|
11168
11030
|
|
|
11169
11031
|
// src/utils/migrations.ts
|
|
@@ -11332,20 +11194,20 @@ var EXPECTED_CSS_VARS = [
|
|
|
11332
11194
|
"--sidebar-ring"
|
|
11333
11195
|
];
|
|
11334
11196
|
function checkMissingCssVars(projectRoot) {
|
|
11335
|
-
const globalsPath =
|
|
11197
|
+
const globalsPath = join16(projectRoot, "app", "globals.css");
|
|
11336
11198
|
if (!existsSync25(globalsPath)) return [];
|
|
11337
11199
|
try {
|
|
11338
|
-
const content =
|
|
11200
|
+
const content = readFileSync16(globalsPath, "utf-8");
|
|
11339
11201
|
return EXPECTED_CSS_VARS.filter((v) => !content.includes(v));
|
|
11340
11202
|
} catch {
|
|
11341
11203
|
return [];
|
|
11342
11204
|
}
|
|
11343
11205
|
}
|
|
11344
11206
|
function patchGlobalsCss(projectRoot, missingVars) {
|
|
11345
|
-
const globalsPath =
|
|
11207
|
+
const globalsPath = join16(projectRoot, "app", "globals.css");
|
|
11346
11208
|
if (!existsSync25(globalsPath) || missingVars.length === 0) return;
|
|
11347
11209
|
const { writeFileSync: writeFileSync13 } = __require("fs");
|
|
11348
|
-
let content =
|
|
11210
|
+
let content = readFileSync16(globalsPath, "utf-8");
|
|
11349
11211
|
const defaultValues = {
|
|
11350
11212
|
"--chart-1": "220 70% 50%",
|
|
11351
11213
|
"--chart-2": "160 60% 45%",
|
|
@@ -11423,26 +11285,26 @@ async function undoCommand(options) {
|
|
|
11423
11285
|
// src/commands/sync.ts
|
|
11424
11286
|
import chalk30 from "chalk";
|
|
11425
11287
|
import ora9 from "ora";
|
|
11426
|
-
import { existsSync as existsSync26, readFileSync as
|
|
11427
|
-
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";
|
|
11428
11290
|
import { readdir as readdir4, readFile as readFile6 } from "fs/promises";
|
|
11429
11291
|
import { DesignSystemManager as DesignSystemManager16 } from "@getcoherent/core";
|
|
11430
11292
|
import { loadManifest as loadManifest13, saveManifest as saveManifest5, findSharedComponent } from "@getcoherent/core";
|
|
11431
11293
|
function extractTokensFromProject(projectRoot) {
|
|
11432
11294
|
const lightColors = {};
|
|
11433
11295
|
const darkColors = {};
|
|
11434
|
-
const globalsPath =
|
|
11296
|
+
const globalsPath = join17(projectRoot, "app", "globals.css");
|
|
11435
11297
|
if (existsSync26(globalsPath)) {
|
|
11436
|
-
const css =
|
|
11298
|
+
const css = readFileSync17(globalsPath, "utf-8");
|
|
11437
11299
|
const rootMatch = css.match(/:root\s*\{([^}]+)\}/s);
|
|
11438
11300
|
if (rootMatch) parseVarsInto(rootMatch[1], lightColors);
|
|
11439
11301
|
const darkMatch = css.match(/\.dark\s*\{([^}]+)\}/s);
|
|
11440
11302
|
if (darkMatch) parseVarsInto(darkMatch[1], darkColors);
|
|
11441
11303
|
}
|
|
11442
|
-
const layoutPath =
|
|
11304
|
+
const layoutPath = join17(projectRoot, "app", "layout.tsx");
|
|
11443
11305
|
let layoutCode = "";
|
|
11444
11306
|
if (existsSync26(layoutPath)) {
|
|
11445
|
-
layoutCode =
|
|
11307
|
+
layoutCode = readFileSync17(layoutPath, "utf-8");
|
|
11446
11308
|
const rootInline = layoutCode.match(/:root\s*\{([^}]+)\}/s);
|
|
11447
11309
|
if (rootInline && Object.keys(lightColors).length === 0) {
|
|
11448
11310
|
parseVarsInto(rootInline[1], lightColors);
|
|
@@ -11460,7 +11322,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
11460
11322
|
defaultMode = "dark";
|
|
11461
11323
|
}
|
|
11462
11324
|
let radius;
|
|
11463
|
-
const allCss = [existsSync26(globalsPath) ?
|
|
11325
|
+
const allCss = [existsSync26(globalsPath) ? readFileSync17(globalsPath, "utf-8") : "", layoutCode].join("\n");
|
|
11464
11326
|
const radiusMatch = allCss.match(/--radius:\s*([^;]+);/);
|
|
11465
11327
|
if (radiusMatch) radius = radiusMatch[1].trim();
|
|
11466
11328
|
return {
|
|
@@ -11483,7 +11345,7 @@ function parseVarsInto(block, target) {
|
|
|
11483
11345
|
}
|
|
11484
11346
|
async function detectCustomComponents(projectRoot, allPageCode) {
|
|
11485
11347
|
const results = [];
|
|
11486
|
-
const componentsDir =
|
|
11348
|
+
const componentsDir = join17(projectRoot, "components");
|
|
11487
11349
|
if (!existsSync26(componentsDir)) return results;
|
|
11488
11350
|
const files = [];
|
|
11489
11351
|
await walkForTsx(componentsDir, files, ["ui"]);
|
|
@@ -11511,7 +11373,7 @@ async function walkForTsx(dir, files, skipDirs) {
|
|
|
11511
11373
|
return;
|
|
11512
11374
|
}
|
|
11513
11375
|
for (const e of entries) {
|
|
11514
|
-
const full =
|
|
11376
|
+
const full = join17(dir, e.name);
|
|
11515
11377
|
if (e.isDirectory()) {
|
|
11516
11378
|
if (skipDirs.includes(e.name) || e.name.startsWith(".")) continue;
|
|
11517
11379
|
await walkForTsx(full, files, skipDirs);
|
|
@@ -11585,7 +11447,7 @@ async function discoverPages(appDir) {
|
|
|
11585
11447
|
return;
|
|
11586
11448
|
}
|
|
11587
11449
|
for (const entry of entries) {
|
|
11588
|
-
const full =
|
|
11450
|
+
const full = join17(dir, entry.name);
|
|
11589
11451
|
if (entry.isDirectory()) {
|
|
11590
11452
|
if (["design-system", "api", "_not-found"].includes(entry.name)) continue;
|
|
11591
11453
|
if (entry.name.startsWith(".")) continue;
|
|
@@ -11662,7 +11524,7 @@ async function syncCommand(options = {}) {
|
|
|
11662
11524
|
if (dryRun) console.log(chalk30.yellow(" [dry-run] No files will be written\n"));
|
|
11663
11525
|
const spinner = ora9("Scanning project files...").start();
|
|
11664
11526
|
try {
|
|
11665
|
-
const appDir =
|
|
11527
|
+
const appDir = join17(project.root, "app");
|
|
11666
11528
|
if (!existsSync26(appDir)) {
|
|
11667
11529
|
spinner.fail("No app/ directory found");
|
|
11668
11530
|
process.exit(1);
|
|
@@ -11888,20 +11750,20 @@ async function syncCommand(options = {}) {
|
|
|
11888
11750
|
}
|
|
11889
11751
|
|
|
11890
11752
|
// src/utils/update-notifier.ts
|
|
11891
|
-
import { existsSync as existsSync27, mkdirSync as mkdirSync8, readFileSync as
|
|
11892
|
-
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";
|
|
11893
11755
|
import { homedir } from "os";
|
|
11894
11756
|
import chalk31 from "chalk";
|
|
11895
11757
|
import { CLI_VERSION as CLI_VERSION5 } from "@getcoherent/core";
|
|
11896
11758
|
var DEBUG5 = process.env.COHERENT_DEBUG === "1";
|
|
11897
11759
|
var PACKAGE_NAME = "@getcoherent/cli";
|
|
11898
|
-
var CACHE_DIR =
|
|
11899
|
-
var CACHE_FILE =
|
|
11760
|
+
var CACHE_DIR = join18(homedir(), ".coherent");
|
|
11761
|
+
var CACHE_FILE = join18(CACHE_DIR, "update-check.json");
|
|
11900
11762
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
11901
11763
|
function readCache() {
|
|
11902
11764
|
try {
|
|
11903
11765
|
if (!existsSync27(CACHE_FILE)) return null;
|
|
11904
|
-
const raw =
|
|
11766
|
+
const raw = readFileSync18(CACHE_FILE, "utf-8");
|
|
11905
11767
|
return JSON.parse(raw);
|
|
11906
11768
|
} catch (e) {
|
|
11907
11769
|
if (DEBUG5) console.error("Failed to read update cache:", e);
|