@getcoherent/cli 0.6.49 → 0.6.51
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/{chunk-VLBVBF6V.js → chunk-IFJK5OPI.js} +2 -2
- package/dist/{chunk-E23FJX2I.js → chunk-NLL3SHN3.js} +17 -19
- package/dist/{chunk-NOM47EB4.js → chunk-U5SNPHVU.js} +90 -4
- package/dist/{chunk-4I7ATX6G.js → chunk-UZGT5APM.js} +2 -2
- package/dist/{code-generator-YSGVHVNN.js → code-generator-IZ6XM6WG.js} +4 -4
- package/dist/{design-constraints-HGNEY3W3.js → design-constraints-PFZDW2XW.js} +1 -1
- package/dist/index.js +493 -441
- package/dist/{plan-generator-PIDLFRWZ.js → plan-generator-D2UBTVCN.js} +3 -3
- package/dist/{quality-validator-2KIT6QKA.js → quality-validator-IM6YFKLI.js} +3 -1
- package/package.json +10 -10
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
warnIfVolatile,
|
|
35
35
|
warnInlineDuplicates,
|
|
36
36
|
writeFile
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-UZGT5APM.js";
|
|
38
38
|
import {
|
|
39
39
|
COHERENT_REQUIRED_PACKAGES,
|
|
40
40
|
ensureUseClientIfNeeded,
|
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
sanitizeMetadataStrings,
|
|
53
53
|
savePlan,
|
|
54
54
|
updateArchitecturePlan
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-IFJK5OPI.js";
|
|
56
56
|
import {
|
|
57
57
|
isValidTsx,
|
|
58
58
|
safeWrite
|
|
@@ -74,7 +74,7 @@ import {
|
|
|
74
74
|
getDesignQualityForType,
|
|
75
75
|
inferPageTypeFromRoute,
|
|
76
76
|
selectContextualRules
|
|
77
|
-
} from "./chunk-
|
|
77
|
+
} from "./chunk-NLL3SHN3.js";
|
|
78
78
|
import {
|
|
79
79
|
fixGlobalsCss,
|
|
80
80
|
generateV4GlobalsCss,
|
|
@@ -87,7 +87,7 @@ import {
|
|
|
87
87
|
formatIssues,
|
|
88
88
|
validatePageQuality,
|
|
89
89
|
verifyIncrementalEdit
|
|
90
|
-
} from "./chunk-
|
|
90
|
+
} from "./chunk-U5SNPHVU.js";
|
|
91
91
|
import {
|
|
92
92
|
__require
|
|
93
93
|
} from "./chunk-3RG5ZIWI.js";
|
|
@@ -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}
|
|
1435
|
-
|
|
1436
|
-
When using a shared component:
|
|
1437
|
-
- Import from @/components/shared/{filename}
|
|
1438
|
-
- If you need it with different props, use its props interface
|
|
1439
|
-
- If the component doesn't accept the prop you need, add the prop to the shared component and update all existing usages
|
|
1301
|
+
- globals.css \u2014 CSS variables (colors, typography, spacing)`;
|
|
1302
|
+
var ARCHITECTURE_COMPACT = `## Architecture
|
|
1440
1303
|
|
|
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}
|
|
1695
|
+
`;
|
|
1696
|
+
}
|
|
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}
|
|
1696
1735
|
`;
|
|
1697
1736
|
}
|
|
1698
|
-
async function
|
|
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
|
});
|
|
@@ -3900,7 +3941,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
3900
3941
|
if (plan && plan.sharedComponents.length > 0) {
|
|
3901
3942
|
spinner.start(`Phase 4.5/6 \u2014 Generating ${plan.sharedComponents.length} shared components from plan...`);
|
|
3902
3943
|
try {
|
|
3903
|
-
const { generateSharedComponentsFromPlan } = await import("./plan-generator-
|
|
3944
|
+
const { generateSharedComponentsFromPlan } = await import("./plan-generator-D2UBTVCN.js");
|
|
3904
3945
|
const generated = await generateSharedComponentsFromPlan(
|
|
3905
3946
|
plan,
|
|
3906
3947
|
styleContext,
|
|
@@ -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,
|
|
@@ -4655,6 +4696,16 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
4655
4696
|
console.log(chalk8.dim(" \u{1F527} Post-generation fixes:"));
|
|
4656
4697
|
fixes.forEach((f) => console.log(chalk8.dim(` ${f}`)));
|
|
4657
4698
|
}
|
|
4699
|
+
const colorIssueTypes = ["raw-color", "inline-style-color", "arbitrary-color", "svg-raw-color", "color-prop"];
|
|
4700
|
+
const colorIssues = validatePageQuality(fixedCode).filter(
|
|
4701
|
+
(i) => i.severity === "error" && colorIssueTypes.includes(i.type)
|
|
4702
|
+
);
|
|
4703
|
+
if (colorIssues.length > 0) {
|
|
4704
|
+
console.warn(
|
|
4705
|
+
`\u26A0 Component has ${colorIssues.length} color issue(s) after auto-fix:
|
|
4706
|
+
` + colorIssues.map((i) => ` L${i.line}: ${i.message}`).join("\n")
|
|
4707
|
+
);
|
|
4708
|
+
}
|
|
4658
4709
|
await writeFile(fullPath, fixedCode);
|
|
4659
4710
|
printSharedComponentReport({
|
|
4660
4711
|
id: resolved.id,
|
|
@@ -4664,7 +4715,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
4664
4715
|
postFixes: fixes
|
|
4665
4716
|
});
|
|
4666
4717
|
try {
|
|
4667
|
-
await
|
|
4718
|
+
await writeAllHarnessFiles(projectRoot);
|
|
4668
4719
|
} catch {
|
|
4669
4720
|
}
|
|
4670
4721
|
return {
|
|
@@ -4731,7 +4782,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
4731
4782
|
fixes.forEach((f) => console.log(chalk8.dim(` ${f}`)));
|
|
4732
4783
|
}
|
|
4733
4784
|
await writeFile(pageFilePath, fixedCode);
|
|
4734
|
-
const manifest = await
|
|
4785
|
+
const manifest = await loadManifest4(projectRoot);
|
|
4735
4786
|
const usedIn = manifest.shared.find((e) => e.id === resolved.id)?.usedIn ?? [];
|
|
4736
4787
|
const filePathRel = routeToRelPath(route, readPlan || isAuthRoute(route));
|
|
4737
4788
|
if (!usedIn.includes(filePathRel)) {
|
|
@@ -4746,7 +4797,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
4746
4797
|
postFixes: fixes
|
|
4747
4798
|
});
|
|
4748
4799
|
try {
|
|
4749
|
-
await
|
|
4800
|
+
await writeAllHarnessFiles(projectRoot);
|
|
4750
4801
|
} catch {
|
|
4751
4802
|
}
|
|
4752
4803
|
return {
|
|
@@ -4833,7 +4884,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
4833
4884
|
await writeFile(fullPath, fixedCode);
|
|
4834
4885
|
usedInFiles.push(relPath);
|
|
4835
4886
|
}
|
|
4836
|
-
const manifest = await
|
|
4887
|
+
const manifest = await loadManifest4(projectRoot);
|
|
4837
4888
|
const nextManifest = updateUsedIn(manifest, created.id, usedInFiles);
|
|
4838
4889
|
await saveManifest2(projectRoot, nextManifest);
|
|
4839
4890
|
printPromoteAndLinkReport({
|
|
@@ -4843,7 +4894,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
4843
4894
|
usedInFiles
|
|
4844
4895
|
});
|
|
4845
4896
|
try {
|
|
4846
|
-
await
|
|
4897
|
+
await writeAllHarnessFiles(projectRoot);
|
|
4847
4898
|
} catch {
|
|
4848
4899
|
}
|
|
4849
4900
|
return {
|
|
@@ -5089,7 +5140,7 @@ Keep all existing functionality intact.`,
|
|
|
5089
5140
|
cm.updateConfig(cfg);
|
|
5090
5141
|
pm.updateConfig(cfg);
|
|
5091
5142
|
}
|
|
5092
|
-
const manifestForAudit = await
|
|
5143
|
+
const manifestForAudit = await loadManifest4(projectRoot);
|
|
5093
5144
|
const planForAudit = loadPlan(projectRoot);
|
|
5094
5145
|
await warnInlineDuplicates(
|
|
5095
5146
|
projectRoot,
|
|
@@ -5320,7 +5371,7 @@ ${pagesCtx}`
|
|
|
5320
5371
|
cm.updateConfig(cfg);
|
|
5321
5372
|
pm.updateConfig(cfg);
|
|
5322
5373
|
}
|
|
5323
|
-
const manifestForAudit = await
|
|
5374
|
+
const manifestForAudit = await loadManifest4(projectRoot);
|
|
5324
5375
|
const planForAudit2 = loadPlan(projectRoot);
|
|
5325
5376
|
await warnInlineDuplicates(
|
|
5326
5377
|
projectRoot,
|
|
@@ -5419,7 +5470,7 @@ Rules:
|
|
|
5419
5470
|
fixes.forEach((f) => console.log(chalk8.dim(` ${f}`)));
|
|
5420
5471
|
}
|
|
5421
5472
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
5422
|
-
const manifest = await
|
|
5473
|
+
const manifest = await loadManifest4(projectRoot);
|
|
5423
5474
|
printPostGenerationReport({
|
|
5424
5475
|
action: "updated",
|
|
5425
5476
|
pageTitle: pageDef.name || pageDef.id || "Page",
|
|
@@ -5563,8 +5614,8 @@ function hasNavChanged(before, after) {
|
|
|
5563
5614
|
// src/commands/chat/interactive.ts
|
|
5564
5615
|
import chalk9 from "chalk";
|
|
5565
5616
|
import { resolve as resolve4 } from "path";
|
|
5566
|
-
import { existsSync as
|
|
5567
|
-
import { DesignSystemManager as
|
|
5617
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5 } from "fs";
|
|
5618
|
+
import { DesignSystemManager as DesignSystemManager3, ComponentManager as ComponentManager3, loadManifest as loadManifest5 } from "@getcoherent/core";
|
|
5568
5619
|
var DEBUG3 = process.env.COHERENT_DEBUG === "1";
|
|
5569
5620
|
async function interactiveChat(options, chatCommandFn) {
|
|
5570
5621
|
const { createInterface } = await import("readline");
|
|
@@ -5573,7 +5624,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
5573
5624
|
const projectRoot = project.root;
|
|
5574
5625
|
const configPath = project.configPath;
|
|
5575
5626
|
const config2 = await loadConfig(configPath);
|
|
5576
|
-
const dsm = new
|
|
5627
|
+
const dsm = new DesignSystemManager3(configPath);
|
|
5577
5628
|
await dsm.load();
|
|
5578
5629
|
const cm = new ComponentManager3(config2);
|
|
5579
5630
|
const validProviders = ["claude", "openai", "auto"];
|
|
@@ -5588,7 +5639,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
5588
5639
|
let history = [];
|
|
5589
5640
|
try {
|
|
5590
5641
|
mkdirSync5(historyDir, { recursive: true });
|
|
5591
|
-
if (
|
|
5642
|
+
if (existsSync7(historyFile)) {
|
|
5592
5643
|
history = readFileSync5(historyFile, "utf-8").split("\n").filter(Boolean).slice(-200);
|
|
5593
5644
|
}
|
|
5594
5645
|
} catch (e) {
|
|
@@ -5634,7 +5685,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
5634
5685
|
return;
|
|
5635
5686
|
}
|
|
5636
5687
|
if (lower === "components" || lower === "list components" || lower.includes("what components")) {
|
|
5637
|
-
const manifest = await
|
|
5688
|
+
const manifest = await loadManifest5(projectRoot);
|
|
5638
5689
|
if (manifest.shared.length === 0) {
|
|
5639
5690
|
console.log(chalk9.gray("\n No shared components yet.\n"));
|
|
5640
5691
|
} else {
|
|
@@ -5676,7 +5727,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
5676
5727
|
}
|
|
5677
5728
|
if (lower === "status") {
|
|
5678
5729
|
const currentConfig = dsm.getConfig();
|
|
5679
|
-
const manifest = await
|
|
5730
|
+
const manifest = await loadManifest5(projectRoot);
|
|
5680
5731
|
console.log(chalk9.bold(`
|
|
5681
5732
|
${currentConfig.name || "Coherent Project"}`));
|
|
5682
5733
|
console.log(
|
|
@@ -5768,7 +5819,7 @@ async function chatCommand(message, options) {
|
|
|
5768
5819
|
const projectRoot = project.root;
|
|
5769
5820
|
const configPath = project.configPath;
|
|
5770
5821
|
const migrationGuard = join6(projectRoot, ".coherent", "migration-in-progress");
|
|
5771
|
-
if (
|
|
5822
|
+
if (existsSync8(migrationGuard)) {
|
|
5772
5823
|
spinner.fail("Migration in progress");
|
|
5773
5824
|
console.error(chalk10.red("\n\u274C A migration is in progress. Run `coherent migrate --rollback` to undo first."));
|
|
5774
5825
|
bail("Migration in progress");
|
|
@@ -5807,7 +5858,7 @@ async function chatCommand(message, options) {
|
|
|
5807
5858
|
spinner.text = "Loading design system configuration...";
|
|
5808
5859
|
}
|
|
5809
5860
|
const storedHashes = await loadHashes(projectRoot);
|
|
5810
|
-
const dsm = new
|
|
5861
|
+
const dsm = new DesignSystemManager4(configPath);
|
|
5811
5862
|
await dsm.load();
|
|
5812
5863
|
const cm = new ComponentManager4(config2);
|
|
5813
5864
|
const pm = new PageManager2(config2, cm);
|
|
@@ -5818,7 +5869,7 @@ async function chatCommand(message, options) {
|
|
|
5818
5869
|
spinner.start(`Creating shared component: ${componentName}...`);
|
|
5819
5870
|
const { createAIProvider: createAIProvider2 } = await import("./ai-provider-CGSIYFZT.js");
|
|
5820
5871
|
const { generateSharedComponent: generateSharedComponent6 } = await import("@getcoherent/core");
|
|
5821
|
-
const { autoFixCode: autoFixCode2 } = await import("./quality-validator-
|
|
5872
|
+
const { autoFixCode: autoFixCode2 } = await import("./quality-validator-IM6YFKLI.js");
|
|
5822
5873
|
const { extractPropsInterface, extractDependencies } = await import("./component-extractor-VYJLT5NR.js");
|
|
5823
5874
|
const aiProvider = await createAIProvider2(provider ?? "auto");
|
|
5824
5875
|
const prompt = `Generate a React component called "${componentName}". Description: ${message}.
|
|
@@ -5857,7 +5908,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
5857
5908
|
async (p) => JSON.stringify(await aiProvider.generateJSON("You are a component classifier.", p))
|
|
5858
5909
|
);
|
|
5859
5910
|
if (classifications.length > 0) {
|
|
5860
|
-
let manifest2 = await
|
|
5911
|
+
let manifest2 = await loadManifest6(projectRoot);
|
|
5861
5912
|
manifest2 = updateEntry(manifest2, genResult.id, {
|
|
5862
5913
|
type: classifications[0].type,
|
|
5863
5914
|
description: classifications[0].description || message
|
|
@@ -5923,10 +5974,10 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
5923
5974
|
}
|
|
5924
5975
|
}
|
|
5925
5976
|
spinner.start("Parsing your request...");
|
|
5926
|
-
let manifest = await
|
|
5977
|
+
let manifest = await loadManifest6(project.root);
|
|
5927
5978
|
const validShared = manifest.shared.filter((s) => {
|
|
5928
5979
|
const fp = resolve5(project.root, s.file);
|
|
5929
|
-
return
|
|
5980
|
+
return existsSync8(fp);
|
|
5930
5981
|
});
|
|
5931
5982
|
if (validShared.length !== manifest.shared.length) {
|
|
5932
5983
|
const cleaned = manifest.shared.length - validShared.length;
|
|
@@ -5995,7 +6046,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
5995
6046
|
} else {
|
|
5996
6047
|
let reusePlanDirective;
|
|
5997
6048
|
try {
|
|
5998
|
-
const singlePageManifest = await
|
|
6049
|
+
const singlePageManifest = await loadManifest6(projectRoot);
|
|
5999
6050
|
if (singlePageManifest.shared.length > 0) {
|
|
6000
6051
|
const reusePlan = buildReusePlan({
|
|
6001
6052
|
pageName: "page",
|
|
@@ -6147,7 +6198,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6147
6198
|
for (const entry of manifest.shared) {
|
|
6148
6199
|
try {
|
|
6149
6200
|
const sharedPath = resolve5(projectRoot, entry.file);
|
|
6150
|
-
if (
|
|
6201
|
+
if (existsSync8(sharedPath)) {
|
|
6151
6202
|
const sharedCode = readFileSync6(sharedPath, "utf-8");
|
|
6152
6203
|
const sharedImports = sharedCode.matchAll(/@\/components\/ui\/([a-z0-9-]+)/g);
|
|
6153
6204
|
for (const m of sharedImports) {
|
|
@@ -6169,7 +6220,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6169
6220
|
for (const componentId of allNeededComponentIds) {
|
|
6170
6221
|
const isRegistered = !!cm.read(componentId);
|
|
6171
6222
|
const filePath = join6(projectRoot, "components", "ui", `${componentId}.tsx`);
|
|
6172
|
-
const fileExists =
|
|
6223
|
+
const fileExists = existsSync8(filePath);
|
|
6173
6224
|
if (DEBUG4) console.log(chalk10.gray(` Checking ${componentId}: registered=${isRegistered} file=${fileExists}`));
|
|
6174
6225
|
if (!isRegistered || !fileExists) {
|
|
6175
6226
|
missingComponents.push(componentId);
|
|
@@ -6287,8 +6338,8 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6287
6338
|
}
|
|
6288
6339
|
try {
|
|
6289
6340
|
const { validateReuse } = await import("./reuse-validator-XR2ZEYC4.js");
|
|
6290
|
-
const { inferPageTypeFromRoute: inferPageTypeFromRoute2 } = await import("./design-constraints-
|
|
6291
|
-
const manifest2 = await
|
|
6341
|
+
const { inferPageTypeFromRoute: inferPageTypeFromRoute2 } = await import("./design-constraints-PFZDW2XW.js");
|
|
6342
|
+
const manifest2 = await loadManifest6(projectRoot);
|
|
6292
6343
|
const reuseplan = projectRoot ? loadPlan(projectRoot) : null;
|
|
6293
6344
|
if (manifest2.shared.length > 0) {
|
|
6294
6345
|
for (const request of normalizedRequests) {
|
|
@@ -6318,7 +6369,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6318
6369
|
const route = page.route || `/${page.id || "page"}`;
|
|
6319
6370
|
const pageFilePath = routeToFsPath(projectRoot, route, false);
|
|
6320
6371
|
let pageCode = "";
|
|
6321
|
-
if (
|
|
6372
|
+
if (existsSync8(pageFilePath)) {
|
|
6322
6373
|
try {
|
|
6323
6374
|
pageCode = readFileSync6(pageFilePath, "utf-8");
|
|
6324
6375
|
} catch {
|
|
@@ -6340,8 +6391,8 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6340
6391
|
}
|
|
6341
6392
|
const missingRoutes = [...allLinkedRoutes].filter((route) => {
|
|
6342
6393
|
if (expandedExisting.has(route)) return false;
|
|
6343
|
-
if (
|
|
6344
|
-
if (
|
|
6394
|
+
if (existsSync8(routeToFsPath(projectRoot, route, false))) return false;
|
|
6395
|
+
if (existsSync8(routeToFsPath(projectRoot, route, true))) return false;
|
|
6345
6396
|
return true;
|
|
6346
6397
|
});
|
|
6347
6398
|
const SCAFFOLD_AI_LIMIT = 10;
|
|
@@ -6406,7 +6457,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6406
6457
|
const filePath = routeToFsPath(projectRoot, linkedRoute, isAuth);
|
|
6407
6458
|
if (isAuth) await ensureAuthRouteGroup(projectRoot);
|
|
6408
6459
|
const dir = resolve5(filePath, "..");
|
|
6409
|
-
if (!
|
|
6460
|
+
if (!existsSync8(dir)) {
|
|
6410
6461
|
mkdirSync6(dir, { recursive: true });
|
|
6411
6462
|
}
|
|
6412
6463
|
const placeholderCode = `export default function ${pageName.replace(/\s/g, "")}Page() {
|
|
@@ -6523,13 +6574,13 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6523
6574
|
const sharedDir = resolve5(projectRoot, "components", "shared");
|
|
6524
6575
|
const layoutFile = resolve5(projectRoot, "app", "layout.tsx");
|
|
6525
6576
|
const filesToHash = [layoutFile];
|
|
6526
|
-
if (
|
|
6577
|
+
if (existsSync8(sharedDir)) {
|
|
6527
6578
|
for (const f of readdirSync3(sharedDir)) {
|
|
6528
6579
|
if (f.endsWith(".tsx")) filesToHash.push(resolve5(sharedDir, f));
|
|
6529
6580
|
}
|
|
6530
6581
|
}
|
|
6531
6582
|
for (const filePath of filesToHash) {
|
|
6532
|
-
if (
|
|
6583
|
+
if (existsSync8(filePath)) {
|
|
6533
6584
|
const rel = relative2(projectRoot, filePath);
|
|
6534
6585
|
updatedHashes[rel] = await computeFileHash(filePath);
|
|
6535
6586
|
}
|
|
@@ -6540,11 +6591,11 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6540
6591
|
}
|
|
6541
6592
|
try {
|
|
6542
6593
|
const { extractPropsInterface, extractDependencies, extractUsageExample } = await import("./component-extractor-VYJLT5NR.js");
|
|
6543
|
-
let currentManifest = await
|
|
6594
|
+
let currentManifest = await loadManifest6(projectRoot);
|
|
6544
6595
|
let manifestChanged = false;
|
|
6545
6596
|
for (const entry of currentManifest.shared) {
|
|
6546
6597
|
const fullPath = resolve5(projectRoot, entry.file);
|
|
6547
|
-
if (!
|
|
6598
|
+
if (!existsSync8(fullPath)) continue;
|
|
6548
6599
|
const code = readFileSync6(fullPath, "utf-8");
|
|
6549
6600
|
const props = extractPropsInterface(code);
|
|
6550
6601
|
const deps = extractDependencies(code);
|
|
@@ -6559,7 +6610,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6559
6610
|
const pageFiles = Array.from(allModified).filter((f) => f.startsWith("app/") && f.endsWith("page.tsx"));
|
|
6560
6611
|
for (const pageFile of pageFiles) {
|
|
6561
6612
|
const fullPath = resolve5(projectRoot, pageFile);
|
|
6562
|
-
if (!
|
|
6613
|
+
if (!existsSync8(fullPath)) continue;
|
|
6563
6614
|
const pageCode = readFileSync6(fullPath, "utf-8");
|
|
6564
6615
|
for (const entry of currentManifest.shared) {
|
|
6565
6616
|
const isUsed = pageCode.includes(`from '@/components/shared/`) && (pageCode.includes(`{ ${entry.name} }`) || pageCode.includes(`{ ${entry.name},`));
|
|
@@ -6617,7 +6668,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
6617
6668
|
${uxRecommendations}
|
|
6618
6669
|
`;
|
|
6619
6670
|
try {
|
|
6620
|
-
if (!
|
|
6671
|
+
if (!existsSync8(recPath)) {
|
|
6621
6672
|
await writeFile(
|
|
6622
6673
|
recPath,
|
|
6623
6674
|
"# UX/UI Recommendations\n\nRecommendations are added here when you use `coherent chat` and the AI suggests improvements.\n"
|
|
@@ -6691,7 +6742,7 @@ ${uxRecommendations}
|
|
|
6691
6742
|
import chalk11 from "chalk";
|
|
6692
6743
|
import ora3 from "ora";
|
|
6693
6744
|
import { spawn } from "child_process";
|
|
6694
|
-
import { existsSync as
|
|
6745
|
+
import { existsSync as existsSync11, rmSync as rmSync3, readFileSync as readFileSync9, writeFileSync as writeFileSync8, readdirSync as readdirSync5 } from "fs";
|
|
6695
6746
|
import { resolve as resolve6, join as join9 } from "path";
|
|
6696
6747
|
import { readdir } from "fs/promises";
|
|
6697
6748
|
|
|
@@ -6744,15 +6795,15 @@ function validateV4GlobalsCss(css) {
|
|
|
6744
6795
|
}
|
|
6745
6796
|
|
|
6746
6797
|
// src/commands/preview.ts
|
|
6747
|
-
import { DesignSystemManager as
|
|
6798
|
+
import { DesignSystemManager as DesignSystemManager5, ComponentGenerator as ComponentGenerator2 } from "@getcoherent/core";
|
|
6748
6799
|
|
|
6749
6800
|
// src/utils/file-watcher.ts
|
|
6750
|
-
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as
|
|
6801
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync7, existsSync as existsSync10 } from "fs";
|
|
6751
6802
|
import { relative as relative4, join as join8 } from "path";
|
|
6752
|
-
import { loadManifest as
|
|
6803
|
+
import { loadManifest as loadManifest7, saveManifest as saveManifest4 } from "@getcoherent/core";
|
|
6753
6804
|
|
|
6754
6805
|
// src/utils/component-integrity.ts
|
|
6755
|
-
import { existsSync as
|
|
6806
|
+
import { existsSync as existsSync9, readFileSync as readFileSync7, readdirSync as readdirSync4 } from "fs";
|
|
6756
6807
|
import { join as join7, relative as relative3 } from "path";
|
|
6757
6808
|
function extractExportedComponentNames(code) {
|
|
6758
6809
|
const names = [];
|
|
@@ -6786,7 +6837,7 @@ function arraysEqual(a, b) {
|
|
|
6786
6837
|
function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
6787
6838
|
const results = [];
|
|
6788
6839
|
const appDir = join7(projectRoot, "app");
|
|
6789
|
-
if (!
|
|
6840
|
+
if (!existsSync9(appDir)) return results;
|
|
6790
6841
|
const pageFiles = collectFiles(appDir, (name) => name === "page.tsx" || name === "page.jsx");
|
|
6791
6842
|
const componentImportPath = componentFile.replace(/\.tsx$/, "").replace(/\.jsx$/, "");
|
|
6792
6843
|
for (const absPath of pageFiles) {
|
|
@@ -6807,7 +6858,7 @@ function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
|
6807
6858
|
function isUsedInLayout(projectRoot, componentName) {
|
|
6808
6859
|
const appDir = join7(projectRoot, "app");
|
|
6809
6860
|
const layoutPaths = [join7(appDir, "layout.tsx")];
|
|
6810
|
-
if (
|
|
6861
|
+
if (existsSync9(appDir)) {
|
|
6811
6862
|
try {
|
|
6812
6863
|
for (const entry of readdirSync4(appDir, { withFileTypes: true })) {
|
|
6813
6864
|
if (entry.isDirectory() && entry.name.startsWith("(") && entry.name.endsWith(")")) {
|
|
@@ -6831,7 +6882,7 @@ function isUsedInLayout(projectRoot, componentName) {
|
|
|
6831
6882
|
function findUnregisteredComponents(projectRoot, manifest) {
|
|
6832
6883
|
const results = [];
|
|
6833
6884
|
const componentsDir = join7(projectRoot, "components");
|
|
6834
|
-
if (!
|
|
6885
|
+
if (!existsSync9(componentsDir)) return results;
|
|
6835
6886
|
const registeredFiles = new Set(manifest.shared.map((s) => s.file));
|
|
6836
6887
|
const registeredNames = new Set(manifest.shared.map((s) => s.name));
|
|
6837
6888
|
const files = collectFiles(
|
|
@@ -6859,7 +6910,7 @@ function findUnregisteredComponents(projectRoot, manifest) {
|
|
|
6859
6910
|
function findInlineDuplicates(projectRoot, manifest) {
|
|
6860
6911
|
const results = [];
|
|
6861
6912
|
const appDir = join7(projectRoot, "app");
|
|
6862
|
-
if (!
|
|
6913
|
+
if (!existsSync9(appDir)) return results;
|
|
6863
6914
|
const pageFiles = collectFiles(appDir, (name) => name === "page.tsx" || name === "page.jsx");
|
|
6864
6915
|
for (const absPath of pageFiles) {
|
|
6865
6916
|
if (absPath.includes("design-system")) continue;
|
|
@@ -6889,7 +6940,7 @@ function findInlineDuplicates(projectRoot, manifest) {
|
|
|
6889
6940
|
}
|
|
6890
6941
|
function findComponentFileByExportName(projectRoot, componentName) {
|
|
6891
6942
|
const componentsDir = join7(projectRoot, "components");
|
|
6892
|
-
if (!
|
|
6943
|
+
if (!existsSync9(componentsDir)) return null;
|
|
6893
6944
|
const files = collectFiles(
|
|
6894
6945
|
componentsDir,
|
|
6895
6946
|
(name) => (name.endsWith(".tsx") || name.endsWith(".jsx")) && !name.startsWith("."),
|
|
@@ -6911,7 +6962,7 @@ function removeOrphanedEntries(projectRoot, manifest) {
|
|
|
6911
6962
|
const removed = [];
|
|
6912
6963
|
const valid = manifest.shared.filter((entry) => {
|
|
6913
6964
|
const filePath = join7(projectRoot, entry.file);
|
|
6914
|
-
if (
|
|
6965
|
+
if (existsSync9(filePath)) return true;
|
|
6915
6966
|
removed.push({ id: entry.id, name: entry.name });
|
|
6916
6967
|
return false;
|
|
6917
6968
|
});
|
|
@@ -6930,7 +6981,7 @@ function reconcileComponents(projectRoot, manifest) {
|
|
|
6930
6981
|
const m = { ...manifest, shared: [...manifest.shared], nextId: manifest.nextId };
|
|
6931
6982
|
m.shared = m.shared.filter((entry) => {
|
|
6932
6983
|
const filePath = join7(projectRoot, entry.file);
|
|
6933
|
-
if (!
|
|
6984
|
+
if (!existsSync9(filePath)) {
|
|
6934
6985
|
const newPath = findComponentFileByExportName(projectRoot, entry.name);
|
|
6935
6986
|
if (newPath) {
|
|
6936
6987
|
result.updated.push({ id: entry.id, field: "file", from: entry.file, to: newPath });
|
|
@@ -7066,7 +7117,7 @@ function findInlineDuplicatesOfShared(content, manifest) {
|
|
|
7066
7117
|
function getWatcherConfig(projectRoot) {
|
|
7067
7118
|
try {
|
|
7068
7119
|
const pkgPath = join8(projectRoot, "package.json");
|
|
7069
|
-
if (!
|
|
7120
|
+
if (!existsSync10(pkgPath)) return defaultWatcherConfig();
|
|
7070
7121
|
const pkg = JSON.parse(readFileSync8(pkgPath, "utf-8"));
|
|
7071
7122
|
const c = pkg?.coherent?.watcher ?? {};
|
|
7072
7123
|
return {
|
|
@@ -7125,7 +7176,7 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
7125
7176
|
if (config2.warnSharedReuse) {
|
|
7126
7177
|
let manifest;
|
|
7127
7178
|
try {
|
|
7128
|
-
manifest = await
|
|
7179
|
+
manifest = await loadManifest7(projectRoot);
|
|
7129
7180
|
} catch {
|
|
7130
7181
|
manifest = { shared: [], nextId: 1 };
|
|
7131
7182
|
}
|
|
@@ -7144,7 +7195,7 @@ async function handleFileDelete(projectRoot, filePath) {
|
|
|
7144
7195
|
if (!relativePath.startsWith("components/") || relativePath.startsWith("components/ui/")) return;
|
|
7145
7196
|
try {
|
|
7146
7197
|
const chalk30 = (await import("chalk")).default;
|
|
7147
|
-
const manifest = await
|
|
7198
|
+
const manifest = await loadManifest7(projectRoot);
|
|
7148
7199
|
const orphaned = manifest.shared.find((s) => s.file === relativePath);
|
|
7149
7200
|
if (orphaned) {
|
|
7150
7201
|
const cleaned = {
|
|
@@ -7154,7 +7205,7 @@ async function handleFileDelete(projectRoot, filePath) {
|
|
|
7154
7205
|
await saveManifest4(projectRoot, cleaned);
|
|
7155
7206
|
console.log(chalk30.cyan(`
|
|
7156
7207
|
\u{1F5D1} Auto-removed ${orphaned.id} (${orphaned.name}) \u2014 file deleted`));
|
|
7157
|
-
await
|
|
7208
|
+
await writeAllHarnessFiles(projectRoot);
|
|
7158
7209
|
}
|
|
7159
7210
|
} catch {
|
|
7160
7211
|
}
|
|
@@ -7165,7 +7216,7 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
7165
7216
|
if (!relativePath.endsWith(".tsx") && !relativePath.endsWith(".jsx")) return;
|
|
7166
7217
|
try {
|
|
7167
7218
|
const chalk30 = (await import("chalk")).default;
|
|
7168
|
-
const manifest = await
|
|
7219
|
+
const manifest = await loadManifest7(projectRoot);
|
|
7169
7220
|
const alreadyRegistered = manifest.shared.some((s) => s.file === relativePath);
|
|
7170
7221
|
if (alreadyRegistered) return;
|
|
7171
7222
|
const code = readFileSync8(filePath, "utf-8");
|
|
@@ -7183,7 +7234,7 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
7183
7234
|
}
|
|
7184
7235
|
async function handleManifestChange(projectRoot) {
|
|
7185
7236
|
try {
|
|
7186
|
-
await
|
|
7237
|
+
await writeAllHarnessFiles(projectRoot);
|
|
7187
7238
|
} catch {
|
|
7188
7239
|
}
|
|
7189
7240
|
}
|
|
@@ -7208,7 +7259,7 @@ function startFileWatcher(projectRoot) {
|
|
|
7208
7259
|
watcher.on("unlink", (fp) => handleFileDelete(projectRoot, fp));
|
|
7209
7260
|
});
|
|
7210
7261
|
const manifestPath = join8(projectRoot, "coherent.components.json");
|
|
7211
|
-
if (
|
|
7262
|
+
if (existsSync10(manifestPath)) {
|
|
7212
7263
|
import("chokidar").then((chokidar) => {
|
|
7213
7264
|
manifestWatcher = chokidar.default.watch(manifestPath, { ignoreInitial: true });
|
|
7214
7265
|
manifestWatcher.on("change", () => handleManifestChange(projectRoot));
|
|
@@ -7222,7 +7273,7 @@ function startFileWatcher(projectRoot) {
|
|
|
7222
7273
|
|
|
7223
7274
|
// src/commands/preview.ts
|
|
7224
7275
|
function getPackageManager(projectRoot) {
|
|
7225
|
-
const hasPnpm =
|
|
7276
|
+
const hasPnpm = existsSync11(resolve6(projectRoot, "pnpm-lock.yaml"));
|
|
7226
7277
|
return hasPnpm ? "pnpm" : "npm";
|
|
7227
7278
|
}
|
|
7228
7279
|
function runInstall(projectRoot) {
|
|
@@ -7246,21 +7297,21 @@ function runInstall(projectRoot) {
|
|
|
7246
7297
|
function checkProjectInitialized(projectRoot) {
|
|
7247
7298
|
const configPath = resolve6(projectRoot, "design-system.config.ts");
|
|
7248
7299
|
const packageJsonPath = resolve6(projectRoot, "package.json");
|
|
7249
|
-
if (!
|
|
7300
|
+
if (!existsSync11(configPath)) {
|
|
7250
7301
|
return false;
|
|
7251
7302
|
}
|
|
7252
|
-
if (!
|
|
7303
|
+
if (!existsSync11(packageJsonPath)) {
|
|
7253
7304
|
return false;
|
|
7254
7305
|
}
|
|
7255
7306
|
return true;
|
|
7256
7307
|
}
|
|
7257
7308
|
function checkDependenciesInstalled(projectRoot) {
|
|
7258
7309
|
const nodeModulesPath = resolve6(projectRoot, "node_modules");
|
|
7259
|
-
return
|
|
7310
|
+
return existsSync11(nodeModulesPath);
|
|
7260
7311
|
}
|
|
7261
7312
|
function clearStaleCache(projectRoot) {
|
|
7262
7313
|
const nextDir = join9(projectRoot, ".next");
|
|
7263
|
-
if (
|
|
7314
|
+
if (existsSync11(nextDir)) {
|
|
7264
7315
|
rmSync3(nextDir, { recursive: true, force: true });
|
|
7265
7316
|
console.log(chalk11.dim(" \u2714 Cleared stale build cache"));
|
|
7266
7317
|
}
|
|
@@ -7276,7 +7327,7 @@ async function preflightDependencyCheck(projectRoot) {
|
|
|
7276
7327
|
}
|
|
7277
7328
|
async function listPageFiles(appDir) {
|
|
7278
7329
|
const out = [];
|
|
7279
|
-
if (!
|
|
7330
|
+
if (!existsSync11(appDir)) return out;
|
|
7280
7331
|
async function walk(dir) {
|
|
7281
7332
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
7282
7333
|
for (const e of entries) {
|
|
@@ -7303,10 +7354,10 @@ async function validateSyntax(projectRoot) {
|
|
|
7303
7354
|
async function fixMissingComponentExports(projectRoot) {
|
|
7304
7355
|
const appDir = join9(projectRoot, "app");
|
|
7305
7356
|
const uiDir = join9(projectRoot, "components", "ui");
|
|
7306
|
-
if (!
|
|
7357
|
+
if (!existsSync11(appDir) || !existsSync11(uiDir)) return;
|
|
7307
7358
|
const pages = await listPageFiles(appDir);
|
|
7308
7359
|
const sharedDir = join9(projectRoot, "components", "shared");
|
|
7309
|
-
if (
|
|
7360
|
+
if (existsSync11(sharedDir)) {
|
|
7310
7361
|
const sharedFiles = readdirSync5(sharedDir).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts")).map((f) => join9(sharedDir, f));
|
|
7311
7362
|
pages.push(...sharedFiles);
|
|
7312
7363
|
}
|
|
@@ -7325,7 +7376,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
7325
7376
|
const configPath = join9(projectRoot, "design-system.config.ts");
|
|
7326
7377
|
let config2 = null;
|
|
7327
7378
|
try {
|
|
7328
|
-
const mgr = new
|
|
7379
|
+
const mgr = new DesignSystemManager5(configPath);
|
|
7329
7380
|
config2 = mgr.getConfig();
|
|
7330
7381
|
} catch {
|
|
7331
7382
|
}
|
|
@@ -7333,7 +7384,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
7333
7384
|
const provider = getComponentProvider();
|
|
7334
7385
|
for (const [componentId, needed] of neededExports) {
|
|
7335
7386
|
const componentFile = join9(uiDir, `${componentId}.tsx`);
|
|
7336
|
-
if (!
|
|
7387
|
+
if (!existsSync11(componentFile)) {
|
|
7337
7388
|
if (provider.has(componentId)) {
|
|
7338
7389
|
try {
|
|
7339
7390
|
const result = await provider.installComponent(componentId, projectRoot);
|
|
@@ -7402,7 +7453,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
7402
7453
|
}
|
|
7403
7454
|
for (const [componentId, needed] of neededSharedExports) {
|
|
7404
7455
|
const componentFile = join9(sharedDir, `${componentId}.tsx`);
|
|
7405
|
-
if (!
|
|
7456
|
+
if (!existsSync11(componentFile)) continue;
|
|
7406
7457
|
let content = readFileSync9(componentFile, "utf-8");
|
|
7407
7458
|
const exportRe = /export\s+(?:const|function|class)\s+(\w+)|export\s*\{([^}]+)\}/g;
|
|
7408
7459
|
const existingExports = /* @__PURE__ */ new Set();
|
|
@@ -7426,9 +7477,9 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
7426
7477
|
}
|
|
7427
7478
|
async function backfillPageAnalysis(projectRoot) {
|
|
7428
7479
|
const configPath = join9(projectRoot, "design-system.config.ts");
|
|
7429
|
-
if (!
|
|
7480
|
+
if (!existsSync11(configPath)) return;
|
|
7430
7481
|
try {
|
|
7431
|
-
const mgr = new
|
|
7482
|
+
const mgr = new DesignSystemManager5(configPath);
|
|
7432
7483
|
const config2 = mgr.getConfig();
|
|
7433
7484
|
let changed = false;
|
|
7434
7485
|
for (const page of config2.pages) {
|
|
@@ -7443,7 +7494,7 @@ async function backfillPageAnalysis(projectRoot) {
|
|
|
7443
7494
|
} else {
|
|
7444
7495
|
filePath = join9(projectRoot, "app", route.slice(1), "page.tsx");
|
|
7445
7496
|
}
|
|
7446
|
-
if (!
|
|
7497
|
+
if (!existsSync11(filePath)) continue;
|
|
7447
7498
|
const code = readFileSync9(filePath, "utf-8");
|
|
7448
7499
|
if (code.length < 50) continue;
|
|
7449
7500
|
page.pageAnalysis = analyzePageCode(code);
|
|
@@ -7595,11 +7646,11 @@ async function openBrowser(url) {
|
|
|
7595
7646
|
}
|
|
7596
7647
|
function startDevServer(projectRoot) {
|
|
7597
7648
|
const packageJsonPath = resolve6(projectRoot, "package.json");
|
|
7598
|
-
if (!
|
|
7649
|
+
if (!existsSync11(packageJsonPath)) {
|
|
7599
7650
|
throw new Error('package.json not found. Run "coherent init" first.');
|
|
7600
7651
|
}
|
|
7601
|
-
const hasPnpm =
|
|
7602
|
-
const hasNpm =
|
|
7652
|
+
const hasPnpm = existsSync11(resolve6(projectRoot, "pnpm-lock.yaml"));
|
|
7653
|
+
const hasNpm = existsSync11(resolve6(projectRoot, "package-lock.json"));
|
|
7603
7654
|
const command = hasPnpm ? "pnpm" : hasNpm ? "npm" : "npx";
|
|
7604
7655
|
const args = hasPnpm ? ["dev", "--turbo"] : hasNpm ? ["run", "dev", "--", "--turbo"] : ["next", "dev", "--turbo"];
|
|
7605
7656
|
const child = spawn(command, args, {
|
|
@@ -7646,7 +7697,7 @@ async function previewCommand() {
|
|
|
7646
7697
|
if (needsGlobalsFix(projectRoot)) {
|
|
7647
7698
|
spinner.text = "Fixing globals.css...";
|
|
7648
7699
|
try {
|
|
7649
|
-
const dsm = new
|
|
7700
|
+
const dsm = new DesignSystemManager5(resolve6(projectRoot, "design-system.config.ts"));
|
|
7650
7701
|
await dsm.load();
|
|
7651
7702
|
const config2 = dsm.getConfig();
|
|
7652
7703
|
fixGlobalsCss(projectRoot, config2);
|
|
@@ -7657,7 +7708,7 @@ async function previewCommand() {
|
|
|
7657
7708
|
}
|
|
7658
7709
|
if (isTailwindV4(projectRoot)) {
|
|
7659
7710
|
const globalsPath = resolve6(projectRoot, "app", "globals.css");
|
|
7660
|
-
if (
|
|
7711
|
+
if (existsSync11(globalsPath)) {
|
|
7661
7712
|
const globalsContent = readFileSync9(globalsPath, "utf-8");
|
|
7662
7713
|
const cssIssues = validateV4GlobalsCss(globalsContent);
|
|
7663
7714
|
if (cssIssues.length > 0) {
|
|
@@ -7699,7 +7750,7 @@ async function previewCommand() {
|
|
|
7699
7750
|
import chalk12 from "chalk";
|
|
7700
7751
|
import ora4 from "ora";
|
|
7701
7752
|
import { spawn as spawn2 } from "child_process";
|
|
7702
|
-
import { existsSync as
|
|
7753
|
+
import { existsSync as existsSync12, rmSync as rmSync4, readdirSync as readdirSync6 } from "fs";
|
|
7703
7754
|
import { resolve as resolve7, join as join10, dirname as dirname4 } from "path";
|
|
7704
7755
|
import { readdir as readdir2, readFile as readFile3, writeFile as writeFile3, mkdir as mkdir2, copyFile } from "fs/promises";
|
|
7705
7756
|
var COPY_EXCLUDE = /* @__PURE__ */ new Set([
|
|
@@ -7710,6 +7761,7 @@ var COPY_EXCLUDE = /* @__PURE__ */ new Set([
|
|
|
7710
7761
|
".tmp-e2e",
|
|
7711
7762
|
".cursorrules",
|
|
7712
7763
|
"CLAUDE.md",
|
|
7764
|
+
"AGENTS.md",
|
|
7713
7765
|
".claude",
|
|
7714
7766
|
".coherent",
|
|
7715
7767
|
"design-system.config.ts",
|
|
@@ -7734,17 +7786,17 @@ async function copyDir(src, dest) {
|
|
|
7734
7786
|
}
|
|
7735
7787
|
}
|
|
7736
7788
|
function checkProjectInitialized2(projectRoot) {
|
|
7737
|
-
return
|
|
7789
|
+
return existsSync12(resolve7(projectRoot, "design-system.config.ts")) && existsSync12(resolve7(projectRoot, "package.json"));
|
|
7738
7790
|
}
|
|
7739
7791
|
function getPackageManager2(projectRoot) {
|
|
7740
|
-
if (
|
|
7741
|
-
if (
|
|
7792
|
+
if (existsSync12(resolve7(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
7793
|
+
if (existsSync12(resolve7(projectRoot, "package-lock.json"))) return "npm";
|
|
7742
7794
|
return "npx";
|
|
7743
7795
|
}
|
|
7744
7796
|
async function patchNextConfigForExport(outRoot) {
|
|
7745
7797
|
for (const name of ["next.config.ts", "next.config.mjs", "next.config.js"]) {
|
|
7746
7798
|
const p = join10(outRoot, name);
|
|
7747
|
-
if (!
|
|
7799
|
+
if (!existsSync12(p)) continue;
|
|
7748
7800
|
let content = await readFile3(p, "utf-8");
|
|
7749
7801
|
if (content.includes("ignoreDuringBuilds")) return;
|
|
7750
7802
|
content = content.replace(
|
|
@@ -7786,7 +7838,7 @@ EXPOSE 3000
|
|
|
7786
7838
|
`;
|
|
7787
7839
|
async function ensureReadmeDeploySection(outRoot) {
|
|
7788
7840
|
const readmePath = join10(outRoot, "README.md");
|
|
7789
|
-
if (!
|
|
7841
|
+
if (!existsSync12(readmePath)) return;
|
|
7790
7842
|
try {
|
|
7791
7843
|
let content = await readFile3(readmePath, "utf-8");
|
|
7792
7844
|
if (/##\s+Deploy\b/m.test(content)) return;
|
|
@@ -7811,14 +7863,14 @@ async function countPages(outRoot) {
|
|
|
7811
7863
|
}
|
|
7812
7864
|
}
|
|
7813
7865
|
const appDir = join10(outRoot, "app");
|
|
7814
|
-
if (
|
|
7866
|
+
if (existsSync12(appDir)) await walk(appDir);
|
|
7815
7867
|
return n;
|
|
7816
7868
|
}
|
|
7817
7869
|
function countComponents(outRoot) {
|
|
7818
7870
|
let n = 0;
|
|
7819
7871
|
for (const sub of ["ui", "shared"]) {
|
|
7820
7872
|
const dir = join10(outRoot, "components", sub);
|
|
7821
|
-
if (!
|
|
7873
|
+
if (!existsSync12(dir)) continue;
|
|
7822
7874
|
try {
|
|
7823
7875
|
n += readdirSync6(dir).filter((f) => f.endsWith(".tsx") || f.endsWith(".jsx")).length;
|
|
7824
7876
|
} catch {
|
|
@@ -7829,7 +7881,7 @@ function countComponents(outRoot) {
|
|
|
7829
7881
|
var IMPORT_FROM_REGEX = /from\s+['"]([^'"]+)['"]/g;
|
|
7830
7882
|
async function collectImportedPackages(dir, extensions) {
|
|
7831
7883
|
const packages = /* @__PURE__ */ new Set();
|
|
7832
|
-
if (!
|
|
7884
|
+
if (!existsSync12(dir)) return packages;
|
|
7833
7885
|
async function walk(d) {
|
|
7834
7886
|
const entries = await readdir2(d, { withFileTypes: true });
|
|
7835
7887
|
for (const e of entries) {
|
|
@@ -7857,7 +7909,7 @@ async function collectImportedPackages(dir, extensions) {
|
|
|
7857
7909
|
}
|
|
7858
7910
|
async function findMissingDepsInExport(outRoot) {
|
|
7859
7911
|
const pkgPath = join10(outRoot, "package.json");
|
|
7860
|
-
if (!
|
|
7912
|
+
if (!existsSync12(pkgPath)) return [];
|
|
7861
7913
|
let pkg;
|
|
7862
7914
|
try {
|
|
7863
7915
|
pkg = JSON.parse(await readFile3(pkgPath, "utf-8"));
|
|
@@ -7878,32 +7930,32 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
7878
7930
|
const removed = [];
|
|
7879
7931
|
for (const p of ["app/design-system", "app/api/design-system"]) {
|
|
7880
7932
|
const full = join10(outputDir, p);
|
|
7881
|
-
if (
|
|
7933
|
+
if (existsSync12(full)) {
|
|
7882
7934
|
rmSync4(full, { recursive: true, force: true });
|
|
7883
7935
|
removed.push(p);
|
|
7884
7936
|
}
|
|
7885
7937
|
}
|
|
7886
7938
|
const appNavPath = join10(outputDir, "app", "AppNav.tsx");
|
|
7887
|
-
if (
|
|
7939
|
+
if (existsSync12(appNavPath)) {
|
|
7888
7940
|
rmSync4(appNavPath, { force: true });
|
|
7889
7941
|
removed.push("app/AppNav.tsx");
|
|
7890
7942
|
}
|
|
7891
7943
|
const layoutPath = join10(outputDir, "app", "layout.tsx");
|
|
7892
|
-
if (
|
|
7944
|
+
if (existsSync12(layoutPath)) {
|
|
7893
7945
|
let layout = await readFile3(layoutPath, "utf-8");
|
|
7894
7946
|
layout = layout.replace(/import\s*\{?\s*AppNav\s*\}?\s*from\s*['"][^'"]+['"]\s*\n?/g, "");
|
|
7895
7947
|
layout = layout.replace(/\s*<AppNav\s*\/?\s*>\s*/g, "\n");
|
|
7896
7948
|
await writeFile3(layoutPath, layout, "utf-8");
|
|
7897
7949
|
}
|
|
7898
7950
|
const sharedHeaderPath = join10(outputDir, "components", "shared", "header.tsx");
|
|
7899
|
-
if (
|
|
7951
|
+
if (existsSync12(sharedHeaderPath)) {
|
|
7900
7952
|
let header = await readFile3(sharedHeaderPath, "utf-8");
|
|
7901
7953
|
header = header.replace(/<Link\s[^>]*href="\/design-system"[^>]*>[\s\S]*?<\/Link>/g, "");
|
|
7902
7954
|
header = header.replace(/\n\s*<>\s*\n/, "\n");
|
|
7903
7955
|
header = header.replace(/\n\s*<\/>\s*\n/, "\n");
|
|
7904
7956
|
await writeFile3(sharedHeaderPath, header, "utf-8");
|
|
7905
7957
|
}
|
|
7906
|
-
if (
|
|
7958
|
+
if (existsSync12(layoutPath)) {
|
|
7907
7959
|
let rootLayout = await readFile3(layoutPath, "utf-8");
|
|
7908
7960
|
const before = rootLayout;
|
|
7909
7961
|
rootLayout = rootLayout.replace(/<Link\s[^>]*href="\/design-system"[^>]*>[\s\S]*?<\/Link>/g, "");
|
|
@@ -7912,7 +7964,7 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
7912
7964
|
}
|
|
7913
7965
|
}
|
|
7914
7966
|
const guardPath2 = join10(outputDir, "app", "ShowWhenNotAuthRoute.tsx");
|
|
7915
|
-
if (
|
|
7967
|
+
if (existsSync12(guardPath2)) {
|
|
7916
7968
|
let guard = await readFile3(guardPath2, "utf-8");
|
|
7917
7969
|
guard = guard.replace(/['"],?\s*'\/design-system['"],?\s*/g, "");
|
|
7918
7970
|
const pathsMatch = guard.match(/HIDDEN_PATHS\s*=\s*\[([^\]]*)\]/);
|
|
@@ -7920,7 +7972,7 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
7920
7972
|
if (remaining.length === 0) {
|
|
7921
7973
|
rmSync4(guardPath2, { force: true });
|
|
7922
7974
|
removed.push("app/ShowWhenNotAuthRoute.tsx");
|
|
7923
|
-
if (
|
|
7975
|
+
if (existsSync12(layoutPath)) {
|
|
7924
7976
|
let layout = await readFile3(layoutPath, "utf-8");
|
|
7925
7977
|
layout = layout.replace(/import\s+\w+\s+from\s*['"]\.\/ShowWhenNotAuthRoute['"]\s*\n?/g, "");
|
|
7926
7978
|
layout = layout.replace(/\s*<ShowWhenNotAuthRoute>\s*\n?/g, "\n");
|
|
@@ -7936,19 +7988,20 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
7936
7988
|
"design-system.config.ts",
|
|
7937
7989
|
".cursorrules",
|
|
7938
7990
|
"CLAUDE.md",
|
|
7991
|
+
"AGENTS.md",
|
|
7939
7992
|
".env",
|
|
7940
7993
|
".env.local",
|
|
7941
7994
|
"recommendations.md"
|
|
7942
7995
|
]) {
|
|
7943
7996
|
const full = join10(outputDir, name);
|
|
7944
|
-
if (
|
|
7997
|
+
if (existsSync12(full)) {
|
|
7945
7998
|
rmSync4(full, { force: true });
|
|
7946
7999
|
removed.push(name);
|
|
7947
8000
|
}
|
|
7948
8001
|
}
|
|
7949
8002
|
for (const dir of [".claude", ".coherent"]) {
|
|
7950
8003
|
const full = join10(outputDir, dir);
|
|
7951
|
-
if (
|
|
8004
|
+
if (existsSync12(full)) {
|
|
7952
8005
|
rmSync4(full, { recursive: true, force: true });
|
|
7953
8006
|
removed.push(dir + "/");
|
|
7954
8007
|
}
|
|
@@ -7972,7 +8025,7 @@ async function exportCommand(options = {}) {
|
|
|
7972
8025
|
process.exit(1);
|
|
7973
8026
|
}
|
|
7974
8027
|
spinner.text = "Copying project...";
|
|
7975
|
-
if (
|
|
8028
|
+
if (existsSync12(outputDir)) rmSync4(outputDir, { recursive: true, force: true });
|
|
7976
8029
|
await copyDir(projectRoot, outputDir);
|
|
7977
8030
|
spinner.succeed("Project copied");
|
|
7978
8031
|
if (!keepDs) {
|
|
@@ -8035,7 +8088,7 @@ async function exportCommand(options = {}) {
|
|
|
8035
8088
|
// src/commands/status.ts
|
|
8036
8089
|
import chalk13 from "chalk";
|
|
8037
8090
|
import { basename as basename2 } from "path";
|
|
8038
|
-
import { DesignSystemManager as
|
|
8091
|
+
import { DesignSystemManager as DesignSystemManager6 } from "@getcoherent/core";
|
|
8039
8092
|
function countTokens(tokens) {
|
|
8040
8093
|
let count = 0;
|
|
8041
8094
|
function countObj(obj) {
|
|
@@ -8067,7 +8120,7 @@ async function statusCommand() {
|
|
|
8067
8120
|
console.log(chalk13.gray("\u{1F4C4} Config: ") + chalk13.white(basename2(project.configPath)));
|
|
8068
8121
|
console.log("");
|
|
8069
8122
|
try {
|
|
8070
|
-
const manager = new
|
|
8123
|
+
const manager = new DesignSystemManager6(project.configPath);
|
|
8071
8124
|
await manager.load();
|
|
8072
8125
|
const config2 = manager.getConfig();
|
|
8073
8126
|
console.log(chalk13.cyan("\u{1F4CA} Statistics:\n"));
|
|
@@ -8110,7 +8163,7 @@ async function statusCommand() {
|
|
|
8110
8163
|
// src/commands/regenerate-docs.ts
|
|
8111
8164
|
import chalk14 from "chalk";
|
|
8112
8165
|
import ora5 from "ora";
|
|
8113
|
-
import { DesignSystemManager as
|
|
8166
|
+
import { DesignSystemManager as DesignSystemManager7 } from "@getcoherent/core";
|
|
8114
8167
|
import { ProjectScaffolder as ProjectScaffolder2 } from "@getcoherent/core";
|
|
8115
8168
|
async function regenerateDocsCommand() {
|
|
8116
8169
|
try {
|
|
@@ -8123,7 +8176,7 @@ async function regenerateDocsCommand() {
|
|
|
8123
8176
|
}
|
|
8124
8177
|
const spinner = ora5("Regenerating documentation pages...").start();
|
|
8125
8178
|
try {
|
|
8126
|
-
const manager = new
|
|
8179
|
+
const manager = new DesignSystemManager7(project.configPath);
|
|
8127
8180
|
await manager.load();
|
|
8128
8181
|
const config2 = manager.getConfig();
|
|
8129
8182
|
const scaffolder = new ProjectScaffolder2(config2, project.root);
|
|
@@ -8147,14 +8200,14 @@ async function regenerateDocsCommand() {
|
|
|
8147
8200
|
|
|
8148
8201
|
// src/commands/fix.ts
|
|
8149
8202
|
import chalk15 from "chalk";
|
|
8150
|
-
import { readdirSync as readdirSync7, readFileSync as readFileSync10, existsSync as
|
|
8203
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync10, existsSync as existsSync13, rmSync as rmSync5, mkdirSync as mkdirSync7 } from "fs";
|
|
8151
8204
|
import { resolve as resolve8, join as join11, relative as relative5, basename as basename3 } from "path";
|
|
8152
8205
|
import {
|
|
8153
|
-
DesignSystemManager as
|
|
8206
|
+
DesignSystemManager as DesignSystemManager8,
|
|
8154
8207
|
ComponentManager as ComponentManager5,
|
|
8155
8208
|
PageManager as PageManager3,
|
|
8156
8209
|
ComponentGenerator as ComponentGenerator3,
|
|
8157
|
-
loadManifest as
|
|
8210
|
+
loadManifest as loadManifest8,
|
|
8158
8211
|
saveManifest as saveManifest5
|
|
8159
8212
|
} from "@getcoherent/core";
|
|
8160
8213
|
function extractComponentIdsFromCode2(code) {
|
|
@@ -8206,7 +8259,7 @@ async function fixCommand(opts = {}) {
|
|
|
8206
8259
|
}
|
|
8207
8260
|
if (!skipCache) {
|
|
8208
8261
|
const nextDir = join11(projectRoot, ".next");
|
|
8209
|
-
if (
|
|
8262
|
+
if (existsSync13(nextDir)) {
|
|
8210
8263
|
if (!dryRun) rmSync5(nextDir, { recursive: true, force: true });
|
|
8211
8264
|
fixes.push("Cleared build cache");
|
|
8212
8265
|
console.log(chalk15.green(" \u2714 Cleared build cache"));
|
|
@@ -8240,7 +8293,7 @@ async function fixCommand(opts = {}) {
|
|
|
8240
8293
|
let cm = null;
|
|
8241
8294
|
let pm = null;
|
|
8242
8295
|
if (allComponentIds.size > 0) {
|
|
8243
|
-
dsm = new
|
|
8296
|
+
dsm = new DesignSystemManager8(project.configPath);
|
|
8244
8297
|
await dsm.load();
|
|
8245
8298
|
const config2 = dsm.getConfig();
|
|
8246
8299
|
cm = new ComponentManager5(config2);
|
|
@@ -8253,7 +8306,7 @@ async function fixCommand(opts = {}) {
|
|
|
8253
8306
|
} else {
|
|
8254
8307
|
const fileName = toKebabCase(id) + ".tsx";
|
|
8255
8308
|
const filePath = resolve8(projectRoot, "components", "ui", fileName);
|
|
8256
|
-
if (!
|
|
8309
|
+
if (!existsSync13(filePath)) missingFiles.push(id);
|
|
8257
8310
|
}
|
|
8258
8311
|
}
|
|
8259
8312
|
const provider = getComponentProvider();
|
|
@@ -8302,8 +8355,8 @@ async function fixCommand(opts = {}) {
|
|
|
8302
8355
|
}
|
|
8303
8356
|
}
|
|
8304
8357
|
}
|
|
8305
|
-
if (!dsm &&
|
|
8306
|
-
dsm = new
|
|
8358
|
+
if (!dsm && existsSync13(project.configPath)) {
|
|
8359
|
+
dsm = new DesignSystemManager8(project.configPath);
|
|
8307
8360
|
await dsm.load();
|
|
8308
8361
|
}
|
|
8309
8362
|
if (dsm && dsm.getConfig().name === "My App") {
|
|
@@ -8311,7 +8364,7 @@ async function fixCommand(opts = {}) {
|
|
|
8311
8364
|
let derivedName = null;
|
|
8312
8365
|
try {
|
|
8313
8366
|
const pkgPath = resolve8(projectRoot, "package.json");
|
|
8314
|
-
if (
|
|
8367
|
+
if (existsSync13(pkgPath)) {
|
|
8315
8368
|
const pkg = JSON.parse(readFileSync10(pkgPath, "utf-8"));
|
|
8316
8369
|
if (typeof pkg.name === "string" && pkg.name) {
|
|
8317
8370
|
derivedName = toTitleCase2(pkg.name);
|
|
@@ -8349,7 +8402,7 @@ async function fixCommand(opts = {}) {
|
|
|
8349
8402
|
const configName = dsm.getConfig().name;
|
|
8350
8403
|
if (configName && configName !== "My App") {
|
|
8351
8404
|
const appLayoutPath = resolve8(projectRoot, "app", "(app)", "layout.tsx");
|
|
8352
|
-
if (
|
|
8405
|
+
if (existsSync13(appLayoutPath)) {
|
|
8353
8406
|
let appLayoutCode = readFileSync10(appLayoutPath, "utf-8");
|
|
8354
8407
|
if (appLayoutCode.includes("My App")) {
|
|
8355
8408
|
appLayoutCode = appLayoutCode.replace(/My App/g, configName);
|
|
@@ -8368,7 +8421,7 @@ async function fixCommand(opts = {}) {
|
|
|
8368
8421
|
}
|
|
8369
8422
|
}
|
|
8370
8423
|
const sharedDir = resolve8(projectRoot, "components", "shared");
|
|
8371
|
-
if (
|
|
8424
|
+
if (existsSync13(sharedDir)) {
|
|
8372
8425
|
try {
|
|
8373
8426
|
for (const f of readdirSync7(sharedDir).filter((n) => n.endsWith(".tsx"))) {
|
|
8374
8427
|
const sharedPath = join11(sharedDir, f);
|
|
@@ -8425,12 +8478,12 @@ async function fixCommand(opts = {}) {
|
|
|
8425
8478
|
console.log(chalk15.green(` \u2714 ${verb} syntax: ${syntaxFixed} file(s) (use client, metadata, quotes)`));
|
|
8426
8479
|
}
|
|
8427
8480
|
try {
|
|
8428
|
-
const { loadPlan: loadPlan2 } = await import("./plan-generator-
|
|
8429
|
-
const { ensurePlanGroupLayouts: ensurePlanGroupLayouts2 } = await import("./code-generator-
|
|
8481
|
+
const { loadPlan: loadPlan2 } = await import("./plan-generator-D2UBTVCN.js");
|
|
8482
|
+
const { ensurePlanGroupLayouts: ensurePlanGroupLayouts2 } = await import("./code-generator-IZ6XM6WG.js");
|
|
8430
8483
|
const plan = loadPlan2(projectRoot);
|
|
8431
8484
|
if (plan) {
|
|
8432
8485
|
if (!dsm) {
|
|
8433
|
-
dsm = new
|
|
8486
|
+
dsm = new DesignSystemManager8(project.configPath);
|
|
8434
8487
|
await dsm.load();
|
|
8435
8488
|
}
|
|
8436
8489
|
await ensurePlanGroupLayouts2(projectRoot, plan, {}, dsm.getConfig());
|
|
@@ -8439,9 +8492,9 @@ async function fixCommand(opts = {}) {
|
|
|
8439
8492
|
console.log(chalk15.green(` \u2714 Verified group layouts: ${layoutTypes}`));
|
|
8440
8493
|
const hasSidebar = plan.groups.some((g) => g.layout === "sidebar" || g.layout === "both");
|
|
8441
8494
|
const sidebarPath = resolve8(projectRoot, "components", "shared", "sidebar.tsx");
|
|
8442
|
-
if (hasSidebar && !
|
|
8495
|
+
if (hasSidebar && !existsSync13(sidebarPath) && !dryRun) {
|
|
8443
8496
|
if (!dsm) {
|
|
8444
|
-
dsm = new
|
|
8497
|
+
dsm = new DesignSystemManager8(project.configPath);
|
|
8445
8498
|
await dsm.load();
|
|
8446
8499
|
}
|
|
8447
8500
|
const { PageGenerator } = await import("@getcoherent/core");
|
|
@@ -8458,7 +8511,7 @@ async function fixCommand(opts = {}) {
|
|
|
8458
8511
|
}
|
|
8459
8512
|
if (hasSidebar && !dryRun) {
|
|
8460
8513
|
const rootLayoutPath = resolve8(projectRoot, "app", "layout.tsx");
|
|
8461
|
-
if (
|
|
8514
|
+
if (existsSync13(rootLayoutPath)) {
|
|
8462
8515
|
let rootCode = readFileSync10(rootLayoutPath, "utf-8");
|
|
8463
8516
|
if (rootCode.includes("<Header")) {
|
|
8464
8517
|
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,10 +8530,10 @@ async function fixCommand(opts = {}) {
|
|
|
8477
8530
|
}
|
|
8478
8531
|
}
|
|
8479
8532
|
const publicLayoutPath = resolve8(projectRoot, "app", "(public)", "layout.tsx");
|
|
8480
|
-
const publicExists =
|
|
8533
|
+
const publicExists = existsSync13(publicLayoutPath);
|
|
8481
8534
|
const needsPublicLayout = !publicExists || !readFileSync10(publicLayoutPath, "utf-8").includes("<Header");
|
|
8482
8535
|
if (needsPublicLayout) {
|
|
8483
|
-
const { buildPublicLayoutCodeForSidebar } = await import("./code-generator-
|
|
8536
|
+
const { buildPublicLayoutCodeForSidebar } = await import("./code-generator-IZ6XM6WG.js");
|
|
8484
8537
|
mkdirSync7(resolve8(projectRoot, "app", "(public)"), { recursive: true });
|
|
8485
8538
|
const publicResult = safeWrite(publicLayoutPath, buildPublicLayoutCodeForSidebar(), projectRoot, backups);
|
|
8486
8539
|
if (publicResult.ok) {
|
|
@@ -8491,7 +8544,7 @@ async function fixCommand(opts = {}) {
|
|
|
8491
8544
|
}
|
|
8492
8545
|
}
|
|
8493
8546
|
const sidebarComponentPath2 = resolve8(projectRoot, "components", "shared", "sidebar.tsx");
|
|
8494
|
-
if (
|
|
8547
|
+
if (existsSync13(sidebarComponentPath2)) {
|
|
8495
8548
|
const existingSidebarCode = readFileSync10(sidebarComponentPath2, "utf-8");
|
|
8496
8549
|
const sidebarConfigName = dsm?.getConfig().name ?? "";
|
|
8497
8550
|
const hasWrongName = existingSidebarCode.includes("My App") && sidebarConfigName !== "My App";
|
|
@@ -8499,7 +8552,7 @@ async function fixCommand(opts = {}) {
|
|
|
8499
8552
|
const isBroken = !isValidTsx(existingSidebarCode, projectRoot);
|
|
8500
8553
|
if (hasWrongName || hasTrigger || isBroken) {
|
|
8501
8554
|
if (!dsm) {
|
|
8502
|
-
dsm = new
|
|
8555
|
+
dsm = new DesignSystemManager8(project.configPath);
|
|
8503
8556
|
await dsm.load();
|
|
8504
8557
|
}
|
|
8505
8558
|
const { PageGenerator } = await import("@getcoherent/core");
|
|
@@ -8520,7 +8573,7 @@ async function fixCommand(opts = {}) {
|
|
|
8520
8573
|
}
|
|
8521
8574
|
const rootPagePath = resolve8(projectRoot, "app", "page.tsx");
|
|
8522
8575
|
const publicPagePath = resolve8(projectRoot, "app", "(public)", "page.tsx");
|
|
8523
|
-
if (
|
|
8576
|
+
if (existsSync13(rootPagePath) && !existsSync13(publicPagePath)) {
|
|
8524
8577
|
const { renameSync } = await import("fs");
|
|
8525
8578
|
mkdirSync7(resolve8(projectRoot, "app", "(public)"), { recursive: true });
|
|
8526
8579
|
renameSync(rootPagePath, publicPagePath);
|
|
@@ -8528,8 +8581,8 @@ async function fixCommand(opts = {}) {
|
|
|
8528
8581
|
console.log(chalk15.green(" \u2714 Moved app/page.tsx \u2192 app/(public)/page.tsx (gets Header/Footer)"));
|
|
8529
8582
|
}
|
|
8530
8583
|
const themeTogglePath = resolve8(projectRoot, "components", "shared", "theme-toggle.tsx");
|
|
8531
|
-
if (!
|
|
8532
|
-
const { generateThemeToggleCode } = await import("./code-generator-
|
|
8584
|
+
if (!existsSync13(themeTogglePath)) {
|
|
8585
|
+
const { generateThemeToggleCode } = await import("./code-generator-IZ6XM6WG.js");
|
|
8533
8586
|
mkdirSync7(resolve8(projectRoot, "components", "shared"), { recursive: true });
|
|
8534
8587
|
const themeResult = safeWrite(themeTogglePath, generateThemeToggleCode(), projectRoot, backups);
|
|
8535
8588
|
if (themeResult.ok) {
|
|
@@ -8545,12 +8598,12 @@ async function fixCommand(opts = {}) {
|
|
|
8545
8598
|
console.log(chalk15.yellow(` \u26A0 Layout repair skipped: ${err instanceof Error ? err.message : "unknown error"}`));
|
|
8546
8599
|
}
|
|
8547
8600
|
const appLayoutRepairPath = resolve8(projectRoot, "app", "(app)", "layout.tsx");
|
|
8548
|
-
if (
|
|
8601
|
+
if (existsSync13(appLayoutRepairPath) && dsm) {
|
|
8549
8602
|
const appLayoutCode = readFileSync10(appLayoutRepairPath, "utf-8");
|
|
8550
8603
|
const isMinimal = appLayoutCode.length < 500 && !appLayoutCode.includes("Header") && !appLayoutCode.includes("Footer") && !appLayoutCode.includes("Sidebar") && !appLayoutCode.includes("SidebarProvider") && !appLayoutCode.includes("SidebarTrigger") && !appLayoutCode.includes("Sheet");
|
|
8551
8604
|
const navType = dsm.getConfig().navigation?.type || "header";
|
|
8552
8605
|
if (isMinimal && navType !== "none") {
|
|
8553
|
-
const { buildAppLayoutCode, buildGroupLayoutCode } = await import("./code-generator-
|
|
8606
|
+
const { buildAppLayoutCode, buildGroupLayoutCode } = await import("./code-generator-IZ6XM6WG.js");
|
|
8554
8607
|
const isSidebar = navType === "sidebar" || navType === "both";
|
|
8555
8608
|
const newLayout = isSidebar ? buildAppLayoutCode(navType, dsm.getConfig().name) : buildGroupLayoutCode("header", dsm.getConfig().pages?.map((p) => p.name) || [], dsm.getConfig().name);
|
|
8556
8609
|
if (!dryRun) {
|
|
@@ -8683,7 +8736,7 @@ async function fixCommand(opts = {}) {
|
|
|
8683
8736
|
}
|
|
8684
8737
|
}
|
|
8685
8738
|
try {
|
|
8686
|
-
let manifest = await
|
|
8739
|
+
let manifest = await loadManifest8(project.root);
|
|
8687
8740
|
let manifestModified = false;
|
|
8688
8741
|
const { manifest: cleaned, removed: orphaned } = removeOrphanedEntries(project.root, manifest);
|
|
8689
8742
|
if (orphaned.length > 0) {
|
|
@@ -8763,7 +8816,7 @@ async function fixCommand(opts = {}) {
|
|
|
8763
8816
|
}
|
|
8764
8817
|
try {
|
|
8765
8818
|
const tsconfigPath = resolve8(projectRoot, "tsconfig.json");
|
|
8766
|
-
if (
|
|
8819
|
+
if (existsSync13(tsconfigPath)) {
|
|
8767
8820
|
const { runTscCheck, applyDeterministicFixes } = await import("./tsc-autofix-S5PKMFSC.js");
|
|
8768
8821
|
const { applyAiFixes } = await import("./tsc-ai-fix-O3EMRWV2.js");
|
|
8769
8822
|
const tscErrors = runTscCheck(projectRoot);
|
|
@@ -8851,8 +8904,8 @@ async function fixCommand(opts = {}) {
|
|
|
8851
8904
|
// src/commands/check.ts
|
|
8852
8905
|
import chalk16 from "chalk";
|
|
8853
8906
|
import { resolve as resolve9 } from "path";
|
|
8854
|
-
import { readdirSync as readdirSync8, readFileSync as readFileSync11, statSync as statSync3, existsSync as
|
|
8855
|
-
import { loadManifest as
|
|
8907
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync11, statSync as statSync3, existsSync as existsSync14 } from "fs";
|
|
8908
|
+
import { loadManifest as loadManifest9 } from "@getcoherent/core";
|
|
8856
8909
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", "design-system"]);
|
|
8857
8910
|
function findTsxFiles(dir) {
|
|
8858
8911
|
const results = [];
|
|
@@ -8887,8 +8940,8 @@ async function checkCommand(opts = {}) {
|
|
|
8887
8940
|
};
|
|
8888
8941
|
let validRoutes = [];
|
|
8889
8942
|
try {
|
|
8890
|
-
const { DesignSystemManager:
|
|
8891
|
-
const dsm = new
|
|
8943
|
+
const { DesignSystemManager: DesignSystemManager14 } = await import("@getcoherent/core");
|
|
8944
|
+
const dsm = new DesignSystemManager14(project.configPath);
|
|
8892
8945
|
await dsm.load();
|
|
8893
8946
|
validRoutes = dsm.getConfig().pages.map((p) => p.route).filter(Boolean);
|
|
8894
8947
|
} catch {
|
|
@@ -8979,11 +9032,11 @@ async function checkCommand(opts = {}) {
|
|
|
8979
9032
|
\u{1F517} Internal Links`) + chalk16.dim(` \u2014 all ${result.links.total} links resolve \u2713`));
|
|
8980
9033
|
}
|
|
8981
9034
|
try {
|
|
8982
|
-
const manifest = await
|
|
9035
|
+
const manifest = await loadManifest9(project.root);
|
|
8983
9036
|
if (manifest.shared.length > 0) {
|
|
8984
9037
|
for (const entry of manifest.shared) {
|
|
8985
9038
|
const fullPath = resolve9(project.root, entry.file);
|
|
8986
|
-
if (!
|
|
9039
|
+
if (!existsSync14(fullPath)) {
|
|
8987
9040
|
result.pages.withErrors++;
|
|
8988
9041
|
if (!opts.json) console.log(chalk16.red(`
|
|
8989
9042
|
\u2717 Missing shared component file: ${entry.id} (${entry.file})`));
|
|
@@ -8995,7 +9048,7 @@ async function checkCommand(opts = {}) {
|
|
|
8995
9048
|
}
|
|
8996
9049
|
if (!skipShared) {
|
|
8997
9050
|
try {
|
|
8998
|
-
const manifest = await
|
|
9051
|
+
const manifest = await loadManifest9(projectRoot);
|
|
8999
9052
|
if (!opts.json && manifest.shared.length > 0) {
|
|
9000
9053
|
console.log(chalk16.cyan(`
|
|
9001
9054
|
\u{1F9E9} Shared Components`) + chalk16.dim(` (${manifest.shared.length} registered)
|
|
@@ -9008,7 +9061,7 @@ async function checkCommand(opts = {}) {
|
|
|
9008
9061
|
let _nameMismatch = 0;
|
|
9009
9062
|
for (const entry of manifest.shared) {
|
|
9010
9063
|
const filePath = resolve9(projectRoot, entry.file);
|
|
9011
|
-
const fileExists =
|
|
9064
|
+
const fileExists = existsSync14(filePath);
|
|
9012
9065
|
if (!fileExists) {
|
|
9013
9066
|
_orphaned++;
|
|
9014
9067
|
if (!opts.json) {
|
|
@@ -9086,7 +9139,7 @@ async function checkCommand(opts = {}) {
|
|
|
9086
9139
|
id: e.id,
|
|
9087
9140
|
name: e.name,
|
|
9088
9141
|
type: e.type,
|
|
9089
|
-
status:
|
|
9142
|
+
status: existsSync14(resolve9(projectRoot, e.file)) ? "ok" : "unused",
|
|
9090
9143
|
message: "",
|
|
9091
9144
|
suggestions: void 0
|
|
9092
9145
|
}))
|
|
@@ -9097,10 +9150,10 @@ async function checkCommand(opts = {}) {
|
|
|
9097
9150
|
if (!skipShared) {
|
|
9098
9151
|
try {
|
|
9099
9152
|
const { validateReuse } = await import("./reuse-validator-XR2ZEYC4.js");
|
|
9100
|
-
const { inferPageTypeFromRoute: inferPageTypeFromRoute2 } = await import("./design-constraints-
|
|
9101
|
-
const manifest = await
|
|
9153
|
+
const { inferPageTypeFromRoute: inferPageTypeFromRoute2 } = await import("./design-constraints-PFZDW2XW.js");
|
|
9154
|
+
const manifest = await loadManifest9(projectRoot);
|
|
9102
9155
|
const appDir = resolve9(projectRoot, "app");
|
|
9103
|
-
const pageFiles =
|
|
9156
|
+
const pageFiles = existsSync14(appDir) ? findTsxFiles(appDir) : [];
|
|
9104
9157
|
if (manifest.shared.length > 0 && pageFiles.length > 0) {
|
|
9105
9158
|
const reuseWarnings = [];
|
|
9106
9159
|
for (const file of pageFiles) {
|
|
@@ -9175,7 +9228,7 @@ async function doctorCommand() {
|
|
|
9175
9228
|
import chalk19 from "chalk";
|
|
9176
9229
|
async function rulesCommand() {
|
|
9177
9230
|
try {
|
|
9178
|
-
const result = await
|
|
9231
|
+
const result = await regenerateAllHarnessFiles();
|
|
9179
9232
|
if (!result.written) {
|
|
9180
9233
|
exitNotCoherent();
|
|
9181
9234
|
}
|
|
@@ -9183,7 +9236,7 @@ async function rulesCommand() {
|
|
|
9183
9236
|
if (result.sharedCount !== void 0) parts.push(`${result.sharedCount} shared components`);
|
|
9184
9237
|
if (result.tokenKeys !== void 0) parts.push(`${result.tokenKeys} design token keys`);
|
|
9185
9238
|
const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
9186
|
-
console.log(chalk19.green(`\u2714 Updated .cursorrules and
|
|
9239
|
+
console.log(chalk19.green(`\u2714 Updated .cursorrules, CLAUDE.md, and AGENTS.md${summary}
|
|
9187
9240
|
`));
|
|
9188
9241
|
} catch (error) {
|
|
9189
9242
|
console.error(chalk19.red("\u274C Command failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
@@ -9209,13 +9262,13 @@ async function auditCommand(options) {
|
|
|
9209
9262
|
import { Command } from "commander";
|
|
9210
9263
|
import chalk22 from "chalk";
|
|
9211
9264
|
import {
|
|
9212
|
-
DesignSystemManager as
|
|
9265
|
+
DesignSystemManager as DesignSystemManager9,
|
|
9213
9266
|
ComponentManager as ComponentManager6,
|
|
9214
|
-
loadManifest as
|
|
9267
|
+
loadManifest as loadManifest10,
|
|
9215
9268
|
generateSharedComponent as generateSharedComponent4,
|
|
9216
9269
|
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout2
|
|
9217
9270
|
} from "@getcoherent/core";
|
|
9218
|
-
import { existsSync as
|
|
9271
|
+
import { existsSync as existsSync15 } from "fs";
|
|
9219
9272
|
import { resolve as resolve10 } from "path";
|
|
9220
9273
|
|
|
9221
9274
|
// src/utils/ds-files.ts
|
|
@@ -9248,11 +9301,11 @@ function createComponentsCommand() {
|
|
|
9248
9301
|
cmd.command("list").description("List all components (shared + UI)").option("--json", "Machine-readable JSON output").action(async (opts) => {
|
|
9249
9302
|
const project = findConfig();
|
|
9250
9303
|
if (!project) exitNotCoherent();
|
|
9251
|
-
const dsm = new
|
|
9304
|
+
const dsm = new DesignSystemManager9(project.configPath);
|
|
9252
9305
|
await dsm.load();
|
|
9253
9306
|
const config2 = dsm.getConfig();
|
|
9254
9307
|
const cm = new ComponentManager6(config2);
|
|
9255
|
-
const manifest = await
|
|
9308
|
+
const manifest = await loadManifest10(project.root);
|
|
9256
9309
|
if (opts.json) {
|
|
9257
9310
|
const installed2 = cm.getAllComponents();
|
|
9258
9311
|
console.log(JSON.stringify({ shared: manifest.shared, ui: installed2 }, null, 2));
|
|
@@ -9312,7 +9365,7 @@ function createComponentsCommand() {
|
|
|
9312
9365
|
sharedCmd.option("--json", "Machine-readable JSON output").option("--verbose", "Show file paths and usage details").action(async (opts) => {
|
|
9313
9366
|
const project = findConfig();
|
|
9314
9367
|
if (!project) exitNotCoherent();
|
|
9315
|
-
const manifest = await
|
|
9368
|
+
const manifest = await loadManifest10(project.root);
|
|
9316
9369
|
if (opts.json) {
|
|
9317
9370
|
console.log(JSON.stringify(manifest, null, 2));
|
|
9318
9371
|
return;
|
|
@@ -9370,9 +9423,9 @@ function createComponentsCommand() {
|
|
|
9370
9423
|
if (updated) console.log(chalk22.cyan(" Updated app/layout.tsx to use shared layout components.\n"));
|
|
9371
9424
|
}
|
|
9372
9425
|
const sharedPagePath = resolve10(project.root, "app/design-system/shared/page.tsx");
|
|
9373
|
-
if (!
|
|
9426
|
+
if (!existsSync15(sharedPagePath)) {
|
|
9374
9427
|
try {
|
|
9375
|
-
const dsm = new
|
|
9428
|
+
const dsm = new DesignSystemManager9(project.configPath);
|
|
9376
9429
|
await dsm.load();
|
|
9377
9430
|
const config2 = dsm.getConfig();
|
|
9378
9431
|
const written = await writeDesignSystemFiles(project.root, config2, { sharedOnly: true });
|
|
@@ -9384,7 +9437,7 @@ function createComponentsCommand() {
|
|
|
9384
9437
|
}
|
|
9385
9438
|
}
|
|
9386
9439
|
try {
|
|
9387
|
-
await
|
|
9440
|
+
await writeAllHarnessFiles(project.root);
|
|
9388
9441
|
} catch (e) {
|
|
9389
9442
|
if (process.env.COHERENT_DEBUG === "1") console.error(chalk22.dim("Could not update .cursorrules:"), e);
|
|
9390
9443
|
}
|
|
@@ -9397,7 +9450,7 @@ import chalk23 from "chalk";
|
|
|
9397
9450
|
import ora6 from "ora";
|
|
9398
9451
|
import { writeFile as writeFile5, mkdir as mkdir4 } from "fs/promises";
|
|
9399
9452
|
import { resolve as resolve11, join as join13, dirname as dirname6 } from "path";
|
|
9400
|
-
import { existsSync as
|
|
9453
|
+
import { existsSync as existsSync16 } from "fs";
|
|
9401
9454
|
import {
|
|
9402
9455
|
FigmaClient,
|
|
9403
9456
|
parseFigmaFileResponse,
|
|
@@ -9410,7 +9463,7 @@ import {
|
|
|
9410
9463
|
generateSharedComponent as generateSharedComponent5,
|
|
9411
9464
|
generatePagesFromFigma,
|
|
9412
9465
|
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout3,
|
|
9413
|
-
DesignSystemManager as
|
|
9466
|
+
DesignSystemManager as DesignSystemManager10,
|
|
9414
9467
|
validateConfig,
|
|
9415
9468
|
FRAMEWORK_VERSIONS as FRAMEWORK_VERSIONS2,
|
|
9416
9469
|
CLI_VERSION as CLI_VERSION3
|
|
@@ -9645,8 +9698,8 @@ async function importFigmaAction(urlOrKey, opts) {
|
|
|
9645
9698
|
if (!dryRun) {
|
|
9646
9699
|
spinner.start("Updating design-system.config.ts...");
|
|
9647
9700
|
const configPath = resolve11(projectRoot, DESIGN_SYSTEM_CONFIG_PATH);
|
|
9648
|
-
const dsm = new
|
|
9649
|
-
if (
|
|
9701
|
+
const dsm = new DesignSystemManager10(configPath);
|
|
9702
|
+
if (existsSync16(configPath)) {
|
|
9650
9703
|
await dsm.load();
|
|
9651
9704
|
const existing = dsm.getConfig();
|
|
9652
9705
|
dsm.updateConfig({
|
|
@@ -9676,7 +9729,7 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
|
|
|
9676
9729
|
spinner.succeed("design-system.config.ts updated");
|
|
9677
9730
|
spinner.start("Ensuring root layout...");
|
|
9678
9731
|
const layoutPath = join13(projectRoot, "app/layout.tsx");
|
|
9679
|
-
if (!
|
|
9732
|
+
if (!existsSync16(layoutPath)) {
|
|
9680
9733
|
await mkdir4(dirname6(layoutPath), { recursive: true });
|
|
9681
9734
|
await writeFile5(layoutPath, MINIMAL_ROOT_LAYOUT, "utf-8");
|
|
9682
9735
|
stats.filesWritten.push("app/layout.tsx");
|
|
@@ -9691,7 +9744,7 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
|
|
|
9691
9744
|
stats.dsFilesWritten = dsFiles.length;
|
|
9692
9745
|
spinner.succeed(`DS viewer: ${dsFiles.length} files`);
|
|
9693
9746
|
try {
|
|
9694
|
-
await
|
|
9747
|
+
await writeAllHarnessFiles(projectRoot);
|
|
9695
9748
|
} catch (e) {
|
|
9696
9749
|
if (process.env.COHERENT_DEBUG === "1") console.error(chalk23.dim("Could not update .cursorrules:"), e);
|
|
9697
9750
|
}
|
|
@@ -9742,7 +9795,7 @@ function printReport(stats, opts) {
|
|
|
9742
9795
|
// src/commands/ds.ts
|
|
9743
9796
|
import chalk24 from "chalk";
|
|
9744
9797
|
import ora7 from "ora";
|
|
9745
|
-
import { DesignSystemManager as
|
|
9798
|
+
import { DesignSystemManager as DesignSystemManager11 } from "@getcoherent/core";
|
|
9746
9799
|
async function dsRegenerateCommand() {
|
|
9747
9800
|
try {
|
|
9748
9801
|
const project = findConfig();
|
|
@@ -9750,7 +9803,7 @@ async function dsRegenerateCommand() {
|
|
|
9750
9803
|
exitNotCoherent();
|
|
9751
9804
|
}
|
|
9752
9805
|
const spinner = ora7("Loading config and regenerating Design System pages...").start();
|
|
9753
|
-
const dsm = new
|
|
9806
|
+
const dsm = new DesignSystemManager11(project.configPath);
|
|
9754
9807
|
await dsm.load();
|
|
9755
9808
|
const config2 = dsm.getConfig();
|
|
9756
9809
|
const written = await writeDesignSystemFiles(project.root, config2);
|
|
@@ -9766,9 +9819,9 @@ async function dsRegenerateCommand() {
|
|
|
9766
9819
|
// src/commands/update.ts
|
|
9767
9820
|
import chalk25 from "chalk";
|
|
9768
9821
|
import ora8 from "ora";
|
|
9769
|
-
import { readFileSync as readFileSync12, existsSync as
|
|
9822
|
+
import { readFileSync as readFileSync12, existsSync as existsSync17 } from "fs";
|
|
9770
9823
|
import { join as join14 } from "path";
|
|
9771
|
-
import { DesignSystemManager as
|
|
9824
|
+
import { DesignSystemManager as DesignSystemManager12, CLI_VERSION as CLI_VERSION4 } from "@getcoherent/core";
|
|
9772
9825
|
|
|
9773
9826
|
// src/utils/migrations.ts
|
|
9774
9827
|
var MIGRATIONS = [
|
|
@@ -9810,7 +9863,7 @@ async function updateCommand(opts) {
|
|
|
9810
9863
|
}
|
|
9811
9864
|
const spinner = ora8("Loading project configuration...").start();
|
|
9812
9865
|
try {
|
|
9813
|
-
const dsm = new
|
|
9866
|
+
const dsm = new DesignSystemManager12(project.configPath);
|
|
9814
9867
|
await dsm.load();
|
|
9815
9868
|
const config2 = dsm.getConfig();
|
|
9816
9869
|
const projectVersion = config2.coherentVersion || "0.0.0";
|
|
@@ -9850,7 +9903,7 @@ async function updateCommand(opts) {
|
|
|
9850
9903
|
const overlayFiles = await writeDesignSystemFiles(project.root, config2);
|
|
9851
9904
|
report.overlayFiles = overlayFiles.length;
|
|
9852
9905
|
spinner.text = "Updating .cursorrules and CLAUDE.md...";
|
|
9853
|
-
const rulesResult = await
|
|
9906
|
+
const rulesResult = await writeAllHarnessFiles(project.root);
|
|
9854
9907
|
report.rulesUpdated = rulesResult.written;
|
|
9855
9908
|
spinner.text = "Checking globals.css...";
|
|
9856
9909
|
report.missingCssVars = checkMissingCssVars(project.root);
|
|
@@ -9937,7 +9990,7 @@ var EXPECTED_CSS_VARS = [
|
|
|
9937
9990
|
];
|
|
9938
9991
|
function checkMissingCssVars(projectRoot) {
|
|
9939
9992
|
const globalsPath = join14(projectRoot, "app", "globals.css");
|
|
9940
|
-
if (!
|
|
9993
|
+
if (!existsSync17(globalsPath)) return [];
|
|
9941
9994
|
try {
|
|
9942
9995
|
const content = readFileSync12(globalsPath, "utf-8");
|
|
9943
9996
|
return EXPECTED_CSS_VARS.filter((v) => !content.includes(v));
|
|
@@ -9947,7 +10000,7 @@ function checkMissingCssVars(projectRoot) {
|
|
|
9947
10000
|
}
|
|
9948
10001
|
function patchGlobalsCss(projectRoot, missingVars) {
|
|
9949
10002
|
const globalsPath = join14(projectRoot, "app", "globals.css");
|
|
9950
|
-
if (!
|
|
10003
|
+
if (!existsSync17(globalsPath) || missingVars.length === 0) return;
|
|
9951
10004
|
const { writeFileSync: writeFileSync11 } = __require("fs");
|
|
9952
10005
|
let content = readFileSync12(globalsPath, "utf-8");
|
|
9953
10006
|
const defaultValues = {
|
|
@@ -10027,16 +10080,16 @@ async function undoCommand(options) {
|
|
|
10027
10080
|
// src/commands/sync.ts
|
|
10028
10081
|
import chalk27 from "chalk";
|
|
10029
10082
|
import ora9 from "ora";
|
|
10030
|
-
import { existsSync as
|
|
10083
|
+
import { existsSync as existsSync18, readFileSync as readFileSync13 } from "fs";
|
|
10031
10084
|
import { join as join15, relative as relative6, dirname as dirname7 } from "path";
|
|
10032
10085
|
import { readdir as readdir3, readFile as readFile4 } from "fs/promises";
|
|
10033
|
-
import { DesignSystemManager as
|
|
10034
|
-
import { loadManifest as
|
|
10086
|
+
import { DesignSystemManager as DesignSystemManager13 } from "@getcoherent/core";
|
|
10087
|
+
import { loadManifest as loadManifest11, saveManifest as saveManifest6, findSharedComponent } from "@getcoherent/core";
|
|
10035
10088
|
function extractTokensFromProject(projectRoot) {
|
|
10036
10089
|
const lightColors = {};
|
|
10037
10090
|
const darkColors = {};
|
|
10038
10091
|
const globalsPath = join15(projectRoot, "app", "globals.css");
|
|
10039
|
-
if (
|
|
10092
|
+
if (existsSync18(globalsPath)) {
|
|
10040
10093
|
const css = readFileSync13(globalsPath, "utf-8");
|
|
10041
10094
|
const rootMatch = css.match(/:root\s*\{([^}]+)\}/s);
|
|
10042
10095
|
if (rootMatch) parseVarsInto(rootMatch[1], lightColors);
|
|
@@ -10045,7 +10098,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
10045
10098
|
}
|
|
10046
10099
|
const layoutPath = join15(projectRoot, "app", "layout.tsx");
|
|
10047
10100
|
let layoutCode = "";
|
|
10048
|
-
if (
|
|
10101
|
+
if (existsSync18(layoutPath)) {
|
|
10049
10102
|
layoutCode = readFileSync13(layoutPath, "utf-8");
|
|
10050
10103
|
const rootInline = layoutCode.match(/:root\s*\{([^}]+)\}/s);
|
|
10051
10104
|
if (rootInline && Object.keys(lightColors).length === 0) {
|
|
@@ -10064,7 +10117,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
10064
10117
|
defaultMode = "dark";
|
|
10065
10118
|
}
|
|
10066
10119
|
let radius;
|
|
10067
|
-
const allCss = [
|
|
10120
|
+
const allCss = [existsSync18(globalsPath) ? readFileSync13(globalsPath, "utf-8") : "", layoutCode].join("\n");
|
|
10068
10121
|
const radiusMatch = allCss.match(/--radius:\s*([^;]+);/);
|
|
10069
10122
|
if (radiusMatch) radius = radiusMatch[1].trim();
|
|
10070
10123
|
return {
|
|
@@ -10088,7 +10141,7 @@ function parseVarsInto(block, target) {
|
|
|
10088
10141
|
async function detectCustomComponents(projectRoot, allPageCode) {
|
|
10089
10142
|
const results = [];
|
|
10090
10143
|
const componentsDir = join15(projectRoot, "components");
|
|
10091
|
-
if (!
|
|
10144
|
+
if (!existsSync18(componentsDir)) return results;
|
|
10092
10145
|
const files = [];
|
|
10093
10146
|
await walkForTsx(componentsDir, files, ["ui"]);
|
|
10094
10147
|
const fileResults = await Promise.all(
|
|
@@ -10279,11 +10332,11 @@ async function syncCommand(options = {}) {
|
|
|
10279
10332
|
const spinner = ora9("Scanning project files...").start();
|
|
10280
10333
|
try {
|
|
10281
10334
|
const appDir = join15(project.root, "app");
|
|
10282
|
-
if (!
|
|
10335
|
+
if (!existsSync18(appDir)) {
|
|
10283
10336
|
spinner.fail("No app/ directory found");
|
|
10284
10337
|
process.exit(1);
|
|
10285
10338
|
}
|
|
10286
|
-
const dsm = new
|
|
10339
|
+
const dsm = new DesignSystemManager13(project.configPath);
|
|
10287
10340
|
await dsm.load();
|
|
10288
10341
|
const config2 = dsm.getConfig();
|
|
10289
10342
|
const discoveredPages = await discoverPages(appDir);
|
|
@@ -10323,7 +10376,7 @@ async function syncCommand(options = {}) {
|
|
|
10323
10376
|
let reconcileResult = null;
|
|
10324
10377
|
if (doComponents) {
|
|
10325
10378
|
spinner.start("Reconciling shared components...");
|
|
10326
|
-
const manifest = await
|
|
10379
|
+
const manifest = await loadManifest11(project.root);
|
|
10327
10380
|
const { manifest: reconciledManifest, result: rr } = reconcileComponents(project.root, manifest);
|
|
10328
10381
|
reconcileResult = rr;
|
|
10329
10382
|
if (!dryRun) {
|
|
@@ -10411,9 +10464,8 @@ async function syncCommand(options = {}) {
|
|
|
10411
10464
|
const written = await writeDesignSystemFiles(project.root, config2);
|
|
10412
10465
|
spinner.succeed(`Regenerated ${written.length} Design System file(s)`);
|
|
10413
10466
|
spinner.start("Updating AI context files...");
|
|
10414
|
-
await
|
|
10415
|
-
|
|
10416
|
-
spinner.succeed("Updated .cursorrules and CLAUDE.md");
|
|
10467
|
+
await writeAllHarnessFiles(project.root);
|
|
10468
|
+
spinner.succeed("Updated .cursorrules, CLAUDE.md, and AGENTS.md");
|
|
10417
10469
|
}
|
|
10418
10470
|
console.log("");
|
|
10419
10471
|
console.log(chalk27.green(`\u2705 Design System ${dryRun ? "analyzed" : "synced"} with actual code
|
|
@@ -10509,7 +10561,7 @@ async function syncCommand(options = {}) {
|
|
|
10509
10561
|
// src/commands/migrate.ts
|
|
10510
10562
|
import chalk28 from "chalk";
|
|
10511
10563
|
import ora10 from "ora";
|
|
10512
|
-
import { existsSync as
|
|
10564
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync8, cpSync, rmSync as rmSync6, writeFileSync as writeFileSync9, readFileSync as readFileSync14, readdirSync as readdirSync9 } from "fs";
|
|
10513
10565
|
import { join as join16 } from "path";
|
|
10514
10566
|
function backupDir(projectRoot) {
|
|
10515
10567
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -10522,11 +10574,11 @@ function createBackup2(projectRoot) {
|
|
|
10522
10574
|
const uiDir = join16(projectRoot, "components", "ui");
|
|
10523
10575
|
const dest = backupDir(projectRoot);
|
|
10524
10576
|
mkdirSync8(dest, { recursive: true });
|
|
10525
|
-
if (
|
|
10577
|
+
if (existsSync19(uiDir)) {
|
|
10526
10578
|
cpSync(uiDir, join16(dest, "components-ui"), { recursive: true });
|
|
10527
10579
|
}
|
|
10528
10580
|
const configPath = join16(projectRoot, "design-system.config.ts");
|
|
10529
|
-
if (
|
|
10581
|
+
if (existsSync19(configPath)) {
|
|
10530
10582
|
cpSync(configPath, join16(dest, "design-system.config.ts"));
|
|
10531
10583
|
}
|
|
10532
10584
|
return dest;
|
|
@@ -10538,24 +10590,24 @@ function setGuard(projectRoot, backupPath) {
|
|
|
10538
10590
|
}
|
|
10539
10591
|
function clearGuard(projectRoot) {
|
|
10540
10592
|
const guard = guardPath(projectRoot);
|
|
10541
|
-
if (
|
|
10593
|
+
if (existsSync19(guard)) rmSync6(guard);
|
|
10542
10594
|
}
|
|
10543
10595
|
function rollback(projectRoot) {
|
|
10544
10596
|
const guard = guardPath(projectRoot);
|
|
10545
|
-
if (!
|
|
10597
|
+
if (!existsSync19(guard)) return false;
|
|
10546
10598
|
try {
|
|
10547
10599
|
const data = JSON.parse(readFileSync14(guard, "utf-8"));
|
|
10548
10600
|
const backup = data.backup;
|
|
10549
|
-
if (!
|
|
10601
|
+
if (!existsSync19(backup)) return false;
|
|
10550
10602
|
const uiBackup = join16(backup, "components-ui");
|
|
10551
10603
|
const uiDir = join16(projectRoot, "components", "ui");
|
|
10552
|
-
if (
|
|
10553
|
-
if (
|
|
10604
|
+
if (existsSync19(uiBackup)) {
|
|
10605
|
+
if (existsSync19(uiDir)) rmSync6(uiDir, { recursive: true });
|
|
10554
10606
|
cpSync(uiBackup, uiDir, { recursive: true });
|
|
10555
10607
|
}
|
|
10556
10608
|
const configBackup = join16(backup, "design-system.config.ts");
|
|
10557
10609
|
const configDest = join16(projectRoot, "design-system.config.ts");
|
|
10558
|
-
if (
|
|
10610
|
+
if (existsSync19(configBackup)) {
|
|
10559
10611
|
cpSync(configBackup, configDest);
|
|
10560
10612
|
}
|
|
10561
10613
|
clearGuard(projectRoot);
|
|
@@ -10583,13 +10635,13 @@ async function migrateAction(options) {
|
|
|
10583
10635
|
return;
|
|
10584
10636
|
}
|
|
10585
10637
|
const guard = guardPath(projectRoot);
|
|
10586
|
-
if (
|
|
10638
|
+
if (existsSync19(guard)) {
|
|
10587
10639
|
console.log(chalk28.yellow("A migration is already in progress."));
|
|
10588
10640
|
console.log(chalk28.dim("Run `coherent migrate --rollback` to undo, or delete .coherent/migration-in-progress"));
|
|
10589
10641
|
return;
|
|
10590
10642
|
}
|
|
10591
10643
|
const uiDir = join16(projectRoot, "components", "ui");
|
|
10592
|
-
if (!
|
|
10644
|
+
if (!existsSync19(uiDir)) {
|
|
10593
10645
|
console.log(chalk28.yellow("No components/ui directory found. Nothing to migrate."));
|
|
10594
10646
|
return;
|
|
10595
10647
|
}
|
|
@@ -10616,7 +10668,7 @@ Found ${migratable.length} component(s) to migrate:`));
|
|
|
10616
10668
|
try {
|
|
10617
10669
|
for (const id of migratable) {
|
|
10618
10670
|
const filePath = join16(uiDir, `${id}.tsx`);
|
|
10619
|
-
if (
|
|
10671
|
+
if (existsSync19(filePath)) rmSync6(filePath);
|
|
10620
10672
|
}
|
|
10621
10673
|
const results = await provider.installBatch(migratable, projectRoot, { force: true });
|
|
10622
10674
|
let migrated = 0;
|
|
@@ -10638,7 +10690,7 @@ Found ${migratable.length} component(s) to migrate:`));
|
|
|
10638
10690
|
}
|
|
10639
10691
|
|
|
10640
10692
|
// src/utils/update-notifier.ts
|
|
10641
|
-
import { existsSync as
|
|
10693
|
+
import { existsSync as existsSync20, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
|
|
10642
10694
|
import { join as join17 } from "path";
|
|
10643
10695
|
import { homedir } from "os";
|
|
10644
10696
|
import chalk29 from "chalk";
|
|
@@ -10650,7 +10702,7 @@ var CACHE_FILE = join17(CACHE_DIR, "update-check.json");
|
|
|
10650
10702
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
10651
10703
|
function readCache() {
|
|
10652
10704
|
try {
|
|
10653
|
-
if (!
|
|
10705
|
+
if (!existsSync20(CACHE_FILE)) return null;
|
|
10654
10706
|
const raw = readFileSync15(CACHE_FILE, "utf-8");
|
|
10655
10707
|
return JSON.parse(raw);
|
|
10656
10708
|
} catch (e) {
|
|
@@ -10660,7 +10712,7 @@ function readCache() {
|
|
|
10660
10712
|
}
|
|
10661
10713
|
function writeCache(data) {
|
|
10662
10714
|
try {
|
|
10663
|
-
if (!
|
|
10715
|
+
if (!existsSync20(CACHE_DIR)) mkdirSync9(CACHE_DIR, { recursive: true });
|
|
10664
10716
|
writeFileSync10(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
10665
10717
|
} catch (e) {
|
|
10666
10718
|
if (DEBUG5) console.error("Failed to write update cache:", e);
|