@getcoherent/cli 0.6.49 → 0.6.50
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/README.md +6 -1
- package/dist/index.js +470 -428
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -101,7 +101,7 @@ import { CLI_VERSION as CLI_VERSION6 } from "@getcoherent/core";
|
|
|
101
101
|
import chalk3 from "chalk";
|
|
102
102
|
import ora from "ora";
|
|
103
103
|
import prompts2 from "prompts";
|
|
104
|
-
import { existsSync as
|
|
104
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, mkdirSync as mkdirSync3, rmSync, writeFileSync as writeFileSync4 } from "fs";
|
|
105
105
|
import { basename, join as join3 } from "path";
|
|
106
106
|
import { execSync } from "child_process";
|
|
107
107
|
import { ProjectScaffolder, ComponentGenerator } from "@getcoherent/core";
|
|
@@ -839,123 +839,14 @@ function hasApiKey() {
|
|
|
839
839
|
return !!(process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY);
|
|
840
840
|
}
|
|
841
841
|
|
|
842
|
-
// src/utils/
|
|
843
|
-
import { writeFileSync as writeFileSync3, existsSync as
|
|
842
|
+
// src/utils/harness-context.ts
|
|
843
|
+
import { writeFileSync as writeFileSync3, existsSync as existsSync2 } from "fs";
|
|
844
844
|
import { join as join2 } from "path";
|
|
845
|
-
import { loadManifest
|
|
846
|
-
import { DesignSystemManager as DesignSystemManager2 } from "@getcoherent/core";
|
|
845
|
+
import { loadManifest, DesignSystemManager } from "@getcoherent/core";
|
|
847
846
|
|
|
848
847
|
// src/utils/claude-code.ts
|
|
849
|
-
import { writeFileSync as writeFileSync2,
|
|
848
|
+
import { writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
850
849
|
import { join } from "path";
|
|
851
|
-
import { loadManifest } from "@getcoherent/core";
|
|
852
|
-
import { DesignSystemManager } from "@getcoherent/core";
|
|
853
|
-
function buildSharedComponentsListForClaude(manifest) {
|
|
854
|
-
if (!manifest.shared || manifest.shared.length === 0) {
|
|
855
|
-
return "No shared components yet. Register with: coherent components shared add <name> --type layout|navigation|data-display|form|feedback|section|widget";
|
|
856
|
-
}
|
|
857
|
-
const order = {
|
|
858
|
-
layout: 0,
|
|
859
|
-
navigation: 1,
|
|
860
|
-
"data-display": 2,
|
|
861
|
-
form: 3,
|
|
862
|
-
feedback: 4,
|
|
863
|
-
section: 5,
|
|
864
|
-
widget: 6
|
|
865
|
-
};
|
|
866
|
-
const sorted = [...manifest.shared].sort(
|
|
867
|
-
(a, b) => (order[a.type] ?? 9) - (order[b.type] ?? 9) || a.name.localeCompare(b.name)
|
|
868
|
-
);
|
|
869
|
-
return sorted.map((e) => {
|
|
870
|
-
const used = e.usedIn.length === 0 ? "\u2014" : e.usedIn.length === 1 && e.usedIn[0] === "app/layout.tsx" ? "layout" : e.usedIn.length + " files";
|
|
871
|
-
return `- ${e.id} ${e.name} (${e.type}) \u2014 ${e.file} \u2014 ${used}`;
|
|
872
|
-
}).join("\n");
|
|
873
|
-
}
|
|
874
|
-
function buildClaudeMdContent(manifest, _config) {
|
|
875
|
-
const sharedList = buildSharedComponentsListForClaude(manifest);
|
|
876
|
-
return `# Coherent Design Method Project
|
|
877
|
-
|
|
878
|
-
This is a Coherent Design Method project \u2014 AI-powered multi-page UI prototype with shared component system.
|
|
879
|
-
|
|
880
|
-
## Architecture
|
|
881
|
-
|
|
882
|
-
- app/ \u2014 Next.js App Router pages
|
|
883
|
-
- components/ui/ \u2014 base shadcn/ui components (Button, Card, Input, etc.)
|
|
884
|
-
- components/shared/ \u2014 shared reusable blocks with unique IDs (CID-XXX)
|
|
885
|
-
- app/design-system/ \u2014 platform overlay (DO NOT MODIFY)
|
|
886
|
-
- coherent.components.json \u2014 shared component manifest
|
|
887
|
-
- design-system.config.ts \u2014 design tokens and page definitions
|
|
888
|
-
|
|
889
|
-
## Shared Components (MUST REUSE)
|
|
890
|
-
|
|
891
|
-
${sharedList}
|
|
892
|
-
|
|
893
|
-
Before creating ANY UI block, check if a shared component exists above. If yes \u2014 IMPORT it. NEVER recreate inline.
|
|
894
|
-
|
|
895
|
-
## Rules
|
|
896
|
-
|
|
897
|
-
- ONLY use @/components/ui/* \u2014 never native <button>, <select>, <input type="checkbox">, <table>
|
|
898
|
-
- Icons: lucide-react only
|
|
899
|
-
- Forms: Label above Input, Switch for toggles, Select for 3+ options
|
|
900
|
-
- Colors: semantic tokens only (bg-background, text-foreground, bg-primary) \u2014 never hardcoded
|
|
901
|
-
- Links: text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground transition-colors (ALL links same style per page)
|
|
902
|
-
- ALL interactive elements MUST have hover: and focus-visible: states \u2014 no exceptions
|
|
903
|
-
- Identical components must look identical everywhere
|
|
904
|
-
- Badge: default=success, secondary=neutral, outline=warning, destructive=error (same status = same variant everywhere)
|
|
905
|
-
- Avatar: size-8 in lists, size-10 in profiles, always rounded-full
|
|
906
|
-
- Dialog: max-w-sm for confirms, max-w-md for forms. Footer: cancel left, action right
|
|
907
|
-
- Tabs: shadcn Tabs. TabsContent space-y-4. TabsList w-full md:w-auto
|
|
908
|
-
- Alert: <Alert> for info, variant="destructive" for errors. Never for success (use toast)
|
|
909
|
-
- Sections: h2 text-lg font-semibold. Separator between sections, border-b between list items
|
|
910
|
-
- Nav active: sidebar=bg-accent font-medium, top nav=text-foreground font-medium
|
|
911
|
-
- Toast: use shadcn toast, not browser alert(). Success=description only 3-5s, error=variant="destructive" persistent
|
|
912
|
-
- Table rows: hover:bg-muted/50, actions via DropdownMenu (MoreHorizontal), wrap in overflow-x-auto
|
|
913
|
-
- Button order: secondary first, primary LAST. Icon+text: icon mr-2 size-4 before text
|
|
914
|
-
- Skeleton: h-4 animate-pulse rounded-md bg-muted (match content shape). Button loading: Loader2 animate-spin
|
|
915
|
-
- Sheet for filters/mobile nav/previews. Dialog for confirmations/blocking. Sheet default side: right
|
|
916
|
-
- Breadcrumb on pages 2+ levels deep. Current page=text-foreground, parents=muted-foreground
|
|
917
|
-
- Code blocks: inline=rounded bg-muted px-1.5 py-0.5 font-mono text-sm. Block=rounded-md bg-muted px-4 py-3
|
|
918
|
-
- Pagination: shadcn Pagination, centered below list. Feeds: "Load more" button instead
|
|
919
|
-
- Card actions: DropdownMenu in CardHeader top-right OR actions in CardFooter, never both
|
|
920
|
-
- Search: relative + Search icon absolute left-3, Input pl-9, debounce 300ms
|
|
921
|
-
- Accordion: shadcn, type="single" for FAQ, "multiple" for settings
|
|
922
|
-
- Popover=small forms, DropdownMenu=action lists, Dialog=complex/blocking
|
|
923
|
-
- ScrollArea=fixed containers, native overflow-y-auto=dynamic content
|
|
924
|
-
- Empty states: centered icon (size-12) + title + description + CTA. Search/filter variants
|
|
925
|
-
- Stat cards: CardTitle text-sm font-medium, metric text-2xl font-bold, trend text-emerald-600/text-destructive
|
|
926
|
-
- Error pages: centered 404/500 + "Go home" CTA. Never dead-end the user
|
|
927
|
-
- Confirmation: title=action-specific, description=consequences, Cancel(outline)+Destructive
|
|
928
|
-
- Multi-step: numbered circles (bg-primary active, bg-muted upcoming), Back/Next buttons
|
|
929
|
-
- Form validation: text-sm text-destructive below input, border-destructive on input. NEVER toast
|
|
930
|
-
- File upload: border-2 border-dashed + Upload icon + "Drag and drop or browse"
|
|
931
|
-
- RadioGroup: 2-3 options=Radio, 4+=Select. Vertical default
|
|
932
|
-
- Notification dot: absolute -top-1 -right-1 size-2 bg-destructive. Count: max "9+"
|
|
933
|
-
- Progress: shadcn Progress h-2. For uploads/quotas. NEVER for page loads
|
|
934
|
-
- Data format: dates=relative recent, absolute older. Numbers=toLocaleString. Currency=$1,234.56
|
|
935
|
-
- Status dots: size-2 rounded-full (emerald=active, destructive=error, yellow=warning)
|
|
936
|
-
- Timeline: vertical dot+line+event. Avatar group: flex -space-x-2 ring-2 ring-background
|
|
937
|
-
- Sidebar: w-64 desktop, Sheet from left mobile. Settings: two-col nav+cards
|
|
938
|
-
- Pricing: grid md:grid-cols-3, highlighted=ring-2 ring-primary, Popular badge
|
|
939
|
-
- Hero: centered py-16 md:py-24, h1 text-3xl md:text-5xl, CTA size="lg"
|
|
940
|
-
- Command palette: shadcn Command, \u2318K trigger. Toggle/ToggleGroup for view switchers
|
|
941
|
-
- Copy: ghost sm button, swap Copy\u2192Check icon 2s. Z-index: content=0, dropdown=50, toast=100
|
|
942
|
-
- Animation: transition-colors for hovers, 150ms. NEVER page load animations
|
|
943
|
-
- Accessibility: WCAG 2.2 AA, contrast \u2265 4.5:1, touch targets \u2265 44px, focus-visible on all interactive
|
|
944
|
-
- Auth pages (login, signup, etc.) go in app/(auth)/ route group \u2014 no Header/Footer
|
|
945
|
-
|
|
946
|
-
## After Making Changes
|
|
947
|
-
|
|
948
|
-
Run after changes:
|
|
949
|
-
- coherent check \u2014 show all quality and consistency issues (read-only)
|
|
950
|
-
- coherent fix \u2014 auto-fix cache, deps, syntax, and style issues
|
|
951
|
-
|
|
952
|
-
## Do NOT modify
|
|
953
|
-
|
|
954
|
-
- app/design-system/* \u2014 platform overlay
|
|
955
|
-
- app/api/design-system/* \u2014 platform API routes
|
|
956
|
-
- coherent.components.json \u2014 managed by platform
|
|
957
|
-
`;
|
|
958
|
-
}
|
|
959
850
|
function ensureDir(dir) {
|
|
960
851
|
try {
|
|
961
852
|
mkdirSync2(dir, { recursive: true });
|
|
@@ -1290,11 +1181,6 @@ var SETTINGS_JSON = `{
|
|
|
1290
1181
|
}
|
|
1291
1182
|
}
|
|
1292
1183
|
`;
|
|
1293
|
-
function writeClaudeMd(projectRoot, manifest, config2) {
|
|
1294
|
-
const content = buildClaudeMdContent(manifest, config2);
|
|
1295
|
-
const outPath = join(projectRoot, "CLAUDE.md");
|
|
1296
|
-
writeFileSync2(outPath, content, "utf-8");
|
|
1297
|
-
}
|
|
1298
1184
|
function writeClaudeCommands(projectRoot) {
|
|
1299
1185
|
const dir = join(projectRoot, ".claude", "commands");
|
|
1300
1186
|
ensureDir(dir);
|
|
@@ -1315,34 +1201,8 @@ function writeClaudeSettings(projectRoot) {
|
|
|
1315
1201
|
ensureDir(dir);
|
|
1316
1202
|
writeFileSync2(join(dir, "settings.json"), SETTINGS_JSON.trim(), "utf-8");
|
|
1317
1203
|
}
|
|
1318
|
-
async function loadManifestAndConfig(projectRoot) {
|
|
1319
|
-
let manifest;
|
|
1320
|
-
try {
|
|
1321
|
-
manifest = await loadManifest(projectRoot);
|
|
1322
|
-
} catch {
|
|
1323
|
-
manifest = { shared: [], nextId: 1 };
|
|
1324
|
-
}
|
|
1325
|
-
let config2 = null;
|
|
1326
|
-
const configPath = join(projectRoot, "design-system.config.ts");
|
|
1327
|
-
if (existsSync2(configPath)) {
|
|
1328
|
-
try {
|
|
1329
|
-
const dsm = new DesignSystemManager(configPath);
|
|
1330
|
-
await dsm.load();
|
|
1331
|
-
config2 = dsm.getConfig();
|
|
1332
|
-
} catch {
|
|
1333
|
-
}
|
|
1334
|
-
}
|
|
1335
|
-
return { manifest, config: config2 };
|
|
1336
|
-
}
|
|
1337
|
-
async function generateClaudeCodeFiles(projectRoot) {
|
|
1338
|
-
const { manifest, config: config2 } = await loadManifestAndConfig(projectRoot);
|
|
1339
|
-
writeClaudeMd(projectRoot, manifest, config2);
|
|
1340
|
-
writeClaudeCommands(projectRoot);
|
|
1341
|
-
writeClaudeSkills(projectRoot);
|
|
1342
|
-
writeClaudeSettings(projectRoot);
|
|
1343
|
-
}
|
|
1344
1204
|
|
|
1345
|
-
// src/utils/
|
|
1205
|
+
// src/utils/harness-context.ts
|
|
1346
1206
|
function buildSharedComponentsList(manifest) {
|
|
1347
1207
|
if (!manifest.shared || manifest.shared.length === 0) {
|
|
1348
1208
|
return `No shared components registered yet.
|
|
@@ -1377,6 +1237,27 @@ register them: coherent components shared add <Name> --type layout|navigation|da
|
|
|
1377
1237
|
|
|
1378
1238
|
${lines.join("\n\n")}`;
|
|
1379
1239
|
}
|
|
1240
|
+
function buildSharedComponentsListCompact(manifest) {
|
|
1241
|
+
if (!manifest.shared || manifest.shared.length === 0) {
|
|
1242
|
+
return "No shared components yet. Register with: coherent components shared add <name> --type layout|navigation|data-display|form|feedback|section|widget";
|
|
1243
|
+
}
|
|
1244
|
+
const order = {
|
|
1245
|
+
layout: 0,
|
|
1246
|
+
navigation: 1,
|
|
1247
|
+
"data-display": 2,
|
|
1248
|
+
form: 3,
|
|
1249
|
+
feedback: 4,
|
|
1250
|
+
section: 5,
|
|
1251
|
+
widget: 6
|
|
1252
|
+
};
|
|
1253
|
+
const sorted = [...manifest.shared].sort(
|
|
1254
|
+
(a, b) => (order[a.type] ?? 9) - (order[b.type] ?? 9) || a.name.localeCompare(b.name)
|
|
1255
|
+
);
|
|
1256
|
+
return sorted.map((e) => {
|
|
1257
|
+
const used = e.usedIn.length === 0 ? "\u2014" : e.usedIn.length === 1 && e.usedIn[0] === "app/layout.tsx" ? "layout" : e.usedIn.length + " files";
|
|
1258
|
+
return `- ${e.id} ${e.name} (${e.type}) \u2014 ${e.file} \u2014 ${used}`;
|
|
1259
|
+
}).join("\n");
|
|
1260
|
+
}
|
|
1380
1261
|
function buildDesignTokensSummary(config2) {
|
|
1381
1262
|
if (!config2?.tokens) {
|
|
1382
1263
|
return `Design tokens are defined in design-system.config.ts and globals.css.
|
|
@@ -1403,14 +1284,7 @@ Use semantic classes: bg-background, text-foreground, bg-primary, text-muted-for
|
|
|
1403
1284
|
return `Current design tokens:
|
|
1404
1285
|
${lines.join("\n")}`;
|
|
1405
1286
|
}
|
|
1406
|
-
|
|
1407
|
-
const sharedList = buildSharedComponentsList(manifest);
|
|
1408
|
-
const tokensSummary = buildDesignTokensSummary(config2);
|
|
1409
|
-
return `# Coherent Design Method \u2014 Project Rules
|
|
1410
|
-
# Auto-generated. Updated when shared components or config change.
|
|
1411
|
-
# Do NOT edit manually \u2014 run \`coherent rules\` to regenerate.
|
|
1412
|
-
|
|
1413
|
-
## Project Architecture
|
|
1287
|
+
var ARCHITECTURE_DETAILED = `## Project Architecture
|
|
1414
1288
|
|
|
1415
1289
|
This is a Coherent Design Method project. It uses Next.js + Tailwind + a built-in component library.
|
|
1416
1290
|
|
|
@@ -1424,21 +1298,16 @@ This is a Coherent Design Method project. It uses Next.js + Tailwind + a built-i
|
|
|
1424
1298
|
### Config files
|
|
1425
1299
|
- design-system.config.ts \u2014 design tokens, pages, navigation
|
|
1426
1300
|
- coherent.components.json \u2014 shared component manifest
|
|
1427
|
-
- globals.css \u2014 CSS variables (colors, typography, spacing)
|
|
1428
|
-
|
|
1429
|
-
## Shared Components (MUST REUSE)
|
|
1430
|
-
|
|
1431
|
-
Before creating ANY UI block, check if a matching shared component exists below.
|
|
1432
|
-
If it does \u2014 IMPORT and USE it. NEVER recreate inline.
|
|
1433
|
-
|
|
1434
|
-
${sharedList}
|
|
1301
|
+
- globals.css \u2014 CSS variables (colors, typography, spacing)`;
|
|
1302
|
+
var ARCHITECTURE_COMPACT = `## Architecture
|
|
1435
1303
|
|
|
1436
|
-
|
|
1437
|
-
-
|
|
1438
|
-
-
|
|
1439
|
-
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1304
|
+
- app/ \u2014 Next.js App Router pages
|
|
1305
|
+
- components/ui/ \u2014 base shadcn/ui components (Button, Card, Input, etc.)
|
|
1306
|
+
- components/shared/ \u2014 shared reusable blocks with unique IDs (CID-XXX)
|
|
1307
|
+
- app/design-system/ \u2014 platform overlay (DO NOT MODIFY)
|
|
1308
|
+
- coherent.components.json \u2014 shared component manifest
|
|
1309
|
+
- design-system.config.ts \u2014 design tokens and page definitions`;
|
|
1310
|
+
var RULES_DETAILED = `## Component Rules (MANDATORY)
|
|
1442
1311
|
|
|
1443
1312
|
- ONLY use components from @/components/ui/* \u2014 never native HTML elements
|
|
1444
1313
|
- NEVER use native <button> \u2014 use: import { Button } from "@/components/ui/button"
|
|
@@ -1634,9 +1503,58 @@ When using a shared component:
|
|
|
1634
1503
|
- Content: z-0. Sticky: z-10. Dropdowns: z-50. Toast: z-[100]. Never arbitrary above z-50.
|
|
1635
1504
|
|
|
1636
1505
|
### Animation
|
|
1637
|
-
- transition-colors for hovers. 150ms default. NEVER animate on page load or decorative
|
|
1506
|
+
- transition-colors for hovers. 150ms default. NEVER animate on page load or decorative.`;
|
|
1507
|
+
var RULES_COMPACT = `## Rules
|
|
1638
1508
|
|
|
1639
|
-
|
|
1509
|
+
- ONLY use @/components/ui/* \u2014 never native <button>, <select>, <input type="checkbox">, <table>
|
|
1510
|
+
- Icons: lucide-react only
|
|
1511
|
+
- Forms: Label above Input, Switch for toggles, Select for 3+ options
|
|
1512
|
+
- Colors: semantic tokens only (bg-background, text-foreground, bg-primary) \u2014 never hardcoded
|
|
1513
|
+
- Links: text-sm text-muted-foreground underline underline-offset-4 hover:text-foreground transition-colors (ALL links same style per page)
|
|
1514
|
+
- ALL interactive elements MUST have hover: and focus-visible: states \u2014 no exceptions
|
|
1515
|
+
- Identical components must look identical everywhere
|
|
1516
|
+
- Badge: default=success, secondary=neutral, outline=warning, destructive=error (same status = same variant everywhere)
|
|
1517
|
+
- Avatar: size-8 in lists, size-10 in profiles, always rounded-full
|
|
1518
|
+
- Dialog: max-w-sm for confirms, max-w-md for forms. Footer: cancel left, action right
|
|
1519
|
+
- Tabs: shadcn Tabs. TabsContent space-y-4. TabsList w-full md:w-auto
|
|
1520
|
+
- Alert: <Alert> for info, variant="destructive" for errors. Never for success (use toast)
|
|
1521
|
+
- Sections: h2 text-lg font-semibold. Separator between sections, border-b between list items
|
|
1522
|
+
- Nav active: sidebar=bg-accent font-medium, top nav=text-foreground font-medium
|
|
1523
|
+
- Toast: use shadcn toast, not browser alert(). Success=description only 3-5s, error=variant="destructive" persistent
|
|
1524
|
+
- Table rows: hover:bg-muted/50, actions via DropdownMenu (MoreHorizontal), wrap in overflow-x-auto
|
|
1525
|
+
- Button order: secondary first, primary LAST. Icon+text: icon mr-2 size-4 before text
|
|
1526
|
+
- Skeleton: h-4 animate-pulse rounded-md bg-muted (match content shape). Button loading: Loader2 animate-spin
|
|
1527
|
+
- Sheet for filters/mobile nav/previews. Dialog for confirmations/blocking. Sheet default side: right
|
|
1528
|
+
- Breadcrumb on pages 2+ levels deep. Current page=text-foreground, parents=muted-foreground
|
|
1529
|
+
- Code blocks: inline=rounded bg-muted px-1.5 py-0.5 font-mono text-sm. Block=rounded-md bg-muted px-4 py-3
|
|
1530
|
+
- Pagination: shadcn Pagination, centered below list. Feeds: "Load more" button instead
|
|
1531
|
+
- Card actions: DropdownMenu in CardHeader top-right OR actions in CardFooter, never both
|
|
1532
|
+
- Search: relative + Search icon absolute left-3, Input pl-9, debounce 300ms
|
|
1533
|
+
- Accordion: shadcn, type="single" for FAQ, "multiple" for settings
|
|
1534
|
+
- Popover=small forms, DropdownMenu=action lists, Dialog=complex/blocking
|
|
1535
|
+
- ScrollArea=fixed containers, native overflow-y-auto=dynamic content
|
|
1536
|
+
- Empty states: centered icon (size-12) + title + description + CTA. Search/filter variants
|
|
1537
|
+
- Stat cards: CardTitle text-sm font-medium, metric text-2xl font-bold, trend text-emerald-600/text-destructive
|
|
1538
|
+
- Error pages: centered 404/500 + "Go home" CTA. Never dead-end the user
|
|
1539
|
+
- Confirmation: title=action-specific, description=consequences, Cancel(outline)+Destructive
|
|
1540
|
+
- Multi-step: numbered circles (bg-primary active, bg-muted upcoming), Back/Next buttons
|
|
1541
|
+
- Form validation: text-sm text-destructive below input, border-destructive on input. NEVER toast
|
|
1542
|
+
- File upload: border-2 border-dashed + Upload icon + "Drag and drop or browse"
|
|
1543
|
+
- RadioGroup: 2-3 options=Radio, 4+=Select. Vertical default
|
|
1544
|
+
- Notification dot: absolute -top-1 -right-1 size-2 bg-destructive. Count: max "9+"
|
|
1545
|
+
- Progress: shadcn Progress h-2. For uploads/quotas. NEVER for page loads
|
|
1546
|
+
- Data format: dates=relative recent, absolute older. Numbers=toLocaleString. Currency=$1,234.56
|
|
1547
|
+
- Status dots: size-2 rounded-full (emerald=active, destructive=error, yellow=warning)
|
|
1548
|
+
- Timeline: vertical dot+line+event. Avatar group: flex -space-x-2 ring-2 ring-background
|
|
1549
|
+
- Sidebar: w-64 desktop, Sheet from left mobile. Settings: two-col nav+cards
|
|
1550
|
+
- Pricing: grid md:grid-cols-3, highlighted=ring-2 ring-primary, Popular badge
|
|
1551
|
+
- Hero: centered py-16 md:py-24, h1 text-3xl md:text-5xl, CTA size="lg"
|
|
1552
|
+
- Command palette: shadcn Command, \u2318K trigger. Toggle/ToggleGroup for view switchers
|
|
1553
|
+
- Copy: ghost sm button, swap Copy\u2192Check icon 2s. Z-index: content=0, dropdown=50, toast=100
|
|
1554
|
+
- Animation: transition-colors for hovers, 150ms. NEVER page load animations
|
|
1555
|
+
- Accessibility: WCAG 2.2 AA, contrast \u2265 4.5:1, touch targets \u2265 44px, focus-visible on all interactive
|
|
1556
|
+
- Auth pages (login, signup, etc.) go in app/(auth)/ route group \u2014 no Header/Footer`;
|
|
1557
|
+
var DESIGN_QUALITY2 = `## Design Quality Standards
|
|
1640
1558
|
|
|
1641
1559
|
- Headlines: text-4xl+ font-bold tracking-tight for page titles, text-5xl+ for heroes, text-2xl+ for sections
|
|
1642
1560
|
- Cards: rounded-xl with border-border/15, ALWAYS hover state: hover:border-border/30 transition-colors
|
|
@@ -1646,45 +1564,33 @@ When using a shared component:
|
|
|
1646
1564
|
- One accent color per page \u2014 never mix blue + purple + emerald
|
|
1647
1565
|
- Hero: min-h-[80vh] centered, gradient text on key phrase (from-white to-zinc-500 bg-clip-text text-transparent)
|
|
1648
1566
|
- Comparison sections: red-400 X for negative, emerald-400 Check for positive
|
|
1649
|
-
- Footer: minimal, border-t border-border/10, py-10, text-sm text-muted-foreground
|
|
1650
|
-
|
|
1651
|
-
## Design Tokens
|
|
1652
|
-
|
|
1653
|
-
${tokensSummary}
|
|
1654
|
-
|
|
1655
|
-
Use semantic token classes (bg-background, text-foreground, bg-primary, etc.).
|
|
1656
|
-
NEVER hardcode colors. NEVER use arbitrary Tailwind values like bg-[#123456].
|
|
1657
|
-
|
|
1658
|
-
## Form Layout Rules
|
|
1567
|
+
- Footer: minimal, border-t border-border/10, py-10, text-sm text-muted-foreground`;
|
|
1568
|
+
var FORMS = `## Form Layout Rules
|
|
1659
1569
|
|
|
1660
1570
|
- Label above Input (never beside on mobile)
|
|
1661
1571
|
- space-y-2 within field group (label + input + description)
|
|
1662
1572
|
- space-y-6 between field groups
|
|
1663
1573
|
- Switch for boolean toggles, not Checkbox
|
|
1664
1574
|
- Select for 3+ options, Radio for 2-3 options
|
|
1665
|
-
- CardFooter for form actions (Save, Cancel)
|
|
1666
|
-
|
|
1667
|
-
## Accessibility (WCAG 2.2 AA)
|
|
1575
|
+
- CardFooter for form actions (Save, Cancel)`;
|
|
1576
|
+
var ACCESSIBILITY = `## Accessibility (WCAG 2.2 AA)
|
|
1668
1577
|
|
|
1669
1578
|
- Text contrast \u2265 4.5:1, UI component contrast \u2265 3:1
|
|
1670
1579
|
- Touch targets \u2265 44\xD744px (min-h-11 min-w-11)
|
|
1671
1580
|
- Focus visible on ALL interactive elements (focus-visible:ring-2)
|
|
1672
1581
|
- Color never the only indicator
|
|
1673
1582
|
- Every form input must have a Label with htmlFor
|
|
1674
|
-
- Heading hierarchy: one h1 per page, no skipped levels
|
|
1675
|
-
|
|
1676
|
-
## Auth Pages
|
|
1583
|
+
- Heading hierarchy: one h1 per page, no skipped levels`;
|
|
1584
|
+
var AUTH = `## Auth Pages
|
|
1677
1585
|
|
|
1678
1586
|
Login, signup, register, forgot-password \u2192 placed in app/(auth)/ route group.
|
|
1679
|
-
These pages do NOT show Header/Footer
|
|
1680
|
-
|
|
1681
|
-
## After Making Changes
|
|
1587
|
+
These pages do NOT show Header/Footer.`;
|
|
1588
|
+
var COMMANDS_SECTION = `## After Making Changes
|
|
1682
1589
|
|
|
1683
1590
|
Run in terminal:
|
|
1684
1591
|
- coherent check \u2014 show all quality and consistency issues (read-only)
|
|
1685
|
-
- coherent fix \u2014 auto-fix cache, deps, syntax, and style issues
|
|
1686
|
-
|
|
1687
|
-
## Platform Overlay (DO NOT TOUCH)
|
|
1592
|
+
- coherent fix \u2014 auto-fix cache, deps, syntax, and style issues`;
|
|
1593
|
+
var PLATFORM = `## Platform Overlay (DO NOT TOUCH)
|
|
1688
1594
|
|
|
1689
1595
|
The following are dev-only platform features, excluded from production export:
|
|
1690
1596
|
- Floating Design System button (in shared Header component)
|
|
@@ -1692,30 +1598,166 @@ The following are dev-only platform features, excluded from production export:
|
|
|
1692
1598
|
- /api/design-system/* routes
|
|
1693
1599
|
- coherent.components.json
|
|
1694
1600
|
|
|
1695
|
-
DO NOT modify or delete these. They are managed by the platform
|
|
1601
|
+
DO NOT modify or delete these. They are managed by the platform.`;
|
|
1602
|
+
var COMMANDS_COMPACT = `## After Making Changes
|
|
1603
|
+
|
|
1604
|
+
Run after changes:
|
|
1605
|
+
- coherent check \u2014 show all quality and consistency issues (read-only)
|
|
1606
|
+
- coherent fix \u2014 auto-fix cache, deps, syntax, and style issues`;
|
|
1607
|
+
var PLATFORM_COMPACT = `## Do NOT modify
|
|
1608
|
+
|
|
1609
|
+
- app/design-system/* \u2014 platform overlay
|
|
1610
|
+
- app/api/design-system/* \u2014 platform API routes
|
|
1611
|
+
- coherent.components.json \u2014 managed by platform`;
|
|
1612
|
+
function buildProjectContext(manifest, config2) {
|
|
1613
|
+
return {
|
|
1614
|
+
sharedComponents: buildSharedComponentsList(manifest),
|
|
1615
|
+
sharedComponentsCompact: buildSharedComponentsListCompact(manifest),
|
|
1616
|
+
designTokens: buildDesignTokensSummary(config2),
|
|
1617
|
+
architectureDetailed: ARCHITECTURE_DETAILED,
|
|
1618
|
+
architectureCompact: ARCHITECTURE_COMPACT,
|
|
1619
|
+
rulesDetailed: RULES_DETAILED,
|
|
1620
|
+
rulesCompact: RULES_COMPACT,
|
|
1621
|
+
designQuality: DESIGN_QUALITY2,
|
|
1622
|
+
forms: FORMS,
|
|
1623
|
+
accessibility: ACCESSIBILITY,
|
|
1624
|
+
auth: AUTH,
|
|
1625
|
+
commands: COMMANDS_SECTION,
|
|
1626
|
+
platform: PLATFORM
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
function formatForCursor(ctx) {
|
|
1630
|
+
return `# Coherent Design Method \u2014 Project Rules
|
|
1631
|
+
# Auto-generated. Updated when shared components or config change.
|
|
1632
|
+
# Do NOT edit manually \u2014 run \`coherent rules\` to regenerate.
|
|
1633
|
+
|
|
1634
|
+
${ctx.architectureDetailed}
|
|
1635
|
+
|
|
1636
|
+
## Shared Components (MUST REUSE)
|
|
1637
|
+
|
|
1638
|
+
Before creating ANY UI block, check if a matching shared component exists below.
|
|
1639
|
+
If it does \u2014 IMPORT and USE it. NEVER recreate inline.
|
|
1640
|
+
|
|
1641
|
+
${ctx.sharedComponents}
|
|
1642
|
+
|
|
1643
|
+
When using a shared component:
|
|
1644
|
+
- Import from @/components/shared/{filename}
|
|
1645
|
+
- If you need it with different props, use its props interface
|
|
1646
|
+
- If the component doesn't accept the prop you need, add the prop to the shared component and update all existing usages
|
|
1647
|
+
|
|
1648
|
+
${ctx.rulesDetailed}
|
|
1649
|
+
|
|
1650
|
+
${ctx.designQuality}
|
|
1651
|
+
|
|
1652
|
+
## Design Tokens
|
|
1653
|
+
|
|
1654
|
+
${ctx.designTokens}
|
|
1655
|
+
|
|
1656
|
+
Use semantic token classes (bg-background, text-foreground, bg-primary, etc.).
|
|
1657
|
+
NEVER hardcode colors. NEVER use arbitrary Tailwind values like bg-[#123456].
|
|
1658
|
+
|
|
1659
|
+
${ctx.forms}
|
|
1660
|
+
|
|
1661
|
+
${ctx.accessibility}
|
|
1662
|
+
|
|
1663
|
+
${ctx.auth}
|
|
1664
|
+
|
|
1665
|
+
${ctx.commands}
|
|
1666
|
+
|
|
1667
|
+
${ctx.platform}
|
|
1668
|
+
`;
|
|
1669
|
+
}
|
|
1670
|
+
function formatForClaude(ctx) {
|
|
1671
|
+
return `# Coherent Design Method Project
|
|
1672
|
+
|
|
1673
|
+
This is a Coherent Design Method project \u2014 AI-powered multi-page UI prototype with shared component system.
|
|
1674
|
+
|
|
1675
|
+
${ctx.architectureCompact}
|
|
1676
|
+
|
|
1677
|
+
## Shared Components (MUST REUSE)
|
|
1678
|
+
|
|
1679
|
+
${ctx.sharedComponentsCompact}
|
|
1680
|
+
|
|
1681
|
+
Before creating ANY UI block, check if a shared component exists above. If yes \u2014 IMPORT it. NEVER recreate inline.
|
|
1682
|
+
|
|
1683
|
+
${ctx.rulesCompact}
|
|
1684
|
+
|
|
1685
|
+
## Design Tokens
|
|
1686
|
+
|
|
1687
|
+
${ctx.designTokens}
|
|
1688
|
+
|
|
1689
|
+
Use semantic token classes (bg-background, text-foreground, bg-primary, etc.).
|
|
1690
|
+
NEVER hardcode colors. NEVER use arbitrary Tailwind values like bg-[#123456].
|
|
1691
|
+
|
|
1692
|
+
${COMMANDS_COMPACT}
|
|
1693
|
+
|
|
1694
|
+
${PLATFORM_COMPACT}
|
|
1696
1695
|
`;
|
|
1697
1696
|
}
|
|
1698
|
-
|
|
1697
|
+
function formatForAgents(ctx) {
|
|
1698
|
+
return `# Project Conventions
|
|
1699
|
+
# Auto-generated by Coherent. Run \`coherent rules\` to regenerate.
|
|
1700
|
+
|
|
1701
|
+
${ctx.architectureDetailed}
|
|
1702
|
+
|
|
1703
|
+
## Shared Components (MUST REUSE)
|
|
1704
|
+
|
|
1705
|
+
Before creating ANY UI block, check if a matching shared component exists below.
|
|
1706
|
+
If it does \u2014 IMPORT and USE it. NEVER recreate inline.
|
|
1707
|
+
|
|
1708
|
+
${ctx.sharedComponents}
|
|
1709
|
+
|
|
1710
|
+
When using a shared component:
|
|
1711
|
+
- Import from @/components/shared/{filename}
|
|
1712
|
+
- If you need it with different props, use its props interface
|
|
1713
|
+
- If the component doesn't accept the prop you need, add the prop to the shared component and update all existing usages
|
|
1714
|
+
|
|
1715
|
+
${ctx.rulesDetailed}
|
|
1716
|
+
|
|
1717
|
+
${ctx.designQuality}
|
|
1718
|
+
|
|
1719
|
+
## Design Tokens
|
|
1720
|
+
|
|
1721
|
+
${ctx.designTokens}
|
|
1722
|
+
|
|
1723
|
+
Use semantic token classes (bg-background, text-foreground, bg-primary, etc.).
|
|
1724
|
+
NEVER hardcode colors. NEVER use arbitrary Tailwind values like bg-[#123456].
|
|
1725
|
+
|
|
1726
|
+
${ctx.forms}
|
|
1727
|
+
|
|
1728
|
+
${ctx.accessibility}
|
|
1729
|
+
|
|
1730
|
+
${ctx.auth}
|
|
1731
|
+
|
|
1732
|
+
${ctx.commands}
|
|
1733
|
+
|
|
1734
|
+
${ctx.platform}
|
|
1735
|
+
`;
|
|
1736
|
+
}
|
|
1737
|
+
async function writeAllHarnessFiles(projectRoot) {
|
|
1699
1738
|
let manifest;
|
|
1700
1739
|
try {
|
|
1701
|
-
manifest = await
|
|
1740
|
+
manifest = await loadManifest(projectRoot);
|
|
1702
1741
|
} catch {
|
|
1703
1742
|
manifest = { shared: [], nextId: 1 };
|
|
1704
1743
|
}
|
|
1705
1744
|
let config2 = null;
|
|
1706
1745
|
const configPath = join2(projectRoot, "design-system.config.ts");
|
|
1707
|
-
if (
|
|
1746
|
+
if (existsSync2(configPath)) {
|
|
1708
1747
|
try {
|
|
1709
|
-
const dsm = new
|
|
1748
|
+
const dsm = new DesignSystemManager(configPath);
|
|
1710
1749
|
await dsm.load();
|
|
1711
1750
|
config2 = dsm.getConfig();
|
|
1712
1751
|
} catch {
|
|
1713
1752
|
}
|
|
1714
1753
|
}
|
|
1715
|
-
const
|
|
1716
|
-
|
|
1717
|
-
writeFileSync3(
|
|
1718
|
-
|
|
1754
|
+
const ctx = buildProjectContext(manifest, config2);
|
|
1755
|
+
writeFileSync3(join2(projectRoot, ".cursorrules"), formatForCursor(ctx), "utf-8");
|
|
1756
|
+
writeFileSync3(join2(projectRoot, "CLAUDE.md"), formatForClaude(ctx), "utf-8");
|
|
1757
|
+
writeFileSync3(join2(projectRoot, "AGENTS.md"), formatForAgents(ctx), "utf-8");
|
|
1758
|
+
writeClaudeCommands(projectRoot);
|
|
1759
|
+
writeClaudeSkills(projectRoot);
|
|
1760
|
+
writeClaudeSettings(projectRoot);
|
|
1719
1761
|
const tokenKeys = config2?.tokens ? [
|
|
1720
1762
|
...Object.keys(config2.tokens.colors?.light ?? {}),
|
|
1721
1763
|
...Object.keys(config2.tokens.colors?.dark ?? {}),
|
|
@@ -1728,19 +1770,19 @@ async function writeCursorRules(projectRoot) {
|
|
|
1728
1770
|
tokenKeys: tokenKeys || void 0
|
|
1729
1771
|
};
|
|
1730
1772
|
}
|
|
1731
|
-
async function
|
|
1773
|
+
async function regenerateAllHarnessFiles() {
|
|
1732
1774
|
const project = findConfig();
|
|
1733
1775
|
if (!project) {
|
|
1734
1776
|
return { written: false };
|
|
1735
1777
|
}
|
|
1736
|
-
return
|
|
1778
|
+
return writeAllHarnessFiles(project.root);
|
|
1737
1779
|
}
|
|
1738
1780
|
|
|
1739
1781
|
// src/commands/init.ts
|
|
1740
1782
|
import { cwd } from "process";
|
|
1741
1783
|
function hasNextInPackageJson(projectPath) {
|
|
1742
1784
|
const pkgPath = join3(projectPath, "package.json");
|
|
1743
|
-
if (!
|
|
1785
|
+
if (!existsSync3(pkgPath)) return false;
|
|
1744
1786
|
try {
|
|
1745
1787
|
const json = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
1746
1788
|
const deps = { ...json.dependencies, ...json.devDependencies };
|
|
@@ -1753,7 +1795,7 @@ function cleanConflictingFiles(projectPath) {
|
|
|
1753
1795
|
const conflicts = [".next", ".coherent", ".cursorrules", ".eslintrc.json", "CLAUDE.md", ".claude", ".vscode"];
|
|
1754
1796
|
for (const name of conflicts) {
|
|
1755
1797
|
const fullPath = join3(projectPath, name);
|
|
1756
|
-
if (
|
|
1798
|
+
if (existsSync3(fullPath)) {
|
|
1757
1799
|
rmSync(fullPath, { recursive: true, force: true });
|
|
1758
1800
|
}
|
|
1759
1801
|
}
|
|
@@ -1761,12 +1803,12 @@ function cleanConflictingFiles(projectPath) {
|
|
|
1761
1803
|
function runCreateNextApp(projectPath) {
|
|
1762
1804
|
cleanConflictingFiles(projectPath);
|
|
1763
1805
|
const envPath = join3(projectPath, ".env");
|
|
1764
|
-
const envBackup =
|
|
1806
|
+
const envBackup = existsSync3(envPath) ? readFileSync2(envPath, "utf-8") : null;
|
|
1765
1807
|
if (envBackup !== null) rmSync(envPath, { force: true });
|
|
1766
1808
|
const cmd = "npx --yes create-next-app@15.2.4 . --typescript --tailwind --eslint --app --no-src-dir --no-turbopack --yes";
|
|
1767
1809
|
execSync(cmd, { cwd: projectPath, stdio: "inherit" });
|
|
1768
1810
|
if (envBackup !== null) {
|
|
1769
|
-
const existing =
|
|
1811
|
+
const existing = existsSync3(envPath) ? readFileSync2(envPath, "utf-8") : "";
|
|
1770
1812
|
writeFileSync4(envPath, existing ? existing + "\n" + envBackup : envBackup, "utf-8");
|
|
1771
1813
|
}
|
|
1772
1814
|
}
|
|
@@ -1774,8 +1816,8 @@ async function ensureCoherentPrerequisites(projectPath) {
|
|
|
1774
1816
|
const libPath = join3(projectPath, "lib");
|
|
1775
1817
|
const utilsPath = join3(projectPath, "lib", "utils.ts");
|
|
1776
1818
|
const componentsUiPath = join3(projectPath, "components", "ui");
|
|
1777
|
-
if (!
|
|
1778
|
-
if (!
|
|
1819
|
+
if (!existsSync3(utilsPath)) {
|
|
1820
|
+
if (!existsSync3(libPath)) mkdirSync3(libPath, { recursive: true });
|
|
1779
1821
|
const cnContent = `import { type ClassValue, clsx } from 'clsx'
|
|
1780
1822
|
import { twMerge } from 'tailwind-merge'
|
|
1781
1823
|
|
|
@@ -1785,7 +1827,7 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
1785
1827
|
`;
|
|
1786
1828
|
await writeFile(utilsPath, cnContent);
|
|
1787
1829
|
}
|
|
1788
|
-
if (!
|
|
1830
|
+
if (!existsSync3(componentsUiPath)) mkdirSync3(componentsUiPath, { recursive: true });
|
|
1789
1831
|
}
|
|
1790
1832
|
async function ensureRegistryComponents(config2, projectPath) {
|
|
1791
1833
|
const provider = getComponentProvider();
|
|
@@ -1793,12 +1835,12 @@ async function ensureRegistryComponents(config2, projectPath) {
|
|
|
1793
1835
|
await provider.installBatch(baseComponents, projectPath);
|
|
1794
1836
|
const generator = new ComponentGenerator(config2);
|
|
1795
1837
|
const uiDir = join3(projectPath, "components", "ui");
|
|
1796
|
-
if (!
|
|
1838
|
+
if (!existsSync3(uiDir)) mkdirSync3(uiDir, { recursive: true });
|
|
1797
1839
|
for (const comp of config2.components) {
|
|
1798
1840
|
if (comp.source === "shadcn") continue;
|
|
1799
1841
|
const fileName = toKebabCase(comp.name) + ".tsx";
|
|
1800
1842
|
const filePath = join3(uiDir, fileName);
|
|
1801
|
-
if (
|
|
1843
|
+
if (existsSync3(filePath)) continue;
|
|
1802
1844
|
const code = await generator.generate(comp);
|
|
1803
1845
|
await writeFile(filePath, code);
|
|
1804
1846
|
}
|
|
@@ -1827,7 +1869,7 @@ async function initCommand(name) {
|
|
|
1827
1869
|
process.exit(1);
|
|
1828
1870
|
}
|
|
1829
1871
|
const targetDir = join3(cwd(), name);
|
|
1830
|
-
if (!
|
|
1872
|
+
if (!existsSync3(targetDir)) {
|
|
1831
1873
|
mkdirSync3(targetDir, { recursive: true });
|
|
1832
1874
|
}
|
|
1833
1875
|
process.chdir(targetDir);
|
|
@@ -1835,7 +1877,7 @@ async function initCommand(name) {
|
|
|
1835
1877
|
let projectPath;
|
|
1836
1878
|
try {
|
|
1837
1879
|
projectPath = cwd();
|
|
1838
|
-
if (!
|
|
1880
|
+
if (!existsSync3(projectPath)) {
|
|
1839
1881
|
throw new Error("ENOENT");
|
|
1840
1882
|
}
|
|
1841
1883
|
} catch (err) {
|
|
@@ -1901,7 +1943,7 @@ async function initCommand(name) {
|
|
|
1901
1943
|
} else {
|
|
1902
1944
|
try {
|
|
1903
1945
|
const pkgPath = join3(projectPath, "package.json");
|
|
1904
|
-
if (
|
|
1946
|
+
if (existsSync3(pkgPath)) {
|
|
1905
1947
|
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
1906
1948
|
if (typeof pkg.name === "string" && pkg.name) {
|
|
1907
1949
|
appName = toTitleCase(pkg.name);
|
|
@@ -1996,8 +2038,7 @@ export default config
|
|
|
1996
2038
|
{ type: "init", description: "Project initialized", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
|
|
1997
2039
|
]);
|
|
1998
2040
|
try {
|
|
1999
|
-
await
|
|
2000
|
-
await generateClaudeCodeFiles(projectPath);
|
|
2041
|
+
await writeAllHarnessFiles(projectPath);
|
|
2001
2042
|
} catch (e) {
|
|
2002
2043
|
if (process.env.COHERENT_DEBUG === "1") console.error(chalk3.dim("Could not write .cursorrules / CLAUDE.md:"), e);
|
|
2003
2044
|
}
|
|
@@ -2013,9 +2054,9 @@ async function configureNextImages(projectPath) {
|
|
|
2013
2054
|
const jsPath = join3(projectPath, "next.config.js");
|
|
2014
2055
|
const mjsPath = join3(projectPath, "next.config.mjs");
|
|
2015
2056
|
let configPath = "";
|
|
2016
|
-
if (
|
|
2017
|
-
else if (
|
|
2018
|
-
else if (
|
|
2057
|
+
if (existsSync3(tsPath)) configPath = tsPath;
|
|
2058
|
+
else if (existsSync3(mjsPath)) configPath = mjsPath;
|
|
2059
|
+
else if (existsSync3(jsPath)) configPath = jsPath;
|
|
2019
2060
|
else return;
|
|
2020
2061
|
const content = `import type { NextConfig } from "next";
|
|
2021
2062
|
|
|
@@ -2055,14 +2096,14 @@ async function createAppRouteGroupLayout(projectPath) {
|
|
|
2055
2096
|
import chalk10 from "chalk";
|
|
2056
2097
|
import ora2 from "ora";
|
|
2057
2098
|
import { resolve as resolve5, relative as relative2, join as join6 } from "path";
|
|
2058
|
-
import { existsSync as
|
|
2099
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
|
|
2059
2100
|
import {
|
|
2060
|
-
DesignSystemManager as
|
|
2101
|
+
DesignSystemManager as DesignSystemManager4,
|
|
2061
2102
|
ComponentManager as ComponentManager4,
|
|
2062
2103
|
PageManager as PageManager2,
|
|
2063
2104
|
CLI_VERSION as CLI_VERSION2,
|
|
2064
2105
|
getTemplateForPageType as getTemplateForPageType2,
|
|
2065
|
-
loadManifest as
|
|
2106
|
+
loadManifest as loadManifest6,
|
|
2066
2107
|
saveManifest as saveManifest3,
|
|
2067
2108
|
updateEntry
|
|
2068
2109
|
} from "@getcoherent/core";
|
|
@@ -2555,8 +2596,8 @@ function extractComponentSpec(changes) {
|
|
|
2555
2596
|
// src/utils/dark-mode.ts
|
|
2556
2597
|
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
2557
2598
|
import { join as join4 } from "path";
|
|
2558
|
-
import { existsSync as
|
|
2559
|
-
import { generateSharedComponent, loadManifest as
|
|
2599
|
+
import { existsSync as existsSync4 } from "fs";
|
|
2600
|
+
import { generateSharedComponent, loadManifest as loadManifest2, integrateSharedLayoutIntoRootLayout } from "@getcoherent/core";
|
|
2560
2601
|
var THEME_TOGGLE_CODE = `'use client'
|
|
2561
2602
|
|
|
2562
2603
|
import { Moon, Sun } from 'lucide-react'
|
|
@@ -2582,7 +2623,7 @@ export function ThemeToggle() {
|
|
|
2582
2623
|
`;
|
|
2583
2624
|
async function setDefaultDarkTheme(projectRoot) {
|
|
2584
2625
|
const layoutPath = join4(projectRoot, "app", "layout.tsx");
|
|
2585
|
-
if (!
|
|
2626
|
+
if (!existsSync4(layoutPath)) return false;
|
|
2586
2627
|
let content = await readFile2(layoutPath, "utf-8");
|
|
2587
2628
|
if (content.includes('<html className="dark"') || content.includes("<html className='dark'")) return true;
|
|
2588
2629
|
content = content.replace(/<html(\s|>)/, '<html className="dark"$1');
|
|
@@ -2590,7 +2631,7 @@ async function setDefaultDarkTheme(projectRoot) {
|
|
|
2590
2631
|
return true;
|
|
2591
2632
|
}
|
|
2592
2633
|
async function ensureThemeToggle(projectRoot) {
|
|
2593
|
-
const manifest = await
|
|
2634
|
+
const manifest = await loadManifest2(projectRoot);
|
|
2594
2635
|
const existing = manifest.shared.find((e) => e.name === "ThemeToggle" || e.name.toLowerCase().includes("themetoggle"));
|
|
2595
2636
|
if (existing) {
|
|
2596
2637
|
await integrateSharedLayoutIntoRootLayout(projectRoot);
|
|
@@ -2610,7 +2651,7 @@ async function ensureThemeToggle(projectRoot) {
|
|
|
2610
2651
|
import { appendFile } from "fs/promises";
|
|
2611
2652
|
|
|
2612
2653
|
// src/utils/backup.ts
|
|
2613
|
-
import { existsSync as
|
|
2654
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync5, readdirSync, rmSync as rmSync2, statSync } from "fs";
|
|
2614
2655
|
import { join as join5, relative, dirname as dirname2 } from "path";
|
|
2615
2656
|
import chalk5 from "chalk";
|
|
2616
2657
|
var DEBUG = process.env.COHERENT_DEBUG === "1";
|
|
@@ -2633,7 +2674,7 @@ function createBackup(projectRoot) {
|
|
|
2633
2674
|
let fileCount = 0;
|
|
2634
2675
|
for (const file of CRITICAL_FILES) {
|
|
2635
2676
|
const src = join5(projectRoot, file);
|
|
2636
|
-
if (
|
|
2677
|
+
if (existsSync5(src)) {
|
|
2637
2678
|
const dest = join5(backupPath, file);
|
|
2638
2679
|
mkdirSync4(dirname2(dest), { recursive: true });
|
|
2639
2680
|
writeFileSync5(dest, readFileSync3(src));
|
|
@@ -2642,7 +2683,7 @@ function createBackup(projectRoot) {
|
|
|
2642
2683
|
}
|
|
2643
2684
|
for (const dir of CRITICAL_DIRS) {
|
|
2644
2685
|
const srcDir = join5(projectRoot, dir);
|
|
2645
|
-
if (!
|
|
2686
|
+
if (!existsSync5(srcDir)) continue;
|
|
2646
2687
|
backupDirectory(srcDir, projectRoot, backupPath);
|
|
2647
2688
|
fileCount += countFiles(srcDir);
|
|
2648
2689
|
}
|
|
@@ -2710,12 +2751,12 @@ function pruneOldBackups(backupBase) {
|
|
|
2710
2751
|
}
|
|
2711
2752
|
function listBackups(projectRoot) {
|
|
2712
2753
|
const backupBase = join5(projectRoot, BACKUP_DIR);
|
|
2713
|
-
if (!
|
|
2754
|
+
if (!existsSync5(backupBase)) return [];
|
|
2714
2755
|
try {
|
|
2715
2756
|
return readdirSync(backupBase).filter((e) => !e.startsWith(".")).map((name) => {
|
|
2716
2757
|
const metaPath = join5(backupBase, name, ".backup-meta.json");
|
|
2717
2758
|
let meta = { timestamp: name, files: 0 };
|
|
2718
|
-
if (
|
|
2759
|
+
if (existsSync5(metaPath)) {
|
|
2719
2760
|
try {
|
|
2720
2761
|
meta = JSON.parse(readFileSync3(metaPath, "utf-8"));
|
|
2721
2762
|
} catch (e) {
|
|
@@ -2731,7 +2772,7 @@ function listBackups(projectRoot) {
|
|
|
2731
2772
|
}
|
|
2732
2773
|
function restoreBackup(projectRoot, backupName) {
|
|
2733
2774
|
const backupPath = join5(projectRoot, BACKUP_DIR, backupName);
|
|
2734
|
-
if (!
|
|
2775
|
+
if (!existsSync5(backupPath)) return false;
|
|
2735
2776
|
try {
|
|
2736
2777
|
restoreDirectory(backupPath, backupPath, projectRoot);
|
|
2737
2778
|
return true;
|
|
@@ -3209,12 +3250,12 @@ function applyDefaults(request) {
|
|
|
3209
3250
|
}
|
|
3210
3251
|
|
|
3211
3252
|
// src/commands/chat/split-generator.ts
|
|
3212
|
-
import { existsSync as
|
|
3253
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
3213
3254
|
import { resolve as resolve2 } from "path";
|
|
3214
3255
|
import { z } from "zod";
|
|
3215
3256
|
import {
|
|
3216
3257
|
SharedComponentTypeSchema,
|
|
3217
|
-
loadManifest as
|
|
3258
|
+
loadManifest as loadManifest3,
|
|
3218
3259
|
saveManifest,
|
|
3219
3260
|
generateSharedComponent as generateSharedComponent2
|
|
3220
3261
|
} from "@getcoherent/core";
|
|
@@ -3660,7 +3701,7 @@ function readExistingAppPageForReference(projectRoot, plan) {
|
|
|
3660
3701
|
for (const group of ["(app)", "(admin)", "(dashboard)"]) {
|
|
3661
3702
|
const filePath = resolve2(projectRoot, "app", group, key, "page.tsx");
|
|
3662
3703
|
try {
|
|
3663
|
-
if (
|
|
3704
|
+
if (existsSync6(filePath)) {
|
|
3664
3705
|
const code = readFileSync4(filePath, "utf-8");
|
|
3665
3706
|
const lines = code.split("\n");
|
|
3666
3707
|
return lines.slice(0, 60).join("\n");
|
|
@@ -3672,7 +3713,7 @@ function readExistingAppPageForReference(projectRoot, plan) {
|
|
|
3672
3713
|
}
|
|
3673
3714
|
}
|
|
3674
3715
|
const appDir = resolve2(projectRoot, "app");
|
|
3675
|
-
if (!
|
|
3716
|
+
if (!existsSync6(appDir)) return null;
|
|
3676
3717
|
try {
|
|
3677
3718
|
const entries = readdirSync2(appDir);
|
|
3678
3719
|
for (const entry of entries) {
|
|
@@ -3682,7 +3723,7 @@ function readExistingAppPageForReference(projectRoot, plan) {
|
|
|
3682
3723
|
const subDirs = readdirSync2(groupDir);
|
|
3683
3724
|
for (const sub of subDirs) {
|
|
3684
3725
|
const pagePath = resolve2(groupDir, sub, "page.tsx");
|
|
3685
|
-
if (
|
|
3726
|
+
if (existsSync6(pagePath)) {
|
|
3686
3727
|
const code = readFileSync4(pagePath, "utf-8");
|
|
3687
3728
|
const lines = code.split("\n");
|
|
3688
3729
|
return lines.slice(0, 60).join("\n");
|
|
@@ -3728,7 +3769,7 @@ var manifestLock = Promise.resolve();
|
|
|
3728
3769
|
async function updateManifestSafe(projectRoot, fn) {
|
|
3729
3770
|
const timeoutMs = 5e3;
|
|
3730
3771
|
const update = manifestLock.then(async () => {
|
|
3731
|
-
const m = await
|
|
3772
|
+
const m = await loadManifest3(projectRoot);
|
|
3732
3773
|
const updated = fn(m);
|
|
3733
3774
|
await saveManifest(projectRoot, updated);
|
|
3734
3775
|
});
|
|
@@ -3908,7 +3949,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
3908
3949
|
await createAIProvider(provider ?? "auto")
|
|
3909
3950
|
);
|
|
3910
3951
|
if (generated.length > 0) {
|
|
3911
|
-
const updatedManifest = await
|
|
3952
|
+
const updatedManifest = await loadManifest3(projectRoot);
|
|
3912
3953
|
parseOpts.sharedComponentsSummary = buildSharedComponentsSummary(updatedManifest);
|
|
3913
3954
|
const names = generated.map((c) => c.name).join(", ");
|
|
3914
3955
|
spinner.succeed(`Phase 4.5/6 \u2014 Generated ${generated.length} shared components (${names})`);
|
|
@@ -3919,7 +3960,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
3919
3960
|
spinner.warn("Phase 4.5/6 \u2014 Could not generate shared components (continuing without)");
|
|
3920
3961
|
}
|
|
3921
3962
|
} else if (homePageCode) {
|
|
3922
|
-
const manifest = await
|
|
3963
|
+
const manifest = await loadManifest3(projectRoot);
|
|
3923
3964
|
const shouldSkip = reusedExistingAnchor && manifest.shared.some((e) => e.type !== "layout");
|
|
3924
3965
|
if (!shouldSkip) {
|
|
3925
3966
|
spinner.start("Phase 4.5/6 \u2014 Extracting shared components (legacy)...");
|
|
@@ -3943,7 +3984,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
3943
3984
|
}
|
|
3944
3985
|
spinner.start(`Phase 5/6 \u2014 Generating ${remainingPages.length} pages in parallel...`);
|
|
3945
3986
|
const sharedComponentsNote = buildSharedComponentsNote(parseOpts.sharedComponentsSummary);
|
|
3946
|
-
const currentManifest = projectRoot ? await
|
|
3987
|
+
const currentManifest = projectRoot ? await loadManifest3(projectRoot) : null;
|
|
3947
3988
|
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="#".`;
|
|
3948
3989
|
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.';
|
|
3949
3990
|
const existingAppPageCode = readExistingAppPageForReference(parseOpts?.projectRoot ?? null, plan);
|
|
@@ -3957,7 +3998,7 @@ ${existingAppPageCode}
|
|
|
3957
3998
|
const existingPageCode = {};
|
|
3958
3999
|
if (projectRoot) {
|
|
3959
4000
|
const appDir = resolve2(projectRoot, "app");
|
|
3960
|
-
if (
|
|
4001
|
+
if (existsSync6(appDir)) {
|
|
3961
4002
|
const pageFiles = readdirSync2(appDir, { recursive: true }).filter(
|
|
3962
4003
|
(f) => typeof f === "string" && f.endsWith("page.tsx")
|
|
3963
4004
|
);
|
|
@@ -4144,7 +4185,7 @@ var SharedExtractionResponseSchema = z.object({
|
|
|
4144
4185
|
components: z.array(SharedExtractionItemSchema).max(5).default([])
|
|
4145
4186
|
});
|
|
4146
4187
|
async function extractSharedComponents(homePageCode, projectRoot, aiProvider) {
|
|
4147
|
-
const manifest = await
|
|
4188
|
+
const manifest = await loadManifest3(projectRoot);
|
|
4148
4189
|
let ai;
|
|
4149
4190
|
try {
|
|
4150
4191
|
ai = await createAIProvider(aiProvider);
|
|
@@ -4199,7 +4240,7 @@ async function extractSharedComponents(homePageCode, projectRoot, aiProvider) {
|
|
|
4199
4240
|
} catch {
|
|
4200
4241
|
}
|
|
4201
4242
|
}
|
|
4202
|
-
const updatedManifest = await
|
|
4243
|
+
const updatedManifest = await loadManifest3(projectRoot);
|
|
4203
4244
|
return { components: results, summary: buildSharedComponentsSummary(updatedManifest) };
|
|
4204
4245
|
}
|
|
4205
4246
|
function extractAppNameFromPrompt(prompt) {
|
|
@@ -4249,7 +4290,7 @@ import { dirname as dirname3 } from "path";
|
|
|
4249
4290
|
import chalk8 from "chalk";
|
|
4250
4291
|
import {
|
|
4251
4292
|
getTemplateForPageType,
|
|
4252
|
-
loadManifest as
|
|
4293
|
+
loadManifest as loadManifest4,
|
|
4253
4294
|
saveManifest as saveManifest2,
|
|
4254
4295
|
updateUsedIn,
|
|
4255
4296
|
findSharedComponentByIdOrName,
|
|
@@ -4664,7 +4705,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
4664
4705
|
postFixes: fixes
|
|
4665
4706
|
});
|
|
4666
4707
|
try {
|
|
4667
|
-
await
|
|
4708
|
+
await writeAllHarnessFiles(projectRoot);
|
|
4668
4709
|
} catch {
|
|
4669
4710
|
}
|
|
4670
4711
|
return {
|
|
@@ -4731,7 +4772,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
4731
4772
|
fixes.forEach((f) => console.log(chalk8.dim(` ${f}`)));
|
|
4732
4773
|
}
|
|
4733
4774
|
await writeFile(pageFilePath, fixedCode);
|
|
4734
|
-
const manifest = await
|
|
4775
|
+
const manifest = await loadManifest4(projectRoot);
|
|
4735
4776
|
const usedIn = manifest.shared.find((e) => e.id === resolved.id)?.usedIn ?? [];
|
|
4736
4777
|
const filePathRel = routeToRelPath(route, readPlan || isAuthRoute(route));
|
|
4737
4778
|
if (!usedIn.includes(filePathRel)) {
|
|
@@ -4746,7 +4787,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
4746
4787
|
postFixes: fixes
|
|
4747
4788
|
});
|
|
4748
4789
|
try {
|
|
4749
|
-
await
|
|
4790
|
+
await writeAllHarnessFiles(projectRoot);
|
|
4750
4791
|
} catch {
|
|
4751
4792
|
}
|
|
4752
4793
|
return {
|
|
@@ -4833,7 +4874,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
4833
4874
|
await writeFile(fullPath, fixedCode);
|
|
4834
4875
|
usedInFiles.push(relPath);
|
|
4835
4876
|
}
|
|
4836
|
-
const manifest = await
|
|
4877
|
+
const manifest = await loadManifest4(projectRoot);
|
|
4837
4878
|
const nextManifest = updateUsedIn(manifest, created.id, usedInFiles);
|
|
4838
4879
|
await saveManifest2(projectRoot, nextManifest);
|
|
4839
4880
|
printPromoteAndLinkReport({
|
|
@@ -4843,7 +4884,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
4843
4884
|
usedInFiles
|
|
4844
4885
|
});
|
|
4845
4886
|
try {
|
|
4846
|
-
await
|
|
4887
|
+
await writeAllHarnessFiles(projectRoot);
|
|
4847
4888
|
} catch {
|
|
4848
4889
|
}
|
|
4849
4890
|
return {
|
|
@@ -5089,7 +5130,7 @@ Keep all existing functionality intact.`,
|
|
|
5089
5130
|
cm.updateConfig(cfg);
|
|
5090
5131
|
pm.updateConfig(cfg);
|
|
5091
5132
|
}
|
|
5092
|
-
const manifestForAudit = await
|
|
5133
|
+
const manifestForAudit = await loadManifest4(projectRoot);
|
|
5093
5134
|
const planForAudit = loadPlan(projectRoot);
|
|
5094
5135
|
await warnInlineDuplicates(
|
|
5095
5136
|
projectRoot,
|
|
@@ -5320,7 +5361,7 @@ ${pagesCtx}`
|
|
|
5320
5361
|
cm.updateConfig(cfg);
|
|
5321
5362
|
pm.updateConfig(cfg);
|
|
5322
5363
|
}
|
|
5323
|
-
const manifestForAudit = await
|
|
5364
|
+
const manifestForAudit = await loadManifest4(projectRoot);
|
|
5324
5365
|
const planForAudit2 = loadPlan(projectRoot);
|
|
5325
5366
|
await warnInlineDuplicates(
|
|
5326
5367
|
projectRoot,
|
|
@@ -5419,7 +5460,7 @@ Rules:
|
|
|
5419
5460
|
fixes.forEach((f) => console.log(chalk8.dim(` ${f}`)));
|
|
5420
5461
|
}
|
|
5421
5462
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
5422
|
-
const manifest = await
|
|
5463
|
+
const manifest = await loadManifest4(projectRoot);
|
|
5423
5464
|
printPostGenerationReport({
|
|
5424
5465
|
action: "updated",
|
|
5425
5466
|
pageTitle: pageDef.name || pageDef.id || "Page",
|
|
@@ -5563,8 +5604,8 @@ function hasNavChanged(before, after) {
|
|
|
5563
5604
|
// src/commands/chat/interactive.ts
|
|
5564
5605
|
import chalk9 from "chalk";
|
|
5565
5606
|
import { resolve as resolve4 } from "path";
|
|
5566
|
-
import { existsSync as
|
|
5567
|
-
import { DesignSystemManager as
|
|
5607
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5 } from "fs";
|
|
5608
|
+
import { DesignSystemManager as DesignSystemManager3, ComponentManager as ComponentManager3, loadManifest as loadManifest5 } from "@getcoherent/core";
|
|
5568
5609
|
var DEBUG3 = process.env.COHERENT_DEBUG === "1";
|
|
5569
5610
|
async function interactiveChat(options, chatCommandFn) {
|
|
5570
5611
|
const { createInterface } = await import("readline");
|
|
@@ -5573,7 +5614,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
5573
5614
|
const projectRoot = project.root;
|
|
5574
5615
|
const configPath = project.configPath;
|
|
5575
5616
|
const config2 = await loadConfig(configPath);
|
|
5576
|
-
const dsm = new
|
|
5617
|
+
const dsm = new DesignSystemManager3(configPath);
|
|
5577
5618
|
await dsm.load();
|
|
5578
5619
|
const cm = new ComponentManager3(config2);
|
|
5579
5620
|
const validProviders = ["claude", "openai", "auto"];
|
|
@@ -5588,7 +5629,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
5588
5629
|
let history = [];
|
|
5589
5630
|
try {
|
|
5590
5631
|
mkdirSync5(historyDir, { recursive: true });
|
|
5591
|
-
if (
|
|
5632
|
+
if (existsSync7(historyFile)) {
|
|
5592
5633
|
history = readFileSync5(historyFile, "utf-8").split("\n").filter(Boolean).slice(-200);
|
|
5593
5634
|
}
|
|
5594
5635
|
} catch (e) {
|
|
@@ -5634,7 +5675,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
5634
5675
|
return;
|
|
5635
5676
|
}
|
|
5636
5677
|
if (lower === "components" || lower === "list components" || lower.includes("what components")) {
|
|
5637
|
-
const manifest = await
|
|
5678
|
+
const manifest = await loadManifest5(projectRoot);
|
|
5638
5679
|
if (manifest.shared.length === 0) {
|
|
5639
5680
|
console.log(chalk9.gray("\n No shared components yet.\n"));
|
|
5640
5681
|
} else {
|
|
@@ -5676,7 +5717,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
5676
5717
|
}
|
|
5677
5718
|
if (lower === "status") {
|
|
5678
5719
|
const currentConfig = dsm.getConfig();
|
|
5679
|
-
const manifest = await
|
|
5720
|
+
const manifest = await loadManifest5(projectRoot);
|
|
5680
5721
|
console.log(chalk9.bold(`
|
|
5681
5722
|
${currentConfig.name || "Coherent Project"}`));
|
|
5682
5723
|
console.log(
|
|
@@ -5768,7 +5809,7 @@ async function chatCommand(message, options) {
|
|
|
5768
5809
|
const projectRoot = project.root;
|
|
5769
5810
|
const configPath = project.configPath;
|
|
5770
5811
|
const migrationGuard = join6(projectRoot, ".coherent", "migration-in-progress");
|
|
5771
|
-
if (
|
|
5812
|
+
if (existsSync8(migrationGuard)) {
|
|
5772
5813
|
spinner.fail("Migration in progress");
|
|
5773
5814
|
console.error(chalk10.red("\n\u274C A migration is in progress. Run `coherent migrate --rollback` to undo first."));
|
|
5774
5815
|
bail("Migration in progress");
|
|
@@ -5807,7 +5848,7 @@ async function chatCommand(message, options) {
|
|
|
5807
5848
|
spinner.text = "Loading design system configuration...";
|
|
5808
5849
|
}
|
|
5809
5850
|
const storedHashes = await loadHashes(projectRoot);
|
|
5810
|
-
const dsm = new
|
|
5851
|
+
const dsm = new DesignSystemManager4(configPath);
|
|
5811
5852
|
await dsm.load();
|
|
5812
5853
|
const cm = new ComponentManager4(config2);
|
|
5813
5854
|
const pm = new PageManager2(config2, cm);
|
|
@@ -5857,7 +5898,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
5857
5898
|
async (p) => JSON.stringify(await aiProvider.generateJSON("You are a component classifier.", p))
|
|
5858
5899
|
);
|
|
5859
5900
|
if (classifications.length > 0) {
|
|
5860
|
-
let manifest2 = await
|
|
5901
|
+
let manifest2 = await loadManifest6(projectRoot);
|
|
5861
5902
|
manifest2 = updateEntry(manifest2, genResult.id, {
|
|
5862
5903
|
type: classifications[0].type,
|
|
5863
5904
|
description: classifications[0].description || message
|
|
@@ -5923,10 +5964,10 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
5923
5964
|
}
|
|
5924
5965
|
}
|
|
5925
5966
|
spinner.start("Parsing your request...");
|
|
5926
|
-
let manifest = await
|
|
5967
|
+
let manifest = await loadManifest6(project.root);
|
|
5927
5968
|
const validShared = manifest.shared.filter((s) => {
|
|
5928
5969
|
const fp = resolve5(project.root, s.file);
|
|
5929
|
-
return
|
|
5970
|
+
return existsSync8(fp);
|
|
5930
5971
|
});
|
|
5931
5972
|
if (validShared.length !== manifest.shared.length) {
|
|
5932
5973
|
const cleaned = manifest.shared.length - validShared.length;
|
|
@@ -5995,7 +6036,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
5995
6036
|
} else {
|
|
5996
6037
|
let reusePlanDirective;
|
|
5997
6038
|
try {
|
|
5998
|
-
const singlePageManifest = await
|
|
6039
|
+
const singlePageManifest = await loadManifest6(projectRoot);
|
|
5999
6040
|
if (singlePageManifest.shared.length > 0) {
|
|
6000
6041
|
const reusePlan = buildReusePlan({
|
|
6001
6042
|
pageName: "page",
|
|
@@ -6147,7 +6188,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6147
6188
|
for (const entry of manifest.shared) {
|
|
6148
6189
|
try {
|
|
6149
6190
|
const sharedPath = resolve5(projectRoot, entry.file);
|
|
6150
|
-
if (
|
|
6191
|
+
if (existsSync8(sharedPath)) {
|
|
6151
6192
|
const sharedCode = readFileSync6(sharedPath, "utf-8");
|
|
6152
6193
|
const sharedImports = sharedCode.matchAll(/@\/components\/ui\/([a-z0-9-]+)/g);
|
|
6153
6194
|
for (const m of sharedImports) {
|
|
@@ -6169,7 +6210,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6169
6210
|
for (const componentId of allNeededComponentIds) {
|
|
6170
6211
|
const isRegistered = !!cm.read(componentId);
|
|
6171
6212
|
const filePath = join6(projectRoot, "components", "ui", `${componentId}.tsx`);
|
|
6172
|
-
const fileExists =
|
|
6213
|
+
const fileExists = existsSync8(filePath);
|
|
6173
6214
|
if (DEBUG4) console.log(chalk10.gray(` Checking ${componentId}: registered=${isRegistered} file=${fileExists}`));
|
|
6174
6215
|
if (!isRegistered || !fileExists) {
|
|
6175
6216
|
missingComponents.push(componentId);
|
|
@@ -6288,7 +6329,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6288
6329
|
try {
|
|
6289
6330
|
const { validateReuse } = await import("./reuse-validator-XR2ZEYC4.js");
|
|
6290
6331
|
const { inferPageTypeFromRoute: inferPageTypeFromRoute2 } = await import("./design-constraints-HGNEY3W3.js");
|
|
6291
|
-
const manifest2 = await
|
|
6332
|
+
const manifest2 = await loadManifest6(projectRoot);
|
|
6292
6333
|
const reuseplan = projectRoot ? loadPlan(projectRoot) : null;
|
|
6293
6334
|
if (manifest2.shared.length > 0) {
|
|
6294
6335
|
for (const request of normalizedRequests) {
|
|
@@ -6318,7 +6359,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6318
6359
|
const route = page.route || `/${page.id || "page"}`;
|
|
6319
6360
|
const pageFilePath = routeToFsPath(projectRoot, route, false);
|
|
6320
6361
|
let pageCode = "";
|
|
6321
|
-
if (
|
|
6362
|
+
if (existsSync8(pageFilePath)) {
|
|
6322
6363
|
try {
|
|
6323
6364
|
pageCode = readFileSync6(pageFilePath, "utf-8");
|
|
6324
6365
|
} catch {
|
|
@@ -6340,8 +6381,8 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6340
6381
|
}
|
|
6341
6382
|
const missingRoutes = [...allLinkedRoutes].filter((route) => {
|
|
6342
6383
|
if (expandedExisting.has(route)) return false;
|
|
6343
|
-
if (
|
|
6344
|
-
if (
|
|
6384
|
+
if (existsSync8(routeToFsPath(projectRoot, route, false))) return false;
|
|
6385
|
+
if (existsSync8(routeToFsPath(projectRoot, route, true))) return false;
|
|
6345
6386
|
return true;
|
|
6346
6387
|
});
|
|
6347
6388
|
const SCAFFOLD_AI_LIMIT = 10;
|
|
@@ -6406,7 +6447,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6406
6447
|
const filePath = routeToFsPath(projectRoot, linkedRoute, isAuth);
|
|
6407
6448
|
if (isAuth) await ensureAuthRouteGroup(projectRoot);
|
|
6408
6449
|
const dir = resolve5(filePath, "..");
|
|
6409
|
-
if (!
|
|
6450
|
+
if (!existsSync8(dir)) {
|
|
6410
6451
|
mkdirSync6(dir, { recursive: true });
|
|
6411
6452
|
}
|
|
6412
6453
|
const placeholderCode = `export default function ${pageName.replace(/\s/g, "")}Page() {
|
|
@@ -6523,13 +6564,13 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6523
6564
|
const sharedDir = resolve5(projectRoot, "components", "shared");
|
|
6524
6565
|
const layoutFile = resolve5(projectRoot, "app", "layout.tsx");
|
|
6525
6566
|
const filesToHash = [layoutFile];
|
|
6526
|
-
if (
|
|
6567
|
+
if (existsSync8(sharedDir)) {
|
|
6527
6568
|
for (const f of readdirSync3(sharedDir)) {
|
|
6528
6569
|
if (f.endsWith(".tsx")) filesToHash.push(resolve5(sharedDir, f));
|
|
6529
6570
|
}
|
|
6530
6571
|
}
|
|
6531
6572
|
for (const filePath of filesToHash) {
|
|
6532
|
-
if (
|
|
6573
|
+
if (existsSync8(filePath)) {
|
|
6533
6574
|
const rel = relative2(projectRoot, filePath);
|
|
6534
6575
|
updatedHashes[rel] = await computeFileHash(filePath);
|
|
6535
6576
|
}
|
|
@@ -6540,11 +6581,11 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6540
6581
|
}
|
|
6541
6582
|
try {
|
|
6542
6583
|
const { extractPropsInterface, extractDependencies, extractUsageExample } = await import("./component-extractor-VYJLT5NR.js");
|
|
6543
|
-
let currentManifest = await
|
|
6584
|
+
let currentManifest = await loadManifest6(projectRoot);
|
|
6544
6585
|
let manifestChanged = false;
|
|
6545
6586
|
for (const entry of currentManifest.shared) {
|
|
6546
6587
|
const fullPath = resolve5(projectRoot, entry.file);
|
|
6547
|
-
if (!
|
|
6588
|
+
if (!existsSync8(fullPath)) continue;
|
|
6548
6589
|
const code = readFileSync6(fullPath, "utf-8");
|
|
6549
6590
|
const props = extractPropsInterface(code);
|
|
6550
6591
|
const deps = extractDependencies(code);
|
|
@@ -6559,7 +6600,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6559
6600
|
const pageFiles = Array.from(allModified).filter((f) => f.startsWith("app/") && f.endsWith("page.tsx"));
|
|
6560
6601
|
for (const pageFile of pageFiles) {
|
|
6561
6602
|
const fullPath = resolve5(projectRoot, pageFile);
|
|
6562
|
-
if (!
|
|
6603
|
+
if (!existsSync8(fullPath)) continue;
|
|
6563
6604
|
const pageCode = readFileSync6(fullPath, "utf-8");
|
|
6564
6605
|
for (const entry of currentManifest.shared) {
|
|
6565
6606
|
const isUsed = pageCode.includes(`from '@/components/shared/`) && (pageCode.includes(`{ ${entry.name} }`) || pageCode.includes(`{ ${entry.name},`));
|
|
@@ -6617,7 +6658,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6617
6658
|
${uxRecommendations}
|
|
6618
6659
|
`;
|
|
6619
6660
|
try {
|
|
6620
|
-
if (!
|
|
6661
|
+
if (!existsSync8(recPath)) {
|
|
6621
6662
|
await writeFile(
|
|
6622
6663
|
recPath,
|
|
6623
6664
|
"# UX/UI Recommendations\n\nRecommendations are added here when you use `coherent chat` and the AI suggests improvements.\n"
|
|
@@ -6691,7 +6732,7 @@ ${uxRecommendations}
|
|
|
6691
6732
|
import chalk11 from "chalk";
|
|
6692
6733
|
import ora3 from "ora";
|
|
6693
6734
|
import { spawn } from "child_process";
|
|
6694
|
-
import { existsSync as
|
|
6735
|
+
import { existsSync as existsSync11, rmSync as rmSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync8, readdirSync as readdirSync5 } from "fs";
|
|
6695
6736
|
import { resolve as resolve6, join as join9 } from "path";
|
|
6696
6737
|
import { readdir } from "fs/promises";
|
|
6697
6738
|
|
|
@@ -6744,15 +6785,15 @@ function validateV4GlobalsCss(css) {
|
|
|
6744
6785
|
}
|
|
6745
6786
|
|
|
6746
6787
|
// src/commands/preview.ts
|
|
6747
|
-
import { DesignSystemManager as
|
|
6788
|
+
import { DesignSystemManager as DesignSystemManager5, ComponentGenerator as ComponentGenerator2 } from "@getcoherent/core";
|
|
6748
6789
|
|
|
6749
6790
|
// src/utils/file-watcher.ts
|
|
6750
|
-
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as
|
|
6791
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync10 } from "fs";
|
|
6751
6792
|
import { relative as relative4, join as join8 } from "path";
|
|
6752
|
-
import { loadManifest as
|
|
6793
|
+
import { loadManifest as loadManifest7, saveManifest as saveManifest4 } from "@getcoherent/core";
|
|
6753
6794
|
|
|
6754
6795
|
// src/utils/component-integrity.ts
|
|
6755
|
-
import { existsSync as
|
|
6796
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "fs";
|
|
6756
6797
|
import { join as join7, relative as relative3 } from "path";
|
|
6757
6798
|
function extractExportedComponentNames(code) {
|
|
6758
6799
|
const names = [];
|
|
@@ -6786,7 +6827,7 @@ function arraysEqual(a, b) {
|
|
|
6786
6827
|
function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
6787
6828
|
const results = [];
|
|
6788
6829
|
const appDir = join7(projectRoot, "app");
|
|
6789
|
-
if (!
|
|
6830
|
+
if (!existsSync9(appDir)) return results;
|
|
6790
6831
|
const pageFiles = collectFiles(appDir, (name) => name === "page.tsx" || name === "page.jsx");
|
|
6791
6832
|
const componentImportPath = componentFile.replace(/\.tsx$/, "").replace(/\.jsx$/, "");
|
|
6792
6833
|
for (const absPath of pageFiles) {
|
|
@@ -6807,7 +6848,7 @@ function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
|
6807
6848
|
function isUsedInLayout(projectRoot, componentName) {
|
|
6808
6849
|
const appDir = join7(projectRoot, "app");
|
|
6809
6850
|
const layoutPaths = [join7(appDir, "layout.tsx")];
|
|
6810
|
-
if (
|
|
6851
|
+
if (existsSync9(appDir)) {
|
|
6811
6852
|
try {
|
|
6812
6853
|
for (const entry of readdirSync4(appDir, { withFileTypes: true })) {
|
|
6813
6854
|
if (entry.isDirectory() && entry.name.startsWith("(") && entry.name.endsWith(")")) {
|
|
@@ -6831,7 +6872,7 @@ function isUsedInLayout(projectRoot, componentName) {
|
|
|
6831
6872
|
function findUnregisteredComponents(projectRoot, manifest) {
|
|
6832
6873
|
const results = [];
|
|
6833
6874
|
const componentsDir = join7(projectRoot, "components");
|
|
6834
|
-
if (!
|
|
6875
|
+
if (!existsSync9(componentsDir)) return results;
|
|
6835
6876
|
const registeredFiles = new Set(manifest.shared.map((s) => s.file));
|
|
6836
6877
|
const registeredNames = new Set(manifest.shared.map((s) => s.name));
|
|
6837
6878
|
const files = collectFiles(
|
|
@@ -6859,7 +6900,7 @@ function findUnregisteredComponents(projectRoot, manifest) {
|
|
|
6859
6900
|
function findInlineDuplicates(projectRoot, manifest) {
|
|
6860
6901
|
const results = [];
|
|
6861
6902
|
const appDir = join7(projectRoot, "app");
|
|
6862
|
-
if (!
|
|
6903
|
+
if (!existsSync9(appDir)) return results;
|
|
6863
6904
|
const pageFiles = collectFiles(appDir, (name) => name === "page.tsx" || name === "page.jsx");
|
|
6864
6905
|
for (const absPath of pageFiles) {
|
|
6865
6906
|
if (absPath.includes("design-system")) continue;
|
|
@@ -6889,7 +6930,7 @@ function findInlineDuplicates(projectRoot, manifest) {
|
|
|
6889
6930
|
}
|
|
6890
6931
|
function findComponentFileByExportName(projectRoot, componentName) {
|
|
6891
6932
|
const componentsDir = join7(projectRoot, "components");
|
|
6892
|
-
if (!
|
|
6933
|
+
if (!existsSync9(componentsDir)) return null;
|
|
6893
6934
|
const files = collectFiles(
|
|
6894
6935
|
componentsDir,
|
|
6895
6936
|
(name) => (name.endsWith(".tsx") || name.endsWith(".jsx")) && !name.startsWith("."),
|
|
@@ -6911,7 +6952,7 @@ function removeOrphanedEntries(projectRoot, manifest) {
|
|
|
6911
6952
|
const removed = [];
|
|
6912
6953
|
const valid = manifest.shared.filter((entry) => {
|
|
6913
6954
|
const filePath = join7(projectRoot, entry.file);
|
|
6914
|
-
if (
|
|
6955
|
+
if (existsSync9(filePath)) return true;
|
|
6915
6956
|
removed.push({ id: entry.id, name: entry.name });
|
|
6916
6957
|
return false;
|
|
6917
6958
|
});
|
|
@@ -6930,7 +6971,7 @@ function reconcileComponents(projectRoot, manifest) {
|
|
|
6930
6971
|
const m = { ...manifest, shared: [...manifest.shared], nextId: manifest.nextId };
|
|
6931
6972
|
m.shared = m.shared.filter((entry) => {
|
|
6932
6973
|
const filePath = join7(projectRoot, entry.file);
|
|
6933
|
-
if (!
|
|
6974
|
+
if (!existsSync9(filePath)) {
|
|
6934
6975
|
const newPath = findComponentFileByExportName(projectRoot, entry.name);
|
|
6935
6976
|
if (newPath) {
|
|
6936
6977
|
result.updated.push({ id: entry.id, field: "file", from: entry.file, to: newPath });
|
|
@@ -7066,7 +7107,7 @@ function findInlineDuplicatesOfShared(content, manifest) {
|
|
|
7066
7107
|
function getWatcherConfig(projectRoot) {
|
|
7067
7108
|
try {
|
|
7068
7109
|
const pkgPath = join8(projectRoot, "package.json");
|
|
7069
|
-
if (!
|
|
7110
|
+
if (!existsSync10(pkgPath)) return defaultWatcherConfig();
|
|
7070
7111
|
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
7071
7112
|
const c = pkg?.coherent?.watcher ?? {};
|
|
7072
7113
|
return {
|
|
@@ -7125,7 +7166,7 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
7125
7166
|
if (config2.warnSharedReuse) {
|
|
7126
7167
|
let manifest;
|
|
7127
7168
|
try {
|
|
7128
|
-
manifest = await
|
|
7169
|
+
manifest = await loadManifest7(projectRoot);
|
|
7129
7170
|
} catch {
|
|
7130
7171
|
manifest = { shared: [], nextId: 1 };
|
|
7131
7172
|
}
|
|
@@ -7144,7 +7185,7 @@ async function handleFileDelete(projectRoot, filePath) {
|
|
|
7144
7185
|
if (!relativePath.startsWith("components/") || relativePath.startsWith("components/ui/")) return;
|
|
7145
7186
|
try {
|
|
7146
7187
|
const chalk30 = (await import("chalk")).default;
|
|
7147
|
-
const manifest = await
|
|
7188
|
+
const manifest = await loadManifest7(projectRoot);
|
|
7148
7189
|
const orphaned = manifest.shared.find((s) => s.file === relativePath);
|
|
7149
7190
|
if (orphaned) {
|
|
7150
7191
|
const cleaned = {
|
|
@@ -7154,7 +7195,7 @@ async function handleFileDelete(projectRoot, filePath) {
|
|
|
7154
7195
|
await saveManifest4(projectRoot, cleaned);
|
|
7155
7196
|
console.log(chalk30.cyan(`
|
|
7156
7197
|
\u{1F5D1} Auto-removed ${orphaned.id} (${orphaned.name}) \u2014 file deleted`));
|
|
7157
|
-
await
|
|
7198
|
+
await writeAllHarnessFiles(projectRoot);
|
|
7158
7199
|
}
|
|
7159
7200
|
} catch {
|
|
7160
7201
|
}
|
|
@@ -7165,7 +7206,7 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
7165
7206
|
if (!relativePath.endsWith(".tsx") && !relativePath.endsWith(".jsx")) return;
|
|
7166
7207
|
try {
|
|
7167
7208
|
const chalk30 = (await import("chalk")).default;
|
|
7168
|
-
const manifest = await
|
|
7209
|
+
const manifest = await loadManifest7(projectRoot);
|
|
7169
7210
|
const alreadyRegistered = manifest.shared.some((s) => s.file === relativePath);
|
|
7170
7211
|
if (alreadyRegistered) return;
|
|
7171
7212
|
const code = readFileSync8(filePath, "utf-8");
|
|
@@ -7183,7 +7224,7 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
7183
7224
|
}
|
|
7184
7225
|
async function handleManifestChange(projectRoot) {
|
|
7185
7226
|
try {
|
|
7186
|
-
await
|
|
7227
|
+
await writeAllHarnessFiles(projectRoot);
|
|
7187
7228
|
} catch {
|
|
7188
7229
|
}
|
|
7189
7230
|
}
|
|
@@ -7208,7 +7249,7 @@ function startFileWatcher(projectRoot) {
|
|
|
7208
7249
|
watcher.on("unlink", (fp) => handleFileDelete(projectRoot, fp));
|
|
7209
7250
|
});
|
|
7210
7251
|
const manifestPath = join8(projectRoot, "coherent.components.json");
|
|
7211
|
-
if (
|
|
7252
|
+
if (existsSync10(manifestPath)) {
|
|
7212
7253
|
import("chokidar").then((chokidar) => {
|
|
7213
7254
|
manifestWatcher = chokidar.default.watch(manifestPath, { ignoreInitial: true });
|
|
7214
7255
|
manifestWatcher.on("change", () => handleManifestChange(projectRoot));
|
|
@@ -7222,7 +7263,7 @@ function startFileWatcher(projectRoot) {
|
|
|
7222
7263
|
|
|
7223
7264
|
// src/commands/preview.ts
|
|
7224
7265
|
function getPackageManager(projectRoot) {
|
|
7225
|
-
const hasPnpm =
|
|
7266
|
+
const hasPnpm = existsSync11(resolve6(projectRoot, "pnpm-lock.yaml"));
|
|
7226
7267
|
return hasPnpm ? "pnpm" : "npm";
|
|
7227
7268
|
}
|
|
7228
7269
|
function runInstall(projectRoot) {
|
|
@@ -7246,21 +7287,21 @@ function runInstall(projectRoot) {
|
|
|
7246
7287
|
function checkProjectInitialized(projectRoot) {
|
|
7247
7288
|
const configPath = resolve6(projectRoot, "design-system.config.ts");
|
|
7248
7289
|
const packageJsonPath = resolve6(projectRoot, "package.json");
|
|
7249
|
-
if (!
|
|
7290
|
+
if (!existsSync11(configPath)) {
|
|
7250
7291
|
return false;
|
|
7251
7292
|
}
|
|
7252
|
-
if (!
|
|
7293
|
+
if (!existsSync11(packageJsonPath)) {
|
|
7253
7294
|
return false;
|
|
7254
7295
|
}
|
|
7255
7296
|
return true;
|
|
7256
7297
|
}
|
|
7257
7298
|
function checkDependenciesInstalled(projectRoot) {
|
|
7258
7299
|
const nodeModulesPath = resolve6(projectRoot, "node_modules");
|
|
7259
|
-
return
|
|
7300
|
+
return existsSync11(nodeModulesPath);
|
|
7260
7301
|
}
|
|
7261
7302
|
function clearStaleCache(projectRoot) {
|
|
7262
7303
|
const nextDir = join9(projectRoot, ".next");
|
|
7263
|
-
if (
|
|
7304
|
+
if (existsSync11(nextDir)) {
|
|
7264
7305
|
rmSync3(nextDir, { recursive: true, force: true });
|
|
7265
7306
|
console.log(chalk11.dim(" \u2714 Cleared stale build cache"));
|
|
7266
7307
|
}
|
|
@@ -7276,7 +7317,7 @@ async function preflightDependencyCheck(projectRoot) {
|
|
|
7276
7317
|
}
|
|
7277
7318
|
async function listPageFiles(appDir) {
|
|
7278
7319
|
const out = [];
|
|
7279
|
-
if (!
|
|
7320
|
+
if (!existsSync11(appDir)) return out;
|
|
7280
7321
|
async function walk(dir) {
|
|
7281
7322
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
7282
7323
|
for (const e of entries) {
|
|
@@ -7303,10 +7344,10 @@ async function validateSyntax(projectRoot) {
|
|
|
7303
7344
|
async function fixMissingComponentExports(projectRoot) {
|
|
7304
7345
|
const appDir = join9(projectRoot, "app");
|
|
7305
7346
|
const uiDir = join9(projectRoot, "components", "ui");
|
|
7306
|
-
if (!
|
|
7347
|
+
if (!existsSync11(appDir) || !existsSync11(uiDir)) return;
|
|
7307
7348
|
const pages = await listPageFiles(appDir);
|
|
7308
7349
|
const sharedDir = join9(projectRoot, "components", "shared");
|
|
7309
|
-
if (
|
|
7350
|
+
if (existsSync11(sharedDir)) {
|
|
7310
7351
|
const sharedFiles = readdirSync5(sharedDir).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts")).map((f) => join9(sharedDir, f));
|
|
7311
7352
|
pages.push(...sharedFiles);
|
|
7312
7353
|
}
|
|
@@ -7325,7 +7366,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
7325
7366
|
const configPath = join9(projectRoot, "design-system.config.ts");
|
|
7326
7367
|
let config2 = null;
|
|
7327
7368
|
try {
|
|
7328
|
-
const mgr = new
|
|
7369
|
+
const mgr = new DesignSystemManager5(configPath);
|
|
7329
7370
|
config2 = mgr.getConfig();
|
|
7330
7371
|
} catch {
|
|
7331
7372
|
}
|
|
@@ -7333,7 +7374,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
7333
7374
|
const provider = getComponentProvider();
|
|
7334
7375
|
for (const [componentId, needed] of neededExports) {
|
|
7335
7376
|
const componentFile = join9(uiDir, `${componentId}.tsx`);
|
|
7336
|
-
if (!
|
|
7377
|
+
if (!existsSync11(componentFile)) {
|
|
7337
7378
|
if (provider.has(componentId)) {
|
|
7338
7379
|
try {
|
|
7339
7380
|
const result = await provider.installComponent(componentId, projectRoot);
|
|
@@ -7402,7 +7443,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
7402
7443
|
}
|
|
7403
7444
|
for (const [componentId, needed] of neededSharedExports) {
|
|
7404
7445
|
const componentFile = join9(sharedDir, `${componentId}.tsx`);
|
|
7405
|
-
if (!
|
|
7446
|
+
if (!existsSync11(componentFile)) continue;
|
|
7406
7447
|
let content = readFileSync9(componentFile, "utf-8");
|
|
7407
7448
|
const exportRe = /export\s+(?:const|function|class)\s+(\w+)|export\s*\{([^}]+)\}/g;
|
|
7408
7449
|
const existingExports = /* @__PURE__ */ new Set();
|
|
@@ -7426,9 +7467,9 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
7426
7467
|
}
|
|
7427
7468
|
async function backfillPageAnalysis(projectRoot) {
|
|
7428
7469
|
const configPath = join9(projectRoot, "design-system.config.ts");
|
|
7429
|
-
if (!
|
|
7470
|
+
if (!existsSync11(configPath)) return;
|
|
7430
7471
|
try {
|
|
7431
|
-
const mgr = new
|
|
7472
|
+
const mgr = new DesignSystemManager5(configPath);
|
|
7432
7473
|
const config2 = mgr.getConfig();
|
|
7433
7474
|
let changed = false;
|
|
7434
7475
|
for (const page of config2.pages) {
|
|
@@ -7443,7 +7484,7 @@ async function backfillPageAnalysis(projectRoot) {
|
|
|
7443
7484
|
} else {
|
|
7444
7485
|
filePath = join9(projectRoot, "app", route.slice(1), "page.tsx");
|
|
7445
7486
|
}
|
|
7446
|
-
if (!
|
|
7487
|
+
if (!existsSync11(filePath)) continue;
|
|
7447
7488
|
const code = readFileSync9(filePath, "utf-8");
|
|
7448
7489
|
if (code.length < 50) continue;
|
|
7449
7490
|
page.pageAnalysis = analyzePageCode(code);
|
|
@@ -7595,11 +7636,11 @@ async function openBrowser(url) {
|
|
|
7595
7636
|
}
|
|
7596
7637
|
function startDevServer(projectRoot) {
|
|
7597
7638
|
const packageJsonPath = resolve6(projectRoot, "package.json");
|
|
7598
|
-
if (!
|
|
7639
|
+
if (!existsSync11(packageJsonPath)) {
|
|
7599
7640
|
throw new Error('package.json not found. Run "coherent init" first.');
|
|
7600
7641
|
}
|
|
7601
|
-
const hasPnpm =
|
|
7602
|
-
const hasNpm =
|
|
7642
|
+
const hasPnpm = existsSync11(resolve6(projectRoot, "pnpm-lock.yaml"));
|
|
7643
|
+
const hasNpm = existsSync11(resolve6(projectRoot, "package-lock.json"));
|
|
7603
7644
|
const command = hasPnpm ? "pnpm" : hasNpm ? "npm" : "npx";
|
|
7604
7645
|
const args = hasPnpm ? ["dev", "--turbo"] : hasNpm ? ["run", "dev", "--", "--turbo"] : ["next", "dev", "--turbo"];
|
|
7605
7646
|
const child = spawn(command, args, {
|
|
@@ -7646,7 +7687,7 @@ async function previewCommand() {
|
|
|
7646
7687
|
if (needsGlobalsFix(projectRoot)) {
|
|
7647
7688
|
spinner.text = "Fixing globals.css...";
|
|
7648
7689
|
try {
|
|
7649
|
-
const dsm = new
|
|
7690
|
+
const dsm = new DesignSystemManager5(resolve6(projectRoot, "design-system.config.ts"));
|
|
7650
7691
|
await dsm.load();
|
|
7651
7692
|
const config2 = dsm.getConfig();
|
|
7652
7693
|
fixGlobalsCss(projectRoot, config2);
|
|
@@ -7657,7 +7698,7 @@ async function previewCommand() {
|
|
|
7657
7698
|
}
|
|
7658
7699
|
if (isTailwindV4(projectRoot)) {
|
|
7659
7700
|
const globalsPath = resolve6(projectRoot, "app", "globals.css");
|
|
7660
|
-
if (
|
|
7701
|
+
if (existsSync11(globalsPath)) {
|
|
7661
7702
|
const globalsContent = readFileSync9(globalsPath, "utf-8");
|
|
7662
7703
|
const cssIssues = validateV4GlobalsCss(globalsContent);
|
|
7663
7704
|
if (cssIssues.length > 0) {
|
|
@@ -7699,7 +7740,7 @@ async function previewCommand() {
|
|
|
7699
7740
|
import chalk12 from "chalk";
|
|
7700
7741
|
import ora4 from "ora";
|
|
7701
7742
|
import { spawn as spawn2 } from "child_process";
|
|
7702
|
-
import { existsSync as
|
|
7743
|
+
import { existsSync as existsSync12, rmSync as rmSync4, readdirSync as readdirSync6 } from "fs";
|
|
7703
7744
|
import { resolve as resolve7, join as join10, dirname as dirname4 } from "path";
|
|
7704
7745
|
import { readdir as readdir2, readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2, copyFile } from "fs/promises";
|
|
7705
7746
|
var COPY_EXCLUDE = /* @__PURE__ */ new Set([
|
|
@@ -7710,6 +7751,7 @@ var COPY_EXCLUDE = /* @__PURE__ */ new Set([
|
|
|
7710
7751
|
".tmp-e2e",
|
|
7711
7752
|
".cursorrules",
|
|
7712
7753
|
"CLAUDE.md",
|
|
7754
|
+
"AGENTS.md",
|
|
7713
7755
|
".claude",
|
|
7714
7756
|
".coherent",
|
|
7715
7757
|
"design-system.config.ts",
|
|
@@ -7734,17 +7776,17 @@ async function copyDir(src, dest) {
|
|
|
7734
7776
|
}
|
|
7735
7777
|
}
|
|
7736
7778
|
function checkProjectInitialized2(projectRoot) {
|
|
7737
|
-
return
|
|
7779
|
+
return existsSync12(resolve7(projectRoot, "design-system.config.ts")) && existsSync12(resolve7(projectRoot, "package.json"));
|
|
7738
7780
|
}
|
|
7739
7781
|
function getPackageManager2(projectRoot) {
|
|
7740
|
-
if (
|
|
7741
|
-
if (
|
|
7782
|
+
if (existsSync12(resolve7(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
7783
|
+
if (existsSync12(resolve7(projectRoot, "package-lock.json"))) return "npm";
|
|
7742
7784
|
return "npx";
|
|
7743
7785
|
}
|
|
7744
7786
|
async function patchNextConfigForExport(outRoot) {
|
|
7745
7787
|
for (const name of ["next.config.ts", "next.config.mjs", "next.config.js"]) {
|
|
7746
7788
|
const p = join10(outRoot, name);
|
|
7747
|
-
if (!
|
|
7789
|
+
if (!existsSync12(p)) continue;
|
|
7748
7790
|
let content = await readFile3(p, "utf-8");
|
|
7749
7791
|
if (content.includes("ignoreDuringBuilds")) return;
|
|
7750
7792
|
content = content.replace(
|
|
@@ -7786,7 +7828,7 @@ EXPOSE 3000
|
|
|
7786
7828
|
`;
|
|
7787
7829
|
async function ensureReadmeDeploySection(outRoot) {
|
|
7788
7830
|
const readmePath = join10(outRoot, "README.md");
|
|
7789
|
-
if (!
|
|
7831
|
+
if (!existsSync12(readmePath)) return;
|
|
7790
7832
|
try {
|
|
7791
7833
|
let content = await readFile3(readmePath, "utf-8");
|
|
7792
7834
|
if (/##\s+Deploy\b/m.test(content)) return;
|
|
@@ -7811,14 +7853,14 @@ async function countPages(outRoot) {
|
|
|
7811
7853
|
}
|
|
7812
7854
|
}
|
|
7813
7855
|
const appDir = join10(outRoot, "app");
|
|
7814
|
-
if (
|
|
7856
|
+
if (existsSync12(appDir)) await walk(appDir);
|
|
7815
7857
|
return n;
|
|
7816
7858
|
}
|
|
7817
7859
|
function countComponents(outRoot) {
|
|
7818
7860
|
let n = 0;
|
|
7819
7861
|
for (const sub of ["ui", "shared"]) {
|
|
7820
7862
|
const dir = join10(outRoot, "components", sub);
|
|
7821
|
-
if (!
|
|
7863
|
+
if (!existsSync12(dir)) continue;
|
|
7822
7864
|
try {
|
|
7823
7865
|
n += readdirSync6(dir).filter((f) => f.endsWith(".tsx") || f.endsWith(".jsx")).length;
|
|
7824
7866
|
} catch {
|
|
@@ -7829,7 +7871,7 @@ function countComponents(outRoot) {
|
|
|
7829
7871
|
var IMPORT_FROM_REGEX = /from\s+['"]([^'"]+)['"]/g;
|
|
7830
7872
|
async function collectImportedPackages(dir, extensions) {
|
|
7831
7873
|
const packages = /* @__PURE__ */ new Set();
|
|
7832
|
-
if (!
|
|
7874
|
+
if (!existsSync12(dir)) return packages;
|
|
7833
7875
|
async function walk(d) {
|
|
7834
7876
|
const entries = await readdir2(d, { withFileTypes: true });
|
|
7835
7877
|
for (const e of entries) {
|
|
@@ -7857,7 +7899,7 @@ async function collectImportedPackages(dir, extensions) {
|
|
|
7857
7899
|
}
|
|
7858
7900
|
async function findMissingDepsInExport(outRoot) {
|
|
7859
7901
|
const pkgPath = join10(outRoot, "package.json");
|
|
7860
|
-
if (!
|
|
7902
|
+
if (!existsSync12(pkgPath)) return [];
|
|
7861
7903
|
let pkg;
|
|
7862
7904
|
try {
|
|
7863
7905
|
pkg = JSON.parse(await readFile3(pkgPath, "utf-8"));
|
|
@@ -7878,32 +7920,32 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
7878
7920
|
const removed = [];
|
|
7879
7921
|
for (const p of ["app/design-system", "app/api/design-system"]) {
|
|
7880
7922
|
const full = join10(outputDir, p);
|
|
7881
|
-
if (
|
|
7923
|
+
if (existsSync12(full)) {
|
|
7882
7924
|
rmSync4(full, { recursive: true, force: true });
|
|
7883
7925
|
removed.push(p);
|
|
7884
7926
|
}
|
|
7885
7927
|
}
|
|
7886
7928
|
const appNavPath = join10(outputDir, "app", "AppNav.tsx");
|
|
7887
|
-
if (
|
|
7929
|
+
if (existsSync12(appNavPath)) {
|
|
7888
7930
|
rmSync4(appNavPath, { force: true });
|
|
7889
7931
|
removed.push("app/AppNav.tsx");
|
|
7890
7932
|
}
|
|
7891
7933
|
const layoutPath = join10(outputDir, "app", "layout.tsx");
|
|
7892
|
-
if (
|
|
7934
|
+
if (existsSync12(layoutPath)) {
|
|
7893
7935
|
let layout = await readFile3(layoutPath, "utf-8");
|
|
7894
7936
|
layout = layout.replace(/import\s*\{?\s*AppNav\s*\}?\s*from\s*['"][^'"]+['"]\s*\n?/g, "");
|
|
7895
7937
|
layout = layout.replace(/\s*<AppNav\s*\/?\s*>\s*/g, "\n");
|
|
7896
7938
|
await writeFile3(layoutPath, layout, "utf-8");
|
|
7897
7939
|
}
|
|
7898
7940
|
const sharedHeaderPath = join10(outputDir, "components", "shared", "header.tsx");
|
|
7899
|
-
if (
|
|
7941
|
+
if (existsSync12(sharedHeaderPath)) {
|
|
7900
7942
|
let header = await readFile3(sharedHeaderPath, "utf-8");
|
|
7901
7943
|
header = header.replace(/<Link\s[^>]*href="\/design-system"[^>]*>[\s\S]*?<\/Link>/g, "");
|
|
7902
7944
|
header = header.replace(/\n\s*<>\s*\n/, "\n");
|
|
7903
7945
|
header = header.replace(/\n\s*<\/>\s*\n/, "\n");
|
|
7904
7946
|
await writeFile3(sharedHeaderPath, header, "utf-8");
|
|
7905
7947
|
}
|
|
7906
|
-
if (
|
|
7948
|
+
if (existsSync12(layoutPath)) {
|
|
7907
7949
|
let rootLayout = await readFile3(layoutPath, "utf-8");
|
|
7908
7950
|
const before = rootLayout;
|
|
7909
7951
|
rootLayout = rootLayout.replace(/<Link\s[^>]*href="\/design-system"[^>]*>[\s\S]*?<\/Link>/g, "");
|
|
@@ -7912,7 +7954,7 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
7912
7954
|
}
|
|
7913
7955
|
}
|
|
7914
7956
|
const guardPath2 = join10(outputDir, "app", "ShowWhenNotAuthRoute.tsx");
|
|
7915
|
-
if (
|
|
7957
|
+
if (existsSync12(guardPath2)) {
|
|
7916
7958
|
let guard = await readFile3(guardPath2, "utf-8");
|
|
7917
7959
|
guard = guard.replace(/['"],?\s*'\/design-system['"],?\s*/g, "");
|
|
7918
7960
|
const pathsMatch = guard.match(/HIDDEN_PATHS\s*=\s*\[([^\]]*)\]/);
|
|
@@ -7920,7 +7962,7 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
7920
7962
|
if (remaining.length === 0) {
|
|
7921
7963
|
rmSync4(guardPath2, { force: true });
|
|
7922
7964
|
removed.push("app/ShowWhenNotAuthRoute.tsx");
|
|
7923
|
-
if (
|
|
7965
|
+
if (existsSync12(layoutPath)) {
|
|
7924
7966
|
let layout = await readFile3(layoutPath, "utf-8");
|
|
7925
7967
|
layout = layout.replace(/import\s+\w+\s+from\s*['"]\.\/ShowWhenNotAuthRoute['"]\s*\n?/g, "");
|
|
7926
7968
|
layout = layout.replace(/\s*<ShowWhenNotAuthRoute>\s*\n?/g, "\n");
|
|
@@ -7936,19 +7978,20 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
7936
7978
|
"design-system.config.ts",
|
|
7937
7979
|
".cursorrules",
|
|
7938
7980
|
"CLAUDE.md",
|
|
7981
|
+
"AGENTS.md",
|
|
7939
7982
|
".env",
|
|
7940
7983
|
".env.local",
|
|
7941
7984
|
"recommendations.md"
|
|
7942
7985
|
]) {
|
|
7943
7986
|
const full = join10(outputDir, name);
|
|
7944
|
-
if (
|
|
7987
|
+
if (existsSync12(full)) {
|
|
7945
7988
|
rmSync4(full, { force: true });
|
|
7946
7989
|
removed.push(name);
|
|
7947
7990
|
}
|
|
7948
7991
|
}
|
|
7949
7992
|
for (const dir of [".claude", ".coherent"]) {
|
|
7950
7993
|
const full = join10(outputDir, dir);
|
|
7951
|
-
if (
|
|
7994
|
+
if (existsSync12(full)) {
|
|
7952
7995
|
rmSync4(full, { recursive: true, force: true });
|
|
7953
7996
|
removed.push(dir + "/");
|
|
7954
7997
|
}
|
|
@@ -7972,7 +8015,7 @@ async function exportCommand(options = {}) {
|
|
|
7972
8015
|
process.exit(1);
|
|
7973
8016
|
}
|
|
7974
8017
|
spinner.text = "Copying project...";
|
|
7975
|
-
if (
|
|
8018
|
+
if (existsSync12(outputDir)) rmSync4(outputDir, { recursive: true, force: true });
|
|
7976
8019
|
await copyDir(projectRoot, outputDir);
|
|
7977
8020
|
spinner.succeed("Project copied");
|
|
7978
8021
|
if (!keepDs) {
|
|
@@ -8035,7 +8078,7 @@ async function exportCommand(options = {}) {
|
|
|
8035
8078
|
// src/commands/status.ts
|
|
8036
8079
|
import chalk13 from "chalk";
|
|
8037
8080
|
import { basename as basename2 } from "path";
|
|
8038
|
-
import { DesignSystemManager as
|
|
8081
|
+
import { DesignSystemManager as DesignSystemManager6 } from "@getcoherent/core";
|
|
8039
8082
|
function countTokens(tokens) {
|
|
8040
8083
|
let count = 0;
|
|
8041
8084
|
function countObj(obj) {
|
|
@@ -8067,7 +8110,7 @@ async function statusCommand() {
|
|
|
8067
8110
|
console.log(chalk13.gray("\u{1F4C4} Config: ") + chalk13.white(basename2(project.configPath)));
|
|
8068
8111
|
console.log("");
|
|
8069
8112
|
try {
|
|
8070
|
-
const manager = new
|
|
8113
|
+
const manager = new DesignSystemManager6(project.configPath);
|
|
8071
8114
|
await manager.load();
|
|
8072
8115
|
const config2 = manager.getConfig();
|
|
8073
8116
|
console.log(chalk13.cyan("\u{1F4CA} Statistics:\n"));
|
|
@@ -8110,7 +8153,7 @@ async function statusCommand() {
|
|
|
8110
8153
|
// src/commands/regenerate-docs.ts
|
|
8111
8154
|
import chalk14 from "chalk";
|
|
8112
8155
|
import ora5 from "ora";
|
|
8113
|
-
import { DesignSystemManager as
|
|
8156
|
+
import { DesignSystemManager as DesignSystemManager7 } from "@getcoherent/core";
|
|
8114
8157
|
import { ProjectScaffolder as ProjectScaffolder2 } from "@getcoherent/core";
|
|
8115
8158
|
async function regenerateDocsCommand() {
|
|
8116
8159
|
try {
|
|
@@ -8123,7 +8166,7 @@ async function regenerateDocsCommand() {
|
|
|
8123
8166
|
}
|
|
8124
8167
|
const spinner = ora5("Regenerating documentation pages...").start();
|
|
8125
8168
|
try {
|
|
8126
|
-
const manager = new
|
|
8169
|
+
const manager = new DesignSystemManager7(project.configPath);
|
|
8127
8170
|
await manager.load();
|
|
8128
8171
|
const config2 = manager.getConfig();
|
|
8129
8172
|
const scaffolder = new ProjectScaffolder2(config2, project.root);
|
|
@@ -8147,14 +8190,14 @@ async function regenerateDocsCommand() {
|
|
|
8147
8190
|
|
|
8148
8191
|
// src/commands/fix.ts
|
|
8149
8192
|
import chalk15 from "chalk";
|
|
8150
|
-
import { readdirSync as readdirSync7, readFileSync as readFileSync10, existsSync as
|
|
8193
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync10, existsSync as existsSync13, rmSync as rmSync5, mkdirSync as mkdirSync7 } from "fs";
|
|
8151
8194
|
import { resolve as resolve8, join as join11, relative as relative5, basename as basename3 } from "path";
|
|
8152
8195
|
import {
|
|
8153
|
-
DesignSystemManager as
|
|
8196
|
+
DesignSystemManager as DesignSystemManager8,
|
|
8154
8197
|
ComponentManager as ComponentManager5,
|
|
8155
8198
|
PageManager as PageManager3,
|
|
8156
8199
|
ComponentGenerator as ComponentGenerator3,
|
|
8157
|
-
loadManifest as
|
|
8200
|
+
loadManifest as loadManifest8,
|
|
8158
8201
|
saveManifest as saveManifest5
|
|
8159
8202
|
} from "@getcoherent/core";
|
|
8160
8203
|
function extractComponentIdsFromCode2(code) {
|
|
@@ -8206,7 +8249,7 @@ async function fixCommand(opts = {}) {
|
|
|
8206
8249
|
}
|
|
8207
8250
|
if (!skipCache) {
|
|
8208
8251
|
const nextDir = join11(projectRoot, ".next");
|
|
8209
|
-
if (
|
|
8252
|
+
if (existsSync13(nextDir)) {
|
|
8210
8253
|
if (!dryRun) rmSync5(nextDir, { recursive: true, force: true });
|
|
8211
8254
|
fixes.push("Cleared build cache");
|
|
8212
8255
|
console.log(chalk15.green(" \u2714 Cleared build cache"));
|
|
@@ -8240,7 +8283,7 @@ async function fixCommand(opts = {}) {
|
|
|
8240
8283
|
let cm = null;
|
|
8241
8284
|
let pm = null;
|
|
8242
8285
|
if (allComponentIds.size > 0) {
|
|
8243
|
-
dsm = new
|
|
8286
|
+
dsm = new DesignSystemManager8(project.configPath);
|
|
8244
8287
|
await dsm.load();
|
|
8245
8288
|
const config2 = dsm.getConfig();
|
|
8246
8289
|
cm = new ComponentManager5(config2);
|
|
@@ -8253,7 +8296,7 @@ async function fixCommand(opts = {}) {
|
|
|
8253
8296
|
} else {
|
|
8254
8297
|
const fileName = toKebabCase(id) + ".tsx";
|
|
8255
8298
|
const filePath = resolve8(projectRoot, "components", "ui", fileName);
|
|
8256
|
-
if (!
|
|
8299
|
+
if (!existsSync13(filePath)) missingFiles.push(id);
|
|
8257
8300
|
}
|
|
8258
8301
|
}
|
|
8259
8302
|
const provider = getComponentProvider();
|
|
@@ -8302,8 +8345,8 @@ async function fixCommand(opts = {}) {
|
|
|
8302
8345
|
}
|
|
8303
8346
|
}
|
|
8304
8347
|
}
|
|
8305
|
-
if (!dsm &&
|
|
8306
|
-
dsm = new
|
|
8348
|
+
if (!dsm && existsSync13(project.configPath)) {
|
|
8349
|
+
dsm = new DesignSystemManager8(project.configPath);
|
|
8307
8350
|
await dsm.load();
|
|
8308
8351
|
}
|
|
8309
8352
|
if (dsm && dsm.getConfig().name === "My App") {
|
|
@@ -8311,7 +8354,7 @@ async function fixCommand(opts = {}) {
|
|
|
8311
8354
|
let derivedName = null;
|
|
8312
8355
|
try {
|
|
8313
8356
|
const pkgPath = resolve8(projectRoot, "package.json");
|
|
8314
|
-
if (
|
|
8357
|
+
if (existsSync13(pkgPath)) {
|
|
8315
8358
|
const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
8316
8359
|
if (typeof pkg.name === "string" && pkg.name) {
|
|
8317
8360
|
derivedName = toTitleCase2(pkg.name);
|
|
@@ -8349,7 +8392,7 @@ async function fixCommand(opts = {}) {
|
|
|
8349
8392
|
const configName = dsm.getConfig().name;
|
|
8350
8393
|
if (configName && configName !== "My App") {
|
|
8351
8394
|
const appLayoutPath = resolve8(projectRoot, "app", "(app)", "layout.tsx");
|
|
8352
|
-
if (
|
|
8395
|
+
if (existsSync13(appLayoutPath)) {
|
|
8353
8396
|
let appLayoutCode = readFileSync10(appLayoutPath, "utf-8");
|
|
8354
8397
|
if (appLayoutCode.includes("My App")) {
|
|
8355
8398
|
appLayoutCode = appLayoutCode.replace(/My App/g, configName);
|
|
@@ -8368,7 +8411,7 @@ async function fixCommand(opts = {}) {
|
|
|
8368
8411
|
}
|
|
8369
8412
|
}
|
|
8370
8413
|
const sharedDir = resolve8(projectRoot, "components", "shared");
|
|
8371
|
-
if (
|
|
8414
|
+
if (existsSync13(sharedDir)) {
|
|
8372
8415
|
try {
|
|
8373
8416
|
for (const f of readdirSync7(sharedDir).filter((n) => n.endsWith(".tsx"))) {
|
|
8374
8417
|
const sharedPath = join11(sharedDir, f);
|
|
@@ -8430,7 +8473,7 @@ async function fixCommand(opts = {}) {
|
|
|
8430
8473
|
const plan = loadPlan2(projectRoot);
|
|
8431
8474
|
if (plan) {
|
|
8432
8475
|
if (!dsm) {
|
|
8433
|
-
dsm = new
|
|
8476
|
+
dsm = new DesignSystemManager8(project.configPath);
|
|
8434
8477
|
await dsm.load();
|
|
8435
8478
|
}
|
|
8436
8479
|
await ensurePlanGroupLayouts2(projectRoot, plan, {}, dsm.getConfig());
|
|
@@ -8439,9 +8482,9 @@ async function fixCommand(opts = {}) {
|
|
|
8439
8482
|
console.log(chalk15.green(` \u2714 Verified group layouts: ${layoutTypes}`));
|
|
8440
8483
|
const hasSidebar = plan.groups.some((g) => g.layout === "sidebar" || g.layout === "both");
|
|
8441
8484
|
const sidebarPath = resolve8(projectRoot, "components", "shared", "sidebar.tsx");
|
|
8442
|
-
if (hasSidebar && !
|
|
8485
|
+
if (hasSidebar && !existsSync13(sidebarPath) && !dryRun) {
|
|
8443
8486
|
if (!dsm) {
|
|
8444
|
-
dsm = new
|
|
8487
|
+
dsm = new DesignSystemManager8(project.configPath);
|
|
8445
8488
|
await dsm.load();
|
|
8446
8489
|
}
|
|
8447
8490
|
const { PageGenerator } = await import("@getcoherent/core");
|
|
@@ -8458,7 +8501,7 @@ async function fixCommand(opts = {}) {
|
|
|
8458
8501
|
}
|
|
8459
8502
|
if (hasSidebar && !dryRun) {
|
|
8460
8503
|
const rootLayoutPath = resolve8(projectRoot, "app", "layout.tsx");
|
|
8461
|
-
if (
|
|
8504
|
+
if (existsSync13(rootLayoutPath)) {
|
|
8462
8505
|
let rootCode = readFileSync10(rootLayoutPath, "utf-8");
|
|
8463
8506
|
if (rootCode.includes("<Header")) {
|
|
8464
8507
|
rootCode = rootCode.replace(/import\s*\{[^}]*Header[^}]*\}[^;\n]*[;\n]?\s*/g, "").replace(/import\s*\{[^}]*Footer[^}]*\}[^;\n]*[;\n]?\s*/g, "").replace(/import\s+ShowWhenNotAuthRoute[^;\n]*[;\n]?\s*/g, "").replace(/<ShowWhenNotAuthRoute>[\s\S]*?<\/ShowWhenNotAuthRoute>/g, (match) => {
|
|
@@ -8477,7 +8520,7 @@ async function fixCommand(opts = {}) {
|
|
|
8477
8520
|
}
|
|
8478
8521
|
}
|
|
8479
8522
|
const publicLayoutPath = resolve8(projectRoot, "app", "(public)", "layout.tsx");
|
|
8480
|
-
const publicExists =
|
|
8523
|
+
const publicExists = existsSync13(publicLayoutPath);
|
|
8481
8524
|
const needsPublicLayout = !publicExists || !readFileSync10(publicLayoutPath, "utf-8").includes("<Header");
|
|
8482
8525
|
if (needsPublicLayout) {
|
|
8483
8526
|
const { buildPublicLayoutCodeForSidebar } = await import("./code-generator-YSGVHVNN.js");
|
|
@@ -8491,7 +8534,7 @@ async function fixCommand(opts = {}) {
|
|
|
8491
8534
|
}
|
|
8492
8535
|
}
|
|
8493
8536
|
const sidebarComponentPath2 = resolve8(projectRoot, "components", "shared", "sidebar.tsx");
|
|
8494
|
-
if (
|
|
8537
|
+
if (existsSync13(sidebarComponentPath2)) {
|
|
8495
8538
|
const existingSidebarCode = readFileSync10(sidebarComponentPath2, "utf-8");
|
|
8496
8539
|
const sidebarConfigName = dsm?.getConfig().name ?? "";
|
|
8497
8540
|
const hasWrongName = existingSidebarCode.includes("My App") && sidebarConfigName !== "My App";
|
|
@@ -8499,7 +8542,7 @@ async function fixCommand(opts = {}) {
|
|
|
8499
8542
|
const isBroken = !isValidTsx(existingSidebarCode, projectRoot);
|
|
8500
8543
|
if (hasWrongName || hasTrigger || isBroken) {
|
|
8501
8544
|
if (!dsm) {
|
|
8502
|
-
dsm = new
|
|
8545
|
+
dsm = new DesignSystemManager8(project.configPath);
|
|
8503
8546
|
await dsm.load();
|
|
8504
8547
|
}
|
|
8505
8548
|
const { PageGenerator } = await import("@getcoherent/core");
|
|
@@ -8520,7 +8563,7 @@ async function fixCommand(opts = {}) {
|
|
|
8520
8563
|
}
|
|
8521
8564
|
const rootPagePath = resolve8(projectRoot, "app", "page.tsx");
|
|
8522
8565
|
const publicPagePath = resolve8(projectRoot, "app", "(public)", "page.tsx");
|
|
8523
|
-
if (
|
|
8566
|
+
if (existsSync13(rootPagePath) && !existsSync13(publicPagePath)) {
|
|
8524
8567
|
const { renameSync } = await import("fs");
|
|
8525
8568
|
mkdirSync7(resolve8(projectRoot, "app", "(public)"), { recursive: true });
|
|
8526
8569
|
renameSync(rootPagePath, publicPagePath);
|
|
@@ -8528,7 +8571,7 @@ async function fixCommand(opts = {}) {
|
|
|
8528
8571
|
console.log(chalk15.green(" \u2714 Moved app/page.tsx \u2192 app/(public)/page.tsx (gets Header/Footer)"));
|
|
8529
8572
|
}
|
|
8530
8573
|
const themeTogglePath = resolve8(projectRoot, "components", "shared", "theme-toggle.tsx");
|
|
8531
|
-
if (!
|
|
8574
|
+
if (!existsSync13(themeTogglePath)) {
|
|
8532
8575
|
const { generateThemeToggleCode } = await import("./code-generator-YSGVHVNN.js");
|
|
8533
8576
|
mkdirSync7(resolve8(projectRoot, "components", "shared"), { recursive: true });
|
|
8534
8577
|
const themeResult = safeWrite(themeTogglePath, generateThemeToggleCode(), projectRoot, backups);
|
|
@@ -8545,7 +8588,7 @@ async function fixCommand(opts = {}) {
|
|
|
8545
8588
|
console.log(chalk15.yellow(` \u26A0 Layout repair skipped: ${err instanceof Error ? err.message : "unknown error"}`));
|
|
8546
8589
|
}
|
|
8547
8590
|
const appLayoutRepairPath = resolve8(projectRoot, "app", "(app)", "layout.tsx");
|
|
8548
|
-
if (
|
|
8591
|
+
if (existsSync13(appLayoutRepairPath) && dsm) {
|
|
8549
8592
|
const appLayoutCode = readFileSync10(appLayoutRepairPath, "utf-8");
|
|
8550
8593
|
const isMinimal = appLayoutCode.length < 500 && !appLayoutCode.includes("Header") && !appLayoutCode.includes("Footer") && !appLayoutCode.includes("Sidebar") && !appLayoutCode.includes("SidebarProvider") && !appLayoutCode.includes("SidebarTrigger") && !appLayoutCode.includes("Sheet");
|
|
8551
8594
|
const navType = dsm.getConfig().navigation?.type || "header";
|
|
@@ -8683,7 +8726,7 @@ async function fixCommand(opts = {}) {
|
|
|
8683
8726
|
}
|
|
8684
8727
|
}
|
|
8685
8728
|
try {
|
|
8686
|
-
let manifest = await
|
|
8729
|
+
let manifest = await loadManifest8(project.root);
|
|
8687
8730
|
let manifestModified = false;
|
|
8688
8731
|
const { manifest: cleaned, removed: orphaned } = removeOrphanedEntries(project.root, manifest);
|
|
8689
8732
|
if (orphaned.length > 0) {
|
|
@@ -8763,7 +8806,7 @@ async function fixCommand(opts = {}) {
|
|
|
8763
8806
|
}
|
|
8764
8807
|
try {
|
|
8765
8808
|
const tsconfigPath = resolve8(projectRoot, "tsconfig.json");
|
|
8766
|
-
if (
|
|
8809
|
+
if (existsSync13(tsconfigPath)) {
|
|
8767
8810
|
const { runTscCheck, applyDeterministicFixes } = await import("./tsc-autofix-S5PKMFSC.js");
|
|
8768
8811
|
const { applyAiFixes } = await import("./tsc-ai-fix-O3EMRWV2.js");
|
|
8769
8812
|
const tscErrors = runTscCheck(projectRoot);
|
|
@@ -8851,8 +8894,8 @@ async function fixCommand(opts = {}) {
|
|
|
8851
8894
|
// src/commands/check.ts
|
|
8852
8895
|
import chalk16 from "chalk";
|
|
8853
8896
|
import { resolve as resolve9 } from "path";
|
|
8854
|
-
import { readdirSync as readdirSync8, readFileSync as readFileSync11, statSync as statSync3, existsSync as
|
|
8855
|
-
import { loadManifest as
|
|
8897
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync11, statSync as statSync3, existsSync as existsSync14 } from "fs";
|
|
8898
|
+
import { loadManifest as loadManifest9 } from "@getcoherent/core";
|
|
8856
8899
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", "design-system"]);
|
|
8857
8900
|
function findTsxFiles(dir) {
|
|
8858
8901
|
const results = [];
|
|
@@ -8887,8 +8930,8 @@ async function checkCommand(opts = {}) {
|
|
|
8887
8930
|
};
|
|
8888
8931
|
let validRoutes = [];
|
|
8889
8932
|
try {
|
|
8890
|
-
const { DesignSystemManager:
|
|
8891
|
-
const dsm = new
|
|
8933
|
+
const { DesignSystemManager: DesignSystemManager14 } = await import("@getcoherent/core");
|
|
8934
|
+
const dsm = new DesignSystemManager14(project.configPath);
|
|
8892
8935
|
await dsm.load();
|
|
8893
8936
|
validRoutes = dsm.getConfig().pages.map((p) => p.route).filter(Boolean);
|
|
8894
8937
|
} catch {
|
|
@@ -8979,11 +9022,11 @@ async function checkCommand(opts = {}) {
|
|
|
8979
9022
|
\u{1F517} Internal Links`) + chalk16.dim(` \u2014 all ${result.links.total} links resolve \u2713`));
|
|
8980
9023
|
}
|
|
8981
9024
|
try {
|
|
8982
|
-
const manifest = await
|
|
9025
|
+
const manifest = await loadManifest9(project.root);
|
|
8983
9026
|
if (manifest.shared.length > 0) {
|
|
8984
9027
|
for (const entry of manifest.shared) {
|
|
8985
9028
|
const fullPath = resolve9(project.root, entry.file);
|
|
8986
|
-
if (!
|
|
9029
|
+
if (!existsSync14(fullPath)) {
|
|
8987
9030
|
result.pages.withErrors++;
|
|
8988
9031
|
if (!opts.json) console.log(chalk16.red(`
|
|
8989
9032
|
\u2717 Missing shared component file: ${entry.id} (${entry.file})`));
|
|
@@ -8995,7 +9038,7 @@ async function checkCommand(opts = {}) {
|
|
|
8995
9038
|
}
|
|
8996
9039
|
if (!skipShared) {
|
|
8997
9040
|
try {
|
|
8998
|
-
const manifest = await
|
|
9041
|
+
const manifest = await loadManifest9(projectRoot);
|
|
8999
9042
|
if (!opts.json && manifest.shared.length > 0) {
|
|
9000
9043
|
console.log(chalk16.cyan(`
|
|
9001
9044
|
\u{1F9E9} Shared Components`) + chalk16.dim(` (${manifest.shared.length} registered)
|
|
@@ -9008,7 +9051,7 @@ async function checkCommand(opts = {}) {
|
|
|
9008
9051
|
let _nameMismatch = 0;
|
|
9009
9052
|
for (const entry of manifest.shared) {
|
|
9010
9053
|
const filePath = resolve9(projectRoot, entry.file);
|
|
9011
|
-
const fileExists =
|
|
9054
|
+
const fileExists = existsSync14(filePath);
|
|
9012
9055
|
if (!fileExists) {
|
|
9013
9056
|
_orphaned++;
|
|
9014
9057
|
if (!opts.json) {
|
|
@@ -9086,7 +9129,7 @@ async function checkCommand(opts = {}) {
|
|
|
9086
9129
|
id: e.id,
|
|
9087
9130
|
name: e.name,
|
|
9088
9131
|
type: e.type,
|
|
9089
|
-
status:
|
|
9132
|
+
status: existsSync14(resolve9(projectRoot, e.file)) ? "ok" : "unused",
|
|
9090
9133
|
message: "",
|
|
9091
9134
|
suggestions: void 0
|
|
9092
9135
|
}))
|
|
@@ -9098,9 +9141,9 @@ async function checkCommand(opts = {}) {
|
|
|
9098
9141
|
try {
|
|
9099
9142
|
const { validateReuse } = await import("./reuse-validator-XR2ZEYC4.js");
|
|
9100
9143
|
const { inferPageTypeFromRoute: inferPageTypeFromRoute2 } = await import("./design-constraints-HGNEY3W3.js");
|
|
9101
|
-
const manifest = await
|
|
9144
|
+
const manifest = await loadManifest9(projectRoot);
|
|
9102
9145
|
const appDir = resolve9(projectRoot, "app");
|
|
9103
|
-
const pageFiles =
|
|
9146
|
+
const pageFiles = existsSync14(appDir) ? findTsxFiles(appDir) : [];
|
|
9104
9147
|
if (manifest.shared.length > 0 && pageFiles.length > 0) {
|
|
9105
9148
|
const reuseWarnings = [];
|
|
9106
9149
|
for (const file of pageFiles) {
|
|
@@ -9175,7 +9218,7 @@ async function doctorCommand() {
|
|
|
9175
9218
|
import chalk19 from "chalk";
|
|
9176
9219
|
async function rulesCommand() {
|
|
9177
9220
|
try {
|
|
9178
|
-
const result = await
|
|
9221
|
+
const result = await regenerateAllHarnessFiles();
|
|
9179
9222
|
if (!result.written) {
|
|
9180
9223
|
exitNotCoherent();
|
|
9181
9224
|
}
|
|
@@ -9183,7 +9226,7 @@ async function rulesCommand() {
|
|
|
9183
9226
|
if (result.sharedCount !== void 0) parts.push(`${result.sharedCount} shared components`);
|
|
9184
9227
|
if (result.tokenKeys !== void 0) parts.push(`${result.tokenKeys} design token keys`);
|
|
9185
9228
|
const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
9186
|
-
console.log(chalk19.green(`\u2714 Updated .cursorrules and
|
|
9229
|
+
console.log(chalk19.green(`\u2714 Updated .cursorrules, CLAUDE.md, and AGENTS.md${summary}
|
|
9187
9230
|
`));
|
|
9188
9231
|
} catch (error) {
|
|
9189
9232
|
console.error(chalk19.red("\u274C Command failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -9209,13 +9252,13 @@ async function auditCommand(options) {
|
|
|
9209
9252
|
import { Command } from "commander";
|
|
9210
9253
|
import chalk22 from "chalk";
|
|
9211
9254
|
import {
|
|
9212
|
-
DesignSystemManager as
|
|
9255
|
+
DesignSystemManager as DesignSystemManager9,
|
|
9213
9256
|
ComponentManager as ComponentManager6,
|
|
9214
|
-
loadManifest as
|
|
9257
|
+
loadManifest as loadManifest10,
|
|
9215
9258
|
generateSharedComponent as generateSharedComponent4,
|
|
9216
9259
|
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout2
|
|
9217
9260
|
} from "@getcoherent/core";
|
|
9218
|
-
import { existsSync as
|
|
9261
|
+
import { existsSync as existsSync15 } from "fs";
|
|
9219
9262
|
import { resolve as resolve10 } from "path";
|
|
9220
9263
|
|
|
9221
9264
|
// src/utils/ds-files.ts
|
|
@@ -9248,11 +9291,11 @@ function createComponentsCommand() {
|
|
|
9248
9291
|
cmd.command("list").description("List all components (shared + UI)").option("--json", "Machine-readable JSON output").action(async (opts) => {
|
|
9249
9292
|
const project = findConfig();
|
|
9250
9293
|
if (!project) exitNotCoherent();
|
|
9251
|
-
const dsm = new
|
|
9294
|
+
const dsm = new DesignSystemManager9(project.configPath);
|
|
9252
9295
|
await dsm.load();
|
|
9253
9296
|
const config2 = dsm.getConfig();
|
|
9254
9297
|
const cm = new ComponentManager6(config2);
|
|
9255
|
-
const manifest = await
|
|
9298
|
+
const manifest = await loadManifest10(project.root);
|
|
9256
9299
|
if (opts.json) {
|
|
9257
9300
|
const installed2 = cm.getAllComponents();
|
|
9258
9301
|
console.log(JSON.stringify({ shared: manifest.shared, ui: installed2 }, null, 2));
|
|
@@ -9312,7 +9355,7 @@ function createComponentsCommand() {
|
|
|
9312
9355
|
sharedCmd.option("--json", "Machine-readable JSON output").option("--verbose", "Show file paths and usage details").action(async (opts) => {
|
|
9313
9356
|
const project = findConfig();
|
|
9314
9357
|
if (!project) exitNotCoherent();
|
|
9315
|
-
const manifest = await
|
|
9358
|
+
const manifest = await loadManifest10(project.root);
|
|
9316
9359
|
if (opts.json) {
|
|
9317
9360
|
console.log(JSON.stringify(manifest, null, 2));
|
|
9318
9361
|
return;
|
|
@@ -9370,9 +9413,9 @@ function createComponentsCommand() {
|
|
|
9370
9413
|
if (updated) console.log(chalk22.cyan(" Updated app/layout.tsx to use shared layout components.\n"));
|
|
9371
9414
|
}
|
|
9372
9415
|
const sharedPagePath = resolve10(project.root, "app/design-system/shared/page.tsx");
|
|
9373
|
-
if (!
|
|
9416
|
+
if (!existsSync15(sharedPagePath)) {
|
|
9374
9417
|
try {
|
|
9375
|
-
const dsm = new
|
|
9418
|
+
const dsm = new DesignSystemManager9(project.configPath);
|
|
9376
9419
|
await dsm.load();
|
|
9377
9420
|
const config2 = dsm.getConfig();
|
|
9378
9421
|
const written = await writeDesignSystemFiles(project.root, config2, { sharedOnly: true });
|
|
@@ -9384,7 +9427,7 @@ function createComponentsCommand() {
|
|
|
9384
9427
|
}
|
|
9385
9428
|
}
|
|
9386
9429
|
try {
|
|
9387
|
-
await
|
|
9430
|
+
await writeAllHarnessFiles(project.root);
|
|
9388
9431
|
} catch (e) {
|
|
9389
9432
|
if (process.env.COHERENT_DEBUG === "1") console.error(chalk22.dim("Could not update .cursorrules:"), e);
|
|
9390
9433
|
}
|
|
@@ -9397,7 +9440,7 @@ import chalk23 from "chalk";
|
|
|
9397
9440
|
import ora6 from "ora";
|
|
9398
9441
|
import { writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
|
|
9399
9442
|
import { resolve as resolve11, join as join13, dirname as dirname6 } from "path";
|
|
9400
|
-
import { existsSync as
|
|
9443
|
+
import { existsSync as existsSync16 } from "fs";
|
|
9401
9444
|
import {
|
|
9402
9445
|
FigmaClient,
|
|
9403
9446
|
parseFigmaFileResponse,
|
|
@@ -9410,7 +9453,7 @@ import {
|
|
|
9410
9453
|
generateSharedComponent as generateSharedComponent5,
|
|
9411
9454
|
generatePagesFromFigma,
|
|
9412
9455
|
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout3,
|
|
9413
|
-
DesignSystemManager as
|
|
9456
|
+
DesignSystemManager as DesignSystemManager10,
|
|
9414
9457
|
validateConfig,
|
|
9415
9458
|
FRAMEWORK_VERSIONS as FRAMEWORK_VERSIONS2,
|
|
9416
9459
|
CLI_VERSION as CLI_VERSION3
|
|
@@ -9645,8 +9688,8 @@ async function importFigmaAction(urlOrKey, opts) {
|
|
|
9645
9688
|
if (!dryRun) {
|
|
9646
9689
|
spinner.start("Updating design-system.config.ts...");
|
|
9647
9690
|
const configPath = resolve11(projectRoot, DESIGN_SYSTEM_CONFIG_PATH);
|
|
9648
|
-
const dsm = new
|
|
9649
|
-
if (
|
|
9691
|
+
const dsm = new DesignSystemManager10(configPath);
|
|
9692
|
+
if (existsSync16(configPath)) {
|
|
9650
9693
|
await dsm.load();
|
|
9651
9694
|
const existing = dsm.getConfig();
|
|
9652
9695
|
dsm.updateConfig({
|
|
@@ -9676,7 +9719,7 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
|
|
|
9676
9719
|
spinner.succeed("design-system.config.ts updated");
|
|
9677
9720
|
spinner.start("Ensuring root layout...");
|
|
9678
9721
|
const layoutPath = join13(projectRoot, "app/layout.tsx");
|
|
9679
|
-
if (!
|
|
9722
|
+
if (!existsSync16(layoutPath)) {
|
|
9680
9723
|
await mkdir4(dirname6(layoutPath), { recursive: true });
|
|
9681
9724
|
await writeFile5(layoutPath, MINIMAL_ROOT_LAYOUT, "utf-8");
|
|
9682
9725
|
stats.filesWritten.push("app/layout.tsx");
|
|
@@ -9691,7 +9734,7 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
|
|
|
9691
9734
|
stats.dsFilesWritten = dsFiles.length;
|
|
9692
9735
|
spinner.succeed(`DS viewer: ${dsFiles.length} files`);
|
|
9693
9736
|
try {
|
|
9694
|
-
await
|
|
9737
|
+
await writeAllHarnessFiles(projectRoot);
|
|
9695
9738
|
} catch (e) {
|
|
9696
9739
|
if (process.env.COHERENT_DEBUG === "1") console.error(chalk23.dim("Could not update .cursorrules:"), e);
|
|
9697
9740
|
}
|
|
@@ -9742,7 +9785,7 @@ function printReport(stats, opts) {
|
|
|
9742
9785
|
// src/commands/ds.ts
|
|
9743
9786
|
import chalk24 from "chalk";
|
|
9744
9787
|
import ora7 from "ora";
|
|
9745
|
-
import { DesignSystemManager as
|
|
9788
|
+
import { DesignSystemManager as DesignSystemManager11 } from "@getcoherent/core";
|
|
9746
9789
|
async function dsRegenerateCommand() {
|
|
9747
9790
|
try {
|
|
9748
9791
|
const project = findConfig();
|
|
@@ -9750,7 +9793,7 @@ async function dsRegenerateCommand() {
|
|
|
9750
9793
|
exitNotCoherent();
|
|
9751
9794
|
}
|
|
9752
9795
|
const spinner = ora7("Loading config and regenerating Design System pages...").start();
|
|
9753
|
-
const dsm = new
|
|
9796
|
+
const dsm = new DesignSystemManager11(project.configPath);
|
|
9754
9797
|
await dsm.load();
|
|
9755
9798
|
const config2 = dsm.getConfig();
|
|
9756
9799
|
const written = await writeDesignSystemFiles(project.root, config2);
|
|
@@ -9766,9 +9809,9 @@ async function dsRegenerateCommand() {
|
|
|
9766
9809
|
// src/commands/update.ts
|
|
9767
9810
|
import chalk25 from "chalk";
|
|
9768
9811
|
import ora8 from "ora";
|
|
9769
|
-
import { readFileSync as readFileSync12, existsSync as
|
|
9812
|
+
import { readFileSync as readFileSync12, existsSync as existsSync17 } from "fs";
|
|
9770
9813
|
import { join as join14 } from "path";
|
|
9771
|
-
import { DesignSystemManager as
|
|
9814
|
+
import { DesignSystemManager as DesignSystemManager12, CLI_VERSION as CLI_VERSION4 } from "@getcoherent/core";
|
|
9772
9815
|
|
|
9773
9816
|
// src/utils/migrations.ts
|
|
9774
9817
|
var MIGRATIONS = [
|
|
@@ -9810,7 +9853,7 @@ async function updateCommand(opts) {
|
|
|
9810
9853
|
}
|
|
9811
9854
|
const spinner = ora8("Loading project configuration...").start();
|
|
9812
9855
|
try {
|
|
9813
|
-
const dsm = new
|
|
9856
|
+
const dsm = new DesignSystemManager12(project.configPath);
|
|
9814
9857
|
await dsm.load();
|
|
9815
9858
|
const config2 = dsm.getConfig();
|
|
9816
9859
|
const projectVersion = config2.coherentVersion || "0.0.0";
|
|
@@ -9850,7 +9893,7 @@ async function updateCommand(opts) {
|
|
|
9850
9893
|
const overlayFiles = await writeDesignSystemFiles(project.root, config2);
|
|
9851
9894
|
report.overlayFiles = overlayFiles.length;
|
|
9852
9895
|
spinner.text = "Updating .cursorrules and CLAUDE.md...";
|
|
9853
|
-
const rulesResult = await
|
|
9896
|
+
const rulesResult = await writeAllHarnessFiles(project.root);
|
|
9854
9897
|
report.rulesUpdated = rulesResult.written;
|
|
9855
9898
|
spinner.text = "Checking globals.css...";
|
|
9856
9899
|
report.missingCssVars = checkMissingCssVars(project.root);
|
|
@@ -9937,7 +9980,7 @@ var EXPECTED_CSS_VARS = [
|
|
|
9937
9980
|
];
|
|
9938
9981
|
function checkMissingCssVars(projectRoot) {
|
|
9939
9982
|
const globalsPath = join14(projectRoot, "app", "globals.css");
|
|
9940
|
-
if (!
|
|
9983
|
+
if (!existsSync17(globalsPath)) return [];
|
|
9941
9984
|
try {
|
|
9942
9985
|
const content = readFileSync12(globalsPath, "utf-8");
|
|
9943
9986
|
return EXPECTED_CSS_VARS.filter((v) => !content.includes(v));
|
|
@@ -9947,7 +9990,7 @@ function checkMissingCssVars(projectRoot) {
|
|
|
9947
9990
|
}
|
|
9948
9991
|
function patchGlobalsCss(projectRoot, missingVars) {
|
|
9949
9992
|
const globalsPath = join14(projectRoot, "app", "globals.css");
|
|
9950
|
-
if (!
|
|
9993
|
+
if (!existsSync17(globalsPath) || missingVars.length === 0) return;
|
|
9951
9994
|
const { writeFileSync: writeFileSync11 } = __require("fs");
|
|
9952
9995
|
let content = readFileSync12(globalsPath, "utf-8");
|
|
9953
9996
|
const defaultValues = {
|
|
@@ -10027,16 +10070,16 @@ async function undoCommand(options) {
|
|
|
10027
10070
|
// src/commands/sync.ts
|
|
10028
10071
|
import chalk27 from "chalk";
|
|
10029
10072
|
import ora9 from "ora";
|
|
10030
|
-
import { existsSync as
|
|
10073
|
+
import { existsSync as existsSync18, readFileSync as readFileSync13 } from "fs";
|
|
10031
10074
|
import { join as join15, relative as relative6, dirname as dirname7 } from "path";
|
|
10032
10075
|
import { readdir as readdir3, readFile as readFile4 } from "fs/promises";
|
|
10033
|
-
import { DesignSystemManager as
|
|
10034
|
-
import { loadManifest as
|
|
10076
|
+
import { DesignSystemManager as DesignSystemManager13 } from "@getcoherent/core";
|
|
10077
|
+
import { loadManifest as loadManifest11, saveManifest as saveManifest6, findSharedComponent } from "@getcoherent/core";
|
|
10035
10078
|
function extractTokensFromProject(projectRoot) {
|
|
10036
10079
|
const lightColors = {};
|
|
10037
10080
|
const darkColors = {};
|
|
10038
10081
|
const globalsPath = join15(projectRoot, "app", "globals.css");
|
|
10039
|
-
if (
|
|
10082
|
+
if (existsSync18(globalsPath)) {
|
|
10040
10083
|
const css = readFileSync13(globalsPath, "utf-8");
|
|
10041
10084
|
const rootMatch = css.match(/:root\s*\{([^}]+)\}/s);
|
|
10042
10085
|
if (rootMatch) parseVarsInto(rootMatch[1], lightColors);
|
|
@@ -10045,7 +10088,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
10045
10088
|
}
|
|
10046
10089
|
const layoutPath = join15(projectRoot, "app", "layout.tsx");
|
|
10047
10090
|
let layoutCode = "";
|
|
10048
|
-
if (
|
|
10091
|
+
if (existsSync18(layoutPath)) {
|
|
10049
10092
|
layoutCode = readFileSync13(layoutPath, "utf-8");
|
|
10050
10093
|
const rootInline = layoutCode.match(/:root\s*\{([^}]+)\}/s);
|
|
10051
10094
|
if (rootInline && Object.keys(lightColors).length === 0) {
|
|
@@ -10064,7 +10107,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
10064
10107
|
defaultMode = "dark";
|
|
10065
10108
|
}
|
|
10066
10109
|
let radius;
|
|
10067
|
-
const allCss = [
|
|
10110
|
+
const allCss = [existsSync18(globalsPath) ? readFileSync13(globalsPath, "utf-8") : "", layoutCode].join("\n");
|
|
10068
10111
|
const radiusMatch = allCss.match(/--radius:\s*([^;]+);/);
|
|
10069
10112
|
if (radiusMatch) radius = radiusMatch[1].trim();
|
|
10070
10113
|
return {
|
|
@@ -10088,7 +10131,7 @@ function parseVarsInto(block, target) {
|
|
|
10088
10131
|
async function detectCustomComponents(projectRoot, allPageCode) {
|
|
10089
10132
|
const results = [];
|
|
10090
10133
|
const componentsDir = join15(projectRoot, "components");
|
|
10091
|
-
if (!
|
|
10134
|
+
if (!existsSync18(componentsDir)) return results;
|
|
10092
10135
|
const files = [];
|
|
10093
10136
|
await walkForTsx(componentsDir, files, ["ui"]);
|
|
10094
10137
|
const fileResults = await Promise.all(
|
|
@@ -10279,11 +10322,11 @@ async function syncCommand(options = {}) {
|
|
|
10279
10322
|
const spinner = ora9("Scanning project files...").start();
|
|
10280
10323
|
try {
|
|
10281
10324
|
const appDir = join15(project.root, "app");
|
|
10282
|
-
if (!
|
|
10325
|
+
if (!existsSync18(appDir)) {
|
|
10283
10326
|
spinner.fail("No app/ directory found");
|
|
10284
10327
|
process.exit(1);
|
|
10285
10328
|
}
|
|
10286
|
-
const dsm = new
|
|
10329
|
+
const dsm = new DesignSystemManager13(project.configPath);
|
|
10287
10330
|
await dsm.load();
|
|
10288
10331
|
const config2 = dsm.getConfig();
|
|
10289
10332
|
const discoveredPages = await discoverPages(appDir);
|
|
@@ -10323,7 +10366,7 @@ async function syncCommand(options = {}) {
|
|
|
10323
10366
|
let reconcileResult = null;
|
|
10324
10367
|
if (doComponents) {
|
|
10325
10368
|
spinner.start("Reconciling shared components...");
|
|
10326
|
-
const manifest = await
|
|
10369
|
+
const manifest = await loadManifest11(project.root);
|
|
10327
10370
|
const { manifest: reconciledManifest, result: rr } = reconcileComponents(project.root, manifest);
|
|
10328
10371
|
reconcileResult = rr;
|
|
10329
10372
|
if (!dryRun) {
|
|
@@ -10411,9 +10454,8 @@ async function syncCommand(options = {}) {
|
|
|
10411
10454
|
const written = await writeDesignSystemFiles(project.root, config2);
|
|
10412
10455
|
spinner.succeed(`Regenerated ${written.length} Design System file(s)`);
|
|
10413
10456
|
spinner.start("Updating AI context files...");
|
|
10414
|
-
await
|
|
10415
|
-
|
|
10416
|
-
spinner.succeed("Updated .cursorrules and CLAUDE.md");
|
|
10457
|
+
await writeAllHarnessFiles(project.root);
|
|
10458
|
+
spinner.succeed("Updated .cursorrules, CLAUDE.md, and AGENTS.md");
|
|
10417
10459
|
}
|
|
10418
10460
|
console.log("");
|
|
10419
10461
|
console.log(chalk27.green(`\u2705 Design System ${dryRun ? "analyzed" : "synced"} with actual code
|
|
@@ -10509,7 +10551,7 @@ async function syncCommand(options = {}) {
|
|
|
10509
10551
|
// src/commands/migrate.ts
|
|
10510
10552
|
import chalk28 from "chalk";
|
|
10511
10553
|
import ora10 from "ora";
|
|
10512
|
-
import { existsSync as
|
|
10554
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, cpSync, rmSync as rmSync6, writeFileSync as writeFileSync9, readFileSync as readFileSync14, readdirSync as readdirSync9 } from "fs";
|
|
10513
10555
|
import { join as join16 } from "path";
|
|
10514
10556
|
function backupDir(projectRoot) {
|
|
10515
10557
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -10522,11 +10564,11 @@ function createBackup2(projectRoot) {
|
|
|
10522
10564
|
const uiDir = join16(projectRoot, "components", "ui");
|
|
10523
10565
|
const dest = backupDir(projectRoot);
|
|
10524
10566
|
mkdirSync8(dest, { recursive: true });
|
|
10525
|
-
if (
|
|
10567
|
+
if (existsSync19(uiDir)) {
|
|
10526
10568
|
cpSync(uiDir, join16(dest, "components-ui"), { recursive: true });
|
|
10527
10569
|
}
|
|
10528
10570
|
const configPath = join16(projectRoot, "design-system.config.ts");
|
|
10529
|
-
if (
|
|
10571
|
+
if (existsSync19(configPath)) {
|
|
10530
10572
|
cpSync(configPath, join16(dest, "design-system.config.ts"));
|
|
10531
10573
|
}
|
|
10532
10574
|
return dest;
|
|
@@ -10538,24 +10580,24 @@ function setGuard(projectRoot, backupPath) {
|
|
|
10538
10580
|
}
|
|
10539
10581
|
function clearGuard(projectRoot) {
|
|
10540
10582
|
const guard = guardPath(projectRoot);
|
|
10541
|
-
if (
|
|
10583
|
+
if (existsSync19(guard)) rmSync6(guard);
|
|
10542
10584
|
}
|
|
10543
10585
|
function rollback(projectRoot) {
|
|
10544
10586
|
const guard = guardPath(projectRoot);
|
|
10545
|
-
if (!
|
|
10587
|
+
if (!existsSync19(guard)) return false;
|
|
10546
10588
|
try {
|
|
10547
10589
|
const data = JSON.parse(readFileSync14(guard, "utf-8"));
|
|
10548
10590
|
const backup = data.backup;
|
|
10549
|
-
if (!
|
|
10591
|
+
if (!existsSync19(backup)) return false;
|
|
10550
10592
|
const uiBackup = join16(backup, "components-ui");
|
|
10551
10593
|
const uiDir = join16(projectRoot, "components", "ui");
|
|
10552
|
-
if (
|
|
10553
|
-
if (
|
|
10594
|
+
if (existsSync19(uiBackup)) {
|
|
10595
|
+
if (existsSync19(uiDir)) rmSync6(uiDir, { recursive: true });
|
|
10554
10596
|
cpSync(uiBackup, uiDir, { recursive: true });
|
|
10555
10597
|
}
|
|
10556
10598
|
const configBackup = join16(backup, "design-system.config.ts");
|
|
10557
10599
|
const configDest = join16(projectRoot, "design-system.config.ts");
|
|
10558
|
-
if (
|
|
10600
|
+
if (existsSync19(configBackup)) {
|
|
10559
10601
|
cpSync(configBackup, configDest);
|
|
10560
10602
|
}
|
|
10561
10603
|
clearGuard(projectRoot);
|
|
@@ -10583,13 +10625,13 @@ async function migrateAction(options) {
|
|
|
10583
10625
|
return;
|
|
10584
10626
|
}
|
|
10585
10627
|
const guard = guardPath(projectRoot);
|
|
10586
|
-
if (
|
|
10628
|
+
if (existsSync19(guard)) {
|
|
10587
10629
|
console.log(chalk28.yellow("A migration is already in progress."));
|
|
10588
10630
|
console.log(chalk28.dim("Run `coherent migrate --rollback` to undo, or delete .coherent/migration-in-progress"));
|
|
10589
10631
|
return;
|
|
10590
10632
|
}
|
|
10591
10633
|
const uiDir = join16(projectRoot, "components", "ui");
|
|
10592
|
-
if (!
|
|
10634
|
+
if (!existsSync19(uiDir)) {
|
|
10593
10635
|
console.log(chalk28.yellow("No components/ui directory found. Nothing to migrate."));
|
|
10594
10636
|
return;
|
|
10595
10637
|
}
|
|
@@ -10616,7 +10658,7 @@ Found ${migratable.length} component(s) to migrate:`));
|
|
|
10616
10658
|
try {
|
|
10617
10659
|
for (const id of migratable) {
|
|
10618
10660
|
const filePath = join16(uiDir, `${id}.tsx`);
|
|
10619
|
-
if (
|
|
10661
|
+
if (existsSync19(filePath)) rmSync6(filePath);
|
|
10620
10662
|
}
|
|
10621
10663
|
const results = await provider.installBatch(migratable, projectRoot, { force: true });
|
|
10622
10664
|
let migrated = 0;
|
|
@@ -10638,7 +10680,7 @@ Found ${migratable.length} component(s) to migrate:`));
|
|
|
10638
10680
|
}
|
|
10639
10681
|
|
|
10640
10682
|
// src/utils/update-notifier.ts
|
|
10641
|
-
import { existsSync as
|
|
10683
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
|
|
10642
10684
|
import { join as join17 } from "path";
|
|
10643
10685
|
import { homedir } from "os";
|
|
10644
10686
|
import chalk29 from "chalk";
|
|
@@ -10650,7 +10692,7 @@ var CACHE_FILE = join17(CACHE_DIR, "update-check.json");
|
|
|
10650
10692
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
10651
10693
|
function readCache() {
|
|
10652
10694
|
try {
|
|
10653
|
-
if (!
|
|
10695
|
+
if (!existsSync20(CACHE_FILE)) return null;
|
|
10654
10696
|
const raw = readFileSync15(CACHE_FILE, "utf-8");
|
|
10655
10697
|
return JSON.parse(raw);
|
|
10656
10698
|
} catch (e) {
|
|
@@ -10660,7 +10702,7 @@ function readCache() {
|
|
|
10660
10702
|
}
|
|
10661
10703
|
function writeCache(data) {
|
|
10662
10704
|
try {
|
|
10663
|
-
if (!
|
|
10705
|
+
if (!existsSync20(CACHE_DIR)) mkdirSync9(CACHE_DIR, { recursive: true });
|
|
10664
10706
|
writeFileSync10(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
10665
10707
|
} catch (e) {
|
|
10666
10708
|
if (DEBUG5) console.error("Failed to write update cache:", e);
|