@getcoherent/cli 0.5.14 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-4M2RBSYF.js +206 -0
- package/dist/index.js +1149 -677
- package/dist/plan-generator-XKMZTEGK.js +29 -0
- package/package.json +10 -10
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateArchitecturePlan,
|
|
3
|
+
getPageGroup,
|
|
4
|
+
getPageType,
|
|
5
|
+
loadPlan,
|
|
6
|
+
savePlan
|
|
7
|
+
} from "./chunk-4M2RBSYF.js";
|
|
1
8
|
import {
|
|
2
9
|
__require
|
|
3
10
|
} from "./chunk-3RG5ZIWI.js";
|
|
@@ -1227,9 +1234,7 @@ var COMPONENT_APIS = {
|
|
|
1227
1234
|
</CommandGroup>
|
|
1228
1235
|
</CommandList>
|
|
1229
1236
|
</Command>`,
|
|
1230
|
-
antiPatterns: [
|
|
1231
|
-
"NEVER build custom search palette \u2014 use Command/CommandDialog"
|
|
1232
|
-
]
|
|
1237
|
+
antiPatterns: ["NEVER build custom search palette \u2014 use Command/CommandDialog"]
|
|
1233
1238
|
},
|
|
1234
1239
|
tabs: {
|
|
1235
1240
|
name: "Tabs",
|
|
@@ -1269,7 +1274,16 @@ var COMPONENT_APIS = {
|
|
|
1269
1274
|
},
|
|
1270
1275
|
table: {
|
|
1271
1276
|
name: "Table",
|
|
1272
|
-
subcomponents: [
|
|
1277
|
+
subcomponents: [
|
|
1278
|
+
"Table",
|
|
1279
|
+
"TableHeader",
|
|
1280
|
+
"TableBody",
|
|
1281
|
+
"TableFooter",
|
|
1282
|
+
"TableRow",
|
|
1283
|
+
"TableHead",
|
|
1284
|
+
"TableCell",
|
|
1285
|
+
"TableCaption"
|
|
1286
|
+
],
|
|
1273
1287
|
importPath: "@/components/ui/table",
|
|
1274
1288
|
keyProps: {},
|
|
1275
1289
|
usage: `<Table>
|
|
@@ -1280,9 +1294,7 @@ var COMPONENT_APIS = {
|
|
|
1280
1294
|
<TableRow><TableCell>Value</TableCell></TableRow>
|
|
1281
1295
|
</TableBody>
|
|
1282
1296
|
</Table>`,
|
|
1283
|
-
antiPatterns: [
|
|
1284
|
-
"NEVER use native <table> \u2014 use shadcn Table for consistent styling"
|
|
1285
|
-
]
|
|
1297
|
+
antiPatterns: ["NEVER use native <table> \u2014 use shadcn Table for consistent styling"]
|
|
1286
1298
|
},
|
|
1287
1299
|
accordion: {
|
|
1288
1300
|
name: "Accordion",
|
|
@@ -1349,17 +1361,15 @@ var ShadcnProvider = class {
|
|
|
1349
1361
|
if (!force && deps.existsSync(componentPath)) return;
|
|
1350
1362
|
try {
|
|
1351
1363
|
await new Promise((resolve16, reject) => {
|
|
1352
|
-
deps.exec(
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
if (err) reject(err);
|
|
1357
|
-
else resolve16();
|
|
1358
|
-
}
|
|
1359
|
-
);
|
|
1364
|
+
deps.exec(`npx shadcn@latest add ${name} --yes --overwrite`, { cwd: projectRoot, timeout: 15e3 }, (err) => {
|
|
1365
|
+
if (err) reject(err);
|
|
1366
|
+
else resolve16();
|
|
1367
|
+
});
|
|
1360
1368
|
});
|
|
1361
1369
|
} catch {
|
|
1362
|
-
console.warn(
|
|
1370
|
+
console.warn(
|
|
1371
|
+
`Could not install ${name} (network error or timeout). Run \`npx shadcn@latest add ${name}\` manually.`
|
|
1372
|
+
);
|
|
1363
1373
|
}
|
|
1364
1374
|
}
|
|
1365
1375
|
async installComponent(id, projectRoot, options) {
|
|
@@ -1452,10 +1462,7 @@ var ShadcnProvider = class {
|
|
|
1452
1462
|
"ring"
|
|
1453
1463
|
];
|
|
1454
1464
|
const lines = sidebarVars.map((v) => ` --color-sidebar-${v}: var(--sidebar-${v});`);
|
|
1455
|
-
const chartLines = Array.from(
|
|
1456
|
-
{ length: 5 },
|
|
1457
|
-
(_, i) => ` --color-chart-${i + 1}: var(--chart-${i + 1});`
|
|
1458
|
-
);
|
|
1465
|
+
const chartLines = Array.from({ length: 5 }, (_, i) => ` --color-chart-${i + 1}: var(--chart-${i + 1});`);
|
|
1459
1466
|
return `@theme inline {
|
|
1460
1467
|
${lines.join("\n")}
|
|
1461
1468
|
${chartLines.join("\n")}
|
|
@@ -1678,7 +1685,8 @@ function createMinimalConfig() {
|
|
|
1678
1685
|
framework: "next",
|
|
1679
1686
|
typescript: true,
|
|
1680
1687
|
cssFramework: "tailwind",
|
|
1681
|
-
autoScaffold: false
|
|
1688
|
+
autoScaffold: false,
|
|
1689
|
+
homePagePlaceholder: true
|
|
1682
1690
|
},
|
|
1683
1691
|
createdAt: now,
|
|
1684
1692
|
updatedAt: now
|
|
@@ -1983,8 +1991,8 @@ function fixEscapedClosingQuotes(code) {
|
|
|
1983
1991
|
}
|
|
1984
1992
|
function fixUnescapedLtInJsx(code) {
|
|
1985
1993
|
let out = code;
|
|
1986
|
-
out = out.replace(/>([
|
|
1987
|
-
out = out.replace(/>([
|
|
1994
|
+
out = out.replace(/>([^<\n]*)<(\d)/g, ">$1<$2");
|
|
1995
|
+
out = out.replace(/>([^<\n]*)<([^/a-zA-Z!{>\n])/g, ">$1<$2");
|
|
1988
1996
|
return out;
|
|
1989
1997
|
}
|
|
1990
1998
|
|
|
@@ -3744,10 +3752,10 @@ async function createAppRouteGroupLayout(projectPath) {
|
|
|
3744
3752
|
}
|
|
3745
3753
|
|
|
3746
3754
|
// src/commands/chat.ts
|
|
3747
|
-
import
|
|
3755
|
+
import chalk14 from "chalk";
|
|
3748
3756
|
import ora2 from "ora";
|
|
3749
3757
|
import { resolve as resolve9, relative as relative2, join as join11 } from "path";
|
|
3750
|
-
import { existsSync as existsSync16, readFileSync as
|
|
3758
|
+
import { existsSync as existsSync16, readFileSync as readFileSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
|
|
3751
3759
|
import {
|
|
3752
3760
|
DesignSystemManager as DesignSystemManager7,
|
|
3753
3761
|
ComponentManager as ComponentManager5,
|
|
@@ -3911,6 +3919,27 @@ var PAGE_TEMPLATES = {
|
|
|
3911
3919
|
"Label"
|
|
3912
3920
|
]
|
|
3913
3921
|
},
|
|
3922
|
+
register: {
|
|
3923
|
+
description: "Registration page with centered card form",
|
|
3924
|
+
sections: [
|
|
3925
|
+
'Centered layout: outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10". Inner div className="w-full max-w-sm".',
|
|
3926
|
+
'Card with CardHeader: CardTitle "Create an account" (text-2xl font-bold), CardDescription "Enter your details to get started" (text-sm text-muted-foreground).',
|
|
3927
|
+
'CardContent with form: name Input, email Input (type="email"), password Input (type="password"), confirm password Input (type="password"), and a Button "Create account" (w-full).',
|
|
3928
|
+
'CardFooter: text "Already have an account?" with a Sign in link. All text is text-sm text-muted-foreground.',
|
|
3929
|
+
'This page uses "use client" (has useState for form state). Do NOT include export const metadata.'
|
|
3930
|
+
],
|
|
3931
|
+
components: [
|
|
3932
|
+
"Card",
|
|
3933
|
+
"CardHeader",
|
|
3934
|
+
"CardTitle",
|
|
3935
|
+
"CardDescription",
|
|
3936
|
+
"CardContent",
|
|
3937
|
+
"CardFooter",
|
|
3938
|
+
"Button",
|
|
3939
|
+
"Input",
|
|
3940
|
+
"Label"
|
|
3941
|
+
]
|
|
3942
|
+
},
|
|
3914
3943
|
pricing: {
|
|
3915
3944
|
description: "Pricing page with tier comparison cards",
|
|
3916
3945
|
sections: [
|
|
@@ -4037,11 +4066,49 @@ var PAGE_TEMPLATES = {
|
|
|
4037
4066
|
"Page header. Timeline: each version with version badge, date, list of entries (type: text). Border-left timeline pattern. Badge component for version/date."
|
|
4038
4067
|
],
|
|
4039
4068
|
components: ["Badge"]
|
|
4069
|
+
},
|
|
4070
|
+
team: {
|
|
4071
|
+
description: "Team page with member cards",
|
|
4072
|
+
sections: [
|
|
4073
|
+
'Page header: h1 "Our Team" className="text-2xl font-bold tracking-tight" + p className="text-sm text-muted-foreground"',
|
|
4074
|
+
'Grid of team member Cards (className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"). Each card: avatar placeholder (bg-muted rounded-full size-12 with initials), name (text-sm font-semibold), role (text-sm text-muted-foreground).'
|
|
4075
|
+
],
|
|
4076
|
+
components: ["Card", "CardContent"]
|
|
4077
|
+
},
|
|
4078
|
+
tasks: {
|
|
4079
|
+
description: "Task list page with status badges and search",
|
|
4080
|
+
sections: [
|
|
4081
|
+
'Page header: h1 "Tasks" + description. Search input with Search icon.',
|
|
4082
|
+
"Task list: divide-y container. Each row: status badge (colored), task title, priority badge, assignee name. Use flex items-center justify-between.",
|
|
4083
|
+
'This page uses "use client" (has useState for search). Do NOT include export const metadata.'
|
|
4084
|
+
],
|
|
4085
|
+
components: ["Input", "Badge"]
|
|
4086
|
+
},
|
|
4087
|
+
"task-detail": {
|
|
4088
|
+
description: "Task detail page with info and activity",
|
|
4089
|
+
sections: [
|
|
4090
|
+
"Back button linking to /tasks. Page header with task title and description.",
|
|
4091
|
+
"Two-column layout (md:grid-cols-2): Left Card with task details (status, priority, assignee, due date). Right Card with activity timeline.",
|
|
4092
|
+
'This page uses "use client" (has useState). Do NOT include export const metadata.'
|
|
4093
|
+
],
|
|
4094
|
+
components: ["Card", "CardHeader", "CardTitle", "CardContent", "Button"]
|
|
4095
|
+
},
|
|
4096
|
+
"reset-password": {
|
|
4097
|
+
description: "Reset password page with centered card form",
|
|
4098
|
+
sections: [
|
|
4099
|
+
'Centered layout: outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10". Inner div className="w-full max-w-sm".',
|
|
4100
|
+
'Card with CardHeader: CardTitle "Reset Password" (text-xl), CardDescription.',
|
|
4101
|
+
'CardContent with form: new password Input (type="password"), confirm password Input (type="password"), Button "Reset password" (w-full).',
|
|
4102
|
+
'Footer text: "Remember your password?" with Sign in link.',
|
|
4103
|
+
'This page uses "use client" (has useState for form state). Do NOT include export const metadata.'
|
|
4104
|
+
],
|
|
4105
|
+
components: ["Card", "CardHeader", "CardTitle", "CardDescription", "CardContent", "Button", "Input", "Label"]
|
|
4040
4106
|
}
|
|
4041
4107
|
};
|
|
4042
4108
|
var AUTH_ROUTE_SEGMENTS = /* @__PURE__ */ new Set([
|
|
4043
4109
|
"login",
|
|
4044
4110
|
"signin",
|
|
4111
|
+
"sign-in",
|
|
4045
4112
|
"sign-up",
|
|
4046
4113
|
"signup",
|
|
4047
4114
|
"register",
|
|
@@ -4057,8 +4124,9 @@ function detectPageType(pageName) {
|
|
|
4057
4124
|
const normalized = pageName.toLowerCase();
|
|
4058
4125
|
if (/dashboard|admin|overview/.test(normalized)) return "dashboard";
|
|
4059
4126
|
if (/login|signin|sign-in/.test(normalized)) return "login";
|
|
4127
|
+
if (/register|signup|sign.?up/.test(normalized)) return "register";
|
|
4060
4128
|
if (/pricing|plans|subscription/.test(normalized)) return "pricing";
|
|
4061
|
-
if (/about|
|
|
4129
|
+
if (/about|company/.test(normalized)) return "about";
|
|
4062
4130
|
if (/contact|support|help/.test(normalized)) return "contact";
|
|
4063
4131
|
if (/settings|preferences|account/.test(normalized)) return "settings";
|
|
4064
4132
|
if (/home|landing|hero/.test(normalized)) return "landing";
|
|
@@ -4069,6 +4137,10 @@ function detectPageType(pageName) {
|
|
|
4069
4137
|
if (/gallery|portfolio|images/.test(normalized)) return "gallery";
|
|
4070
4138
|
if (/faq|frequently|questions/.test(normalized)) return "faq";
|
|
4071
4139
|
if (/changelog|release|versions/.test(normalized)) return "changelog";
|
|
4140
|
+
if (/team|members/.test(normalized)) return "team";
|
|
4141
|
+
if (/tasks?/.test(normalized) && /detail|\[id\]/.test(normalized)) return "task-detail";
|
|
4142
|
+
if (/tasks?/.test(normalized)) return "tasks";
|
|
4143
|
+
if (/reset.?password/.test(normalized)) return "reset-password";
|
|
4072
4144
|
return null;
|
|
4073
4145
|
}
|
|
4074
4146
|
function expandPageRequest(pageName, userRequest) {
|
|
@@ -4154,6 +4226,8 @@ LINKS & INTERACTIVE STATES (consistency is critical):
|
|
|
4154
4226
|
- ALL links on the SAME page MUST use the SAME style. Never mix underlined and non-underlined text links.
|
|
4155
4227
|
- ALL Button variants MUST have: hover: state, focus-visible:ring-2 focus-visible:ring-ring, active: state, disabled:opacity-50.
|
|
4156
4228
|
- ALL interactive elements MUST have visible hover and focus-visible states.
|
|
4229
|
+
- CRITICAL: Every <Link> MUST have an href prop. Missing href causes runtime errors. Never use <Link className="..."> or <Button asChild><Link> without href.
|
|
4230
|
+
- When shared components exist (@/components/shared/*), ALWAYS import and use them instead of re-implementing similar patterns inline.
|
|
4157
4231
|
|
|
4158
4232
|
ICONS:
|
|
4159
4233
|
- Size: ALWAYS size-4 (16px). Color: ALWAYS text-muted-foreground. Import: ALWAYS from lucide-react.
|
|
@@ -4183,12 +4257,11 @@ CONTENT (zero placeholders):
|
|
|
4183
4257
|
- NEVER: "Lorem ipsum", "Card content", "Description here"
|
|
4184
4258
|
- ALWAYS: Real, contextual content. Realistic metric names, values, dates.
|
|
4185
4259
|
`;
|
|
4186
|
-
var
|
|
4187
|
-
## DESIGN QUALITY
|
|
4260
|
+
var DESIGN_QUALITY_COMMON = `
|
|
4261
|
+
## DESIGN QUALITY \u2014 COMMON
|
|
4188
4262
|
|
|
4189
4263
|
### Typography Hierarchy
|
|
4190
4264
|
- Page headline (h1): text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight leading-[1.1]
|
|
4191
|
-
- Landing/marketing hero headline: text-5xl md:text-6xl lg:text-7xl font-bold tracking-tight leading-[1.05]
|
|
4192
4265
|
- Section titles (h2): text-2xl md:text-3xl font-bold
|
|
4193
4266
|
- Card titles (h3): text-sm font-semibold (never text-base or text-lg)
|
|
4194
4267
|
- Body text: text-sm text-muted-foreground leading-relaxed
|
|
@@ -4201,6 +4274,29 @@ var DESIGN_QUALITY = `
|
|
|
4201
4274
|
- Sections alternate between bg-background and bg-muted/5 for rhythm
|
|
4202
4275
|
- Section dividers: border-t border-border/10 (subtle, not heavy)
|
|
4203
4276
|
|
|
4277
|
+
### Buttons with Icons
|
|
4278
|
+
- Buttons containing text + icon: ALWAYS use inline-flex items-center gap-2 whitespace-nowrap
|
|
4279
|
+
- Icon inside button: h-4 w-4 (never larger), placed AFTER text for arrows, BEFORE text for action icons
|
|
4280
|
+
- NEVER let button content wrap to multiple lines \u2014 use whitespace-nowrap on the Button component
|
|
4281
|
+
- CTA buttons: use the Button component, NEVER raw <button> or <a> styled as button
|
|
4282
|
+
|
|
4283
|
+
### Accent Color Discipline
|
|
4284
|
+
- ONE accent color per page (primary or emerald-400)
|
|
4285
|
+
- Use for: CTAs, terminal text, check icons, feature icon backgrounds, active states
|
|
4286
|
+
- NEVER mix blue + purple + emerald on same page
|
|
4287
|
+
- Badge: outline style (border-border/30 bg-transparent) not filled color
|
|
4288
|
+
- Status icons: text-emerald-400 for positive, text-red-400 for negative
|
|
4289
|
+
|
|
4290
|
+
### Dark Theme Implementation
|
|
4291
|
+
- html element: className="dark"
|
|
4292
|
+
- Background: use CSS variables from globals.css dark section
|
|
4293
|
+
- Text: text-foreground for primary, text-muted-foreground for secondary
|
|
4294
|
+
- NEVER hardcode dark colors (bg-gray-900) \u2014 always use semantic tokens
|
|
4295
|
+
- Cards and elevated elements: slightly lighter than background (bg-card or bg-zinc-900/50)
|
|
4296
|
+
`;
|
|
4297
|
+
var DESIGN_QUALITY_MARKETING = `
|
|
4298
|
+
## DESIGN QUALITY \u2014 MARKETING PAGES
|
|
4299
|
+
|
|
4204
4300
|
### Spacing Rhythm (3 distinct levels)
|
|
4205
4301
|
- Between sections: py-20 md:py-28 (generous)
|
|
4206
4302
|
- Within sections (title to content): mb-12 md:mb-16
|
|
@@ -4208,11 +4304,8 @@ var DESIGN_QUALITY = `
|
|
|
4208
4304
|
- Between cards in grid: gap-5 (tight)
|
|
4209
4305
|
- NEVER uniform spacing everywhere \u2014 contrast creates rhythm
|
|
4210
4306
|
|
|
4211
|
-
###
|
|
4212
|
-
-
|
|
4213
|
-
- Icon inside button: h-4 w-4 (never larger), placed AFTER text for arrows, BEFORE text for action icons
|
|
4214
|
-
- NEVER let button content wrap to multiple lines \u2014 use whitespace-nowrap on the Button component
|
|
4215
|
-
- CTA buttons: use the Button component, NEVER raw <button> or <a> styled as button
|
|
4307
|
+
### Hero headline
|
|
4308
|
+
- Landing/marketing hero headline: text-5xl md:text-6xl lg:text-7xl font-bold tracking-tight leading-[1.05]
|
|
4216
4309
|
|
|
4217
4310
|
### Icons in Feature Cards
|
|
4218
4311
|
- Wrap in colored container: bg-primary/10 rounded-lg p-2.5
|
|
@@ -4228,14 +4321,7 @@ var DESIGN_QUALITY = `
|
|
|
4228
4321
|
- Title bar (optional): flex with 3 dots (bg-zinc-700 rounded-full w-2.5 h-2.5) + title text-zinc-500 text-[11px]
|
|
4229
4322
|
- Copy button: text-zinc-500 hover:text-zinc-300
|
|
4230
4323
|
|
|
4231
|
-
###
|
|
4232
|
-
- ONE accent color per page (primary or emerald-400)
|
|
4233
|
-
- Use for: CTAs, terminal text, check icons, feature icon backgrounds, active states
|
|
4234
|
-
- NEVER mix blue + purple + emerald on same page
|
|
4235
|
-
- Badge: outline style (border-border/30 bg-transparent) not filled color
|
|
4236
|
-
- Status icons: text-emerald-400 for positive, text-red-400 for negative
|
|
4237
|
-
|
|
4238
|
-
### Hero Section (landing/marketing pages)
|
|
4324
|
+
### Hero Section
|
|
4239
4325
|
- Minimum height: min-h-[80vh] flex items-center justify-center
|
|
4240
4326
|
- Content: centered, max-w-3xl, flex flex-col items-center text-center gap-8
|
|
4241
4327
|
- Badge above headline: small, outline, text-xs tracking-wide
|
|
@@ -4252,8 +4338,6 @@ var DESIGN_QUALITY = `
|
|
|
4252
4338
|
### Step/Process Sections
|
|
4253
4339
|
- Numbered steps: circle with border, number inside (w-10 h-10 rounded-full border border-border/30 text-sm)
|
|
4254
4340
|
- Label above: text-xs font-semibold tracking-widest uppercase text-muted-foreground
|
|
4255
|
-
- Each step has a code block showing the command
|
|
4256
|
-
- Description below: text-sm text-muted-foreground
|
|
4257
4341
|
|
|
4258
4342
|
### Footer
|
|
4259
4343
|
- Minimal: border-t border-border/10, py-10
|
|
@@ -4261,13 +4345,66 @@ var DESIGN_QUALITY = `
|
|
|
4261
4345
|
- Links: hover:text-foreground transition-colors
|
|
4262
4346
|
- Layout: flex justify-between on desktop, stack on mobile
|
|
4263
4347
|
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
|
|
4348
|
+
NEVER include app-style elements (sidebar widgets, data tables, filters) on marketing pages.
|
|
4349
|
+
`;
|
|
4350
|
+
var DESIGN_QUALITY_APP = `
|
|
4351
|
+
## DESIGN QUALITY \u2014 APP PAGES
|
|
4352
|
+
|
|
4353
|
+
### Spacing
|
|
4354
|
+
- gap-4 md:gap-6 between sections
|
|
4355
|
+
- p-4 lg:p-6 content padding
|
|
4356
|
+
- Within cards: p-4 to p-6 (compact)
|
|
4357
|
+
- Between cards in grid: gap-4 (tight)
|
|
4358
|
+
|
|
4359
|
+
### Layout
|
|
4360
|
+
- Data tables, card grids, filters, stat rows
|
|
4361
|
+
- Page wrapper: flex flex-1 flex-col gap-4 p-4 lg:p-6
|
|
4362
|
+
- Stats grid: grid gap-4 md:grid-cols-2 lg:grid-cols-4
|
|
4363
|
+
- Content: functional, scannable, data-dense
|
|
4364
|
+
|
|
4365
|
+
NEVER include marketing sections (hero, pricing, testimonials) on app pages.
|
|
4270
4366
|
`;
|
|
4367
|
+
var DESIGN_QUALITY_AUTH = `
|
|
4368
|
+
## DESIGN QUALITY \u2014 AUTH PAGES
|
|
4369
|
+
|
|
4370
|
+
### Layout
|
|
4371
|
+
- Centered card: flex min-h-svh items-center justify-center p-6 md:p-10
|
|
4372
|
+
- Card width: w-full max-w-sm
|
|
4373
|
+
- No navigation, no section containers, no sidebar
|
|
4374
|
+
- Single focused form with clear CTA
|
|
4375
|
+
- Card \u2192 CardHeader (title + description) \u2192 CardContent (form) \u2192 CardFooter (link to other auth page)
|
|
4376
|
+
|
|
4377
|
+
NEVER include navigation bars, sidebars, or multi-section layouts on auth pages.
|
|
4378
|
+
`;
|
|
4379
|
+
function getDesignQualityForType(type) {
|
|
4380
|
+
switch (type) {
|
|
4381
|
+
case "marketing":
|
|
4382
|
+
return DESIGN_QUALITY_MARKETING;
|
|
4383
|
+
case "app":
|
|
4384
|
+
return DESIGN_QUALITY_APP;
|
|
4385
|
+
case "auth":
|
|
4386
|
+
return DESIGN_QUALITY_AUTH;
|
|
4387
|
+
}
|
|
4388
|
+
}
|
|
4389
|
+
function inferPageTypeFromRoute(route) {
|
|
4390
|
+
const slug = route.replace(/^\//, "").split("/")[0] || "";
|
|
4391
|
+
const authSlugs = /* @__PURE__ */ new Set([
|
|
4392
|
+
"login",
|
|
4393
|
+
"register",
|
|
4394
|
+
"sign-up",
|
|
4395
|
+
"signup",
|
|
4396
|
+
"sign-in",
|
|
4397
|
+
"signin",
|
|
4398
|
+
"forgot-password",
|
|
4399
|
+
"reset-password"
|
|
4400
|
+
]);
|
|
4401
|
+
const marketingSlugs = /* @__PURE__ */ new Set(["pricing", "features", "about", "blog", "contact", "terms", "privacy"]);
|
|
4402
|
+
if (authSlugs.has(slug)) return "auth";
|
|
4403
|
+
if (marketingSlugs.has(slug) || slug === "") return "marketing";
|
|
4404
|
+
return "app";
|
|
4405
|
+
}
|
|
4406
|
+
var DESIGN_QUALITY = `${DESIGN_QUALITY_COMMON}
|
|
4407
|
+
${DESIGN_QUALITY_MARKETING}`;
|
|
4271
4408
|
var VISUAL_DEPTH = `
|
|
4272
4409
|
## VISUAL DEPTH TECHNIQUES (pick 1-3 per page based on context)
|
|
4273
4410
|
|
|
@@ -4905,6 +5042,11 @@ async function parseModification(message, context, provider = "auto", options) {
|
|
|
4905
5042
|
const navigation = !Array.isArray(raw2) && raw2?.navigation ? raw2.navigation : void 0;
|
|
4906
5043
|
return { requests: requestsArray2, uxRecommendations: void 0, navigation };
|
|
4907
5044
|
}
|
|
5045
|
+
if (options?.lightweight) {
|
|
5046
|
+
const raw2 = await ai.parseModification(message);
|
|
5047
|
+
const requestsArray2 = Array.isArray(raw2) ? raw2 : raw2?.requests ?? [];
|
|
5048
|
+
return { requests: requestsArray2, uxRecommendations: void 0 };
|
|
5049
|
+
}
|
|
4908
5050
|
const componentRegistry = buildComponentRegistry(context.componentManager);
|
|
4909
5051
|
let enhancedMessage = message;
|
|
4910
5052
|
let isExpandedPageRequest = false;
|
|
@@ -5013,6 +5155,7 @@ For editing an existing shared component use type "modify-layout-block" with tar
|
|
|
5013
5155
|
return `You are a design-forward UI architect. Your goal is to create interfaces that are not just functional, but visually distinctive and memorable \u2014 while staying within shadcn/ui and Tailwind CSS.
|
|
5014
5156
|
|
|
5015
5157
|
Parse the user's natural language request into structured modification requests.
|
|
5158
|
+
${sharedSection}
|
|
5016
5159
|
${designThinking}
|
|
5017
5160
|
${coreRules}
|
|
5018
5161
|
${designQuality}
|
|
@@ -5037,7 +5180,6 @@ LINKING RULES (CRITICAL \u2014 prevents broken links):
|
|
|
5037
5180
|
- Navigation components should link to ALL existing page routes.
|
|
5038
5181
|
|
|
5039
5182
|
${componentRegistry}
|
|
5040
|
-
${sharedSection}
|
|
5041
5183
|
|
|
5042
5184
|
Available shadcn/ui components (can be auto-installed): ${availableShadcn.join(", ")}
|
|
5043
5185
|
|
|
@@ -5264,6 +5406,20 @@ Return valid JSON only, no markdown code fence. Use this shape:
|
|
|
5264
5406
|
{ "requests": [ ... array of ModificationRequest ... ], "uxRecommendations": "optional markdown or omit key" }
|
|
5265
5407
|
Legacy: returning only a JSON array of requests is still accepted.`;
|
|
5266
5408
|
}
|
|
5409
|
+
function buildLightweightPagePrompt(pageName, route, styleContext, sharedComponentsSummary, pageType) {
|
|
5410
|
+
const designConstraints = pageType ? getDesignQualityForType(pageType) : "";
|
|
5411
|
+
return [
|
|
5412
|
+
`Generate complete pageCode for a page called "${pageName}" at route "${route}".`,
|
|
5413
|
+
`Output valid TSX with a default export React component.`,
|
|
5414
|
+
`Use shadcn/ui components (import from @/components/ui/*). Use Tailwind CSS semantic tokens only.`,
|
|
5415
|
+
pageType ? `PAGE TYPE: ${pageType}` : "",
|
|
5416
|
+
designConstraints,
|
|
5417
|
+
styleContext ? `Follow this style context:
|
|
5418
|
+
${styleContext}` : "",
|
|
5419
|
+
sharedComponentsSummary ? `Available shared components:
|
|
5420
|
+
${sharedComponentsSummary}` : ""
|
|
5421
|
+
].filter(Boolean).join("\n\n");
|
|
5422
|
+
}
|
|
5267
5423
|
async function checkComponentReuse(requests, componentManager) {
|
|
5268
5424
|
const enhanced = [];
|
|
5269
5425
|
for (const request of requests) {
|
|
@@ -6251,6 +6407,17 @@ function validatePageQuality(code, validRoutes) {
|
|
|
6251
6407
|
severity: "error"
|
|
6252
6408
|
});
|
|
6253
6409
|
}
|
|
6410
|
+
const linkWithoutHrefRe = /<(?:Link|a)\b(?![^>]*\bhref\s*=)[^>]*>/g;
|
|
6411
|
+
let linkNoHrefMatch;
|
|
6412
|
+
while ((linkNoHrefMatch = linkWithoutHrefRe.exec(code)) !== null) {
|
|
6413
|
+
const matchLine = code.slice(0, linkNoHrefMatch.index).split("\n").length;
|
|
6414
|
+
issues.push({
|
|
6415
|
+
line: matchLine,
|
|
6416
|
+
type: "LINK_MISSING_HREF",
|
|
6417
|
+
message: "<Link> or <a> without href prop \u2014 causes Next.js runtime error. Add href attribute.",
|
|
6418
|
+
severity: "error"
|
|
6419
|
+
});
|
|
6420
|
+
}
|
|
6254
6421
|
issues.push(...detectComponentIssues(code));
|
|
6255
6422
|
return issues;
|
|
6256
6423
|
}
|
|
@@ -6356,11 +6523,36 @@ async function autoFixCode(code) {
|
|
|
6356
6523
|
fixes.push("fixed escaped closing quotes in strings");
|
|
6357
6524
|
}
|
|
6358
6525
|
const beforeEntityFix = fixed;
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6526
|
+
const isInsideAttrValue = (line, idx) => {
|
|
6527
|
+
let inQuote = false;
|
|
6528
|
+
let inAttr = false;
|
|
6529
|
+
for (let i = 0; i < idx; i++) {
|
|
6530
|
+
if (line[i] === "=" && line[i + 1] === '"') {
|
|
6531
|
+
inAttr = true;
|
|
6532
|
+
inQuote = true;
|
|
6533
|
+
i++;
|
|
6534
|
+
} else if (inAttr && line[i] === '"') {
|
|
6535
|
+
inAttr = false;
|
|
6536
|
+
inQuote = false;
|
|
6537
|
+
}
|
|
6538
|
+
}
|
|
6539
|
+
return inQuote;
|
|
6540
|
+
};
|
|
6541
|
+
fixed = fixed.split("\n").map((line) => {
|
|
6542
|
+
let l = line;
|
|
6543
|
+
l = l.replace(/<=/g, (m, offset) => isInsideAttrValue(line, offset) ? m : "<=");
|
|
6544
|
+
l = l.replace(/>=/g, (m, offset) => isInsideAttrValue(line, offset) ? m : ">=");
|
|
6545
|
+
l = l.replace(/&&/g, (m, offset) => isInsideAttrValue(line, offset) ? m : "&&");
|
|
6546
|
+
l = l.replace(
|
|
6547
|
+
/([\w)\]])\s*<\s*([\w(])/g,
|
|
6548
|
+
(m, p1, p2, offset) => isInsideAttrValue(line, offset) ? m : `${p1} < ${p2}`
|
|
6549
|
+
);
|
|
6550
|
+
l = l.replace(
|
|
6551
|
+
/([\w)\]])\s*>\s*([\w(])/g,
|
|
6552
|
+
(m, p1, p2, offset) => isInsideAttrValue(line, offset) ? m : `${p1} > ${p2}`
|
|
6553
|
+
);
|
|
6554
|
+
return l;
|
|
6555
|
+
}).join("\n");
|
|
6364
6556
|
if (fixed !== beforeEntityFix) {
|
|
6365
6557
|
fixes.push("Fixed syntax issues");
|
|
6366
6558
|
}
|
|
@@ -6707,6 +6899,11 @@ ${selectImport}`
|
|
|
6707
6899
|
if (fixed !== beforeAsChildFlex) {
|
|
6708
6900
|
fixes.push("added inline-flex to Button asChild children (base-ui compat)");
|
|
6709
6901
|
}
|
|
6902
|
+
const beforeLinkHrefFix = fixed;
|
|
6903
|
+
fixed = fixed.replace(/<(Link|a)\b(?![^>]*\bhref\s*=)([^>]*)>/g, '<$1 href="/"$2>');
|
|
6904
|
+
if (fixed !== beforeLinkHrefFix) {
|
|
6905
|
+
fixes.push('added href="/" to <Link>/<a> missing href');
|
|
6906
|
+
}
|
|
6710
6907
|
const { code: fixedByRules, fixes: ruleFixes } = applyComponentRules(fixed);
|
|
6711
6908
|
if (ruleFixes.length > 0) {
|
|
6712
6909
|
fixed = fixedByRules;
|
|
@@ -6830,7 +7027,16 @@ import { DesignSystemManager as DesignSystemManager3, loadManifest as loadManife
|
|
|
6830
7027
|
import chalk8 from "chalk";
|
|
6831
7028
|
var MARKETING_ROUTES = /* @__PURE__ */ new Set(["", "landing", "pricing", "about", "contact", "blog", "features"]);
|
|
6832
7029
|
var MIN_ANCHOR_PAGE_CODE_CHARS = 120;
|
|
6833
|
-
var AUTH_ROUTE_SLUGS = /* @__PURE__ */ new Set([
|
|
7030
|
+
var AUTH_ROUTE_SLUGS = /* @__PURE__ */ new Set([
|
|
7031
|
+
"login",
|
|
7032
|
+
"signin",
|
|
7033
|
+
"sign-in",
|
|
7034
|
+
"register",
|
|
7035
|
+
"sign-up",
|
|
7036
|
+
"signup",
|
|
7037
|
+
"forgot-password",
|
|
7038
|
+
"reset-password"
|
|
7039
|
+
]);
|
|
6834
7040
|
function inferRouteUsesAuthSegment(route) {
|
|
6835
7041
|
const slug = route.replace(/^\//, "").split("/")[0] || "";
|
|
6836
7042
|
return AUTH_ROUTE_SLUGS.has(slug);
|
|
@@ -6852,30 +7058,30 @@ function isMarketingRoute(route) {
|
|
|
6852
7058
|
const slug = route.replace(/^\//, "").split("/")[0] || "";
|
|
6853
7059
|
return MARKETING_ROUTES.has(slug);
|
|
6854
7060
|
}
|
|
6855
|
-
function routeToFsPath(projectRoot, route,
|
|
7061
|
+
function routeToFsPath(projectRoot, route, isAuthOrPlan) {
|
|
7062
|
+
const plan = typeof isAuthOrPlan === "object" ? isAuthOrPlan : void 0;
|
|
7063
|
+
const isAuth = typeof isAuthOrPlan === "boolean" ? isAuthOrPlan : false;
|
|
6856
7064
|
const slug = route.replace(/^\//, "");
|
|
6857
|
-
if (
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
return resolve5(projectRoot, "app", "page.tsx");
|
|
6862
|
-
}
|
|
6863
|
-
if (isMarketingRoute(route)) {
|
|
6864
|
-
return resolve5(projectRoot, "app", slug, "page.tsx");
|
|
7065
|
+
if (!slug) return resolve5(projectRoot, "app", "page.tsx");
|
|
7066
|
+
if (plan) {
|
|
7067
|
+
const group = getPageGroup(route, plan);
|
|
7068
|
+
if (group) return resolve5(projectRoot, "app", `(${group.id})`, slug, "page.tsx");
|
|
6865
7069
|
}
|
|
7070
|
+
if (isAuth) return resolve5(projectRoot, "app", "(auth)", slug || "login", "page.tsx");
|
|
7071
|
+
if (isMarketingRoute(route)) return resolve5(projectRoot, "app", slug, "page.tsx");
|
|
6866
7072
|
return resolve5(projectRoot, "app", "(app)", slug, "page.tsx");
|
|
6867
7073
|
}
|
|
6868
|
-
function routeToRelPath(route,
|
|
7074
|
+
function routeToRelPath(route, isAuthOrPlan) {
|
|
7075
|
+
const plan = typeof isAuthOrPlan === "object" ? isAuthOrPlan : void 0;
|
|
7076
|
+
const isAuth = typeof isAuthOrPlan === "boolean" ? isAuthOrPlan : false;
|
|
6869
7077
|
const slug = route.replace(/^\//, "");
|
|
6870
|
-
if (
|
|
6871
|
-
|
|
6872
|
-
|
|
6873
|
-
|
|
6874
|
-
return "app/page.tsx";
|
|
6875
|
-
}
|
|
6876
|
-
if (isMarketingRoute(route)) {
|
|
6877
|
-
return `app/${slug}/page.tsx`;
|
|
7078
|
+
if (!slug) return "app/page.tsx";
|
|
7079
|
+
if (plan) {
|
|
7080
|
+
const group = getPageGroup(route, plan);
|
|
7081
|
+
if (group) return `app/(${group.id})/${slug}/page.tsx`;
|
|
6878
7082
|
}
|
|
7083
|
+
if (isAuth) return `app/(auth)/${slug || "login"}/page.tsx`;
|
|
7084
|
+
if (isMarketingRoute(route)) return `app/${slug}/page.tsx`;
|
|
6879
7085
|
return `app/(app)/${slug}/page.tsx`;
|
|
6880
7086
|
}
|
|
6881
7087
|
function deduplicatePages(pages) {
|
|
@@ -6900,13 +7106,24 @@ function extractComponentIdsFromCode(code) {
|
|
|
6900
7106
|
}
|
|
6901
7107
|
return ids;
|
|
6902
7108
|
}
|
|
6903
|
-
async function warnInlineDuplicates(projectRoot, pageName, pageCode, manifest) {
|
|
7109
|
+
async function warnInlineDuplicates(projectRoot, pageName, route, pageCode, manifest, plan) {
|
|
6904
7110
|
const sectionOrWidget = manifest.shared.filter((e) => e.type === "section" || e.type === "widget");
|
|
6905
7111
|
if (sectionOrWidget.length === 0) return;
|
|
7112
|
+
const plannedForPage = plan ? new Set(plan.sharedComponents.filter((c) => c.usedBy.includes(route)).map((c) => c.name)) : null;
|
|
6906
7113
|
for (const e of sectionOrWidget) {
|
|
7114
|
+
if (plannedForPage && !plannedForPage.has(e.name)) continue;
|
|
6907
7115
|
const kebab = e.file.replace(/^components\/shared\//, "").replace(/\.tsx$/, "");
|
|
6908
7116
|
const hasImport = pageCode.includes(`@/components/shared/${kebab}`);
|
|
6909
7117
|
if (hasImport) continue;
|
|
7118
|
+
if (plannedForPage) {
|
|
7119
|
+
console.log(
|
|
7120
|
+
chalk8.yellow(
|
|
7121
|
+
`
|
|
7122
|
+
\u26A0 Page "${pageName}" should use shared component ${e.name} (per architecture plan) but it's not imported. Import from @/components/shared/${kebab}`
|
|
7123
|
+
)
|
|
7124
|
+
);
|
|
7125
|
+
continue;
|
|
7126
|
+
}
|
|
6910
7127
|
const sameNameAsTag = new RegExp(`<\\/?${e.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s>]`).test(pageCode);
|
|
6911
7128
|
if (sameNameAsTag) {
|
|
6912
7129
|
console.log(
|
|
@@ -7397,7 +7614,10 @@ function applyDefaults(request) {
|
|
|
7397
7614
|
|
|
7398
7615
|
// src/commands/chat/split-generator.ts
|
|
7399
7616
|
import { z } from "zod";
|
|
7400
|
-
import {
|
|
7617
|
+
import {
|
|
7618
|
+
loadManifest as loadManifest5,
|
|
7619
|
+
generateSharedComponent as generateSharedComponent2
|
|
7620
|
+
} from "@getcoherent/core";
|
|
7401
7621
|
|
|
7402
7622
|
// src/utils/page-analyzer.ts
|
|
7403
7623
|
var FORM_COMPONENTS = /* @__PURE__ */ new Set(["Input", "Textarea", "Label", "Select", "Checkbox", "Switch"]);
|
|
@@ -7515,6 +7735,7 @@ async function pMap(items, fn, concurrency = 3) {
|
|
|
7515
7735
|
}
|
|
7516
7736
|
|
|
7517
7737
|
// src/commands/chat/split-generator.ts
|
|
7738
|
+
import chalk9 from "chalk";
|
|
7518
7739
|
function buildExistingPagesContext(config2) {
|
|
7519
7740
|
const pages = config2.pages || [];
|
|
7520
7741
|
const analyzed = pages.filter((p) => p.pageAnalysis);
|
|
@@ -7604,6 +7825,28 @@ function buildSharedComponentsSummary(manifest) {
|
|
|
7604
7825
|
Import: @/components/shared/${importPath}${propsLine}`;
|
|
7605
7826
|
}).join("\n");
|
|
7606
7827
|
}
|
|
7828
|
+
function buildSharedComponentsNote(sharedComponentsSummary) {
|
|
7829
|
+
if (!sharedComponentsSummary) return void 0;
|
|
7830
|
+
return `SHARED COMPONENTS \u2014 MANDATORY REUSE:
|
|
7831
|
+
Before implementing any section, check this list. Import and use matching components from @/components/shared/. Do NOT re-implement these patterns inline.
|
|
7832
|
+
|
|
7833
|
+
${sharedComponentsSummary}`;
|
|
7834
|
+
}
|
|
7835
|
+
function formatPlanSummary(plan) {
|
|
7836
|
+
if (plan.groups.length === 0) return "";
|
|
7837
|
+
const groupLines = plan.groups.map((g) => ` Group "${g.id}" (layout: ${g.layout}): ${g.pages.join(", ")}`);
|
|
7838
|
+
const compLines = plan.sharedComponents.map(
|
|
7839
|
+
(c) => ` ${c.name} (${c.type}) \u2014 ${c.description}; usedBy: ${c.usedBy.join(", ")}`
|
|
7840
|
+
);
|
|
7841
|
+
const parts = [`ARCHITECTURE PLAN:
|
|
7842
|
+
Groups:
|
|
7843
|
+
${groupLines.join("\n")}`];
|
|
7844
|
+
if (compLines.length > 0) {
|
|
7845
|
+
parts.push(`Shared Components:
|
|
7846
|
+
${compLines.join("\n")}`);
|
|
7847
|
+
}
|
|
7848
|
+
return parts.join("\n");
|
|
7849
|
+
}
|
|
7607
7850
|
async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts) {
|
|
7608
7851
|
let pageNames = [];
|
|
7609
7852
|
spinner.start("Phase 1/5 \u2014 Planning pages...");
|
|
@@ -7637,7 +7880,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7637
7880
|
}
|
|
7638
7881
|
if (pageNames.length === 0) {
|
|
7639
7882
|
spinner.fail("Could not determine pages to create");
|
|
7640
|
-
return [];
|
|
7883
|
+
return { requests: [], plan: null };
|
|
7641
7884
|
}
|
|
7642
7885
|
pageNames = deduplicatePages(pageNames);
|
|
7643
7886
|
const hasHomePage = pageNames.some((p) => p.route === "/");
|
|
@@ -7659,7 +7902,41 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7659
7902
|
const allRoutes = pageNames.map((p) => p.route).join(", ");
|
|
7660
7903
|
const allPagesList = pageNames.map((p) => `${p.name} (${p.route})`).join(", ");
|
|
7661
7904
|
const inferredNote = inferred.length > 0 ? ` (${inferred.length} auto-inferred)` : "";
|
|
7662
|
-
spinner.succeed(`Phase 1/
|
|
7905
|
+
spinner.succeed(`Phase 1/6 \u2014 Found ${pageNames.length} pages${inferredNote}: ${allPagesList}`);
|
|
7906
|
+
let plan = null;
|
|
7907
|
+
if (parseOpts.projectRoot) {
|
|
7908
|
+
spinner.start("Phase 2/6 \u2014 Generating architecture plan...");
|
|
7909
|
+
try {
|
|
7910
|
+
const ai = await createAIProvider(provider ?? "auto");
|
|
7911
|
+
const layoutHint = modCtx.config.navigation?.type || null;
|
|
7912
|
+
plan = await generateArchitecturePlan(pageNames, message, ai, layoutHint);
|
|
7913
|
+
if (plan) {
|
|
7914
|
+
const groupsSummary = plan.groups.map((g) => `${g.id} (${g.layout}, ${g.pages.length} pages)`).join(", ");
|
|
7915
|
+
const sharedSummary = plan.sharedComponents.length > 0 ? plan.sharedComponents.map((c) => `${c.name} \u2192 ${c.usedBy.join(", ")}`).join(" | ") : "";
|
|
7916
|
+
const totalPages = plan.groups.reduce((sum, g) => sum + g.pages.length, 0);
|
|
7917
|
+
spinner.succeed(`Phase 2/6 \u2014 Architecture plan created`);
|
|
7918
|
+
console.log(chalk9.dim(` Groups: ${groupsSummary}`));
|
|
7919
|
+
if (sharedSummary) console.log(chalk9.dim(` Shared: ${sharedSummary}`));
|
|
7920
|
+
console.log(chalk9.dim(` Total: ${totalPages} pages, ${plan.sharedComponents.length} shared components`));
|
|
7921
|
+
if (plan.sharedComponents.length > 0 && parseOpts.projectRoot) {
|
|
7922
|
+
const allDeps = new Set(plan.sharedComponents.flatMap((c) => c.shadcnDeps));
|
|
7923
|
+
if (allDeps.size > 0) {
|
|
7924
|
+
const componentProvider = getComponentProvider();
|
|
7925
|
+
for (const dep of allDeps) {
|
|
7926
|
+
try {
|
|
7927
|
+
await componentProvider.installComponent(dep, parseOpts.projectRoot);
|
|
7928
|
+
} catch {
|
|
7929
|
+
}
|
|
7930
|
+
}
|
|
7931
|
+
}
|
|
7932
|
+
}
|
|
7933
|
+
} else {
|
|
7934
|
+
spinner.warn("Phase 2/6 \u2014 Plan generation failed (continuing without plan)");
|
|
7935
|
+
}
|
|
7936
|
+
} catch {
|
|
7937
|
+
spinner.warn("Phase 2/6 \u2014 Plan generation failed (continuing without plan)");
|
|
7938
|
+
}
|
|
7939
|
+
}
|
|
7663
7940
|
const homeIdx = pageNames.findIndex((p) => p.route === "/");
|
|
7664
7941
|
const homePage = homeIdx !== -1 ? pageNames[homeIdx] : pageNames[0];
|
|
7665
7942
|
const remainingPages = pageNames.filter((_, i) => i !== (homeIdx !== -1 ? homeIdx : 0));
|
|
@@ -7667,17 +7944,18 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7667
7944
|
let homeRequest = null;
|
|
7668
7945
|
let homePageCode = "";
|
|
7669
7946
|
let reusedExistingAnchor = false;
|
|
7670
|
-
|
|
7947
|
+
const isPlaceholder = modCtx.config?.settings?.homePagePlaceholder === true;
|
|
7948
|
+
if (projectRoot && remainingPages.length > 0 && !isPlaceholder) {
|
|
7671
7949
|
const existingCode = readAnchorPageCodeFromDisk(projectRoot, homePage.route);
|
|
7672
7950
|
if (existingCode) {
|
|
7673
7951
|
reusedExistingAnchor = true;
|
|
7674
7952
|
homePageCode = existingCode;
|
|
7675
|
-
spinner.start(`Phase
|
|
7676
|
-
spinner.succeed(`Phase
|
|
7953
|
+
spinner.start(`Phase 3/6 \u2014 Loading ${homePage.name} from disk (style anchor)...`);
|
|
7954
|
+
spinner.succeed(`Phase 3/6 \u2014 Reused existing ${homePage.name} page (skipped AI regeneration)`);
|
|
7677
7955
|
}
|
|
7678
7956
|
}
|
|
7679
7957
|
if (!reusedExistingAnchor) {
|
|
7680
|
-
spinner.start(`Phase
|
|
7958
|
+
spinner.start(`Phase 3/6 \u2014 Generating ${homePage.name} page (sets design direction)...`);
|
|
7681
7959
|
try {
|
|
7682
7960
|
const homeResult = await parseModification(
|
|
7683
7961
|
`Create ONE page called "${homePage.name}" at route "${homePage.route}". Context: ${message}. This REPLACES the default placeholder page \u2014 generate a complete, content-rich landing page for the project described above. Generate complete pageCode. Include a branded site-wide <header> with navigation links to ALL these pages: ${allPagesList}. Use these EXACT routes in navigation: ${allRoutes}. Include a <footer> at the bottom. Make it visually polished \u2014 this page sets the design direction for the entire site. Do not generate other pages.`,
|
|
@@ -7699,68 +7977,102 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7699
7977
|
changes: { id: homePage.id, name: homePage.name, route: homePage.route }
|
|
7700
7978
|
};
|
|
7701
7979
|
}
|
|
7702
|
-
spinner.succeed(`Phase
|
|
7980
|
+
spinner.succeed(`Phase 3/6 \u2014 ${homePage.name} page generated`);
|
|
7703
7981
|
}
|
|
7704
|
-
spinner.start("Phase
|
|
7982
|
+
spinner.start("Phase 4/6 \u2014 Extracting design patterns...");
|
|
7705
7983
|
const styleContext = homePageCode ? extractStyleContext(homePageCode) : "";
|
|
7706
7984
|
if (styleContext) {
|
|
7707
7985
|
const lineCount = styleContext.split("\n").length - 1;
|
|
7708
7986
|
const source = reusedExistingAnchor ? `${homePage.name} (existing file)` : homePage.name;
|
|
7709
|
-
spinner.succeed(`Phase
|
|
7987
|
+
spinner.succeed(`Phase 4/6 \u2014 Extracted ${lineCount} style patterns from ${source}`);
|
|
7710
7988
|
} else {
|
|
7711
|
-
spinner.succeed("Phase
|
|
7989
|
+
spinner.succeed("Phase 4/6 \u2014 No style patterns extracted (anchor page had no code)");
|
|
7712
7990
|
}
|
|
7713
|
-
if (remainingPages.length >= 2 &&
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
if (!shouldSkip) {
|
|
7717
|
-
spinner.start("Phase 3.5/5 \u2014 Extracting shared components...");
|
|
7991
|
+
if (remainingPages.length >= 2 && projectRoot) {
|
|
7992
|
+
if (plan && plan.sharedComponents.length > 0) {
|
|
7993
|
+
spinner.start(`Phase 4.5/6 \u2014 Generating ${plan.sharedComponents.length} shared components from plan...`);
|
|
7718
7994
|
try {
|
|
7719
|
-
const
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7995
|
+
const { generateSharedComponentsFromPlan } = await import("./plan-generator-XKMZTEGK.js");
|
|
7996
|
+
const generated = await generateSharedComponentsFromPlan(
|
|
7997
|
+
plan,
|
|
7998
|
+
styleContext,
|
|
7999
|
+
projectRoot,
|
|
8000
|
+
await createAIProvider(provider ?? "auto")
|
|
8001
|
+
);
|
|
8002
|
+
if (generated.length > 0) {
|
|
8003
|
+
const updatedManifest = await loadManifest5(projectRoot);
|
|
8004
|
+
parseOpts.sharedComponentsSummary = buildSharedComponentsSummary(updatedManifest);
|
|
8005
|
+
const names = generated.map((c) => c.name).join(", ");
|
|
8006
|
+
spinner.succeed(`Phase 4.5/6 \u2014 Generated ${generated.length} shared components (${names})`);
|
|
7724
8007
|
} else {
|
|
7725
|
-
spinner.succeed("Phase
|
|
8008
|
+
spinner.succeed("Phase 4.5/6 \u2014 No shared components generated");
|
|
7726
8009
|
}
|
|
7727
8010
|
} catch {
|
|
7728
|
-
spinner.warn("Phase
|
|
8011
|
+
spinner.warn("Phase 4.5/6 \u2014 Could not generate shared components (continuing without)");
|
|
8012
|
+
}
|
|
8013
|
+
} else if (homePageCode) {
|
|
8014
|
+
const manifest = await loadManifest5(projectRoot);
|
|
8015
|
+
const shouldSkip = reusedExistingAnchor && manifest.shared.some((e) => e.type !== "layout");
|
|
8016
|
+
if (!shouldSkip) {
|
|
8017
|
+
spinner.start("Phase 4.5/6 \u2014 Extracting shared components (legacy)...");
|
|
8018
|
+
try {
|
|
8019
|
+
const extraction = await extractSharedComponents(homePageCode, projectRoot, provider ?? "auto");
|
|
8020
|
+
parseOpts.sharedComponentsSummary = extraction.summary;
|
|
8021
|
+
if (extraction.components.length > 0) {
|
|
8022
|
+
const names = extraction.components.map((c) => c.name).join(", ");
|
|
8023
|
+
spinner.succeed(`Phase 4.5/6 \u2014 Extracted ${extraction.components.length} shared components (${names})`);
|
|
8024
|
+
} else {
|
|
8025
|
+
spinner.succeed("Phase 4.5/6 \u2014 No shared components extracted");
|
|
8026
|
+
}
|
|
8027
|
+
} catch {
|
|
8028
|
+
spinner.warn("Phase 4.5/6 \u2014 Could not extract shared components (continuing without)");
|
|
8029
|
+
}
|
|
7729
8030
|
}
|
|
7730
8031
|
}
|
|
7731
8032
|
}
|
|
7732
8033
|
if (remainingPages.length === 0) {
|
|
7733
|
-
return homeRequest ? [homeRequest] : [];
|
|
8034
|
+
return { requests: homeRequest ? [homeRequest] : [], plan };
|
|
7734
8035
|
}
|
|
7735
|
-
spinner.start(`Phase
|
|
7736
|
-
const
|
|
8036
|
+
spinner.start(`Phase 5/6 \u2014 Generating ${remainingPages.length} pages in parallel...`);
|
|
8037
|
+
const sharedLayoutNote = "Header and Footer are shared components rendered by the root layout. Do NOT include any site-wide <header>, <nav>, or <footer> in this page. Start with the main content directly.";
|
|
8038
|
+
const sharedComponentsNote = buildSharedComponentsNote(parseOpts.sharedComponentsSummary);
|
|
7737
8039
|
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="#".`;
|
|
7738
8040
|
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.';
|
|
8041
|
+
const planSummaryNote = plan ? formatPlanSummary(plan) : "";
|
|
7739
8042
|
const existingPagesContext = buildExistingPagesContext(modCtx.config);
|
|
7740
8043
|
const AI_CONCURRENCY = 3;
|
|
7741
|
-
let
|
|
8044
|
+
let phase5Done = 0;
|
|
7742
8045
|
const remainingRequests = await pMap(
|
|
7743
8046
|
remainingPages,
|
|
7744
8047
|
async ({ name, id, route }) => {
|
|
8048
|
+
const isAuth = isAuthRoute(route) || isAuthRoute(name);
|
|
8049
|
+
const pageType = plan ? getPageType(route, plan) : inferPageTypeFromRoute(route);
|
|
8050
|
+
const designConstraints = getDesignQualityForType(pageType);
|
|
8051
|
+
const authNote = isAuth ? 'For this auth page: use centered card layout with outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10" and inner div className="w-full max-w-sm". Do NOT use section containers or full-width wrappers. The auth layout provides centering \u2014 just output the card content.' : void 0;
|
|
7745
8052
|
const prompt = [
|
|
7746
8053
|
`Create ONE page called "${name}" at route "${route}".`,
|
|
7747
8054
|
`Context: ${message}.`,
|
|
7748
8055
|
`Generate complete pageCode for this single page only. Do not generate other pages.`,
|
|
7749
|
-
|
|
8056
|
+
`PAGE TYPE: ${pageType}`,
|
|
8057
|
+
designConstraints,
|
|
8058
|
+
sharedLayoutNote,
|
|
8059
|
+
sharedComponentsNote,
|
|
7750
8060
|
routeNote,
|
|
7751
8061
|
alignmentNote,
|
|
8062
|
+
authNote,
|
|
8063
|
+
planSummaryNote,
|
|
7752
8064
|
existingPagesContext,
|
|
7753
8065
|
styleContext
|
|
7754
8066
|
].filter(Boolean).join("\n\n");
|
|
7755
8067
|
try {
|
|
7756
8068
|
const result = await parseModification(prompt, modCtx, provider, parseOpts);
|
|
7757
|
-
|
|
7758
|
-
spinner.text = `Phase
|
|
8069
|
+
phase5Done++;
|
|
8070
|
+
spinner.text = `Phase 5/6 \u2014 ${phase5Done}/${remainingPages.length} pages generated...`;
|
|
7759
8071
|
const codePage = result.requests.find((r) => r.type === "add-page");
|
|
7760
8072
|
return codePage || { type: "add-page", target: "new", changes: { id, name, route } };
|
|
7761
8073
|
} catch {
|
|
7762
|
-
|
|
7763
|
-
spinner.text = `Phase
|
|
8074
|
+
phase5Done++;
|
|
8075
|
+
spinner.text = `Phase 5/6 \u2014 ${phase5Done}/${remainingPages.length} pages generated...`;
|
|
7764
8076
|
return { type: "add-page", target: "new", changes: { id, name, route } };
|
|
7765
8077
|
}
|
|
7766
8078
|
},
|
|
@@ -7768,19 +8080,25 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7768
8080
|
);
|
|
7769
8081
|
const allRequests = reusedExistingAnchor ? [...remainingRequests] : homeRequest ? [homeRequest, ...remainingRequests] : [...remainingRequests];
|
|
7770
8082
|
const emptyPages = allRequests.filter((r) => r.type === "add-page" && !r.changes?.pageCode);
|
|
7771
|
-
if (emptyPages.length > 0
|
|
8083
|
+
if (emptyPages.length > 0) {
|
|
7772
8084
|
spinner.text = `Retrying ${emptyPages.length} page(s) without code...`;
|
|
7773
8085
|
for (const req of emptyPages) {
|
|
7774
8086
|
const page = req.changes;
|
|
7775
8087
|
const pageName = page.name || page.id || "page";
|
|
7776
8088
|
const pageRoute = page.route || `/${pageName.toLowerCase()}`;
|
|
7777
8089
|
try {
|
|
7778
|
-
const
|
|
7779
|
-
|
|
7780
|
-
|
|
7781
|
-
|
|
7782
|
-
|
|
8090
|
+
const retryPageType = plan ? getPageType(pageRoute, plan) : inferPageTypeFromRoute(pageRoute);
|
|
8091
|
+
const lightweightPrompt = buildLightweightPagePrompt(
|
|
8092
|
+
pageName,
|
|
8093
|
+
pageRoute,
|
|
8094
|
+
styleContext || "",
|
|
8095
|
+
parseOpts.sharedComponentsSummary,
|
|
8096
|
+
retryPageType
|
|
7783
8097
|
);
|
|
8098
|
+
const retryResult = await parseModification(lightweightPrompt, modCtx, provider, {
|
|
8099
|
+
...parseOpts,
|
|
8100
|
+
lightweight: true
|
|
8101
|
+
});
|
|
7784
8102
|
const codePage = retryResult.requests.find((r) => r.type === "add-page");
|
|
7785
8103
|
if (codePage && codePage.changes?.pageCode) {
|
|
7786
8104
|
const idx = allRequests.indexOf(req);
|
|
@@ -7791,8 +8109,8 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7791
8109
|
}
|
|
7792
8110
|
}
|
|
7793
8111
|
const withCode = allRequests.filter((r) => r.changes?.pageCode).length;
|
|
7794
|
-
spinner.succeed(`Phase
|
|
7795
|
-
return allRequests;
|
|
8112
|
+
spinner.succeed(`Phase 5/6 \u2014 Generated ${allRequests.length} pages (${withCode} with full code)`);
|
|
8113
|
+
return { requests: allRequests, plan };
|
|
7796
8114
|
}
|
|
7797
8115
|
var SharedExtractionItemSchema = z.object({
|
|
7798
8116
|
name: z.string().min(2).max(50),
|
|
@@ -7825,7 +8143,9 @@ async function extractSharedComponents(homePageCode, projectRoot, aiProvider) {
|
|
|
7825
8143
|
} catch {
|
|
7826
8144
|
return { components: [], summary: buildSharedComponentsSummary(manifest) };
|
|
7827
8145
|
}
|
|
7828
|
-
const reservedSet = new Set(
|
|
8146
|
+
const reservedSet = new Set(
|
|
8147
|
+
getComponentProvider().listNames().map((n) => n.toLowerCase())
|
|
8148
|
+
);
|
|
7829
8149
|
const existingSet = new Set(manifest.shared.map((e) => e.name.toLowerCase()));
|
|
7830
8150
|
const seenNames = /* @__PURE__ */ new Set();
|
|
7831
8151
|
const filtered = rawItems.filter((item) => {
|
|
@@ -7904,7 +8224,7 @@ function extractAppNameFromPrompt(prompt) {
|
|
|
7904
8224
|
import { resolve as resolve7 } from "path";
|
|
7905
8225
|
import { mkdir as mkdir4 } from "fs/promises";
|
|
7906
8226
|
import { dirname as dirname6 } from "path";
|
|
7907
|
-
import
|
|
8227
|
+
import chalk12 from "chalk";
|
|
7908
8228
|
import {
|
|
7909
8229
|
getTemplateForPageType,
|
|
7910
8230
|
loadManifest as loadManifest6,
|
|
@@ -7916,7 +8236,7 @@ import {
|
|
|
7916
8236
|
|
|
7917
8237
|
// src/commands/chat/code-generator.ts
|
|
7918
8238
|
import { resolve as resolve6 } from "path";
|
|
7919
|
-
import { existsSync as existsSync14 } from "fs";
|
|
8239
|
+
import { existsSync as existsSync14, readdirSync as readdirSync2, readFileSync as readFileSync9 } from "fs";
|
|
7920
8240
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
7921
8241
|
import { dirname as dirname5 } from "path";
|
|
7922
8242
|
import {
|
|
@@ -7925,7 +8245,7 @@ import {
|
|
|
7925
8245
|
TailwindConfigGenerator
|
|
7926
8246
|
} from "@getcoherent/core";
|
|
7927
8247
|
import { integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout2, generateSharedComponent as generateSharedComponent3 } from "@getcoherent/core";
|
|
7928
|
-
import
|
|
8248
|
+
import chalk10 from "chalk";
|
|
7929
8249
|
|
|
7930
8250
|
// src/utils/file-hashes.ts
|
|
7931
8251
|
import { createHash } from "crypto";
|
|
@@ -8029,7 +8349,7 @@ async function canOverwriteShared(projectRoot, componentFile, storedHashes) {
|
|
|
8029
8349
|
if (!storedHash) return true;
|
|
8030
8350
|
const edited = await isManuallyEdited(filePath, storedHash);
|
|
8031
8351
|
if (edited) {
|
|
8032
|
-
console.log(
|
|
8352
|
+
console.log(chalk10.yellow(` \u26A0 Skipping ${componentFile} \u2014 manually edited since last generation`));
|
|
8033
8353
|
}
|
|
8034
8354
|
return !edited;
|
|
8035
8355
|
}
|
|
@@ -8092,9 +8412,31 @@ async function regenerateLayout(config2, projectRoot, options = { navChanged: fa
|
|
|
8092
8412
|
await ensureAppRouteGroupLayout(projectRoot, config2.navigation?.type, options.navChanged);
|
|
8093
8413
|
} catch (err) {
|
|
8094
8414
|
if (process.env.COHERENT_DEBUG === "1") {
|
|
8095
|
-
console.log(
|
|
8415
|
+
console.log(chalk10.dim("Layout integration warning:", err));
|
|
8416
|
+
}
|
|
8417
|
+
}
|
|
8418
|
+
}
|
|
8419
|
+
async function scanAndInstallSharedDeps(projectRoot) {
|
|
8420
|
+
const sharedDir = resolve6(projectRoot, "components", "shared");
|
|
8421
|
+
if (!existsSync14(sharedDir)) return [];
|
|
8422
|
+
const files = readdirSync2(sharedDir).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts"));
|
|
8423
|
+
const installed = [];
|
|
8424
|
+
const provider = getComponentProvider();
|
|
8425
|
+
for (const file of files) {
|
|
8426
|
+
const code = readFileSync9(resolve6(sharedDir, file), "utf-8");
|
|
8427
|
+
const importMatches = [...code.matchAll(/@\/components\/ui\/([a-z0-9-]+)/g)];
|
|
8428
|
+
for (const [, componentId] of importMatches) {
|
|
8429
|
+
const uiPath = resolve6(projectRoot, "components", "ui", `${componentId}.tsx`);
|
|
8430
|
+
if (!existsSync14(uiPath) && provider.has(componentId)) {
|
|
8431
|
+
try {
|
|
8432
|
+
await provider.installComponent(componentId, projectRoot);
|
|
8433
|
+
installed.push(componentId);
|
|
8434
|
+
} catch {
|
|
8435
|
+
}
|
|
8436
|
+
}
|
|
8096
8437
|
}
|
|
8097
8438
|
}
|
|
8439
|
+
return [...new Set(installed)];
|
|
8098
8440
|
}
|
|
8099
8441
|
async function ensureAppRouteGroupLayout(projectRoot, navType, forceUpdate = false) {
|
|
8100
8442
|
const layoutPath = resolve6(projectRoot, "app", "(app)", "layout.tsx");
|
|
@@ -8138,6 +8480,59 @@ export default function AppLayout({
|
|
|
8138
8480
|
}
|
|
8139
8481
|
`;
|
|
8140
8482
|
}
|
|
8483
|
+
function buildGroupLayoutCode(layout, _pages) {
|
|
8484
|
+
if (layout === "sidebar" || layout === "both") {
|
|
8485
|
+
return `import { Sidebar } from '@/components/shared/sidebar'
|
|
8486
|
+
|
|
8487
|
+
export default function GroupLayout({
|
|
8488
|
+
children,
|
|
8489
|
+
}: {
|
|
8490
|
+
children: React.ReactNode
|
|
8491
|
+
}) {
|
|
8492
|
+
return (
|
|
8493
|
+
<div className="flex min-h-[calc(100vh-3.5rem)]">
|
|
8494
|
+
<Sidebar />
|
|
8495
|
+
<main className="flex-1 px-4 sm:px-6 lg:px-8 py-6">
|
|
8496
|
+
{children}
|
|
8497
|
+
</main>
|
|
8498
|
+
</div>
|
|
8499
|
+
)
|
|
8500
|
+
}
|
|
8501
|
+
`;
|
|
8502
|
+
}
|
|
8503
|
+
if (layout === "none") {
|
|
8504
|
+
return `export default function GroupLayout({
|
|
8505
|
+
children,
|
|
8506
|
+
}: {
|
|
8507
|
+
children: React.ReactNode
|
|
8508
|
+
}) {
|
|
8509
|
+
return <>{children}</>
|
|
8510
|
+
}
|
|
8511
|
+
`;
|
|
8512
|
+
}
|
|
8513
|
+
return `export default function GroupLayout({
|
|
8514
|
+
children,
|
|
8515
|
+
}: {
|
|
8516
|
+
children: React.ReactNode
|
|
8517
|
+
}) {
|
|
8518
|
+
return (
|
|
8519
|
+
<main className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
|
|
8520
|
+
{children}
|
|
8521
|
+
</main>
|
|
8522
|
+
)
|
|
8523
|
+
}
|
|
8524
|
+
`;
|
|
8525
|
+
}
|
|
8526
|
+
async function ensurePlanGroupLayouts(projectRoot, plan) {
|
|
8527
|
+
const { mkdir: mkdirAsync } = await import("fs/promises");
|
|
8528
|
+
for (const group of plan.groups) {
|
|
8529
|
+
const groupDir = resolve6(projectRoot, "app", `(${group.id})`);
|
|
8530
|
+
await mkdirAsync(groupDir, { recursive: true });
|
|
8531
|
+
const layoutPath = resolve6(groupDir, "layout.tsx");
|
|
8532
|
+
const code = buildGroupLayoutCode(group.layout, group.pages);
|
|
8533
|
+
await writeFile(layoutPath, code);
|
|
8534
|
+
}
|
|
8535
|
+
}
|
|
8141
8536
|
async function regenerateFiles(modified, config2, projectRoot, options = { navChanged: false }) {
|
|
8142
8537
|
const componentIds = /* @__PURE__ */ new Set();
|
|
8143
8538
|
const pageIds = /* @__PURE__ */ new Set();
|
|
@@ -8153,6 +8548,10 @@ async function regenerateFiles(modified, config2, projectRoot, options = { navCh
|
|
|
8153
8548
|
navChanged: options.navChanged,
|
|
8154
8549
|
storedHashes: options.storedHashes
|
|
8155
8550
|
});
|
|
8551
|
+
const sharedInstalled = await scanAndInstallSharedDeps(projectRoot);
|
|
8552
|
+
if (sharedInstalled.length > 0 && process.env.COHERENT_DEBUG === "1") {
|
|
8553
|
+
console.log(chalk10.dim(` Auto-installed shared deps: ${sharedInstalled.join(", ")}`));
|
|
8554
|
+
}
|
|
8156
8555
|
}
|
|
8157
8556
|
if (componentIds.size > 0) {
|
|
8158
8557
|
const twGen = new TailwindConfigGenerator(config2);
|
|
@@ -8208,7 +8607,7 @@ function extractBalancedTag(source, tagName) {
|
|
|
8208
8607
|
}
|
|
8209
8608
|
|
|
8210
8609
|
// src/commands/chat/reporting.ts
|
|
8211
|
-
import
|
|
8610
|
+
import chalk11 from "chalk";
|
|
8212
8611
|
function extractImportsFrom(code, fromPath) {
|
|
8213
8612
|
const escaped = fromPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8214
8613
|
const regex = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*['"\`]${escaped}[^'"\`]*['"\`]`, "g");
|
|
@@ -8230,27 +8629,27 @@ function printPostGenerationReport(opts) {
|
|
|
8230
8629
|
const iconCount = extractImportsFrom(code, "lucide-react").length;
|
|
8231
8630
|
const hasInstalled = postFixes.some((f) => f.startsWith("Installed:"));
|
|
8232
8631
|
const syntaxStatus = postFixes.length > 0 ? postFixes.some((f) => f.includes("metadata")) ? "fixed (escaped metadata quotes) \u2714" : "fixed \u2714" : "valid \u2714";
|
|
8233
|
-
console.log(
|
|
8632
|
+
console.log(chalk11.green(`
|
|
8234
8633
|
\u2705 Page "${pageTitle}" ${action} at ${filePath}
|
|
8235
8634
|
`));
|
|
8236
8635
|
if (uiComponents.length > 0) {
|
|
8237
|
-
console.log(
|
|
8636
|
+
console.log(chalk11.dim(` Components: ${uiComponents.join(", ")} (from @/components/ui)`));
|
|
8238
8637
|
}
|
|
8239
8638
|
if (inCodeShared.length > 0) {
|
|
8240
|
-
console.log(
|
|
8639
|
+
console.log(chalk11.dim(` Shared: ${inCodeShared.map((s) => `${s.id} (${s.name})`).join(", ")}`));
|
|
8241
8640
|
}
|
|
8242
8641
|
if (layoutShared.length > 0) {
|
|
8243
|
-
console.log(
|
|
8642
|
+
console.log(chalk11.dim(` Layout: ${layoutShared.map((l) => `${l.id} (${l.name})`).join(", ")} via layout.tsx`));
|
|
8244
8643
|
}
|
|
8245
8644
|
if (iconCount > 0) {
|
|
8246
|
-
console.log(
|
|
8645
|
+
console.log(chalk11.dim(` Icons: ${iconCount} from lucide-react`));
|
|
8247
8646
|
}
|
|
8248
8647
|
if (hasInstalled) {
|
|
8249
|
-
console.log(
|
|
8648
|
+
console.log(chalk11.dim(" Dependencies: installed \u2714"));
|
|
8250
8649
|
}
|
|
8251
|
-
console.log(
|
|
8650
|
+
console.log(chalk11.dim(` Syntax: ${syntaxStatus}`));
|
|
8252
8651
|
if (route) {
|
|
8253
|
-
console.log(
|
|
8652
|
+
console.log(chalk11.cyan(`
|
|
8254
8653
|
Preview: http://localhost:3000${route}`));
|
|
8255
8654
|
}
|
|
8256
8655
|
console.log("");
|
|
@@ -8258,35 +8657,35 @@ function printPostGenerationReport(opts) {
|
|
|
8258
8657
|
function printSharedComponentReport(opts) {
|
|
8259
8658
|
const { id, name, file, instruction, postFixes = [] } = opts;
|
|
8260
8659
|
const syntaxStatus = postFixes.length > 0 ? "fixed \u2714" : "valid \u2714";
|
|
8261
|
-
console.log(
|
|
8660
|
+
console.log(chalk11.green(`
|
|
8262
8661
|
\u2705 Updated ${id} (${name}) at ${file}
|
|
8263
8662
|
`));
|
|
8264
8663
|
if (instruction) {
|
|
8265
8664
|
const snippet = instruction.length > 60 ? instruction.slice(0, 57) + "..." : instruction;
|
|
8266
|
-
console.log(
|
|
8665
|
+
console.log(chalk11.dim(` Changed: ${snippet}`));
|
|
8267
8666
|
}
|
|
8268
|
-
console.log(
|
|
8269
|
-
console.log(
|
|
8667
|
+
console.log(chalk11.dim(" Affects: all pages via layout.tsx"));
|
|
8668
|
+
console.log(chalk11.dim(` Syntax: ${syntaxStatus}`));
|
|
8270
8669
|
console.log("");
|
|
8271
8670
|
}
|
|
8272
8671
|
function printLinkSharedReport(opts) {
|
|
8273
8672
|
const { sharedId, sharedName, pageTarget, route, postFixes = [] } = opts;
|
|
8274
8673
|
const syntaxStatus = postFixes.length > 0 ? "fixed \u2714" : "valid \u2714";
|
|
8275
|
-
console.log(
|
|
8674
|
+
console.log(chalk11.green(`
|
|
8276
8675
|
\u2705 Linked ${sharedId} (${sharedName}) to page "${pageTarget}"
|
|
8277
8676
|
`));
|
|
8278
|
-
console.log(
|
|
8279
|
-
console.log(
|
|
8677
|
+
console.log(chalk11.dim(` Syntax: ${syntaxStatus}`));
|
|
8678
|
+
console.log(chalk11.cyan(` Preview: http://localhost:3000${route}`));
|
|
8280
8679
|
console.log("");
|
|
8281
8680
|
}
|
|
8282
8681
|
function printPromoteAndLinkReport(opts) {
|
|
8283
8682
|
const { id, name, file, usedInFiles, postFixes = [] } = opts;
|
|
8284
8683
|
const syntaxStatus = postFixes.length > 0 ? "fixed \u2714" : "valid \u2714";
|
|
8285
|
-
console.log(
|
|
8684
|
+
console.log(chalk11.green(`
|
|
8286
8685
|
\u2705 Created ${id} (${name}) at ${file}
|
|
8287
8686
|
`));
|
|
8288
|
-
console.log(
|
|
8289
|
-
console.log(
|
|
8687
|
+
console.log(chalk11.dim(` Linked to: ${usedInFiles.length} page(s)`));
|
|
8688
|
+
console.log(chalk11.dim(` Syntax: ${syntaxStatus}`));
|
|
8290
8689
|
console.log("");
|
|
8291
8690
|
}
|
|
8292
8691
|
function showPreview(requests, results, config2, preflightInstalledNames) {
|
|
@@ -8304,23 +8703,23 @@ function showPreview(requests, results, config2, preflightInstalledNames) {
|
|
|
8304
8703
|
const modifiedSharedComponents = successfulPairs.filter(({ request }) => request.type === "modify-layout-block");
|
|
8305
8704
|
const modifiedPages = successfulPairs.filter(({ request }) => request.type === "update-page");
|
|
8306
8705
|
const tokenChanges = successfulPairs.filter(({ request }) => request.type === "update-token");
|
|
8307
|
-
console.log(
|
|
8706
|
+
console.log(chalk11.bold.cyan("\n\u{1F4CB} Changes Applied:\n"));
|
|
8308
8707
|
if (preflightInstalledNames && preflightInstalledNames.length > 0) {
|
|
8309
|
-
console.log(
|
|
8708
|
+
console.log(chalk11.cyan("\u{1F50D} Pre-flight check: Installed missing components:"));
|
|
8310
8709
|
preflightInstalledNames.forEach((name) => {
|
|
8311
|
-
console.log(
|
|
8710
|
+
console.log(chalk11.green(` \u2728 Auto-installed ${name}`));
|
|
8312
8711
|
});
|
|
8313
8712
|
console.log("");
|
|
8314
8713
|
}
|
|
8315
8714
|
if (addedComponents.length > 0) {
|
|
8316
8715
|
const names = addedComponents.map(({ request }) => request.changes.name).filter(Boolean);
|
|
8317
|
-
console.log(
|
|
8318
|
-
console.log(
|
|
8716
|
+
console.log(chalk11.green("\u{1F4E6} Components:"));
|
|
8717
|
+
console.log(chalk11.white(` \u2728 Auto-installed: ${names.join(", ")}`));
|
|
8319
8718
|
}
|
|
8320
8719
|
if (customComponents.length > 0) {
|
|
8321
8720
|
const names = customComponents.map(({ request }) => request.changes.name).filter(Boolean);
|
|
8322
|
-
if (addedComponents.length === 0) console.log(
|
|
8323
|
-
console.log(
|
|
8721
|
+
if (addedComponents.length === 0) console.log(chalk11.green("\u{1F4E6} Components:"));
|
|
8722
|
+
console.log(chalk11.white(` \u2728 Created: ${names.join(", ")}`));
|
|
8324
8723
|
}
|
|
8325
8724
|
const usedComponentIds = /* @__PURE__ */ new Set();
|
|
8326
8725
|
addedPages.forEach(({ request }) => {
|
|
@@ -8335,71 +8734,71 @@ function showPreview(requests, results, config2, preflightInstalledNames) {
|
|
|
8335
8734
|
]);
|
|
8336
8735
|
const reusedIds = [...usedComponentIds].filter((id) => !newComponentIds.has(id));
|
|
8337
8736
|
if (reusedIds.length > 0) {
|
|
8338
|
-
if (addedComponents.length === 0 && customComponents.length === 0) console.log(
|
|
8339
|
-
console.log(
|
|
8737
|
+
if (addedComponents.length === 0 && customComponents.length === 0) console.log(chalk11.green("\u{1F4E6} Components:"));
|
|
8738
|
+
console.log(chalk11.white(` \u{1F504} Reused: ${reusedIds.join(", ")}`));
|
|
8340
8739
|
}
|
|
8341
8740
|
if (addedComponents.length > 0 || customComponents.length > 0 || reusedIds.length > 0) {
|
|
8342
8741
|
console.log("");
|
|
8343
8742
|
}
|
|
8344
8743
|
if (addedPages.length > 0) {
|
|
8345
|
-
console.log(
|
|
8744
|
+
console.log(chalk11.green("\u{1F4C4} Pages Created:"));
|
|
8346
8745
|
addedPages.forEach(({ request }) => {
|
|
8347
8746
|
const page = request.changes;
|
|
8348
8747
|
const route = page.route || "/";
|
|
8349
|
-
console.log(
|
|
8350
|
-
console.log(
|
|
8351
|
-
console.log(
|
|
8748
|
+
console.log(chalk11.white(` \u2728 ${page.name || "Page"}`));
|
|
8749
|
+
console.log(chalk11.gray(` Route: ${route}`));
|
|
8750
|
+
console.log(chalk11.gray(` Sections: ${page.sections?.length ?? 0}`));
|
|
8352
8751
|
});
|
|
8353
8752
|
console.log("");
|
|
8354
8753
|
}
|
|
8355
8754
|
if (modifiedComponents.length > 0 || modifiedSharedComponents.length > 0 || modifiedPages.length > 0 || tokenChanges.length > 0) {
|
|
8356
|
-
console.log(
|
|
8755
|
+
console.log(chalk11.yellow("\u{1F527} Modified:"));
|
|
8357
8756
|
modifiedComponents.forEach(({ result }) => {
|
|
8358
|
-
console.log(
|
|
8757
|
+
console.log(chalk11.white(` \u2022 ${result.message}`));
|
|
8359
8758
|
});
|
|
8360
8759
|
modifiedSharedComponents.forEach(({ result }) => {
|
|
8361
|
-
console.log(
|
|
8760
|
+
console.log(chalk11.white(` \u2022 ${result.message}`));
|
|
8362
8761
|
});
|
|
8363
8762
|
modifiedPages.forEach(({ result }) => {
|
|
8364
|
-
console.log(
|
|
8763
|
+
console.log(chalk11.white(` \u2022 ${result.message}`));
|
|
8365
8764
|
});
|
|
8366
8765
|
tokenChanges.forEach(({ result }) => {
|
|
8367
|
-
console.log(
|
|
8766
|
+
console.log(chalk11.white(` \u2022 ${result.message}`));
|
|
8368
8767
|
});
|
|
8369
8768
|
console.log("");
|
|
8370
8769
|
}
|
|
8371
8770
|
if (failedPairs.length > 0) {
|
|
8372
|
-
console.log(
|
|
8771
|
+
console.log(chalk11.red("\u274C Failed modifications:"));
|
|
8373
8772
|
failedPairs.forEach(({ result }) => {
|
|
8374
|
-
console.log(
|
|
8773
|
+
console.log(chalk11.gray(` \u2716 ${result.message}`));
|
|
8375
8774
|
});
|
|
8376
8775
|
console.log("");
|
|
8377
8776
|
}
|
|
8378
8777
|
const successCount = successfulPairs.length;
|
|
8379
8778
|
const totalCount = results.length;
|
|
8380
8779
|
if (successCount === totalCount) {
|
|
8381
|
-
console.log(
|
|
8780
|
+
console.log(chalk11.green.bold(`\u2705 Success! ${successCount} modification(s) applied
|
|
8382
8781
|
`));
|
|
8383
8782
|
} else {
|
|
8384
|
-
console.log(
|
|
8783
|
+
console.log(chalk11.yellow.bold(`\u26A0\uFE0F Partial success: ${successCount}/${totalCount} modification(s) applied
|
|
8385
8784
|
`));
|
|
8386
8785
|
}
|
|
8387
8786
|
if (addedPages.length > 0) {
|
|
8388
8787
|
const firstPage = addedPages[0].request.changes;
|
|
8389
8788
|
const route = firstPage?.route || "/";
|
|
8390
|
-
console.log(
|
|
8391
|
-
console.log(
|
|
8392
|
-
console.log(
|
|
8393
|
-
console.log(
|
|
8789
|
+
console.log(chalk11.cyan("\u{1F680} What's next:\n"));
|
|
8790
|
+
console.log(chalk11.white(" \u{1F4FA} View in browser:"));
|
|
8791
|
+
console.log(chalk11.cyan(" coherent preview"));
|
|
8792
|
+
console.log(chalk11.gray(` \u2192 Opens http://localhost:3000${route}
|
|
8394
8793
|
`));
|
|
8395
|
-
console.log(
|
|
8396
|
-
console.log(
|
|
8397
|
-
console.log(
|
|
8794
|
+
console.log(chalk11.white(" \u{1F3A8} Customize:"));
|
|
8795
|
+
console.log(chalk11.cyan(' coherent chat "make buttons rounded"'));
|
|
8796
|
+
console.log(chalk11.cyan(` coherent chat "add hero section to ${firstPage?.name ?? "page"}"`));
|
|
8398
8797
|
console.log("");
|
|
8399
8798
|
} else if (successCount > 0) {
|
|
8400
|
-
console.log(
|
|
8401
|
-
console.log(
|
|
8402
|
-
console.log(
|
|
8799
|
+
console.log(chalk11.cyan("\u{1F680} What's next:\n"));
|
|
8800
|
+
console.log(chalk11.white(" \u{1F4FA} Preview changes:"));
|
|
8801
|
+
console.log(chalk11.cyan(" coherent preview\n"));
|
|
8403
8802
|
}
|
|
8404
8803
|
}
|
|
8405
8804
|
function getChangeDescription(request, config2) {
|
|
@@ -8548,8 +8947,8 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8548
8947
|
const newCode = await ai.editSharedComponentCode(currentCode, instruction, resolved.name);
|
|
8549
8948
|
const { fixedCode, fixes } = await validateAndFixGeneratedCode(projectRoot, newCode, { isPage: false });
|
|
8550
8949
|
if (fixes.length > 0) {
|
|
8551
|
-
console.log(
|
|
8552
|
-
fixes.forEach((f) => console.log(
|
|
8950
|
+
console.log(chalk12.dim(" \u{1F527} Post-generation fixes:"));
|
|
8951
|
+
fixes.forEach((f) => console.log(chalk12.dim(` ${f}`)));
|
|
8553
8952
|
}
|
|
8554
8953
|
await writeFile(fullPath, fixedCode);
|
|
8555
8954
|
printSharedComponentReport({
|
|
@@ -8622,8 +9021,8 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8622
9021
|
const newPageCode = await ai.replaceInlineWithShared(pageCode, sharedCode, resolved.name, changes?.blockHint);
|
|
8623
9022
|
const { fixedCode, fixes } = await validateAndFixGeneratedCode(projectRoot, newPageCode, { isPage: true });
|
|
8624
9023
|
if (fixes.length > 0) {
|
|
8625
|
-
console.log(
|
|
8626
|
-
fixes.forEach((f) => console.log(
|
|
9024
|
+
console.log(chalk12.dim(" \u{1F527} Post-generation fixes:"));
|
|
9025
|
+
fixes.forEach((f) => console.log(chalk12.dim(` ${f}`)));
|
|
8627
9026
|
}
|
|
8628
9027
|
await writeFile(pageFilePath, fixedCode);
|
|
8629
9028
|
const manifest = await loadManifest6(projectRoot);
|
|
@@ -8725,8 +9124,8 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8725
9124
|
const newCode = await ai.replaceInlineWithShared(linkPageCode, sharedCode, created.name, blockHint);
|
|
8726
9125
|
const { fixedCode, fixes } = await validateAndFixGeneratedCode(projectRoot, newCode, { isPage: true });
|
|
8727
9126
|
if (fixes.length > 0) {
|
|
8728
|
-
console.log(
|
|
8729
|
-
fixes.forEach((f) => console.log(
|
|
9127
|
+
console.log(chalk12.dim(" \u{1F527} Post-generation fixes:"));
|
|
9128
|
+
fixes.forEach((f) => console.log(chalk12.dim(` ${f}`)));
|
|
8730
9129
|
}
|
|
8731
9130
|
await writeFile(fullPath, fixedCode);
|
|
8732
9131
|
usedInFiles.push(relPath);
|
|
@@ -8817,7 +9216,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8817
9216
|
const aiPageCode = typeof page.pageCode === "string" && page.pageCode.trim() !== "" ? page.pageCode : void 0;
|
|
8818
9217
|
if (aiPageCode) {
|
|
8819
9218
|
finalPageCode = aiPageCode;
|
|
8820
|
-
if (DEBUG2) console.log(
|
|
9219
|
+
if (DEBUG2) console.log(chalk12.dim(` [pageCode] Using AI-generated pageCode (user content priority)`));
|
|
8821
9220
|
} else {
|
|
8822
9221
|
const inferredType = page.pageType || inferPageType(page.route || "", page.name || "");
|
|
8823
9222
|
if (inferredType) {
|
|
@@ -8832,19 +9231,19 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8832
9231
|
const content = page.structuredContent || getDefaultContent(inferredType, page.name || pageName);
|
|
8833
9232
|
finalPageCode = templateFn(content, opts);
|
|
8834
9233
|
if (DEBUG2)
|
|
8835
|
-
console.log(
|
|
9234
|
+
console.log(chalk12.dim(` [template] Used "${inferredType}" template (inferred from route/name)`));
|
|
8836
9235
|
} catch {
|
|
8837
|
-
if (DEBUG2) console.log(
|
|
9236
|
+
if (DEBUG2) console.log(chalk12.dim(` [template] Failed for "${inferredType}"`));
|
|
8838
9237
|
}
|
|
8839
9238
|
}
|
|
8840
9239
|
}
|
|
8841
9240
|
}
|
|
8842
9241
|
if (!finalPageCode) {
|
|
8843
|
-
console.log(
|
|
9242
|
+
console.log(chalk12.yellow(`
|
|
8844
9243
|
\u26A0\uFE0F Page "${page.name || page.id}" has no generated code \u2014 it will appear empty.`));
|
|
8845
|
-
console.log(
|
|
9244
|
+
console.log(chalk12.dim(" This usually means the AI did not produce pageCode for this page."));
|
|
8846
9245
|
console.log(
|
|
8847
|
-
|
|
9246
|
+
chalk12.dim(
|
|
8848
9247
|
' Try running: coherent chat "regenerate the ' + (page.name || page.id) + ' page with full content"'
|
|
8849
9248
|
)
|
|
8850
9249
|
);
|
|
@@ -8898,7 +9297,9 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8898
9297
|
}
|
|
8899
9298
|
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
8900
9299
|
codeToWrite = layoutStripped;
|
|
8901
|
-
|
|
9300
|
+
const currentPlan = projectRoot ? loadPlan(projectRoot) : null;
|
|
9301
|
+
const pageType = currentPlan ? getPageType(route, currentPlan) : isMarketingRoute(route) ? "marketing" : isAuth ? "auth" : "app";
|
|
9302
|
+
if (pageType === "app") {
|
|
8902
9303
|
const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
|
|
8903
9304
|
if (wrapperFixed) {
|
|
8904
9305
|
codeToWrite = normalized;
|
|
@@ -8908,8 +9309,8 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8908
9309
|
const allFixes = [...postFixes, ...autoFixes];
|
|
8909
9310
|
if (stripped.length > 0) allFixes.push(`stripped inline ${stripped.join(", ")} (layout owns these)`);
|
|
8910
9311
|
if (allFixes.length > 0) {
|
|
8911
|
-
console.log(
|
|
8912
|
-
allFixes.forEach((f) => console.log(
|
|
9312
|
+
console.log(chalk12.dim(" \u{1F527} Post-generation fixes:"));
|
|
9313
|
+
allFixes.forEach((f) => console.log(chalk12.dim(` ${f}`)));
|
|
8913
9314
|
}
|
|
8914
9315
|
await writeFile(filePath, codeToWrite);
|
|
8915
9316
|
const pageIdx = dsm.getConfig().pages.findIndex((p) => p.id === page.id);
|
|
@@ -8921,7 +9322,15 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8921
9322
|
pm.updateConfig(cfg);
|
|
8922
9323
|
}
|
|
8923
9324
|
const manifestForAudit = await loadManifest6(projectRoot);
|
|
8924
|
-
|
|
9325
|
+
const planForAudit = loadPlan(projectRoot);
|
|
9326
|
+
await warnInlineDuplicates(
|
|
9327
|
+
projectRoot,
|
|
9328
|
+
page.name || page.id || route.slice(1),
|
|
9329
|
+
route,
|
|
9330
|
+
codeToWrite,
|
|
9331
|
+
manifestForAudit,
|
|
9332
|
+
planForAudit ?? void 0
|
|
9333
|
+
);
|
|
8925
9334
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
8926
9335
|
printPostGenerationReport({
|
|
8927
9336
|
action: "created",
|
|
@@ -8938,7 +9347,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8938
9347
|
const errors = issues.filter((i) => i.severity === "error");
|
|
8939
9348
|
if (errors.length >= 2 && aiProvider) {
|
|
8940
9349
|
console.log(
|
|
8941
|
-
|
|
9350
|
+
chalk12.yellow(`
|
|
8942
9351
|
\u{1F504} ${errors.length} quality errors \u2014 attempting AI fix for ${page.name || page.id}...`)
|
|
8943
9352
|
);
|
|
8944
9353
|
try {
|
|
@@ -8967,7 +9376,7 @@ Rules:
|
|
|
8967
9376
|
await writeFile(filePath, codeToWrite);
|
|
8968
9377
|
issues = validatePageQuality(codeToWrite);
|
|
8969
9378
|
const finalErrors = issues.filter((i) => i.severity === "error").length;
|
|
8970
|
-
console.log(
|
|
9379
|
+
console.log(chalk12.green(` \u2714 Quality fix: ${errors.length} \u2192 ${finalErrors} errors`));
|
|
8971
9380
|
}
|
|
8972
9381
|
}
|
|
8973
9382
|
}
|
|
@@ -8976,15 +9385,15 @@ Rules:
|
|
|
8976
9385
|
}
|
|
8977
9386
|
const report = formatIssues(issues);
|
|
8978
9387
|
if (report) {
|
|
8979
|
-
console.log(
|
|
9388
|
+
console.log(chalk12.yellow(`
|
|
8980
9389
|
\u{1F50D} Quality check for ${page.name || page.id}:`));
|
|
8981
|
-
console.log(
|
|
9390
|
+
console.log(chalk12.dim(report));
|
|
8982
9391
|
}
|
|
8983
9392
|
const consistency = checkDesignConsistency(codeToWrite);
|
|
8984
9393
|
if (consistency.length > 0) {
|
|
8985
|
-
console.log(
|
|
9394
|
+
console.log(chalk12.yellow(`
|
|
8986
9395
|
\u{1F3A8} Design consistency for ${page.name || page.id}:`));
|
|
8987
|
-
consistency.forEach((w) => console.log(
|
|
9396
|
+
consistency.forEach((w) => console.log(chalk12.dim(` \u26A0 [${w.type}] ${w.message}`)));
|
|
8988
9397
|
}
|
|
8989
9398
|
}
|
|
8990
9399
|
}
|
|
@@ -8999,9 +9408,9 @@ Rules:
|
|
|
8999
9408
|
const changes = request.changes;
|
|
9000
9409
|
const instruction = originalMessage || (typeof changes?.instruction === "string" ? changes.instruction : void 0);
|
|
9001
9410
|
let resolvedPageCode = typeof changes?.pageCode === "string" && changes.pageCode.trim() !== "" ? changes.pageCode : void 0;
|
|
9002
|
-
if (DEBUG2 && instruction) console.log(
|
|
9411
|
+
if (DEBUG2 && instruction) console.log(chalk12.dim(` [update-page] instruction: ${instruction.slice(0, 120)}...`));
|
|
9003
9412
|
if (DEBUG2 && resolvedPageCode)
|
|
9004
|
-
console.log(
|
|
9413
|
+
console.log(chalk12.dim(` [update-page] has pageCode (${resolvedPageCode.length} chars)`));
|
|
9005
9414
|
const configChanges = { ...changes };
|
|
9006
9415
|
delete configChanges.pageCode;
|
|
9007
9416
|
delete configChanges.pageType;
|
|
@@ -9025,12 +9434,12 @@ Rules:
|
|
|
9025
9434
|
try {
|
|
9026
9435
|
currentCode = await readFile(absPath);
|
|
9027
9436
|
} catch {
|
|
9028
|
-
if (DEBUG2) console.log(
|
|
9437
|
+
if (DEBUG2) console.log(chalk12.dim(` [update-page] Could not read current file at ${absPath}`));
|
|
9029
9438
|
}
|
|
9030
9439
|
if (currentCode) {
|
|
9031
9440
|
const ai = await createAIProvider(aiProvider ?? "auto");
|
|
9032
9441
|
if (ai.editPageCode) {
|
|
9033
|
-
console.log(
|
|
9442
|
+
console.log(chalk12.dim(" \u270F\uFE0F Applying changes to existing page..."));
|
|
9034
9443
|
const coreRules = CORE_CONSTRAINTS;
|
|
9035
9444
|
const qualityRules = DESIGN_QUALITY;
|
|
9036
9445
|
const contextualRules = selectContextualRules(instruction);
|
|
@@ -9050,15 +9459,15 @@ ${contextualRules}
|
|
|
9050
9459
|
${routeRules}
|
|
9051
9460
|
${pagesCtx}`
|
|
9052
9461
|
);
|
|
9053
|
-
if (DEBUG2) console.log(
|
|
9462
|
+
if (DEBUG2) console.log(chalk12.dim(` [update-page] AI returned ${resolvedPageCode.length} chars`));
|
|
9054
9463
|
const editIssues = verifyIncrementalEdit(currentCode, resolvedPageCode);
|
|
9055
9464
|
if (editIssues.length > 0) {
|
|
9056
|
-
console.log(
|
|
9465
|
+
console.log(chalk12.yellow(`
|
|
9057
9466
|
\u26A0 Incremental edit issues for ${pageDef.name || pageDef.id}:`));
|
|
9058
|
-
editIssues.forEach((issue) => console.log(
|
|
9467
|
+
editIssues.forEach((issue) => console.log(chalk12.dim(` [${issue.type}] ${issue.message}`)));
|
|
9059
9468
|
}
|
|
9060
9469
|
} else {
|
|
9061
|
-
console.log(
|
|
9470
|
+
console.log(chalk12.yellow(" \u26A0 AI provider does not support editPageCode"));
|
|
9062
9471
|
}
|
|
9063
9472
|
}
|
|
9064
9473
|
}
|
|
@@ -9098,7 +9507,9 @@ ${pagesCtx}`
|
|
|
9098
9507
|
}
|
|
9099
9508
|
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
9100
9509
|
codeToWrite = layoutStripped;
|
|
9101
|
-
|
|
9510
|
+
const currentPlan2 = projectRoot ? loadPlan(projectRoot) : null;
|
|
9511
|
+
const pageType2 = currentPlan2 ? getPageType(route, currentPlan2) : isMarketingRoute(route) ? "marketing" : isAuth ? "auth" : "app";
|
|
9512
|
+
if (pageType2 === "app") {
|
|
9102
9513
|
const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
|
|
9103
9514
|
if (wrapperFixed) {
|
|
9104
9515
|
codeToWrite = normalized;
|
|
@@ -9108,8 +9519,8 @@ ${pagesCtx}`
|
|
|
9108
9519
|
const allFixes = [...postFixes, ...autoFixes];
|
|
9109
9520
|
if (stripped.length > 0) allFixes.push(`stripped inline ${stripped.join(", ")} (layout owns these)`);
|
|
9110
9521
|
if (allFixes.length > 0) {
|
|
9111
|
-
console.log(
|
|
9112
|
-
allFixes.forEach((f) => console.log(
|
|
9522
|
+
console.log(chalk12.dim(" \u{1F527} Post-generation fixes:"));
|
|
9523
|
+
allFixes.forEach((f) => console.log(chalk12.dim(` ${f}`)));
|
|
9113
9524
|
}
|
|
9114
9525
|
await writeFile(absPath, codeToWrite);
|
|
9115
9526
|
const updatePageIdx = dsm.getConfig().pages.findIndex((p) => p.id === pageDef.id);
|
|
@@ -9121,11 +9532,14 @@ ${pagesCtx}`
|
|
|
9121
9532
|
pm.updateConfig(cfg);
|
|
9122
9533
|
}
|
|
9123
9534
|
const manifestForAudit = await loadManifest6(projectRoot);
|
|
9535
|
+
const planForAudit2 = loadPlan(projectRoot);
|
|
9124
9536
|
await warnInlineDuplicates(
|
|
9125
9537
|
projectRoot,
|
|
9126
9538
|
pageDef.name || pageDef.id || route.slice(1),
|
|
9539
|
+
route,
|
|
9127
9540
|
codeToWrite,
|
|
9128
|
-
manifestForAudit
|
|
9541
|
+
manifestForAudit,
|
|
9542
|
+
planForAudit2 ?? void 0
|
|
9129
9543
|
);
|
|
9130
9544
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
9131
9545
|
printPostGenerationReport({
|
|
@@ -9142,15 +9556,15 @@ ${pagesCtx}`
|
|
|
9142
9556
|
const issues = validatePageQuality(codeToWrite);
|
|
9143
9557
|
const report = formatIssues(issues);
|
|
9144
9558
|
if (report) {
|
|
9145
|
-
console.log(
|
|
9559
|
+
console.log(chalk12.yellow(`
|
|
9146
9560
|
\u{1F50D} Quality check for ${pageDef.name || pageDef.id}:`));
|
|
9147
|
-
console.log(
|
|
9561
|
+
console.log(chalk12.dim(report));
|
|
9148
9562
|
}
|
|
9149
9563
|
const consistency = checkDesignConsistency(codeToWrite);
|
|
9150
9564
|
if (consistency.length > 0) {
|
|
9151
|
-
console.log(
|
|
9565
|
+
console.log(chalk12.yellow(`
|
|
9152
9566
|
\u{1F3A8} Design consistency for ${pageDef.name || pageDef.id}:`));
|
|
9153
|
-
consistency.forEach((w) => console.log(
|
|
9567
|
+
consistency.forEach((w) => console.log(chalk12.dim(` \u26A0 [${w.type}] ${w.message}`)));
|
|
9154
9568
|
}
|
|
9155
9569
|
} else {
|
|
9156
9570
|
try {
|
|
@@ -9159,8 +9573,8 @@ ${pagesCtx}`
|
|
|
9159
9573
|
if (fixes.length > 0) {
|
|
9160
9574
|
code = fixed;
|
|
9161
9575
|
await writeFile(absPath, code);
|
|
9162
|
-
console.log(
|
|
9163
|
-
fixes.forEach((f) => console.log(
|
|
9576
|
+
console.log(chalk12.dim(" \u{1F527} Auto-fixes applied:"));
|
|
9577
|
+
fixes.forEach((f) => console.log(chalk12.dim(` ${f}`)));
|
|
9164
9578
|
}
|
|
9165
9579
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
9166
9580
|
const manifest = await loadManifest6(projectRoot);
|
|
@@ -9177,9 +9591,9 @@ ${pagesCtx}`
|
|
|
9177
9591
|
const issues = validatePageQuality(code);
|
|
9178
9592
|
const report = formatIssues(issues);
|
|
9179
9593
|
if (report) {
|
|
9180
|
-
console.log(
|
|
9594
|
+
console.log(chalk12.yellow(`
|
|
9181
9595
|
\u{1F50D} Quality check for ${pageDef.name || pageDef.id}:`));
|
|
9182
|
-
console.log(
|
|
9596
|
+
console.log(chalk12.dim(report));
|
|
9183
9597
|
}
|
|
9184
9598
|
} catch {
|
|
9185
9599
|
}
|
|
@@ -9219,6 +9633,11 @@ function inferPageType(route, name) {
|
|
|
9219
9633
|
if (/\bchangelog\b/.test(key)) return "changelog";
|
|
9220
9634
|
if (/\babout\b/.test(key)) return "about";
|
|
9221
9635
|
if (/\bsettings?\b/.test(key)) return "settings";
|
|
9636
|
+
if (/\bteam\b|\bmember\b/.test(key)) return "team";
|
|
9637
|
+
if (/\btasks?\b/.test(key) && /\[id\]|\bdetail\b/.test(key)) return "task-detail";
|
|
9638
|
+
if (/\btasks?\b/.test(key)) return "tasks";
|
|
9639
|
+
if (/\breset.?password\b/.test(key)) return "reset-password";
|
|
9640
|
+
if (/\bprofile\b|\baccount\b/.test(key)) return "profile";
|
|
9222
9641
|
return null;
|
|
9223
9642
|
}
|
|
9224
9643
|
function getDefaultContent(pageType, pageName) {
|
|
@@ -9262,6 +9681,26 @@ function getDefaultContent(pageType, pageName) {
|
|
|
9262
9681
|
settings: {
|
|
9263
9682
|
title: "Settings",
|
|
9264
9683
|
description: "Manage your account and preferences"
|
|
9684
|
+
},
|
|
9685
|
+
team: {
|
|
9686
|
+
title: "Our Team",
|
|
9687
|
+
description: "Meet the people behind the product"
|
|
9688
|
+
},
|
|
9689
|
+
tasks: {
|
|
9690
|
+
title: "Tasks",
|
|
9691
|
+
description: "Manage and track your tasks"
|
|
9692
|
+
},
|
|
9693
|
+
"task-detail": {
|
|
9694
|
+
title: "Task Detail",
|
|
9695
|
+
description: "View task details and activity"
|
|
9696
|
+
},
|
|
9697
|
+
"reset-password": {
|
|
9698
|
+
title: "Reset Password",
|
|
9699
|
+
description: "Enter your new password"
|
|
9700
|
+
},
|
|
9701
|
+
profile: {
|
|
9702
|
+
title: "Profile",
|
|
9703
|
+
description: "View and edit your profile"
|
|
9265
9704
|
}
|
|
9266
9705
|
};
|
|
9267
9706
|
return defaults[pageType] || { title: pageName, description: "" };
|
|
@@ -9277,9 +9716,9 @@ function hasNavChanged(before, after) {
|
|
|
9277
9716
|
}
|
|
9278
9717
|
|
|
9279
9718
|
// src/commands/chat/interactive.ts
|
|
9280
|
-
import
|
|
9719
|
+
import chalk13 from "chalk";
|
|
9281
9720
|
import { resolve as resolve8 } from "path";
|
|
9282
|
-
import { existsSync as existsSync15, readFileSync as
|
|
9721
|
+
import { existsSync as existsSync15, readFileSync as readFileSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
9283
9722
|
import { DesignSystemManager as DesignSystemManager6, ComponentManager as ComponentManager4, loadManifest as loadManifest7 } from "@getcoherent/core";
|
|
9284
9723
|
var DEBUG3 = process.env.COHERENT_DEBUG === "1";
|
|
9285
9724
|
async function interactiveChat(options, chatCommandFn) {
|
|
@@ -9295,7 +9734,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9295
9734
|
const validProviders = ["claude", "openai", "auto"];
|
|
9296
9735
|
const provider = (options.provider || "auto").toLowerCase();
|
|
9297
9736
|
if (!validProviders.includes(provider)) {
|
|
9298
|
-
console.error(
|
|
9737
|
+
console.error(chalk13.red(`
|
|
9299
9738
|
\u274C Invalid provider: ${options.provider}`));
|
|
9300
9739
|
process.exit(1);
|
|
9301
9740
|
}
|
|
@@ -9305,18 +9744,18 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9305
9744
|
try {
|
|
9306
9745
|
mkdirSync5(historyDir, { recursive: true });
|
|
9307
9746
|
if (existsSync15(historyFile)) {
|
|
9308
|
-
history =
|
|
9747
|
+
history = readFileSync10(historyFile, "utf-8").split("\n").filter(Boolean).slice(-200);
|
|
9309
9748
|
}
|
|
9310
9749
|
} catch (e) {
|
|
9311
9750
|
if (DEBUG3) console.error("Failed to load REPL history:", e);
|
|
9312
9751
|
}
|
|
9313
|
-
console.log(
|
|
9314
|
-
console.log(
|
|
9315
|
-
console.log(
|
|
9752
|
+
console.log(chalk13.cyan("\n\u{1F3A8} Coherent Interactive Mode"));
|
|
9753
|
+
console.log(chalk13.dim(" Type your requests, or use built-in commands."));
|
|
9754
|
+
console.log(chalk13.dim(' Type "help" for available commands, "exit" to quit.\n'));
|
|
9316
9755
|
const rl = createInterface({
|
|
9317
9756
|
input: process.stdin,
|
|
9318
9757
|
output: process.stdout,
|
|
9319
|
-
prompt:
|
|
9758
|
+
prompt: chalk13.cyan("Coherent> "),
|
|
9320
9759
|
history,
|
|
9321
9760
|
historySize: 200
|
|
9322
9761
|
});
|
|
@@ -9330,37 +9769,37 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9330
9769
|
const lower = input.toLowerCase();
|
|
9331
9770
|
if (lower === "exit" || lower === "quit" || lower === "q") {
|
|
9332
9771
|
saveHistory();
|
|
9333
|
-
console.log(
|
|
9772
|
+
console.log(chalk13.dim("\nBye!\n"));
|
|
9334
9773
|
rl.close();
|
|
9335
9774
|
process.exit(0);
|
|
9336
9775
|
}
|
|
9337
9776
|
if (lower === "help") {
|
|
9338
|
-
console.log(
|
|
9339
|
-
console.log(
|
|
9340
|
-
console.log(
|
|
9341
|
-
console.log(
|
|
9342
|
-
console.log(
|
|
9343
|
-
console.log(
|
|
9344
|
-
console.log(
|
|
9345
|
-
console.log(
|
|
9346
|
-
console.log(
|
|
9347
|
-
console.log(
|
|
9348
|
-
console.log(
|
|
9777
|
+
console.log(chalk13.bold("\n Built-in commands:"));
|
|
9778
|
+
console.log(chalk13.white(" components") + chalk13.dim(" \u2014 list shared and UI components"));
|
|
9779
|
+
console.log(chalk13.white(" pages") + chalk13.dim(" \u2014 list all pages"));
|
|
9780
|
+
console.log(chalk13.white(" tokens") + chalk13.dim(" \u2014 show design tokens"));
|
|
9781
|
+
console.log(chalk13.white(" status") + chalk13.dim(" \u2014 project summary"));
|
|
9782
|
+
console.log(chalk13.white(" help") + chalk13.dim(" \u2014 this help"));
|
|
9783
|
+
console.log(chalk13.white(" exit") + chalk13.dim(" \u2014 quit interactive mode"));
|
|
9784
|
+
console.log(chalk13.bold("\n Target shortcuts:"));
|
|
9785
|
+
console.log(chalk13.white(" @ComponentName <msg>") + chalk13.dim(" \u2014 target a shared component"));
|
|
9786
|
+
console.log(chalk13.white(" @/route <msg>") + chalk13.dim(" \u2014 target a page by route"));
|
|
9787
|
+
console.log(chalk13.dim("\n Anything else is sent to AI as a modification request.\n"));
|
|
9349
9788
|
rl.prompt();
|
|
9350
9789
|
return;
|
|
9351
9790
|
}
|
|
9352
9791
|
if (lower === "components" || lower === "list components" || lower.includes("what components")) {
|
|
9353
9792
|
const manifest = await loadManifest7(projectRoot);
|
|
9354
9793
|
if (manifest.shared.length === 0) {
|
|
9355
|
-
console.log(
|
|
9794
|
+
console.log(chalk13.gray("\n No shared components yet.\n"));
|
|
9356
9795
|
} else {
|
|
9357
9796
|
console.log("");
|
|
9358
9797
|
const order = { layout: 0, section: 1, widget: 2 };
|
|
9359
9798
|
const sorted = [...manifest.shared].sort((a, b) => (order[a.type] ?? 9) - (order[b.type] ?? 9));
|
|
9360
9799
|
sorted.forEach((entry) => {
|
|
9361
|
-
const usage = entry.usedIn.includes("app/layout.tsx") ?
|
|
9800
|
+
const usage = entry.usedIn.includes("app/layout.tsx") ? chalk13.green("all pages") : entry.usedIn.length > 0 ? chalk13.gray(entry.usedIn.join(", ")) : chalk13.gray("unused");
|
|
9362
9801
|
console.log(
|
|
9363
|
-
` ${
|
|
9802
|
+
` ${chalk13.cyan(entry.id.padEnd(8))} ${chalk13.white(entry.name.padEnd(18))} ${chalk13.gray(entry.type.padEnd(9))} ${usage}`
|
|
9364
9803
|
);
|
|
9365
9804
|
});
|
|
9366
9805
|
console.log("");
|
|
@@ -9371,11 +9810,11 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9371
9810
|
if (lower === "pages" || lower === "list pages" || lower.includes("what pages")) {
|
|
9372
9811
|
const currentConfig = dsm.getConfig();
|
|
9373
9812
|
if (currentConfig.pages.length === 0) {
|
|
9374
|
-
console.log(
|
|
9813
|
+
console.log(chalk13.gray("\n No pages yet.\n"));
|
|
9375
9814
|
} else {
|
|
9376
9815
|
console.log("");
|
|
9377
9816
|
currentConfig.pages.forEach((p) => {
|
|
9378
|
-
console.log(` ${
|
|
9817
|
+
console.log(` ${chalk13.white(p.name.padEnd(22))} ${chalk13.gray(p.route)}`);
|
|
9379
9818
|
});
|
|
9380
9819
|
console.log("");
|
|
9381
9820
|
}
|
|
@@ -9385,10 +9824,10 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9385
9824
|
if (lower === "status") {
|
|
9386
9825
|
const currentConfig = dsm.getConfig();
|
|
9387
9826
|
const manifest = await loadManifest7(projectRoot);
|
|
9388
|
-
console.log(
|
|
9827
|
+
console.log(chalk13.bold(`
|
|
9389
9828
|
${currentConfig.name || "Coherent Project"}`));
|
|
9390
9829
|
console.log(
|
|
9391
|
-
|
|
9830
|
+
chalk13.dim(
|
|
9392
9831
|
` Pages: ${currentConfig.pages.length} | Shared components: ${manifest.shared.length} | UI components: ${cm.getAllComponents().length}
|
|
9393
9832
|
`
|
|
9394
9833
|
)
|
|
@@ -9399,21 +9838,21 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9399
9838
|
if (lower === "tokens" || lower === "show tokens" || lower === "design tokens") {
|
|
9400
9839
|
const currentConfig = dsm.getConfig();
|
|
9401
9840
|
const t = currentConfig.tokens;
|
|
9402
|
-
console.log(
|
|
9403
|
-
console.log(
|
|
9841
|
+
console.log(chalk13.bold("\n Design Tokens\n"));
|
|
9842
|
+
console.log(chalk13.cyan(" Colors (light)"));
|
|
9404
9843
|
for (const [k, v] of Object.entries(t.colors.light)) {
|
|
9405
|
-
console.log(` ${
|
|
9844
|
+
console.log(` ${chalk13.white(k.padEnd(14))} ${chalk13.gray(v)}`);
|
|
9406
9845
|
}
|
|
9407
|
-
console.log(
|
|
9408
|
-
console.log(` ${
|
|
9409
|
-
console.log(` ${
|
|
9410
|
-
console.log(
|
|
9846
|
+
console.log(chalk13.cyan("\n Typography"));
|
|
9847
|
+
console.log(` ${chalk13.white("sans".padEnd(14))} ${chalk13.gray(t.typography.fontFamily.sans)}`);
|
|
9848
|
+
console.log(` ${chalk13.white("mono".padEnd(14))} ${chalk13.gray(t.typography.fontFamily.mono)}`);
|
|
9849
|
+
console.log(chalk13.cyan("\n Spacing"));
|
|
9411
9850
|
for (const [k, v] of Object.entries(t.spacing)) {
|
|
9412
|
-
console.log(` ${
|
|
9851
|
+
console.log(` ${chalk13.white(k.padEnd(14))} ${chalk13.gray(v)}`);
|
|
9413
9852
|
}
|
|
9414
|
-
console.log(
|
|
9853
|
+
console.log(chalk13.cyan("\n Radius"));
|
|
9415
9854
|
for (const [k, v] of Object.entries(t.radius)) {
|
|
9416
|
-
console.log(` ${
|
|
9855
|
+
console.log(` ${chalk13.white(k.padEnd(14))} ${chalk13.gray(v)}`);
|
|
9417
9856
|
}
|
|
9418
9857
|
console.log("");
|
|
9419
9858
|
rl.prompt();
|
|
@@ -9435,7 +9874,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9435
9874
|
await dsm.load();
|
|
9436
9875
|
} catch (err) {
|
|
9437
9876
|
if (!err._printed) {
|
|
9438
|
-
console.error(
|
|
9877
|
+
console.error(chalk13.red(`
|
|
9439
9878
|
Error: ${err.message}
|
|
9440
9879
|
`));
|
|
9441
9880
|
}
|
|
@@ -9467,8 +9906,8 @@ async function chatCommand(message, options) {
|
|
|
9467
9906
|
process.exit(1);
|
|
9468
9907
|
};
|
|
9469
9908
|
if (!message) {
|
|
9470
|
-
console.error(
|
|
9471
|
-
console.log(
|
|
9909
|
+
console.error(chalk14.red('\n\u274C No message provided. Use: coherent chat "your request"\n'));
|
|
9910
|
+
console.log(chalk14.dim(" Or use interactive mode: coherent chat -i\n"));
|
|
9472
9911
|
bail("No message provided");
|
|
9473
9912
|
}
|
|
9474
9913
|
const spinner = ora2("Processing your request...").start();
|
|
@@ -9478,7 +9917,7 @@ async function chatCommand(message, options) {
|
|
|
9478
9917
|
const migrationGuard = join11(projectRoot, ".coherent", "migration-in-progress");
|
|
9479
9918
|
if (existsSync16(migrationGuard)) {
|
|
9480
9919
|
spinner.fail("Migration in progress");
|
|
9481
|
-
console.error(
|
|
9920
|
+
console.error(chalk14.red("\n\u274C A migration is in progress. Run `coherent migrate --rollback` to undo first."));
|
|
9482
9921
|
bail("Migration in progress");
|
|
9483
9922
|
}
|
|
9484
9923
|
const validProviders = ["claude", "openai", "auto"];
|
|
@@ -9488,20 +9927,20 @@ async function chatCommand(message, options) {
|
|
|
9488
9927
|
releaseLock = await acquireProjectLock(projectRoot);
|
|
9489
9928
|
if (!validProviders.includes(provider)) {
|
|
9490
9929
|
spinner.fail("Invalid provider");
|
|
9491
|
-
console.error(
|
|
9930
|
+
console.error(chalk14.red(`
|
|
9492
9931
|
\u274C Invalid provider: ${options.provider}`));
|
|
9493
|
-
console.log(
|
|
9932
|
+
console.log(chalk14.dim(`Valid options: ${validProviders.join(", ")}`));
|
|
9494
9933
|
bail(`Invalid provider: ${options.provider}`);
|
|
9495
9934
|
}
|
|
9496
9935
|
spinner.text = "Loading design system configuration...";
|
|
9497
9936
|
const config2 = await loadConfig(configPath);
|
|
9498
9937
|
if (config2.coherentVersion && config2.coherentVersion !== CLI_VERSION2) {
|
|
9499
9938
|
spinner.stop();
|
|
9500
|
-
console.log(
|
|
9501
|
-
console.log(
|
|
9502
|
-
console.log(
|
|
9503
|
-
console.log(
|
|
9504
|
-
console.log(
|
|
9939
|
+
console.log(chalk14.yellow("\n\u26A0\uFE0F Version mismatch detected\n"));
|
|
9940
|
+
console.log(chalk14.gray(" Project created with: ") + chalk14.white(`v${config2.coherentVersion}`));
|
|
9941
|
+
console.log(chalk14.gray(" Current CLI version: ") + chalk14.white(`v${CLI_VERSION2}`));
|
|
9942
|
+
console.log(chalk14.cyan("\n \u{1F4A1} Run `coherent update` to apply latest changes to your project.\n"));
|
|
9943
|
+
console.log(chalk14.dim(" Continuing anyway...\n"));
|
|
9505
9944
|
spinner.start("Loading design system configuration...");
|
|
9506
9945
|
}
|
|
9507
9946
|
if (needsGlobalsFix(projectRoot)) {
|
|
@@ -9528,9 +9967,9 @@ async function chatCommand(message, options) {
|
|
|
9528
9967
|
const done = await setDefaultDarkTheme(projectRoot);
|
|
9529
9968
|
spinner.stop();
|
|
9530
9969
|
if (done) {
|
|
9531
|
-
console.log(
|
|
9970
|
+
console.log(chalk14.green("\n\u2705 Default theme set to dark. Reload the app to see changes.\n"));
|
|
9532
9971
|
} else {
|
|
9533
|
-
console.log(
|
|
9972
|
+
console.log(chalk14.yellow("\n\u26A0\uFE0F Could not update layout (app/layout.tsx not found).\n"));
|
|
9534
9973
|
}
|
|
9535
9974
|
return;
|
|
9536
9975
|
}
|
|
@@ -9546,10 +9985,10 @@ async function chatCommand(message, options) {
|
|
|
9546
9985
|
dsm.updateConfig(cfg);
|
|
9547
9986
|
dsm.save();
|
|
9548
9987
|
spinner.stop();
|
|
9549
|
-
console.log(
|
|
9988
|
+
console.log(chalk14.green("\n\u2705 Default theme set to light. Reload the app to see changes.\n"));
|
|
9550
9989
|
} catch {
|
|
9551
9990
|
spinner.stop();
|
|
9552
|
-
console.log(
|
|
9991
|
+
console.log(chalk14.yellow("\n\u26A0\uFE0F Could not update layout (app/layout.tsx not found).\n"));
|
|
9553
9992
|
}
|
|
9554
9993
|
return;
|
|
9555
9994
|
}
|
|
@@ -9559,7 +9998,7 @@ async function chatCommand(message, options) {
|
|
|
9559
9998
|
const { created, id } = await ensureThemeToggle(projectRoot);
|
|
9560
9999
|
spinner.stop();
|
|
9561
10000
|
console.log(
|
|
9562
|
-
|
|
10001
|
+
chalk14.green(
|
|
9563
10002
|
`
|
|
9564
10003
|
\u2705 ${created ? `Created ${id} (ThemeToggle) and added to layout` : "ThemeToggle already present; layout updated"}.
|
|
9565
10004
|
`
|
|
@@ -9567,7 +10006,7 @@ async function chatCommand(message, options) {
|
|
|
9567
10006
|
);
|
|
9568
10007
|
} catch (e) {
|
|
9569
10008
|
spinner.fail("Failed to add theme toggle");
|
|
9570
|
-
if (e instanceof Error) console.error(
|
|
10009
|
+
if (e instanceof Error) console.error(chalk14.red("\n\u274C " + e.message + "\n"));
|
|
9571
10010
|
}
|
|
9572
10011
|
return;
|
|
9573
10012
|
}
|
|
@@ -9583,12 +10022,12 @@ async function chatCommand(message, options) {
|
|
|
9583
10022
|
manifest = { ...manifest, shared: validShared };
|
|
9584
10023
|
await saveManifest2(project.root, manifest);
|
|
9585
10024
|
if (DEBUG4) {
|
|
9586
|
-
console.log(
|
|
10025
|
+
console.log(chalk14.dim(`[pre-gen] Cleaned ${cleaned} orphaned component(s) from manifest`));
|
|
9587
10026
|
}
|
|
9588
10027
|
}
|
|
9589
10028
|
const sharedComponentsSummary = buildSharedComponentsSummary(manifest);
|
|
9590
10029
|
if (DEBUG4 && sharedComponentsSummary) {
|
|
9591
|
-
console.log(
|
|
10030
|
+
console.log(chalk14.dim("[add-page] sharedComponentsSummary in prompt:\n" + sharedComponentsSummary));
|
|
9592
10031
|
}
|
|
9593
10032
|
let requests;
|
|
9594
10033
|
let uxRecommendations;
|
|
@@ -9600,7 +10039,12 @@ async function chatCommand(message, options) {
|
|
|
9600
10039
|
) || []).length >= SPLIT_THRESHOLD;
|
|
9601
10040
|
if (multiPageHint) {
|
|
9602
10041
|
try {
|
|
9603
|
-
|
|
10042
|
+
const splitResult = await splitGeneratePages(spinner, message, modCtx, provider, parseOpts);
|
|
10043
|
+
requests = splitResult.requests;
|
|
10044
|
+
if (splitResult.plan && projectRoot) {
|
|
10045
|
+
savePlan(projectRoot, splitResult.plan);
|
|
10046
|
+
await ensurePlanGroupLayouts(projectRoot, splitResult.plan);
|
|
10047
|
+
}
|
|
9604
10048
|
uxRecommendations = void 0;
|
|
9605
10049
|
} catch {
|
|
9606
10050
|
spinner.warn("Split generation encountered an issue \u2014 trying page-by-page...");
|
|
@@ -9673,7 +10117,12 @@ async function chatCommand(message, options) {
|
|
|
9673
10117
|
if (isTruncated || isJsonError) {
|
|
9674
10118
|
spinner.warn("Response too large \u2014 splitting into smaller requests...");
|
|
9675
10119
|
try {
|
|
9676
|
-
|
|
10120
|
+
const splitResult = await splitGeneratePages(spinner, message, modCtx, provider, parseOpts);
|
|
10121
|
+
requests = splitResult.requests;
|
|
10122
|
+
if (splitResult.plan && projectRoot) {
|
|
10123
|
+
savePlan(projectRoot, splitResult.plan);
|
|
10124
|
+
await ensurePlanGroupLayouts(projectRoot, splitResult.plan);
|
|
10125
|
+
}
|
|
9677
10126
|
uxRecommendations = void 0;
|
|
9678
10127
|
} catch {
|
|
9679
10128
|
throw firstError;
|
|
@@ -9685,10 +10134,10 @@ async function chatCommand(message, options) {
|
|
|
9685
10134
|
}
|
|
9686
10135
|
if (requests.length === 0) {
|
|
9687
10136
|
spinner.fail("No modifications found in your request");
|
|
9688
|
-
console.log(
|
|
9689
|
-
console.log(
|
|
9690
|
-
console.log(
|
|
9691
|
-
console.log(
|
|
10137
|
+
console.log(chalk14.yellow("\n\u{1F4A1} Try being more specific, e.g.:"));
|
|
10138
|
+
console.log(chalk14.dim(' - "make buttons blue"'));
|
|
10139
|
+
console.log(chalk14.dim(' - "add a pricing page"'));
|
|
10140
|
+
console.log(chalk14.dim(' - "change primary color to green"'));
|
|
9692
10141
|
return;
|
|
9693
10142
|
}
|
|
9694
10143
|
spinner.succeed(`Parsed ${requests.length} modification(s)`);
|
|
@@ -9696,11 +10145,11 @@ async function chatCommand(message, options) {
|
|
|
9696
10145
|
normalizedRequests = normalizedRequests.map((req) => {
|
|
9697
10146
|
const result = normalizeRequest(req, dsm.getConfig());
|
|
9698
10147
|
if ("error" in result) {
|
|
9699
|
-
console.log(
|
|
10148
|
+
console.log(chalk14.yellow(` \u26A0 Skipped: ${result.error}`));
|
|
9700
10149
|
return null;
|
|
9701
10150
|
}
|
|
9702
10151
|
if (result.type !== req.type) {
|
|
9703
|
-
console.log(
|
|
10152
|
+
console.log(chalk14.dim(` \u2139 Adjusted: ${req.type} \u2192 ${result.type} (target: ${req.target})`));
|
|
9704
10153
|
}
|
|
9705
10154
|
return result;
|
|
9706
10155
|
}).filter((r) => r !== null);
|
|
@@ -9756,13 +10205,13 @@ async function chatCommand(message, options) {
|
|
|
9756
10205
|
}
|
|
9757
10206
|
}
|
|
9758
10207
|
if (DEBUG4) {
|
|
9759
|
-
console.log(
|
|
10208
|
+
console.log(chalk14.gray(`
|
|
9760
10209
|
[DEBUG] Pre-flight analysis for page "${page.name || page.route}": `));
|
|
9761
|
-
console.log(
|
|
10210
|
+
console.log(chalk14.gray(` Page sections: ${page.sections?.length || 0}`));
|
|
9762
10211
|
if (page.sections?.[0]?.props?.fields) {
|
|
9763
|
-
console.log(
|
|
10212
|
+
console.log(chalk14.gray(` First section has ${page.sections[0].props.fields.length} fields`));
|
|
9764
10213
|
page.sections[0].props.fields.forEach((f, i) => {
|
|
9765
|
-
console.log(
|
|
10214
|
+
console.log(chalk14.gray(` Field ${i}: component=${f.component}`));
|
|
9766
10215
|
});
|
|
9767
10216
|
}
|
|
9768
10217
|
}
|
|
@@ -9772,7 +10221,7 @@ async function chatCommand(message, options) {
|
|
|
9772
10221
|
try {
|
|
9773
10222
|
const sharedPath = resolve9(projectRoot, entry.file);
|
|
9774
10223
|
if (existsSync16(sharedPath)) {
|
|
9775
|
-
const sharedCode =
|
|
10224
|
+
const sharedCode = readFileSync11(sharedPath, "utf-8");
|
|
9776
10225
|
const sharedImports = sharedCode.matchAll(/@\/components\/ui\/([a-z0-9-]+)/g);
|
|
9777
10226
|
for (const m of sharedImports) {
|
|
9778
10227
|
if (m[1]) allNeededComponentIds.add(m[1]);
|
|
@@ -9785,8 +10234,8 @@ async function chatCommand(message, options) {
|
|
|
9785
10234
|
const INVALID_COMPONENT_IDS = /* @__PURE__ */ new Set(["ui", "shared", "lib", "utils", "hooks", "app", "components"]);
|
|
9786
10235
|
for (const id of INVALID_COMPONENT_IDS) allNeededComponentIds.delete(id);
|
|
9787
10236
|
if (DEBUG4) {
|
|
9788
|
-
console.log(
|
|
9789
|
-
console.log(
|
|
10237
|
+
console.log(chalk14.gray("\n[DEBUG] Pre-flight analysis (consolidated):"));
|
|
10238
|
+
console.log(chalk14.gray(` All needed components: ${Array.from(allNeededComponentIds).join(", ")}`));
|
|
9790
10239
|
console.log("");
|
|
9791
10240
|
}
|
|
9792
10241
|
const missingComponents = [];
|
|
@@ -9794,56 +10243,59 @@ async function chatCommand(message, options) {
|
|
|
9794
10243
|
const isRegistered = !!cm.read(componentId);
|
|
9795
10244
|
const filePath = join11(projectRoot, "components", "ui", `${componentId}.tsx`);
|
|
9796
10245
|
const fileExists = existsSync16(filePath);
|
|
9797
|
-
if (DEBUG4) console.log(
|
|
10246
|
+
if (DEBUG4) console.log(chalk14.gray(` Checking ${componentId}: registered=${isRegistered} file=${fileExists}`));
|
|
9798
10247
|
if (!isRegistered || !fileExists) {
|
|
9799
10248
|
missingComponents.push(componentId);
|
|
9800
10249
|
}
|
|
9801
10250
|
}
|
|
9802
10251
|
if (missingComponents.length > 0) {
|
|
9803
10252
|
spinner.stop();
|
|
9804
|
-
console.log(
|
|
10253
|
+
console.log(chalk14.cyan("\n\u{1F50D} Pre-flight check: Installing missing components...\n"));
|
|
9805
10254
|
const provider2 = getComponentProvider();
|
|
9806
10255
|
for (const componentId of missingComponents) {
|
|
9807
10256
|
if (DEBUG4) {
|
|
9808
|
-
console.log(
|
|
9809
|
-
console.log(
|
|
10257
|
+
console.log(chalk14.gray(` [DEBUG] Trying to install: ${componentId}`));
|
|
10258
|
+
console.log(chalk14.gray(` [DEBUG] provider.has(${componentId}): ${provider2.has(componentId)}`));
|
|
9810
10259
|
}
|
|
9811
10260
|
if (provider2.has(componentId)) {
|
|
9812
10261
|
try {
|
|
9813
10262
|
const result = await provider2.installComponent(componentId, projectRoot);
|
|
9814
|
-
if (DEBUG4) console.log(
|
|
10263
|
+
if (DEBUG4) console.log(chalk14.gray(` [DEBUG] installComponent result: ${result.success}`));
|
|
9815
10264
|
if (result.success && result.componentDef) {
|
|
9816
10265
|
if (!cm.read(componentId)) {
|
|
9817
|
-
if (DEBUG4)
|
|
10266
|
+
if (DEBUG4)
|
|
10267
|
+
console.log(
|
|
10268
|
+
chalk14.gray(` [DEBUG] Registering ${result.componentDef.id} (${result.componentDef.name})`)
|
|
10269
|
+
);
|
|
9818
10270
|
const regResult = await cm.register(result.componentDef);
|
|
9819
10271
|
if (DEBUG4) {
|
|
9820
10272
|
console.log(
|
|
9821
|
-
|
|
10273
|
+
chalk14.gray(
|
|
9822
10274
|
` [DEBUG] Register result: ${regResult.success ? "SUCCESS" : "FAILED"}${!regResult.success && regResult.message ? ` - ${regResult.message}` : ""}`
|
|
9823
10275
|
)
|
|
9824
10276
|
);
|
|
9825
10277
|
}
|
|
9826
10278
|
if (regResult.success) {
|
|
9827
10279
|
preflightInstalledIds.push(result.componentDef.id);
|
|
9828
|
-
console.log(
|
|
10280
|
+
console.log(chalk14.green(` \u2728 Auto-installed ${result.componentDef.name} component`));
|
|
9829
10281
|
dsm.updateConfig(regResult.config);
|
|
9830
10282
|
cm.updateConfig(regResult.config);
|
|
9831
10283
|
pm.updateConfig(regResult.config);
|
|
9832
10284
|
}
|
|
9833
10285
|
} else {
|
|
9834
10286
|
preflightInstalledIds.push(result.componentDef.id);
|
|
9835
|
-
console.log(
|
|
10287
|
+
console.log(chalk14.green(` \u2728 Re-installed ${result.componentDef.name} component (file was missing)`));
|
|
9836
10288
|
}
|
|
9837
10289
|
}
|
|
9838
10290
|
} catch (error) {
|
|
9839
|
-
console.log(
|
|
9840
|
-
console.log(
|
|
10291
|
+
console.log(chalk14.red(` \u274C Failed to install ${componentId}:`));
|
|
10292
|
+
console.log(chalk14.red(` ${error instanceof Error ? error.message : error}`));
|
|
9841
10293
|
if (error instanceof Error && error.stack) {
|
|
9842
|
-
console.log(
|
|
10294
|
+
console.log(chalk14.gray(` ${error.stack.split("\n")[1]}`));
|
|
9843
10295
|
}
|
|
9844
10296
|
}
|
|
9845
10297
|
} else {
|
|
9846
|
-
console.log(
|
|
10298
|
+
console.log(chalk14.yellow(` \u26A0\uFE0F Component ${componentId} not available`));
|
|
9847
10299
|
}
|
|
9848
10300
|
}
|
|
9849
10301
|
console.log("");
|
|
@@ -9854,11 +10306,11 @@ async function chatCommand(message, options) {
|
|
|
9854
10306
|
const toInstallNpm = [...neededPkgs].filter((p) => !installedPkgs.has(p));
|
|
9855
10307
|
if (toInstallNpm.length > 0) {
|
|
9856
10308
|
spinner.stop();
|
|
9857
|
-
console.log(
|
|
10309
|
+
console.log(chalk14.cyan(`
|
|
9858
10310
|
\u{1F4E6} Auto-installing missing dependencies: ${toInstallNpm.join(", ")}
|
|
9859
10311
|
`));
|
|
9860
10312
|
const ok = await installPackages(projectRoot, toInstallNpm);
|
|
9861
|
-
if (!ok) console.log(
|
|
10313
|
+
if (!ok) console.log(chalk14.yellow(` Run manually: npm install ${toInstallNpm.join(" ")}
|
|
9862
10314
|
`));
|
|
9863
10315
|
spinner.start("Applying modifications...");
|
|
9864
10316
|
}
|
|
@@ -9869,7 +10321,7 @@ async function chatCommand(message, options) {
|
|
|
9869
10321
|
if (componentId && preflightComponentIds.has(componentId)) {
|
|
9870
10322
|
if (DEBUG4) {
|
|
9871
10323
|
console.log(
|
|
9872
|
-
|
|
10324
|
+
chalk14.gray(`[DEBUG] Filtered duplicate add-component: ${componentId} (already installed in pre-flight)`)
|
|
9873
10325
|
);
|
|
9874
10326
|
}
|
|
9875
10327
|
return false;
|
|
@@ -9878,11 +10330,11 @@ async function chatCommand(message, options) {
|
|
|
9878
10330
|
return true;
|
|
9879
10331
|
});
|
|
9880
10332
|
if (DEBUG4 && preflightComponentIds.size > 0) {
|
|
9881
|
-
console.log(
|
|
10333
|
+
console.log(chalk14.gray(`[DEBUG] Remaining requests after filtering: ${normalizedRequests.length}`));
|
|
9882
10334
|
}
|
|
9883
10335
|
try {
|
|
9884
10336
|
createBackup(projectRoot);
|
|
9885
|
-
if (DEBUG4) console.log(
|
|
10337
|
+
if (DEBUG4) console.log(chalk14.dim("[backup] Created snapshot"));
|
|
9886
10338
|
} catch {
|
|
9887
10339
|
}
|
|
9888
10340
|
const navBefore = takeNavSnapshot(
|
|
@@ -9894,6 +10346,17 @@ async function chatCommand(message, options) {
|
|
|
9894
10346
|
const result = await applyModification(request, dsm, cm, pm, projectRoot, provider, message);
|
|
9895
10347
|
results.push(result);
|
|
9896
10348
|
}
|
|
10349
|
+
for (const request of normalizedRequests) {
|
|
10350
|
+
const changes = request.changes;
|
|
10351
|
+
if ((request.type === "add-page" || request.type === "update-page") && changes?.route === "/" && changes?.pageCode) {
|
|
10352
|
+
const cfg = dsm.getConfig();
|
|
10353
|
+
if (cfg.settings.homePagePlaceholder) {
|
|
10354
|
+
cfg.settings.homePagePlaceholder = false;
|
|
10355
|
+
dsm.updateConfig(cfg);
|
|
10356
|
+
}
|
|
10357
|
+
break;
|
|
10358
|
+
}
|
|
10359
|
+
}
|
|
9897
10360
|
const currentConfig = dsm.getConfig();
|
|
9898
10361
|
const autoScaffoldEnabled = currentConfig.settings.autoScaffold === true;
|
|
9899
10362
|
const scaffoldedPages = [];
|
|
@@ -9907,7 +10370,7 @@ async function chatCommand(message, options) {
|
|
|
9907
10370
|
let pageCode = "";
|
|
9908
10371
|
if (existsSync16(pageFilePath)) {
|
|
9909
10372
|
try {
|
|
9910
|
-
pageCode =
|
|
10373
|
+
pageCode = readFileSync11(pageFilePath, "utf-8");
|
|
9911
10374
|
} catch {
|
|
9912
10375
|
}
|
|
9913
10376
|
}
|
|
@@ -9926,10 +10389,10 @@ async function chatCommand(message, options) {
|
|
|
9926
10389
|
const SCAFFOLD_AI_LIMIT = 10;
|
|
9927
10390
|
if (missingRoutes.length > 0 && missingRoutes.length <= SCAFFOLD_AI_LIMIT) {
|
|
9928
10391
|
spinner.stop();
|
|
9929
|
-
console.log(
|
|
10392
|
+
console.log(chalk14.cyan(`
|
|
9930
10393
|
\u{1F517} Auto-scaffolding ${missingRoutes.length} linked page(s)...`));
|
|
9931
10394
|
console.log(
|
|
9932
|
-
|
|
10395
|
+
chalk14.dim(
|
|
9933
10396
|
` (${missingRoutes.length} additional AI call(s) \u2014 disable with settings.autoScaffold: false in config)
|
|
9934
10397
|
`
|
|
9935
10398
|
)
|
|
@@ -9966,7 +10429,7 @@ async function chatCommand(message, options) {
|
|
|
9966
10429
|
}
|
|
9967
10430
|
} catch (err) {
|
|
9968
10431
|
scaffoldSpinner.warn(` Could not scaffold "${pageName}" (${linkedRoute}) \u2014 skipped`);
|
|
9969
|
-
if (DEBUG4) console.log(
|
|
10432
|
+
if (DEBUG4) console.log(chalk14.dim(` ${err instanceof Error ? err.message : "unknown error"}`));
|
|
9970
10433
|
}
|
|
9971
10434
|
}
|
|
9972
10435
|
console.log("");
|
|
@@ -9974,7 +10437,7 @@ async function chatCommand(message, options) {
|
|
|
9974
10437
|
} else if (missingRoutes.length > SCAFFOLD_AI_LIMIT) {
|
|
9975
10438
|
spinner.stop();
|
|
9976
10439
|
console.log(
|
|
9977
|
-
|
|
10440
|
+
chalk14.yellow(
|
|
9978
10441
|
`
|
|
9979
10442
|
\u26A0 Found ${missingRoutes.length} linked pages \u2014 creating placeholder pages (too many for AI generation).`
|
|
9980
10443
|
)
|
|
@@ -10003,7 +10466,7 @@ async function chatCommand(message, options) {
|
|
|
10003
10466
|
scaffoldedPages.push({ route: linkedRoute, name: `${pageName} (placeholder)` });
|
|
10004
10467
|
}
|
|
10005
10468
|
console.log(
|
|
10006
|
-
|
|
10469
|
+
chalk14.cyan(` Created ${missingRoutes.length} placeholder pages. Use \`coherent chat\` to fill them.
|
|
10007
10470
|
`)
|
|
10008
10471
|
);
|
|
10009
10472
|
spinner.start("Finalizing...");
|
|
@@ -10018,7 +10481,7 @@ async function chatCommand(message, options) {
|
|
|
10018
10481
|
for (const mod of result.modified) {
|
|
10019
10482
|
if (mod.startsWith("app/") && mod.endsWith("/page.tsx")) {
|
|
10020
10483
|
try {
|
|
10021
|
-
const code =
|
|
10484
|
+
const code = readFileSync11(resolve9(projectRoot, mod), "utf-8");
|
|
10022
10485
|
const issues = validatePageQuality(code, allRoutes).filter(
|
|
10023
10486
|
(i) => i.type === "BROKEN_INTERNAL_LINK"
|
|
10024
10487
|
);
|
|
@@ -10031,9 +10494,9 @@ async function chatCommand(message, options) {
|
|
|
10031
10494
|
}
|
|
10032
10495
|
}
|
|
10033
10496
|
if (linkIssues.length > 0) {
|
|
10034
|
-
console.log(
|
|
10497
|
+
console.log(chalk14.yellow("\n\u{1F517} Broken internal links:"));
|
|
10035
10498
|
for (const { page, message: message2 } of linkIssues) {
|
|
10036
|
-
console.log(
|
|
10499
|
+
console.log(chalk14.dim(` ${page}: ${message2}`));
|
|
10037
10500
|
}
|
|
10038
10501
|
}
|
|
10039
10502
|
}
|
|
@@ -10046,7 +10509,7 @@ async function chatCommand(message, options) {
|
|
|
10046
10509
|
if (latestConfig.theme.defaultMode !== targetMode) {
|
|
10047
10510
|
latestConfig.theme.defaultMode = targetMode;
|
|
10048
10511
|
dsm.updateConfig(latestConfig);
|
|
10049
|
-
if (DEBUG4) console.log(
|
|
10512
|
+
if (DEBUG4) console.log(chalk14.dim(` [theme] Set defaultMode to "${targetMode}"`));
|
|
10050
10513
|
}
|
|
10051
10514
|
const layoutPath = resolve9(projectRoot, "app", "layout.tsx");
|
|
10052
10515
|
try {
|
|
@@ -10054,11 +10517,11 @@ async function chatCommand(message, options) {
|
|
|
10054
10517
|
if (targetMode === "dark" && !layoutCode.includes('className="dark"')) {
|
|
10055
10518
|
layoutCode = layoutCode.replace(/<html\s+lang="en"/, '<html lang="en" className="dark"');
|
|
10056
10519
|
await writeFile(layoutPath, layoutCode);
|
|
10057
|
-
console.log(
|
|
10520
|
+
console.log(chalk14.dim(` \u{1F319} Applied dark theme to layout`));
|
|
10058
10521
|
} else if (targetMode === "light" && layoutCode.includes('className="dark"')) {
|
|
10059
10522
|
layoutCode = layoutCode.replace(' className="dark"', "");
|
|
10060
10523
|
await writeFile(layoutPath, layoutCode);
|
|
10061
|
-
console.log(
|
|
10524
|
+
console.log(chalk14.dim(` \u2600\uFE0F Applied light theme to layout`));
|
|
10062
10525
|
}
|
|
10063
10526
|
} catch {
|
|
10064
10527
|
}
|
|
@@ -10088,6 +10551,10 @@ async function chatCommand(message, options) {
|
|
|
10088
10551
|
await regenerateFiles(Array.from(allModified), updatedConfig, projectRoot, { navChanged, storedHashes });
|
|
10089
10552
|
spinner.succeed("Files regenerated");
|
|
10090
10553
|
}
|
|
10554
|
+
const finalDeps = await scanAndInstallSharedDeps(projectRoot);
|
|
10555
|
+
if (finalDeps.length > 0) {
|
|
10556
|
+
console.log(chalk14.dim(` Auto-installed shared deps: ${finalDeps.join(", ")}`));
|
|
10557
|
+
}
|
|
10091
10558
|
try {
|
|
10092
10559
|
fixGlobalsCss(projectRoot, updatedConfig);
|
|
10093
10560
|
} catch {
|
|
@@ -10098,7 +10565,7 @@ async function chatCommand(message, options) {
|
|
|
10098
10565
|
const layoutFile = resolve9(projectRoot, "app", "layout.tsx");
|
|
10099
10566
|
const filesToHash = [layoutFile];
|
|
10100
10567
|
if (existsSync16(sharedDir)) {
|
|
10101
|
-
for (const f of
|
|
10568
|
+
for (const f of readdirSync3(sharedDir)) {
|
|
10102
10569
|
if (f.endsWith(".tsx")) filesToHash.push(resolve9(sharedDir, f));
|
|
10103
10570
|
}
|
|
10104
10571
|
}
|
|
@@ -10110,7 +10577,7 @@ async function chatCommand(message, options) {
|
|
|
10110
10577
|
}
|
|
10111
10578
|
await saveHashes(projectRoot, updatedHashes);
|
|
10112
10579
|
} catch {
|
|
10113
|
-
if (DEBUG4) console.log(
|
|
10580
|
+
if (DEBUG4) console.log(chalk14.dim("[hashes] Could not save file hashes"));
|
|
10114
10581
|
}
|
|
10115
10582
|
const successfulPairs = normalizedRequests.map((request, index) => ({ request, result: results[index] })).filter(({ result }) => result.success);
|
|
10116
10583
|
if (successfulPairs.length > 0) {
|
|
@@ -10127,9 +10594,9 @@ async function chatCommand(message, options) {
|
|
|
10127
10594
|
const preflightNames = preflightInstalledIds.map((id) => updatedConfig.components.find((c) => c.id === id)?.name).filter(Boolean);
|
|
10128
10595
|
showPreview(normalizedRequests, results, updatedConfig, preflightNames);
|
|
10129
10596
|
if (scaffoldedPages.length > 0) {
|
|
10130
|
-
console.log(
|
|
10597
|
+
console.log(chalk14.cyan("\u{1F517} Auto-scaffolded linked pages:"));
|
|
10131
10598
|
scaffoldedPages.forEach(({ route, name }) => {
|
|
10132
|
-
console.log(
|
|
10599
|
+
console.log(chalk14.white(` \u2728 ${name} \u2192 ${route}`));
|
|
10133
10600
|
});
|
|
10134
10601
|
console.log("");
|
|
10135
10602
|
}
|
|
@@ -10151,58 +10618,58 @@ ${uxRecommendations}
|
|
|
10151
10618
|
);
|
|
10152
10619
|
}
|
|
10153
10620
|
await appendFile(recPath, section);
|
|
10154
|
-
console.log(
|
|
10621
|
+
console.log(chalk14.cyan("\n\u{1F4CB} UX Recommendations:"));
|
|
10155
10622
|
for (const line of uxRecommendations.split("\n").filter(Boolean)) {
|
|
10156
|
-
console.log(
|
|
10623
|
+
console.log(chalk14.dim(` ${line}`));
|
|
10157
10624
|
}
|
|
10158
|
-
console.log(
|
|
10625
|
+
console.log(chalk14.dim(" \u2192 Saved to /design-system/docs/recommendations"));
|
|
10159
10626
|
} catch (e) {
|
|
10160
10627
|
console.log(
|
|
10161
|
-
|
|
10628
|
+
chalk14.yellow("\n\u26A0\uFE0F Could not write recommendations.md: " + (e instanceof Error ? e.message : String(e)))
|
|
10162
10629
|
);
|
|
10163
|
-
console.log(
|
|
10630
|
+
console.log(chalk14.dim("Recommendations:\n") + uxRecommendations);
|
|
10164
10631
|
}
|
|
10165
10632
|
}
|
|
10166
10633
|
} catch (error) {
|
|
10167
10634
|
spinner.fail("Chat command failed");
|
|
10168
|
-
console.error(
|
|
10635
|
+
console.error(chalk14.red("\n\u2716 Chat command failed"));
|
|
10169
10636
|
const zodError = error;
|
|
10170
10637
|
const issues = zodError.issues || error.errors;
|
|
10171
10638
|
if (issues && Array.isArray(issues)) {
|
|
10172
|
-
console.log(
|
|
10639
|
+
console.log(chalk14.yellow("\n\u26A0\uFE0F AI generated incomplete data. Missing or invalid fields:"));
|
|
10173
10640
|
issues.forEach((err) => {
|
|
10174
|
-
console.log(
|
|
10641
|
+
console.log(chalk14.gray(` \u2022 ${err.path.join(".")}: ${err.message}`));
|
|
10175
10642
|
});
|
|
10176
|
-
console.log(
|
|
10177
|
-
console.log(
|
|
10178
|
-
console.log(
|
|
10643
|
+
console.log(chalk14.cyan("\n\u{1F4A1} Try being more specific, e.g.:"));
|
|
10644
|
+
console.log(chalk14.white(' coherent chat "add a dashboard page with hero section using Button component"'));
|
|
10645
|
+
console.log(chalk14.white(' coherent chat "add pricing page"'));
|
|
10179
10646
|
} else if (error instanceof Error) {
|
|
10180
|
-
console.error(
|
|
10647
|
+
console.error(chalk14.red(error.message));
|
|
10181
10648
|
if (error.message.includes("Unterminated string") || error.message.includes("Unexpected end of JSON") || error.message.includes("Failed to parse modification") && error.message.includes("JSON")) {
|
|
10182
10649
|
console.log(
|
|
10183
|
-
|
|
10650
|
+
chalk14.yellow("\n\u{1F4A1} The AI response was too large or contained invalid JSON. Try splitting your request:")
|
|
10184
10651
|
);
|
|
10185
|
-
console.log(
|
|
10186
|
-
console.log(
|
|
10187
|
-
console.log(
|
|
10652
|
+
console.log(chalk14.white(' coherent chat "add dashboard page with stats and recent activity"'));
|
|
10653
|
+
console.log(chalk14.white(' coherent chat "add account page"'));
|
|
10654
|
+
console.log(chalk14.white(' coherent chat "add settings page"'));
|
|
10188
10655
|
} else if (error.message.includes("API key not found") || error.message.includes("ANTHROPIC_API_KEY") || error.message.includes("OPENAI_API_KEY")) {
|
|
10189
10656
|
const isOpenAI = error.message.includes("OpenAI") || typeof provider !== "undefined" && provider === "openai";
|
|
10190
10657
|
const providerName = isOpenAI ? "OpenAI" : "Anthropic Claude";
|
|
10191
10658
|
const envVar = isOpenAI ? "OPENAI_API_KEY" : "ANTHROPIC_API_KEY";
|
|
10192
10659
|
const url = isOpenAI ? "https://platform.openai.com" : "https://console.anthropic.com";
|
|
10193
|
-
console.log(
|
|
10194
|
-
console.log(
|
|
10195
|
-
console.log(
|
|
10196
|
-
console.log(
|
|
10197
|
-
console.log(
|
|
10198
|
-
console.log(
|
|
10660
|
+
console.log(chalk14.yellow("\n\u{1F4A1} Setup Instructions:"));
|
|
10661
|
+
console.log(chalk14.dim(` 1. Get your ${providerName} API key from: ${url}`));
|
|
10662
|
+
console.log(chalk14.dim(" 2. Create a .env file in the current directory:"));
|
|
10663
|
+
console.log(chalk14.cyan(` echo "${envVar}=your_key_here" > .env`));
|
|
10664
|
+
console.log(chalk14.dim(" 3. Or export it in your shell:"));
|
|
10665
|
+
console.log(chalk14.cyan(` export ${envVar}=your_key_here`));
|
|
10199
10666
|
if (isOpenAI) {
|
|
10200
|
-
console.log(
|
|
10201
|
-
console.log(
|
|
10667
|
+
console.log(chalk14.dim('\n Also ensure "openai" package is installed:'));
|
|
10668
|
+
console.log(chalk14.cyan(" npm install openai"));
|
|
10202
10669
|
}
|
|
10203
10670
|
}
|
|
10204
10671
|
} else {
|
|
10205
|
-
console.error(
|
|
10672
|
+
console.error(chalk14.red("Unknown error occurred"));
|
|
10206
10673
|
}
|
|
10207
10674
|
console.log("");
|
|
10208
10675
|
if (options._throwOnError) {
|
|
@@ -10215,21 +10682,21 @@ ${uxRecommendations}
|
|
|
10215
10682
|
}
|
|
10216
10683
|
|
|
10217
10684
|
// src/commands/preview.ts
|
|
10218
|
-
import
|
|
10685
|
+
import chalk15 from "chalk";
|
|
10219
10686
|
import ora3 from "ora";
|
|
10220
10687
|
import { spawn } from "child_process";
|
|
10221
|
-
import { existsSync as existsSync19, rmSync as rmSync3, readFileSync as
|
|
10688
|
+
import { existsSync as existsSync19, rmSync as rmSync3, readFileSync as readFileSync14, writeFileSync as writeFileSync10, readdirSync as readdirSync5 } from "fs";
|
|
10222
10689
|
import { resolve as resolve10, join as join14 } from "path";
|
|
10223
10690
|
import { readdir as readdir2 } from "fs/promises";
|
|
10224
10691
|
import { DesignSystemManager as DesignSystemManager8, ComponentGenerator as ComponentGenerator3 } from "@getcoherent/core";
|
|
10225
10692
|
|
|
10226
10693
|
// src/utils/file-watcher.ts
|
|
10227
|
-
import { readFileSync as
|
|
10694
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync18 } from "fs";
|
|
10228
10695
|
import { relative as relative4, join as join13 } from "path";
|
|
10229
10696
|
import { loadManifest as loadManifest9, saveManifest as saveManifest3 } from "@getcoherent/core";
|
|
10230
10697
|
|
|
10231
10698
|
// src/utils/component-integrity.ts
|
|
10232
|
-
import { existsSync as existsSync17, readFileSync as
|
|
10699
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12, readdirSync as readdirSync4 } from "fs";
|
|
10233
10700
|
import { join as join12, relative as relative3 } from "path";
|
|
10234
10701
|
function extractExportedComponentNames(code) {
|
|
10235
10702
|
const names = [];
|
|
@@ -10263,7 +10730,7 @@ function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
|
10263
10730
|
for (const absPath of pageFiles) {
|
|
10264
10731
|
if (absPath.includes("design-system")) continue;
|
|
10265
10732
|
try {
|
|
10266
|
-
const code =
|
|
10733
|
+
const code = readFileSync12(absPath, "utf-8");
|
|
10267
10734
|
const hasNamedImport = new RegExp(`import\\s+\\{[^}]*\\b${componentName}\\b[^}]*\\}\\s+from\\s+['"]`).test(code);
|
|
10268
10735
|
const hasDefaultImport = new RegExp(`import\\s+${componentName}\\s+from\\s+['"]`).test(code);
|
|
10269
10736
|
const hasPathImport = code.includes(`@/${componentImportPath}`);
|
|
@@ -10279,7 +10746,7 @@ function isUsedInLayout(projectRoot, componentName) {
|
|
|
10279
10746
|
const layoutPath = join12(projectRoot, "app", "layout.tsx");
|
|
10280
10747
|
if (!existsSync17(layoutPath)) return false;
|
|
10281
10748
|
try {
|
|
10282
|
-
const code =
|
|
10749
|
+
const code = readFileSync12(layoutPath, "utf-8");
|
|
10283
10750
|
return code.includes(componentName);
|
|
10284
10751
|
} catch {
|
|
10285
10752
|
return false;
|
|
@@ -10300,7 +10767,7 @@ function findUnregisteredComponents(projectRoot, manifest) {
|
|
|
10300
10767
|
const relFile = relative3(projectRoot, absPath);
|
|
10301
10768
|
if (registeredFiles.has(relFile)) continue;
|
|
10302
10769
|
try {
|
|
10303
|
-
const code =
|
|
10770
|
+
const code = readFileSync12(absPath, "utf-8");
|
|
10304
10771
|
const exports = extractExportedComponentNames(code);
|
|
10305
10772
|
for (const name of exports) {
|
|
10306
10773
|
if (registeredNames.has(name)) continue;
|
|
@@ -10322,7 +10789,7 @@ function findInlineDuplicates(projectRoot, manifest) {
|
|
|
10322
10789
|
if (absPath.includes("design-system")) continue;
|
|
10323
10790
|
let code;
|
|
10324
10791
|
try {
|
|
10325
|
-
code =
|
|
10792
|
+
code = readFileSync12(absPath, "utf-8");
|
|
10326
10793
|
} catch {
|
|
10327
10794
|
continue;
|
|
10328
10795
|
}
|
|
@@ -10354,7 +10821,7 @@ function findComponentFileByExportName(projectRoot, componentName) {
|
|
|
10354
10821
|
);
|
|
10355
10822
|
for (const absPath of files) {
|
|
10356
10823
|
try {
|
|
10357
|
-
const code =
|
|
10824
|
+
const code = readFileSync12(absPath, "utf-8");
|
|
10358
10825
|
const exports = extractExportedComponentNames(code);
|
|
10359
10826
|
if (exports.includes(componentName)) {
|
|
10360
10827
|
return relative3(projectRoot, absPath);
|
|
@@ -10399,7 +10866,7 @@ function reconcileComponents(projectRoot, manifest) {
|
|
|
10399
10866
|
}
|
|
10400
10867
|
let code;
|
|
10401
10868
|
try {
|
|
10402
|
-
code =
|
|
10869
|
+
code = readFileSync12(join12(projectRoot, entry.file), "utf-8");
|
|
10403
10870
|
} catch {
|
|
10404
10871
|
return true;
|
|
10405
10872
|
}
|
|
@@ -10476,7 +10943,7 @@ function collectFiles(dir, filter, skipDirs = []) {
|
|
|
10476
10943
|
function walk(d) {
|
|
10477
10944
|
let entries;
|
|
10478
10945
|
try {
|
|
10479
|
-
entries =
|
|
10946
|
+
entries = readdirSync4(d, { withFileTypes: true });
|
|
10480
10947
|
} catch {
|
|
10481
10948
|
return;
|
|
10482
10949
|
}
|
|
@@ -10522,7 +10989,7 @@ function getWatcherConfig(projectRoot) {
|
|
|
10522
10989
|
try {
|
|
10523
10990
|
const pkgPath = join13(projectRoot, "package.json");
|
|
10524
10991
|
if (!existsSync18(pkgPath)) return defaultWatcherConfig();
|
|
10525
|
-
const pkg = JSON.parse(
|
|
10992
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
10526
10993
|
const c = pkg?.coherent?.watcher ?? {};
|
|
10527
10994
|
return {
|
|
10528
10995
|
enabled: c.enabled !== false,
|
|
@@ -10550,18 +11017,18 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
10550
11017
|
if (relativePath.includes("node_modules") || relativePath.includes(".next")) return;
|
|
10551
11018
|
let content;
|
|
10552
11019
|
try {
|
|
10553
|
-
content =
|
|
11020
|
+
content = readFileSync13(filePath, "utf-8");
|
|
10554
11021
|
} catch {
|
|
10555
11022
|
return;
|
|
10556
11023
|
}
|
|
10557
11024
|
const config2 = getWatcherConfig(projectRoot);
|
|
10558
|
-
const
|
|
11025
|
+
const chalk34 = (await import("chalk")).default;
|
|
10559
11026
|
if (config2.autoInstall) {
|
|
10560
11027
|
const missing = findMissingPackagesInCode(content, projectRoot);
|
|
10561
11028
|
if (missing.length > 0) {
|
|
10562
11029
|
const ok = await installPackages(projectRoot, missing);
|
|
10563
11030
|
if (ok) {
|
|
10564
|
-
console.log(
|
|
11031
|
+
console.log(chalk34.cyan(`
|
|
10565
11032
|
\u{1F527} Auto-installed: ${missing.join(", ")} (needed by ${relativePath})`));
|
|
10566
11033
|
}
|
|
10567
11034
|
}
|
|
@@ -10570,12 +11037,12 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
10570
11037
|
const fixed = sanitizeMetadataStrings(ensureUseClientIfNeeded(content));
|
|
10571
11038
|
if (fixed !== content) {
|
|
10572
11039
|
writeFileSync9(filePath, fixed, "utf-8");
|
|
10573
|
-
console.log(
|
|
11040
|
+
console.log(chalk34.cyan(` \u{1F527} Auto-fixed syntax in ${relativePath}`));
|
|
10574
11041
|
}
|
|
10575
11042
|
}
|
|
10576
11043
|
if (config2.warnNativeElements && hasNativeElements(content)) {
|
|
10577
|
-
console.log(
|
|
10578
|
-
console.log(
|
|
11044
|
+
console.log(chalk34.yellow(` \u26A0 ${relativePath}: uses native HTML elements (<button>, <select>, etc.)`));
|
|
11045
|
+
console.log(chalk34.dim(" Use components from @/components/ui/ instead"));
|
|
10579
11046
|
}
|
|
10580
11047
|
if (config2.warnSharedReuse) {
|
|
10581
11048
|
let manifest;
|
|
@@ -10588,8 +11055,8 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
10588
11055
|
const dupes = findInlineDuplicatesOfShared(content, manifest);
|
|
10589
11056
|
for (const d of dupes) {
|
|
10590
11057
|
const importPath = d.file.replace(/\.tsx$/, "").replace(/^components\/shared\//, "");
|
|
10591
|
-
console.log(
|
|
10592
|
-
console.log(
|
|
11058
|
+
console.log(chalk34.yellow(` \u26A0 ${relativePath}: has inline code similar to ${d.cid} (${d.name})`));
|
|
11059
|
+
console.log(chalk34.dim(` Consider: import { ${d.name} } from "@/components/shared/${importPath}"`));
|
|
10593
11060
|
}
|
|
10594
11061
|
}
|
|
10595
11062
|
}
|
|
@@ -10598,7 +11065,7 @@ async function handleFileDelete(projectRoot, filePath) {
|
|
|
10598
11065
|
const relativePath = relative4(projectRoot, filePath).replace(/\\/g, "/");
|
|
10599
11066
|
if (!relativePath.startsWith("components/") || relativePath.startsWith("components/ui/")) return;
|
|
10600
11067
|
try {
|
|
10601
|
-
const
|
|
11068
|
+
const chalk34 = (await import("chalk")).default;
|
|
10602
11069
|
const manifest = await loadManifest9(projectRoot);
|
|
10603
11070
|
const orphaned = manifest.shared.find((s) => s.file === relativePath);
|
|
10604
11071
|
if (orphaned) {
|
|
@@ -10607,7 +11074,7 @@ async function handleFileDelete(projectRoot, filePath) {
|
|
|
10607
11074
|
shared: manifest.shared.filter((s) => s.id !== orphaned.id)
|
|
10608
11075
|
};
|
|
10609
11076
|
await saveManifest3(projectRoot, cleaned);
|
|
10610
|
-
console.log(
|
|
11077
|
+
console.log(chalk34.cyan(`
|
|
10611
11078
|
\u{1F5D1} Auto-removed ${orphaned.id} (${orphaned.name}) \u2014 file deleted`));
|
|
10612
11079
|
await writeCursorRules(projectRoot);
|
|
10613
11080
|
}
|
|
@@ -10619,18 +11086,18 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
10619
11086
|
if (!relativePath.startsWith("components/") || relativePath.startsWith("components/ui/")) return;
|
|
10620
11087
|
if (!relativePath.endsWith(".tsx") && !relativePath.endsWith(".jsx")) return;
|
|
10621
11088
|
try {
|
|
10622
|
-
const
|
|
11089
|
+
const chalk34 = (await import("chalk")).default;
|
|
10623
11090
|
const manifest = await loadManifest9(projectRoot);
|
|
10624
11091
|
const alreadyRegistered = manifest.shared.some((s) => s.file === relativePath);
|
|
10625
11092
|
if (alreadyRegistered) return;
|
|
10626
|
-
const code =
|
|
11093
|
+
const code = readFileSync13(filePath, "utf-8");
|
|
10627
11094
|
const exports = extractExportedComponentNames(code);
|
|
10628
11095
|
if (exports.length > 0) {
|
|
10629
11096
|
const alreadyByName = exports.every((n) => manifest.shared.some((s) => s.name === n));
|
|
10630
11097
|
if (!alreadyByName) {
|
|
10631
|
-
console.log(
|
|
11098
|
+
console.log(chalk34.cyan(`
|
|
10632
11099
|
\u2139 New component detected: ${exports[0]} in ${relativePath}`));
|
|
10633
|
-
console.log(
|
|
11100
|
+
console.log(chalk34.dim(" Register with: coherent sync"));
|
|
10634
11101
|
}
|
|
10635
11102
|
}
|
|
10636
11103
|
} catch {
|
|
@@ -10717,17 +11184,17 @@ function clearStaleCache(projectRoot) {
|
|
|
10717
11184
|
const nextDir = join14(projectRoot, ".next");
|
|
10718
11185
|
if (existsSync19(nextDir)) {
|
|
10719
11186
|
rmSync3(nextDir, { recursive: true, force: true });
|
|
10720
|
-
console.log(
|
|
11187
|
+
console.log(chalk15.dim(" \u2714 Cleared stale build cache"));
|
|
10721
11188
|
}
|
|
10722
11189
|
}
|
|
10723
11190
|
async function preflightDependencyCheck(projectRoot) {
|
|
10724
11191
|
const missing = await findMissingPackages(projectRoot);
|
|
10725
11192
|
if (missing.length === 0) return;
|
|
10726
|
-
console.log(
|
|
11193
|
+
console.log(chalk15.cyan(`
|
|
10727
11194
|
Auto-installing missing dependencies: ${missing.join(", ")}`));
|
|
10728
11195
|
const ok = await installPackages(projectRoot, missing);
|
|
10729
|
-
if (ok) console.log(
|
|
10730
|
-
else console.log(
|
|
11196
|
+
if (ok) console.log(chalk15.dim(" \u2714 Installed"));
|
|
11197
|
+
else console.log(chalk15.yellow(` Run manually: npm install ${missing.join(" ")}`));
|
|
10731
11198
|
}
|
|
10732
11199
|
async function listPageFiles(appDir) {
|
|
10733
11200
|
const out = [];
|
|
@@ -10747,11 +11214,11 @@ async function validateSyntax(projectRoot) {
|
|
|
10747
11214
|
const appDir = join14(projectRoot, "app");
|
|
10748
11215
|
const pages = await listPageFiles(appDir);
|
|
10749
11216
|
for (const file of pages) {
|
|
10750
|
-
const content =
|
|
11217
|
+
const content = readFileSync14(file, "utf-8");
|
|
10751
11218
|
const fixed = fixUnescapedLtInJsx(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)));
|
|
10752
11219
|
if (fixed !== content) {
|
|
10753
11220
|
writeFileSync10(file, fixed, "utf-8");
|
|
10754
|
-
console.log(
|
|
11221
|
+
console.log(chalk15.dim(` \u2714 Auto-fixed syntax: ${file.replace(projectRoot, ".").replace(/^\.[/\\]/, "")}`));
|
|
10755
11222
|
}
|
|
10756
11223
|
}
|
|
10757
11224
|
}
|
|
@@ -10760,9 +11227,14 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10760
11227
|
const uiDir = join14(projectRoot, "components", "ui");
|
|
10761
11228
|
if (!existsSync19(appDir) || !existsSync19(uiDir)) return;
|
|
10762
11229
|
const pages = await listPageFiles(appDir);
|
|
11230
|
+
const sharedDir = join14(projectRoot, "components", "shared");
|
|
11231
|
+
if (existsSync19(sharedDir)) {
|
|
11232
|
+
const sharedFiles = readdirSync5(sharedDir).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts")).map((f) => join14(sharedDir, f));
|
|
11233
|
+
pages.push(...sharedFiles);
|
|
11234
|
+
}
|
|
10763
11235
|
const neededExports = /* @__PURE__ */ new Map();
|
|
10764
11236
|
for (const file of pages) {
|
|
10765
|
-
const content =
|
|
11237
|
+
const content = readFileSync14(file, "utf-8");
|
|
10766
11238
|
const importRe = /import\s*\{([^}]+)\}\s*from\s*['"]@\/components\/ui\/([^'"]+)['"]/g;
|
|
10767
11239
|
let m;
|
|
10768
11240
|
while ((m = importRe.exec(content)) !== null) {
|
|
@@ -10788,7 +11260,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10788
11260
|
try {
|
|
10789
11261
|
const result = await provider.installComponent(componentId, projectRoot);
|
|
10790
11262
|
if (result.success) {
|
|
10791
|
-
console.log(
|
|
11263
|
+
console.log(chalk15.dim(` \u2714 Installed missing ${componentId}.tsx`));
|
|
10792
11264
|
}
|
|
10793
11265
|
} catch {
|
|
10794
11266
|
}
|
|
@@ -10800,13 +11272,13 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10800
11272
|
mkdirSync10(uiDir, { recursive: true });
|
|
10801
11273
|
const newContent = await generator.generate(def);
|
|
10802
11274
|
writeFileSync10(componentFile, newContent, "utf-8");
|
|
10803
|
-
console.log(
|
|
11275
|
+
console.log(chalk15.dim(` \u2714 Created missing ${componentId}.tsx`));
|
|
10804
11276
|
} catch {
|
|
10805
11277
|
}
|
|
10806
11278
|
}
|
|
10807
11279
|
continue;
|
|
10808
11280
|
}
|
|
10809
|
-
const content =
|
|
11281
|
+
const content = readFileSync14(componentFile, "utf-8");
|
|
10810
11282
|
const exportRe = /export\s+(?:const|function|class)\s+(\w+)|export\s*\{([^}]+)\}/g;
|
|
10811
11283
|
const existingExports = /* @__PURE__ */ new Set();
|
|
10812
11284
|
let em;
|
|
@@ -10823,7 +11295,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10823
11295
|
try {
|
|
10824
11296
|
const result = await provider.installComponent(componentId, projectRoot, { force: true });
|
|
10825
11297
|
if (result.success) {
|
|
10826
|
-
console.log(
|
|
11298
|
+
console.log(chalk15.dim(` \u2714 Reinstalled ${componentId}.tsx (added missing exports: ${missing.join(", ")})`));
|
|
10827
11299
|
}
|
|
10828
11300
|
} catch {
|
|
10829
11301
|
}
|
|
@@ -10833,7 +11305,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10833
11305
|
try {
|
|
10834
11306
|
const newContent = await generator.generate(def);
|
|
10835
11307
|
writeFileSync10(componentFile, newContent, "utf-8");
|
|
10836
|
-
console.log(
|
|
11308
|
+
console.log(chalk15.dim(` \u2714 Regenerated ${componentId}.tsx (added missing exports: ${missing.join(", ")})`));
|
|
10837
11309
|
} catch {
|
|
10838
11310
|
}
|
|
10839
11311
|
}
|
|
@@ -10859,7 +11331,7 @@ async function backfillPageAnalysis(projectRoot) {
|
|
|
10859
11331
|
filePath = join14(projectRoot, "app", route.slice(1), "page.tsx");
|
|
10860
11332
|
}
|
|
10861
11333
|
if (!existsSync19(filePath)) continue;
|
|
10862
|
-
const code =
|
|
11334
|
+
const code = readFileSync14(filePath, "utf-8");
|
|
10863
11335
|
if (code.length < 50) continue;
|
|
10864
11336
|
page.pageAnalysis = analyzePageCode(code);
|
|
10865
11337
|
changed = true;
|
|
@@ -10892,14 +11364,14 @@ async function healthCheck(port) {
|
|
|
10892
11364
|
try {
|
|
10893
11365
|
const res = await fetch(`http://localhost:${port}`);
|
|
10894
11366
|
if (res.status === 200) {
|
|
10895
|
-
console.log(
|
|
11367
|
+
console.log(chalk15.green(`
|
|
10896
11368
|
\u2705 Preview healthy at http://localhost:${port}`));
|
|
10897
11369
|
} else {
|
|
10898
|
-
console.log(
|
|
11370
|
+
console.log(chalk15.yellow(`
|
|
10899
11371
|
\u26A0 Preview returned ${res.status}. Run: coherent fix`));
|
|
10900
11372
|
}
|
|
10901
11373
|
} catch {
|
|
10902
|
-
console.log(
|
|
11374
|
+
console.log(chalk15.yellow(`
|
|
10903
11375
|
\u26A0 Preview not responding. Run: coherent fix`));
|
|
10904
11376
|
}
|
|
10905
11377
|
}
|
|
@@ -10938,29 +11410,29 @@ function launchWithMonitoring(projectRoot, restarts) {
|
|
|
10938
11410
|
const shadcnId = extractShadcnComponentFromModuleNotFound(msg);
|
|
10939
11411
|
if (shadcnId && !installingSet.has(shadcnId)) {
|
|
10940
11412
|
installingSet.add(shadcnId);
|
|
10941
|
-
console.log(
|
|
11413
|
+
console.log(chalk15.yellow(`
|
|
10942
11414
|
\u26A0 Missing component detected: ${shadcnId}`));
|
|
10943
|
-
console.log(
|
|
11415
|
+
console.log(chalk15.cyan(" Auto-installing..."));
|
|
10944
11416
|
autoInstallShadcnComponent(shadcnId, projectRoot).then((ok) => {
|
|
10945
11417
|
if (ok) {
|
|
10946
|
-
console.log(
|
|
11418
|
+
console.log(chalk15.green(` \u2714 Installed ${shadcnId}.tsx. Restarting...`));
|
|
10947
11419
|
intentionalRestart = true;
|
|
10948
11420
|
server.kill("SIGTERM");
|
|
10949
11421
|
launchWithMonitoring(projectRoot, restarts + 1).then(resolvePromise).catch(rejectPromise);
|
|
10950
11422
|
} else {
|
|
10951
|
-
console.log(
|
|
11423
|
+
console.log(chalk15.red(` \u2716 Could not install ${shadcnId}. Run: npx shadcn@latest add ${shadcnId}`));
|
|
10952
11424
|
}
|
|
10953
11425
|
});
|
|
10954
11426
|
} else if (!shadcnId) {
|
|
10955
11427
|
const pkg = extractPackageFromModuleNotFound(msg);
|
|
10956
11428
|
if (pkg && !installingSet.has(pkg)) {
|
|
10957
11429
|
installingSet.add(pkg);
|
|
10958
|
-
console.log(
|
|
11430
|
+
console.log(chalk15.yellow(`
|
|
10959
11431
|
\u26A0 Missing package detected: ${pkg}`));
|
|
10960
|
-
console.log(
|
|
11432
|
+
console.log(chalk15.cyan(" Auto-installing..."));
|
|
10961
11433
|
installPackages(projectRoot, [pkg]).then((ok) => {
|
|
10962
11434
|
if (ok) {
|
|
10963
|
-
console.log(
|
|
11435
|
+
console.log(chalk15.green(` \u2714 Installed ${pkg}. Restarting...`));
|
|
10964
11436
|
intentionalRestart = true;
|
|
10965
11437
|
server.kill("SIGTERM");
|
|
10966
11438
|
launchWithMonitoring(projectRoot, restarts + 1).then(resolvePromise).catch(rejectPromise);
|
|
@@ -10970,19 +11442,19 @@ function launchWithMonitoring(projectRoot, restarts) {
|
|
|
10970
11442
|
}
|
|
10971
11443
|
}
|
|
10972
11444
|
if (msg.includes("Failed to compile")) {
|
|
10973
|
-
console.log(
|
|
10974
|
-
console.log(
|
|
10975
|
-
console.log(
|
|
11445
|
+
console.log(chalk15.yellow("\n\u26A0 Compilation error detected."));
|
|
11446
|
+
console.log(chalk15.dim(' Hint: run "coherent fix" in another terminal to auto-fix'));
|
|
11447
|
+
console.log(chalk15.dim(' Or: coherent chat "fix the broken page"'));
|
|
10976
11448
|
}
|
|
10977
11449
|
});
|
|
10978
11450
|
server.on("exit", (code) => {
|
|
10979
11451
|
if (intentionalRestart) return;
|
|
10980
11452
|
if (code !== 0 && code !== null) {
|
|
10981
|
-
console.log(
|
|
11453
|
+
console.log(chalk15.red(`
|
|
10982
11454
|
\u274C Dev server exited with code ${code}`));
|
|
10983
|
-
console.log(
|
|
11455
|
+
console.log(chalk15.dim(' Check the output above. Fix and run "coherent preview" again.\n'));
|
|
10984
11456
|
} else {
|
|
10985
|
-
console.log(
|
|
11457
|
+
console.log(chalk15.dim("\n\u{1F44B} Dev server stopped"));
|
|
10986
11458
|
}
|
|
10987
11459
|
process.exit(code ?? 0);
|
|
10988
11460
|
});
|
|
@@ -10991,7 +11463,7 @@ function launchWithMonitoring(projectRoot, restarts) {
|
|
|
10991
11463
|
});
|
|
10992
11464
|
const shutdown = () => {
|
|
10993
11465
|
closeWatcher();
|
|
10994
|
-
console.log(
|
|
11466
|
+
console.log(chalk15.dim("\n\n\u{1F6D1} Stopping dev server..."));
|
|
10995
11467
|
server.kill("SIGTERM");
|
|
10996
11468
|
};
|
|
10997
11469
|
process.on("SIGINT", shutdown);
|
|
@@ -11003,9 +11475,9 @@ async function openBrowser(url) {
|
|
|
11003
11475
|
const open = await import("open");
|
|
11004
11476
|
await open.default(url);
|
|
11005
11477
|
} catch (error) {
|
|
11006
|
-
console.log(
|
|
11478
|
+
console.log(chalk15.yellow(`
|
|
11007
11479
|
\u26A0\uFE0F Could not open browser automatically`));
|
|
11008
|
-
console.log(
|
|
11480
|
+
console.log(chalk15.dim(` Please open ${url} manually`));
|
|
11009
11481
|
}
|
|
11010
11482
|
}
|
|
11011
11483
|
function startDevServer(projectRoot) {
|
|
@@ -11036,8 +11508,8 @@ async function previewCommand() {
|
|
|
11036
11508
|
try {
|
|
11037
11509
|
if (!checkProjectInitialized(projectRoot)) {
|
|
11038
11510
|
spinner.fail("Project not initialized");
|
|
11039
|
-
console.log(
|
|
11040
|
-
console.log(
|
|
11511
|
+
console.log(chalk15.red("\n\u274C Project not found"));
|
|
11512
|
+
console.log(chalk15.dim('Run "coherent init" first to create a project.'));
|
|
11041
11513
|
process.exit(1);
|
|
11042
11514
|
}
|
|
11043
11515
|
spinner.text = "Checking dependencies...";
|
|
@@ -11045,16 +11517,16 @@ async function previewCommand() {
|
|
|
11045
11517
|
spinner.warn("Dependencies not installed");
|
|
11046
11518
|
const pm = getPackageManager(projectRoot);
|
|
11047
11519
|
const installCommand = pm === "pnpm" ? "pnpm install" : "npm install";
|
|
11048
|
-
console.log(
|
|
11049
|
-
console.log(
|
|
11520
|
+
console.log(chalk15.yellow("\n\u26A0\uFE0F Dependencies not installed"));
|
|
11521
|
+
console.log(chalk15.cyan(`
|
|
11050
11522
|
Running ${installCommand}...
|
|
11051
11523
|
`));
|
|
11052
11524
|
const ok = await runInstall(projectRoot);
|
|
11053
11525
|
if (!ok) {
|
|
11054
|
-
console.error(
|
|
11526
|
+
console.error(chalk15.red('\n\u274C Install failed. Fix errors above and run "coherent preview" again.\n'));
|
|
11055
11527
|
process.exit(1);
|
|
11056
11528
|
}
|
|
11057
|
-
console.log(
|
|
11529
|
+
console.log(chalk15.green("\n\u2705 Dependencies installed\n"));
|
|
11058
11530
|
} else {
|
|
11059
11531
|
spinner.succeed("Project ready");
|
|
11060
11532
|
}
|
|
@@ -11077,30 +11549,30 @@ async function previewCommand() {
|
|
|
11077
11549
|
await fixMissingComponentExports(projectRoot);
|
|
11078
11550
|
await backfillPageAnalysis(projectRoot);
|
|
11079
11551
|
spinner.succeed("Project ready");
|
|
11080
|
-
console.log(
|
|
11081
|
-
console.log(
|
|
11552
|
+
console.log(chalk15.dim(" \u{1F4A1} Edited files manually? Run `coherent sync` to update the Design System.\n"));
|
|
11553
|
+
console.log(chalk15.blue("\n\u{1F680} Starting Next.js dev server...\n"));
|
|
11082
11554
|
await launchWithMonitoring(projectRoot, 0);
|
|
11083
11555
|
} catch (error) {
|
|
11084
11556
|
spinner.fail("Failed to start dev server");
|
|
11085
11557
|
if (error instanceof Error) {
|
|
11086
|
-
console.error(
|
|
11558
|
+
console.error(chalk15.red(`
|
|
11087
11559
|
\u274C ${error.message}`));
|
|
11088
11560
|
if (error.message.includes("package.json")) {
|
|
11089
|
-
console.log(
|
|
11090
|
-
console.log(
|
|
11561
|
+
console.log(chalk15.yellow("\n\u{1F4A1} Tip: Make sure you're in a Coherent project directory."));
|
|
11562
|
+
console.log(chalk15.dim(' Run "coherent init" to create a new project.'));
|
|
11091
11563
|
}
|
|
11092
11564
|
} else {
|
|
11093
|
-
console.error(
|
|
11565
|
+
console.error(chalk15.red("Unknown error occurred"));
|
|
11094
11566
|
}
|
|
11095
11567
|
process.exit(1);
|
|
11096
11568
|
}
|
|
11097
11569
|
}
|
|
11098
11570
|
|
|
11099
11571
|
// src/commands/export.ts
|
|
11100
|
-
import
|
|
11572
|
+
import chalk16 from "chalk";
|
|
11101
11573
|
import ora4 from "ora";
|
|
11102
11574
|
import { spawn as spawn2 } from "child_process";
|
|
11103
|
-
import { existsSync as existsSync20, rmSync as rmSync4, readdirSync as
|
|
11575
|
+
import { existsSync as existsSync20, rmSync as rmSync4, readdirSync as readdirSync6 } from "fs";
|
|
11104
11576
|
import { resolve as resolve11, join as join15, dirname as dirname7 } from "path";
|
|
11105
11577
|
import { readdir as readdir3, readFile as readFile6, writeFile as writeFile5, mkdir as mkdir5, copyFile as copyFile2 } from "fs/promises";
|
|
11106
11578
|
var COPY_EXCLUDE = /* @__PURE__ */ new Set([
|
|
@@ -11221,7 +11693,7 @@ function countComponents(outRoot) {
|
|
|
11221
11693
|
const dir = join15(outRoot, "components", sub);
|
|
11222
11694
|
if (!existsSync20(dir)) continue;
|
|
11223
11695
|
try {
|
|
11224
|
-
n +=
|
|
11696
|
+
n += readdirSync6(dir).filter((f) => f.endsWith(".tsx") || f.endsWith(".jsx")).length;
|
|
11225
11697
|
} catch {
|
|
11226
11698
|
}
|
|
11227
11699
|
}
|
|
@@ -11376,7 +11848,7 @@ async function exportCommand(options = {}) {
|
|
|
11376
11848
|
const missingDeps = await findMissingDepsInExport(outputDir);
|
|
11377
11849
|
if (missingDeps.length > 0) {
|
|
11378
11850
|
console.log(
|
|
11379
|
-
|
|
11851
|
+
chalk16.yellow(
|
|
11380
11852
|
"\n\u26A0\uFE0F Warning: exported code imports packages not in package.json: " + missingDeps.join(", ") + "\n Add them to dependencies and run npm install in the export dir.\n"
|
|
11381
11853
|
)
|
|
11382
11854
|
);
|
|
@@ -11392,7 +11864,7 @@ async function exportCommand(options = {}) {
|
|
|
11392
11864
|
spinner.succeed("Dependencies installed");
|
|
11393
11865
|
await ensureReadmeDeploySection(outputDir);
|
|
11394
11866
|
await patchNextConfigForExport(outputDir);
|
|
11395
|
-
console.log(
|
|
11867
|
+
console.log(chalk16.dim("\n Tip: run `coherent check` before export to catch quality issues.\n"));
|
|
11396
11868
|
let buildOk = false;
|
|
11397
11869
|
if (doBuild) {
|
|
11398
11870
|
spinner.start("Running next build...");
|
|
@@ -11402,7 +11874,7 @@ async function exportCommand(options = {}) {
|
|
|
11402
11874
|
spinner.succeed("Build: success");
|
|
11403
11875
|
} catch (e) {
|
|
11404
11876
|
spinner.fail("Build failed");
|
|
11405
|
-
if (e instanceof Error) console.error(
|
|
11877
|
+
if (e instanceof Error) console.error(chalk16.red(e.message));
|
|
11406
11878
|
}
|
|
11407
11879
|
} else {
|
|
11408
11880
|
buildOk = true;
|
|
@@ -11410,23 +11882,23 @@ async function exportCommand(options = {}) {
|
|
|
11410
11882
|
const pageCount = await countPages(outputDir);
|
|
11411
11883
|
const componentCount = countComponents(outputDir);
|
|
11412
11884
|
spinner.stop();
|
|
11413
|
-
console.log(
|
|
11414
|
-
console.log(
|
|
11415
|
-
console.log(
|
|
11416
|
-
console.log(
|
|
11885
|
+
console.log(chalk16.green("\n\u2705 Exported to " + outputDir + "\n"));
|
|
11886
|
+
console.log(chalk16.blue(" Pages: " + pageCount));
|
|
11887
|
+
console.log(chalk16.blue(" Components: " + componentCount + " (base + shared)"));
|
|
11888
|
+
console.log(chalk16.blue(" Build: " + (doBuild ? buildOk ? "success" : "failed" : "skipped (--no-build)")));
|
|
11417
11889
|
console.log("");
|
|
11418
|
-
console.log(
|
|
11419
|
-
console.log(
|
|
11890
|
+
console.log(chalk16.dim(" Deploy: npx vercel " + outputDir));
|
|
11891
|
+
console.log(chalk16.dim(" or: npx netlify deploy --dir " + outputDir + "/.next"));
|
|
11420
11892
|
console.log("");
|
|
11421
11893
|
} catch (error) {
|
|
11422
11894
|
spinner.fail("Export failed");
|
|
11423
|
-
if (error instanceof Error) console.error(
|
|
11895
|
+
if (error instanceof Error) console.error(chalk16.red("\n\u274C " + error.message));
|
|
11424
11896
|
process.exit(1);
|
|
11425
11897
|
}
|
|
11426
11898
|
}
|
|
11427
11899
|
|
|
11428
11900
|
// src/commands/status.ts
|
|
11429
|
-
import
|
|
11901
|
+
import chalk17 from "chalk";
|
|
11430
11902
|
import { basename } from "path";
|
|
11431
11903
|
import { DesignSystemManager as DesignSystemManager9 } from "@getcoherent/core";
|
|
11432
11904
|
function countTokens(tokens) {
|
|
@@ -11450,58 +11922,58 @@ async function statusCommand() {
|
|
|
11450
11922
|
try {
|
|
11451
11923
|
const project = findConfig();
|
|
11452
11924
|
if (!project) {
|
|
11453
|
-
console.log(
|
|
11925
|
+
console.log(chalk17.yellow("\u26A0\uFE0F Not in a Coherent project\n"));
|
|
11454
11926
|
console.log("Initialize a project:");
|
|
11455
|
-
console.log(
|
|
11927
|
+
console.log(chalk17.white(" $ coherent init\n"));
|
|
11456
11928
|
return;
|
|
11457
11929
|
}
|
|
11458
|
-
console.log(
|
|
11459
|
-
console.log(
|
|
11460
|
-
console.log(
|
|
11930
|
+
console.log(chalk17.cyan("\n\u2728 Current Project\n"));
|
|
11931
|
+
console.log(chalk17.gray("\u{1F4C1} Location: ") + chalk17.white(project.root));
|
|
11932
|
+
console.log(chalk17.gray("\u{1F4C4} Config: ") + chalk17.white(basename(project.configPath)));
|
|
11461
11933
|
console.log("");
|
|
11462
11934
|
try {
|
|
11463
11935
|
const manager = new DesignSystemManager9(project.configPath);
|
|
11464
11936
|
await manager.load();
|
|
11465
11937
|
const config2 = manager.getConfig();
|
|
11466
|
-
console.log(
|
|
11938
|
+
console.log(chalk17.cyan("\u{1F4CA} Statistics:\n"));
|
|
11467
11939
|
const pageCount = Array.isArray(config2.pages) ? config2.pages.length : Object.keys(config2.pages || {}).length;
|
|
11468
|
-
console.log(
|
|
11940
|
+
console.log(chalk17.gray(" Pages: ") + chalk17.white(String(pageCount)));
|
|
11469
11941
|
const componentCount = Array.isArray(config2.components) ? config2.components.length : Object.keys(config2.components || {}).length;
|
|
11470
|
-
console.log(
|
|
11942
|
+
console.log(chalk17.gray(" Components: ") + chalk17.white(String(componentCount)));
|
|
11471
11943
|
const tokenCount = countTokens(config2.tokens);
|
|
11472
|
-
console.log(
|
|
11944
|
+
console.log(chalk17.gray(" Design tokens: ") + chalk17.white(String(tokenCount)));
|
|
11473
11945
|
console.log("");
|
|
11474
11946
|
const recent = readRecentChanges(project.root);
|
|
11475
11947
|
if (recent.length > 0) {
|
|
11476
|
-
console.log(
|
|
11948
|
+
console.log(chalk17.cyan("\u{1F4DD} Recent changes:\n"));
|
|
11477
11949
|
recent.slice(0, 5).forEach((change) => {
|
|
11478
11950
|
const ago = formatTimeAgo(change.timestamp);
|
|
11479
|
-
console.log(
|
|
11951
|
+
console.log(chalk17.gray(" \u2022 ") + chalk17.white(change.description) + chalk17.gray(` (${ago})`));
|
|
11480
11952
|
});
|
|
11481
11953
|
console.log("");
|
|
11482
11954
|
}
|
|
11483
|
-
console.log(
|
|
11484
|
-
console.log(
|
|
11485
|
-
console.log(
|
|
11486
|
-
console.log(
|
|
11955
|
+
console.log(chalk17.cyan("\u{1F680} Quick actions:\n"));
|
|
11956
|
+
console.log(chalk17.white(' $ coherent chat "add new page"'));
|
|
11957
|
+
console.log(chalk17.white(" $ coherent preview"));
|
|
11958
|
+
console.log(chalk17.white(" $ coherent export"));
|
|
11487
11959
|
console.log("");
|
|
11488
11960
|
} catch (error) {
|
|
11489
|
-
console.error(
|
|
11961
|
+
console.error(chalk17.red("Error loading config:"));
|
|
11490
11962
|
if (error instanceof Error) {
|
|
11491
|
-
console.error(
|
|
11963
|
+
console.error(chalk17.red(` ${error.message}`));
|
|
11492
11964
|
} else {
|
|
11493
|
-
console.error(
|
|
11965
|
+
console.error(chalk17.red(" Unknown error"));
|
|
11494
11966
|
}
|
|
11495
11967
|
console.log("");
|
|
11496
11968
|
}
|
|
11497
11969
|
} catch (error) {
|
|
11498
|
-
console.error(
|
|
11970
|
+
console.error(chalk17.red("\u274C Command failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
11499
11971
|
process.exit(1);
|
|
11500
11972
|
}
|
|
11501
11973
|
}
|
|
11502
11974
|
|
|
11503
11975
|
// src/commands/regenerate-docs.ts
|
|
11504
|
-
import
|
|
11976
|
+
import chalk18 from "chalk";
|
|
11505
11977
|
import ora5 from "ora";
|
|
11506
11978
|
import { DesignSystemManager as DesignSystemManager10 } from "@getcoherent/core";
|
|
11507
11979
|
import { ProjectScaffolder as ProjectScaffolder2 } from "@getcoherent/core";
|
|
@@ -11509,9 +11981,9 @@ async function regenerateDocsCommand() {
|
|
|
11509
11981
|
try {
|
|
11510
11982
|
const project = findConfig();
|
|
11511
11983
|
if (!project) {
|
|
11512
|
-
console.log(
|
|
11984
|
+
console.log(chalk18.yellow("\u26A0\uFE0F Not in a Coherent project\n"));
|
|
11513
11985
|
console.log("Run this command from a project root that has design-system.config.ts");
|
|
11514
|
-
console.log(
|
|
11986
|
+
console.log(chalk18.white(" $ coherent init # in an empty folder first\n"));
|
|
11515
11987
|
process.exit(1);
|
|
11516
11988
|
}
|
|
11517
11989
|
const spinner = ora5("Regenerating documentation pages...").start();
|
|
@@ -11523,24 +11995,24 @@ async function regenerateDocsCommand() {
|
|
|
11523
11995
|
await scaffolder.generateDocsPages();
|
|
11524
11996
|
spinner.succeed("Documentation pages updated");
|
|
11525
11997
|
console.log(
|
|
11526
|
-
|
|
11998
|
+
chalk18.gray(
|
|
11527
11999
|
"\nUpdated: app/design-system/docs/ (layout, page, components, tokens, for-designers, recommendations)\n"
|
|
11528
12000
|
)
|
|
11529
12001
|
);
|
|
11530
12002
|
} catch (err) {
|
|
11531
12003
|
spinner.fail("Failed to regenerate docs");
|
|
11532
|
-
console.error(
|
|
12004
|
+
console.error(chalk18.red(err instanceof Error ? err.message : String(err)));
|
|
11533
12005
|
process.exit(1);
|
|
11534
12006
|
}
|
|
11535
12007
|
} catch (error) {
|
|
11536
|
-
console.error(
|
|
12008
|
+
console.error(chalk18.red("\u274C Command failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
11537
12009
|
process.exit(1);
|
|
11538
12010
|
}
|
|
11539
12011
|
}
|
|
11540
12012
|
|
|
11541
12013
|
// src/commands/fix.ts
|
|
11542
|
-
import
|
|
11543
|
-
import { readdirSync as
|
|
12014
|
+
import chalk19 from "chalk";
|
|
12015
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync15, existsSync as existsSync21, writeFileSync as writeFileSync11, rmSync as rmSync5, mkdirSync as mkdirSync7 } from "fs";
|
|
11544
12016
|
import { resolve as resolve12, join as join16 } from "path";
|
|
11545
12017
|
import {
|
|
11546
12018
|
DesignSystemManager as DesignSystemManager11,
|
|
@@ -11565,7 +12037,7 @@ function extractComponentIdsFromCode2(code) {
|
|
|
11565
12037
|
function listTsxFiles(dir) {
|
|
11566
12038
|
const files = [];
|
|
11567
12039
|
try {
|
|
11568
|
-
const entries =
|
|
12040
|
+
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
11569
12041
|
for (const e of entries) {
|
|
11570
12042
|
const full = join16(dir, e.name);
|
|
11571
12043
|
if (e.isDirectory() && e.name !== "node_modules" && !e.name.startsWith(".")) {
|
|
@@ -11591,31 +12063,31 @@ async function fixCommand(opts = {}) {
|
|
|
11591
12063
|
const fixes = [];
|
|
11592
12064
|
const remaining = [];
|
|
11593
12065
|
if (dryRun) {
|
|
11594
|
-
console.log(
|
|
12066
|
+
console.log(chalk19.cyan("\ncoherent fix --dry-run\n"));
|
|
11595
12067
|
} else {
|
|
11596
|
-
console.log(
|
|
12068
|
+
console.log(chalk19.cyan("\ncoherent fix\n"));
|
|
11597
12069
|
}
|
|
11598
12070
|
if (!skipCache) {
|
|
11599
12071
|
const nextDir = join16(projectRoot, ".next");
|
|
11600
12072
|
if (existsSync21(nextDir)) {
|
|
11601
12073
|
if (!dryRun) rmSync5(nextDir, { recursive: true, force: true });
|
|
11602
12074
|
fixes.push("Cleared build cache");
|
|
11603
|
-
console.log(
|
|
12075
|
+
console.log(chalk19.green(" \u2714 Cleared build cache"));
|
|
11604
12076
|
}
|
|
11605
12077
|
}
|
|
11606
12078
|
const missingPkgs = await findMissingPackages(projectRoot);
|
|
11607
12079
|
if (missingPkgs.length > 0) {
|
|
11608
12080
|
if (dryRun) {
|
|
11609
12081
|
fixes.push(`Would install packages: ${missingPkgs.join(", ")}`);
|
|
11610
|
-
console.log(
|
|
12082
|
+
console.log(chalk19.green(` \u2714 Would install packages: ${missingPkgs.join(", ")}`));
|
|
11611
12083
|
} else {
|
|
11612
12084
|
const ok = await installPackages(projectRoot, missingPkgs);
|
|
11613
12085
|
if (ok) {
|
|
11614
12086
|
fixes.push(`Installed missing packages: ${missingPkgs.join(", ")}`);
|
|
11615
|
-
console.log(
|
|
12087
|
+
console.log(chalk19.green(` \u2714 Installed missing packages: ${missingPkgs.join(", ")}`));
|
|
11616
12088
|
} else {
|
|
11617
12089
|
remaining.push(`Failed to install: ${missingPkgs.join(", ")}. Run: npm install ${missingPkgs.join(" ")}`);
|
|
11618
|
-
console.log(
|
|
12090
|
+
console.log(chalk19.yellow(` \u26A0 Could not install: ${missingPkgs.join(", ")}`));
|
|
11619
12091
|
}
|
|
11620
12092
|
}
|
|
11621
12093
|
}
|
|
@@ -11624,7 +12096,7 @@ async function fixCommand(opts = {}) {
|
|
|
11624
12096
|
const componentsTsxFiles = listTsxFiles(resolve12(projectRoot, "components"));
|
|
11625
12097
|
const allComponentIds = /* @__PURE__ */ new Set();
|
|
11626
12098
|
for (const file of [...allTsxFiles, ...componentsTsxFiles]) {
|
|
11627
|
-
const content =
|
|
12099
|
+
const content = readFileSync15(file, "utf-8");
|
|
11628
12100
|
extractComponentIdsFromCode2(content).forEach((id) => allComponentIds.add(id));
|
|
11629
12101
|
}
|
|
11630
12102
|
let dsm = null;
|
|
@@ -11652,7 +12124,7 @@ async function fixCommand(opts = {}) {
|
|
|
11652
12124
|
if (toInstall.length > 0) {
|
|
11653
12125
|
if (dryRun) {
|
|
11654
12126
|
fixes.push(`Would install components: ${toInstall.join(", ")}`);
|
|
11655
|
-
console.log(
|
|
12127
|
+
console.log(chalk19.green(` \u2714 Would install components: ${toInstall.join(", ")}`));
|
|
11656
12128
|
} else {
|
|
11657
12129
|
let installed = 0;
|
|
11658
12130
|
for (const componentId of toInstall) {
|
|
@@ -11681,14 +12153,14 @@ async function fixCommand(opts = {}) {
|
|
|
11681
12153
|
installed++;
|
|
11682
12154
|
} catch (err) {
|
|
11683
12155
|
console.log(
|
|
11684
|
-
|
|
12156
|
+
chalk19.yellow(` \u26A0 Failed to install ${componentId}: ${err instanceof Error ? err.message : "unknown"}`)
|
|
11685
12157
|
);
|
|
11686
12158
|
}
|
|
11687
12159
|
}
|
|
11688
12160
|
if (installed > 0) {
|
|
11689
12161
|
await dsm.save();
|
|
11690
12162
|
fixes.push(`Installed missing components: ${toInstall.join(", ")}`);
|
|
11691
|
-
console.log(
|
|
12163
|
+
console.log(chalk19.green(` \u2714 Installed missing components: ${toInstall.join(", ")}`));
|
|
11692
12164
|
}
|
|
11693
12165
|
}
|
|
11694
12166
|
}
|
|
@@ -11696,7 +12168,7 @@ async function fixCommand(opts = {}) {
|
|
|
11696
12168
|
const userTsxFiles = allTsxFiles.filter((f) => !f.includes("/design-system/"));
|
|
11697
12169
|
let syntaxFixed = 0;
|
|
11698
12170
|
for (const file of userTsxFiles) {
|
|
11699
|
-
const content =
|
|
12171
|
+
const content = readFileSync15(file, "utf-8");
|
|
11700
12172
|
const fixed = fixUnescapedLtInJsx(
|
|
11701
12173
|
fixEscapedClosingQuotes(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)))
|
|
11702
12174
|
);
|
|
@@ -11708,13 +12180,13 @@ async function fixCommand(opts = {}) {
|
|
|
11708
12180
|
if (syntaxFixed > 0) {
|
|
11709
12181
|
const verb = dryRun ? "Would fix" : "Fixed";
|
|
11710
12182
|
fixes.push(`${verb} syntax in ${syntaxFixed} file(s)`);
|
|
11711
|
-
console.log(
|
|
12183
|
+
console.log(chalk19.green(` \u2714 ${verb} syntax: ${syntaxFixed} file(s) (use client, metadata, quotes)`));
|
|
11712
12184
|
}
|
|
11713
12185
|
if (!skipQuality) {
|
|
11714
12186
|
let qualityFixCount = 0;
|
|
11715
12187
|
const qualityFixDetails = [];
|
|
11716
12188
|
for (const file of userTsxFiles) {
|
|
11717
|
-
const content =
|
|
12189
|
+
const content = readFileSync15(file, "utf-8");
|
|
11718
12190
|
const { code: autoFixed, fixes: fileFixes } = await autoFixCode(content);
|
|
11719
12191
|
if (autoFixed !== content) {
|
|
11720
12192
|
if (!dryRun) writeFileSync11(file, autoFixed, "utf-8");
|
|
@@ -11726,14 +12198,14 @@ async function fixCommand(opts = {}) {
|
|
|
11726
12198
|
const uniqueFixes = [...new Set(qualityFixDetails)];
|
|
11727
12199
|
const verb = dryRun ? "Would fix" : "Fixed";
|
|
11728
12200
|
fixes.push(`${verb} quality in ${qualityFixCount} file(s)`);
|
|
11729
|
-
console.log(
|
|
12201
|
+
console.log(chalk19.green(` \u2714 ${verb} ${uniqueFixes.length} quality issue type(s): ${uniqueFixes.join(", ")}`));
|
|
11730
12202
|
}
|
|
11731
12203
|
}
|
|
11732
12204
|
let totalErrors = 0;
|
|
11733
12205
|
let totalWarnings = 0;
|
|
11734
12206
|
const fileIssues = [];
|
|
11735
12207
|
for (const file of allTsxFiles) {
|
|
11736
|
-
const code = dryRun ?
|
|
12208
|
+
const code = dryRun ? readFileSync15(file, "utf-8") : readFileSync15(file, "utf-8");
|
|
11737
12209
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11738
12210
|
const baseName = file.split("/").pop() || "";
|
|
11739
12211
|
const isAuthPage = relativePath.includes("(auth)");
|
|
@@ -11771,13 +12243,13 @@ async function fixCommand(opts = {}) {
|
|
|
11771
12243
|
if (dryRun) {
|
|
11772
12244
|
fixes.push(`Would update ${o.id} path to ${newPath}`);
|
|
11773
12245
|
} else {
|
|
11774
|
-
console.log(
|
|
12246
|
+
console.log(chalk19.green(` \u2714 Updated ${o.id} (${o.name}) path \u2192 ${newPath}`));
|
|
11775
12247
|
}
|
|
11776
12248
|
} else {
|
|
11777
12249
|
if (dryRun) {
|
|
11778
12250
|
fixes.push(`Would remove orphaned ${o.id} (${o.name})`);
|
|
11779
12251
|
} else {
|
|
11780
|
-
console.log(
|
|
12252
|
+
console.log(chalk19.green(` \u2714 Removed orphaned ${o.id} (${o.name}) \u2014 file missing`));
|
|
11781
12253
|
}
|
|
11782
12254
|
}
|
|
11783
12255
|
}
|
|
@@ -11790,7 +12262,7 @@ async function fixCommand(opts = {}) {
|
|
|
11790
12262
|
entry.usedIn = fullActual;
|
|
11791
12263
|
manifestModified = true;
|
|
11792
12264
|
if (!dryRun) {
|
|
11793
|
-
console.log(
|
|
12265
|
+
console.log(chalk19.green(` \u2714 Updated ${entry.id} usedIn: ${fullActual.join(", ") || "none"}`));
|
|
11794
12266
|
}
|
|
11795
12267
|
}
|
|
11796
12268
|
}
|
|
@@ -11808,7 +12280,7 @@ async function fixCommand(opts = {}) {
|
|
|
11808
12280
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11809
12281
|
});
|
|
11810
12282
|
manifest.nextId++;
|
|
11811
|
-
console.log(
|
|
12283
|
+
console.log(chalk19.green(` \u2714 Registered ${id} (${comp.name}) from ${comp.file}`));
|
|
11812
12284
|
} else {
|
|
11813
12285
|
fixes.push(`Would register ${comp.name} from ${comp.file}`);
|
|
11814
12286
|
}
|
|
@@ -11828,41 +12300,41 @@ async function fixCommand(opts = {}) {
|
|
|
11828
12300
|
} catch {
|
|
11829
12301
|
}
|
|
11830
12302
|
if (fixes.length === 0 && totalErrors === 0 && totalWarnings === 0 && remaining.length === 0) {
|
|
11831
|
-
console.log(
|
|
11832
|
-
console.log(
|
|
12303
|
+
console.log(chalk19.green("\n \u2705 Everything looks good \u2014 no issues found\n"));
|
|
12304
|
+
console.log(chalk19.cyan(" Run: coherent preview\n"));
|
|
11833
12305
|
return;
|
|
11834
12306
|
}
|
|
11835
12307
|
if (fixes.length > 0) console.log("");
|
|
11836
12308
|
if (totalErrors > 0 || totalWarnings > 0 || remaining.length > 0) {
|
|
11837
|
-
console.log(
|
|
11838
|
-
console.log(
|
|
12309
|
+
console.log(chalk19.dim(" \u2500".repeat(25)));
|
|
12310
|
+
console.log(chalk19.yellow(`
|
|
11839
12311
|
Remaining (need manual fix or AI):`));
|
|
11840
12312
|
for (const { path: path4, report } of fileIssues) {
|
|
11841
|
-
console.log(
|
|
12313
|
+
console.log(chalk19.dim(` \u{1F4C4} ${path4}`));
|
|
11842
12314
|
console.log(report);
|
|
11843
12315
|
}
|
|
11844
12316
|
for (const r of remaining) {
|
|
11845
|
-
console.log(
|
|
12317
|
+
console.log(chalk19.yellow(` \u26A0 ${r}`));
|
|
11846
12318
|
}
|
|
11847
12319
|
console.log("");
|
|
11848
12320
|
const parts = [];
|
|
11849
|
-
if (totalErrors > 0) parts.push(
|
|
11850
|
-
if (totalWarnings > 0) parts.push(
|
|
12321
|
+
if (totalErrors > 0) parts.push(chalk19.red(`\u274C ${totalErrors} error(s)`));
|
|
12322
|
+
if (totalWarnings > 0) parts.push(chalk19.yellow(`\u26A0 ${totalWarnings} warning(s)`));
|
|
11851
12323
|
if (parts.length > 0) console.log(` ${parts.join(" ")}`);
|
|
11852
12324
|
}
|
|
11853
|
-
console.log(
|
|
12325
|
+
console.log(chalk19.cyan("\n Run: coherent preview\n"));
|
|
11854
12326
|
}
|
|
11855
12327
|
|
|
11856
12328
|
// src/commands/check.ts
|
|
11857
|
-
import
|
|
12329
|
+
import chalk20 from "chalk";
|
|
11858
12330
|
import { resolve as resolve13 } from "path";
|
|
11859
|
-
import { readdirSync as
|
|
12331
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync16, statSync as statSync2, existsSync as existsSync22 } from "fs";
|
|
11860
12332
|
import { loadManifest as loadManifest11 } from "@getcoherent/core";
|
|
11861
12333
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", "design-system"]);
|
|
11862
12334
|
function findTsxFiles(dir) {
|
|
11863
12335
|
const results = [];
|
|
11864
12336
|
try {
|
|
11865
|
-
const entries =
|
|
12337
|
+
const entries = readdirSync8(dir);
|
|
11866
12338
|
for (const entry of entries) {
|
|
11867
12339
|
const full = resolve13(dir, entry);
|
|
11868
12340
|
const stat = statSync2(full);
|
|
@@ -11902,7 +12374,7 @@ async function checkCommand(opts = {}) {
|
|
|
11902
12374
|
const appDir = resolve13(projectRoot, "app");
|
|
11903
12375
|
const files = findTsxFiles(appDir);
|
|
11904
12376
|
result.pages.total = files.length;
|
|
11905
|
-
if (!opts.json) console.log(
|
|
12377
|
+
if (!opts.json) console.log(chalk20.cyan("\n \u{1F4C4} Pages") + chalk20.dim(` (${files.length} scanned)
|
|
11906
12378
|
`));
|
|
11907
12379
|
const autoFixableTypes = /* @__PURE__ */ new Set([
|
|
11908
12380
|
"RAW_COLOR",
|
|
@@ -11913,7 +12385,7 @@ async function checkCommand(opts = {}) {
|
|
|
11913
12385
|
"NATIVE_TABLE"
|
|
11914
12386
|
]);
|
|
11915
12387
|
for (const file of files) {
|
|
11916
|
-
const code =
|
|
12388
|
+
const code = readFileSync16(file, "utf-8");
|
|
11917
12389
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11918
12390
|
const baseName = file.split("/").pop() || "";
|
|
11919
12391
|
const isAuthPage = relativePath.includes("(auth)");
|
|
@@ -11932,7 +12404,7 @@ async function checkCommand(opts = {}) {
|
|
|
11932
12404
|
result.autoFixable += fileAutoFixable;
|
|
11933
12405
|
if (filteredIssues.length === 0) {
|
|
11934
12406
|
result.pages.clean++;
|
|
11935
|
-
if (!opts.json) console.log(
|
|
12407
|
+
if (!opts.json) console.log(chalk20.green(` \u2714 ${relativePath}`) + chalk20.dim(" \u2014 clean"));
|
|
11936
12408
|
continue;
|
|
11937
12409
|
}
|
|
11938
12410
|
if (errors > 0) result.pages.withErrors++;
|
|
@@ -11945,9 +12417,9 @@ async function checkCommand(opts = {}) {
|
|
|
11945
12417
|
});
|
|
11946
12418
|
if (!opts.json) {
|
|
11947
12419
|
const parts = [];
|
|
11948
|
-
if (errors > 0) parts.push(
|
|
11949
|
-
if (warnings > 0) parts.push(
|
|
11950
|
-
console.log(
|
|
12420
|
+
if (errors > 0) parts.push(chalk20.red(`${errors} error(s)`));
|
|
12421
|
+
if (warnings > 0) parts.push(chalk20.yellow(`${warnings} warning(s)`));
|
|
12422
|
+
console.log(chalk20.yellow(` \u26A0 ${relativePath}`) + chalk20.dim(` \u2014 ${parts.join(", ")}`));
|
|
11951
12423
|
console.log(formatIssues(filteredIssues));
|
|
11952
12424
|
}
|
|
11953
12425
|
}
|
|
@@ -11955,7 +12427,7 @@ async function checkCommand(opts = {}) {
|
|
|
11955
12427
|
routeSet.add("/");
|
|
11956
12428
|
routeSet.add("#");
|
|
11957
12429
|
for (const file of files) {
|
|
11958
|
-
const code =
|
|
12430
|
+
const code = readFileSync16(file, "utf-8");
|
|
11959
12431
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11960
12432
|
const lines = code.split("\n");
|
|
11961
12433
|
const linkHrefRe = /href\s*=\s*["'](\/[a-z0-9/-]*)["']/gi;
|
|
@@ -11973,15 +12445,15 @@ async function checkCommand(opts = {}) {
|
|
|
11973
12445
|
}
|
|
11974
12446
|
}
|
|
11975
12447
|
if (!opts.json && result.links.broken.length > 0) {
|
|
11976
|
-
console.log(
|
|
11977
|
-
\u{1F517} Internal Links`) +
|
|
12448
|
+
console.log(chalk20.yellow(`
|
|
12449
|
+
\u{1F517} Internal Links`) + chalk20.dim(` (${result.links.total} scanned)
|
|
11978
12450
|
`));
|
|
11979
12451
|
for (const b of result.links.broken) {
|
|
11980
|
-
console.log(
|
|
12452
|
+
console.log(chalk20.red(` \u2717 ${b.file}:${b.line}`) + chalk20.dim(` \u2192 ${b.href} (route does not exist)`));
|
|
11981
12453
|
}
|
|
11982
12454
|
} else if (!opts.json && result.links.total > 0) {
|
|
11983
|
-
console.log(
|
|
11984
|
-
\u{1F517} Internal Links`) +
|
|
12455
|
+
console.log(chalk20.green(`
|
|
12456
|
+
\u{1F517} Internal Links`) + chalk20.dim(` \u2014 all ${result.links.total} links resolve \u2713`));
|
|
11985
12457
|
}
|
|
11986
12458
|
try {
|
|
11987
12459
|
const manifest = await loadManifest11(project.root);
|
|
@@ -11990,7 +12462,7 @@ async function checkCommand(opts = {}) {
|
|
|
11990
12462
|
const fullPath = resolve13(project.root, entry.file);
|
|
11991
12463
|
if (!existsSync22(fullPath)) {
|
|
11992
12464
|
result.pages.withErrors++;
|
|
11993
|
-
if (!opts.json) console.log(
|
|
12465
|
+
if (!opts.json) console.log(chalk20.red(`
|
|
11994
12466
|
\u2717 Missing shared component file: ${entry.id} (${entry.file})`));
|
|
11995
12467
|
}
|
|
11996
12468
|
}
|
|
@@ -12002,8 +12474,8 @@ async function checkCommand(opts = {}) {
|
|
|
12002
12474
|
try {
|
|
12003
12475
|
const manifest = await loadManifest11(projectRoot);
|
|
12004
12476
|
if (!opts.json && manifest.shared.length > 0) {
|
|
12005
|
-
console.log(
|
|
12006
|
-
\u{1F9E9} Shared Components`) +
|
|
12477
|
+
console.log(chalk20.cyan(`
|
|
12478
|
+
\u{1F9E9} Shared Components`) + chalk20.dim(` (${manifest.shared.length} registered)
|
|
12007
12479
|
`));
|
|
12008
12480
|
}
|
|
12009
12481
|
let consistent = 0;
|
|
@@ -12017,23 +12489,23 @@ async function checkCommand(opts = {}) {
|
|
|
12017
12489
|
if (!fileExists) {
|
|
12018
12490
|
_orphaned++;
|
|
12019
12491
|
if (!opts.json) {
|
|
12020
|
-
console.log(
|
|
12021
|
-
console.log(
|
|
12492
|
+
console.log(chalk20.red(` \u274C ${entry.id} (${entry.name}) \u2014 file missing: ${entry.file}`));
|
|
12493
|
+
console.log(chalk20.dim(` Fix: coherent fix or coherent sync`));
|
|
12022
12494
|
}
|
|
12023
12495
|
continue;
|
|
12024
12496
|
}
|
|
12025
12497
|
try {
|
|
12026
|
-
const code =
|
|
12498
|
+
const code = readFileSync16(filePath, "utf-8");
|
|
12027
12499
|
const actualExports = extractExportedComponentNames(code);
|
|
12028
12500
|
if (actualExports.length > 0 && !actualExports.includes(entry.name)) {
|
|
12029
12501
|
_nameMismatch++;
|
|
12030
12502
|
if (!opts.json) {
|
|
12031
12503
|
console.log(
|
|
12032
|
-
|
|
12504
|
+
chalk20.yellow(
|
|
12033
12505
|
` \u26A0 ${entry.id} \u2014 manifest name "${entry.name}" doesn't match export "${actualExports[0]}"`
|
|
12034
12506
|
)
|
|
12035
12507
|
);
|
|
12036
|
-
console.log(
|
|
12508
|
+
console.log(chalk20.dim(` Fix: coherent sync`));
|
|
12037
12509
|
}
|
|
12038
12510
|
}
|
|
12039
12511
|
} catch {
|
|
@@ -12048,35 +12520,35 @@ async function checkCommand(opts = {}) {
|
|
|
12048
12520
|
if (totalUsage === 0) {
|
|
12049
12521
|
unused++;
|
|
12050
12522
|
if (!opts.json) {
|
|
12051
|
-
console.log(
|
|
12052
|
-
console.log(
|
|
12523
|
+
console.log(chalk20.blue(` \u2139 ${entry.id} (${entry.name}) \u2014 registered but not used anywhere`));
|
|
12524
|
+
console.log(chalk20.dim(` Remove: coherent components shared remove ${entry.id}`));
|
|
12053
12525
|
}
|
|
12054
12526
|
} else {
|
|
12055
12527
|
consistent++;
|
|
12056
12528
|
const usageDesc = inLayout ? `layout + ${actualUsedIn.length} page(s)` : `${actualUsedIn.length} page(s)`;
|
|
12057
12529
|
if (!opts.json) {
|
|
12058
|
-
const staleNote = isStale ?
|
|
12059
|
-
console.log(
|
|
12530
|
+
const staleNote = isStale ? chalk20.yellow(" [usedIn stale]") : "";
|
|
12531
|
+
console.log(chalk20.green(` \u2714 ${entry.id} (${entry.name})`) + chalk20.dim(` \u2014 ${usageDesc}`) + staleNote);
|
|
12060
12532
|
}
|
|
12061
12533
|
}
|
|
12062
12534
|
}
|
|
12063
12535
|
const unregistered = findUnregisteredComponents(projectRoot, manifest);
|
|
12064
12536
|
if (unregistered.length > 0 && !opts.json) {
|
|
12065
|
-
console.log(
|
|
12537
|
+
console.log(chalk20.cyan(`
|
|
12066
12538
|
\u{1F4E6} Unregistered components found:`));
|
|
12067
12539
|
for (const comp of unregistered) {
|
|
12068
|
-
console.log(
|
|
12069
|
-
console.log(
|
|
12540
|
+
console.log(chalk20.blue(` \u2139 ${comp.name}`) + chalk20.dim(` \u2014 ${comp.file} (not in manifest)`));
|
|
12541
|
+
console.log(chalk20.dim(` Register: coherent sync`));
|
|
12070
12542
|
}
|
|
12071
12543
|
}
|
|
12072
12544
|
const inlineDupes = findInlineDuplicates(projectRoot, manifest);
|
|
12073
12545
|
if (inlineDupes.length > 0 && !opts.json) {
|
|
12074
|
-
console.log(
|
|
12546
|
+
console.log(chalk20.cyan(`
|
|
12075
12547
|
\u{1F50D} Inline duplicates:`));
|
|
12076
12548
|
for (const dup of inlineDupes) {
|
|
12077
|
-
console.log(
|
|
12549
|
+
console.log(chalk20.yellow(` \u26A0 ${dup.pageFile}`) + chalk20.dim(` has inline ${dup.componentName}`));
|
|
12078
12550
|
console.log(
|
|
12079
|
-
|
|
12551
|
+
chalk20.dim(
|
|
12080
12552
|
` Use shared: import { ${dup.componentName} } from "@/${dup.sharedFile.replace(".tsx", "")}"`
|
|
12081
12553
|
)
|
|
12082
12554
|
);
|
|
@@ -12103,24 +12575,24 @@ async function checkCommand(opts = {}) {
|
|
|
12103
12575
|
console.log(JSON.stringify(result, null, 2));
|
|
12104
12576
|
return;
|
|
12105
12577
|
}
|
|
12106
|
-
console.log(
|
|
12578
|
+
console.log(chalk20.dim("\n " + "\u2500".repeat(50)));
|
|
12107
12579
|
const summaryParts = [];
|
|
12108
12580
|
if (!skipPages) {
|
|
12109
|
-
summaryParts.push(`${
|
|
12110
|
-
if (result.pages.withErrors > 0) summaryParts.push(
|
|
12111
|
-
if (result.pages.withWarnings > 0) summaryParts.push(
|
|
12581
|
+
summaryParts.push(`${chalk20.green(`${result.pages.clean} clean`)} pages`);
|
|
12582
|
+
if (result.pages.withErrors > 0) summaryParts.push(chalk20.red(`${result.pages.withErrors} with errors`));
|
|
12583
|
+
if (result.pages.withWarnings > 0) summaryParts.push(chalk20.yellow(`${result.pages.withWarnings} with warnings`));
|
|
12112
12584
|
}
|
|
12113
12585
|
if (!skipShared && result.shared.total > 0) {
|
|
12114
12586
|
summaryParts.push(`${result.shared.consistent} healthy shared`);
|
|
12115
12587
|
if (result.shared.unused > 0) summaryParts.push(`${result.shared.unused} unused`);
|
|
12116
12588
|
}
|
|
12117
12589
|
if (result.links.broken.length > 0) {
|
|
12118
|
-
summaryParts.push(
|
|
12590
|
+
summaryParts.push(chalk20.red(`${result.links.broken.length} broken link(s)`));
|
|
12119
12591
|
}
|
|
12120
12592
|
console.log(`
|
|
12121
12593
|
${summaryParts.join(" | ")}`);
|
|
12122
12594
|
if (result.autoFixable > 0) {
|
|
12123
|
-
console.log(
|
|
12595
|
+
console.log(chalk20.cyan(`
|
|
12124
12596
|
Auto-fixable: ${result.autoFixable} issues. Run: coherent fix`));
|
|
12125
12597
|
}
|
|
12126
12598
|
console.log("");
|
|
@@ -12129,21 +12601,21 @@ async function checkCommand(opts = {}) {
|
|
|
12129
12601
|
}
|
|
12130
12602
|
|
|
12131
12603
|
// src/commands/repair.ts
|
|
12132
|
-
import
|
|
12604
|
+
import chalk21 from "chalk";
|
|
12133
12605
|
async function repairCommand() {
|
|
12134
|
-
console.log(
|
|
12606
|
+
console.log(chalk21.dim(" \u2139\uFE0F `coherent repair` is deprecated \u2014 use `coherent fix` instead\n"));
|
|
12135
12607
|
await fixCommand();
|
|
12136
12608
|
}
|
|
12137
12609
|
|
|
12138
12610
|
// src/commands/doctor.ts
|
|
12139
|
-
import
|
|
12611
|
+
import chalk22 from "chalk";
|
|
12140
12612
|
async function doctorCommand() {
|
|
12141
|
-
console.log(
|
|
12613
|
+
console.log(chalk22.dim(" \u2139\uFE0F `coherent doctor` is deprecated \u2014 use `coherent fix` instead\n"));
|
|
12142
12614
|
await fixCommand();
|
|
12143
12615
|
}
|
|
12144
12616
|
|
|
12145
12617
|
// src/commands/rules.ts
|
|
12146
|
-
import
|
|
12618
|
+
import chalk23 from "chalk";
|
|
12147
12619
|
async function rulesCommand() {
|
|
12148
12620
|
try {
|
|
12149
12621
|
const result = await regenerateCursorRules();
|
|
@@ -12154,31 +12626,31 @@ async function rulesCommand() {
|
|
|
12154
12626
|
if (result.sharedCount !== void 0) parts.push(`${result.sharedCount} shared components`);
|
|
12155
12627
|
if (result.tokenKeys !== void 0) parts.push(`${result.tokenKeys} design token keys`);
|
|
12156
12628
|
const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
12157
|
-
console.log(
|
|
12629
|
+
console.log(chalk23.green(`\u2714 Updated .cursorrules and CLAUDE.md${summary}
|
|
12158
12630
|
`));
|
|
12159
12631
|
} catch (error) {
|
|
12160
|
-
console.error(
|
|
12632
|
+
console.error(chalk23.red("\u274C Command failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
12161
12633
|
process.exit(1);
|
|
12162
12634
|
}
|
|
12163
12635
|
}
|
|
12164
12636
|
|
|
12165
12637
|
// src/commands/validate.ts
|
|
12166
|
-
import
|
|
12638
|
+
import chalk24 from "chalk";
|
|
12167
12639
|
async function validateCommand() {
|
|
12168
|
-
console.log(
|
|
12640
|
+
console.log(chalk24.dim(" \u2139\uFE0F `coherent validate` is deprecated \u2014 use `coherent check` instead\n"));
|
|
12169
12641
|
await checkCommand({ pages: true });
|
|
12170
12642
|
}
|
|
12171
12643
|
|
|
12172
12644
|
// src/commands/audit.ts
|
|
12173
|
-
import
|
|
12645
|
+
import chalk25 from "chalk";
|
|
12174
12646
|
async function auditCommand(options) {
|
|
12175
|
-
console.log(
|
|
12647
|
+
console.log(chalk25.dim(" \u2139\uFE0F `coherent audit` is deprecated \u2014 use `coherent check` instead\n"));
|
|
12176
12648
|
await checkCommand({ shared: true, json: options.json });
|
|
12177
12649
|
}
|
|
12178
12650
|
|
|
12179
12651
|
// src/commands/components.ts
|
|
12180
12652
|
import { Command } from "commander";
|
|
12181
|
-
import
|
|
12653
|
+
import chalk26 from "chalk";
|
|
12182
12654
|
import {
|
|
12183
12655
|
DesignSystemManager as DesignSystemManager12,
|
|
12184
12656
|
ComponentManager as ComponentManager7,
|
|
@@ -12229,9 +12701,9 @@ function createComponentsCommand() {
|
|
|
12229
12701
|
console.log(JSON.stringify({ shared: manifest.shared, ui: installed2 }, null, 2));
|
|
12230
12702
|
return;
|
|
12231
12703
|
}
|
|
12232
|
-
console.log(
|
|
12704
|
+
console.log(chalk26.bold("\n\u{1F4E6} Shared Components"));
|
|
12233
12705
|
if (manifest.shared.length === 0) {
|
|
12234
|
-
console.log(
|
|
12706
|
+
console.log(chalk26.gray(" None yet. Generate pages with header/footer to create them.\n"));
|
|
12235
12707
|
} else {
|
|
12236
12708
|
const order = { layout: 0, section: 1, widget: 2 };
|
|
12237
12709
|
const sorted = [...manifest.shared].sort(
|
|
@@ -12239,9 +12711,9 @@ function createComponentsCommand() {
|
|
|
12239
12711
|
);
|
|
12240
12712
|
console.log("");
|
|
12241
12713
|
sorted.forEach((entry) => {
|
|
12242
|
-
const usage = entry.usedIn.length === 0 ?
|
|
12714
|
+
const usage = entry.usedIn.length === 0 ? chalk26.gray("unused") : entry.usedIn.includes("app/layout.tsx") ? chalk26.green("all pages") : chalk26.gray(entry.usedIn.join(", "));
|
|
12243
12715
|
console.log(
|
|
12244
|
-
` ${
|
|
12716
|
+
` ${chalk26.cyan(entry.id.padEnd(8))} ${chalk26.white(entry.name.padEnd(18))} ${chalk26.gray(entry.type.padEnd(9))} ${usage}`
|
|
12245
12717
|
);
|
|
12246
12718
|
});
|
|
12247
12719
|
console.log("");
|
|
@@ -12250,24 +12722,24 @@ function createComponentsCommand() {
|
|
|
12250
12722
|
const availableShadcn = listShadcnComponents();
|
|
12251
12723
|
const installedIds = new Set(installed.map((c) => c.id));
|
|
12252
12724
|
const notInstalled = availableShadcn.filter((id) => !installedIds.has(id));
|
|
12253
|
-
console.log(
|
|
12725
|
+
console.log(chalk26.bold("\u{1F9E9} UI Components (shadcn)"));
|
|
12254
12726
|
if (installed.length === 0) {
|
|
12255
|
-
console.log(
|
|
12727
|
+
console.log(chalk26.gray(" None installed yet.\n"));
|
|
12256
12728
|
} else {
|
|
12257
12729
|
const names = installed.map((c) => c.name).sort();
|
|
12258
|
-
console.log(
|
|
12730
|
+
console.log(chalk26.green(` Installed (${names.length}): `) + chalk26.white(names.join(", ")));
|
|
12259
12731
|
}
|
|
12260
12732
|
if (notInstalled.length > 0) {
|
|
12261
|
-
console.log(
|
|
12733
|
+
console.log(chalk26.gray(` Available (${notInstalled.length}): `) + chalk26.gray(notInstalled.join(", ")));
|
|
12262
12734
|
}
|
|
12263
12735
|
console.log("");
|
|
12264
|
-
console.log(
|
|
12265
|
-
console.log(
|
|
12266
|
-
console.log(
|
|
12736
|
+
console.log(chalk26.cyan("\u{1F4A1} Commands:"));
|
|
12737
|
+
console.log(chalk26.white(' coherent chat "add a testimonial component"'));
|
|
12738
|
+
console.log(chalk26.white(' coherent chat --component "Header" "add a search button"'));
|
|
12267
12739
|
console.log("");
|
|
12268
12740
|
});
|
|
12269
12741
|
cmd.command("add <name>").description("Install a specific component").action(async (name) => {
|
|
12270
|
-
console.log(
|
|
12742
|
+
console.log(chalk26.yellow(`
|
|
12271
12743
|
\u{1F4A1} Use: coherent chat "add ${name} component"
|
|
12272
12744
|
`));
|
|
12273
12745
|
});
|
|
@@ -12280,25 +12752,25 @@ function createComponentsCommand() {
|
|
|
12280
12752
|
console.log(JSON.stringify(manifest, null, 2));
|
|
12281
12753
|
return;
|
|
12282
12754
|
}
|
|
12283
|
-
console.log(
|
|
12755
|
+
console.log(chalk26.bold("\n\u{1F4E6} Shared Components\n"));
|
|
12284
12756
|
if (manifest.shared.length === 0) {
|
|
12285
|
-
console.log(
|
|
12286
|
-
console.log(
|
|
12757
|
+
console.log(chalk26.yellow(" No shared components yet.\n"));
|
|
12758
|
+
console.log(chalk26.gray(' Create via chat: coherent chat "add a page with header and footer"\n'));
|
|
12287
12759
|
return;
|
|
12288
12760
|
}
|
|
12289
12761
|
const order = { layout: 0, section: 1, widget: 2 };
|
|
12290
12762
|
const sorted = [...manifest.shared].sort((a, b) => order[a.type] - order[b.type] || a.name.localeCompare(b.name));
|
|
12291
12763
|
sorted.forEach((entry) => {
|
|
12292
|
-
const usedIn = entry.usedIn.length === 0 ?
|
|
12293
|
-
console.log(
|
|
12764
|
+
const usedIn = entry.usedIn.length === 0 ? chalk26.gray("(not used yet)") : entry.usedIn.length === 1 && entry.usedIn[0] === "app/layout.tsx" ? chalk26.gray("layout.tsx (all pages)") : chalk26.gray(`used in: ${entry.usedIn.join(", ")}`);
|
|
12765
|
+
console.log(chalk26.cyan(` ${entry.id}`), chalk26.white(entry.name), chalk26.gray(entry.type));
|
|
12294
12766
|
if (opts.verbose) {
|
|
12295
|
-
console.log(
|
|
12296
|
-
if (entry.description) console.log(
|
|
12767
|
+
console.log(chalk26.gray(` file: ${entry.file}`));
|
|
12768
|
+
if (entry.description) console.log(chalk26.gray(` ${entry.description}`));
|
|
12297
12769
|
}
|
|
12298
|
-
console.log(
|
|
12770
|
+
console.log(chalk26.gray(` ${usedIn}`));
|
|
12299
12771
|
console.log("");
|
|
12300
12772
|
});
|
|
12301
|
-
console.log(
|
|
12773
|
+
console.log(chalk26.cyan("\u{1F4A1} Modify by ID:"), chalk26.white('coherent chat "in CID-001 add a search button"\n'));
|
|
12302
12774
|
});
|
|
12303
12775
|
sharedCmd.command("add <name>").description("Create a shared component (layout/section/widget) and register in manifest").option("-t, --type <type>", "Type: layout | section | widget", "layout").option("-d, --description <desc>", "Description").action(async (name, opts) => {
|
|
12304
12776
|
const project = findConfig();
|
|
@@ -12310,12 +12782,12 @@ function createComponentsCommand() {
|
|
|
12310
12782
|
description: opts.description,
|
|
12311
12783
|
usedIn: type === "layout" ? ["app/layout.tsx"] : []
|
|
12312
12784
|
});
|
|
12313
|
-
console.log(
|
|
12785
|
+
console.log(chalk26.green(`
|
|
12314
12786
|
\u2705 Created ${result.id} (${result.name}) at ${result.file}
|
|
12315
12787
|
`));
|
|
12316
12788
|
if (type === "layout") {
|
|
12317
12789
|
const updated = await integrateSharedLayoutIntoRootLayout3(project.root);
|
|
12318
|
-
if (updated) console.log(
|
|
12790
|
+
if (updated) console.log(chalk26.cyan(" Updated app/layout.tsx to use shared layout components.\n"));
|
|
12319
12791
|
}
|
|
12320
12792
|
const sharedPagePath = resolve14(project.root, "app/design-system/shared/page.tsx");
|
|
12321
12793
|
if (!existsSync23(sharedPagePath)) {
|
|
@@ -12325,23 +12797,23 @@ function createComponentsCommand() {
|
|
|
12325
12797
|
const config2 = dsm.getConfig();
|
|
12326
12798
|
const written = await writeDesignSystemFiles(project.root, config2, { sharedOnly: true });
|
|
12327
12799
|
if (written.length > 0) {
|
|
12328
|
-
console.log(
|
|
12800
|
+
console.log(chalk26.cyan(" Added Design System shared pages: /design-system/shared\n"));
|
|
12329
12801
|
}
|
|
12330
12802
|
} catch (e) {
|
|
12331
|
-
if (process.env.COHERENT_DEBUG === "1") console.error(
|
|
12803
|
+
if (process.env.COHERENT_DEBUG === "1") console.error(chalk26.dim("DS shared pages write failed:"), e);
|
|
12332
12804
|
}
|
|
12333
12805
|
}
|
|
12334
12806
|
try {
|
|
12335
12807
|
await writeCursorRules(project.root);
|
|
12336
12808
|
} catch (e) {
|
|
12337
|
-
if (process.env.COHERENT_DEBUG === "1") console.error(
|
|
12809
|
+
if (process.env.COHERENT_DEBUG === "1") console.error(chalk26.dim("Could not update .cursorrules:"), e);
|
|
12338
12810
|
}
|
|
12339
12811
|
});
|
|
12340
12812
|
return cmd;
|
|
12341
12813
|
}
|
|
12342
12814
|
|
|
12343
12815
|
// src/commands/import-cmd.ts
|
|
12344
|
-
import
|
|
12816
|
+
import chalk27 from "chalk";
|
|
12345
12817
|
import ora6 from "ora";
|
|
12346
12818
|
import { writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
|
|
12347
12819
|
import { resolve as resolve15, join as join18, dirname as dirname9 } from "path";
|
|
@@ -12446,25 +12918,25 @@ function createImportCommand() {
|
|
|
12446
12918
|
}
|
|
12447
12919
|
async function importFigmaAction(urlOrKey, opts) {
|
|
12448
12920
|
if (typeof urlOrKey !== "string" || !urlOrKey.trim()) {
|
|
12449
|
-
console.error(
|
|
12450
|
-
console.log(
|
|
12921
|
+
console.error(chalk27.red("\n\u274C Figma URL or file key is required.\n"));
|
|
12922
|
+
console.log(chalk27.dim(" Usage: coherent import figma <url-or-key> --token <your-token>\n"));
|
|
12451
12923
|
process.exit(1);
|
|
12452
12924
|
}
|
|
12453
12925
|
const token = opts.token ?? process.env.FIGMA_ACCESS_TOKEN ?? process.env.FIGMA_TOKEN;
|
|
12454
12926
|
if (!token || typeof token !== "string") {
|
|
12455
|
-
console.error(
|
|
12456
|
-
console.log(
|
|
12457
|
-
console.log(
|
|
12458
|
-
console.log(
|
|
12927
|
+
console.error(chalk27.red("\n\u274C Figma token required.\n"));
|
|
12928
|
+
console.log(chalk27.dim(" Use: coherent import figma <url-or-key> --token <your-token>"));
|
|
12929
|
+
console.log(chalk27.dim(" Or set FIGMA_ACCESS_TOKEN or FIGMA_TOKEN in your environment.\n"));
|
|
12930
|
+
console.log(chalk27.dim(" Get a token: Figma \u2192 Settings \u2192 Personal access tokens.\n"));
|
|
12459
12931
|
process.exit(1);
|
|
12460
12932
|
}
|
|
12461
12933
|
const generatePages = opts.pages !== false;
|
|
12462
12934
|
const dryRun = Boolean(opts.dryRun);
|
|
12463
12935
|
const fileKey = FigmaClient.extractFileKey(urlOrKey);
|
|
12464
12936
|
if (!fileKey) {
|
|
12465
|
-
console.error(
|
|
12466
|
-
console.log(
|
|
12467
|
-
console.log(
|
|
12937
|
+
console.error(chalk27.red("\n\u274C Invalid Figma URL or file key.\n"));
|
|
12938
|
+
console.log(chalk27.dim(" Use a URL like: https://www.figma.com/file/ABC123/MyDesign"));
|
|
12939
|
+
console.log(chalk27.dim(" Or the file key: ABC123\n"));
|
|
12468
12940
|
process.exit(1);
|
|
12469
12941
|
}
|
|
12470
12942
|
const project = findConfig();
|
|
@@ -12641,7 +13113,7 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
|
|
|
12641
13113
|
try {
|
|
12642
13114
|
await writeCursorRules(projectRoot);
|
|
12643
13115
|
} catch (e) {
|
|
12644
|
-
if (process.env.COHERENT_DEBUG === "1") console.error(
|
|
13116
|
+
if (process.env.COHERENT_DEBUG === "1") console.error(chalk27.dim("Could not update .cursorrules:"), e);
|
|
12645
13117
|
}
|
|
12646
13118
|
} else {
|
|
12647
13119
|
stats.filesWritten.push(DESIGN_SYSTEM_CONFIG_PATH);
|
|
@@ -12651,7 +13123,7 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
|
|
|
12651
13123
|
} catch (err) {
|
|
12652
13124
|
spinner.fail("Import failed");
|
|
12653
13125
|
const message = err instanceof Error ? err.message : String(err);
|
|
12654
|
-
console.error(
|
|
13126
|
+
console.error(chalk27.red("\n\u274C " + message + "\n"));
|
|
12655
13127
|
process.exit(1);
|
|
12656
13128
|
}
|
|
12657
13129
|
}
|
|
@@ -12659,36 +13131,36 @@ function printReport(stats, opts) {
|
|
|
12659
13131
|
const { dryRun, generatePages, fileName } = opts;
|
|
12660
13132
|
console.log("");
|
|
12661
13133
|
if (dryRun) {
|
|
12662
|
-
console.log(
|
|
13134
|
+
console.log(chalk27.yellow("\u2550\u2550\u2550 Dry run (no files written) \u2550\u2550\u2550"));
|
|
12663
13135
|
console.log("");
|
|
12664
13136
|
}
|
|
12665
|
-
console.log(
|
|
13137
|
+
console.log(chalk27.green("\u2705 Figma import complete"));
|
|
12666
13138
|
console.log("");
|
|
12667
|
-
console.log(
|
|
12668
|
-
console.log(
|
|
12669
|
-
console.log(
|
|
12670
|
-
console.log(
|
|
12671
|
-
console.log(
|
|
13139
|
+
console.log(chalk27.cyan(" Statistics"));
|
|
13140
|
+
console.log(chalk27.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
13141
|
+
console.log(chalk27.blue(` File: ${fileName}`));
|
|
13142
|
+
console.log(chalk27.blue(` Color styles: ${stats.colorStyles}`));
|
|
13143
|
+
console.log(chalk27.blue(` Text styles: ${stats.textStyles}`));
|
|
12672
13144
|
console.log(
|
|
12673
|
-
|
|
13145
|
+
chalk27.blue(` Components: ${stats.componentsTotal} (${stats.baseCount} \u2192 base, ${stats.sharedCount} \u2192 shared)`)
|
|
12674
13146
|
);
|
|
12675
|
-
console.log(
|
|
12676
|
-
console.log(
|
|
12677
|
-
console.log(
|
|
12678
|
-
console.log(
|
|
13147
|
+
console.log(chalk27.blue(` Pages: ${stats.pagesGenerated}${!generatePages ? " (skipped by --no-pages)" : ""}`));
|
|
13148
|
+
console.log(chalk27.blue(` design-system.config: ${stats.configUpdated ? "updated" : "\u2014"}`));
|
|
13149
|
+
console.log(chalk27.blue(` Layout (Header/Footer): ${stats.layoutIntegrated ? "integrated" : "\u2014"}`));
|
|
13150
|
+
console.log(chalk27.blue(` DS viewer files: ${stats.dsFilesWritten}`));
|
|
12679
13151
|
console.log(
|
|
12680
|
-
|
|
13152
|
+
chalk27.blue(` Total files ${dryRun ? "that would be written" : "written"}: ${stats.filesWritten.length}`)
|
|
12681
13153
|
);
|
|
12682
13154
|
console.log("");
|
|
12683
13155
|
if (stats.filesWritten.length > 0 && stats.filesWritten.length <= 30) {
|
|
12684
|
-
console.log(
|
|
12685
|
-
stats.filesWritten.forEach((f) => console.log(
|
|
13156
|
+
console.log(chalk27.dim(" Files:"));
|
|
13157
|
+
stats.filesWritten.forEach((f) => console.log(chalk27.dim(` ${f}`)));
|
|
12686
13158
|
console.log("");
|
|
12687
13159
|
}
|
|
12688
13160
|
}
|
|
12689
13161
|
|
|
12690
13162
|
// src/commands/ds.ts
|
|
12691
|
-
import
|
|
13163
|
+
import chalk28 from "chalk";
|
|
12692
13164
|
import ora7 from "ora";
|
|
12693
13165
|
import { DesignSystemManager as DesignSystemManager14 } from "@getcoherent/core";
|
|
12694
13166
|
async function dsRegenerateCommand() {
|
|
@@ -12703,18 +13175,18 @@ async function dsRegenerateCommand() {
|
|
|
12703
13175
|
const config2 = dsm.getConfig();
|
|
12704
13176
|
const written = await writeDesignSystemFiles(project.root, config2);
|
|
12705
13177
|
spinner.succeed(`Regenerated ${written.length} Design System file(s)`);
|
|
12706
|
-
console.log(
|
|
12707
|
-
console.log(
|
|
13178
|
+
console.log(chalk28.gray(" app/design-system/* and app/api/design-system/*\n"));
|
|
13179
|
+
console.log(chalk28.cyan(" Open /design-system in the app to view.\n"));
|
|
12708
13180
|
} catch (error) {
|
|
12709
|
-
console.error(
|
|
13181
|
+
console.error(chalk28.red("\u274C Command failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
12710
13182
|
process.exit(1);
|
|
12711
13183
|
}
|
|
12712
13184
|
}
|
|
12713
13185
|
|
|
12714
13186
|
// src/commands/update.ts
|
|
12715
|
-
import
|
|
13187
|
+
import chalk29 from "chalk";
|
|
12716
13188
|
import ora8 from "ora";
|
|
12717
|
-
import { readFileSync as
|
|
13189
|
+
import { readFileSync as readFileSync17, existsSync as existsSync25 } from "fs";
|
|
12718
13190
|
import { join as join19 } from "path";
|
|
12719
13191
|
import { DesignSystemManager as DesignSystemManager15, CLI_VERSION as CLI_VERSION4 } from "@getcoherent/core";
|
|
12720
13192
|
|
|
@@ -12764,14 +13236,14 @@ async function updateCommand(opts) {
|
|
|
12764
13236
|
const projectVersion = config2.coherentVersion || "0.0.0";
|
|
12765
13237
|
if (compareSemver(projectVersion, CLI_VERSION4) === 0) {
|
|
12766
13238
|
spinner.succeed("Project is already up to date");
|
|
12767
|
-
console.log(
|
|
13239
|
+
console.log(chalk29.gray(` Version: v${CLI_VERSION4}
|
|
12768
13240
|
`));
|
|
12769
13241
|
return;
|
|
12770
13242
|
}
|
|
12771
13243
|
if (compareSemver(projectVersion, CLI_VERSION4) > 0) {
|
|
12772
13244
|
spinner.warn("Project was created with a newer CLI version");
|
|
12773
|
-
console.log(
|
|
12774
|
-
console.log(
|
|
13245
|
+
console.log(chalk29.yellow(` Project: v${projectVersion} \u2192 CLI: v${CLI_VERSION4}`));
|
|
13246
|
+
console.log(chalk29.yellow(" Update your CLI: npm install -g @getcoherent/cli@latest\n"));
|
|
12775
13247
|
return;
|
|
12776
13248
|
}
|
|
12777
13249
|
const report = {
|
|
@@ -12817,36 +13289,36 @@ async function updateCommand(opts) {
|
|
|
12817
13289
|
}
|
|
12818
13290
|
function printReport2(report) {
|
|
12819
13291
|
const from = report.fromVersion ? `v${report.fromVersion}` : "unknown";
|
|
12820
|
-
console.log(
|
|
13292
|
+
console.log(chalk29.green(`
|
|
12821
13293
|
\u2714 Project updated: ${from} \u2192 v${report.toVersion}
|
|
12822
13294
|
`));
|
|
12823
13295
|
if (report.overlayFiles > 0) {
|
|
12824
|
-
console.log(
|
|
13296
|
+
console.log(chalk29.white(` \u2714 Regenerated platform overlay (${report.overlayFiles} files)`));
|
|
12825
13297
|
}
|
|
12826
13298
|
if (report.migrationsApplied.length > 0) {
|
|
12827
13299
|
for (const desc of report.migrationsApplied) {
|
|
12828
|
-
console.log(
|
|
13300
|
+
console.log(chalk29.white(` \u2714 Migrated config: ${desc}`));
|
|
12829
13301
|
}
|
|
12830
13302
|
}
|
|
12831
13303
|
if (report.rulesUpdated) {
|
|
12832
|
-
console.log(
|
|
13304
|
+
console.log(chalk29.white(" \u2714 Updated .cursorrules and CLAUDE.md"));
|
|
12833
13305
|
}
|
|
12834
13306
|
if (report.missingCssVars.length > 0) {
|
|
12835
13307
|
console.log("");
|
|
12836
|
-
console.log(
|
|
13308
|
+
console.log(chalk29.yellow(" \u26A0 New CSS variables available in globals.css:"));
|
|
12837
13309
|
for (const v of report.missingCssVars.slice(0, 10)) {
|
|
12838
|
-
console.log(
|
|
13310
|
+
console.log(chalk29.gray(` ${v}`));
|
|
12839
13311
|
}
|
|
12840
13312
|
if (report.missingCssVars.length > 10) {
|
|
12841
|
-
console.log(
|
|
13313
|
+
console.log(chalk29.gray(` ... and ${report.missingCssVars.length - 10} more`));
|
|
12842
13314
|
}
|
|
12843
13315
|
console.log("");
|
|
12844
|
-
console.log(
|
|
12845
|
-
console.log(
|
|
13316
|
+
console.log(chalk29.cyan(" To add them automatically:"));
|
|
13317
|
+
console.log(chalk29.white(" coherent update --patch-globals\n"));
|
|
12846
13318
|
}
|
|
12847
13319
|
console.log("");
|
|
12848
|
-
console.log(
|
|
12849
|
-
console.log(
|
|
13320
|
+
console.log(chalk29.dim(" Your pages and components were NOT modified."));
|
|
13321
|
+
console.log(chalk29.dim(" Run `coherent check` to check existing pages against new rules.\n"));
|
|
12850
13322
|
}
|
|
12851
13323
|
var EXPECTED_CSS_VARS = [
|
|
12852
13324
|
"--background",
|
|
@@ -12887,7 +13359,7 @@ function checkMissingCssVars(projectRoot) {
|
|
|
12887
13359
|
const globalsPath = join19(projectRoot, "app", "globals.css");
|
|
12888
13360
|
if (!existsSync25(globalsPath)) return [];
|
|
12889
13361
|
try {
|
|
12890
|
-
const content =
|
|
13362
|
+
const content = readFileSync17(globalsPath, "utf-8");
|
|
12891
13363
|
return EXPECTED_CSS_VARS.filter((v) => !content.includes(v));
|
|
12892
13364
|
} catch {
|
|
12893
13365
|
return [];
|
|
@@ -12897,7 +13369,7 @@ function patchGlobalsCss(projectRoot, missingVars) {
|
|
|
12897
13369
|
const globalsPath = join19(projectRoot, "app", "globals.css");
|
|
12898
13370
|
if (!existsSync25(globalsPath) || missingVars.length === 0) return;
|
|
12899
13371
|
const { writeFileSync: writeFileSync14 } = __require("fs");
|
|
12900
|
-
let content =
|
|
13372
|
+
let content = readFileSync17(globalsPath, "utf-8");
|
|
12901
13373
|
const defaultValues = {
|
|
12902
13374
|
"--chart-1": "220 70% 50%",
|
|
12903
13375
|
"--chart-2": "160 60% 45%",
|
|
@@ -12930,7 +13402,7 @@ function patchGlobalsCss(projectRoot, missingVars) {
|
|
|
12930
13402
|
}
|
|
12931
13403
|
|
|
12932
13404
|
// src/commands/undo.ts
|
|
12933
|
-
import
|
|
13405
|
+
import chalk30 from "chalk";
|
|
12934
13406
|
async function undoCommand(options) {
|
|
12935
13407
|
try {
|
|
12936
13408
|
const project = findConfig();
|
|
@@ -12939,43 +13411,43 @@ async function undoCommand(options) {
|
|
|
12939
13411
|
const backups = listBackups(projectRoot);
|
|
12940
13412
|
if (options.list) {
|
|
12941
13413
|
if (backups.length === 0) {
|
|
12942
|
-
console.log(
|
|
13414
|
+
console.log(chalk30.yellow("No backups found."));
|
|
12943
13415
|
return;
|
|
12944
13416
|
}
|
|
12945
|
-
console.log(
|
|
13417
|
+
console.log(chalk30.bold("\n\u{1F4E6} Available backups:\n"));
|
|
12946
13418
|
for (const b of backups) {
|
|
12947
13419
|
const date = new Date(b.timestamp);
|
|
12948
13420
|
const timeStr = date.toLocaleString();
|
|
12949
|
-
console.log(
|
|
12950
|
-
console.log(
|
|
13421
|
+
console.log(chalk30.white(` ${b.name}`));
|
|
13422
|
+
console.log(chalk30.dim(` ${timeStr} \u2014 ${b.files} file(s)`));
|
|
12951
13423
|
console.log();
|
|
12952
13424
|
}
|
|
12953
13425
|
return;
|
|
12954
13426
|
}
|
|
12955
13427
|
if (backups.length === 0) {
|
|
12956
|
-
console.log(
|
|
13428
|
+
console.log(chalk30.yellow("No backups found. Nothing to undo."));
|
|
12957
13429
|
return;
|
|
12958
13430
|
}
|
|
12959
13431
|
const latest = backups[0];
|
|
12960
13432
|
const ok = restoreBackup(projectRoot, latest.name);
|
|
12961
13433
|
if (!ok) {
|
|
12962
|
-
console.log(
|
|
13434
|
+
console.log(chalk30.red("Failed to restore backup."));
|
|
12963
13435
|
return;
|
|
12964
13436
|
}
|
|
12965
|
-
console.log(
|
|
12966
|
-
console.log(
|
|
12967
|
-
console.log(
|
|
12968
|
-
console.log(
|
|
13437
|
+
console.log(chalk30.green("\n\u2705 Restored to previous state:\n"));
|
|
13438
|
+
console.log(chalk30.dim(` Snapshot: ${new Date(latest.timestamp).toLocaleString()}`));
|
|
13439
|
+
console.log(chalk30.dim(` Files: ${latest.files} restored`));
|
|
13440
|
+
console.log(chalk30.cyan("\n Run: coherent preview\n"));
|
|
12969
13441
|
} catch (error) {
|
|
12970
|
-
console.error(
|
|
13442
|
+
console.error(chalk30.red("\u274C Undo failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
12971
13443
|
process.exit(1);
|
|
12972
13444
|
}
|
|
12973
13445
|
}
|
|
12974
13446
|
|
|
12975
13447
|
// src/commands/sync.ts
|
|
12976
|
-
import
|
|
13448
|
+
import chalk31 from "chalk";
|
|
12977
13449
|
import ora9 from "ora";
|
|
12978
|
-
import { existsSync as existsSync26, readFileSync as
|
|
13450
|
+
import { existsSync as existsSync26, readFileSync as readFileSync18 } from "fs";
|
|
12979
13451
|
import { join as join20, relative as relative5, dirname as dirname10 } from "path";
|
|
12980
13452
|
import { readdir as readdir4, readFile as readFile7 } from "fs/promises";
|
|
12981
13453
|
import { DesignSystemManager as DesignSystemManager16 } from "@getcoherent/core";
|
|
@@ -12985,7 +13457,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
12985
13457
|
const darkColors = {};
|
|
12986
13458
|
const globalsPath = join20(projectRoot, "app", "globals.css");
|
|
12987
13459
|
if (existsSync26(globalsPath)) {
|
|
12988
|
-
const css =
|
|
13460
|
+
const css = readFileSync18(globalsPath, "utf-8");
|
|
12989
13461
|
const rootMatch = css.match(/:root\s*\{([^}]+)\}/s);
|
|
12990
13462
|
if (rootMatch) parseVarsInto(rootMatch[1], lightColors);
|
|
12991
13463
|
const darkMatch = css.match(/\.dark\s*\{([^}]+)\}/s);
|
|
@@ -12994,7 +13466,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
12994
13466
|
const layoutPath = join20(projectRoot, "app", "layout.tsx");
|
|
12995
13467
|
let layoutCode = "";
|
|
12996
13468
|
if (existsSync26(layoutPath)) {
|
|
12997
|
-
layoutCode =
|
|
13469
|
+
layoutCode = readFileSync18(layoutPath, "utf-8");
|
|
12998
13470
|
const rootInline = layoutCode.match(/:root\s*\{([^}]+)\}/s);
|
|
12999
13471
|
if (rootInline && Object.keys(lightColors).length === 0) {
|
|
13000
13472
|
parseVarsInto(rootInline[1], lightColors);
|
|
@@ -13012,7 +13484,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
13012
13484
|
defaultMode = "dark";
|
|
13013
13485
|
}
|
|
13014
13486
|
let radius;
|
|
13015
|
-
const allCss = [existsSync26(globalsPath) ?
|
|
13487
|
+
const allCss = [existsSync26(globalsPath) ? readFileSync18(globalsPath, "utf-8") : "", layoutCode].join("\n");
|
|
13016
13488
|
const radiusMatch = allCss.match(/--radius:\s*([^;]+);/);
|
|
13017
13489
|
if (radiusMatch) radius = radiusMatch[1].trim();
|
|
13018
13490
|
return {
|
|
@@ -13211,7 +13683,7 @@ async function syncCommand(options = {}) {
|
|
|
13211
13683
|
const doTokens = runAll || options.tokens === true;
|
|
13212
13684
|
const doComponents = runAll || options.components === true;
|
|
13213
13685
|
const doPatterns = runAll || options.patterns === true;
|
|
13214
|
-
if (dryRun) console.log(
|
|
13686
|
+
if (dryRun) console.log(chalk31.yellow(" [dry-run] No files will be written\n"));
|
|
13215
13687
|
const spinner = ora9("Scanning project files...").start();
|
|
13216
13688
|
try {
|
|
13217
13689
|
const appDir = join20(project.root, "app");
|
|
@@ -13352,84 +13824,84 @@ async function syncCommand(options = {}) {
|
|
|
13352
13824
|
spinner.succeed("Updated .cursorrules and CLAUDE.md");
|
|
13353
13825
|
}
|
|
13354
13826
|
console.log("");
|
|
13355
|
-
console.log(
|
|
13827
|
+
console.log(chalk31.green(`\u2705 Design System ${dryRun ? "analyzed" : "synced"} with actual code
|
|
13356
13828
|
`));
|
|
13357
|
-
console.log(
|
|
13829
|
+
console.log(chalk31.blue("\u{1F4C4} Pages:"));
|
|
13358
13830
|
for (const page of discoveredPages) {
|
|
13359
13831
|
const a = analyzePageCode(page.code);
|
|
13360
13832
|
const comps = Object.entries(a.componentUsage || {}).filter(([, c]) => c > 0).map(([n]) => n);
|
|
13361
|
-
console.log(
|
|
13362
|
-
if (comps.length > 0) console.log(
|
|
13363
|
-
if (a.sections?.length) console.log(
|
|
13833
|
+
console.log(chalk31.gray(` ${page.route} \u2014 ${page.name}`));
|
|
13834
|
+
if (comps.length > 0) console.log(chalk31.gray(` Components: ${comps.join(", ")}`));
|
|
13835
|
+
if (a.sections?.length) console.log(chalk31.gray(` Sections: ${a.sections.map((s) => s.name).join(", ")}`));
|
|
13364
13836
|
}
|
|
13365
13837
|
if (doTokens && extractedTokens) {
|
|
13366
13838
|
console.log("");
|
|
13367
|
-
console.log(
|
|
13839
|
+
console.log(chalk31.blue("\u{1F3A8} Design Tokens (from globals.css):"));
|
|
13368
13840
|
const lc = Object.keys(extractedTokens.colors.light).length;
|
|
13369
13841
|
const dc = Object.keys(extractedTokens.colors.dark).length;
|
|
13370
|
-
console.log(
|
|
13371
|
-
console.log(
|
|
13372
|
-
if (extractedTokens.radius) console.log(
|
|
13842
|
+
console.log(chalk31.gray(` Light: ${lc} variables | Dark: ${dc} variables`));
|
|
13843
|
+
console.log(chalk31.gray(` Default mode: ${extractedTokens.defaultMode}`));
|
|
13844
|
+
if (extractedTokens.radius) console.log(chalk31.gray(` Border radius: ${extractedTokens.radius}`));
|
|
13373
13845
|
}
|
|
13374
13846
|
if (doComponents && reconcileResult) {
|
|
13375
13847
|
console.log("");
|
|
13376
|
-
console.log(
|
|
13848
|
+
console.log(chalk31.blue("\u{1F9E9} Shared Components:"));
|
|
13377
13849
|
for (const r of reconcileResult.removed) {
|
|
13378
|
-
console.log(
|
|
13850
|
+
console.log(chalk31.red(` \u{1F5D1} Removed ${r.id} (${r.name}) \u2014 ${r.reason}`));
|
|
13379
13851
|
}
|
|
13380
13852
|
for (const u of reconcileResult.updated) {
|
|
13381
|
-
console.log(
|
|
13853
|
+
console.log(chalk31.cyan(` \u{1F4DD} Updated ${u.id} ${u.field}: ${u.from} \u2192 ${u.to}`));
|
|
13382
13854
|
}
|
|
13383
13855
|
for (const a of reconcileResult.added) {
|
|
13384
|
-
console.log(
|
|
13856
|
+
console.log(chalk31.green(` \u2728 Added ${a.id} (${a.name}) \u2014 ${a.file} (${a.type})`));
|
|
13385
13857
|
}
|
|
13386
13858
|
for (const w of reconcileResult.warnings) {
|
|
13387
|
-
console.log(
|
|
13388
|
-
console.log(
|
|
13859
|
+
console.log(chalk31.yellow(` \u26A0 ${w.message}`));
|
|
13860
|
+
console.log(chalk31.dim(` ${w.suggestion}`));
|
|
13389
13861
|
}
|
|
13390
13862
|
if (reconcileResult.removed.length === 0 && reconcileResult.updated.length === 0 && reconcileResult.added.length === 0 && reconcileResult.warnings.length === 0) {
|
|
13391
|
-
console.log(
|
|
13863
|
+
console.log(chalk31.gray(" All components consistent \u2713"));
|
|
13392
13864
|
}
|
|
13393
13865
|
}
|
|
13394
13866
|
if (doPatterns && Object.keys(stylePatterns).length > 0) {
|
|
13395
13867
|
console.log("");
|
|
13396
|
-
console.log(
|
|
13397
|
-
if (stylePatterns.card) console.log(
|
|
13398
|
-
if (stylePatterns.section) console.log(
|
|
13399
|
-
if (stylePatterns.terminal) console.log(
|
|
13400
|
-
if (stylePatterns.iconContainer) console.log(
|
|
13868
|
+
console.log(chalk31.blue("\u{1F4D0} Style Patterns:"));
|
|
13869
|
+
if (stylePatterns.card) console.log(chalk31.gray(` Cards: ${stylePatterns.card.slice(0, 80)}`));
|
|
13870
|
+
if (stylePatterns.section) console.log(chalk31.gray(` Sections: ${stylePatterns.section}`));
|
|
13871
|
+
if (stylePatterns.terminal) console.log(chalk31.gray(` Terminal: ${stylePatterns.terminal.slice(0, 80)}`));
|
|
13872
|
+
if (stylePatterns.iconContainer) console.log(chalk31.gray(` Icons: ${stylePatterns.iconContainer.slice(0, 80)}`));
|
|
13401
13873
|
if (stylePatterns.heroHeadline)
|
|
13402
|
-
console.log(
|
|
13874
|
+
console.log(chalk31.gray(` Hero headline: ${stylePatterns.heroHeadline.slice(0, 80)}`));
|
|
13403
13875
|
if (stylePatterns.sectionTitle)
|
|
13404
|
-
console.log(
|
|
13876
|
+
console.log(chalk31.gray(` Section title: ${stylePatterns.sectionTitle.slice(0, 80)}`));
|
|
13405
13877
|
}
|
|
13406
13878
|
const tokenUsage = extractActualTokenUsage(allPageCode);
|
|
13407
13879
|
if (tokenUsage.colors.length > 0) {
|
|
13408
13880
|
console.log("");
|
|
13409
|
-
console.log(
|
|
13881
|
+
console.log(chalk31.blue("\u{1F3F7}\uFE0F Actual token usage (from classNames):"));
|
|
13410
13882
|
console.log(
|
|
13411
|
-
|
|
13883
|
+
chalk31.gray(
|
|
13412
13884
|
` Colors: ${tokenUsage.colors.slice(0, 12).join(", ")}${tokenUsage.colors.length > 12 ? ` (+${tokenUsage.colors.length - 12})` : ""}`
|
|
13413
13885
|
)
|
|
13414
13886
|
);
|
|
13415
13887
|
console.log(
|
|
13416
|
-
|
|
13888
|
+
chalk31.gray(
|
|
13417
13889
|
` Typography: ${tokenUsage.typography.slice(0, 8).join(", ")}${tokenUsage.typography.length > 8 ? ` (+${tokenUsage.typography.length - 8})` : ""}`
|
|
13418
13890
|
)
|
|
13419
13891
|
);
|
|
13420
|
-
console.log(
|
|
13892
|
+
console.log(chalk31.gray(` Radius: ${tokenUsage.borderRadius.join(", ")}`));
|
|
13421
13893
|
}
|
|
13422
13894
|
const reusable = extractReusablePatterns(allPageCode);
|
|
13423
13895
|
if (reusable.length > 0) {
|
|
13424
13896
|
console.log("");
|
|
13425
|
-
console.log(
|
|
13897
|
+
console.log(chalk31.blue(`\u{1F501} Repeating patterns (${reusable.length} \u2014 potential reusable components):`));
|
|
13426
13898
|
for (const p of reusable.slice(0, 5)) {
|
|
13427
|
-
console.log(
|
|
13899
|
+
console.log(chalk31.gray(` \xD7${p.count}: ${p.sample}${p.sample.length < p.pattern.length ? "..." : ""}`));
|
|
13428
13900
|
}
|
|
13429
13901
|
}
|
|
13430
13902
|
console.log("");
|
|
13431
13903
|
if (!dryRun) {
|
|
13432
|
-
console.log(
|
|
13904
|
+
console.log(chalk31.cyan(" Open /design-system in the app to see the updated view."));
|
|
13433
13905
|
}
|
|
13434
13906
|
console.log("");
|
|
13435
13907
|
} catch (err) {
|
|
@@ -13440,9 +13912,9 @@ async function syncCommand(options = {}) {
|
|
|
13440
13912
|
}
|
|
13441
13913
|
|
|
13442
13914
|
// src/commands/migrate.ts
|
|
13443
|
-
import
|
|
13915
|
+
import chalk32 from "chalk";
|
|
13444
13916
|
import ora10 from "ora";
|
|
13445
|
-
import { existsSync as existsSync27, mkdirSync as mkdirSync8, cpSync, rmSync as rmSync6, writeFileSync as writeFileSync12, readFileSync as
|
|
13917
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync8, cpSync, rmSync as rmSync6, writeFileSync as writeFileSync12, readFileSync as readFileSync19, readdirSync as readdirSync9 } from "fs";
|
|
13446
13918
|
import { join as join21 } from "path";
|
|
13447
13919
|
function backupDir(projectRoot) {
|
|
13448
13920
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -13477,7 +13949,7 @@ function rollback(projectRoot) {
|
|
|
13477
13949
|
const guard = guardPath(projectRoot);
|
|
13478
13950
|
if (!existsSync27(guard)) return false;
|
|
13479
13951
|
try {
|
|
13480
|
-
const data = JSON.parse(
|
|
13952
|
+
const data = JSON.parse(readFileSync19(guard, "utf-8"));
|
|
13481
13953
|
const backup = data.backup;
|
|
13482
13954
|
if (!existsSync27(backup)) return false;
|
|
13483
13955
|
const uiBackup = join21(backup, "components-ui");
|
|
@@ -13517,30 +13989,30 @@ async function migrateAction(options) {
|
|
|
13517
13989
|
}
|
|
13518
13990
|
const guard = guardPath(projectRoot);
|
|
13519
13991
|
if (existsSync27(guard)) {
|
|
13520
|
-
console.log(
|
|
13521
|
-
console.log(
|
|
13992
|
+
console.log(chalk32.yellow("A migration is already in progress."));
|
|
13993
|
+
console.log(chalk32.dim("Run `coherent migrate --rollback` to undo, or delete .coherent/migration-in-progress"));
|
|
13522
13994
|
return;
|
|
13523
13995
|
}
|
|
13524
13996
|
const uiDir = join21(projectRoot, "components", "ui");
|
|
13525
13997
|
if (!existsSync27(uiDir)) {
|
|
13526
|
-
console.log(
|
|
13998
|
+
console.log(chalk32.yellow("No components/ui directory found. Nothing to migrate."));
|
|
13527
13999
|
return;
|
|
13528
14000
|
}
|
|
13529
14001
|
const provider = getComponentProvider();
|
|
13530
14002
|
const managedIds = new Set(provider.listNames());
|
|
13531
|
-
const files =
|
|
14003
|
+
const files = readdirSync9(uiDir).filter((f) => f.endsWith(".tsx"));
|
|
13532
14004
|
const migratable = files.map((f) => f.replace(".tsx", "")).filter((id) => managedIds.has(id));
|
|
13533
14005
|
if (migratable.length === 0) {
|
|
13534
|
-
console.log(
|
|
14006
|
+
console.log(chalk32.green("All components are already up to date."));
|
|
13535
14007
|
return;
|
|
13536
14008
|
}
|
|
13537
|
-
console.log(
|
|
14009
|
+
console.log(chalk32.cyan(`
|
|
13538
14010
|
Found ${migratable.length} component(s) to migrate:`));
|
|
13539
14011
|
for (const id of migratable) {
|
|
13540
|
-
console.log(
|
|
14012
|
+
console.log(chalk32.dim(` - ${id}`));
|
|
13541
14013
|
}
|
|
13542
14014
|
if (options.dryRun) {
|
|
13543
|
-
console.log(
|
|
14015
|
+
console.log(chalk32.yellow("\n[dry-run] No changes applied."));
|
|
13544
14016
|
return;
|
|
13545
14017
|
}
|
|
13546
14018
|
const spinner = ora10("Migrating components...").start();
|
|
@@ -13557,12 +14029,12 @@ Found ${migratable.length} component(s) to migrate:`));
|
|
|
13557
14029
|
if (result.success) {
|
|
13558
14030
|
migrated++;
|
|
13559
14031
|
} else {
|
|
13560
|
-
console.warn(
|
|
14032
|
+
console.warn(chalk32.yellow(` \u26A0 Failed to migrate ${id}`));
|
|
13561
14033
|
}
|
|
13562
14034
|
}
|
|
13563
14035
|
clearGuard(projectRoot);
|
|
13564
14036
|
spinner.succeed(`Migrated ${migrated}/${migratable.length} components to real shadcn/ui`);
|
|
13565
|
-
console.log(
|
|
14037
|
+
console.log(chalk32.dim(` Backup saved to: ${backup}`));
|
|
13566
14038
|
} catch (err) {
|
|
13567
14039
|
spinner.fail("Migration failed \u2014 rolling back");
|
|
13568
14040
|
rollback(projectRoot);
|
|
@@ -13571,10 +14043,10 @@ Found ${migratable.length} component(s) to migrate:`));
|
|
|
13571
14043
|
}
|
|
13572
14044
|
|
|
13573
14045
|
// src/utils/update-notifier.ts
|
|
13574
|
-
import { existsSync as existsSync28, mkdirSync as mkdirSync9, readFileSync as
|
|
14046
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync9, readFileSync as readFileSync20, writeFileSync as writeFileSync13 } from "fs";
|
|
13575
14047
|
import { join as join22 } from "path";
|
|
13576
14048
|
import { homedir } from "os";
|
|
13577
|
-
import
|
|
14049
|
+
import chalk33 from "chalk";
|
|
13578
14050
|
import { CLI_VERSION as CLI_VERSION5 } from "@getcoherent/core";
|
|
13579
14051
|
var DEBUG5 = process.env.COHERENT_DEBUG === "1";
|
|
13580
14052
|
var PACKAGE_NAME = "@getcoherent/cli";
|
|
@@ -13584,7 +14056,7 @@ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
|
13584
14056
|
function readCache() {
|
|
13585
14057
|
try {
|
|
13586
14058
|
if (!existsSync28(CACHE_FILE)) return null;
|
|
13587
|
-
const raw =
|
|
14059
|
+
const raw = readFileSync20(CACHE_FILE, "utf-8");
|
|
13588
14060
|
return JSON.parse(raw);
|
|
13589
14061
|
} catch (e) {
|
|
13590
14062
|
if (DEBUG5) console.error("Failed to read update cache:", e);
|
|
@@ -13645,8 +14117,8 @@ async function checkForUpdates() {
|
|
|
13645
14117
|
}
|
|
13646
14118
|
function printUpdateNotice(latest) {
|
|
13647
14119
|
console.log(
|
|
13648
|
-
|
|
13649
|
-
\u2B06 Update available: v${CLI_VERSION5} \u2192 v${latest}`) +
|
|
14120
|
+
chalk33.yellow(`
|
|
14121
|
+
\u2B06 Update available: v${CLI_VERSION5} \u2192 v${latest}`) + chalk33.dim(`
|
|
13650
14122
|
Run: npm update -g ${PACKAGE_NAME}
|
|
13651
14123
|
`)
|
|
13652
14124
|
);
|