@getcoherent/cli 0.6.10 → 0.6.12
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
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CORE_CONSTRAINTS,
|
|
3
3
|
DESIGN_QUALITY,
|
|
4
|
+
DESIGN_QUALITY_COMMON,
|
|
4
5
|
DESIGN_THINKING,
|
|
5
6
|
INTERACTION_PATTERNS,
|
|
6
7
|
VISUAL_DEPTH,
|
|
@@ -13,7 +14,7 @@ import {
|
|
|
13
14
|
routeToKey,
|
|
14
15
|
savePlan,
|
|
15
16
|
selectContextualRules
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-CLPILU3Z.js";
|
|
17
18
|
import {
|
|
18
19
|
__require
|
|
19
20
|
} from "./chunk-3RG5ZIWI.js";
|
|
@@ -1369,10 +1370,10 @@ var ShadcnProvider = class {
|
|
|
1369
1370
|
const componentPath = path.join(projectRoot, "components", "ui", `${name}.tsx`);
|
|
1370
1371
|
if (!force && deps.existsSync(componentPath)) return;
|
|
1371
1372
|
try {
|
|
1372
|
-
await new Promise((
|
|
1373
|
+
await new Promise((resolve17, reject) => {
|
|
1373
1374
|
deps.exec(`npx shadcn@latest add ${name} --yes --overwrite`, { cwd: projectRoot, timeout: 15e3 }, (err) => {
|
|
1374
1375
|
if (err) reject(err);
|
|
1375
|
-
else
|
|
1376
|
+
else resolve17();
|
|
1376
1377
|
});
|
|
1377
1378
|
});
|
|
1378
1379
|
} catch {
|
|
@@ -1413,13 +1414,13 @@ var ShadcnProvider = class {
|
|
|
1413
1414
|
}
|
|
1414
1415
|
if (toInstall.length === 0) return results;
|
|
1415
1416
|
try {
|
|
1416
|
-
await new Promise((
|
|
1417
|
+
await new Promise((resolve17, reject) => {
|
|
1417
1418
|
deps.exec(
|
|
1418
1419
|
`npx shadcn@latest add ${toInstall.join(" ")} --yes --overwrite`,
|
|
1419
1420
|
{ cwd: projectRoot, timeout: 3e4 },
|
|
1420
1421
|
(err) => {
|
|
1421
1422
|
if (err) reject(err);
|
|
1422
|
-
else
|
|
1423
|
+
else resolve17();
|
|
1423
1424
|
}
|
|
1424
1425
|
);
|
|
1425
1426
|
});
|
|
@@ -1931,7 +1932,7 @@ function installPackages(projectRoot, packages) {
|
|
|
1931
1932
|
if (packages.length === 0) return Promise.resolve(true);
|
|
1932
1933
|
const safe = packages.filter((p) => SAFE_PKG_NAME.test(p));
|
|
1933
1934
|
if (safe.length === 0) return Promise.resolve(true);
|
|
1934
|
-
return new Promise((
|
|
1935
|
+
return new Promise((resolve17) => {
|
|
1935
1936
|
try {
|
|
1936
1937
|
const hasPnpm = existsSync4(join3(projectRoot, "pnpm-lock.yaml"));
|
|
1937
1938
|
if (hasPnpm) {
|
|
@@ -1942,10 +1943,10 @@ function installPackages(projectRoot, packages) {
|
|
|
1942
1943
|
stdio: "pipe"
|
|
1943
1944
|
});
|
|
1944
1945
|
}
|
|
1945
|
-
|
|
1946
|
+
resolve17(true);
|
|
1946
1947
|
} catch (e) {
|
|
1947
1948
|
if (process.env.COHERENT_DEBUG === "1") console.error("Failed to install packages:", e);
|
|
1948
|
-
|
|
1949
|
+
resolve17(false);
|
|
1949
1950
|
}
|
|
1950
1951
|
});
|
|
1951
1952
|
}
|
|
@@ -3372,6 +3373,7 @@ function generateV4GlobalsCss(config2) {
|
|
|
3372
3373
|
--color-accent-foreground: var(--accent-foreground);
|
|
3373
3374
|
--color-destructive: var(--destructive);
|
|
3374
3375
|
--color-destructive-foreground: var(--destructive-foreground);
|
|
3376
|
+
--color-transparent: transparent;
|
|
3375
3377
|
--color-border: var(--border);
|
|
3376
3378
|
--color-input: var(--input);
|
|
3377
3379
|
--color-ring: var(--ring);
|
|
@@ -3383,6 +3385,24 @@ function generateV4GlobalsCss(config2) {
|
|
|
3383
3385
|
--color-warning: var(--warning);
|
|
3384
3386
|
--color-error: var(--error);
|
|
3385
3387
|
--color-info: var(--info);
|
|
3388
|
+
--color-black: #000;
|
|
3389
|
+
--color-white: #fff;
|
|
3390
|
+
--color-sidebar-background: var(--sidebar-background);
|
|
3391
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
3392
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
3393
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
3394
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
3395
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
3396
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
3397
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
3398
|
+
--color-sidebar-muted: var(--sidebar-muted);
|
|
3399
|
+
--color-sidebar-muted-foreground: var(--sidebar-muted-foreground);
|
|
3400
|
+
--color-chart-1: var(--chart-1);
|
|
3401
|
+
--color-chart-2: var(--chart-2);
|
|
3402
|
+
--color-chart-3: var(--chart-3);
|
|
3403
|
+
--color-chart-4: var(--chart-4);
|
|
3404
|
+
--color-chart-5: var(--chart-5);
|
|
3405
|
+
--radius-xs: 0.125rem;
|
|
3386
3406
|
--radius-sm: ${radius.sm || "0.25rem"};
|
|
3387
3407
|
--radius-md: ${radius.md || "0.5rem"};
|
|
3388
3408
|
--radius-lg: ${radius.lg || "0.75rem"};
|
|
@@ -3414,6 +3434,21 @@ function generateV4GlobalsCss(config2) {
|
|
|
3414
3434
|
--warning: ${light.warning};
|
|
3415
3435
|
--error: ${light.error};
|
|
3416
3436
|
--info: ${light.info || light.primary};
|
|
3437
|
+
--sidebar-background: ${light.background};
|
|
3438
|
+
--sidebar-foreground: ${light.foreground};
|
|
3439
|
+
--sidebar-primary: ${light.primary};
|
|
3440
|
+
--sidebar-primary-foreground: ${contrastFg(light.primary)};
|
|
3441
|
+
--sidebar-accent: ${light.accent || light.muted};
|
|
3442
|
+
--sidebar-accent-foreground: ${light.foreground};
|
|
3443
|
+
--sidebar-border: ${light.border};
|
|
3444
|
+
--sidebar-ring: ${light.primary};
|
|
3445
|
+
--sidebar-muted: ${light.muted};
|
|
3446
|
+
--sidebar-muted-foreground: ${blendColors(light.foreground, light.background, 0.45)};
|
|
3447
|
+
--chart-1: ${light.primary};
|
|
3448
|
+
--chart-2: ${light.success};
|
|
3449
|
+
--chart-3: ${light.warning};
|
|
3450
|
+
--chart-4: ${light.error};
|
|
3451
|
+
--chart-5: ${light.info || light.primary};
|
|
3417
3452
|
}
|
|
3418
3453
|
|
|
3419
3454
|
.dark {
|
|
@@ -3440,6 +3475,21 @@ function generateV4GlobalsCss(config2) {
|
|
|
3440
3475
|
--warning: ${dark.warning};
|
|
3441
3476
|
--error: ${dark.error};
|
|
3442
3477
|
--info: ${dark.info || dark.primary};
|
|
3478
|
+
--sidebar-background: ${dark.background};
|
|
3479
|
+
--sidebar-foreground: ${dark.foreground};
|
|
3480
|
+
--sidebar-primary: ${dark.primary};
|
|
3481
|
+
--sidebar-primary-foreground: ${contrastFg(dark.primary)};
|
|
3482
|
+
--sidebar-accent: ${dark.accent || dark.muted};
|
|
3483
|
+
--sidebar-accent-foreground: ${dark.foreground};
|
|
3484
|
+
--sidebar-border: ${dark.border};
|
|
3485
|
+
--sidebar-ring: ${dark.primary};
|
|
3486
|
+
--sidebar-muted: ${dark.muted};
|
|
3487
|
+
--sidebar-muted-foreground: ${blendColors(dark.foreground, dark.background, 0.45)};
|
|
3488
|
+
--chart-1: ${dark.primary};
|
|
3489
|
+
--chart-2: ${dark.success};
|
|
3490
|
+
--chart-3: ${dark.warning};
|
|
3491
|
+
--chart-4: ${dark.error};
|
|
3492
|
+
--chart-5: ${dark.info || dark.primary};
|
|
3443
3493
|
}
|
|
3444
3494
|
|
|
3445
3495
|
* {
|
|
@@ -3763,8 +3813,8 @@ async function createAppRouteGroupLayout(projectPath) {
|
|
|
3763
3813
|
// src/commands/chat.ts
|
|
3764
3814
|
import chalk14 from "chalk";
|
|
3765
3815
|
import ora2 from "ora";
|
|
3766
|
-
import { resolve as
|
|
3767
|
-
import { existsSync as
|
|
3816
|
+
import { resolve as resolve10, relative as relative2, join as join11 } from "path";
|
|
3817
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12, mkdirSync as mkdirSync6, readdirSync as readdirSync4 } from "fs";
|
|
3768
3818
|
import {
|
|
3769
3819
|
DesignSystemManager as DesignSystemManager7,
|
|
3770
3820
|
ComponentManager as ComponentManager5,
|
|
@@ -4919,6 +4969,17 @@ function needsGlobalsFix(projectRoot) {
|
|
|
4919
4969
|
if (isTailwindV4(projectRoot)) {
|
|
4920
4970
|
if (!content.includes("@theme inline")) return true;
|
|
4921
4971
|
if (content.includes("@tailwind base")) return true;
|
|
4972
|
+
const REQUIRED_V4_TOKENS = [
|
|
4973
|
+
"--color-transparent",
|
|
4974
|
+
"--color-sidebar-background",
|
|
4975
|
+
"--color-chart-1",
|
|
4976
|
+
"--color-black",
|
|
4977
|
+
"--color-white",
|
|
4978
|
+
"--radius-xs"
|
|
4979
|
+
];
|
|
4980
|
+
for (const token of REQUIRED_V4_TOKENS) {
|
|
4981
|
+
if (!content.includes(token)) return true;
|
|
4982
|
+
}
|
|
4922
4983
|
return false;
|
|
4923
4984
|
}
|
|
4924
4985
|
if (content.includes(":root {") || content.includes(".dark {")) return true;
|
|
@@ -6077,19 +6138,21 @@ ${selectImport}`
|
|
|
6077
6138
|
/(<TabsTrigger\b[^>]*className=")([^"]*)(")/g,
|
|
6078
6139
|
(_m, pre, classes, post) => {
|
|
6079
6140
|
const cleaned = classes.replace(/\b(border-input|border\b|outline\b)\s*/g, "").trim();
|
|
6080
|
-
|
|
6081
|
-
if (withBorder0 !== classes.trim()) return `${pre}${withBorder0}${post}`;
|
|
6141
|
+
if (cleaned !== classes.trim()) return `${pre}${cleaned}${post}`;
|
|
6082
6142
|
return _m;
|
|
6083
6143
|
}
|
|
6084
6144
|
);
|
|
6085
|
-
fixed = fixed.replace(/<TabsTrigger\b(?![^>]*className=)(?![^>]*border-0)/g, '<TabsTrigger className="border-0"');
|
|
6086
6145
|
if (fixed !== beforeTabsFix) {
|
|
6087
|
-
fixes.push("
|
|
6146
|
+
fixes.push("stripped border from TabsTrigger (shadcn handles active state)");
|
|
6088
6147
|
}
|
|
6089
|
-
const
|
|
6090
|
-
fixed = fixed.replace(
|
|
6091
|
-
|
|
6092
|
-
|
|
6148
|
+
const beforeJunkFix = fixed;
|
|
6149
|
+
fixed = fixed.replace(/className="([^"]*)"/g, (_match, classes) => {
|
|
6150
|
+
const cleaned = classes.split(/\s+/).filter((c) => c !== "-0").join(" ");
|
|
6151
|
+
if (cleaned !== classes.trim()) return `className="${cleaned}"`;
|
|
6152
|
+
return _match;
|
|
6153
|
+
});
|
|
6154
|
+
if (fixed !== beforeJunkFix) {
|
|
6155
|
+
fixes.push("removed junk classes (-0)");
|
|
6093
6156
|
}
|
|
6094
6157
|
fixed = fixed.replace(/className="([^"]*)"/g, (_match, inner) => {
|
|
6095
6158
|
const cleaned = inner.replace(/\s{2,}/g, " ").trim();
|
|
@@ -6810,6 +6873,8 @@ function applyDefaults(request) {
|
|
|
6810
6873
|
}
|
|
6811
6874
|
|
|
6812
6875
|
// src/commands/chat/split-generator.ts
|
|
6876
|
+
import { existsSync as existsSync14, readFileSync as readFileSync9, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
6877
|
+
import { resolve as resolve6 } from "path";
|
|
6813
6878
|
import { z } from "zod";
|
|
6814
6879
|
import {
|
|
6815
6880
|
loadManifest as loadManifest5,
|
|
@@ -7041,9 +7106,55 @@ ${groupLines.join("\n")}`];
|
|
|
7041
7106
|
if (compLines.length > 0) {
|
|
7042
7107
|
parts.push(`Shared Components:
|
|
7043
7108
|
${compLines.join("\n")}`);
|
|
7109
|
+
}
|
|
7110
|
+
const noteEntries = Object.entries(plan.pageNotes || {}).filter(
|
|
7111
|
+
([, note]) => note.sections && note.sections.length > 0
|
|
7112
|
+
);
|
|
7113
|
+
if (noteEntries.length > 0) {
|
|
7114
|
+
const noteLines = noteEntries.map(([key, note]) => ` ${key}: ${note.sections.join(", ")}`);
|
|
7115
|
+
parts.push(`Page Sections:
|
|
7116
|
+
${noteLines.join("\n")}`);
|
|
7044
7117
|
}
|
|
7045
7118
|
return parts.join("\n");
|
|
7046
7119
|
}
|
|
7120
|
+
function readExistingAppPageForReference(projectRoot, plan) {
|
|
7121
|
+
if (!projectRoot) return null;
|
|
7122
|
+
if (plan?.pageNotes) {
|
|
7123
|
+
for (const [key, note] of Object.entries(plan.pageNotes)) {
|
|
7124
|
+
if (note.type !== "app") continue;
|
|
7125
|
+
for (const group of ["(app)", "(admin)", "(dashboard)"]) {
|
|
7126
|
+
const filePath = resolve6(projectRoot, "app", group, key, "page.tsx");
|
|
7127
|
+
if (existsSync14(filePath)) {
|
|
7128
|
+
const code = readFileSync9(filePath, "utf-8");
|
|
7129
|
+
const lines = code.split("\n");
|
|
7130
|
+
return lines.slice(0, 200).join("\n");
|
|
7131
|
+
}
|
|
7132
|
+
}
|
|
7133
|
+
}
|
|
7134
|
+
}
|
|
7135
|
+
const appDir = resolve6(projectRoot, "app");
|
|
7136
|
+
if (!existsSync14(appDir)) return null;
|
|
7137
|
+
try {
|
|
7138
|
+
const entries = readdirSync2(appDir);
|
|
7139
|
+
for (const entry of entries) {
|
|
7140
|
+
if (!entry.startsWith("(") || entry === "(auth)") continue;
|
|
7141
|
+
const groupDir = resolve6(appDir, entry);
|
|
7142
|
+
if (!statSync2(groupDir).isDirectory()) continue;
|
|
7143
|
+
const subDirs = readdirSync2(groupDir);
|
|
7144
|
+
for (const sub of subDirs) {
|
|
7145
|
+
const pagePath = resolve6(groupDir, sub, "page.tsx");
|
|
7146
|
+
if (existsSync14(pagePath)) {
|
|
7147
|
+
const code = readFileSync9(pagePath, "utf-8");
|
|
7148
|
+
const lines = code.split("\n");
|
|
7149
|
+
return lines.slice(0, 200).join("\n");
|
|
7150
|
+
}
|
|
7151
|
+
}
|
|
7152
|
+
}
|
|
7153
|
+
} catch {
|
|
7154
|
+
return null;
|
|
7155
|
+
}
|
|
7156
|
+
return null;
|
|
7157
|
+
}
|
|
7047
7158
|
async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts) {
|
|
7048
7159
|
let pageNames = [];
|
|
7049
7160
|
spinner.start("Phase 1/6 \u2014 Planning pages...");
|
|
@@ -7198,7 +7309,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7198
7309
|
if (plan && plan.sharedComponents.length > 0) {
|
|
7199
7310
|
spinner.start(`Phase 4.5/6 \u2014 Generating ${plan.sharedComponents.length} shared components from plan...`);
|
|
7200
7311
|
try {
|
|
7201
|
-
const { generateSharedComponentsFromPlan } = await import("./plan-generator-
|
|
7312
|
+
const { generateSharedComponentsFromPlan } = await import("./plan-generator-QUESV7GS.js");
|
|
7202
7313
|
const generated = await generateSharedComponentsFromPlan(
|
|
7203
7314
|
plan,
|
|
7204
7315
|
styleContext,
|
|
@@ -7245,6 +7356,13 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7245
7356
|
const routeNote = `EXISTING ROUTES in this project: ${allRoutes}. All internal links MUST point to one of these routes. If a target doesn't exist, use href="#".`;
|
|
7246
7357
|
const alignmentNote = 'CRITICAL LAYOUT RULE: Every <section> must wrap its content in a container div matching the header width. Use the EXACT same container classes as shown in the style context (e.g. className="container max-w-6xl px-4" or className="max-w-6xl mx-auto px-4"). Inner content can use narrower max-w for text centering, but the outer section container MUST match.';
|
|
7247
7358
|
const planSummaryNote = plan ? formatPlanSummary(plan) : "";
|
|
7359
|
+
const existingAppPageCode = readExistingAppPageForReference(parseOpts?.projectRoot ?? null, plan);
|
|
7360
|
+
const existingAppPageNote = existingAppPageCode ? `
|
|
7361
|
+
EXISTING APP PAGE (match these UI patterns for consistency):
|
|
7362
|
+
\`\`\`
|
|
7363
|
+
${existingAppPageCode}
|
|
7364
|
+
\`\`\`
|
|
7365
|
+
` : "";
|
|
7248
7366
|
const existingPagesContext = buildExistingPagesContext(modCtx.config);
|
|
7249
7367
|
const AI_CONCURRENCY = 3;
|
|
7250
7368
|
let phase5Done = 0;
|
|
@@ -7267,6 +7385,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7267
7385
|
alignmentNote,
|
|
7268
7386
|
authNote,
|
|
7269
7387
|
planSummaryNote,
|
|
7388
|
+
pageType !== "auth" ? existingAppPageNote : void 0,
|
|
7270
7389
|
existingPagesContext,
|
|
7271
7390
|
styleContext
|
|
7272
7391
|
].filter(Boolean).join("\n\n");
|
|
@@ -7427,7 +7546,7 @@ function extractAppNameFromPrompt(prompt) {
|
|
|
7427
7546
|
}
|
|
7428
7547
|
|
|
7429
7548
|
// src/commands/chat/modification-handler.ts
|
|
7430
|
-
import { resolve as
|
|
7549
|
+
import { resolve as resolve8 } from "path";
|
|
7431
7550
|
import { mkdir as mkdir4 } from "fs/promises";
|
|
7432
7551
|
import { dirname as dirname6 } from "path";
|
|
7433
7552
|
import chalk12 from "chalk";
|
|
@@ -7441,8 +7560,8 @@ import {
|
|
|
7441
7560
|
} from "@getcoherent/core";
|
|
7442
7561
|
|
|
7443
7562
|
// src/commands/chat/code-generator.ts
|
|
7444
|
-
import { resolve as
|
|
7445
|
-
import { existsSync as
|
|
7563
|
+
import { resolve as resolve7 } from "path";
|
|
7564
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, readFileSync as readFileSync10 } from "fs";
|
|
7446
7565
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
7447
7566
|
import { dirname as dirname5 } from "path";
|
|
7448
7567
|
import {
|
|
@@ -7507,8 +7626,8 @@ async function ensureComponentsInstalled(componentIds, cm, dsm, pm, projectRoot)
|
|
|
7507
7626
|
for (const componentId of ids) {
|
|
7508
7627
|
const isRegistered = !!cm.read(componentId);
|
|
7509
7628
|
const fileName = toKebabCase(componentId) + ".tsx";
|
|
7510
|
-
const filePath =
|
|
7511
|
-
const fileExists =
|
|
7629
|
+
const filePath = resolve7(projectRoot, "components", "ui", fileName);
|
|
7630
|
+
const fileExists = existsSync15(filePath);
|
|
7512
7631
|
if (isRegistered && fileExists) continue;
|
|
7513
7632
|
const result = await provider.installComponent(componentId, projectRoot);
|
|
7514
7633
|
if (result.success && result.componentDef) {
|
|
@@ -7532,7 +7651,7 @@ async function regenerateComponent(componentId, config2, projectRoot) {
|
|
|
7532
7651
|
const generator = new ComponentGenerator2(config2);
|
|
7533
7652
|
const code = await generator.generate(component);
|
|
7534
7653
|
const fileName = toKebabCase(component.name) + ".tsx";
|
|
7535
|
-
const filePath =
|
|
7654
|
+
const filePath = resolve7(projectRoot, "components", "ui", fileName);
|
|
7536
7655
|
await writeFile(filePath, code);
|
|
7537
7656
|
}
|
|
7538
7657
|
async function regeneratePage(pageId, config2, projectRoot) {
|
|
@@ -7549,8 +7668,8 @@ async function regeneratePage(pageId, config2, projectRoot) {
|
|
|
7549
7668
|
await writeFile(filePath, code);
|
|
7550
7669
|
}
|
|
7551
7670
|
async function canOverwriteShared(projectRoot, componentFile, storedHashes) {
|
|
7552
|
-
const filePath =
|
|
7553
|
-
if (!
|
|
7671
|
+
const filePath = resolve7(projectRoot, componentFile);
|
|
7672
|
+
if (!existsSync15(filePath)) return true;
|
|
7554
7673
|
const storedHash = storedHashes[componentFile];
|
|
7555
7674
|
if (!storedHash) return true;
|
|
7556
7675
|
const edited = await isManuallyEdited(filePath, storedHash);
|
|
@@ -7567,7 +7686,7 @@ async function regenerateLayout(config2, projectRoot, options = { navChanged: fa
|
|
|
7567
7686
|
if (!initialized) {
|
|
7568
7687
|
const layout = config2.pages[0]?.layout || "centered";
|
|
7569
7688
|
const code = await generator.generateLayout(layout, appType, { skipNav: true });
|
|
7570
|
-
await writeFile(
|
|
7689
|
+
await writeFile(resolve7(projectRoot, "app", "layout.tsx"), code);
|
|
7571
7690
|
}
|
|
7572
7691
|
if (config2.navigation?.enabled && appType === "multi-page") {
|
|
7573
7692
|
const navType = config2.navigation.type || "header";
|
|
@@ -7623,17 +7742,17 @@ async function regenerateLayout(config2, projectRoot, options = { navChanged: fa
|
|
|
7623
7742
|
}
|
|
7624
7743
|
}
|
|
7625
7744
|
async function scanAndInstallSharedDeps(projectRoot) {
|
|
7626
|
-
const sharedDir =
|
|
7627
|
-
if (!
|
|
7628
|
-
const files =
|
|
7745
|
+
const sharedDir = resolve7(projectRoot, "components", "shared");
|
|
7746
|
+
if (!existsSync15(sharedDir)) return [];
|
|
7747
|
+
const files = readdirSync3(sharedDir).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts"));
|
|
7629
7748
|
const installed = [];
|
|
7630
7749
|
const provider = getComponentProvider();
|
|
7631
7750
|
for (const file of files) {
|
|
7632
|
-
const code =
|
|
7751
|
+
const code = readFileSync10(resolve7(sharedDir, file), "utf-8");
|
|
7633
7752
|
const importMatches = [...code.matchAll(/@\/components\/ui\/([a-z0-9-]+)/g)];
|
|
7634
7753
|
for (const [, componentId] of importMatches) {
|
|
7635
|
-
const uiPath =
|
|
7636
|
-
if (!
|
|
7754
|
+
const uiPath = resolve7(projectRoot, "components", "ui", `${componentId}.tsx`);
|
|
7755
|
+
if (!existsSync15(uiPath) && provider.has(componentId)) {
|
|
7637
7756
|
try {
|
|
7638
7757
|
await provider.installComponent(componentId, projectRoot);
|
|
7639
7758
|
installed.push(componentId);
|
|
@@ -7645,10 +7764,10 @@ async function scanAndInstallSharedDeps(projectRoot) {
|
|
|
7645
7764
|
return [...new Set(installed)];
|
|
7646
7765
|
}
|
|
7647
7766
|
async function ensureAppRouteGroupLayout(projectRoot, navType, forceUpdate = false) {
|
|
7648
|
-
const layoutPath =
|
|
7649
|
-
if (
|
|
7767
|
+
const layoutPath = resolve7(projectRoot, "app", "(app)", "layout.tsx");
|
|
7768
|
+
if (existsSync15(layoutPath) && !forceUpdate) return;
|
|
7650
7769
|
const { mkdir: mkdirAsync } = await import("fs/promises");
|
|
7651
|
-
await mkdirAsync(
|
|
7770
|
+
await mkdirAsync(resolve7(projectRoot, "app", "(app)"), { recursive: true });
|
|
7652
7771
|
const code = buildAppLayoutCode(navType);
|
|
7653
7772
|
await writeFile(layoutPath, code);
|
|
7654
7773
|
}
|
|
@@ -7736,9 +7855,9 @@ export default function GroupLayout({
|
|
|
7736
7855
|
async function ensurePlanGroupLayouts(projectRoot, plan) {
|
|
7737
7856
|
const { mkdir: mkdirAsync } = await import("fs/promises");
|
|
7738
7857
|
for (const group of plan.groups) {
|
|
7739
|
-
const groupDir =
|
|
7858
|
+
const groupDir = resolve7(projectRoot, "app", `(${group.id})`);
|
|
7740
7859
|
await mkdirAsync(groupDir, { recursive: true });
|
|
7741
|
-
const layoutPath =
|
|
7860
|
+
const layoutPath = resolve7(groupDir, "layout.tsx");
|
|
7742
7861
|
const code = buildGroupLayoutCode(group.layout, group.pages);
|
|
7743
7862
|
await writeFile(layoutPath, code);
|
|
7744
7863
|
}
|
|
@@ -7765,11 +7884,11 @@ async function regenerateFiles(modified, config2, projectRoot, options = { navCh
|
|
|
7765
7884
|
}
|
|
7766
7885
|
if (componentIds.size > 0) {
|
|
7767
7886
|
const twGen = new TailwindConfigGenerator(config2);
|
|
7768
|
-
const twPath =
|
|
7769
|
-
const twCjsPath =
|
|
7770
|
-
if (
|
|
7887
|
+
const twPath = resolve7(projectRoot, "tailwind.config.ts");
|
|
7888
|
+
const twCjsPath = resolve7(projectRoot, "tailwind.config.cjs");
|
|
7889
|
+
if (existsSync15(twPath)) {
|
|
7771
7890
|
await writeFile(twPath, await twGen.generate());
|
|
7772
|
-
} else if (
|
|
7891
|
+
} else if (existsSync15(twCjsPath)) {
|
|
7773
7892
|
await writeFile(twCjsPath, await twGen.generateCjs());
|
|
7774
7893
|
}
|
|
7775
7894
|
}
|
|
@@ -8141,7 +8260,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8141
8260
|
modified: []
|
|
8142
8261
|
};
|
|
8143
8262
|
}
|
|
8144
|
-
const fullPath =
|
|
8263
|
+
const fullPath = resolve8(projectRoot, resolved.file);
|
|
8145
8264
|
let currentCode;
|
|
8146
8265
|
try {
|
|
8147
8266
|
currentCode = await readFile(fullPath);
|
|
@@ -8215,7 +8334,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8215
8334
|
} catch {
|
|
8216
8335
|
return { success: false, message: `Could not read ${pageFilePath}`, modified: [] };
|
|
8217
8336
|
}
|
|
8218
|
-
const sharedPath =
|
|
8337
|
+
const sharedPath = resolve8(projectRoot, resolved.file);
|
|
8219
8338
|
let sharedCode;
|
|
8220
8339
|
try {
|
|
8221
8340
|
sharedCode = await readFile(sharedPath);
|
|
@@ -8294,7 +8413,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8294
8413
|
}
|
|
8295
8414
|
let sourceCode;
|
|
8296
8415
|
try {
|
|
8297
|
-
sourceCode = await readFile(
|
|
8416
|
+
sourceCode = await readFile(resolve8(projectRoot, sourcePath));
|
|
8298
8417
|
} catch {
|
|
8299
8418
|
return { success: false, message: `Could not read ${sourcePath}`, modified: [] };
|
|
8300
8419
|
}
|
|
@@ -8314,7 +8433,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8314
8433
|
description: `Extracted from ${sourcePageName}: ${blockHint}`,
|
|
8315
8434
|
usedIn: []
|
|
8316
8435
|
});
|
|
8317
|
-
const sharedPath =
|
|
8436
|
+
const sharedPath = resolve8(projectRoot, created.file);
|
|
8318
8437
|
let sharedCode;
|
|
8319
8438
|
try {
|
|
8320
8439
|
sharedCode = await readFile(sharedPath);
|
|
@@ -8325,7 +8444,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8325
8444
|
for (const pageName of allPagesToLink) {
|
|
8326
8445
|
const relPath = routeToPath(pageName);
|
|
8327
8446
|
if (!relPath) continue;
|
|
8328
|
-
const fullPath =
|
|
8447
|
+
const fullPath = resolve8(projectRoot, relPath);
|
|
8329
8448
|
let linkPageCode;
|
|
8330
8449
|
try {
|
|
8331
8450
|
linkPageCode = await readFile(fullPath);
|
|
@@ -8659,7 +8778,10 @@ Rules:
|
|
|
8659
8778
|
if (ai.editPageCode) {
|
|
8660
8779
|
console.log(chalk12.dim(" \u270F\uFE0F Applying changes to existing page..."));
|
|
8661
8780
|
const coreRules = CORE_CONSTRAINTS;
|
|
8662
|
-
const
|
|
8781
|
+
const pageRoute = pageDef.route || `/${pageDef.id}`;
|
|
8782
|
+
const pageType = inferPageTypeFromRoute(pageRoute);
|
|
8783
|
+
const qualityRules = `${DESIGN_QUALITY_COMMON}
|
|
8784
|
+
${getDesignQualityForType(pageType)}`;
|
|
8663
8785
|
const contextualRules = selectContextualRules(instruction);
|
|
8664
8786
|
const existingRoutes = dsm.getConfig().pages.map((p) => p.route).join(", ");
|
|
8665
8787
|
const routeRules = `
|
|
@@ -8949,8 +9071,8 @@ function hasNavChanged(before, after) {
|
|
|
8949
9071
|
|
|
8950
9072
|
// src/commands/chat/interactive.ts
|
|
8951
9073
|
import chalk13 from "chalk";
|
|
8952
|
-
import { resolve as
|
|
8953
|
-
import { existsSync as
|
|
9074
|
+
import { resolve as resolve9 } from "path";
|
|
9075
|
+
import { existsSync as existsSync16, readFileSync as readFileSync11, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
8954
9076
|
import { DesignSystemManager as DesignSystemManager6, ComponentManager as ComponentManager4, loadManifest as loadManifest7 } from "@getcoherent/core";
|
|
8955
9077
|
var DEBUG3 = process.env.COHERENT_DEBUG === "1";
|
|
8956
9078
|
async function interactiveChat(options, chatCommandFn) {
|
|
@@ -8970,13 +9092,13 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
8970
9092
|
\u274C Invalid provider: ${options.provider}`));
|
|
8971
9093
|
process.exit(1);
|
|
8972
9094
|
}
|
|
8973
|
-
const historyDir =
|
|
8974
|
-
const historyFile =
|
|
9095
|
+
const historyDir = resolve9(homedir2(), ".coherent");
|
|
9096
|
+
const historyFile = resolve9(historyDir, "history");
|
|
8975
9097
|
let history = [];
|
|
8976
9098
|
try {
|
|
8977
9099
|
mkdirSync5(historyDir, { recursive: true });
|
|
8978
|
-
if (
|
|
8979
|
-
history =
|
|
9100
|
+
if (existsSync16(historyFile)) {
|
|
9101
|
+
history = readFileSync11(historyFile, "utf-8").split("\n").filter(Boolean).slice(-200);
|
|
8980
9102
|
}
|
|
8981
9103
|
} catch (e) {
|
|
8982
9104
|
if (DEBUG3) console.error("Failed to load REPL history:", e);
|
|
@@ -9147,7 +9269,7 @@ async function chatCommand(message, options) {
|
|
|
9147
9269
|
const projectRoot = project.root;
|
|
9148
9270
|
const configPath = project.configPath;
|
|
9149
9271
|
const migrationGuard = join11(projectRoot, ".coherent", "migration-in-progress");
|
|
9150
|
-
if (
|
|
9272
|
+
if (existsSync17(migrationGuard)) {
|
|
9151
9273
|
spinner.fail("Migration in progress");
|
|
9152
9274
|
console.error(chalk14.red("\n\u274C A migration is in progress. Run `coherent migrate --rollback` to undo first."));
|
|
9153
9275
|
bail("Migration in progress");
|
|
@@ -9207,7 +9329,7 @@ async function chatCommand(message, options) {
|
|
|
9207
9329
|
}
|
|
9208
9330
|
if (/switch to light mode|default to light|make.*light.*(default|theme)|light theme/i.test(message)) {
|
|
9209
9331
|
spinner.start("Setting default theme to light...");
|
|
9210
|
-
const layoutPath =
|
|
9332
|
+
const layoutPath = resolve10(projectRoot, "app/layout.tsx");
|
|
9211
9333
|
try {
|
|
9212
9334
|
let layout = await readFile(layoutPath);
|
|
9213
9335
|
layout = layout.replace(/className="dark"/, "");
|
|
@@ -9246,8 +9368,8 @@ async function chatCommand(message, options) {
|
|
|
9246
9368
|
spinner.start("Parsing your request...");
|
|
9247
9369
|
let manifest = await loadManifest8(project.root);
|
|
9248
9370
|
const validShared = manifest.shared.filter((s) => {
|
|
9249
|
-
const fp =
|
|
9250
|
-
return
|
|
9371
|
+
const fp = resolve10(project.root, s.file);
|
|
9372
|
+
return existsSync17(fp);
|
|
9251
9373
|
});
|
|
9252
9374
|
if (validShared.length !== manifest.shared.length) {
|
|
9253
9375
|
const cleaned = manifest.shared.length - validShared.length;
|
|
@@ -9451,9 +9573,9 @@ async function chatCommand(message, options) {
|
|
|
9451
9573
|
if (manifest.shared.length > 0) {
|
|
9452
9574
|
for (const entry of manifest.shared) {
|
|
9453
9575
|
try {
|
|
9454
|
-
const sharedPath =
|
|
9455
|
-
if (
|
|
9456
|
-
const sharedCode =
|
|
9576
|
+
const sharedPath = resolve10(projectRoot, entry.file);
|
|
9577
|
+
if (existsSync17(sharedPath)) {
|
|
9578
|
+
const sharedCode = readFileSync12(sharedPath, "utf-8");
|
|
9457
9579
|
const sharedImports = sharedCode.matchAll(/@\/components\/ui\/([a-z0-9-]+)/g);
|
|
9458
9580
|
for (const m of sharedImports) {
|
|
9459
9581
|
if (m[1]) allNeededComponentIds.add(m[1]);
|
|
@@ -9474,7 +9596,7 @@ async function chatCommand(message, options) {
|
|
|
9474
9596
|
for (const componentId of allNeededComponentIds) {
|
|
9475
9597
|
const isRegistered = !!cm.read(componentId);
|
|
9476
9598
|
const filePath = join11(projectRoot, "components", "ui", `${componentId}.tsx`);
|
|
9477
|
-
const fileExists =
|
|
9599
|
+
const fileExists = existsSync17(filePath);
|
|
9478
9600
|
if (DEBUG4) console.log(chalk14.gray(` Checking ${componentId}: registered=${isRegistered} file=${fileExists}`));
|
|
9479
9601
|
if (!isRegistered || !fileExists) {
|
|
9480
9602
|
missingComponents.push(componentId);
|
|
@@ -9600,9 +9722,9 @@ async function chatCommand(message, options) {
|
|
|
9600
9722
|
const route = page.route || `/${page.id || "page"}`;
|
|
9601
9723
|
const pageFilePath = routeToFsPath(projectRoot, route, false);
|
|
9602
9724
|
let pageCode = "";
|
|
9603
|
-
if (
|
|
9725
|
+
if (existsSync17(pageFilePath)) {
|
|
9604
9726
|
try {
|
|
9605
|
-
pageCode =
|
|
9727
|
+
pageCode = readFileSync12(pageFilePath, "utf-8");
|
|
9606
9728
|
} catch {
|
|
9607
9729
|
}
|
|
9608
9730
|
}
|
|
@@ -9622,8 +9744,8 @@ async function chatCommand(message, options) {
|
|
|
9622
9744
|
}
|
|
9623
9745
|
const missingRoutes = [...allLinkedRoutes].filter((route) => {
|
|
9624
9746
|
if (expandedExisting.has(route)) return false;
|
|
9625
|
-
if (
|
|
9626
|
-
if (
|
|
9747
|
+
if (existsSync17(routeToFsPath(projectRoot, route, false))) return false;
|
|
9748
|
+
if (existsSync17(routeToFsPath(projectRoot, route, true))) return false;
|
|
9627
9749
|
return true;
|
|
9628
9750
|
});
|
|
9629
9751
|
const SCAFFOLD_AI_LIMIT = 10;
|
|
@@ -9687,8 +9809,8 @@ async function chatCommand(message, options) {
|
|
|
9687
9809
|
const isAuth = isAuthRoute(linkedRoute);
|
|
9688
9810
|
const filePath = routeToFsPath(projectRoot, linkedRoute, isAuth);
|
|
9689
9811
|
if (isAuth) await ensureAuthRouteGroup(projectRoot);
|
|
9690
|
-
const dir =
|
|
9691
|
-
if (!
|
|
9812
|
+
const dir = resolve10(filePath, "..");
|
|
9813
|
+
if (!existsSync17(dir)) {
|
|
9692
9814
|
mkdirSync6(dir, { recursive: true });
|
|
9693
9815
|
}
|
|
9694
9816
|
const placeholderCode = `export default function ${pageName.replace(/\s/g, "")}Page() {
|
|
@@ -9721,7 +9843,7 @@ async function chatCommand(message, options) {
|
|
|
9721
9843
|
for (const mod of result.modified) {
|
|
9722
9844
|
if (mod.startsWith("app/") && mod.endsWith("/page.tsx")) {
|
|
9723
9845
|
try {
|
|
9724
|
-
const code =
|
|
9846
|
+
const code = readFileSync12(resolve10(projectRoot, mod), "utf-8");
|
|
9725
9847
|
const issues = validatePageQuality(code, allRoutes).filter(
|
|
9726
9848
|
(i) => i.type === "BROKEN_INTERNAL_LINK"
|
|
9727
9849
|
);
|
|
@@ -9751,7 +9873,7 @@ async function chatCommand(message, options) {
|
|
|
9751
9873
|
dsm.updateConfig(latestConfig);
|
|
9752
9874
|
if (DEBUG4) console.log(chalk14.dim(` [theme] Set defaultMode to "${targetMode}"`));
|
|
9753
9875
|
}
|
|
9754
|
-
const layoutPath =
|
|
9876
|
+
const layoutPath = resolve10(projectRoot, "app", "layout.tsx");
|
|
9755
9877
|
try {
|
|
9756
9878
|
let layoutCode = await readFile(layoutPath);
|
|
9757
9879
|
if (targetMode === "dark" && !layoutCode.includes('className="dark"')) {
|
|
@@ -9801,16 +9923,16 @@ async function chatCommand(message, options) {
|
|
|
9801
9923
|
}
|
|
9802
9924
|
try {
|
|
9803
9925
|
const updatedHashes = { ...storedHashes };
|
|
9804
|
-
const sharedDir =
|
|
9805
|
-
const layoutFile =
|
|
9926
|
+
const sharedDir = resolve10(projectRoot, "components", "shared");
|
|
9927
|
+
const layoutFile = resolve10(projectRoot, "app", "layout.tsx");
|
|
9806
9928
|
const filesToHash = [layoutFile];
|
|
9807
|
-
if (
|
|
9808
|
-
for (const f of
|
|
9809
|
-
if (f.endsWith(".tsx")) filesToHash.push(
|
|
9929
|
+
if (existsSync17(sharedDir)) {
|
|
9930
|
+
for (const f of readdirSync4(sharedDir)) {
|
|
9931
|
+
if (f.endsWith(".tsx")) filesToHash.push(resolve10(sharedDir, f));
|
|
9810
9932
|
}
|
|
9811
9933
|
}
|
|
9812
9934
|
for (const filePath of filesToHash) {
|
|
9813
|
-
if (
|
|
9935
|
+
if (existsSync17(filePath)) {
|
|
9814
9936
|
const rel = relative2(projectRoot, filePath);
|
|
9815
9937
|
updatedHashes[rel] = await computeFileHash(filePath);
|
|
9816
9938
|
}
|
|
@@ -9842,7 +9964,7 @@ async function chatCommand(message, options) {
|
|
|
9842
9964
|
console.log("");
|
|
9843
9965
|
}
|
|
9844
9966
|
if (uxRecommendations) {
|
|
9845
|
-
const recPath =
|
|
9967
|
+
const recPath = resolve10(projectRoot, "recommendations.md");
|
|
9846
9968
|
const section = `
|
|
9847
9969
|
|
|
9848
9970
|
---
|
|
@@ -9852,7 +9974,7 @@ async function chatCommand(message, options) {
|
|
|
9852
9974
|
${uxRecommendations}
|
|
9853
9975
|
`;
|
|
9854
9976
|
try {
|
|
9855
|
-
if (!
|
|
9977
|
+
if (!existsSync17(recPath)) {
|
|
9856
9978
|
await writeFile(
|
|
9857
9979
|
recPath,
|
|
9858
9980
|
"# UX/UI Recommendations\n\nRecommendations are added here when you use `coherent chat` and the AI suggests improvements.\n"
|
|
@@ -9926,18 +10048,68 @@ ${uxRecommendations}
|
|
|
9926
10048
|
import chalk15 from "chalk";
|
|
9927
10049
|
import ora3 from "ora";
|
|
9928
10050
|
import { spawn } from "child_process";
|
|
9929
|
-
import { existsSync as
|
|
9930
|
-
import { resolve as
|
|
10051
|
+
import { existsSync as existsSync20, rmSync as rmSync3, readFileSync as readFileSync15, writeFileSync as writeFileSync10, readdirSync as readdirSync6 } from "fs";
|
|
10052
|
+
import { resolve as resolve11, join as join14 } from "path";
|
|
9931
10053
|
import { readdir as readdir2 } from "fs/promises";
|
|
10054
|
+
|
|
10055
|
+
// src/utils/css-validator.ts
|
|
10056
|
+
var REQUIRED_THEME_TOKENS = [
|
|
10057
|
+
"--color-transparent",
|
|
10058
|
+
"--color-black",
|
|
10059
|
+
"--color-white",
|
|
10060
|
+
"--color-sidebar-background",
|
|
10061
|
+
"--color-sidebar-foreground",
|
|
10062
|
+
"--color-sidebar-primary",
|
|
10063
|
+
"--color-sidebar-primary-foreground",
|
|
10064
|
+
"--color-sidebar-accent",
|
|
10065
|
+
"--color-sidebar-accent-foreground",
|
|
10066
|
+
"--color-sidebar-border",
|
|
10067
|
+
"--color-sidebar-ring",
|
|
10068
|
+
"--color-sidebar-muted",
|
|
10069
|
+
"--color-sidebar-muted-foreground",
|
|
10070
|
+
"--color-chart-1",
|
|
10071
|
+
"--color-chart-2",
|
|
10072
|
+
"--color-chart-3",
|
|
10073
|
+
"--color-chart-4",
|
|
10074
|
+
"--color-chart-5",
|
|
10075
|
+
"--radius-xs"
|
|
10076
|
+
];
|
|
10077
|
+
var VAR_REFERENCE_RE = /var\(--([^)]+)\)/;
|
|
10078
|
+
function validateV4GlobalsCss(css) {
|
|
10079
|
+
const issues = [];
|
|
10080
|
+
if (css.includes("@tailwind base") || css.includes("@tailwind components")) {
|
|
10081
|
+
issues.push("Stale v3 directive (@tailwind) found in v4 CSS \u2014 remove it");
|
|
10082
|
+
}
|
|
10083
|
+
const themeMatch = css.match(/@theme\s+inline\s*\{([^}]*(?:\{[^}]*\}[^}]*)*)\}/s);
|
|
10084
|
+
const themeBlock = themeMatch ? themeMatch[1] : "";
|
|
10085
|
+
for (const token of REQUIRED_THEME_TOKENS) {
|
|
10086
|
+
if (!themeBlock.includes(token)) {
|
|
10087
|
+
issues.push(`Missing @theme token: ${token}`);
|
|
10088
|
+
}
|
|
10089
|
+
}
|
|
10090
|
+
const themeLines = themeBlock.split("\n");
|
|
10091
|
+
for (const line of themeLines) {
|
|
10092
|
+
const varMatch = line.match(VAR_REFERENCE_RE);
|
|
10093
|
+
if (!varMatch) continue;
|
|
10094
|
+
const referencedVar = `--${varMatch[1]}`;
|
|
10095
|
+
const definedInRoot = css.includes(`${referencedVar}:`) || css.includes(`${referencedVar} :`);
|
|
10096
|
+
if (!definedInRoot) {
|
|
10097
|
+
issues.push(`@theme references var(${referencedVar}) but it is not defined in :root/.dark`);
|
|
10098
|
+
}
|
|
10099
|
+
}
|
|
10100
|
+
return issues;
|
|
10101
|
+
}
|
|
10102
|
+
|
|
10103
|
+
// src/commands/preview.ts
|
|
9932
10104
|
import { DesignSystemManager as DesignSystemManager8, ComponentGenerator as ComponentGenerator3 } from "@getcoherent/core";
|
|
9933
10105
|
|
|
9934
10106
|
// src/utils/file-watcher.ts
|
|
9935
|
-
import { readFileSync as
|
|
10107
|
+
import { readFileSync as readFileSync14, writeFileSync as writeFileSync9, existsSync as existsSync19 } from "fs";
|
|
9936
10108
|
import { relative as relative4, join as join13 } from "path";
|
|
9937
10109
|
import { loadManifest as loadManifest9, saveManifest as saveManifest3 } from "@getcoherent/core";
|
|
9938
10110
|
|
|
9939
10111
|
// src/utils/component-integrity.ts
|
|
9940
|
-
import { existsSync as
|
|
10112
|
+
import { existsSync as existsSync18, readFileSync as readFileSync13, readdirSync as readdirSync5 } from "fs";
|
|
9941
10113
|
import { join as join12, relative as relative3 } from "path";
|
|
9942
10114
|
function extractExportedComponentNames(code) {
|
|
9943
10115
|
const names = [];
|
|
@@ -9965,13 +10137,13 @@ function arraysEqual(a, b) {
|
|
|
9965
10137
|
function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
9966
10138
|
const results = [];
|
|
9967
10139
|
const appDir = join12(projectRoot, "app");
|
|
9968
|
-
if (!
|
|
10140
|
+
if (!existsSync18(appDir)) return results;
|
|
9969
10141
|
const pageFiles = collectFiles(appDir, (name) => name === "page.tsx" || name === "page.jsx");
|
|
9970
10142
|
const componentImportPath = componentFile.replace(/\.tsx$/, "").replace(/\.jsx$/, "");
|
|
9971
10143
|
for (const absPath of pageFiles) {
|
|
9972
10144
|
if (absPath.includes("design-system")) continue;
|
|
9973
10145
|
try {
|
|
9974
|
-
const code =
|
|
10146
|
+
const code = readFileSync13(absPath, "utf-8");
|
|
9975
10147
|
const hasNamedImport = new RegExp(`import\\s+\\{[^}]*\\b${componentName}\\b[^}]*\\}\\s+from\\s+['"]`).test(code);
|
|
9976
10148
|
const hasDefaultImport = new RegExp(`import\\s+${componentName}\\s+from\\s+['"]`).test(code);
|
|
9977
10149
|
const hasPathImport = code.includes(`@/${componentImportPath}`);
|
|
@@ -9985,9 +10157,9 @@ function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
|
9985
10157
|
}
|
|
9986
10158
|
function isUsedInLayout(projectRoot, componentName) {
|
|
9987
10159
|
const layoutPath = join12(projectRoot, "app", "layout.tsx");
|
|
9988
|
-
if (!
|
|
10160
|
+
if (!existsSync18(layoutPath)) return false;
|
|
9989
10161
|
try {
|
|
9990
|
-
const code =
|
|
10162
|
+
const code = readFileSync13(layoutPath, "utf-8");
|
|
9991
10163
|
return code.includes(componentName);
|
|
9992
10164
|
} catch {
|
|
9993
10165
|
return false;
|
|
@@ -9996,7 +10168,7 @@ function isUsedInLayout(projectRoot, componentName) {
|
|
|
9996
10168
|
function findUnregisteredComponents(projectRoot, manifest) {
|
|
9997
10169
|
const results = [];
|
|
9998
10170
|
const componentsDir = join12(projectRoot, "components");
|
|
9999
|
-
if (!
|
|
10171
|
+
if (!existsSync18(componentsDir)) return results;
|
|
10000
10172
|
const registeredFiles = new Set(manifest.shared.map((s) => s.file));
|
|
10001
10173
|
const registeredNames = new Set(manifest.shared.map((s) => s.name));
|
|
10002
10174
|
const files = collectFiles(
|
|
@@ -10008,7 +10180,7 @@ function findUnregisteredComponents(projectRoot, manifest) {
|
|
|
10008
10180
|
const relFile = relative3(projectRoot, absPath);
|
|
10009
10181
|
if (registeredFiles.has(relFile)) continue;
|
|
10010
10182
|
try {
|
|
10011
|
-
const code =
|
|
10183
|
+
const code = readFileSync13(absPath, "utf-8");
|
|
10012
10184
|
const exports = extractExportedComponentNames(code);
|
|
10013
10185
|
for (const name of exports) {
|
|
10014
10186
|
if (registeredNames.has(name)) continue;
|
|
@@ -10024,13 +10196,13 @@ function findUnregisteredComponents(projectRoot, manifest) {
|
|
|
10024
10196
|
function findInlineDuplicates(projectRoot, manifest) {
|
|
10025
10197
|
const results = [];
|
|
10026
10198
|
const appDir = join12(projectRoot, "app");
|
|
10027
|
-
if (!
|
|
10199
|
+
if (!existsSync18(appDir)) return results;
|
|
10028
10200
|
const pageFiles = collectFiles(appDir, (name) => name === "page.tsx" || name === "page.jsx");
|
|
10029
10201
|
for (const absPath of pageFiles) {
|
|
10030
10202
|
if (absPath.includes("design-system")) continue;
|
|
10031
10203
|
let code;
|
|
10032
10204
|
try {
|
|
10033
|
-
code =
|
|
10205
|
+
code = readFileSync13(absPath, "utf-8");
|
|
10034
10206
|
} catch {
|
|
10035
10207
|
continue;
|
|
10036
10208
|
}
|
|
@@ -10054,7 +10226,7 @@ function findInlineDuplicates(projectRoot, manifest) {
|
|
|
10054
10226
|
}
|
|
10055
10227
|
function findComponentFileByExportName(projectRoot, componentName) {
|
|
10056
10228
|
const componentsDir = join12(projectRoot, "components");
|
|
10057
|
-
if (!
|
|
10229
|
+
if (!existsSync18(componentsDir)) return null;
|
|
10058
10230
|
const files = collectFiles(
|
|
10059
10231
|
componentsDir,
|
|
10060
10232
|
(name) => (name.endsWith(".tsx") || name.endsWith(".jsx")) && !name.startsWith("."),
|
|
@@ -10062,7 +10234,7 @@ function findComponentFileByExportName(projectRoot, componentName) {
|
|
|
10062
10234
|
);
|
|
10063
10235
|
for (const absPath of files) {
|
|
10064
10236
|
try {
|
|
10065
|
-
const code =
|
|
10237
|
+
const code = readFileSync13(absPath, "utf-8");
|
|
10066
10238
|
const exports = extractExportedComponentNames(code);
|
|
10067
10239
|
if (exports.includes(componentName)) {
|
|
10068
10240
|
return relative3(projectRoot, absPath);
|
|
@@ -10076,7 +10248,7 @@ function removeOrphanedEntries(projectRoot, manifest) {
|
|
|
10076
10248
|
const removed = [];
|
|
10077
10249
|
const valid = manifest.shared.filter((entry) => {
|
|
10078
10250
|
const filePath = join12(projectRoot, entry.file);
|
|
10079
|
-
if (
|
|
10251
|
+
if (existsSync18(filePath)) return true;
|
|
10080
10252
|
removed.push({ id: entry.id, name: entry.name });
|
|
10081
10253
|
return false;
|
|
10082
10254
|
});
|
|
@@ -10095,7 +10267,7 @@ function reconcileComponents(projectRoot, manifest) {
|
|
|
10095
10267
|
const m = { ...manifest, shared: [...manifest.shared], nextId: manifest.nextId };
|
|
10096
10268
|
m.shared = m.shared.filter((entry) => {
|
|
10097
10269
|
const filePath = join12(projectRoot, entry.file);
|
|
10098
|
-
if (!
|
|
10270
|
+
if (!existsSync18(filePath)) {
|
|
10099
10271
|
const newPath = findComponentFileByExportName(projectRoot, entry.name);
|
|
10100
10272
|
if (newPath) {
|
|
10101
10273
|
result.updated.push({ id: entry.id, field: "file", from: entry.file, to: newPath });
|
|
@@ -10107,7 +10279,7 @@ function reconcileComponents(projectRoot, manifest) {
|
|
|
10107
10279
|
}
|
|
10108
10280
|
let code;
|
|
10109
10281
|
try {
|
|
10110
|
-
code =
|
|
10282
|
+
code = readFileSync13(join12(projectRoot, entry.file), "utf-8");
|
|
10111
10283
|
} catch {
|
|
10112
10284
|
return true;
|
|
10113
10285
|
}
|
|
@@ -10184,7 +10356,7 @@ function collectFiles(dir, filter, skipDirs = []) {
|
|
|
10184
10356
|
function walk(d) {
|
|
10185
10357
|
let entries;
|
|
10186
10358
|
try {
|
|
10187
|
-
entries =
|
|
10359
|
+
entries = readdirSync5(d, { withFileTypes: true });
|
|
10188
10360
|
} catch {
|
|
10189
10361
|
return;
|
|
10190
10362
|
}
|
|
@@ -10229,8 +10401,8 @@ function findInlineDuplicatesOfShared(content, manifest) {
|
|
|
10229
10401
|
function getWatcherConfig(projectRoot) {
|
|
10230
10402
|
try {
|
|
10231
10403
|
const pkgPath = join13(projectRoot, "package.json");
|
|
10232
|
-
if (!
|
|
10233
|
-
const pkg = JSON.parse(
|
|
10404
|
+
if (!existsSync19(pkgPath)) return defaultWatcherConfig();
|
|
10405
|
+
const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
|
|
10234
10406
|
const c = pkg?.coherent?.watcher ?? {};
|
|
10235
10407
|
return {
|
|
10236
10408
|
enabled: c.enabled !== false,
|
|
@@ -10258,7 +10430,7 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
10258
10430
|
if (relativePath.includes("node_modules") || relativePath.includes(".next")) return;
|
|
10259
10431
|
let content;
|
|
10260
10432
|
try {
|
|
10261
|
-
content =
|
|
10433
|
+
content = readFileSync14(filePath, "utf-8");
|
|
10262
10434
|
} catch {
|
|
10263
10435
|
return;
|
|
10264
10436
|
}
|
|
@@ -10331,7 +10503,7 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
10331
10503
|
const manifest = await loadManifest9(projectRoot);
|
|
10332
10504
|
const alreadyRegistered = manifest.shared.some((s) => s.file === relativePath);
|
|
10333
10505
|
if (alreadyRegistered) return;
|
|
10334
|
-
const code =
|
|
10506
|
+
const code = readFileSync14(filePath, "utf-8");
|
|
10335
10507
|
const exports = extractExportedComponentNames(code);
|
|
10336
10508
|
if (exports.length > 0) {
|
|
10337
10509
|
const alreadyByName = exports.every((n) => manifest.shared.some((s) => s.name === n));
|
|
@@ -10371,7 +10543,7 @@ function startFileWatcher(projectRoot) {
|
|
|
10371
10543
|
watcher.on("unlink", (fp) => handleFileDelete(projectRoot, fp));
|
|
10372
10544
|
});
|
|
10373
10545
|
const manifestPath = join13(projectRoot, "coherent.components.json");
|
|
10374
|
-
if (
|
|
10546
|
+
if (existsSync19(manifestPath)) {
|
|
10375
10547
|
import("chokidar").then((chokidar) => {
|
|
10376
10548
|
manifestWatcher = chokidar.default.watch(manifestPath, { ignoreInitial: true });
|
|
10377
10549
|
manifestWatcher.on("change", () => handleManifestChange(projectRoot));
|
|
@@ -10385,7 +10557,7 @@ function startFileWatcher(projectRoot) {
|
|
|
10385
10557
|
|
|
10386
10558
|
// src/commands/preview.ts
|
|
10387
10559
|
function getPackageManager(projectRoot) {
|
|
10388
|
-
const hasPnpm =
|
|
10560
|
+
const hasPnpm = existsSync20(resolve11(projectRoot, "pnpm-lock.yaml"));
|
|
10389
10561
|
return hasPnpm ? "pnpm" : "npm";
|
|
10390
10562
|
}
|
|
10391
10563
|
function runInstall(projectRoot) {
|
|
@@ -10407,23 +10579,23 @@ function runInstall(projectRoot) {
|
|
|
10407
10579
|
});
|
|
10408
10580
|
}
|
|
10409
10581
|
function checkProjectInitialized(projectRoot) {
|
|
10410
|
-
const configPath =
|
|
10411
|
-
const packageJsonPath =
|
|
10412
|
-
if (!
|
|
10582
|
+
const configPath = resolve11(projectRoot, "design-system.config.ts");
|
|
10583
|
+
const packageJsonPath = resolve11(projectRoot, "package.json");
|
|
10584
|
+
if (!existsSync20(configPath)) {
|
|
10413
10585
|
return false;
|
|
10414
10586
|
}
|
|
10415
|
-
if (!
|
|
10587
|
+
if (!existsSync20(packageJsonPath)) {
|
|
10416
10588
|
return false;
|
|
10417
10589
|
}
|
|
10418
10590
|
return true;
|
|
10419
10591
|
}
|
|
10420
10592
|
function checkDependenciesInstalled(projectRoot) {
|
|
10421
|
-
const nodeModulesPath =
|
|
10422
|
-
return
|
|
10593
|
+
const nodeModulesPath = resolve11(projectRoot, "node_modules");
|
|
10594
|
+
return existsSync20(nodeModulesPath);
|
|
10423
10595
|
}
|
|
10424
10596
|
function clearStaleCache(projectRoot) {
|
|
10425
10597
|
const nextDir = join14(projectRoot, ".next");
|
|
10426
|
-
if (
|
|
10598
|
+
if (existsSync20(nextDir)) {
|
|
10427
10599
|
rmSync3(nextDir, { recursive: true, force: true });
|
|
10428
10600
|
console.log(chalk15.dim(" \u2714 Cleared stale build cache"));
|
|
10429
10601
|
}
|
|
@@ -10439,7 +10611,7 @@ async function preflightDependencyCheck(projectRoot) {
|
|
|
10439
10611
|
}
|
|
10440
10612
|
async function listPageFiles(appDir) {
|
|
10441
10613
|
const out = [];
|
|
10442
|
-
if (!
|
|
10614
|
+
if (!existsSync20(appDir)) return out;
|
|
10443
10615
|
async function walk(dir) {
|
|
10444
10616
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
10445
10617
|
for (const e of entries) {
|
|
@@ -10455,7 +10627,7 @@ async function validateSyntax(projectRoot) {
|
|
|
10455
10627
|
const appDir = join14(projectRoot, "app");
|
|
10456
10628
|
const pages = await listPageFiles(appDir);
|
|
10457
10629
|
for (const file of pages) {
|
|
10458
|
-
const content =
|
|
10630
|
+
const content = readFileSync15(file, "utf-8");
|
|
10459
10631
|
const fixed = fixUnescapedLtInJsx(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)));
|
|
10460
10632
|
if (fixed !== content) {
|
|
10461
10633
|
writeFileSync10(file, fixed, "utf-8");
|
|
@@ -10466,16 +10638,16 @@ async function validateSyntax(projectRoot) {
|
|
|
10466
10638
|
async function fixMissingComponentExports(projectRoot) {
|
|
10467
10639
|
const appDir = join14(projectRoot, "app");
|
|
10468
10640
|
const uiDir = join14(projectRoot, "components", "ui");
|
|
10469
|
-
if (!
|
|
10641
|
+
if (!existsSync20(appDir) || !existsSync20(uiDir)) return;
|
|
10470
10642
|
const pages = await listPageFiles(appDir);
|
|
10471
10643
|
const sharedDir = join14(projectRoot, "components", "shared");
|
|
10472
|
-
if (
|
|
10473
|
-
const sharedFiles =
|
|
10644
|
+
if (existsSync20(sharedDir)) {
|
|
10645
|
+
const sharedFiles = readdirSync6(sharedDir).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts")).map((f) => join14(sharedDir, f));
|
|
10474
10646
|
pages.push(...sharedFiles);
|
|
10475
10647
|
}
|
|
10476
10648
|
const neededExports = /* @__PURE__ */ new Map();
|
|
10477
10649
|
for (const file of pages) {
|
|
10478
|
-
const content =
|
|
10650
|
+
const content = readFileSync15(file, "utf-8");
|
|
10479
10651
|
const importRe = /import\s*\{([^}]+)\}\s*from\s*['"]@\/components\/ui\/([^'"]+)['"]/g;
|
|
10480
10652
|
let m;
|
|
10481
10653
|
while ((m = importRe.exec(content)) !== null) {
|
|
@@ -10496,7 +10668,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10496
10668
|
const provider = getComponentProvider();
|
|
10497
10669
|
for (const [componentId, needed] of neededExports) {
|
|
10498
10670
|
const componentFile = join14(uiDir, `${componentId}.tsx`);
|
|
10499
|
-
if (!
|
|
10671
|
+
if (!existsSync20(componentFile)) {
|
|
10500
10672
|
if (provider.has(componentId)) {
|
|
10501
10673
|
try {
|
|
10502
10674
|
const result = await provider.installComponent(componentId, projectRoot);
|
|
@@ -10519,7 +10691,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10519
10691
|
}
|
|
10520
10692
|
continue;
|
|
10521
10693
|
}
|
|
10522
|
-
const content =
|
|
10694
|
+
const content = readFileSync15(componentFile, "utf-8");
|
|
10523
10695
|
const exportRe = /export\s+(?:const|function|class)\s+(\w+)|export\s*\{([^}]+)\}/g;
|
|
10524
10696
|
const existingExports = /* @__PURE__ */ new Set();
|
|
10525
10697
|
let em;
|
|
@@ -10554,7 +10726,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10554
10726
|
}
|
|
10555
10727
|
async function backfillPageAnalysis(projectRoot) {
|
|
10556
10728
|
const configPath = join14(projectRoot, "design-system.config.ts");
|
|
10557
|
-
if (!
|
|
10729
|
+
if (!existsSync20(configPath)) return;
|
|
10558
10730
|
try {
|
|
10559
10731
|
const mgr = new DesignSystemManager8(configPath);
|
|
10560
10732
|
const config2 = mgr.getConfig();
|
|
@@ -10571,8 +10743,8 @@ async function backfillPageAnalysis(projectRoot) {
|
|
|
10571
10743
|
} else {
|
|
10572
10744
|
filePath = join14(projectRoot, "app", route.slice(1), "page.tsx");
|
|
10573
10745
|
}
|
|
10574
|
-
if (!
|
|
10575
|
-
const code =
|
|
10746
|
+
if (!existsSync20(filePath)) continue;
|
|
10747
|
+
const code = readFileSync15(filePath, "utf-8");
|
|
10576
10748
|
if (code.length < 50) continue;
|
|
10577
10749
|
page.pageAnalysis = analyzePageCode(code);
|
|
10578
10750
|
changed = true;
|
|
@@ -10722,12 +10894,12 @@ async function openBrowser(url) {
|
|
|
10722
10894
|
}
|
|
10723
10895
|
}
|
|
10724
10896
|
function startDevServer(projectRoot) {
|
|
10725
|
-
const packageJsonPath =
|
|
10726
|
-
if (!
|
|
10897
|
+
const packageJsonPath = resolve11(projectRoot, "package.json");
|
|
10898
|
+
if (!existsSync20(packageJsonPath)) {
|
|
10727
10899
|
throw new Error('package.json not found. Run "coherent init" first.');
|
|
10728
10900
|
}
|
|
10729
|
-
const hasPnpm =
|
|
10730
|
-
const hasNpm =
|
|
10901
|
+
const hasPnpm = existsSync20(resolve11(projectRoot, "pnpm-lock.yaml"));
|
|
10902
|
+
const hasNpm = existsSync20(resolve11(projectRoot, "package-lock.json"));
|
|
10731
10903
|
const command = hasPnpm ? "pnpm" : hasNpm ? "npm" : "npx";
|
|
10732
10904
|
const args = hasPnpm ? ["dev", "--turbo"] : hasNpm ? ["run", "dev", "--", "--turbo"] : ["next", "dev", "--turbo"];
|
|
10733
10905
|
const child = spawn(command, args, {
|
|
@@ -10774,7 +10946,7 @@ async function previewCommand() {
|
|
|
10774
10946
|
if (needsGlobalsFix(projectRoot)) {
|
|
10775
10947
|
spinner.text = "Fixing globals.css...";
|
|
10776
10948
|
try {
|
|
10777
|
-
const dsm = new DesignSystemManager8(
|
|
10949
|
+
const dsm = new DesignSystemManager8(resolve11(projectRoot, "design-system.config.ts"));
|
|
10778
10950
|
await dsm.load();
|
|
10779
10951
|
const config2 = dsm.getConfig();
|
|
10780
10952
|
fixGlobalsCss(projectRoot, config2);
|
|
@@ -10783,6 +10955,20 @@ async function previewCommand() {
|
|
|
10783
10955
|
spinner.warn("Could not auto-fix globals.css");
|
|
10784
10956
|
}
|
|
10785
10957
|
}
|
|
10958
|
+
if (isTailwindV4(projectRoot)) {
|
|
10959
|
+
const globalsPath = resolve11(projectRoot, "app", "globals.css");
|
|
10960
|
+
if (existsSync20(globalsPath)) {
|
|
10961
|
+
const globalsContent = readFileSync15(globalsPath, "utf-8");
|
|
10962
|
+
const cssIssues = validateV4GlobalsCss(globalsContent);
|
|
10963
|
+
if (cssIssues.length > 0) {
|
|
10964
|
+
console.log(chalk15.yellow("\n\u26A0\uFE0F globals.css validation warnings:"));
|
|
10965
|
+
for (const issue of cssIssues) {
|
|
10966
|
+
console.log(chalk15.yellow(` \u2022 ${issue}`));
|
|
10967
|
+
}
|
|
10968
|
+
console.log(chalk15.dim(' Run "coherent chat" to regenerate globals.css\n'));
|
|
10969
|
+
}
|
|
10970
|
+
}
|
|
10971
|
+
}
|
|
10786
10972
|
spinner.text = "Pre-flight: dependencies and syntax...";
|
|
10787
10973
|
await preflightDependencyCheck(projectRoot);
|
|
10788
10974
|
clearStaleCache(projectRoot);
|
|
@@ -10813,8 +10999,8 @@ async function previewCommand() {
|
|
|
10813
10999
|
import chalk16 from "chalk";
|
|
10814
11000
|
import ora4 from "ora";
|
|
10815
11001
|
import { spawn as spawn2 } from "child_process";
|
|
10816
|
-
import { existsSync as
|
|
10817
|
-
import { resolve as
|
|
11002
|
+
import { existsSync as existsSync21, rmSync as rmSync4, readdirSync as readdirSync7 } from "fs";
|
|
11003
|
+
import { resolve as resolve12, join as join15, dirname as dirname7 } from "path";
|
|
10818
11004
|
import { readdir as readdir3, readFile as readFile6, writeFile as writeFile5, mkdir as mkdir5, copyFile as copyFile2 } from "fs/promises";
|
|
10819
11005
|
var COPY_EXCLUDE = /* @__PURE__ */ new Set([
|
|
10820
11006
|
"node_modules",
|
|
@@ -10848,17 +11034,17 @@ async function copyDir(src, dest) {
|
|
|
10848
11034
|
}
|
|
10849
11035
|
}
|
|
10850
11036
|
function checkProjectInitialized2(projectRoot) {
|
|
10851
|
-
return
|
|
11037
|
+
return existsSync21(resolve12(projectRoot, "design-system.config.ts")) && existsSync21(resolve12(projectRoot, "package.json"));
|
|
10852
11038
|
}
|
|
10853
11039
|
function getPackageManager2(projectRoot) {
|
|
10854
|
-
if (
|
|
10855
|
-
if (
|
|
11040
|
+
if (existsSync21(resolve12(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
11041
|
+
if (existsSync21(resolve12(projectRoot, "package-lock.json"))) return "npm";
|
|
10856
11042
|
return "npx";
|
|
10857
11043
|
}
|
|
10858
11044
|
async function patchNextConfigForExport(outRoot) {
|
|
10859
11045
|
for (const name of ["next.config.ts", "next.config.mjs", "next.config.js"]) {
|
|
10860
11046
|
const p = join15(outRoot, name);
|
|
10861
|
-
if (!
|
|
11047
|
+
if (!existsSync21(p)) continue;
|
|
10862
11048
|
let content = await readFile6(p, "utf-8");
|
|
10863
11049
|
if (content.includes("ignoreDuringBuilds")) return;
|
|
10864
11050
|
content = content.replace(
|
|
@@ -10873,9 +11059,9 @@ async function buildProduction(projectRoot) {
|
|
|
10873
11059
|
const pm = getPackageManager2(projectRoot);
|
|
10874
11060
|
const command = pm === "pnpm" ? "pnpm" : pm === "npm" ? "npm" : "npx";
|
|
10875
11061
|
const args = pm === "npx" ? ["next", "build"] : ["run", "build"];
|
|
10876
|
-
return new Promise((
|
|
11062
|
+
return new Promise((resolve17, reject) => {
|
|
10877
11063
|
const child = spawn2(command, args, { cwd: projectRoot, stdio: "inherit", shell: true });
|
|
10878
|
-
child.on("exit", (code) => code === 0 ?
|
|
11064
|
+
child.on("exit", (code) => code === 0 ? resolve17() : reject(new Error(`Build failed with exit code ${code}`)));
|
|
10879
11065
|
child.on("error", (error) => reject(new Error(`Failed to start build: ${error.message}`)));
|
|
10880
11066
|
});
|
|
10881
11067
|
}
|
|
@@ -10900,7 +11086,7 @@ EXPOSE 3000
|
|
|
10900
11086
|
`;
|
|
10901
11087
|
async function ensureReadmeDeploySection(outRoot) {
|
|
10902
11088
|
const readmePath = join15(outRoot, "README.md");
|
|
10903
|
-
if (!
|
|
11089
|
+
if (!existsSync21(readmePath)) return;
|
|
10904
11090
|
try {
|
|
10905
11091
|
let content = await readFile6(readmePath, "utf-8");
|
|
10906
11092
|
if (/##\s+Deploy\b/m.test(content)) return;
|
|
@@ -10925,16 +11111,16 @@ async function countPages(outRoot) {
|
|
|
10925
11111
|
}
|
|
10926
11112
|
}
|
|
10927
11113
|
const appDir = join15(outRoot, "app");
|
|
10928
|
-
if (
|
|
11114
|
+
if (existsSync21(appDir)) await walk(appDir);
|
|
10929
11115
|
return n;
|
|
10930
11116
|
}
|
|
10931
11117
|
function countComponents(outRoot) {
|
|
10932
11118
|
let n = 0;
|
|
10933
11119
|
for (const sub of ["ui", "shared"]) {
|
|
10934
11120
|
const dir = join15(outRoot, "components", sub);
|
|
10935
|
-
if (!
|
|
11121
|
+
if (!existsSync21(dir)) continue;
|
|
10936
11122
|
try {
|
|
10937
|
-
n +=
|
|
11123
|
+
n += readdirSync7(dir).filter((f) => f.endsWith(".tsx") || f.endsWith(".jsx")).length;
|
|
10938
11124
|
} catch {
|
|
10939
11125
|
}
|
|
10940
11126
|
}
|
|
@@ -10943,7 +11129,7 @@ function countComponents(outRoot) {
|
|
|
10943
11129
|
var IMPORT_FROM_REGEX2 = /from\s+['"]([^'"]+)['"]/g;
|
|
10944
11130
|
async function collectImportedPackages2(dir, extensions) {
|
|
10945
11131
|
const packages = /* @__PURE__ */ new Set();
|
|
10946
|
-
if (!
|
|
11132
|
+
if (!existsSync21(dir)) return packages;
|
|
10947
11133
|
async function walk(d) {
|
|
10948
11134
|
const entries = await readdir3(d, { withFileTypes: true });
|
|
10949
11135
|
for (const e of entries) {
|
|
@@ -10971,7 +11157,7 @@ async function collectImportedPackages2(dir, extensions) {
|
|
|
10971
11157
|
}
|
|
10972
11158
|
async function findMissingDepsInExport(outRoot) {
|
|
10973
11159
|
const pkgPath = join15(outRoot, "package.json");
|
|
10974
|
-
if (!
|
|
11160
|
+
if (!existsSync21(pkgPath)) return [];
|
|
10975
11161
|
let pkg;
|
|
10976
11162
|
try {
|
|
10977
11163
|
pkg = JSON.parse(await readFile6(pkgPath, "utf-8"));
|
|
@@ -10992,25 +11178,25 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
10992
11178
|
const removed = [];
|
|
10993
11179
|
for (const p of ["app/design-system", "app/api/design-system"]) {
|
|
10994
11180
|
const full = join15(outputDir, p);
|
|
10995
|
-
if (
|
|
11181
|
+
if (existsSync21(full)) {
|
|
10996
11182
|
rmSync4(full, { recursive: true, force: true });
|
|
10997
11183
|
removed.push(p);
|
|
10998
11184
|
}
|
|
10999
11185
|
}
|
|
11000
11186
|
const appNavPath = join15(outputDir, "app", "AppNav.tsx");
|
|
11001
|
-
if (
|
|
11187
|
+
if (existsSync21(appNavPath)) {
|
|
11002
11188
|
rmSync4(appNavPath, { force: true });
|
|
11003
11189
|
removed.push("app/AppNav.tsx");
|
|
11004
11190
|
}
|
|
11005
11191
|
const layoutPath = join15(outputDir, "app", "layout.tsx");
|
|
11006
|
-
if (
|
|
11192
|
+
if (existsSync21(layoutPath)) {
|
|
11007
11193
|
let layout = await readFile6(layoutPath, "utf-8");
|
|
11008
11194
|
layout = layout.replace(/import\s*\{?\s*AppNav\s*\}?\s*from\s*['"][^'"]+['"]\s*\n?/g, "");
|
|
11009
11195
|
layout = layout.replace(/\s*<AppNav\s*\/?\s*>\s*/g, "\n");
|
|
11010
11196
|
await writeFile5(layoutPath, layout, "utf-8");
|
|
11011
11197
|
}
|
|
11012
11198
|
const sharedHeaderPath = join15(outputDir, "components", "shared", "header.tsx");
|
|
11013
|
-
if (
|
|
11199
|
+
if (existsSync21(sharedHeaderPath)) {
|
|
11014
11200
|
let header = await readFile6(sharedHeaderPath, "utf-8");
|
|
11015
11201
|
header = header.replace(/<Link\s[^>]*href="\/design-system"[^>]*>[\s\S]*?<\/Link>/g, "");
|
|
11016
11202
|
header = header.replace(/\n\s*<>\s*\n/, "\n");
|
|
@@ -11018,7 +11204,7 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
11018
11204
|
await writeFile5(sharedHeaderPath, header, "utf-8");
|
|
11019
11205
|
}
|
|
11020
11206
|
const guardPath2 = join15(outputDir, "app", "ShowWhenNotAuthRoute.tsx");
|
|
11021
|
-
if (
|
|
11207
|
+
if (existsSync21(guardPath2)) {
|
|
11022
11208
|
let guard = await readFile6(guardPath2, "utf-8");
|
|
11023
11209
|
guard = guard.replace(/['"],?\s*'\/design-system['"],?\s*/g, "");
|
|
11024
11210
|
const pathsMatch = guard.match(/HIDDEN_PATHS\s*=\s*\[([^\]]*)\]/);
|
|
@@ -11026,7 +11212,7 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
11026
11212
|
if (remaining.length === 0) {
|
|
11027
11213
|
rmSync4(guardPath2, { force: true });
|
|
11028
11214
|
removed.push("app/ShowWhenNotAuthRoute.tsx");
|
|
11029
|
-
if (
|
|
11215
|
+
if (existsSync21(layoutPath)) {
|
|
11030
11216
|
let layout = await readFile6(layoutPath, "utf-8");
|
|
11031
11217
|
layout = layout.replace(/import\s+\w+\s+from\s*['"]\.\/ShowWhenNotAuthRoute['"]\s*\n?/g, "");
|
|
11032
11218
|
layout = layout.replace(/\s*<ShowWhenNotAuthRoute>\s*\n?/g, "\n");
|
|
@@ -11047,14 +11233,14 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
11047
11233
|
"recommendations.md"
|
|
11048
11234
|
]) {
|
|
11049
11235
|
const full = join15(outputDir, name);
|
|
11050
|
-
if (
|
|
11236
|
+
if (existsSync21(full)) {
|
|
11051
11237
|
rmSync4(full, { force: true });
|
|
11052
11238
|
removed.push(name);
|
|
11053
11239
|
}
|
|
11054
11240
|
}
|
|
11055
11241
|
for (const dir of [".claude", ".coherent"]) {
|
|
11056
11242
|
const full = join15(outputDir, dir);
|
|
11057
|
-
if (
|
|
11243
|
+
if (existsSync21(full)) {
|
|
11058
11244
|
rmSync4(full, { recursive: true, force: true });
|
|
11059
11245
|
removed.push(dir + "/");
|
|
11060
11246
|
}
|
|
@@ -11062,7 +11248,7 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
11062
11248
|
return removed;
|
|
11063
11249
|
}
|
|
11064
11250
|
async function exportCommand(options = {}) {
|
|
11065
|
-
const outputDir =
|
|
11251
|
+
const outputDir = resolve12(process.cwd(), options.output ?? "./export");
|
|
11066
11252
|
const doBuild = options.build !== false;
|
|
11067
11253
|
const keepDs = options.keepDs === true;
|
|
11068
11254
|
const spinner = ora4("Preparing export...").start();
|
|
@@ -11078,7 +11264,7 @@ async function exportCommand(options = {}) {
|
|
|
11078
11264
|
process.exit(1);
|
|
11079
11265
|
}
|
|
11080
11266
|
spinner.text = "Copying project...";
|
|
11081
|
-
if (
|
|
11267
|
+
if (existsSync21(outputDir)) rmSync4(outputDir, { recursive: true, force: true });
|
|
11082
11268
|
await copyDir(projectRoot, outputDir);
|
|
11083
11269
|
spinner.succeed("Project copied");
|
|
11084
11270
|
if (!keepDs) {
|
|
@@ -11253,8 +11439,8 @@ async function regenerateDocsCommand() {
|
|
|
11253
11439
|
|
|
11254
11440
|
// src/commands/fix.ts
|
|
11255
11441
|
import chalk19 from "chalk";
|
|
11256
|
-
import { readdirSync as
|
|
11257
|
-
import { resolve as
|
|
11442
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync16, existsSync as existsSync22, writeFileSync as writeFileSync11, rmSync as rmSync5, mkdirSync as mkdirSync7 } from "fs";
|
|
11443
|
+
import { resolve as resolve13, join as join16 } from "path";
|
|
11258
11444
|
import {
|
|
11259
11445
|
DesignSystemManager as DesignSystemManager11,
|
|
11260
11446
|
ComponentManager as ComponentManager6,
|
|
@@ -11278,7 +11464,7 @@ function extractComponentIdsFromCode2(code) {
|
|
|
11278
11464
|
function listTsxFiles(dir) {
|
|
11279
11465
|
const files = [];
|
|
11280
11466
|
try {
|
|
11281
|
-
const entries =
|
|
11467
|
+
const entries = readdirSync8(dir, { withFileTypes: true });
|
|
11282
11468
|
for (const e of entries) {
|
|
11283
11469
|
const full = join16(dir, e.name);
|
|
11284
11470
|
if (e.isDirectory() && e.name !== "node_modules" && !e.name.startsWith(".")) {
|
|
@@ -11310,7 +11496,7 @@ async function fixCommand(opts = {}) {
|
|
|
11310
11496
|
}
|
|
11311
11497
|
if (!skipCache) {
|
|
11312
11498
|
const nextDir = join16(projectRoot, ".next");
|
|
11313
|
-
if (
|
|
11499
|
+
if (existsSync22(nextDir)) {
|
|
11314
11500
|
if (!dryRun) rmSync5(nextDir, { recursive: true, force: true });
|
|
11315
11501
|
fixes.push("Cleared build cache");
|
|
11316
11502
|
console.log(chalk19.green(" \u2714 Cleared build cache"));
|
|
@@ -11332,12 +11518,12 @@ async function fixCommand(opts = {}) {
|
|
|
11332
11518
|
}
|
|
11333
11519
|
}
|
|
11334
11520
|
}
|
|
11335
|
-
const appDir =
|
|
11521
|
+
const appDir = resolve13(projectRoot, "app");
|
|
11336
11522
|
const allTsxFiles = listTsxFiles(appDir);
|
|
11337
|
-
const componentsTsxFiles = listTsxFiles(
|
|
11523
|
+
const componentsTsxFiles = listTsxFiles(resolve13(projectRoot, "components"));
|
|
11338
11524
|
const allComponentIds = /* @__PURE__ */ new Set();
|
|
11339
11525
|
for (const file of [...allTsxFiles, ...componentsTsxFiles]) {
|
|
11340
|
-
const content =
|
|
11526
|
+
const content = readFileSync16(file, "utf-8");
|
|
11341
11527
|
extractComponentIdsFromCode2(content).forEach((id) => allComponentIds.add(id));
|
|
11342
11528
|
}
|
|
11343
11529
|
let dsm = null;
|
|
@@ -11356,8 +11542,8 @@ async function fixCommand(opts = {}) {
|
|
|
11356
11542
|
missingComponents.push(id);
|
|
11357
11543
|
} else {
|
|
11358
11544
|
const fileName = toKebabCase(id) + ".tsx";
|
|
11359
|
-
const filePath =
|
|
11360
|
-
if (!
|
|
11545
|
+
const filePath = resolve13(projectRoot, "components", "ui", fileName);
|
|
11546
|
+
if (!existsSync22(filePath)) missingFiles.push(id);
|
|
11361
11547
|
}
|
|
11362
11548
|
}
|
|
11363
11549
|
const provider = getComponentProvider();
|
|
@@ -11386,8 +11572,8 @@ async function fixCommand(opts = {}) {
|
|
|
11386
11572
|
const generator = new ComponentGenerator4(updatedConfig);
|
|
11387
11573
|
const code = await generator.generate(component);
|
|
11388
11574
|
const fileName = toKebabCase(component.name) + ".tsx";
|
|
11389
|
-
const filePath =
|
|
11390
|
-
mkdirSync7(
|
|
11575
|
+
const filePath = resolve13(projectRoot, "components", "ui", fileName);
|
|
11576
|
+
mkdirSync7(resolve13(projectRoot, "components", "ui"), { recursive: true });
|
|
11391
11577
|
await writeFile(filePath, code);
|
|
11392
11578
|
}
|
|
11393
11579
|
}
|
|
@@ -11409,7 +11595,7 @@ async function fixCommand(opts = {}) {
|
|
|
11409
11595
|
const userTsxFiles = allTsxFiles.filter((f) => !f.includes("/design-system/"));
|
|
11410
11596
|
let syntaxFixed = 0;
|
|
11411
11597
|
for (const file of userTsxFiles) {
|
|
11412
|
-
const content =
|
|
11598
|
+
const content = readFileSync16(file, "utf-8");
|
|
11413
11599
|
const fixed = fixUnescapedLtInJsx(
|
|
11414
11600
|
fixEscapedClosingQuotes(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)))
|
|
11415
11601
|
);
|
|
@@ -11427,7 +11613,7 @@ async function fixCommand(opts = {}) {
|
|
|
11427
11613
|
let qualityFixCount = 0;
|
|
11428
11614
|
const qualityFixDetails = [];
|
|
11429
11615
|
for (const file of userTsxFiles) {
|
|
11430
|
-
const content =
|
|
11616
|
+
const content = readFileSync16(file, "utf-8");
|
|
11431
11617
|
const { code: autoFixed, fixes: fileFixes } = await autoFixCode(content);
|
|
11432
11618
|
if (autoFixed !== content) {
|
|
11433
11619
|
if (!dryRun) writeFileSync11(file, autoFixed, "utf-8");
|
|
@@ -11446,7 +11632,7 @@ async function fixCommand(opts = {}) {
|
|
|
11446
11632
|
let totalWarnings = 0;
|
|
11447
11633
|
const fileIssues = [];
|
|
11448
11634
|
for (const file of allTsxFiles) {
|
|
11449
|
-
const code = dryRun ?
|
|
11635
|
+
const code = dryRun ? readFileSync16(file, "utf-8") : readFileSync16(file, "utf-8");
|
|
11450
11636
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11451
11637
|
const baseName = file.split("/").pop() || "";
|
|
11452
11638
|
const isAuthPage = relativePath.includes("(auth)");
|
|
@@ -11568,17 +11754,17 @@ async function fixCommand(opts = {}) {
|
|
|
11568
11754
|
|
|
11569
11755
|
// src/commands/check.ts
|
|
11570
11756
|
import chalk20 from "chalk";
|
|
11571
|
-
import { resolve as
|
|
11572
|
-
import { readdirSync as
|
|
11757
|
+
import { resolve as resolve14 } from "path";
|
|
11758
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync17, statSync as statSync3, existsSync as existsSync23 } from "fs";
|
|
11573
11759
|
import { loadManifest as loadManifest11 } from "@getcoherent/core";
|
|
11574
11760
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", "design-system"]);
|
|
11575
11761
|
function findTsxFiles(dir) {
|
|
11576
11762
|
const results = [];
|
|
11577
11763
|
try {
|
|
11578
|
-
const entries =
|
|
11764
|
+
const entries = readdirSync9(dir);
|
|
11579
11765
|
for (const entry of entries) {
|
|
11580
|
-
const full =
|
|
11581
|
-
const stat =
|
|
11766
|
+
const full = resolve14(dir, entry);
|
|
11767
|
+
const stat = statSync3(full);
|
|
11582
11768
|
if (stat.isDirectory() && !entry.startsWith(".") && !EXCLUDED_DIRS.has(entry)) {
|
|
11583
11769
|
results.push(...findTsxFiles(full));
|
|
11584
11770
|
} else if (entry.endsWith(".tsx")) {
|
|
@@ -11612,7 +11798,7 @@ async function checkCommand(opts = {}) {
|
|
|
11612
11798
|
} catch {
|
|
11613
11799
|
}
|
|
11614
11800
|
if (!skipPages) {
|
|
11615
|
-
const appDir =
|
|
11801
|
+
const appDir = resolve14(projectRoot, "app");
|
|
11616
11802
|
const files = findTsxFiles(appDir);
|
|
11617
11803
|
result.pages.total = files.length;
|
|
11618
11804
|
if (!opts.json) console.log(chalk20.cyan("\n \u{1F4C4} Pages") + chalk20.dim(` (${files.length} scanned)
|
|
@@ -11626,7 +11812,7 @@ async function checkCommand(opts = {}) {
|
|
|
11626
11812
|
"NATIVE_TABLE"
|
|
11627
11813
|
]);
|
|
11628
11814
|
for (const file of files) {
|
|
11629
|
-
const code =
|
|
11815
|
+
const code = readFileSync17(file, "utf-8");
|
|
11630
11816
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11631
11817
|
const baseName = file.split("/").pop() || "";
|
|
11632
11818
|
const isAuthPage = relativePath.includes("(auth)");
|
|
@@ -11668,7 +11854,7 @@ async function checkCommand(opts = {}) {
|
|
|
11668
11854
|
routeSet.add("/");
|
|
11669
11855
|
routeSet.add("#");
|
|
11670
11856
|
for (const file of files) {
|
|
11671
|
-
const code =
|
|
11857
|
+
const code = readFileSync17(file, "utf-8");
|
|
11672
11858
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11673
11859
|
const lines = code.split("\n");
|
|
11674
11860
|
const linkHrefRe = /href\s*=\s*["'](\/[a-z0-9/-]*)["']/gi;
|
|
@@ -11700,8 +11886,8 @@ async function checkCommand(opts = {}) {
|
|
|
11700
11886
|
const manifest = await loadManifest11(project.root);
|
|
11701
11887
|
if (manifest.shared.length > 0) {
|
|
11702
11888
|
for (const entry of manifest.shared) {
|
|
11703
|
-
const fullPath =
|
|
11704
|
-
if (!
|
|
11889
|
+
const fullPath = resolve14(project.root, entry.file);
|
|
11890
|
+
if (!existsSync23(fullPath)) {
|
|
11705
11891
|
result.pages.withErrors++;
|
|
11706
11892
|
if (!opts.json) console.log(chalk20.red(`
|
|
11707
11893
|
\u2717 Missing shared component file: ${entry.id} (${entry.file})`));
|
|
@@ -11725,8 +11911,8 @@ async function checkCommand(opts = {}) {
|
|
|
11725
11911
|
let _staleUsedIn = 0;
|
|
11726
11912
|
let _nameMismatch = 0;
|
|
11727
11913
|
for (const entry of manifest.shared) {
|
|
11728
|
-
const filePath =
|
|
11729
|
-
const fileExists =
|
|
11914
|
+
const filePath = resolve14(projectRoot, entry.file);
|
|
11915
|
+
const fileExists = existsSync23(filePath);
|
|
11730
11916
|
if (!fileExists) {
|
|
11731
11917
|
_orphaned++;
|
|
11732
11918
|
if (!opts.json) {
|
|
@@ -11736,7 +11922,7 @@ async function checkCommand(opts = {}) {
|
|
|
11736
11922
|
continue;
|
|
11737
11923
|
}
|
|
11738
11924
|
try {
|
|
11739
|
-
const code =
|
|
11925
|
+
const code = readFileSync17(filePath, "utf-8");
|
|
11740
11926
|
const actualExports = extractExportedComponentNames(code);
|
|
11741
11927
|
if (actualExports.length > 0 && !actualExports.includes(entry.name)) {
|
|
11742
11928
|
_nameMismatch++;
|
|
@@ -11804,7 +11990,7 @@ async function checkCommand(opts = {}) {
|
|
|
11804
11990
|
id: e.id,
|
|
11805
11991
|
name: e.name,
|
|
11806
11992
|
type: e.type,
|
|
11807
|
-
status:
|
|
11993
|
+
status: existsSync23(resolve14(projectRoot, e.file)) ? "ok" : "unused",
|
|
11808
11994
|
message: "",
|
|
11809
11995
|
suggestions: void 0
|
|
11810
11996
|
}))
|
|
@@ -11899,8 +12085,8 @@ import {
|
|
|
11899
12085
|
generateSharedComponent as generateSharedComponent5,
|
|
11900
12086
|
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout3
|
|
11901
12087
|
} from "@getcoherent/core";
|
|
11902
|
-
import { existsSync as
|
|
11903
|
-
import { resolve as
|
|
12088
|
+
import { existsSync as existsSync24 } from "fs";
|
|
12089
|
+
import { resolve as resolve15 } from "path";
|
|
11904
12090
|
|
|
11905
12091
|
// src/utils/ds-files.ts
|
|
11906
12092
|
import { mkdir as mkdir6, writeFile as writeFile6 } from "fs/promises";
|
|
@@ -12030,8 +12216,8 @@ function createComponentsCommand() {
|
|
|
12030
12216
|
const updated = await integrateSharedLayoutIntoRootLayout3(project.root);
|
|
12031
12217
|
if (updated) console.log(chalk26.cyan(" Updated app/layout.tsx to use shared layout components.\n"));
|
|
12032
12218
|
}
|
|
12033
|
-
const sharedPagePath =
|
|
12034
|
-
if (!
|
|
12219
|
+
const sharedPagePath = resolve15(project.root, "app/design-system/shared/page.tsx");
|
|
12220
|
+
if (!existsSync24(sharedPagePath)) {
|
|
12035
12221
|
try {
|
|
12036
12222
|
const dsm = new DesignSystemManager12(project.configPath);
|
|
12037
12223
|
await dsm.load();
|
|
@@ -12057,8 +12243,8 @@ function createComponentsCommand() {
|
|
|
12057
12243
|
import chalk27 from "chalk";
|
|
12058
12244
|
import ora6 from "ora";
|
|
12059
12245
|
import { writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
|
|
12060
|
-
import { resolve as
|
|
12061
|
-
import { existsSync as
|
|
12246
|
+
import { resolve as resolve16, join as join18, dirname as dirname9 } from "path";
|
|
12247
|
+
import { existsSync as existsSync25 } from "fs";
|
|
12062
12248
|
import {
|
|
12063
12249
|
FigmaClient,
|
|
12064
12250
|
parseFigmaFileResponse,
|
|
@@ -12282,7 +12468,7 @@ async function importFigmaAction(urlOrKey, opts) {
|
|
|
12282
12468
|
if (dryRun) stats.filesWritten.push(FIGMA_COMPONENT_MAP_FILENAME);
|
|
12283
12469
|
else
|
|
12284
12470
|
await writeFile7(
|
|
12285
|
-
|
|
12471
|
+
resolve16(projectRoot, FIGMA_COMPONENT_MAP_FILENAME),
|
|
12286
12472
|
JSON.stringify(componentMapObj, null, 2),
|
|
12287
12473
|
"utf-8"
|
|
12288
12474
|
);
|
|
@@ -12305,9 +12491,9 @@ async function importFigmaAction(urlOrKey, opts) {
|
|
|
12305
12491
|
const fullConfig = buildFigmaImportConfig(mergedConfig, pageDefs, intermediate.fileName);
|
|
12306
12492
|
if (!dryRun) {
|
|
12307
12493
|
spinner.start("Updating design-system.config.ts...");
|
|
12308
|
-
const configPath =
|
|
12494
|
+
const configPath = resolve16(projectRoot, DESIGN_SYSTEM_CONFIG_PATH);
|
|
12309
12495
|
const dsm = new DesignSystemManager13(configPath);
|
|
12310
|
-
if (
|
|
12496
|
+
if (existsSync25(configPath)) {
|
|
12311
12497
|
await dsm.load();
|
|
12312
12498
|
const existing = dsm.getConfig();
|
|
12313
12499
|
dsm.updateConfig({
|
|
@@ -12337,7 +12523,7 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
|
|
|
12337
12523
|
spinner.succeed("design-system.config.ts updated");
|
|
12338
12524
|
spinner.start("Ensuring root layout...");
|
|
12339
12525
|
const layoutPath = join18(projectRoot, "app/layout.tsx");
|
|
12340
|
-
if (!
|
|
12526
|
+
if (!existsSync25(layoutPath)) {
|
|
12341
12527
|
await mkdir7(dirname9(layoutPath), { recursive: true });
|
|
12342
12528
|
await writeFile7(layoutPath, MINIMAL_ROOT_LAYOUT, "utf-8");
|
|
12343
12529
|
stats.filesWritten.push("app/layout.tsx");
|
|
@@ -12427,7 +12613,7 @@ async function dsRegenerateCommand() {
|
|
|
12427
12613
|
// src/commands/update.ts
|
|
12428
12614
|
import chalk29 from "chalk";
|
|
12429
12615
|
import ora8 from "ora";
|
|
12430
|
-
import { readFileSync as
|
|
12616
|
+
import { readFileSync as readFileSync18, existsSync as existsSync26 } from "fs";
|
|
12431
12617
|
import { join as join19 } from "path";
|
|
12432
12618
|
import { DesignSystemManager as DesignSystemManager15, CLI_VERSION as CLI_VERSION4 } from "@getcoherent/core";
|
|
12433
12619
|
|
|
@@ -12598,9 +12784,9 @@ var EXPECTED_CSS_VARS = [
|
|
|
12598
12784
|
];
|
|
12599
12785
|
function checkMissingCssVars(projectRoot) {
|
|
12600
12786
|
const globalsPath = join19(projectRoot, "app", "globals.css");
|
|
12601
|
-
if (!
|
|
12787
|
+
if (!existsSync26(globalsPath)) return [];
|
|
12602
12788
|
try {
|
|
12603
|
-
const content =
|
|
12789
|
+
const content = readFileSync18(globalsPath, "utf-8");
|
|
12604
12790
|
return EXPECTED_CSS_VARS.filter((v) => !content.includes(v));
|
|
12605
12791
|
} catch {
|
|
12606
12792
|
return [];
|
|
@@ -12608,9 +12794,9 @@ function checkMissingCssVars(projectRoot) {
|
|
|
12608
12794
|
}
|
|
12609
12795
|
function patchGlobalsCss(projectRoot, missingVars) {
|
|
12610
12796
|
const globalsPath = join19(projectRoot, "app", "globals.css");
|
|
12611
|
-
if (!
|
|
12797
|
+
if (!existsSync26(globalsPath) || missingVars.length === 0) return;
|
|
12612
12798
|
const { writeFileSync: writeFileSync14 } = __require("fs");
|
|
12613
|
-
let content =
|
|
12799
|
+
let content = readFileSync18(globalsPath, "utf-8");
|
|
12614
12800
|
const defaultValues = {
|
|
12615
12801
|
"--chart-1": "220 70% 50%",
|
|
12616
12802
|
"--chart-2": "160 60% 45%",
|
|
@@ -12688,7 +12874,7 @@ async function undoCommand(options) {
|
|
|
12688
12874
|
// src/commands/sync.ts
|
|
12689
12875
|
import chalk31 from "chalk";
|
|
12690
12876
|
import ora9 from "ora";
|
|
12691
|
-
import { existsSync as
|
|
12877
|
+
import { existsSync as existsSync27, readFileSync as readFileSync19 } from "fs";
|
|
12692
12878
|
import { join as join20, relative as relative5, dirname as dirname10 } from "path";
|
|
12693
12879
|
import { readdir as readdir4, readFile as readFile7 } from "fs/promises";
|
|
12694
12880
|
import { DesignSystemManager as DesignSystemManager16 } from "@getcoherent/core";
|
|
@@ -12697,8 +12883,8 @@ function extractTokensFromProject(projectRoot) {
|
|
|
12697
12883
|
const lightColors = {};
|
|
12698
12884
|
const darkColors = {};
|
|
12699
12885
|
const globalsPath = join20(projectRoot, "app", "globals.css");
|
|
12700
|
-
if (
|
|
12701
|
-
const css =
|
|
12886
|
+
if (existsSync27(globalsPath)) {
|
|
12887
|
+
const css = readFileSync19(globalsPath, "utf-8");
|
|
12702
12888
|
const rootMatch = css.match(/:root\s*\{([^}]+)\}/s);
|
|
12703
12889
|
if (rootMatch) parseVarsInto(rootMatch[1], lightColors);
|
|
12704
12890
|
const darkMatch = css.match(/\.dark\s*\{([^}]+)\}/s);
|
|
@@ -12706,8 +12892,8 @@ function extractTokensFromProject(projectRoot) {
|
|
|
12706
12892
|
}
|
|
12707
12893
|
const layoutPath = join20(projectRoot, "app", "layout.tsx");
|
|
12708
12894
|
let layoutCode = "";
|
|
12709
|
-
if (
|
|
12710
|
-
layoutCode =
|
|
12895
|
+
if (existsSync27(layoutPath)) {
|
|
12896
|
+
layoutCode = readFileSync19(layoutPath, "utf-8");
|
|
12711
12897
|
const rootInline = layoutCode.match(/:root\s*\{([^}]+)\}/s);
|
|
12712
12898
|
if (rootInline && Object.keys(lightColors).length === 0) {
|
|
12713
12899
|
parseVarsInto(rootInline[1], lightColors);
|
|
@@ -12725,7 +12911,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
12725
12911
|
defaultMode = "dark";
|
|
12726
12912
|
}
|
|
12727
12913
|
let radius;
|
|
12728
|
-
const allCss = [
|
|
12914
|
+
const allCss = [existsSync27(globalsPath) ? readFileSync19(globalsPath, "utf-8") : "", layoutCode].join("\n");
|
|
12729
12915
|
const radiusMatch = allCss.match(/--radius:\s*([^;]+);/);
|
|
12730
12916
|
if (radiusMatch) radius = radiusMatch[1].trim();
|
|
12731
12917
|
return {
|
|
@@ -12749,7 +12935,7 @@ function parseVarsInto(block, target) {
|
|
|
12749
12935
|
async function detectCustomComponents(projectRoot, allPageCode) {
|
|
12750
12936
|
const results = [];
|
|
12751
12937
|
const componentsDir = join20(projectRoot, "components");
|
|
12752
|
-
if (!
|
|
12938
|
+
if (!existsSync27(componentsDir)) return results;
|
|
12753
12939
|
const files = [];
|
|
12754
12940
|
await walkForTsx(componentsDir, files, ["ui"]);
|
|
12755
12941
|
const fileResults = await Promise.all(
|
|
@@ -12928,7 +13114,7 @@ async function syncCommand(options = {}) {
|
|
|
12928
13114
|
const spinner = ora9("Scanning project files...").start();
|
|
12929
13115
|
try {
|
|
12930
13116
|
const appDir = join20(project.root, "app");
|
|
12931
|
-
if (!
|
|
13117
|
+
if (!existsSync27(appDir)) {
|
|
12932
13118
|
spinner.fail("No app/ directory found");
|
|
12933
13119
|
process.exit(1);
|
|
12934
13120
|
}
|
|
@@ -13155,7 +13341,7 @@ async function syncCommand(options = {}) {
|
|
|
13155
13341
|
// src/commands/migrate.ts
|
|
13156
13342
|
import chalk32 from "chalk";
|
|
13157
13343
|
import ora10 from "ora";
|
|
13158
|
-
import { existsSync as
|
|
13344
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync8, cpSync, rmSync as rmSync6, writeFileSync as writeFileSync12, readFileSync as readFileSync20, readdirSync as readdirSync10 } from "fs";
|
|
13159
13345
|
import { join as join21 } from "path";
|
|
13160
13346
|
function backupDir(projectRoot) {
|
|
13161
13347
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -13168,11 +13354,11 @@ function createBackup2(projectRoot) {
|
|
|
13168
13354
|
const uiDir = join21(projectRoot, "components", "ui");
|
|
13169
13355
|
const dest = backupDir(projectRoot);
|
|
13170
13356
|
mkdirSync8(dest, { recursive: true });
|
|
13171
|
-
if (
|
|
13357
|
+
if (existsSync28(uiDir)) {
|
|
13172
13358
|
cpSync(uiDir, join21(dest, "components-ui"), { recursive: true });
|
|
13173
13359
|
}
|
|
13174
13360
|
const configPath = join21(projectRoot, "design-system.config.ts");
|
|
13175
|
-
if (
|
|
13361
|
+
if (existsSync28(configPath)) {
|
|
13176
13362
|
cpSync(configPath, join21(dest, "design-system.config.ts"));
|
|
13177
13363
|
}
|
|
13178
13364
|
return dest;
|
|
@@ -13184,24 +13370,24 @@ function setGuard(projectRoot, backupPath) {
|
|
|
13184
13370
|
}
|
|
13185
13371
|
function clearGuard(projectRoot) {
|
|
13186
13372
|
const guard = guardPath(projectRoot);
|
|
13187
|
-
if (
|
|
13373
|
+
if (existsSync28(guard)) rmSync6(guard);
|
|
13188
13374
|
}
|
|
13189
13375
|
function rollback(projectRoot) {
|
|
13190
13376
|
const guard = guardPath(projectRoot);
|
|
13191
|
-
if (!
|
|
13377
|
+
if (!existsSync28(guard)) return false;
|
|
13192
13378
|
try {
|
|
13193
|
-
const data = JSON.parse(
|
|
13379
|
+
const data = JSON.parse(readFileSync20(guard, "utf-8"));
|
|
13194
13380
|
const backup = data.backup;
|
|
13195
|
-
if (!
|
|
13381
|
+
if (!existsSync28(backup)) return false;
|
|
13196
13382
|
const uiBackup = join21(backup, "components-ui");
|
|
13197
13383
|
const uiDir = join21(projectRoot, "components", "ui");
|
|
13198
|
-
if (
|
|
13199
|
-
if (
|
|
13384
|
+
if (existsSync28(uiBackup)) {
|
|
13385
|
+
if (existsSync28(uiDir)) rmSync6(uiDir, { recursive: true });
|
|
13200
13386
|
cpSync(uiBackup, uiDir, { recursive: true });
|
|
13201
13387
|
}
|
|
13202
13388
|
const configBackup = join21(backup, "design-system.config.ts");
|
|
13203
13389
|
const configDest = join21(projectRoot, "design-system.config.ts");
|
|
13204
|
-
if (
|
|
13390
|
+
if (existsSync28(configBackup)) {
|
|
13205
13391
|
cpSync(configBackup, configDest);
|
|
13206
13392
|
}
|
|
13207
13393
|
clearGuard(projectRoot);
|
|
@@ -13229,19 +13415,19 @@ async function migrateAction(options) {
|
|
|
13229
13415
|
return;
|
|
13230
13416
|
}
|
|
13231
13417
|
const guard = guardPath(projectRoot);
|
|
13232
|
-
if (
|
|
13418
|
+
if (existsSync28(guard)) {
|
|
13233
13419
|
console.log(chalk32.yellow("A migration is already in progress."));
|
|
13234
13420
|
console.log(chalk32.dim("Run `coherent migrate --rollback` to undo, or delete .coherent/migration-in-progress"));
|
|
13235
13421
|
return;
|
|
13236
13422
|
}
|
|
13237
13423
|
const uiDir = join21(projectRoot, "components", "ui");
|
|
13238
|
-
if (!
|
|
13424
|
+
if (!existsSync28(uiDir)) {
|
|
13239
13425
|
console.log(chalk32.yellow("No components/ui directory found. Nothing to migrate."));
|
|
13240
13426
|
return;
|
|
13241
13427
|
}
|
|
13242
13428
|
const provider = getComponentProvider();
|
|
13243
13429
|
const managedIds = new Set(provider.listNames());
|
|
13244
|
-
const files =
|
|
13430
|
+
const files = readdirSync10(uiDir).filter((f) => f.endsWith(".tsx"));
|
|
13245
13431
|
const migratable = files.map((f) => f.replace(".tsx", "")).filter((id) => managedIds.has(id));
|
|
13246
13432
|
if (migratable.length === 0) {
|
|
13247
13433
|
console.log(chalk32.green("All components are already up to date."));
|
|
@@ -13262,7 +13448,7 @@ Found ${migratable.length} component(s) to migrate:`));
|
|
|
13262
13448
|
try {
|
|
13263
13449
|
for (const id of migratable) {
|
|
13264
13450
|
const filePath = join21(uiDir, `${id}.tsx`);
|
|
13265
|
-
if (
|
|
13451
|
+
if (existsSync28(filePath)) rmSync6(filePath);
|
|
13266
13452
|
}
|
|
13267
13453
|
const results = await provider.installBatch(migratable, projectRoot, { force: true });
|
|
13268
13454
|
let migrated = 0;
|
|
@@ -13284,7 +13470,7 @@ Found ${migratable.length} component(s) to migrate:`));
|
|
|
13284
13470
|
}
|
|
13285
13471
|
|
|
13286
13472
|
// src/utils/update-notifier.ts
|
|
13287
|
-
import { existsSync as
|
|
13473
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync9, readFileSync as readFileSync21, writeFileSync as writeFileSync13 } from "fs";
|
|
13288
13474
|
import { join as join22 } from "path";
|
|
13289
13475
|
import { homedir } from "os";
|
|
13290
13476
|
import chalk33 from "chalk";
|
|
@@ -13296,8 +13482,8 @@ var CACHE_FILE = join22(CACHE_DIR, "update-check.json");
|
|
|
13296
13482
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
13297
13483
|
function readCache() {
|
|
13298
13484
|
try {
|
|
13299
|
-
if (!
|
|
13300
|
-
const raw =
|
|
13485
|
+
if (!existsSync29(CACHE_FILE)) return null;
|
|
13486
|
+
const raw = readFileSync21(CACHE_FILE, "utf-8");
|
|
13301
13487
|
return JSON.parse(raw);
|
|
13302
13488
|
} catch (e) {
|
|
13303
13489
|
if (DEBUG5) console.error("Failed to read update cache:", e);
|
|
@@ -13306,7 +13492,7 @@ function readCache() {
|
|
|
13306
13492
|
}
|
|
13307
13493
|
function writeCache(data) {
|
|
13308
13494
|
try {
|
|
13309
|
-
if (!
|
|
13495
|
+
if (!existsSync29(CACHE_DIR)) mkdirSync9(CACHE_DIR, { recursive: true });
|
|
13310
13496
|
writeFileSync13(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
13311
13497
|
} catch (e) {
|
|
13312
13498
|
if (DEBUG5) console.error("Failed to write update cache:", e);
|