@getcoherent/cli 0.5.15 → 0.6.1
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-INW3BQSX.js +202 -0
- package/dist/{claude-RFHVT7RC.js → claude-BZ3HSBD3.js} +16 -0
- package/dist/index.js +999 -647
- package/dist/{openai-provider-FSXSVEYD.js → openai-provider-XUI7ZHUR.js} +20 -0
- package/dist/plan-generator-IS3YDZUW.js +29 -0
- package/package.json +10 -10
- package/LICENSE +0 -21
package/dist/index.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
import {
|
|
2
|
+
generateArchitecturePlan,
|
|
3
|
+
getPageGroup,
|
|
4
|
+
getPageType,
|
|
5
|
+
loadPlan,
|
|
6
|
+
routeToKey,
|
|
7
|
+
savePlan
|
|
8
|
+
} from "./chunk-INW3BQSX.js";
|
|
1
9
|
import {
|
|
2
10
|
__require
|
|
3
11
|
} from "./chunk-3RG5ZIWI.js";
|
|
@@ -1984,8 +1992,8 @@ function fixEscapedClosingQuotes(code) {
|
|
|
1984
1992
|
}
|
|
1985
1993
|
function fixUnescapedLtInJsx(code) {
|
|
1986
1994
|
let out = code;
|
|
1987
|
-
out = out.replace(/>([
|
|
1988
|
-
out = out.replace(/>([
|
|
1995
|
+
out = out.replace(/>([^<\n]*)<(\d)/g, ">$1<$2");
|
|
1996
|
+
out = out.replace(/>([^<\n]*)<([^/a-zA-Z!{>\n])/g, ">$1<$2");
|
|
1989
1997
|
return out;
|
|
1990
1998
|
}
|
|
1991
1999
|
|
|
@@ -3745,7 +3753,7 @@ async function createAppRouteGroupLayout(projectPath) {
|
|
|
3745
3753
|
}
|
|
3746
3754
|
|
|
3747
3755
|
// src/commands/chat.ts
|
|
3748
|
-
import
|
|
3756
|
+
import chalk14 from "chalk";
|
|
3749
3757
|
import ora2 from "ora";
|
|
3750
3758
|
import { resolve as resolve9, relative as relative2, join as join11 } from "path";
|
|
3751
3759
|
import { existsSync as existsSync16, readFileSync as readFileSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
|
|
@@ -3832,7 +3840,7 @@ Please set ${envVar} in your environment or .env file.`);
|
|
|
3832
3840
|
}
|
|
3833
3841
|
if (preferredProvider === "openai") {
|
|
3834
3842
|
try {
|
|
3835
|
-
const { OpenAIClient } = await import("./openai-provider-
|
|
3843
|
+
const { OpenAIClient } = await import("./openai-provider-XUI7ZHUR.js");
|
|
3836
3844
|
return await OpenAIClient.create(apiKey2, config2?.model);
|
|
3837
3845
|
} catch (error) {
|
|
3838
3846
|
if (error.message?.includes("not installed")) {
|
|
@@ -3846,7 +3854,7 @@ Error: ${error.message}`
|
|
|
3846
3854
|
);
|
|
3847
3855
|
}
|
|
3848
3856
|
} else {
|
|
3849
|
-
const { ClaudeClient } = await import("./claude-
|
|
3857
|
+
const { ClaudeClient } = await import("./claude-BZ3HSBD3.js");
|
|
3850
3858
|
return ClaudeClient.create(apiKey2, config2?.model);
|
|
3851
3859
|
}
|
|
3852
3860
|
}
|
|
@@ -3859,7 +3867,7 @@ Error: ${error.message}`
|
|
|
3859
3867
|
switch (provider) {
|
|
3860
3868
|
case "openai":
|
|
3861
3869
|
try {
|
|
3862
|
-
const { OpenAIClient } = await import("./openai-provider-
|
|
3870
|
+
const { OpenAIClient } = await import("./openai-provider-XUI7ZHUR.js");
|
|
3863
3871
|
return await OpenAIClient.create(apiKey, config2?.model);
|
|
3864
3872
|
} catch (error) {
|
|
3865
3873
|
if (error.message?.includes("not installed")) {
|
|
@@ -3873,7 +3881,7 @@ Error: ${error.message}`
|
|
|
3873
3881
|
);
|
|
3874
3882
|
}
|
|
3875
3883
|
case "claude":
|
|
3876
|
-
const { ClaudeClient } = await import("./claude-
|
|
3884
|
+
const { ClaudeClient } = await import("./claude-BZ3HSBD3.js");
|
|
3877
3885
|
return ClaudeClient.create(apiKey, config2?.model);
|
|
3878
3886
|
default:
|
|
3879
3887
|
throw new Error(`Unsupported AI provider: ${provider}`);
|
|
@@ -3894,7 +3902,7 @@ var PAGE_TEMPLATES = {
|
|
|
3894
3902
|
login: {
|
|
3895
3903
|
description: "Login page with centered card form",
|
|
3896
3904
|
sections: [
|
|
3897
|
-
'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-
|
|
3905
|
+
'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-md".',
|
|
3898
3906
|
'Card with CardHeader: CardTitle "Sign in" (text-2xl font-bold), CardDescription "Enter your credentials to access your account" (text-sm text-muted-foreground).',
|
|
3899
3907
|
'CardContent with form: email Input (type="email", placeholder="you@example.com"), password Input (type="password"), a "Forgot password?" link (text-sm text-muted-foreground hover:text-foreground), and a Button "Sign in" (w-full).',
|
|
3900
3908
|
`CardFooter: text "Don't have an account?" with a Sign up link. All text is text-sm text-muted-foreground.`,
|
|
@@ -3915,7 +3923,7 @@ var PAGE_TEMPLATES = {
|
|
|
3915
3923
|
register: {
|
|
3916
3924
|
description: "Registration page with centered card form",
|
|
3917
3925
|
sections: [
|
|
3918
|
-
'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-
|
|
3926
|
+
'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-md".',
|
|
3919
3927
|
'Card with CardHeader: CardTitle "Create an account" (text-2xl font-bold), CardDescription "Enter your details to get started" (text-sm text-muted-foreground).',
|
|
3920
3928
|
'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).',
|
|
3921
3929
|
'CardFooter: text "Already have an account?" with a Sign in link. All text is text-sm text-muted-foreground.',
|
|
@@ -4089,7 +4097,7 @@ var PAGE_TEMPLATES = {
|
|
|
4089
4097
|
"reset-password": {
|
|
4090
4098
|
description: "Reset password page with centered card form",
|
|
4091
4099
|
sections: [
|
|
4092
|
-
'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-
|
|
4100
|
+
'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-md".',
|
|
4093
4101
|
'Card with CardHeader: CardTitle "Reset Password" (text-xl), CardDescription.',
|
|
4094
4102
|
'CardContent with form: new password Input (type="password"), confirm password Input (type="password"), Button "Reset password" (w-full).',
|
|
4095
4103
|
'Footer text: "Remember your password?" with Sign in link.',
|
|
@@ -4101,6 +4109,7 @@ var PAGE_TEMPLATES = {
|
|
|
4101
4109
|
var AUTH_ROUTE_SEGMENTS = /* @__PURE__ */ new Set([
|
|
4102
4110
|
"login",
|
|
4103
4111
|
"signin",
|
|
4112
|
+
"sign-in",
|
|
4104
4113
|
"sign-up",
|
|
4105
4114
|
"signup",
|
|
4106
4115
|
"register",
|
|
@@ -4197,7 +4206,7 @@ LAYOUT PATTERNS:
|
|
|
4197
4206
|
- Stats/KPI grid: grid gap-4 md:grid-cols-2 lg:grid-cols-4
|
|
4198
4207
|
- Card grid (3 col): grid gap-4 md:grid-cols-3
|
|
4199
4208
|
- Full-height page: min-h-svh (not min-h-screen)
|
|
4200
|
-
- Centered form (login/signup): flex min-h-svh flex-col items-center justify-center p-6 md:p-10 \u2192 div w-full max-w-
|
|
4209
|
+
- Centered form (login/signup): flex min-h-svh flex-col items-center justify-center p-6 md:p-10 \u2192 div w-full max-w-md
|
|
4201
4210
|
- Page content wrapper: flex flex-1 flex-col gap-4 p-4 lg:p-6
|
|
4202
4211
|
- Responsive: primary md: and lg:. Use sm:/xl: only when genuinely needed. Avoid 2xl:. NEVER arbitrary like min-[800px].
|
|
4203
4212
|
|
|
@@ -4249,12 +4258,11 @@ CONTENT (zero placeholders):
|
|
|
4249
4258
|
- NEVER: "Lorem ipsum", "Card content", "Description here"
|
|
4250
4259
|
- ALWAYS: Real, contextual content. Realistic metric names, values, dates.
|
|
4251
4260
|
`;
|
|
4252
|
-
var
|
|
4253
|
-
## DESIGN QUALITY
|
|
4261
|
+
var DESIGN_QUALITY_COMMON = `
|
|
4262
|
+
## DESIGN QUALITY \u2014 COMMON
|
|
4254
4263
|
|
|
4255
4264
|
### Typography Hierarchy
|
|
4256
4265
|
- Page headline (h1): text-4xl md:text-5xl lg:text-6xl font-bold tracking-tight leading-[1.1]
|
|
4257
|
-
- Landing/marketing hero headline: text-5xl md:text-6xl lg:text-7xl font-bold tracking-tight leading-[1.05]
|
|
4258
4266
|
- Section titles (h2): text-2xl md:text-3xl font-bold
|
|
4259
4267
|
- Card titles (h3): text-sm font-semibold (never text-base or text-lg)
|
|
4260
4268
|
- Body text: text-sm text-muted-foreground leading-relaxed
|
|
@@ -4267,6 +4275,29 @@ var DESIGN_QUALITY = `
|
|
|
4267
4275
|
- Sections alternate between bg-background and bg-muted/5 for rhythm
|
|
4268
4276
|
- Section dividers: border-t border-border/10 (subtle, not heavy)
|
|
4269
4277
|
|
|
4278
|
+
### Buttons with Icons
|
|
4279
|
+
- Buttons containing text + icon: ALWAYS use inline-flex items-center gap-2 whitespace-nowrap
|
|
4280
|
+
- Icon inside button: h-4 w-4 (never larger), placed AFTER text for arrows, BEFORE text for action icons
|
|
4281
|
+
- NEVER let button content wrap to multiple lines \u2014 use whitespace-nowrap on the Button component
|
|
4282
|
+
- CTA buttons: use the Button component, NEVER raw <button> or <a> styled as button
|
|
4283
|
+
|
|
4284
|
+
### Accent Color Discipline
|
|
4285
|
+
- ONE accent color per page (primary or emerald-400)
|
|
4286
|
+
- Use for: CTAs, terminal text, check icons, feature icon backgrounds, active states
|
|
4287
|
+
- NEVER mix blue + purple + emerald on same page
|
|
4288
|
+
- Badge: outline style (border-border/30 bg-transparent) not filled color
|
|
4289
|
+
- Status icons: text-emerald-400 for positive, text-red-400 for negative
|
|
4290
|
+
|
|
4291
|
+
### Dark Theme Implementation
|
|
4292
|
+
- html element: className="dark"
|
|
4293
|
+
- Background: use CSS variables from globals.css dark section
|
|
4294
|
+
- Text: text-foreground for primary, text-muted-foreground for secondary
|
|
4295
|
+
- NEVER hardcode dark colors (bg-gray-900) \u2014 always use semantic tokens
|
|
4296
|
+
- Cards and elevated elements: slightly lighter than background (bg-card or bg-zinc-900/50)
|
|
4297
|
+
`;
|
|
4298
|
+
var DESIGN_QUALITY_MARKETING = `
|
|
4299
|
+
## DESIGN QUALITY \u2014 MARKETING PAGES
|
|
4300
|
+
|
|
4270
4301
|
### Spacing Rhythm (3 distinct levels)
|
|
4271
4302
|
- Between sections: py-20 md:py-28 (generous)
|
|
4272
4303
|
- Within sections (title to content): mb-12 md:mb-16
|
|
@@ -4274,11 +4305,8 @@ var DESIGN_QUALITY = `
|
|
|
4274
4305
|
- Between cards in grid: gap-5 (tight)
|
|
4275
4306
|
- NEVER uniform spacing everywhere \u2014 contrast creates rhythm
|
|
4276
4307
|
|
|
4277
|
-
###
|
|
4278
|
-
-
|
|
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
|
|
4308
|
+
### Hero headline
|
|
4309
|
+
- Landing/marketing hero headline: text-5xl md:text-6xl lg:text-7xl font-bold tracking-tight leading-[1.05]
|
|
4282
4310
|
|
|
4283
4311
|
### Icons in Feature Cards
|
|
4284
4312
|
- Wrap in colored container: bg-primary/10 rounded-lg p-2.5
|
|
@@ -4294,14 +4322,7 @@ var DESIGN_QUALITY = `
|
|
|
4294
4322
|
- Title bar (optional): flex with 3 dots (bg-zinc-700 rounded-full w-2.5 h-2.5) + title text-zinc-500 text-[11px]
|
|
4295
4323
|
- Copy button: text-zinc-500 hover:text-zinc-300
|
|
4296
4324
|
|
|
4297
|
-
###
|
|
4298
|
-
- ONE accent color per page (primary or emerald-400)
|
|
4299
|
-
- Use for: CTAs, terminal text, check icons, feature icon backgrounds, active states
|
|
4300
|
-
- NEVER mix blue + purple + emerald on same page
|
|
4301
|
-
- Badge: outline style (border-border/30 bg-transparent) not filled color
|
|
4302
|
-
- Status icons: text-emerald-400 for positive, text-red-400 for negative
|
|
4303
|
-
|
|
4304
|
-
### Hero Section (landing/marketing pages)
|
|
4325
|
+
### Hero Section
|
|
4305
4326
|
- Minimum height: min-h-[80vh] flex items-center justify-center
|
|
4306
4327
|
- Content: centered, max-w-3xl, flex flex-col items-center text-center gap-8
|
|
4307
4328
|
- Badge above headline: small, outline, text-xs tracking-wide
|
|
@@ -4318,8 +4339,6 @@ var DESIGN_QUALITY = `
|
|
|
4318
4339
|
### Step/Process Sections
|
|
4319
4340
|
- Numbered steps: circle with border, number inside (w-10 h-10 rounded-full border border-border/30 text-sm)
|
|
4320
4341
|
- Label above: text-xs font-semibold tracking-widest uppercase text-muted-foreground
|
|
4321
|
-
- Each step has a code block showing the command
|
|
4322
|
-
- Description below: text-sm text-muted-foreground
|
|
4323
4342
|
|
|
4324
4343
|
### Footer
|
|
4325
4344
|
- Minimal: border-t border-border/10, py-10
|
|
@@ -4327,13 +4346,80 @@ var DESIGN_QUALITY = `
|
|
|
4327
4346
|
- Links: hover:text-foreground transition-colors
|
|
4328
4347
|
- Layout: flex justify-between on desktop, stack on mobile
|
|
4329
4348
|
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4349
|
+
NEVER include app-style elements (sidebar widgets, data tables, filters) on marketing pages.
|
|
4350
|
+
`;
|
|
4351
|
+
var DESIGN_QUALITY_APP = `
|
|
4352
|
+
## DESIGN QUALITY \u2014 APP PAGES
|
|
4353
|
+
|
|
4354
|
+
### Spacing
|
|
4355
|
+
- gap-4 md:gap-6 between sections
|
|
4356
|
+
- p-4 lg:p-6 content padding
|
|
4357
|
+
- Within cards: p-4 to p-6 (compact)
|
|
4358
|
+
- Between cards in grid: gap-4 (tight)
|
|
4359
|
+
|
|
4360
|
+
### Layout
|
|
4361
|
+
- Data tables, card grids, filters, stat rows
|
|
4362
|
+
- Page wrapper: flex flex-1 flex-col gap-4 p-4 lg:p-6
|
|
4363
|
+
- Stats grid: grid gap-4 md:grid-cols-2 lg:grid-cols-4
|
|
4364
|
+
- Content: functional, scannable, data-dense
|
|
4365
|
+
|
|
4366
|
+
### Toolbars & Filters
|
|
4367
|
+
- Filter row: flex flex-wrap items-center gap-2
|
|
4368
|
+
- Search input: MUST use flex-1 to fill remaining horizontal space
|
|
4369
|
+
- Filters/selects: fixed width (w-[180px] or auto), do NOT flex-grow
|
|
4370
|
+
- On mobile (sm:): search full width, filters wrap to next line
|
|
4371
|
+
|
|
4372
|
+
NEVER include marketing sections (hero, pricing, testimonials) on app pages.
|
|
4373
|
+
`;
|
|
4374
|
+
var DESIGN_QUALITY_AUTH = `
|
|
4375
|
+
## DESIGN QUALITY \u2014 AUTH PAGES
|
|
4376
|
+
|
|
4377
|
+
### Layout
|
|
4378
|
+
- Centered card: flex min-h-svh items-center justify-center p-6 md:p-10
|
|
4379
|
+
- Card width: w-full max-w-md
|
|
4380
|
+
- No navigation, no section containers, no sidebar
|
|
4381
|
+
- Single focused form with clear CTA
|
|
4382
|
+
- Card \u2192 CardHeader (title + description) \u2192 CardContent (form) \u2192 CardFooter (link to other auth page)
|
|
4383
|
+
|
|
4384
|
+
NEVER include navigation bars, sidebars, or multi-section layouts on auth pages.
|
|
4385
|
+
`;
|
|
4386
|
+
var DESIGN_QUALITY_CRITICAL = `
|
|
4387
|
+
## CRITICAL CODE RULES (violations will be auto-corrected)
|
|
4388
|
+
- Every lucide-react icon MUST have className="... shrink-0" to prevent flex squishing
|
|
4389
|
+
- Button with asChild wrapping Link: the inner element MUST have className="inline-flex items-center gap-2"
|
|
4390
|
+
- NEVER use raw Tailwind colors (bg-blue-500, text-gray-600). ONLY semantic tokens: bg-primary, text-muted-foreground, etc.
|
|
4391
|
+
- <Link> and <a> MUST always have an href attribute. Never omit href.
|
|
4392
|
+
- CardTitle: NEVER add text-xl, text-2xl, text-lg. CardTitle is text-sm font-medium by default.
|
|
4336
4393
|
`;
|
|
4394
|
+
function getDesignQualityForType(type) {
|
|
4395
|
+
switch (type) {
|
|
4396
|
+
case "marketing":
|
|
4397
|
+
return DESIGN_QUALITY_MARKETING + DESIGN_QUALITY_CRITICAL;
|
|
4398
|
+
case "app":
|
|
4399
|
+
return DESIGN_QUALITY_APP + DESIGN_QUALITY_CRITICAL;
|
|
4400
|
+
case "auth":
|
|
4401
|
+
return DESIGN_QUALITY_AUTH + DESIGN_QUALITY_CRITICAL;
|
|
4402
|
+
}
|
|
4403
|
+
}
|
|
4404
|
+
function inferPageTypeFromRoute(route) {
|
|
4405
|
+
const slug = route.replace(/^\//, "").split("/")[0] || "";
|
|
4406
|
+
const authSlugs = /* @__PURE__ */ new Set([
|
|
4407
|
+
"login",
|
|
4408
|
+
"register",
|
|
4409
|
+
"sign-up",
|
|
4410
|
+
"signup",
|
|
4411
|
+
"sign-in",
|
|
4412
|
+
"signin",
|
|
4413
|
+
"forgot-password",
|
|
4414
|
+
"reset-password"
|
|
4415
|
+
]);
|
|
4416
|
+
const marketingSlugs = /* @__PURE__ */ new Set(["pricing", "features", "about", "blog", "contact", "terms", "privacy"]);
|
|
4417
|
+
if (authSlugs.has(slug)) return "auth";
|
|
4418
|
+
if (marketingSlugs.has(slug) || slug === "") return "marketing";
|
|
4419
|
+
return "app";
|
|
4420
|
+
}
|
|
4421
|
+
var DESIGN_QUALITY = `${DESIGN_QUALITY_COMMON}
|
|
4422
|
+
${DESIGN_QUALITY_MARKETING}`;
|
|
4337
4423
|
var VISUAL_DEPTH = `
|
|
4338
4424
|
## VISUAL DEPTH TECHNIQUES (pick 1-3 per page based on context)
|
|
4339
4425
|
|
|
@@ -5210,7 +5296,7 @@ pageCode rules (shadcn/ui blocks quality):
|
|
|
5210
5296
|
- Full Next.js App Router page. Imports from '@/components/ui/...' for registry components.
|
|
5211
5297
|
- Follow ALL design constraints above: text-sm base, semantic colors only, restricted spacing, weight-based hierarchy.
|
|
5212
5298
|
- Stat card pattern: Card > CardHeader(flex flex-row items-center justify-between space-y-0 pb-2) > CardTitle(text-sm font-medium) + Icon(size-4 text-muted-foreground) ; CardContent > metric(text-2xl font-bold) + change(text-xs text-muted-foreground).
|
|
5213
|
-
- Login/form pattern: outer div(flex min-h-svh flex-col items-center justify-center p-6 md:p-10) > inner div(w-full max-w-
|
|
5299
|
+
- Login/form pattern: outer div(flex min-h-svh flex-col items-center justify-center p-6 md:p-10) > inner div(w-full max-w-md) > Card with form.
|
|
5214
5300
|
- Dashboard pattern: div(space-y-6) > page header(h1 text-2xl font-bold tracking-tight + p text-sm text-muted-foreground) > stats grid(grid gap-4 md:grid-cols-2 lg:grid-cols-4) > content cards. No <main> wrapper \u2014 the layout provides it.
|
|
5215
5301
|
- No placeholders: real contextual copy only. Use the EXACT text, language, and content from the user's request.
|
|
5216
5302
|
- IMAGES: For avatar/profile photos, use https://i.pravatar.cc/150?u=<unique-seed> (e.g. ?u=sarah.johnson). For hero/product images, use https://picsum.photos/800/400?random=N. Use standard <img> tags with className, NOT Next.js <Image>. Always provide alt text.
|
|
@@ -5335,11 +5421,14 @@ Return valid JSON only, no markdown code fence. Use this shape:
|
|
|
5335
5421
|
{ "requests": [ ... array of ModificationRequest ... ], "uxRecommendations": "optional markdown or omit key" }
|
|
5336
5422
|
Legacy: returning only a JSON array of requests is still accepted.`;
|
|
5337
5423
|
}
|
|
5338
|
-
function buildLightweightPagePrompt(pageName, route, styleContext, sharedComponentsSummary) {
|
|
5424
|
+
function buildLightweightPagePrompt(pageName, route, styleContext, sharedComponentsSummary, pageType) {
|
|
5425
|
+
const designConstraints = pageType ? getDesignQualityForType(pageType) : "";
|
|
5339
5426
|
return [
|
|
5340
5427
|
`Generate complete pageCode for a page called "${pageName}" at route "${route}".`,
|
|
5341
5428
|
`Output valid TSX with a default export React component.`,
|
|
5342
5429
|
`Use shadcn/ui components (import from @/components/ui/*). Use Tailwind CSS semantic tokens only.`,
|
|
5430
|
+
pageType ? `PAGE TYPE: ${pageType}` : "",
|
|
5431
|
+
designConstraints,
|
|
5343
5432
|
styleContext ? `Follow this style context:
|
|
5344
5433
|
${styleContext}` : "",
|
|
5345
5434
|
sharedComponentsSummary ? `Available shared components:
|
|
@@ -5989,7 +6078,7 @@ function checkLines(code, pattern, type, message, severity, skipCommentsAndStrin
|
|
|
5989
6078
|
}
|
|
5990
6079
|
return issues;
|
|
5991
6080
|
}
|
|
5992
|
-
function validatePageQuality(code, validRoutes) {
|
|
6081
|
+
function validatePageQuality(code, validRoutes, pageType) {
|
|
5993
6082
|
const issues = [];
|
|
5994
6083
|
const allLines = code.split("\n");
|
|
5995
6084
|
const isTerminalContext = (lineNum) => {
|
|
@@ -6159,21 +6248,23 @@ function validatePageQuality(code, validRoutes) {
|
|
|
6159
6248
|
"warning"
|
|
6160
6249
|
)
|
|
6161
6250
|
);
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6251
|
+
if (pageType !== "auth") {
|
|
6252
|
+
const h1Matches = code.match(/<h1[\s>]/g);
|
|
6253
|
+
if (!h1Matches || h1Matches.length === 0) {
|
|
6254
|
+
issues.push({
|
|
6255
|
+
line: 0,
|
|
6256
|
+
type: "NO_H1",
|
|
6257
|
+
message: "Page has no <h1> \u2014 every page should have exactly one h1 heading",
|
|
6258
|
+
severity: "warning"
|
|
6259
|
+
});
|
|
6260
|
+
} else if (h1Matches.length > 1) {
|
|
6261
|
+
issues.push({
|
|
6262
|
+
line: 0,
|
|
6263
|
+
type: "MULTIPLE_H1",
|
|
6264
|
+
message: `Page has ${h1Matches.length} <h1> elements \u2014 use exactly one per page`,
|
|
6265
|
+
severity: "warning"
|
|
6266
|
+
});
|
|
6267
|
+
}
|
|
6177
6268
|
}
|
|
6178
6269
|
const headingLevels = [...code.matchAll(/<h([1-6])[\s>]/g)].map((m) => parseInt(m[1]));
|
|
6179
6270
|
const hasCardContext = /\bCard\b|\bCardTitle\b|\bCardHeader\b/.test(code);
|
|
@@ -6347,6 +6438,24 @@ function validatePageQuality(code, validRoutes) {
|
|
|
6347
6438
|
issues.push(...detectComponentIssues(code));
|
|
6348
6439
|
return issues;
|
|
6349
6440
|
}
|
|
6441
|
+
function resolveHref(linkText, context) {
|
|
6442
|
+
if (!context) return "/";
|
|
6443
|
+
const text = linkText.trim().toLowerCase();
|
|
6444
|
+
if (context.linkMap) {
|
|
6445
|
+
for (const [label, route] of Object.entries(context.linkMap)) {
|
|
6446
|
+
if (label.toLowerCase() === text) return route;
|
|
6447
|
+
}
|
|
6448
|
+
}
|
|
6449
|
+
if (context.knownRoutes) {
|
|
6450
|
+
const cleaned = text.replace(/^(back\s+to|go\s+to|view\s+all|see\s+all|return\s+to)\s+/i, "").trim();
|
|
6451
|
+
for (const route of context.knownRoutes) {
|
|
6452
|
+
const slug = route.split("/").filter(Boolean).pop() || "";
|
|
6453
|
+
const routeName = slug.replace(/[-_]/g, " ");
|
|
6454
|
+
if (routeName && cleaned === routeName) return route;
|
|
6455
|
+
}
|
|
6456
|
+
}
|
|
6457
|
+
return "/";
|
|
6458
|
+
}
|
|
6350
6459
|
function replaceRawColors(classes, colorMap) {
|
|
6351
6460
|
let changed = false;
|
|
6352
6461
|
let result = classes;
|
|
@@ -6439,7 +6548,7 @@ function replaceRawColors(classes, colorMap) {
|
|
|
6439
6548
|
});
|
|
6440
6549
|
return { result, changed };
|
|
6441
6550
|
}
|
|
6442
|
-
async function autoFixCode(code) {
|
|
6551
|
+
async function autoFixCode(code, context) {
|
|
6443
6552
|
const fixes = [];
|
|
6444
6553
|
let fixed = code;
|
|
6445
6554
|
const beforeQuoteFix = fixed;
|
|
@@ -6449,11 +6558,36 @@ async function autoFixCode(code) {
|
|
|
6449
6558
|
fixes.push("fixed escaped closing quotes in strings");
|
|
6450
6559
|
}
|
|
6451
6560
|
const beforeEntityFix = fixed;
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6456
|
-
|
|
6561
|
+
const isInsideAttrValue = (line, idx) => {
|
|
6562
|
+
let inQuote = false;
|
|
6563
|
+
let inAttr = false;
|
|
6564
|
+
for (let i = 0; i < idx; i++) {
|
|
6565
|
+
if (line[i] === "=" && line[i + 1] === '"') {
|
|
6566
|
+
inAttr = true;
|
|
6567
|
+
inQuote = true;
|
|
6568
|
+
i++;
|
|
6569
|
+
} else if (inAttr && line[i] === '"') {
|
|
6570
|
+
inAttr = false;
|
|
6571
|
+
inQuote = false;
|
|
6572
|
+
}
|
|
6573
|
+
}
|
|
6574
|
+
return inQuote;
|
|
6575
|
+
};
|
|
6576
|
+
fixed = fixed.split("\n").map((line) => {
|
|
6577
|
+
let l = line;
|
|
6578
|
+
l = l.replace(/<=/g, (m, offset) => isInsideAttrValue(line, offset) ? m : "<=");
|
|
6579
|
+
l = l.replace(/>=/g, (m, offset) => isInsideAttrValue(line, offset) ? m : ">=");
|
|
6580
|
+
l = l.replace(/&&/g, (m, offset) => isInsideAttrValue(line, offset) ? m : "&&");
|
|
6581
|
+
l = l.replace(
|
|
6582
|
+
/([\w)\]])\s*<\s*([\w(])/g,
|
|
6583
|
+
(m, p1, p2, offset) => isInsideAttrValue(line, offset) ? m : `${p1} < ${p2}`
|
|
6584
|
+
);
|
|
6585
|
+
l = l.replace(
|
|
6586
|
+
/([\w)\]])\s*>\s*([\w(])/g,
|
|
6587
|
+
(m, p1, p2, offset) => isInsideAttrValue(line, offset) ? m : `${p1} > ${p2}`
|
|
6588
|
+
);
|
|
6589
|
+
return l;
|
|
6590
|
+
}).join("\n");
|
|
6457
6591
|
if (fixed !== beforeEntityFix) {
|
|
6458
6592
|
fixes.push("Fixed syntax issues");
|
|
6459
6593
|
}
|
|
@@ -6801,9 +6935,14 @@ ${selectImport}`
|
|
|
6801
6935
|
fixes.push("added inline-flex to Button asChild children (base-ui compat)");
|
|
6802
6936
|
}
|
|
6803
6937
|
const beforeLinkHrefFix = fixed;
|
|
6804
|
-
fixed = fixed.replace(/<(Link|a)\b(?![^>]*\bhref\s*=)([^>]*)>/g,
|
|
6938
|
+
fixed = fixed.replace(/<(Link|a)\b(?![^>]*\bhref\s*=)([^>]*)>([\s\S]*?)<\/\1>/g, (_match, tag, attrs, children) => {
|
|
6939
|
+
const textContent = children.replace(/<[^>]*>/g, "").trim();
|
|
6940
|
+
const href = resolveHref(textContent, context);
|
|
6941
|
+
return `<${tag} href="${href}"${attrs}>${children}</${tag}>`;
|
|
6942
|
+
});
|
|
6943
|
+
fixed = fixed.replace(/<(Link|a)\b(?![^>]*\bhref\s*=)([^>]*)\/?>/g, '<$1 href="/"$2>');
|
|
6805
6944
|
if (fixed !== beforeLinkHrefFix) {
|
|
6806
|
-
fixes.push(
|
|
6945
|
+
fixes.push("added href to <Link>/<a> missing href");
|
|
6807
6946
|
}
|
|
6808
6947
|
const { code: fixedByRules, fixes: ruleFixes } = applyComponentRules(fixed);
|
|
6809
6948
|
if (ruleFixes.length > 0) {
|
|
@@ -6928,7 +7067,16 @@ import { DesignSystemManager as DesignSystemManager3, loadManifest as loadManife
|
|
|
6928
7067
|
import chalk8 from "chalk";
|
|
6929
7068
|
var MARKETING_ROUTES = /* @__PURE__ */ new Set(["", "landing", "pricing", "about", "contact", "blog", "features"]);
|
|
6930
7069
|
var MIN_ANCHOR_PAGE_CODE_CHARS = 120;
|
|
6931
|
-
var AUTH_ROUTE_SLUGS = /* @__PURE__ */ new Set([
|
|
7070
|
+
var AUTH_ROUTE_SLUGS = /* @__PURE__ */ new Set([
|
|
7071
|
+
"login",
|
|
7072
|
+
"signin",
|
|
7073
|
+
"sign-in",
|
|
7074
|
+
"register",
|
|
7075
|
+
"sign-up",
|
|
7076
|
+
"signup",
|
|
7077
|
+
"forgot-password",
|
|
7078
|
+
"reset-password"
|
|
7079
|
+
]);
|
|
6932
7080
|
function inferRouteUsesAuthSegment(route) {
|
|
6933
7081
|
const slug = route.replace(/^\//, "").split("/")[0] || "";
|
|
6934
7082
|
return AUTH_ROUTE_SLUGS.has(slug);
|
|
@@ -6950,34 +7098,42 @@ function isMarketingRoute(route) {
|
|
|
6950
7098
|
const slug = route.replace(/^\//, "").split("/")[0] || "";
|
|
6951
7099
|
return MARKETING_ROUTES.has(slug);
|
|
6952
7100
|
}
|
|
6953
|
-
function routeToFsPath(projectRoot, route,
|
|
7101
|
+
function routeToFsPath(projectRoot, route, isAuthOrPlan) {
|
|
7102
|
+
const plan = typeof isAuthOrPlan === "object" ? isAuthOrPlan : void 0;
|
|
7103
|
+
const isAuth = typeof isAuthOrPlan === "boolean" ? isAuthOrPlan : false;
|
|
6954
7104
|
const slug = route.replace(/^\//, "");
|
|
6955
|
-
if (
|
|
6956
|
-
|
|
6957
|
-
|
|
6958
|
-
|
|
6959
|
-
return resolve5(projectRoot, "app", "page.tsx");
|
|
6960
|
-
}
|
|
6961
|
-
if (isMarketingRoute(route)) {
|
|
6962
|
-
return resolve5(projectRoot, "app", slug, "page.tsx");
|
|
7105
|
+
if (!slug) return resolve5(projectRoot, "app", "page.tsx");
|
|
7106
|
+
if (plan) {
|
|
7107
|
+
const group = getPageGroup(route, plan);
|
|
7108
|
+
if (group) return resolve5(projectRoot, "app", `(${group.id})`, slug, "page.tsx");
|
|
6963
7109
|
}
|
|
7110
|
+
if (isAuth) return resolve5(projectRoot, "app", "(auth)", slug || "login", "page.tsx");
|
|
7111
|
+
if (isMarketingRoute(route)) return resolve5(projectRoot, "app", slug, "page.tsx");
|
|
6964
7112
|
return resolve5(projectRoot, "app", "(app)", slug, "page.tsx");
|
|
6965
7113
|
}
|
|
6966
|
-
function routeToRelPath(route,
|
|
7114
|
+
function routeToRelPath(route, isAuthOrPlan) {
|
|
7115
|
+
const plan = typeof isAuthOrPlan === "object" ? isAuthOrPlan : void 0;
|
|
7116
|
+
const isAuth = typeof isAuthOrPlan === "boolean" ? isAuthOrPlan : false;
|
|
6967
7117
|
const slug = route.replace(/^\//, "");
|
|
6968
|
-
if (
|
|
6969
|
-
|
|
6970
|
-
|
|
6971
|
-
|
|
6972
|
-
return "app/page.tsx";
|
|
6973
|
-
}
|
|
6974
|
-
if (isMarketingRoute(route)) {
|
|
6975
|
-
return `app/${slug}/page.tsx`;
|
|
7118
|
+
if (!slug) return "app/page.tsx";
|
|
7119
|
+
if (plan) {
|
|
7120
|
+
const group = getPageGroup(route, plan);
|
|
7121
|
+
if (group) return `app/(${group.id})/${slug}/page.tsx`;
|
|
6976
7122
|
}
|
|
7123
|
+
if (isAuth) return `app/(auth)/${slug || "login"}/page.tsx`;
|
|
7124
|
+
if (isMarketingRoute(route)) return `app/${slug}/page.tsx`;
|
|
6977
7125
|
return `app/(app)/${slug}/page.tsx`;
|
|
6978
7126
|
}
|
|
7127
|
+
var AUTH_SYNONYMS = {
|
|
7128
|
+
"/register": "/signup",
|
|
7129
|
+
"/registration": "/signup",
|
|
7130
|
+
"/sign-up": "/signup",
|
|
7131
|
+
"/signin": "/login",
|
|
7132
|
+
"/sign-in": "/login"
|
|
7133
|
+
};
|
|
6979
7134
|
function deduplicatePages(pages) {
|
|
6980
|
-
const
|
|
7135
|
+
const canonicalize = (route) => AUTH_SYNONYMS[route] || route;
|
|
7136
|
+
const normalize = (route) => canonicalize(route).replace(/\/$/, "").replace(/s$/, "").replace(/ue$/, "");
|
|
6981
7137
|
const seen = /* @__PURE__ */ new Map();
|
|
6982
7138
|
return pages.filter((page, idx) => {
|
|
6983
7139
|
const norm = normalize(page.route);
|
|
@@ -6998,13 +7154,24 @@ function extractComponentIdsFromCode(code) {
|
|
|
6998
7154
|
}
|
|
6999
7155
|
return ids;
|
|
7000
7156
|
}
|
|
7001
|
-
async function warnInlineDuplicates(projectRoot, pageName, pageCode, manifest) {
|
|
7157
|
+
async function warnInlineDuplicates(projectRoot, pageName, route, pageCode, manifest, plan) {
|
|
7002
7158
|
const sectionOrWidget = manifest.shared.filter((e) => e.type === "section" || e.type === "widget");
|
|
7003
7159
|
if (sectionOrWidget.length === 0) return;
|
|
7160
|
+
const plannedForPage = plan ? new Set(plan.sharedComponents.filter((c) => c.usedBy.includes(route)).map((c) => c.name)) : null;
|
|
7004
7161
|
for (const e of sectionOrWidget) {
|
|
7162
|
+
if (plannedForPage && !plannedForPage.has(e.name)) continue;
|
|
7005
7163
|
const kebab = e.file.replace(/^components\/shared\//, "").replace(/\.tsx$/, "");
|
|
7006
7164
|
const hasImport = pageCode.includes(`@/components/shared/${kebab}`);
|
|
7007
7165
|
if (hasImport) continue;
|
|
7166
|
+
if (plannedForPage) {
|
|
7167
|
+
console.log(
|
|
7168
|
+
chalk8.yellow(
|
|
7169
|
+
`
|
|
7170
|
+
\u26A0 Page "${pageName}" should use shared component ${e.name} (per architecture plan) but it's not imported. Import from @/components/shared/${kebab}`
|
|
7171
|
+
)
|
|
7172
|
+
);
|
|
7173
|
+
continue;
|
|
7174
|
+
}
|
|
7008
7175
|
const sameNameAsTag = new RegExp(`<\\/?${e.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}[\\s>]`).test(pageCode);
|
|
7009
7176
|
if (sameNameAsTag) {
|
|
7010
7177
|
console.log(
|
|
@@ -7115,8 +7282,8 @@ ${currentCode}
|
|
|
7115
7282
|
|
|
7116
7283
|
// src/commands/chat/request-parser.ts
|
|
7117
7284
|
var AUTH_FLOW_PATTERNS = {
|
|
7118
|
-
"/login": ["/
|
|
7119
|
-
"/signin": ["/
|
|
7285
|
+
"/login": ["/signup", "/forgot-password"],
|
|
7286
|
+
"/signin": ["/signup", "/forgot-password"],
|
|
7120
7287
|
"/signup": ["/login"],
|
|
7121
7288
|
"/register": ["/login"],
|
|
7122
7289
|
"/forgot-password": ["/login", "/reset-password"],
|
|
@@ -7159,14 +7326,19 @@ function extractInternalLinks(code) {
|
|
|
7159
7326
|
function inferRelatedPages(plannedPages) {
|
|
7160
7327
|
const plannedRoutes = new Set(plannedPages.map((p) => p.route));
|
|
7161
7328
|
const inferred = [];
|
|
7162
|
-
|
|
7329
|
+
const queue = [...plannedPages];
|
|
7330
|
+
let i = 0;
|
|
7331
|
+
while (i < queue.length) {
|
|
7332
|
+
const { route } = queue[i++];
|
|
7163
7333
|
const authRelated = AUTH_FLOW_PATTERNS[route];
|
|
7164
7334
|
if (authRelated) {
|
|
7165
7335
|
for (const rel of authRelated) {
|
|
7166
7336
|
if (!plannedRoutes.has(rel)) {
|
|
7167
7337
|
const slug = rel.slice(1);
|
|
7168
7338
|
const name = slug.split("-").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
7169
|
-
|
|
7339
|
+
const page = { id: slug, name, route: rel };
|
|
7340
|
+
inferred.push(page);
|
|
7341
|
+
queue.push(page);
|
|
7170
7342
|
plannedRoutes.add(rel);
|
|
7171
7343
|
}
|
|
7172
7344
|
}
|
|
@@ -7176,6 +7348,7 @@ function inferRelatedPages(plannedPages) {
|
|
|
7176
7348
|
for (const rel of rule.related) {
|
|
7177
7349
|
if (!plannedRoutes.has(rel.route)) {
|
|
7178
7350
|
inferred.push(rel);
|
|
7351
|
+
queue.push(rel);
|
|
7179
7352
|
plannedRoutes.add(rel.route);
|
|
7180
7353
|
}
|
|
7181
7354
|
}
|
|
@@ -7203,7 +7376,7 @@ function extractPageNamesFromMessage(message) {
|
|
|
7203
7376
|
settings: "/settings",
|
|
7204
7377
|
account: "/account",
|
|
7205
7378
|
"personal account": "/account",
|
|
7206
|
-
registration: "/
|
|
7379
|
+
registration: "/signup",
|
|
7207
7380
|
signup: "/signup",
|
|
7208
7381
|
"sign up": "/signup",
|
|
7209
7382
|
login: "/login",
|
|
@@ -7616,6 +7789,7 @@ async function pMap(items, fn, concurrency = 3) {
|
|
|
7616
7789
|
}
|
|
7617
7790
|
|
|
7618
7791
|
// src/commands/chat/split-generator.ts
|
|
7792
|
+
import chalk9 from "chalk";
|
|
7619
7793
|
function buildExistingPagesContext(config2) {
|
|
7620
7794
|
const pages = config2.pages || [];
|
|
7621
7795
|
const analyzed = pages.filter((p) => p.pageAnalysis);
|
|
@@ -7712,6 +7886,21 @@ Before implementing any section, check this list. Import and use matching compon
|
|
|
7712
7886
|
|
|
7713
7887
|
${sharedComponentsSummary}`;
|
|
7714
7888
|
}
|
|
7889
|
+
function formatPlanSummary(plan) {
|
|
7890
|
+
if (plan.groups.length === 0) return "";
|
|
7891
|
+
const groupLines = plan.groups.map((g) => ` Group "${g.id}" (layout: ${g.layout}): ${g.pages.join(", ")}`);
|
|
7892
|
+
const compLines = plan.sharedComponents.map(
|
|
7893
|
+
(c) => ` ${c.name} (${c.type}) \u2014 ${c.description}; usedBy: ${c.usedBy.join(", ")}`
|
|
7894
|
+
);
|
|
7895
|
+
const parts = [`ARCHITECTURE PLAN:
|
|
7896
|
+
Groups:
|
|
7897
|
+
${groupLines.join("\n")}`];
|
|
7898
|
+
if (compLines.length > 0) {
|
|
7899
|
+
parts.push(`Shared Components:
|
|
7900
|
+
${compLines.join("\n")}`);
|
|
7901
|
+
}
|
|
7902
|
+
return parts.join("\n");
|
|
7903
|
+
}
|
|
7715
7904
|
async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts) {
|
|
7716
7905
|
let pageNames = [];
|
|
7717
7906
|
spinner.start("Phase 1/5 \u2014 Planning pages...");
|
|
@@ -7745,7 +7934,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7745
7934
|
}
|
|
7746
7935
|
if (pageNames.length === 0) {
|
|
7747
7936
|
spinner.fail("Could not determine pages to create");
|
|
7748
|
-
return [];
|
|
7937
|
+
return { requests: [], plan: null };
|
|
7749
7938
|
}
|
|
7750
7939
|
pageNames = deduplicatePages(pageNames);
|
|
7751
7940
|
const hasHomePage = pageNames.some((p) => p.route === "/");
|
|
@@ -7767,7 +7956,41 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7767
7956
|
const allRoutes = pageNames.map((p) => p.route).join(", ");
|
|
7768
7957
|
const allPagesList = pageNames.map((p) => `${p.name} (${p.route})`).join(", ");
|
|
7769
7958
|
const inferredNote = inferred.length > 0 ? ` (${inferred.length} auto-inferred)` : "";
|
|
7770
|
-
spinner.succeed(`Phase 1/
|
|
7959
|
+
spinner.succeed(`Phase 1/6 \u2014 Found ${pageNames.length} pages${inferredNote}: ${allPagesList}`);
|
|
7960
|
+
let plan = null;
|
|
7961
|
+
if (parseOpts.projectRoot) {
|
|
7962
|
+
spinner.start("Phase 2/6 \u2014 Generating architecture plan...");
|
|
7963
|
+
try {
|
|
7964
|
+
const ai = await createAIProvider(provider ?? "auto");
|
|
7965
|
+
const layoutHint = modCtx.config.navigation?.type || null;
|
|
7966
|
+
plan = await generateArchitecturePlan(pageNames, message, ai, layoutHint);
|
|
7967
|
+
if (plan) {
|
|
7968
|
+
const groupsSummary = plan.groups.map((g) => `${g.id} (${g.layout}, ${g.pages.length} pages)`).join(", ");
|
|
7969
|
+
const sharedSummary = plan.sharedComponents.length > 0 ? plan.sharedComponents.map((c) => `${c.name} \u2192 ${c.usedBy.join(", ")}`).join(" | ") : "";
|
|
7970
|
+
const totalPages = plan.groups.reduce((sum, g) => sum + g.pages.length, 0);
|
|
7971
|
+
spinner.succeed(`Phase 2/6 \u2014 Architecture plan created`);
|
|
7972
|
+
console.log(chalk9.dim(` Groups: ${groupsSummary}`));
|
|
7973
|
+
if (sharedSummary) console.log(chalk9.dim(` Shared: ${sharedSummary}`));
|
|
7974
|
+
console.log(chalk9.dim(` Total: ${totalPages} pages, ${plan.sharedComponents.length} shared components`));
|
|
7975
|
+
if (plan.sharedComponents.length > 0 && parseOpts.projectRoot) {
|
|
7976
|
+
const allDeps = new Set(plan.sharedComponents.flatMap((c) => c.shadcnDeps));
|
|
7977
|
+
if (allDeps.size > 0) {
|
|
7978
|
+
const componentProvider = getComponentProvider();
|
|
7979
|
+
for (const dep of allDeps) {
|
|
7980
|
+
try {
|
|
7981
|
+
await componentProvider.installComponent(dep, parseOpts.projectRoot);
|
|
7982
|
+
} catch {
|
|
7983
|
+
}
|
|
7984
|
+
}
|
|
7985
|
+
}
|
|
7986
|
+
}
|
|
7987
|
+
} else {
|
|
7988
|
+
spinner.warn("Phase 2/6 \u2014 Plan generation failed (continuing without plan)");
|
|
7989
|
+
}
|
|
7990
|
+
} catch {
|
|
7991
|
+
spinner.warn("Phase 2/6 \u2014 Plan generation failed (continuing without plan)");
|
|
7992
|
+
}
|
|
7993
|
+
}
|
|
7771
7994
|
const homeIdx = pageNames.findIndex((p) => p.route === "/");
|
|
7772
7995
|
const homePage = homeIdx !== -1 ? pageNames[homeIdx] : pageNames[0];
|
|
7773
7996
|
const remainingPages = pageNames.filter((_, i) => i !== (homeIdx !== -1 ? homeIdx : 0));
|
|
@@ -7781,12 +8004,12 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7781
8004
|
if (existingCode) {
|
|
7782
8005
|
reusedExistingAnchor = true;
|
|
7783
8006
|
homePageCode = existingCode;
|
|
7784
|
-
spinner.start(`Phase
|
|
7785
|
-
spinner.succeed(`Phase
|
|
8007
|
+
spinner.start(`Phase 3/6 \u2014 Loading ${homePage.name} from disk (style anchor)...`);
|
|
8008
|
+
spinner.succeed(`Phase 3/6 \u2014 Reused existing ${homePage.name} page (skipped AI regeneration)`);
|
|
7786
8009
|
}
|
|
7787
8010
|
}
|
|
7788
8011
|
if (!reusedExistingAnchor) {
|
|
7789
|
-
spinner.start(`Phase
|
|
8012
|
+
spinner.start(`Phase 3/6 \u2014 Generating ${homePage.name} page (sets design direction)...`);
|
|
7790
8013
|
try {
|
|
7791
8014
|
const homeResult = await parseModification(
|
|
7792
8015
|
`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.`,
|
|
@@ -7808,73 +8031,102 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7808
8031
|
changes: { id: homePage.id, name: homePage.name, route: homePage.route }
|
|
7809
8032
|
};
|
|
7810
8033
|
}
|
|
7811
|
-
spinner.succeed(`Phase
|
|
8034
|
+
spinner.succeed(`Phase 3/6 \u2014 ${homePage.name} page generated`);
|
|
7812
8035
|
}
|
|
7813
|
-
spinner.start("Phase
|
|
8036
|
+
spinner.start("Phase 4/6 \u2014 Extracting design patterns...");
|
|
7814
8037
|
const styleContext = homePageCode ? extractStyleContext(homePageCode) : "";
|
|
7815
8038
|
if (styleContext) {
|
|
7816
8039
|
const lineCount = styleContext.split("\n").length - 1;
|
|
7817
8040
|
const source = reusedExistingAnchor ? `${homePage.name} (existing file)` : homePage.name;
|
|
7818
|
-
spinner.succeed(`Phase
|
|
8041
|
+
spinner.succeed(`Phase 4/6 \u2014 Extracted ${lineCount} style patterns from ${source}`);
|
|
7819
8042
|
} else {
|
|
7820
|
-
spinner.succeed("Phase
|
|
8043
|
+
spinner.succeed("Phase 4/6 \u2014 No style patterns extracted (anchor page had no code)");
|
|
7821
8044
|
}
|
|
7822
|
-
if (remainingPages.length >= 2 &&
|
|
7823
|
-
|
|
7824
|
-
|
|
7825
|
-
if (!shouldSkip) {
|
|
7826
|
-
spinner.start("Phase 3.5/5 \u2014 Extracting shared components...");
|
|
8045
|
+
if (remainingPages.length >= 2 && projectRoot) {
|
|
8046
|
+
if (plan && plan.sharedComponents.length > 0) {
|
|
8047
|
+
spinner.start(`Phase 4.5/6 \u2014 Generating ${plan.sharedComponents.length} shared components from plan...`);
|
|
7827
8048
|
try {
|
|
7828
|
-
const
|
|
7829
|
-
|
|
7830
|
-
|
|
7831
|
-
|
|
7832
|
-
|
|
8049
|
+
const { generateSharedComponentsFromPlan } = await import("./plan-generator-IS3YDZUW.js");
|
|
8050
|
+
const generated = await generateSharedComponentsFromPlan(
|
|
8051
|
+
plan,
|
|
8052
|
+
styleContext,
|
|
8053
|
+
projectRoot,
|
|
8054
|
+
await createAIProvider(provider ?? "auto")
|
|
8055
|
+
);
|
|
8056
|
+
if (generated.length > 0) {
|
|
8057
|
+
const updatedManifest = await loadManifest5(projectRoot);
|
|
8058
|
+
parseOpts.sharedComponentsSummary = buildSharedComponentsSummary(updatedManifest);
|
|
8059
|
+
const names = generated.map((c) => c.name).join(", ");
|
|
8060
|
+
spinner.succeed(`Phase 4.5/6 \u2014 Generated ${generated.length} shared components (${names})`);
|
|
7833
8061
|
} else {
|
|
7834
|
-
spinner.succeed("Phase
|
|
8062
|
+
spinner.succeed("Phase 4.5/6 \u2014 No shared components generated");
|
|
7835
8063
|
}
|
|
7836
8064
|
} catch {
|
|
7837
|
-
spinner.warn("Phase
|
|
8065
|
+
spinner.warn("Phase 4.5/6 \u2014 Could not generate shared components (continuing without)");
|
|
8066
|
+
}
|
|
8067
|
+
} else if (homePageCode) {
|
|
8068
|
+
const manifest = await loadManifest5(projectRoot);
|
|
8069
|
+
const shouldSkip = reusedExistingAnchor && manifest.shared.some((e) => e.type !== "layout");
|
|
8070
|
+
if (!shouldSkip) {
|
|
8071
|
+
spinner.start("Phase 4.5/6 \u2014 Extracting shared components (legacy)...");
|
|
8072
|
+
try {
|
|
8073
|
+
const extraction = await extractSharedComponents(homePageCode, projectRoot, provider ?? "auto");
|
|
8074
|
+
parseOpts.sharedComponentsSummary = extraction.summary;
|
|
8075
|
+
if (extraction.components.length > 0) {
|
|
8076
|
+
const names = extraction.components.map((c) => c.name).join(", ");
|
|
8077
|
+
spinner.succeed(`Phase 4.5/6 \u2014 Extracted ${extraction.components.length} shared components (${names})`);
|
|
8078
|
+
} else {
|
|
8079
|
+
spinner.succeed("Phase 4.5/6 \u2014 No shared components extracted");
|
|
8080
|
+
}
|
|
8081
|
+
} catch {
|
|
8082
|
+
spinner.warn("Phase 4.5/6 \u2014 Could not extract shared components (continuing without)");
|
|
8083
|
+
}
|
|
7838
8084
|
}
|
|
7839
8085
|
}
|
|
7840
8086
|
}
|
|
7841
8087
|
if (remainingPages.length === 0) {
|
|
7842
|
-
return homeRequest ? [homeRequest] : [];
|
|
8088
|
+
return { requests: homeRequest ? [homeRequest] : [], plan };
|
|
7843
8089
|
}
|
|
7844
|
-
spinner.start(`Phase
|
|
8090
|
+
spinner.start(`Phase 5/6 \u2014 Generating ${remainingPages.length} pages in parallel...`);
|
|
7845
8091
|
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.";
|
|
7846
8092
|
const sharedComponentsNote = buildSharedComponentsNote(parseOpts.sharedComponentsSummary);
|
|
7847
8093
|
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="#".`;
|
|
7848
8094
|
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.';
|
|
8095
|
+
const planSummaryNote = plan ? formatPlanSummary(plan) : "";
|
|
7849
8096
|
const existingPagesContext = buildExistingPagesContext(modCtx.config);
|
|
7850
8097
|
const AI_CONCURRENCY = 3;
|
|
7851
|
-
let
|
|
8098
|
+
let phase5Done = 0;
|
|
7852
8099
|
const remainingRequests = await pMap(
|
|
7853
8100
|
remainingPages,
|
|
7854
8101
|
async ({ name, id, route }) => {
|
|
7855
8102
|
const isAuth = isAuthRoute(route) || isAuthRoute(name);
|
|
7856
|
-
const
|
|
8103
|
+
const pageType = plan ? getPageType(route, plan) : inferPageTypeFromRoute(route);
|
|
8104
|
+
const designConstraints = getDesignQualityForType(pageType);
|
|
8105
|
+
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-md". Do NOT use section containers or full-width wrappers. The auth layout provides centering \u2014 just output the card content.' : void 0;
|
|
7857
8106
|
const prompt = [
|
|
7858
8107
|
`Create ONE page called "${name}" at route "${route}".`,
|
|
7859
8108
|
`Context: ${message}.`,
|
|
7860
8109
|
`Generate complete pageCode for this single page only. Do not generate other pages.`,
|
|
8110
|
+
`PAGE TYPE: ${pageType}`,
|
|
8111
|
+
designConstraints,
|
|
7861
8112
|
sharedLayoutNote,
|
|
7862
8113
|
sharedComponentsNote,
|
|
7863
8114
|
routeNote,
|
|
7864
8115
|
alignmentNote,
|
|
7865
8116
|
authNote,
|
|
8117
|
+
planSummaryNote,
|
|
7866
8118
|
existingPagesContext,
|
|
7867
8119
|
styleContext
|
|
7868
8120
|
].filter(Boolean).join("\n\n");
|
|
7869
8121
|
try {
|
|
7870
8122
|
const result = await parseModification(prompt, modCtx, provider, parseOpts);
|
|
7871
|
-
|
|
7872
|
-
spinner.text = `Phase
|
|
8123
|
+
phase5Done++;
|
|
8124
|
+
spinner.text = `Phase 5/6 \u2014 ${phase5Done}/${remainingPages.length} pages generated...`;
|
|
7873
8125
|
const codePage = result.requests.find((r) => r.type === "add-page");
|
|
7874
8126
|
return codePage || { type: "add-page", target: "new", changes: { id, name, route } };
|
|
7875
8127
|
} catch {
|
|
7876
|
-
|
|
7877
|
-
spinner.text = `Phase
|
|
8128
|
+
phase5Done++;
|
|
8129
|
+
spinner.text = `Phase 5/6 \u2014 ${phase5Done}/${remainingPages.length} pages generated...`;
|
|
7878
8130
|
return { type: "add-page", target: "new", changes: { id, name, route } };
|
|
7879
8131
|
}
|
|
7880
8132
|
},
|
|
@@ -7889,18 +8141,18 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7889
8141
|
const pageName = page.name || page.id || "page";
|
|
7890
8142
|
const pageRoute = page.route || `/${pageName.toLowerCase()}`;
|
|
7891
8143
|
try {
|
|
8144
|
+
const retryPageType = plan ? getPageType(pageRoute, plan) : inferPageTypeFromRoute(pageRoute);
|
|
7892
8145
|
const lightweightPrompt = buildLightweightPagePrompt(
|
|
7893
8146
|
pageName,
|
|
7894
8147
|
pageRoute,
|
|
7895
8148
|
styleContext || "",
|
|
7896
|
-
parseOpts.sharedComponentsSummary
|
|
7897
|
-
|
|
7898
|
-
const retryResult = await parseModification(
|
|
7899
|
-
lightweightPrompt,
|
|
7900
|
-
modCtx,
|
|
7901
|
-
provider,
|
|
7902
|
-
{ ...parseOpts, lightweight: true }
|
|
8149
|
+
parseOpts.sharedComponentsSummary,
|
|
8150
|
+
retryPageType
|
|
7903
8151
|
);
|
|
8152
|
+
const retryResult = await parseModification(lightweightPrompt, modCtx, provider, {
|
|
8153
|
+
...parseOpts,
|
|
8154
|
+
lightweight: true
|
|
8155
|
+
});
|
|
7904
8156
|
const codePage = retryResult.requests.find((r) => r.type === "add-page");
|
|
7905
8157
|
if (codePage && codePage.changes?.pageCode) {
|
|
7906
8158
|
const idx = allRequests.indexOf(req);
|
|
@@ -7911,8 +8163,8 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7911
8163
|
}
|
|
7912
8164
|
}
|
|
7913
8165
|
const withCode = allRequests.filter((r) => r.changes?.pageCode).length;
|
|
7914
|
-
spinner.succeed(`Phase
|
|
7915
|
-
return allRequests;
|
|
8166
|
+
spinner.succeed(`Phase 5/6 \u2014 Generated ${allRequests.length} pages (${withCode} with full code)`);
|
|
8167
|
+
return { requests: allRequests, plan };
|
|
7916
8168
|
}
|
|
7917
8169
|
var SharedExtractionItemSchema = z.object({
|
|
7918
8170
|
name: z.string().min(2).max(50),
|
|
@@ -8026,7 +8278,7 @@ function extractAppNameFromPrompt(prompt) {
|
|
|
8026
8278
|
import { resolve as resolve7 } from "path";
|
|
8027
8279
|
import { mkdir as mkdir4 } from "fs/promises";
|
|
8028
8280
|
import { dirname as dirname6 } from "path";
|
|
8029
|
-
import
|
|
8281
|
+
import chalk12 from "chalk";
|
|
8030
8282
|
import {
|
|
8031
8283
|
getTemplateForPageType,
|
|
8032
8284
|
loadManifest as loadManifest6,
|
|
@@ -8047,7 +8299,7 @@ import {
|
|
|
8047
8299
|
TailwindConfigGenerator
|
|
8048
8300
|
} from "@getcoherent/core";
|
|
8049
8301
|
import { integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout2, generateSharedComponent as generateSharedComponent3 } from "@getcoherent/core";
|
|
8050
|
-
import
|
|
8302
|
+
import chalk10 from "chalk";
|
|
8051
8303
|
|
|
8052
8304
|
// src/utils/file-hashes.ts
|
|
8053
8305
|
import { createHash } from "crypto";
|
|
@@ -8151,7 +8403,7 @@ async function canOverwriteShared(projectRoot, componentFile, storedHashes) {
|
|
|
8151
8403
|
if (!storedHash) return true;
|
|
8152
8404
|
const edited = await isManuallyEdited(filePath, storedHash);
|
|
8153
8405
|
if (edited) {
|
|
8154
|
-
console.log(
|
|
8406
|
+
console.log(chalk10.yellow(` \u26A0 Skipping ${componentFile} \u2014 manually edited since last generation`));
|
|
8155
8407
|
}
|
|
8156
8408
|
return !edited;
|
|
8157
8409
|
}
|
|
@@ -8214,7 +8466,7 @@ async function regenerateLayout(config2, projectRoot, options = { navChanged: fa
|
|
|
8214
8466
|
await ensureAppRouteGroupLayout(projectRoot, config2.navigation?.type, options.navChanged);
|
|
8215
8467
|
} catch (err) {
|
|
8216
8468
|
if (process.env.COHERENT_DEBUG === "1") {
|
|
8217
|
-
console.log(
|
|
8469
|
+
console.log(chalk10.dim("Layout integration warning:", err));
|
|
8218
8470
|
}
|
|
8219
8471
|
}
|
|
8220
8472
|
}
|
|
@@ -8282,6 +8534,59 @@ export default function AppLayout({
|
|
|
8282
8534
|
}
|
|
8283
8535
|
`;
|
|
8284
8536
|
}
|
|
8537
|
+
function buildGroupLayoutCode(layout, _pages) {
|
|
8538
|
+
if (layout === "sidebar" || layout === "both") {
|
|
8539
|
+
return `import { Sidebar } from '@/components/shared/sidebar'
|
|
8540
|
+
|
|
8541
|
+
export default function GroupLayout({
|
|
8542
|
+
children,
|
|
8543
|
+
}: {
|
|
8544
|
+
children: React.ReactNode
|
|
8545
|
+
}) {
|
|
8546
|
+
return (
|
|
8547
|
+
<div className="flex min-h-[calc(100vh-3.5rem)]">
|
|
8548
|
+
<Sidebar />
|
|
8549
|
+
<main className="flex-1 px-4 sm:px-6 lg:px-8 py-6">
|
|
8550
|
+
{children}
|
|
8551
|
+
</main>
|
|
8552
|
+
</div>
|
|
8553
|
+
)
|
|
8554
|
+
}
|
|
8555
|
+
`;
|
|
8556
|
+
}
|
|
8557
|
+
if (layout === "none") {
|
|
8558
|
+
return `export default function GroupLayout({
|
|
8559
|
+
children,
|
|
8560
|
+
}: {
|
|
8561
|
+
children: React.ReactNode
|
|
8562
|
+
}) {
|
|
8563
|
+
return <>{children}</>
|
|
8564
|
+
}
|
|
8565
|
+
`;
|
|
8566
|
+
}
|
|
8567
|
+
return `export default function GroupLayout({
|
|
8568
|
+
children,
|
|
8569
|
+
}: {
|
|
8570
|
+
children: React.ReactNode
|
|
8571
|
+
}) {
|
|
8572
|
+
return (
|
|
8573
|
+
<main className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
|
|
8574
|
+
{children}
|
|
8575
|
+
</main>
|
|
8576
|
+
)
|
|
8577
|
+
}
|
|
8578
|
+
`;
|
|
8579
|
+
}
|
|
8580
|
+
async function ensurePlanGroupLayouts(projectRoot, plan) {
|
|
8581
|
+
const { mkdir: mkdirAsync } = await import("fs/promises");
|
|
8582
|
+
for (const group of plan.groups) {
|
|
8583
|
+
const groupDir = resolve6(projectRoot, "app", `(${group.id})`);
|
|
8584
|
+
await mkdirAsync(groupDir, { recursive: true });
|
|
8585
|
+
const layoutPath = resolve6(groupDir, "layout.tsx");
|
|
8586
|
+
const code = buildGroupLayoutCode(group.layout, group.pages);
|
|
8587
|
+
await writeFile(layoutPath, code);
|
|
8588
|
+
}
|
|
8589
|
+
}
|
|
8285
8590
|
async function regenerateFiles(modified, config2, projectRoot, options = { navChanged: false }) {
|
|
8286
8591
|
const componentIds = /* @__PURE__ */ new Set();
|
|
8287
8592
|
const pageIds = /* @__PURE__ */ new Set();
|
|
@@ -8299,7 +8604,7 @@ async function regenerateFiles(modified, config2, projectRoot, options = { navCh
|
|
|
8299
8604
|
});
|
|
8300
8605
|
const sharedInstalled = await scanAndInstallSharedDeps(projectRoot);
|
|
8301
8606
|
if (sharedInstalled.length > 0 && process.env.COHERENT_DEBUG === "1") {
|
|
8302
|
-
console.log(
|
|
8607
|
+
console.log(chalk10.dim(` Auto-installed shared deps: ${sharedInstalled.join(", ")}`));
|
|
8303
8608
|
}
|
|
8304
8609
|
}
|
|
8305
8610
|
if (componentIds.size > 0) {
|
|
@@ -8356,7 +8661,7 @@ function extractBalancedTag(source, tagName) {
|
|
|
8356
8661
|
}
|
|
8357
8662
|
|
|
8358
8663
|
// src/commands/chat/reporting.ts
|
|
8359
|
-
import
|
|
8664
|
+
import chalk11 from "chalk";
|
|
8360
8665
|
function extractImportsFrom(code, fromPath) {
|
|
8361
8666
|
const escaped = fromPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8362
8667
|
const regex = new RegExp(`import\\s*\\{([^}]+)\\}\\s*from\\s*['"\`]${escaped}[^'"\`]*['"\`]`, "g");
|
|
@@ -8378,27 +8683,27 @@ function printPostGenerationReport(opts) {
|
|
|
8378
8683
|
const iconCount = extractImportsFrom(code, "lucide-react").length;
|
|
8379
8684
|
const hasInstalled = postFixes.some((f) => f.startsWith("Installed:"));
|
|
8380
8685
|
const syntaxStatus = postFixes.length > 0 ? postFixes.some((f) => f.includes("metadata")) ? "fixed (escaped metadata quotes) \u2714" : "fixed \u2714" : "valid \u2714";
|
|
8381
|
-
console.log(
|
|
8686
|
+
console.log(chalk11.green(`
|
|
8382
8687
|
\u2705 Page "${pageTitle}" ${action} at ${filePath}
|
|
8383
8688
|
`));
|
|
8384
8689
|
if (uiComponents.length > 0) {
|
|
8385
|
-
console.log(
|
|
8690
|
+
console.log(chalk11.dim(` Components: ${uiComponents.join(", ")} (from @/components/ui)`));
|
|
8386
8691
|
}
|
|
8387
8692
|
if (inCodeShared.length > 0) {
|
|
8388
|
-
console.log(
|
|
8693
|
+
console.log(chalk11.dim(` Shared: ${inCodeShared.map((s) => `${s.id} (${s.name})`).join(", ")}`));
|
|
8389
8694
|
}
|
|
8390
8695
|
if (layoutShared.length > 0) {
|
|
8391
|
-
console.log(
|
|
8696
|
+
console.log(chalk11.dim(` Layout: ${layoutShared.map((l) => `${l.id} (${l.name})`).join(", ")} via layout.tsx`));
|
|
8392
8697
|
}
|
|
8393
8698
|
if (iconCount > 0) {
|
|
8394
|
-
console.log(
|
|
8699
|
+
console.log(chalk11.dim(` Icons: ${iconCount} from lucide-react`));
|
|
8395
8700
|
}
|
|
8396
8701
|
if (hasInstalled) {
|
|
8397
|
-
console.log(
|
|
8702
|
+
console.log(chalk11.dim(" Dependencies: installed \u2714"));
|
|
8398
8703
|
}
|
|
8399
|
-
console.log(
|
|
8704
|
+
console.log(chalk11.dim(` Syntax: ${syntaxStatus}`));
|
|
8400
8705
|
if (route) {
|
|
8401
|
-
console.log(
|
|
8706
|
+
console.log(chalk11.cyan(`
|
|
8402
8707
|
Preview: http://localhost:3000${route}`));
|
|
8403
8708
|
}
|
|
8404
8709
|
console.log("");
|
|
@@ -8406,35 +8711,35 @@ function printPostGenerationReport(opts) {
|
|
|
8406
8711
|
function printSharedComponentReport(opts) {
|
|
8407
8712
|
const { id, name, file, instruction, postFixes = [] } = opts;
|
|
8408
8713
|
const syntaxStatus = postFixes.length > 0 ? "fixed \u2714" : "valid \u2714";
|
|
8409
|
-
console.log(
|
|
8714
|
+
console.log(chalk11.green(`
|
|
8410
8715
|
\u2705 Updated ${id} (${name}) at ${file}
|
|
8411
8716
|
`));
|
|
8412
8717
|
if (instruction) {
|
|
8413
8718
|
const snippet = instruction.length > 60 ? instruction.slice(0, 57) + "..." : instruction;
|
|
8414
|
-
console.log(
|
|
8719
|
+
console.log(chalk11.dim(` Changed: ${snippet}`));
|
|
8415
8720
|
}
|
|
8416
|
-
console.log(
|
|
8417
|
-
console.log(
|
|
8721
|
+
console.log(chalk11.dim(" Affects: all pages via layout.tsx"));
|
|
8722
|
+
console.log(chalk11.dim(` Syntax: ${syntaxStatus}`));
|
|
8418
8723
|
console.log("");
|
|
8419
8724
|
}
|
|
8420
8725
|
function printLinkSharedReport(opts) {
|
|
8421
8726
|
const { sharedId, sharedName, pageTarget, route, postFixes = [] } = opts;
|
|
8422
8727
|
const syntaxStatus = postFixes.length > 0 ? "fixed \u2714" : "valid \u2714";
|
|
8423
|
-
console.log(
|
|
8728
|
+
console.log(chalk11.green(`
|
|
8424
8729
|
\u2705 Linked ${sharedId} (${sharedName}) to page "${pageTarget}"
|
|
8425
8730
|
`));
|
|
8426
|
-
console.log(
|
|
8427
|
-
console.log(
|
|
8731
|
+
console.log(chalk11.dim(` Syntax: ${syntaxStatus}`));
|
|
8732
|
+
console.log(chalk11.cyan(` Preview: http://localhost:3000${route}`));
|
|
8428
8733
|
console.log("");
|
|
8429
8734
|
}
|
|
8430
8735
|
function printPromoteAndLinkReport(opts) {
|
|
8431
8736
|
const { id, name, file, usedInFiles, postFixes = [] } = opts;
|
|
8432
8737
|
const syntaxStatus = postFixes.length > 0 ? "fixed \u2714" : "valid \u2714";
|
|
8433
|
-
console.log(
|
|
8738
|
+
console.log(chalk11.green(`
|
|
8434
8739
|
\u2705 Created ${id} (${name}) at ${file}
|
|
8435
8740
|
`));
|
|
8436
|
-
console.log(
|
|
8437
|
-
console.log(
|
|
8741
|
+
console.log(chalk11.dim(` Linked to: ${usedInFiles.length} page(s)`));
|
|
8742
|
+
console.log(chalk11.dim(` Syntax: ${syntaxStatus}`));
|
|
8438
8743
|
console.log("");
|
|
8439
8744
|
}
|
|
8440
8745
|
function showPreview(requests, results, config2, preflightInstalledNames) {
|
|
@@ -8452,23 +8757,23 @@ function showPreview(requests, results, config2, preflightInstalledNames) {
|
|
|
8452
8757
|
const modifiedSharedComponents = successfulPairs.filter(({ request }) => request.type === "modify-layout-block");
|
|
8453
8758
|
const modifiedPages = successfulPairs.filter(({ request }) => request.type === "update-page");
|
|
8454
8759
|
const tokenChanges = successfulPairs.filter(({ request }) => request.type === "update-token");
|
|
8455
|
-
console.log(
|
|
8760
|
+
console.log(chalk11.bold.cyan("\n\u{1F4CB} Changes Applied:\n"));
|
|
8456
8761
|
if (preflightInstalledNames && preflightInstalledNames.length > 0) {
|
|
8457
|
-
console.log(
|
|
8762
|
+
console.log(chalk11.cyan("\u{1F50D} Pre-flight check: Installed missing components:"));
|
|
8458
8763
|
preflightInstalledNames.forEach((name) => {
|
|
8459
|
-
console.log(
|
|
8764
|
+
console.log(chalk11.green(` \u2728 Auto-installed ${name}`));
|
|
8460
8765
|
});
|
|
8461
8766
|
console.log("");
|
|
8462
8767
|
}
|
|
8463
8768
|
if (addedComponents.length > 0) {
|
|
8464
8769
|
const names = addedComponents.map(({ request }) => request.changes.name).filter(Boolean);
|
|
8465
|
-
console.log(
|
|
8466
|
-
console.log(
|
|
8770
|
+
console.log(chalk11.green("\u{1F4E6} Components:"));
|
|
8771
|
+
console.log(chalk11.white(` \u2728 Auto-installed: ${names.join(", ")}`));
|
|
8467
8772
|
}
|
|
8468
8773
|
if (customComponents.length > 0) {
|
|
8469
8774
|
const names = customComponents.map(({ request }) => request.changes.name).filter(Boolean);
|
|
8470
|
-
if (addedComponents.length === 0) console.log(
|
|
8471
|
-
console.log(
|
|
8775
|
+
if (addedComponents.length === 0) console.log(chalk11.green("\u{1F4E6} Components:"));
|
|
8776
|
+
console.log(chalk11.white(` \u2728 Created: ${names.join(", ")}`));
|
|
8472
8777
|
}
|
|
8473
8778
|
const usedComponentIds = /* @__PURE__ */ new Set();
|
|
8474
8779
|
addedPages.forEach(({ request }) => {
|
|
@@ -8483,71 +8788,73 @@ function showPreview(requests, results, config2, preflightInstalledNames) {
|
|
|
8483
8788
|
]);
|
|
8484
8789
|
const reusedIds = [...usedComponentIds].filter((id) => !newComponentIds.has(id));
|
|
8485
8790
|
if (reusedIds.length > 0) {
|
|
8486
|
-
if (addedComponents.length === 0 && customComponents.length === 0) console.log(
|
|
8487
|
-
console.log(
|
|
8791
|
+
if (addedComponents.length === 0 && customComponents.length === 0) console.log(chalk11.green("\u{1F4E6} Components:"));
|
|
8792
|
+
console.log(chalk11.white(` \u{1F504} Reused: ${reusedIds.join(", ")}`));
|
|
8488
8793
|
}
|
|
8489
8794
|
if (addedComponents.length > 0 || customComponents.length > 0 || reusedIds.length > 0) {
|
|
8490
8795
|
console.log("");
|
|
8491
8796
|
}
|
|
8492
8797
|
if (addedPages.length > 0) {
|
|
8493
|
-
console.log(
|
|
8798
|
+
console.log(chalk11.green("\u{1F4C4} Pages Created:"));
|
|
8494
8799
|
addedPages.forEach(({ request }) => {
|
|
8495
8800
|
const page = request.changes;
|
|
8496
8801
|
const route = page.route || "/";
|
|
8497
|
-
console.log(
|
|
8498
|
-
console.log(
|
|
8499
|
-
|
|
8802
|
+
console.log(chalk11.white(` \u2728 ${page.name || "Page"}`));
|
|
8803
|
+
console.log(chalk11.gray(` Route: ${route}`));
|
|
8804
|
+
const configPage = config2.pages?.find((p) => p.id === page.id || p.route === (page.route || "/"));
|
|
8805
|
+
const sectionCount = configPage?.pageAnalysis?.sections?.length ?? page.sections?.length ?? 0;
|
|
8806
|
+
console.log(chalk11.gray(` Sections: ${sectionCount}`));
|
|
8500
8807
|
});
|
|
8501
8808
|
console.log("");
|
|
8502
8809
|
}
|
|
8503
8810
|
if (modifiedComponents.length > 0 || modifiedSharedComponents.length > 0 || modifiedPages.length > 0 || tokenChanges.length > 0) {
|
|
8504
|
-
console.log(
|
|
8811
|
+
console.log(chalk11.yellow("\u{1F527} Modified:"));
|
|
8505
8812
|
modifiedComponents.forEach(({ result }) => {
|
|
8506
|
-
console.log(
|
|
8813
|
+
console.log(chalk11.white(` \u2022 ${result.message}`));
|
|
8507
8814
|
});
|
|
8508
8815
|
modifiedSharedComponents.forEach(({ result }) => {
|
|
8509
|
-
console.log(
|
|
8816
|
+
console.log(chalk11.white(` \u2022 ${result.message}`));
|
|
8510
8817
|
});
|
|
8511
8818
|
modifiedPages.forEach(({ result }) => {
|
|
8512
|
-
console.log(
|
|
8819
|
+
console.log(chalk11.white(` \u2022 ${result.message}`));
|
|
8513
8820
|
});
|
|
8514
8821
|
tokenChanges.forEach(({ result }) => {
|
|
8515
|
-
console.log(
|
|
8822
|
+
console.log(chalk11.white(` \u2022 ${result.message}`));
|
|
8516
8823
|
});
|
|
8517
8824
|
console.log("");
|
|
8518
8825
|
}
|
|
8519
8826
|
if (failedPairs.length > 0) {
|
|
8520
|
-
console.log(
|
|
8827
|
+
console.log(chalk11.red("\u274C Failed modifications:"));
|
|
8521
8828
|
failedPairs.forEach(({ result }) => {
|
|
8522
|
-
console.log(
|
|
8829
|
+
console.log(chalk11.gray(` \u2716 ${result.message}`));
|
|
8523
8830
|
});
|
|
8524
8831
|
console.log("");
|
|
8525
8832
|
}
|
|
8526
8833
|
const successCount = successfulPairs.length;
|
|
8527
8834
|
const totalCount = results.length;
|
|
8528
8835
|
if (successCount === totalCount) {
|
|
8529
|
-
console.log(
|
|
8836
|
+
console.log(chalk11.green.bold(`\u2705 Success! ${successCount} modification(s) applied
|
|
8530
8837
|
`));
|
|
8531
8838
|
} else {
|
|
8532
|
-
console.log(
|
|
8839
|
+
console.log(chalk11.yellow.bold(`\u26A0\uFE0F Partial success: ${successCount}/${totalCount} modification(s) applied
|
|
8533
8840
|
`));
|
|
8534
8841
|
}
|
|
8535
8842
|
if (addedPages.length > 0) {
|
|
8536
8843
|
const firstPage = addedPages[0].request.changes;
|
|
8537
8844
|
const route = firstPage?.route || "/";
|
|
8538
|
-
console.log(
|
|
8539
|
-
console.log(
|
|
8540
|
-
console.log(
|
|
8541
|
-
console.log(
|
|
8845
|
+
console.log(chalk11.cyan("\u{1F680} What's next:\n"));
|
|
8846
|
+
console.log(chalk11.white(" \u{1F4FA} View in browser:"));
|
|
8847
|
+
console.log(chalk11.cyan(" coherent preview"));
|
|
8848
|
+
console.log(chalk11.gray(` \u2192 Opens http://localhost:3000${route}
|
|
8542
8849
|
`));
|
|
8543
|
-
console.log(
|
|
8544
|
-
console.log(
|
|
8545
|
-
console.log(
|
|
8850
|
+
console.log(chalk11.white(" \u{1F3A8} Customize:"));
|
|
8851
|
+
console.log(chalk11.cyan(' coherent chat "make buttons rounded"'));
|
|
8852
|
+
console.log(chalk11.cyan(` coherent chat "add hero section to ${firstPage?.name ?? "page"}"`));
|
|
8546
8853
|
console.log("");
|
|
8547
8854
|
} else if (successCount > 0) {
|
|
8548
|
-
console.log(
|
|
8549
|
-
console.log(
|
|
8550
|
-
console.log(
|
|
8855
|
+
console.log(chalk11.cyan("\u{1F680} What's next:\n"));
|
|
8856
|
+
console.log(chalk11.white(" \u{1F4FA} Preview changes:"));
|
|
8857
|
+
console.log(chalk11.cyan(" coherent preview\n"));
|
|
8551
8858
|
}
|
|
8552
8859
|
}
|
|
8553
8860
|
function getChangeDescription(request, config2) {
|
|
@@ -8696,8 +9003,8 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8696
9003
|
const newCode = await ai.editSharedComponentCode(currentCode, instruction, resolved.name);
|
|
8697
9004
|
const { fixedCode, fixes } = await validateAndFixGeneratedCode(projectRoot, newCode, { isPage: false });
|
|
8698
9005
|
if (fixes.length > 0) {
|
|
8699
|
-
console.log(
|
|
8700
|
-
fixes.forEach((f) => console.log(
|
|
9006
|
+
console.log(chalk12.dim(" \u{1F527} Post-generation fixes:"));
|
|
9007
|
+
fixes.forEach((f) => console.log(chalk12.dim(` ${f}`)));
|
|
8701
9008
|
}
|
|
8702
9009
|
await writeFile(fullPath, fixedCode);
|
|
8703
9010
|
printSharedComponentReport({
|
|
@@ -8770,8 +9077,8 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8770
9077
|
const newPageCode = await ai.replaceInlineWithShared(pageCode, sharedCode, resolved.name, changes?.blockHint);
|
|
8771
9078
|
const { fixedCode, fixes } = await validateAndFixGeneratedCode(projectRoot, newPageCode, { isPage: true });
|
|
8772
9079
|
if (fixes.length > 0) {
|
|
8773
|
-
console.log(
|
|
8774
|
-
fixes.forEach((f) => console.log(
|
|
9080
|
+
console.log(chalk12.dim(" \u{1F527} Post-generation fixes:"));
|
|
9081
|
+
fixes.forEach((f) => console.log(chalk12.dim(` ${f}`)));
|
|
8775
9082
|
}
|
|
8776
9083
|
await writeFile(pageFilePath, fixedCode);
|
|
8777
9084
|
const manifest = await loadManifest6(projectRoot);
|
|
@@ -8873,8 +9180,8 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8873
9180
|
const newCode = await ai.replaceInlineWithShared(linkPageCode, sharedCode, created.name, blockHint);
|
|
8874
9181
|
const { fixedCode, fixes } = await validateAndFixGeneratedCode(projectRoot, newCode, { isPage: true });
|
|
8875
9182
|
if (fixes.length > 0) {
|
|
8876
|
-
console.log(
|
|
8877
|
-
fixes.forEach((f) => console.log(
|
|
9183
|
+
console.log(chalk12.dim(" \u{1F527} Post-generation fixes:"));
|
|
9184
|
+
fixes.forEach((f) => console.log(chalk12.dim(` ${f}`)));
|
|
8878
9185
|
}
|
|
8879
9186
|
await writeFile(fullPath, fixedCode);
|
|
8880
9187
|
usedInFiles.push(relPath);
|
|
@@ -8965,7 +9272,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8965
9272
|
const aiPageCode = typeof page.pageCode === "string" && page.pageCode.trim() !== "" ? page.pageCode : void 0;
|
|
8966
9273
|
if (aiPageCode) {
|
|
8967
9274
|
finalPageCode = aiPageCode;
|
|
8968
|
-
if (DEBUG2) console.log(
|
|
9275
|
+
if (DEBUG2) console.log(chalk12.dim(` [pageCode] Using AI-generated pageCode (user content priority)`));
|
|
8969
9276
|
} else {
|
|
8970
9277
|
const inferredType = page.pageType || inferPageType(page.route || "", page.name || "");
|
|
8971
9278
|
if (inferredType) {
|
|
@@ -8980,19 +9287,19 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8980
9287
|
const content = page.structuredContent || getDefaultContent(inferredType, page.name || pageName);
|
|
8981
9288
|
finalPageCode = templateFn(content, opts);
|
|
8982
9289
|
if (DEBUG2)
|
|
8983
|
-
console.log(
|
|
9290
|
+
console.log(chalk12.dim(` [template] Used "${inferredType}" template (inferred from route/name)`));
|
|
8984
9291
|
} catch {
|
|
8985
|
-
if (DEBUG2) console.log(
|
|
9292
|
+
if (DEBUG2) console.log(chalk12.dim(` [template] Failed for "${inferredType}"`));
|
|
8986
9293
|
}
|
|
8987
9294
|
}
|
|
8988
9295
|
}
|
|
8989
9296
|
}
|
|
8990
9297
|
if (!finalPageCode) {
|
|
8991
|
-
console.log(
|
|
9298
|
+
console.log(chalk12.yellow(`
|
|
8992
9299
|
\u26A0\uFE0F Page "${page.name || page.id}" has no generated code \u2014 it will appear empty.`));
|
|
8993
|
-
console.log(
|
|
9300
|
+
console.log(chalk12.dim(" This usually means the AI did not produce pageCode for this page."));
|
|
8994
9301
|
console.log(
|
|
8995
|
-
|
|
9302
|
+
chalk12.dim(
|
|
8996
9303
|
' Try running: coherent chat "regenerate the ' + (page.name || page.id) + ' page with full content"'
|
|
8997
9304
|
)
|
|
8998
9305
|
);
|
|
@@ -9034,7 +9341,13 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
9034
9341
|
isPage: true
|
|
9035
9342
|
});
|
|
9036
9343
|
let codeToWrite = fixedCode;
|
|
9037
|
-
const
|
|
9344
|
+
const currentPlan = projectRoot ? loadPlan(projectRoot) : null;
|
|
9345
|
+
const autoFixCtx = route ? {
|
|
9346
|
+
currentRoute: route,
|
|
9347
|
+
knownRoutes: dsm.getConfig().pages.map((p) => p.route).filter(Boolean),
|
|
9348
|
+
linkMap: currentPlan?.pageNotes[routeToKey(route)]?.links
|
|
9349
|
+
} : void 0;
|
|
9350
|
+
const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite, autoFixCtx);
|
|
9038
9351
|
codeToWrite = autoFixed;
|
|
9039
9352
|
const hasDashboardPage = dsm.getConfig().pages.some((p) => p.route === "/dashboard");
|
|
9040
9353
|
if (!hasDashboardPage) {
|
|
@@ -9046,7 +9359,9 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
9046
9359
|
}
|
|
9047
9360
|
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
9048
9361
|
codeToWrite = layoutStripped;
|
|
9049
|
-
|
|
9362
|
+
const qualityPageType = currentPlan ? getPageType(route, currentPlan) : inferPageTypeFromRoute(route);
|
|
9363
|
+
const pageType = currentPlan ? getPageType(route, currentPlan) : isMarketingRoute(route) ? "marketing" : isAuth ? "auth" : "app";
|
|
9364
|
+
if (pageType === "app") {
|
|
9050
9365
|
const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
|
|
9051
9366
|
if (wrapperFixed) {
|
|
9052
9367
|
codeToWrite = normalized;
|
|
@@ -9056,8 +9371,8 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
9056
9371
|
const allFixes = [...postFixes, ...autoFixes];
|
|
9057
9372
|
if (stripped.length > 0) allFixes.push(`stripped inline ${stripped.join(", ")} (layout owns these)`);
|
|
9058
9373
|
if (allFixes.length > 0) {
|
|
9059
|
-
console.log(
|
|
9060
|
-
allFixes.forEach((f) => console.log(
|
|
9374
|
+
console.log(chalk12.dim(" \u{1F527} Post-generation fixes:"));
|
|
9375
|
+
allFixes.forEach((f) => console.log(chalk12.dim(` ${f}`)));
|
|
9061
9376
|
}
|
|
9062
9377
|
await writeFile(filePath, codeToWrite);
|
|
9063
9378
|
const pageIdx = dsm.getConfig().pages.findIndex((p) => p.id === page.id);
|
|
@@ -9069,7 +9384,15 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
9069
9384
|
pm.updateConfig(cfg);
|
|
9070
9385
|
}
|
|
9071
9386
|
const manifestForAudit = await loadManifest6(projectRoot);
|
|
9072
|
-
|
|
9387
|
+
const planForAudit = loadPlan(projectRoot);
|
|
9388
|
+
await warnInlineDuplicates(
|
|
9389
|
+
projectRoot,
|
|
9390
|
+
page.name || page.id || route.slice(1),
|
|
9391
|
+
route,
|
|
9392
|
+
codeToWrite,
|
|
9393
|
+
manifestForAudit,
|
|
9394
|
+
planForAudit ?? void 0
|
|
9395
|
+
);
|
|
9073
9396
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
9074
9397
|
printPostGenerationReport({
|
|
9075
9398
|
action: "created",
|
|
@@ -9082,11 +9405,11 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
9082
9405
|
layoutShared: manifestForAudit.shared.filter((c) => c.type === "layout"),
|
|
9083
9406
|
allShared: manifestForAudit.shared
|
|
9084
9407
|
});
|
|
9085
|
-
let issues = validatePageQuality(codeToWrite);
|
|
9408
|
+
let issues = validatePageQuality(codeToWrite, void 0, qualityPageType);
|
|
9086
9409
|
const errors = issues.filter((i) => i.severity === "error");
|
|
9087
9410
|
if (errors.length >= 2 && aiProvider) {
|
|
9088
9411
|
console.log(
|
|
9089
|
-
|
|
9412
|
+
chalk12.yellow(`
|
|
9090
9413
|
\u{1F504} ${errors.length} quality errors \u2014 attempting AI fix for ${page.name || page.id}...`)
|
|
9091
9414
|
);
|
|
9092
9415
|
try {
|
|
@@ -9103,19 +9426,19 @@ Rules:
|
|
|
9103
9426
|
- Keep all existing functionality and layout intact`;
|
|
9104
9427
|
const fixedCode2 = await ai.editPageCode(codeToWrite, instruction, page.name || page.id || "Page");
|
|
9105
9428
|
if (fixedCode2 && fixedCode2.length > 100 && /export\s+default/.test(fixedCode2)) {
|
|
9106
|
-
const recheck = validatePageQuality(fixedCode2);
|
|
9429
|
+
const recheck = validatePageQuality(fixedCode2, void 0, qualityPageType);
|
|
9107
9430
|
const recheckErrors = recheck.filter((i) => i.severity === "error");
|
|
9108
9431
|
if (recheckErrors.length < errors.length) {
|
|
9109
9432
|
codeToWrite = fixedCode2;
|
|
9110
|
-
const { code: reFixed, fixes: reFixes } = await autoFixCode(codeToWrite);
|
|
9433
|
+
const { code: reFixed, fixes: reFixes } = await autoFixCode(codeToWrite, autoFixCtx);
|
|
9111
9434
|
if (reFixes.length > 0) {
|
|
9112
9435
|
codeToWrite = reFixed;
|
|
9113
9436
|
postFixes.push(...reFixes);
|
|
9114
9437
|
}
|
|
9115
9438
|
await writeFile(filePath, codeToWrite);
|
|
9116
|
-
issues = validatePageQuality(codeToWrite);
|
|
9439
|
+
issues = validatePageQuality(codeToWrite, void 0, qualityPageType);
|
|
9117
9440
|
const finalErrors = issues.filter((i) => i.severity === "error").length;
|
|
9118
|
-
console.log(
|
|
9441
|
+
console.log(chalk12.green(` \u2714 Quality fix: ${errors.length} \u2192 ${finalErrors} errors`));
|
|
9119
9442
|
}
|
|
9120
9443
|
}
|
|
9121
9444
|
}
|
|
@@ -9124,15 +9447,15 @@ Rules:
|
|
|
9124
9447
|
}
|
|
9125
9448
|
const report = formatIssues(issues);
|
|
9126
9449
|
if (report) {
|
|
9127
|
-
console.log(
|
|
9450
|
+
console.log(chalk12.yellow(`
|
|
9128
9451
|
\u{1F50D} Quality check for ${page.name || page.id}:`));
|
|
9129
|
-
console.log(
|
|
9452
|
+
console.log(chalk12.dim(report));
|
|
9130
9453
|
}
|
|
9131
9454
|
const consistency = checkDesignConsistency(codeToWrite);
|
|
9132
9455
|
if (consistency.length > 0) {
|
|
9133
|
-
console.log(
|
|
9456
|
+
console.log(chalk12.yellow(`
|
|
9134
9457
|
\u{1F3A8} Design consistency for ${page.name || page.id}:`));
|
|
9135
|
-
consistency.forEach((w) => console.log(
|
|
9458
|
+
consistency.forEach((w) => console.log(chalk12.dim(` \u26A0 [${w.type}] ${w.message}`)));
|
|
9136
9459
|
}
|
|
9137
9460
|
}
|
|
9138
9461
|
}
|
|
@@ -9147,9 +9470,9 @@ Rules:
|
|
|
9147
9470
|
const changes = request.changes;
|
|
9148
9471
|
const instruction = originalMessage || (typeof changes?.instruction === "string" ? changes.instruction : void 0);
|
|
9149
9472
|
let resolvedPageCode = typeof changes?.pageCode === "string" && changes.pageCode.trim() !== "" ? changes.pageCode : void 0;
|
|
9150
|
-
if (DEBUG2 && instruction) console.log(
|
|
9473
|
+
if (DEBUG2 && instruction) console.log(chalk12.dim(` [update-page] instruction: ${instruction.slice(0, 120)}...`));
|
|
9151
9474
|
if (DEBUG2 && resolvedPageCode)
|
|
9152
|
-
console.log(
|
|
9475
|
+
console.log(chalk12.dim(` [update-page] has pageCode (${resolvedPageCode.length} chars)`));
|
|
9153
9476
|
const configChanges = { ...changes };
|
|
9154
9477
|
delete configChanges.pageCode;
|
|
9155
9478
|
delete configChanges.pageType;
|
|
@@ -9173,12 +9496,12 @@ Rules:
|
|
|
9173
9496
|
try {
|
|
9174
9497
|
currentCode = await readFile(absPath);
|
|
9175
9498
|
} catch {
|
|
9176
|
-
if (DEBUG2) console.log(
|
|
9499
|
+
if (DEBUG2) console.log(chalk12.dim(` [update-page] Could not read current file at ${absPath}`));
|
|
9177
9500
|
}
|
|
9178
9501
|
if (currentCode) {
|
|
9179
9502
|
const ai = await createAIProvider(aiProvider ?? "auto");
|
|
9180
9503
|
if (ai.editPageCode) {
|
|
9181
|
-
console.log(
|
|
9504
|
+
console.log(chalk12.dim(" \u270F\uFE0F Applying changes to existing page..."));
|
|
9182
9505
|
const coreRules = CORE_CONSTRAINTS;
|
|
9183
9506
|
const qualityRules = DESIGN_QUALITY;
|
|
9184
9507
|
const contextualRules = selectContextualRules(instruction);
|
|
@@ -9198,15 +9521,15 @@ ${contextualRules}
|
|
|
9198
9521
|
${routeRules}
|
|
9199
9522
|
${pagesCtx}`
|
|
9200
9523
|
);
|
|
9201
|
-
if (DEBUG2) console.log(
|
|
9524
|
+
if (DEBUG2) console.log(chalk12.dim(` [update-page] AI returned ${resolvedPageCode.length} chars`));
|
|
9202
9525
|
const editIssues = verifyIncrementalEdit(currentCode, resolvedPageCode);
|
|
9203
9526
|
if (editIssues.length > 0) {
|
|
9204
|
-
console.log(
|
|
9527
|
+
console.log(chalk12.yellow(`
|
|
9205
9528
|
\u26A0 Incremental edit issues for ${pageDef.name || pageDef.id}:`));
|
|
9206
|
-
editIssues.forEach((issue) => console.log(
|
|
9529
|
+
editIssues.forEach((issue) => console.log(chalk12.dim(` [${issue.type}] ${issue.message}`)));
|
|
9207
9530
|
}
|
|
9208
9531
|
} else {
|
|
9209
|
-
console.log(
|
|
9532
|
+
console.log(chalk12.yellow(" \u26A0 AI provider does not support editPageCode"));
|
|
9210
9533
|
}
|
|
9211
9534
|
}
|
|
9212
9535
|
}
|
|
@@ -9234,7 +9557,13 @@ ${pagesCtx}`
|
|
|
9234
9557
|
isPage: true
|
|
9235
9558
|
});
|
|
9236
9559
|
let codeToWrite = fixedCode;
|
|
9237
|
-
const
|
|
9560
|
+
const currentPlan2 = projectRoot ? loadPlan(projectRoot) : null;
|
|
9561
|
+
const autoFixCtx2 = route ? {
|
|
9562
|
+
currentRoute: route,
|
|
9563
|
+
knownRoutes: dsm.getConfig().pages.map((p) => p.route).filter(Boolean),
|
|
9564
|
+
linkMap: currentPlan2?.pageNotes[routeToKey(route)]?.links
|
|
9565
|
+
} : void 0;
|
|
9566
|
+
const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite, autoFixCtx2);
|
|
9238
9567
|
codeToWrite = autoFixed;
|
|
9239
9568
|
const hasDashboardPage = dsm.getConfig().pages.some((p) => p.route === "/dashboard");
|
|
9240
9569
|
if (!hasDashboardPage) {
|
|
@@ -9246,7 +9575,9 @@ ${pagesCtx}`
|
|
|
9246
9575
|
}
|
|
9247
9576
|
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
9248
9577
|
codeToWrite = layoutStripped;
|
|
9249
|
-
|
|
9578
|
+
const qualityPageType2 = currentPlan2 ? getPageType(route, currentPlan2) : inferPageTypeFromRoute(route);
|
|
9579
|
+
const pageType2 = currentPlan2 ? getPageType(route, currentPlan2) : isMarketingRoute(route) ? "marketing" : isAuth ? "auth" : "app";
|
|
9580
|
+
if (pageType2 === "app") {
|
|
9250
9581
|
const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
|
|
9251
9582
|
if (wrapperFixed) {
|
|
9252
9583
|
codeToWrite = normalized;
|
|
@@ -9256,8 +9587,8 @@ ${pagesCtx}`
|
|
|
9256
9587
|
const allFixes = [...postFixes, ...autoFixes];
|
|
9257
9588
|
if (stripped.length > 0) allFixes.push(`stripped inline ${stripped.join(", ")} (layout owns these)`);
|
|
9258
9589
|
if (allFixes.length > 0) {
|
|
9259
|
-
console.log(
|
|
9260
|
-
allFixes.forEach((f) => console.log(
|
|
9590
|
+
console.log(chalk12.dim(" \u{1F527} Post-generation fixes:"));
|
|
9591
|
+
allFixes.forEach((f) => console.log(chalk12.dim(` ${f}`)));
|
|
9261
9592
|
}
|
|
9262
9593
|
await writeFile(absPath, codeToWrite);
|
|
9263
9594
|
const updatePageIdx = dsm.getConfig().pages.findIndex((p) => p.id === pageDef.id);
|
|
@@ -9269,11 +9600,14 @@ ${pagesCtx}`
|
|
|
9269
9600
|
pm.updateConfig(cfg);
|
|
9270
9601
|
}
|
|
9271
9602
|
const manifestForAudit = await loadManifest6(projectRoot);
|
|
9603
|
+
const planForAudit2 = loadPlan(projectRoot);
|
|
9272
9604
|
await warnInlineDuplicates(
|
|
9273
9605
|
projectRoot,
|
|
9274
9606
|
pageDef.name || pageDef.id || route.slice(1),
|
|
9607
|
+
route,
|
|
9275
9608
|
codeToWrite,
|
|
9276
|
-
manifestForAudit
|
|
9609
|
+
manifestForAudit,
|
|
9610
|
+
planForAudit2 ?? void 0
|
|
9277
9611
|
);
|
|
9278
9612
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
9279
9613
|
printPostGenerationReport({
|
|
@@ -9287,28 +9621,34 @@ ${pagesCtx}`
|
|
|
9287
9621
|
allShared: manifestForAudit.shared,
|
|
9288
9622
|
layoutShared: manifestForAudit.shared.filter((c) => c.type === "layout")
|
|
9289
9623
|
});
|
|
9290
|
-
const issues = validatePageQuality(codeToWrite);
|
|
9624
|
+
const issues = validatePageQuality(codeToWrite, void 0, qualityPageType2);
|
|
9291
9625
|
const report = formatIssues(issues);
|
|
9292
9626
|
if (report) {
|
|
9293
|
-
console.log(
|
|
9627
|
+
console.log(chalk12.yellow(`
|
|
9294
9628
|
\u{1F50D} Quality check for ${pageDef.name || pageDef.id}:`));
|
|
9295
|
-
console.log(
|
|
9629
|
+
console.log(chalk12.dim(report));
|
|
9296
9630
|
}
|
|
9297
9631
|
const consistency = checkDesignConsistency(codeToWrite);
|
|
9298
9632
|
if (consistency.length > 0) {
|
|
9299
|
-
console.log(
|
|
9633
|
+
console.log(chalk12.yellow(`
|
|
9300
9634
|
\u{1F3A8} Design consistency for ${pageDef.name || pageDef.id}:`));
|
|
9301
|
-
consistency.forEach((w) => console.log(
|
|
9635
|
+
consistency.forEach((w) => console.log(chalk12.dim(` \u26A0 [${w.type}] ${w.message}`)));
|
|
9302
9636
|
}
|
|
9303
9637
|
} else {
|
|
9304
9638
|
try {
|
|
9305
9639
|
let code = await readFile(absPath);
|
|
9306
|
-
const
|
|
9640
|
+
const currentPlanForElse = projectRoot ? loadPlan(projectRoot) : null;
|
|
9641
|
+
const autoFixCtxElse = route ? {
|
|
9642
|
+
currentRoute: route,
|
|
9643
|
+
knownRoutes: dsm.getConfig().pages.map((p) => p.route).filter(Boolean),
|
|
9644
|
+
linkMap: currentPlanForElse?.pageNotes[routeToKey(route)]?.links
|
|
9645
|
+
} : void 0;
|
|
9646
|
+
const { code: fixed, fixes } = await autoFixCode(code, autoFixCtxElse);
|
|
9307
9647
|
if (fixes.length > 0) {
|
|
9308
9648
|
code = fixed;
|
|
9309
9649
|
await writeFile(absPath, code);
|
|
9310
|
-
console.log(
|
|
9311
|
-
fixes.forEach((f) => console.log(
|
|
9650
|
+
console.log(chalk12.dim(" \u{1F527} Auto-fixes applied:"));
|
|
9651
|
+
fixes.forEach((f) => console.log(chalk12.dim(` ${f}`)));
|
|
9312
9652
|
}
|
|
9313
9653
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
9314
9654
|
const manifest = await loadManifest6(projectRoot);
|
|
@@ -9322,12 +9662,14 @@ ${pagesCtx}`
|
|
|
9322
9662
|
allShared: manifest.shared,
|
|
9323
9663
|
layoutShared: manifest.shared.filter((c) => c.type === "layout")
|
|
9324
9664
|
});
|
|
9325
|
-
const
|
|
9665
|
+
const currentPlanForQuality = currentPlanForElse;
|
|
9666
|
+
const pageTypeForQuality = currentPlanForQuality ? getPageType(route, currentPlanForQuality) : inferPageTypeFromRoute(route);
|
|
9667
|
+
const issues = validatePageQuality(code, void 0, pageTypeForQuality);
|
|
9326
9668
|
const report = formatIssues(issues);
|
|
9327
9669
|
if (report) {
|
|
9328
|
-
console.log(
|
|
9670
|
+
console.log(chalk12.yellow(`
|
|
9329
9671
|
\u{1F50D} Quality check for ${pageDef.name || pageDef.id}:`));
|
|
9330
|
-
console.log(
|
|
9672
|
+
console.log(chalk12.dim(report));
|
|
9331
9673
|
}
|
|
9332
9674
|
} catch {
|
|
9333
9675
|
}
|
|
@@ -9450,7 +9792,7 @@ function hasNavChanged(before, after) {
|
|
|
9450
9792
|
}
|
|
9451
9793
|
|
|
9452
9794
|
// src/commands/chat/interactive.ts
|
|
9453
|
-
import
|
|
9795
|
+
import chalk13 from "chalk";
|
|
9454
9796
|
import { resolve as resolve8 } from "path";
|
|
9455
9797
|
import { existsSync as existsSync15, readFileSync as readFileSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
9456
9798
|
import { DesignSystemManager as DesignSystemManager6, ComponentManager as ComponentManager4, loadManifest as loadManifest7 } from "@getcoherent/core";
|
|
@@ -9468,7 +9810,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9468
9810
|
const validProviders = ["claude", "openai", "auto"];
|
|
9469
9811
|
const provider = (options.provider || "auto").toLowerCase();
|
|
9470
9812
|
if (!validProviders.includes(provider)) {
|
|
9471
|
-
console.error(
|
|
9813
|
+
console.error(chalk13.red(`
|
|
9472
9814
|
\u274C Invalid provider: ${options.provider}`));
|
|
9473
9815
|
process.exit(1);
|
|
9474
9816
|
}
|
|
@@ -9483,13 +9825,13 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9483
9825
|
} catch (e) {
|
|
9484
9826
|
if (DEBUG3) console.error("Failed to load REPL history:", e);
|
|
9485
9827
|
}
|
|
9486
|
-
console.log(
|
|
9487
|
-
console.log(
|
|
9488
|
-
console.log(
|
|
9828
|
+
console.log(chalk13.cyan("\n\u{1F3A8} Coherent Interactive Mode"));
|
|
9829
|
+
console.log(chalk13.dim(" Type your requests, or use built-in commands."));
|
|
9830
|
+
console.log(chalk13.dim(' Type "help" for available commands, "exit" to quit.\n'));
|
|
9489
9831
|
const rl = createInterface({
|
|
9490
9832
|
input: process.stdin,
|
|
9491
9833
|
output: process.stdout,
|
|
9492
|
-
prompt:
|
|
9834
|
+
prompt: chalk13.cyan("Coherent> "),
|
|
9493
9835
|
history,
|
|
9494
9836
|
historySize: 200
|
|
9495
9837
|
});
|
|
@@ -9503,37 +9845,37 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9503
9845
|
const lower = input.toLowerCase();
|
|
9504
9846
|
if (lower === "exit" || lower === "quit" || lower === "q") {
|
|
9505
9847
|
saveHistory();
|
|
9506
|
-
console.log(
|
|
9848
|
+
console.log(chalk13.dim("\nBye!\n"));
|
|
9507
9849
|
rl.close();
|
|
9508
9850
|
process.exit(0);
|
|
9509
9851
|
}
|
|
9510
9852
|
if (lower === "help") {
|
|
9511
|
-
console.log(
|
|
9512
|
-
console.log(
|
|
9513
|
-
console.log(
|
|
9514
|
-
console.log(
|
|
9515
|
-
console.log(
|
|
9516
|
-
console.log(
|
|
9517
|
-
console.log(
|
|
9518
|
-
console.log(
|
|
9519
|
-
console.log(
|
|
9520
|
-
console.log(
|
|
9521
|
-
console.log(
|
|
9853
|
+
console.log(chalk13.bold("\n Built-in commands:"));
|
|
9854
|
+
console.log(chalk13.white(" components") + chalk13.dim(" \u2014 list shared and UI components"));
|
|
9855
|
+
console.log(chalk13.white(" pages") + chalk13.dim(" \u2014 list all pages"));
|
|
9856
|
+
console.log(chalk13.white(" tokens") + chalk13.dim(" \u2014 show design tokens"));
|
|
9857
|
+
console.log(chalk13.white(" status") + chalk13.dim(" \u2014 project summary"));
|
|
9858
|
+
console.log(chalk13.white(" help") + chalk13.dim(" \u2014 this help"));
|
|
9859
|
+
console.log(chalk13.white(" exit") + chalk13.dim(" \u2014 quit interactive mode"));
|
|
9860
|
+
console.log(chalk13.bold("\n Target shortcuts:"));
|
|
9861
|
+
console.log(chalk13.white(" @ComponentName <msg>") + chalk13.dim(" \u2014 target a shared component"));
|
|
9862
|
+
console.log(chalk13.white(" @/route <msg>") + chalk13.dim(" \u2014 target a page by route"));
|
|
9863
|
+
console.log(chalk13.dim("\n Anything else is sent to AI as a modification request.\n"));
|
|
9522
9864
|
rl.prompt();
|
|
9523
9865
|
return;
|
|
9524
9866
|
}
|
|
9525
9867
|
if (lower === "components" || lower === "list components" || lower.includes("what components")) {
|
|
9526
9868
|
const manifest = await loadManifest7(projectRoot);
|
|
9527
9869
|
if (manifest.shared.length === 0) {
|
|
9528
|
-
console.log(
|
|
9870
|
+
console.log(chalk13.gray("\n No shared components yet.\n"));
|
|
9529
9871
|
} else {
|
|
9530
9872
|
console.log("");
|
|
9531
9873
|
const order = { layout: 0, section: 1, widget: 2 };
|
|
9532
9874
|
const sorted = [...manifest.shared].sort((a, b) => (order[a.type] ?? 9) - (order[b.type] ?? 9));
|
|
9533
9875
|
sorted.forEach((entry) => {
|
|
9534
|
-
const usage = entry.usedIn.includes("app/layout.tsx") ?
|
|
9876
|
+
const usage = entry.usedIn.includes("app/layout.tsx") ? chalk13.green("all pages") : entry.usedIn.length > 0 ? chalk13.gray(entry.usedIn.join(", ")) : chalk13.gray("unused");
|
|
9535
9877
|
console.log(
|
|
9536
|
-
` ${
|
|
9878
|
+
` ${chalk13.cyan(entry.id.padEnd(8))} ${chalk13.white(entry.name.padEnd(18))} ${chalk13.gray(entry.type.padEnd(9))} ${usage}`
|
|
9537
9879
|
);
|
|
9538
9880
|
});
|
|
9539
9881
|
console.log("");
|
|
@@ -9544,11 +9886,11 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9544
9886
|
if (lower === "pages" || lower === "list pages" || lower.includes("what pages")) {
|
|
9545
9887
|
const currentConfig = dsm.getConfig();
|
|
9546
9888
|
if (currentConfig.pages.length === 0) {
|
|
9547
|
-
console.log(
|
|
9889
|
+
console.log(chalk13.gray("\n No pages yet.\n"));
|
|
9548
9890
|
} else {
|
|
9549
9891
|
console.log("");
|
|
9550
9892
|
currentConfig.pages.forEach((p) => {
|
|
9551
|
-
console.log(` ${
|
|
9893
|
+
console.log(` ${chalk13.white(p.name.padEnd(22))} ${chalk13.gray(p.route)}`);
|
|
9552
9894
|
});
|
|
9553
9895
|
console.log("");
|
|
9554
9896
|
}
|
|
@@ -9558,10 +9900,10 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9558
9900
|
if (lower === "status") {
|
|
9559
9901
|
const currentConfig = dsm.getConfig();
|
|
9560
9902
|
const manifest = await loadManifest7(projectRoot);
|
|
9561
|
-
console.log(
|
|
9903
|
+
console.log(chalk13.bold(`
|
|
9562
9904
|
${currentConfig.name || "Coherent Project"}`));
|
|
9563
9905
|
console.log(
|
|
9564
|
-
|
|
9906
|
+
chalk13.dim(
|
|
9565
9907
|
` Pages: ${currentConfig.pages.length} | Shared components: ${manifest.shared.length} | UI components: ${cm.getAllComponents().length}
|
|
9566
9908
|
`
|
|
9567
9909
|
)
|
|
@@ -9572,21 +9914,21 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9572
9914
|
if (lower === "tokens" || lower === "show tokens" || lower === "design tokens") {
|
|
9573
9915
|
const currentConfig = dsm.getConfig();
|
|
9574
9916
|
const t = currentConfig.tokens;
|
|
9575
|
-
console.log(
|
|
9576
|
-
console.log(
|
|
9917
|
+
console.log(chalk13.bold("\n Design Tokens\n"));
|
|
9918
|
+
console.log(chalk13.cyan(" Colors (light)"));
|
|
9577
9919
|
for (const [k, v] of Object.entries(t.colors.light)) {
|
|
9578
|
-
console.log(` ${
|
|
9920
|
+
console.log(` ${chalk13.white(k.padEnd(14))} ${chalk13.gray(v)}`);
|
|
9579
9921
|
}
|
|
9580
|
-
console.log(
|
|
9581
|
-
console.log(` ${
|
|
9582
|
-
console.log(` ${
|
|
9583
|
-
console.log(
|
|
9922
|
+
console.log(chalk13.cyan("\n Typography"));
|
|
9923
|
+
console.log(` ${chalk13.white("sans".padEnd(14))} ${chalk13.gray(t.typography.fontFamily.sans)}`);
|
|
9924
|
+
console.log(` ${chalk13.white("mono".padEnd(14))} ${chalk13.gray(t.typography.fontFamily.mono)}`);
|
|
9925
|
+
console.log(chalk13.cyan("\n Spacing"));
|
|
9584
9926
|
for (const [k, v] of Object.entries(t.spacing)) {
|
|
9585
|
-
console.log(` ${
|
|
9927
|
+
console.log(` ${chalk13.white(k.padEnd(14))} ${chalk13.gray(v)}`);
|
|
9586
9928
|
}
|
|
9587
|
-
console.log(
|
|
9929
|
+
console.log(chalk13.cyan("\n Radius"));
|
|
9588
9930
|
for (const [k, v] of Object.entries(t.radius)) {
|
|
9589
|
-
console.log(` ${
|
|
9931
|
+
console.log(` ${chalk13.white(k.padEnd(14))} ${chalk13.gray(v)}`);
|
|
9590
9932
|
}
|
|
9591
9933
|
console.log("");
|
|
9592
9934
|
rl.prompt();
|
|
@@ -9608,7 +9950,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9608
9950
|
await dsm.load();
|
|
9609
9951
|
} catch (err) {
|
|
9610
9952
|
if (!err._printed) {
|
|
9611
|
-
console.error(
|
|
9953
|
+
console.error(chalk13.red(`
|
|
9612
9954
|
Error: ${err.message}
|
|
9613
9955
|
`));
|
|
9614
9956
|
}
|
|
@@ -9640,8 +9982,8 @@ async function chatCommand(message, options) {
|
|
|
9640
9982
|
process.exit(1);
|
|
9641
9983
|
};
|
|
9642
9984
|
if (!message) {
|
|
9643
|
-
console.error(
|
|
9644
|
-
console.log(
|
|
9985
|
+
console.error(chalk14.red('\n\u274C No message provided. Use: coherent chat "your request"\n'));
|
|
9986
|
+
console.log(chalk14.dim(" Or use interactive mode: coherent chat -i\n"));
|
|
9645
9987
|
bail("No message provided");
|
|
9646
9988
|
}
|
|
9647
9989
|
const spinner = ora2("Processing your request...").start();
|
|
@@ -9651,7 +9993,7 @@ async function chatCommand(message, options) {
|
|
|
9651
9993
|
const migrationGuard = join11(projectRoot, ".coherent", "migration-in-progress");
|
|
9652
9994
|
if (existsSync16(migrationGuard)) {
|
|
9653
9995
|
spinner.fail("Migration in progress");
|
|
9654
|
-
console.error(
|
|
9996
|
+
console.error(chalk14.red("\n\u274C A migration is in progress. Run `coherent migrate --rollback` to undo first."));
|
|
9655
9997
|
bail("Migration in progress");
|
|
9656
9998
|
}
|
|
9657
9999
|
const validProviders = ["claude", "openai", "auto"];
|
|
@@ -9661,20 +10003,20 @@ async function chatCommand(message, options) {
|
|
|
9661
10003
|
releaseLock = await acquireProjectLock(projectRoot);
|
|
9662
10004
|
if (!validProviders.includes(provider)) {
|
|
9663
10005
|
spinner.fail("Invalid provider");
|
|
9664
|
-
console.error(
|
|
10006
|
+
console.error(chalk14.red(`
|
|
9665
10007
|
\u274C Invalid provider: ${options.provider}`));
|
|
9666
|
-
console.log(
|
|
10008
|
+
console.log(chalk14.dim(`Valid options: ${validProviders.join(", ")}`));
|
|
9667
10009
|
bail(`Invalid provider: ${options.provider}`);
|
|
9668
10010
|
}
|
|
9669
10011
|
spinner.text = "Loading design system configuration...";
|
|
9670
10012
|
const config2 = await loadConfig(configPath);
|
|
9671
10013
|
if (config2.coherentVersion && config2.coherentVersion !== CLI_VERSION2) {
|
|
9672
10014
|
spinner.stop();
|
|
9673
|
-
console.log(
|
|
9674
|
-
console.log(
|
|
9675
|
-
console.log(
|
|
9676
|
-
console.log(
|
|
9677
|
-
console.log(
|
|
10015
|
+
console.log(chalk14.yellow("\n\u26A0\uFE0F Version mismatch detected\n"));
|
|
10016
|
+
console.log(chalk14.gray(" Project created with: ") + chalk14.white(`v${config2.coherentVersion}`));
|
|
10017
|
+
console.log(chalk14.gray(" Current CLI version: ") + chalk14.white(`v${CLI_VERSION2}`));
|
|
10018
|
+
console.log(chalk14.cyan("\n \u{1F4A1} Run `coherent update` to apply latest changes to your project.\n"));
|
|
10019
|
+
console.log(chalk14.dim(" Continuing anyway...\n"));
|
|
9678
10020
|
spinner.start("Loading design system configuration...");
|
|
9679
10021
|
}
|
|
9680
10022
|
if (needsGlobalsFix(projectRoot)) {
|
|
@@ -9701,9 +10043,9 @@ async function chatCommand(message, options) {
|
|
|
9701
10043
|
const done = await setDefaultDarkTheme(projectRoot);
|
|
9702
10044
|
spinner.stop();
|
|
9703
10045
|
if (done) {
|
|
9704
|
-
console.log(
|
|
10046
|
+
console.log(chalk14.green("\n\u2705 Default theme set to dark. Reload the app to see changes.\n"));
|
|
9705
10047
|
} else {
|
|
9706
|
-
console.log(
|
|
10048
|
+
console.log(chalk14.yellow("\n\u26A0\uFE0F Could not update layout (app/layout.tsx not found).\n"));
|
|
9707
10049
|
}
|
|
9708
10050
|
return;
|
|
9709
10051
|
}
|
|
@@ -9719,10 +10061,10 @@ async function chatCommand(message, options) {
|
|
|
9719
10061
|
dsm.updateConfig(cfg);
|
|
9720
10062
|
dsm.save();
|
|
9721
10063
|
spinner.stop();
|
|
9722
|
-
console.log(
|
|
10064
|
+
console.log(chalk14.green("\n\u2705 Default theme set to light. Reload the app to see changes.\n"));
|
|
9723
10065
|
} catch {
|
|
9724
10066
|
spinner.stop();
|
|
9725
|
-
console.log(
|
|
10067
|
+
console.log(chalk14.yellow("\n\u26A0\uFE0F Could not update layout (app/layout.tsx not found).\n"));
|
|
9726
10068
|
}
|
|
9727
10069
|
return;
|
|
9728
10070
|
}
|
|
@@ -9732,7 +10074,7 @@ async function chatCommand(message, options) {
|
|
|
9732
10074
|
const { created, id } = await ensureThemeToggle(projectRoot);
|
|
9733
10075
|
spinner.stop();
|
|
9734
10076
|
console.log(
|
|
9735
|
-
|
|
10077
|
+
chalk14.green(
|
|
9736
10078
|
`
|
|
9737
10079
|
\u2705 ${created ? `Created ${id} (ThemeToggle) and added to layout` : "ThemeToggle already present; layout updated"}.
|
|
9738
10080
|
`
|
|
@@ -9740,7 +10082,7 @@ async function chatCommand(message, options) {
|
|
|
9740
10082
|
);
|
|
9741
10083
|
} catch (e) {
|
|
9742
10084
|
spinner.fail("Failed to add theme toggle");
|
|
9743
|
-
if (e instanceof Error) console.error(
|
|
10085
|
+
if (e instanceof Error) console.error(chalk14.red("\n\u274C " + e.message + "\n"));
|
|
9744
10086
|
}
|
|
9745
10087
|
return;
|
|
9746
10088
|
}
|
|
@@ -9756,12 +10098,12 @@ async function chatCommand(message, options) {
|
|
|
9756
10098
|
manifest = { ...manifest, shared: validShared };
|
|
9757
10099
|
await saveManifest2(project.root, manifest);
|
|
9758
10100
|
if (DEBUG4) {
|
|
9759
|
-
console.log(
|
|
10101
|
+
console.log(chalk14.dim(`[pre-gen] Cleaned ${cleaned} orphaned component(s) from manifest`));
|
|
9760
10102
|
}
|
|
9761
10103
|
}
|
|
9762
10104
|
const sharedComponentsSummary = buildSharedComponentsSummary(manifest);
|
|
9763
10105
|
if (DEBUG4 && sharedComponentsSummary) {
|
|
9764
|
-
console.log(
|
|
10106
|
+
console.log(chalk14.dim("[add-page] sharedComponentsSummary in prompt:\n" + sharedComponentsSummary));
|
|
9765
10107
|
}
|
|
9766
10108
|
let requests;
|
|
9767
10109
|
let uxRecommendations;
|
|
@@ -9773,7 +10115,12 @@ async function chatCommand(message, options) {
|
|
|
9773
10115
|
) || []).length >= SPLIT_THRESHOLD;
|
|
9774
10116
|
if (multiPageHint) {
|
|
9775
10117
|
try {
|
|
9776
|
-
|
|
10118
|
+
const splitResult = await splitGeneratePages(spinner, message, modCtx, provider, parseOpts);
|
|
10119
|
+
requests = splitResult.requests;
|
|
10120
|
+
if (splitResult.plan && projectRoot) {
|
|
10121
|
+
savePlan(projectRoot, splitResult.plan);
|
|
10122
|
+
await ensurePlanGroupLayouts(projectRoot, splitResult.plan);
|
|
10123
|
+
}
|
|
9777
10124
|
uxRecommendations = void 0;
|
|
9778
10125
|
} catch {
|
|
9779
10126
|
spinner.warn("Split generation encountered an issue \u2014 trying page-by-page...");
|
|
@@ -9846,7 +10193,12 @@ async function chatCommand(message, options) {
|
|
|
9846
10193
|
if (isTruncated || isJsonError) {
|
|
9847
10194
|
spinner.warn("Response too large \u2014 splitting into smaller requests...");
|
|
9848
10195
|
try {
|
|
9849
|
-
|
|
10196
|
+
const splitResult = await splitGeneratePages(spinner, message, modCtx, provider, parseOpts);
|
|
10197
|
+
requests = splitResult.requests;
|
|
10198
|
+
if (splitResult.plan && projectRoot) {
|
|
10199
|
+
savePlan(projectRoot, splitResult.plan);
|
|
10200
|
+
await ensurePlanGroupLayouts(projectRoot, splitResult.plan);
|
|
10201
|
+
}
|
|
9850
10202
|
uxRecommendations = void 0;
|
|
9851
10203
|
} catch {
|
|
9852
10204
|
throw firstError;
|
|
@@ -9858,10 +10210,10 @@ async function chatCommand(message, options) {
|
|
|
9858
10210
|
}
|
|
9859
10211
|
if (requests.length === 0) {
|
|
9860
10212
|
spinner.fail("No modifications found in your request");
|
|
9861
|
-
console.log(
|
|
9862
|
-
console.log(
|
|
9863
|
-
console.log(
|
|
9864
|
-
console.log(
|
|
10213
|
+
console.log(chalk14.yellow("\n\u{1F4A1} Try being more specific, e.g.:"));
|
|
10214
|
+
console.log(chalk14.dim(' - "make buttons blue"'));
|
|
10215
|
+
console.log(chalk14.dim(' - "add a pricing page"'));
|
|
10216
|
+
console.log(chalk14.dim(' - "change primary color to green"'));
|
|
9865
10217
|
return;
|
|
9866
10218
|
}
|
|
9867
10219
|
spinner.succeed(`Parsed ${requests.length} modification(s)`);
|
|
@@ -9869,11 +10221,11 @@ async function chatCommand(message, options) {
|
|
|
9869
10221
|
normalizedRequests = normalizedRequests.map((req) => {
|
|
9870
10222
|
const result = normalizeRequest(req, dsm.getConfig());
|
|
9871
10223
|
if ("error" in result) {
|
|
9872
|
-
console.log(
|
|
10224
|
+
console.log(chalk14.yellow(` \u26A0 Skipped: ${result.error}`));
|
|
9873
10225
|
return null;
|
|
9874
10226
|
}
|
|
9875
10227
|
if (result.type !== req.type) {
|
|
9876
|
-
console.log(
|
|
10228
|
+
console.log(chalk14.dim(` \u2139 Adjusted: ${req.type} \u2192 ${result.type} (target: ${req.target})`));
|
|
9877
10229
|
}
|
|
9878
10230
|
return result;
|
|
9879
10231
|
}).filter((r) => r !== null);
|
|
@@ -9929,13 +10281,13 @@ async function chatCommand(message, options) {
|
|
|
9929
10281
|
}
|
|
9930
10282
|
}
|
|
9931
10283
|
if (DEBUG4) {
|
|
9932
|
-
console.log(
|
|
10284
|
+
console.log(chalk14.gray(`
|
|
9933
10285
|
[DEBUG] Pre-flight analysis for page "${page.name || page.route}": `));
|
|
9934
|
-
console.log(
|
|
10286
|
+
console.log(chalk14.gray(` Page sections: ${page.sections?.length || 0}`));
|
|
9935
10287
|
if (page.sections?.[0]?.props?.fields) {
|
|
9936
|
-
console.log(
|
|
10288
|
+
console.log(chalk14.gray(` First section has ${page.sections[0].props.fields.length} fields`));
|
|
9937
10289
|
page.sections[0].props.fields.forEach((f, i) => {
|
|
9938
|
-
console.log(
|
|
10290
|
+
console.log(chalk14.gray(` Field ${i}: component=${f.component}`));
|
|
9939
10291
|
});
|
|
9940
10292
|
}
|
|
9941
10293
|
}
|
|
@@ -9958,8 +10310,8 @@ async function chatCommand(message, options) {
|
|
|
9958
10310
|
const INVALID_COMPONENT_IDS = /* @__PURE__ */ new Set(["ui", "shared", "lib", "utils", "hooks", "app", "components"]);
|
|
9959
10311
|
for (const id of INVALID_COMPONENT_IDS) allNeededComponentIds.delete(id);
|
|
9960
10312
|
if (DEBUG4) {
|
|
9961
|
-
console.log(
|
|
9962
|
-
console.log(
|
|
10313
|
+
console.log(chalk14.gray("\n[DEBUG] Pre-flight analysis (consolidated):"));
|
|
10314
|
+
console.log(chalk14.gray(` All needed components: ${Array.from(allNeededComponentIds).join(", ")}`));
|
|
9963
10315
|
console.log("");
|
|
9964
10316
|
}
|
|
9965
10317
|
const missingComponents = [];
|
|
@@ -9967,59 +10319,59 @@ async function chatCommand(message, options) {
|
|
|
9967
10319
|
const isRegistered = !!cm.read(componentId);
|
|
9968
10320
|
const filePath = join11(projectRoot, "components", "ui", `${componentId}.tsx`);
|
|
9969
10321
|
const fileExists = existsSync16(filePath);
|
|
9970
|
-
if (DEBUG4) console.log(
|
|
10322
|
+
if (DEBUG4) console.log(chalk14.gray(` Checking ${componentId}: registered=${isRegistered} file=${fileExists}`));
|
|
9971
10323
|
if (!isRegistered || !fileExists) {
|
|
9972
10324
|
missingComponents.push(componentId);
|
|
9973
10325
|
}
|
|
9974
10326
|
}
|
|
9975
10327
|
if (missingComponents.length > 0) {
|
|
9976
10328
|
spinner.stop();
|
|
9977
|
-
console.log(
|
|
10329
|
+
console.log(chalk14.cyan("\n\u{1F50D} Pre-flight check: Installing missing components...\n"));
|
|
9978
10330
|
const provider2 = getComponentProvider();
|
|
9979
10331
|
for (const componentId of missingComponents) {
|
|
9980
10332
|
if (DEBUG4) {
|
|
9981
|
-
console.log(
|
|
9982
|
-
console.log(
|
|
10333
|
+
console.log(chalk14.gray(` [DEBUG] Trying to install: ${componentId}`));
|
|
10334
|
+
console.log(chalk14.gray(` [DEBUG] provider.has(${componentId}): ${provider2.has(componentId)}`));
|
|
9983
10335
|
}
|
|
9984
10336
|
if (provider2.has(componentId)) {
|
|
9985
10337
|
try {
|
|
9986
10338
|
const result = await provider2.installComponent(componentId, projectRoot);
|
|
9987
|
-
if (DEBUG4) console.log(
|
|
10339
|
+
if (DEBUG4) console.log(chalk14.gray(` [DEBUG] installComponent result: ${result.success}`));
|
|
9988
10340
|
if (result.success && result.componentDef) {
|
|
9989
10341
|
if (!cm.read(componentId)) {
|
|
9990
10342
|
if (DEBUG4)
|
|
9991
10343
|
console.log(
|
|
9992
|
-
|
|
10344
|
+
chalk14.gray(` [DEBUG] Registering ${result.componentDef.id} (${result.componentDef.name})`)
|
|
9993
10345
|
);
|
|
9994
10346
|
const regResult = await cm.register(result.componentDef);
|
|
9995
10347
|
if (DEBUG4) {
|
|
9996
10348
|
console.log(
|
|
9997
|
-
|
|
10349
|
+
chalk14.gray(
|
|
9998
10350
|
` [DEBUG] Register result: ${regResult.success ? "SUCCESS" : "FAILED"}${!regResult.success && regResult.message ? ` - ${regResult.message}` : ""}`
|
|
9999
10351
|
)
|
|
10000
10352
|
);
|
|
10001
10353
|
}
|
|
10002
10354
|
if (regResult.success) {
|
|
10003
10355
|
preflightInstalledIds.push(result.componentDef.id);
|
|
10004
|
-
console.log(
|
|
10356
|
+
console.log(chalk14.green(` \u2728 Auto-installed ${result.componentDef.name} component`));
|
|
10005
10357
|
dsm.updateConfig(regResult.config);
|
|
10006
10358
|
cm.updateConfig(regResult.config);
|
|
10007
10359
|
pm.updateConfig(regResult.config);
|
|
10008
10360
|
}
|
|
10009
10361
|
} else {
|
|
10010
10362
|
preflightInstalledIds.push(result.componentDef.id);
|
|
10011
|
-
console.log(
|
|
10363
|
+
console.log(chalk14.green(` \u2728 Re-installed ${result.componentDef.name} component (file was missing)`));
|
|
10012
10364
|
}
|
|
10013
10365
|
}
|
|
10014
10366
|
} catch (error) {
|
|
10015
|
-
console.log(
|
|
10016
|
-
console.log(
|
|
10367
|
+
console.log(chalk14.red(` \u274C Failed to install ${componentId}:`));
|
|
10368
|
+
console.log(chalk14.red(` ${error instanceof Error ? error.message : error}`));
|
|
10017
10369
|
if (error instanceof Error && error.stack) {
|
|
10018
|
-
console.log(
|
|
10370
|
+
console.log(chalk14.gray(` ${error.stack.split("\n")[1]}`));
|
|
10019
10371
|
}
|
|
10020
10372
|
}
|
|
10021
10373
|
} else {
|
|
10022
|
-
console.log(
|
|
10374
|
+
console.log(chalk14.yellow(` \u26A0\uFE0F Component ${componentId} not available`));
|
|
10023
10375
|
}
|
|
10024
10376
|
}
|
|
10025
10377
|
console.log("");
|
|
@@ -10030,11 +10382,11 @@ async function chatCommand(message, options) {
|
|
|
10030
10382
|
const toInstallNpm = [...neededPkgs].filter((p) => !installedPkgs.has(p));
|
|
10031
10383
|
if (toInstallNpm.length > 0) {
|
|
10032
10384
|
spinner.stop();
|
|
10033
|
-
console.log(
|
|
10385
|
+
console.log(chalk14.cyan(`
|
|
10034
10386
|
\u{1F4E6} Auto-installing missing dependencies: ${toInstallNpm.join(", ")}
|
|
10035
10387
|
`));
|
|
10036
10388
|
const ok = await installPackages(projectRoot, toInstallNpm);
|
|
10037
|
-
if (!ok) console.log(
|
|
10389
|
+
if (!ok) console.log(chalk14.yellow(` Run manually: npm install ${toInstallNpm.join(" ")}
|
|
10038
10390
|
`));
|
|
10039
10391
|
spinner.start("Applying modifications...");
|
|
10040
10392
|
}
|
|
@@ -10045,7 +10397,7 @@ async function chatCommand(message, options) {
|
|
|
10045
10397
|
if (componentId && preflightComponentIds.has(componentId)) {
|
|
10046
10398
|
if (DEBUG4) {
|
|
10047
10399
|
console.log(
|
|
10048
|
-
|
|
10400
|
+
chalk14.gray(`[DEBUG] Filtered duplicate add-component: ${componentId} (already installed in pre-flight)`)
|
|
10049
10401
|
);
|
|
10050
10402
|
}
|
|
10051
10403
|
return false;
|
|
@@ -10054,11 +10406,11 @@ async function chatCommand(message, options) {
|
|
|
10054
10406
|
return true;
|
|
10055
10407
|
});
|
|
10056
10408
|
if (DEBUG4 && preflightComponentIds.size > 0) {
|
|
10057
|
-
console.log(
|
|
10409
|
+
console.log(chalk14.gray(`[DEBUG] Remaining requests after filtering: ${normalizedRequests.length}`));
|
|
10058
10410
|
}
|
|
10059
10411
|
try {
|
|
10060
10412
|
createBackup(projectRoot);
|
|
10061
|
-
if (DEBUG4) console.log(
|
|
10413
|
+
if (DEBUG4) console.log(chalk14.dim("[backup] Created snapshot"));
|
|
10062
10414
|
} catch {
|
|
10063
10415
|
}
|
|
10064
10416
|
const navBefore = takeNavSnapshot(
|
|
@@ -10113,10 +10465,10 @@ async function chatCommand(message, options) {
|
|
|
10113
10465
|
const SCAFFOLD_AI_LIMIT = 10;
|
|
10114
10466
|
if (missingRoutes.length > 0 && missingRoutes.length <= SCAFFOLD_AI_LIMIT) {
|
|
10115
10467
|
spinner.stop();
|
|
10116
|
-
console.log(
|
|
10468
|
+
console.log(chalk14.cyan(`
|
|
10117
10469
|
\u{1F517} Auto-scaffolding ${missingRoutes.length} linked page(s)...`));
|
|
10118
10470
|
console.log(
|
|
10119
|
-
|
|
10471
|
+
chalk14.dim(
|
|
10120
10472
|
` (${missingRoutes.length} additional AI call(s) \u2014 disable with settings.autoScaffold: false in config)
|
|
10121
10473
|
`
|
|
10122
10474
|
)
|
|
@@ -10153,7 +10505,7 @@ async function chatCommand(message, options) {
|
|
|
10153
10505
|
}
|
|
10154
10506
|
} catch (err) {
|
|
10155
10507
|
scaffoldSpinner.warn(` Could not scaffold "${pageName}" (${linkedRoute}) \u2014 skipped`);
|
|
10156
|
-
if (DEBUG4) console.log(
|
|
10508
|
+
if (DEBUG4) console.log(chalk14.dim(` ${err instanceof Error ? err.message : "unknown error"}`));
|
|
10157
10509
|
}
|
|
10158
10510
|
}
|
|
10159
10511
|
console.log("");
|
|
@@ -10161,7 +10513,7 @@ async function chatCommand(message, options) {
|
|
|
10161
10513
|
} else if (missingRoutes.length > SCAFFOLD_AI_LIMIT) {
|
|
10162
10514
|
spinner.stop();
|
|
10163
10515
|
console.log(
|
|
10164
|
-
|
|
10516
|
+
chalk14.yellow(
|
|
10165
10517
|
`
|
|
10166
10518
|
\u26A0 Found ${missingRoutes.length} linked pages \u2014 creating placeholder pages (too many for AI generation).`
|
|
10167
10519
|
)
|
|
@@ -10190,7 +10542,7 @@ async function chatCommand(message, options) {
|
|
|
10190
10542
|
scaffoldedPages.push({ route: linkedRoute, name: `${pageName} (placeholder)` });
|
|
10191
10543
|
}
|
|
10192
10544
|
console.log(
|
|
10193
|
-
|
|
10545
|
+
chalk14.cyan(` Created ${missingRoutes.length} placeholder pages. Use \`coherent chat\` to fill them.
|
|
10194
10546
|
`)
|
|
10195
10547
|
);
|
|
10196
10548
|
spinner.start("Finalizing...");
|
|
@@ -10218,9 +10570,9 @@ async function chatCommand(message, options) {
|
|
|
10218
10570
|
}
|
|
10219
10571
|
}
|
|
10220
10572
|
if (linkIssues.length > 0) {
|
|
10221
|
-
console.log(
|
|
10573
|
+
console.log(chalk14.yellow("\n\u{1F517} Broken internal links:"));
|
|
10222
10574
|
for (const { page, message: message2 } of linkIssues) {
|
|
10223
|
-
console.log(
|
|
10575
|
+
console.log(chalk14.dim(` ${page}: ${message2}`));
|
|
10224
10576
|
}
|
|
10225
10577
|
}
|
|
10226
10578
|
}
|
|
@@ -10233,7 +10585,7 @@ async function chatCommand(message, options) {
|
|
|
10233
10585
|
if (latestConfig.theme.defaultMode !== targetMode) {
|
|
10234
10586
|
latestConfig.theme.defaultMode = targetMode;
|
|
10235
10587
|
dsm.updateConfig(latestConfig);
|
|
10236
|
-
if (DEBUG4) console.log(
|
|
10588
|
+
if (DEBUG4) console.log(chalk14.dim(` [theme] Set defaultMode to "${targetMode}"`));
|
|
10237
10589
|
}
|
|
10238
10590
|
const layoutPath = resolve9(projectRoot, "app", "layout.tsx");
|
|
10239
10591
|
try {
|
|
@@ -10241,11 +10593,11 @@ async function chatCommand(message, options) {
|
|
|
10241
10593
|
if (targetMode === "dark" && !layoutCode.includes('className="dark"')) {
|
|
10242
10594
|
layoutCode = layoutCode.replace(/<html\s+lang="en"/, '<html lang="en" className="dark"');
|
|
10243
10595
|
await writeFile(layoutPath, layoutCode);
|
|
10244
|
-
console.log(
|
|
10596
|
+
console.log(chalk14.dim(` \u{1F319} Applied dark theme to layout`));
|
|
10245
10597
|
} else if (targetMode === "light" && layoutCode.includes('className="dark"')) {
|
|
10246
10598
|
layoutCode = layoutCode.replace(' className="dark"', "");
|
|
10247
10599
|
await writeFile(layoutPath, layoutCode);
|
|
10248
|
-
console.log(
|
|
10600
|
+
console.log(chalk14.dim(` \u2600\uFE0F Applied light theme to layout`));
|
|
10249
10601
|
}
|
|
10250
10602
|
} catch {
|
|
10251
10603
|
}
|
|
@@ -10277,7 +10629,7 @@ async function chatCommand(message, options) {
|
|
|
10277
10629
|
}
|
|
10278
10630
|
const finalDeps = await scanAndInstallSharedDeps(projectRoot);
|
|
10279
10631
|
if (finalDeps.length > 0) {
|
|
10280
|
-
console.log(
|
|
10632
|
+
console.log(chalk14.dim(` Auto-installed shared deps: ${finalDeps.join(", ")}`));
|
|
10281
10633
|
}
|
|
10282
10634
|
try {
|
|
10283
10635
|
fixGlobalsCss(projectRoot, updatedConfig);
|
|
@@ -10301,7 +10653,7 @@ async function chatCommand(message, options) {
|
|
|
10301
10653
|
}
|
|
10302
10654
|
await saveHashes(projectRoot, updatedHashes);
|
|
10303
10655
|
} catch {
|
|
10304
|
-
if (DEBUG4) console.log(
|
|
10656
|
+
if (DEBUG4) console.log(chalk14.dim("[hashes] Could not save file hashes"));
|
|
10305
10657
|
}
|
|
10306
10658
|
const successfulPairs = normalizedRequests.map((request, index) => ({ request, result: results[index] })).filter(({ result }) => result.success);
|
|
10307
10659
|
if (successfulPairs.length > 0) {
|
|
@@ -10318,9 +10670,9 @@ async function chatCommand(message, options) {
|
|
|
10318
10670
|
const preflightNames = preflightInstalledIds.map((id) => updatedConfig.components.find((c) => c.id === id)?.name).filter(Boolean);
|
|
10319
10671
|
showPreview(normalizedRequests, results, updatedConfig, preflightNames);
|
|
10320
10672
|
if (scaffoldedPages.length > 0) {
|
|
10321
|
-
console.log(
|
|
10673
|
+
console.log(chalk14.cyan("\u{1F517} Auto-scaffolded linked pages:"));
|
|
10322
10674
|
scaffoldedPages.forEach(({ route, name }) => {
|
|
10323
|
-
console.log(
|
|
10675
|
+
console.log(chalk14.white(` \u2728 ${name} \u2192 ${route}`));
|
|
10324
10676
|
});
|
|
10325
10677
|
console.log("");
|
|
10326
10678
|
}
|
|
@@ -10342,58 +10694,58 @@ ${uxRecommendations}
|
|
|
10342
10694
|
);
|
|
10343
10695
|
}
|
|
10344
10696
|
await appendFile(recPath, section);
|
|
10345
|
-
console.log(
|
|
10697
|
+
console.log(chalk14.cyan("\n\u{1F4CB} UX Recommendations:"));
|
|
10346
10698
|
for (const line of uxRecommendations.split("\n").filter(Boolean)) {
|
|
10347
|
-
console.log(
|
|
10699
|
+
console.log(chalk14.dim(` ${line}`));
|
|
10348
10700
|
}
|
|
10349
|
-
console.log(
|
|
10701
|
+
console.log(chalk14.dim(" \u2192 Saved to /design-system/docs/recommendations"));
|
|
10350
10702
|
} catch (e) {
|
|
10351
10703
|
console.log(
|
|
10352
|
-
|
|
10704
|
+
chalk14.yellow("\n\u26A0\uFE0F Could not write recommendations.md: " + (e instanceof Error ? e.message : String(e)))
|
|
10353
10705
|
);
|
|
10354
|
-
console.log(
|
|
10706
|
+
console.log(chalk14.dim("Recommendations:\n") + uxRecommendations);
|
|
10355
10707
|
}
|
|
10356
10708
|
}
|
|
10357
10709
|
} catch (error) {
|
|
10358
10710
|
spinner.fail("Chat command failed");
|
|
10359
|
-
console.error(
|
|
10711
|
+
console.error(chalk14.red("\n\u2716 Chat command failed"));
|
|
10360
10712
|
const zodError = error;
|
|
10361
10713
|
const issues = zodError.issues || error.errors;
|
|
10362
10714
|
if (issues && Array.isArray(issues)) {
|
|
10363
|
-
console.log(
|
|
10715
|
+
console.log(chalk14.yellow("\n\u26A0\uFE0F AI generated incomplete data. Missing or invalid fields:"));
|
|
10364
10716
|
issues.forEach((err) => {
|
|
10365
|
-
console.log(
|
|
10717
|
+
console.log(chalk14.gray(` \u2022 ${err.path.join(".")}: ${err.message}`));
|
|
10366
10718
|
});
|
|
10367
|
-
console.log(
|
|
10368
|
-
console.log(
|
|
10369
|
-
console.log(
|
|
10719
|
+
console.log(chalk14.cyan("\n\u{1F4A1} Try being more specific, e.g.:"));
|
|
10720
|
+
console.log(chalk14.white(' coherent chat "add a dashboard page with hero section using Button component"'));
|
|
10721
|
+
console.log(chalk14.white(' coherent chat "add pricing page"'));
|
|
10370
10722
|
} else if (error instanceof Error) {
|
|
10371
|
-
console.error(
|
|
10723
|
+
console.error(chalk14.red(error.message));
|
|
10372
10724
|
if (error.message.includes("Unterminated string") || error.message.includes("Unexpected end of JSON") || error.message.includes("Failed to parse modification") && error.message.includes("JSON")) {
|
|
10373
10725
|
console.log(
|
|
10374
|
-
|
|
10726
|
+
chalk14.yellow("\n\u{1F4A1} The AI response was too large or contained invalid JSON. Try splitting your request:")
|
|
10375
10727
|
);
|
|
10376
|
-
console.log(
|
|
10377
|
-
console.log(
|
|
10378
|
-
console.log(
|
|
10728
|
+
console.log(chalk14.white(' coherent chat "add dashboard page with stats and recent activity"'));
|
|
10729
|
+
console.log(chalk14.white(' coherent chat "add account page"'));
|
|
10730
|
+
console.log(chalk14.white(' coherent chat "add settings page"'));
|
|
10379
10731
|
} else if (error.message.includes("API key not found") || error.message.includes("ANTHROPIC_API_KEY") || error.message.includes("OPENAI_API_KEY")) {
|
|
10380
10732
|
const isOpenAI = error.message.includes("OpenAI") || typeof provider !== "undefined" && provider === "openai";
|
|
10381
10733
|
const providerName = isOpenAI ? "OpenAI" : "Anthropic Claude";
|
|
10382
10734
|
const envVar = isOpenAI ? "OPENAI_API_KEY" : "ANTHROPIC_API_KEY";
|
|
10383
10735
|
const url = isOpenAI ? "https://platform.openai.com" : "https://console.anthropic.com";
|
|
10384
|
-
console.log(
|
|
10385
|
-
console.log(
|
|
10386
|
-
console.log(
|
|
10387
|
-
console.log(
|
|
10388
|
-
console.log(
|
|
10389
|
-
console.log(
|
|
10736
|
+
console.log(chalk14.yellow("\n\u{1F4A1} Setup Instructions:"));
|
|
10737
|
+
console.log(chalk14.dim(` 1. Get your ${providerName} API key from: ${url}`));
|
|
10738
|
+
console.log(chalk14.dim(" 2. Create a .env file in the current directory:"));
|
|
10739
|
+
console.log(chalk14.cyan(` echo "${envVar}=your_key_here" > .env`));
|
|
10740
|
+
console.log(chalk14.dim(" 3. Or export it in your shell:"));
|
|
10741
|
+
console.log(chalk14.cyan(` export ${envVar}=your_key_here`));
|
|
10390
10742
|
if (isOpenAI) {
|
|
10391
|
-
console.log(
|
|
10392
|
-
console.log(
|
|
10743
|
+
console.log(chalk14.dim('\n Also ensure "openai" package is installed:'));
|
|
10744
|
+
console.log(chalk14.cyan(" npm install openai"));
|
|
10393
10745
|
}
|
|
10394
10746
|
}
|
|
10395
10747
|
} else {
|
|
10396
|
-
console.error(
|
|
10748
|
+
console.error(chalk14.red("Unknown error occurred"));
|
|
10397
10749
|
}
|
|
10398
10750
|
console.log("");
|
|
10399
10751
|
if (options._throwOnError) {
|
|
@@ -10406,7 +10758,7 @@ ${uxRecommendations}
|
|
|
10406
10758
|
}
|
|
10407
10759
|
|
|
10408
10760
|
// src/commands/preview.ts
|
|
10409
|
-
import
|
|
10761
|
+
import chalk15 from "chalk";
|
|
10410
10762
|
import ora3 from "ora";
|
|
10411
10763
|
import { spawn } from "child_process";
|
|
10412
10764
|
import { existsSync as existsSync19, rmSync as rmSync3, readFileSync as readFileSync14, writeFileSync as writeFileSync10, readdirSync as readdirSync5 } from "fs";
|
|
@@ -10746,13 +11098,13 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
10746
11098
|
return;
|
|
10747
11099
|
}
|
|
10748
11100
|
const config2 = getWatcherConfig(projectRoot);
|
|
10749
|
-
const
|
|
11101
|
+
const chalk34 = (await import("chalk")).default;
|
|
10750
11102
|
if (config2.autoInstall) {
|
|
10751
11103
|
const missing = findMissingPackagesInCode(content, projectRoot);
|
|
10752
11104
|
if (missing.length > 0) {
|
|
10753
11105
|
const ok = await installPackages(projectRoot, missing);
|
|
10754
11106
|
if (ok) {
|
|
10755
|
-
console.log(
|
|
11107
|
+
console.log(chalk34.cyan(`
|
|
10756
11108
|
\u{1F527} Auto-installed: ${missing.join(", ")} (needed by ${relativePath})`));
|
|
10757
11109
|
}
|
|
10758
11110
|
}
|
|
@@ -10761,12 +11113,12 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
10761
11113
|
const fixed = sanitizeMetadataStrings(ensureUseClientIfNeeded(content));
|
|
10762
11114
|
if (fixed !== content) {
|
|
10763
11115
|
writeFileSync9(filePath, fixed, "utf-8");
|
|
10764
|
-
console.log(
|
|
11116
|
+
console.log(chalk34.cyan(` \u{1F527} Auto-fixed syntax in ${relativePath}`));
|
|
10765
11117
|
}
|
|
10766
11118
|
}
|
|
10767
11119
|
if (config2.warnNativeElements && hasNativeElements(content)) {
|
|
10768
|
-
console.log(
|
|
10769
|
-
console.log(
|
|
11120
|
+
console.log(chalk34.yellow(` \u26A0 ${relativePath}: uses native HTML elements (<button>, <select>, etc.)`));
|
|
11121
|
+
console.log(chalk34.dim(" Use components from @/components/ui/ instead"));
|
|
10770
11122
|
}
|
|
10771
11123
|
if (config2.warnSharedReuse) {
|
|
10772
11124
|
let manifest;
|
|
@@ -10779,8 +11131,8 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
10779
11131
|
const dupes = findInlineDuplicatesOfShared(content, manifest);
|
|
10780
11132
|
for (const d of dupes) {
|
|
10781
11133
|
const importPath = d.file.replace(/\.tsx$/, "").replace(/^components\/shared\//, "");
|
|
10782
|
-
console.log(
|
|
10783
|
-
console.log(
|
|
11134
|
+
console.log(chalk34.yellow(` \u26A0 ${relativePath}: has inline code similar to ${d.cid} (${d.name})`));
|
|
11135
|
+
console.log(chalk34.dim(` Consider: import { ${d.name} } from "@/components/shared/${importPath}"`));
|
|
10784
11136
|
}
|
|
10785
11137
|
}
|
|
10786
11138
|
}
|
|
@@ -10789,7 +11141,7 @@ async function handleFileDelete(projectRoot, filePath) {
|
|
|
10789
11141
|
const relativePath = relative4(projectRoot, filePath).replace(/\\/g, "/");
|
|
10790
11142
|
if (!relativePath.startsWith("components/") || relativePath.startsWith("components/ui/")) return;
|
|
10791
11143
|
try {
|
|
10792
|
-
const
|
|
11144
|
+
const chalk34 = (await import("chalk")).default;
|
|
10793
11145
|
const manifest = await loadManifest9(projectRoot);
|
|
10794
11146
|
const orphaned = manifest.shared.find((s) => s.file === relativePath);
|
|
10795
11147
|
if (orphaned) {
|
|
@@ -10798,7 +11150,7 @@ async function handleFileDelete(projectRoot, filePath) {
|
|
|
10798
11150
|
shared: manifest.shared.filter((s) => s.id !== orphaned.id)
|
|
10799
11151
|
};
|
|
10800
11152
|
await saveManifest3(projectRoot, cleaned);
|
|
10801
|
-
console.log(
|
|
11153
|
+
console.log(chalk34.cyan(`
|
|
10802
11154
|
\u{1F5D1} Auto-removed ${orphaned.id} (${orphaned.name}) \u2014 file deleted`));
|
|
10803
11155
|
await writeCursorRules(projectRoot);
|
|
10804
11156
|
}
|
|
@@ -10810,7 +11162,7 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
10810
11162
|
if (!relativePath.startsWith("components/") || relativePath.startsWith("components/ui/")) return;
|
|
10811
11163
|
if (!relativePath.endsWith(".tsx") && !relativePath.endsWith(".jsx")) return;
|
|
10812
11164
|
try {
|
|
10813
|
-
const
|
|
11165
|
+
const chalk34 = (await import("chalk")).default;
|
|
10814
11166
|
const manifest = await loadManifest9(projectRoot);
|
|
10815
11167
|
const alreadyRegistered = manifest.shared.some((s) => s.file === relativePath);
|
|
10816
11168
|
if (alreadyRegistered) return;
|
|
@@ -10819,9 +11171,9 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
10819
11171
|
if (exports.length > 0) {
|
|
10820
11172
|
const alreadyByName = exports.every((n) => manifest.shared.some((s) => s.name === n));
|
|
10821
11173
|
if (!alreadyByName) {
|
|
10822
|
-
console.log(
|
|
11174
|
+
console.log(chalk34.cyan(`
|
|
10823
11175
|
\u2139 New component detected: ${exports[0]} in ${relativePath}`));
|
|
10824
|
-
console.log(
|
|
11176
|
+
console.log(chalk34.dim(" Register with: coherent sync"));
|
|
10825
11177
|
}
|
|
10826
11178
|
}
|
|
10827
11179
|
} catch {
|
|
@@ -10908,17 +11260,17 @@ function clearStaleCache(projectRoot) {
|
|
|
10908
11260
|
const nextDir = join14(projectRoot, ".next");
|
|
10909
11261
|
if (existsSync19(nextDir)) {
|
|
10910
11262
|
rmSync3(nextDir, { recursive: true, force: true });
|
|
10911
|
-
console.log(
|
|
11263
|
+
console.log(chalk15.dim(" \u2714 Cleared stale build cache"));
|
|
10912
11264
|
}
|
|
10913
11265
|
}
|
|
10914
11266
|
async function preflightDependencyCheck(projectRoot) {
|
|
10915
11267
|
const missing = await findMissingPackages(projectRoot);
|
|
10916
11268
|
if (missing.length === 0) return;
|
|
10917
|
-
console.log(
|
|
11269
|
+
console.log(chalk15.cyan(`
|
|
10918
11270
|
Auto-installing missing dependencies: ${missing.join(", ")}`));
|
|
10919
11271
|
const ok = await installPackages(projectRoot, missing);
|
|
10920
|
-
if (ok) console.log(
|
|
10921
|
-
else console.log(
|
|
11272
|
+
if (ok) console.log(chalk15.dim(" \u2714 Installed"));
|
|
11273
|
+
else console.log(chalk15.yellow(` Run manually: npm install ${missing.join(" ")}`));
|
|
10922
11274
|
}
|
|
10923
11275
|
async function listPageFiles(appDir) {
|
|
10924
11276
|
const out = [];
|
|
@@ -10942,7 +11294,7 @@ async function validateSyntax(projectRoot) {
|
|
|
10942
11294
|
const fixed = fixUnescapedLtInJsx(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)));
|
|
10943
11295
|
if (fixed !== content) {
|
|
10944
11296
|
writeFileSync10(file, fixed, "utf-8");
|
|
10945
|
-
console.log(
|
|
11297
|
+
console.log(chalk15.dim(` \u2714 Auto-fixed syntax: ${file.replace(projectRoot, ".").replace(/^\.[/\\]/, "")}`));
|
|
10946
11298
|
}
|
|
10947
11299
|
}
|
|
10948
11300
|
}
|
|
@@ -10984,7 +11336,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10984
11336
|
try {
|
|
10985
11337
|
const result = await provider.installComponent(componentId, projectRoot);
|
|
10986
11338
|
if (result.success) {
|
|
10987
|
-
console.log(
|
|
11339
|
+
console.log(chalk15.dim(` \u2714 Installed missing ${componentId}.tsx`));
|
|
10988
11340
|
}
|
|
10989
11341
|
} catch {
|
|
10990
11342
|
}
|
|
@@ -10996,7 +11348,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10996
11348
|
mkdirSync10(uiDir, { recursive: true });
|
|
10997
11349
|
const newContent = await generator.generate(def);
|
|
10998
11350
|
writeFileSync10(componentFile, newContent, "utf-8");
|
|
10999
|
-
console.log(
|
|
11351
|
+
console.log(chalk15.dim(` \u2714 Created missing ${componentId}.tsx`));
|
|
11000
11352
|
} catch {
|
|
11001
11353
|
}
|
|
11002
11354
|
}
|
|
@@ -11019,7 +11371,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
11019
11371
|
try {
|
|
11020
11372
|
const result = await provider.installComponent(componentId, projectRoot, { force: true });
|
|
11021
11373
|
if (result.success) {
|
|
11022
|
-
console.log(
|
|
11374
|
+
console.log(chalk15.dim(` \u2714 Reinstalled ${componentId}.tsx (added missing exports: ${missing.join(", ")})`));
|
|
11023
11375
|
}
|
|
11024
11376
|
} catch {
|
|
11025
11377
|
}
|
|
@@ -11029,7 +11381,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
11029
11381
|
try {
|
|
11030
11382
|
const newContent = await generator.generate(def);
|
|
11031
11383
|
writeFileSync10(componentFile, newContent, "utf-8");
|
|
11032
|
-
console.log(
|
|
11384
|
+
console.log(chalk15.dim(` \u2714 Regenerated ${componentId}.tsx (added missing exports: ${missing.join(", ")})`));
|
|
11033
11385
|
} catch {
|
|
11034
11386
|
}
|
|
11035
11387
|
}
|
|
@@ -11088,14 +11440,14 @@ async function healthCheck(port) {
|
|
|
11088
11440
|
try {
|
|
11089
11441
|
const res = await fetch(`http://localhost:${port}`);
|
|
11090
11442
|
if (res.status === 200) {
|
|
11091
|
-
console.log(
|
|
11443
|
+
console.log(chalk15.green(`
|
|
11092
11444
|
\u2705 Preview healthy at http://localhost:${port}`));
|
|
11093
11445
|
} else {
|
|
11094
|
-
console.log(
|
|
11446
|
+
console.log(chalk15.yellow(`
|
|
11095
11447
|
\u26A0 Preview returned ${res.status}. Run: coherent fix`));
|
|
11096
11448
|
}
|
|
11097
11449
|
} catch {
|
|
11098
|
-
console.log(
|
|
11450
|
+
console.log(chalk15.yellow(`
|
|
11099
11451
|
\u26A0 Preview not responding. Run: coherent fix`));
|
|
11100
11452
|
}
|
|
11101
11453
|
}
|
|
@@ -11134,29 +11486,29 @@ function launchWithMonitoring(projectRoot, restarts) {
|
|
|
11134
11486
|
const shadcnId = extractShadcnComponentFromModuleNotFound(msg);
|
|
11135
11487
|
if (shadcnId && !installingSet.has(shadcnId)) {
|
|
11136
11488
|
installingSet.add(shadcnId);
|
|
11137
|
-
console.log(
|
|
11489
|
+
console.log(chalk15.yellow(`
|
|
11138
11490
|
\u26A0 Missing component detected: ${shadcnId}`));
|
|
11139
|
-
console.log(
|
|
11491
|
+
console.log(chalk15.cyan(" Auto-installing..."));
|
|
11140
11492
|
autoInstallShadcnComponent(shadcnId, projectRoot).then((ok) => {
|
|
11141
11493
|
if (ok) {
|
|
11142
|
-
console.log(
|
|
11494
|
+
console.log(chalk15.green(` \u2714 Installed ${shadcnId}.tsx. Restarting...`));
|
|
11143
11495
|
intentionalRestart = true;
|
|
11144
11496
|
server.kill("SIGTERM");
|
|
11145
11497
|
launchWithMonitoring(projectRoot, restarts + 1).then(resolvePromise).catch(rejectPromise);
|
|
11146
11498
|
} else {
|
|
11147
|
-
console.log(
|
|
11499
|
+
console.log(chalk15.red(` \u2716 Could not install ${shadcnId}. Run: npx shadcn@latest add ${shadcnId}`));
|
|
11148
11500
|
}
|
|
11149
11501
|
});
|
|
11150
11502
|
} else if (!shadcnId) {
|
|
11151
11503
|
const pkg = extractPackageFromModuleNotFound(msg);
|
|
11152
11504
|
if (pkg && !installingSet.has(pkg)) {
|
|
11153
11505
|
installingSet.add(pkg);
|
|
11154
|
-
console.log(
|
|
11506
|
+
console.log(chalk15.yellow(`
|
|
11155
11507
|
\u26A0 Missing package detected: ${pkg}`));
|
|
11156
|
-
console.log(
|
|
11508
|
+
console.log(chalk15.cyan(" Auto-installing..."));
|
|
11157
11509
|
installPackages(projectRoot, [pkg]).then((ok) => {
|
|
11158
11510
|
if (ok) {
|
|
11159
|
-
console.log(
|
|
11511
|
+
console.log(chalk15.green(` \u2714 Installed ${pkg}. Restarting...`));
|
|
11160
11512
|
intentionalRestart = true;
|
|
11161
11513
|
server.kill("SIGTERM");
|
|
11162
11514
|
launchWithMonitoring(projectRoot, restarts + 1).then(resolvePromise).catch(rejectPromise);
|
|
@@ -11166,19 +11518,19 @@ function launchWithMonitoring(projectRoot, restarts) {
|
|
|
11166
11518
|
}
|
|
11167
11519
|
}
|
|
11168
11520
|
if (msg.includes("Failed to compile")) {
|
|
11169
|
-
console.log(
|
|
11170
|
-
console.log(
|
|
11171
|
-
console.log(
|
|
11521
|
+
console.log(chalk15.yellow("\n\u26A0 Compilation error detected."));
|
|
11522
|
+
console.log(chalk15.dim(' Hint: run "coherent fix" in another terminal to auto-fix'));
|
|
11523
|
+
console.log(chalk15.dim(' Or: coherent chat "fix the broken page"'));
|
|
11172
11524
|
}
|
|
11173
11525
|
});
|
|
11174
11526
|
server.on("exit", (code) => {
|
|
11175
11527
|
if (intentionalRestart) return;
|
|
11176
11528
|
if (code !== 0 && code !== null) {
|
|
11177
|
-
console.log(
|
|
11529
|
+
console.log(chalk15.red(`
|
|
11178
11530
|
\u274C Dev server exited with code ${code}`));
|
|
11179
|
-
console.log(
|
|
11531
|
+
console.log(chalk15.dim(' Check the output above. Fix and run "coherent preview" again.\n'));
|
|
11180
11532
|
} else {
|
|
11181
|
-
console.log(
|
|
11533
|
+
console.log(chalk15.dim("\n\u{1F44B} Dev server stopped"));
|
|
11182
11534
|
}
|
|
11183
11535
|
process.exit(code ?? 0);
|
|
11184
11536
|
});
|
|
@@ -11187,7 +11539,7 @@ function launchWithMonitoring(projectRoot, restarts) {
|
|
|
11187
11539
|
});
|
|
11188
11540
|
const shutdown = () => {
|
|
11189
11541
|
closeWatcher();
|
|
11190
|
-
console.log(
|
|
11542
|
+
console.log(chalk15.dim("\n\n\u{1F6D1} Stopping dev server..."));
|
|
11191
11543
|
server.kill("SIGTERM");
|
|
11192
11544
|
};
|
|
11193
11545
|
process.on("SIGINT", shutdown);
|
|
@@ -11199,9 +11551,9 @@ async function openBrowser(url) {
|
|
|
11199
11551
|
const open = await import("open");
|
|
11200
11552
|
await open.default(url);
|
|
11201
11553
|
} catch (error) {
|
|
11202
|
-
console.log(
|
|
11554
|
+
console.log(chalk15.yellow(`
|
|
11203
11555
|
\u26A0\uFE0F Could not open browser automatically`));
|
|
11204
|
-
console.log(
|
|
11556
|
+
console.log(chalk15.dim(` Please open ${url} manually`));
|
|
11205
11557
|
}
|
|
11206
11558
|
}
|
|
11207
11559
|
function startDevServer(projectRoot) {
|
|
@@ -11232,8 +11584,8 @@ async function previewCommand() {
|
|
|
11232
11584
|
try {
|
|
11233
11585
|
if (!checkProjectInitialized(projectRoot)) {
|
|
11234
11586
|
spinner.fail("Project not initialized");
|
|
11235
|
-
console.log(
|
|
11236
|
-
console.log(
|
|
11587
|
+
console.log(chalk15.red("\n\u274C Project not found"));
|
|
11588
|
+
console.log(chalk15.dim('Run "coherent init" first to create a project.'));
|
|
11237
11589
|
process.exit(1);
|
|
11238
11590
|
}
|
|
11239
11591
|
spinner.text = "Checking dependencies...";
|
|
@@ -11241,16 +11593,16 @@ async function previewCommand() {
|
|
|
11241
11593
|
spinner.warn("Dependencies not installed");
|
|
11242
11594
|
const pm = getPackageManager(projectRoot);
|
|
11243
11595
|
const installCommand = pm === "pnpm" ? "pnpm install" : "npm install";
|
|
11244
|
-
console.log(
|
|
11245
|
-
console.log(
|
|
11596
|
+
console.log(chalk15.yellow("\n\u26A0\uFE0F Dependencies not installed"));
|
|
11597
|
+
console.log(chalk15.cyan(`
|
|
11246
11598
|
Running ${installCommand}...
|
|
11247
11599
|
`));
|
|
11248
11600
|
const ok = await runInstall(projectRoot);
|
|
11249
11601
|
if (!ok) {
|
|
11250
|
-
console.error(
|
|
11602
|
+
console.error(chalk15.red('\n\u274C Install failed. Fix errors above and run "coherent preview" again.\n'));
|
|
11251
11603
|
process.exit(1);
|
|
11252
11604
|
}
|
|
11253
|
-
console.log(
|
|
11605
|
+
console.log(chalk15.green("\n\u2705 Dependencies installed\n"));
|
|
11254
11606
|
} else {
|
|
11255
11607
|
spinner.succeed("Project ready");
|
|
11256
11608
|
}
|
|
@@ -11273,27 +11625,27 @@ async function previewCommand() {
|
|
|
11273
11625
|
await fixMissingComponentExports(projectRoot);
|
|
11274
11626
|
await backfillPageAnalysis(projectRoot);
|
|
11275
11627
|
spinner.succeed("Project ready");
|
|
11276
|
-
console.log(
|
|
11277
|
-
console.log(
|
|
11628
|
+
console.log(chalk15.dim(" \u{1F4A1} Edited files manually? Run `coherent sync` to update the Design System.\n"));
|
|
11629
|
+
console.log(chalk15.blue("\n\u{1F680} Starting Next.js dev server...\n"));
|
|
11278
11630
|
await launchWithMonitoring(projectRoot, 0);
|
|
11279
11631
|
} catch (error) {
|
|
11280
11632
|
spinner.fail("Failed to start dev server");
|
|
11281
11633
|
if (error instanceof Error) {
|
|
11282
|
-
console.error(
|
|
11634
|
+
console.error(chalk15.red(`
|
|
11283
11635
|
\u274C ${error.message}`));
|
|
11284
11636
|
if (error.message.includes("package.json")) {
|
|
11285
|
-
console.log(
|
|
11286
|
-
console.log(
|
|
11637
|
+
console.log(chalk15.yellow("\n\u{1F4A1} Tip: Make sure you're in a Coherent project directory."));
|
|
11638
|
+
console.log(chalk15.dim(' Run "coherent init" to create a new project.'));
|
|
11287
11639
|
}
|
|
11288
11640
|
} else {
|
|
11289
|
-
console.error(
|
|
11641
|
+
console.error(chalk15.red("Unknown error occurred"));
|
|
11290
11642
|
}
|
|
11291
11643
|
process.exit(1);
|
|
11292
11644
|
}
|
|
11293
11645
|
}
|
|
11294
11646
|
|
|
11295
11647
|
// src/commands/export.ts
|
|
11296
|
-
import
|
|
11648
|
+
import chalk16 from "chalk";
|
|
11297
11649
|
import ora4 from "ora";
|
|
11298
11650
|
import { spawn as spawn2 } from "child_process";
|
|
11299
11651
|
import { existsSync as existsSync20, rmSync as rmSync4, readdirSync as readdirSync6 } from "fs";
|
|
@@ -11572,7 +11924,7 @@ async function exportCommand(options = {}) {
|
|
|
11572
11924
|
const missingDeps = await findMissingDepsInExport(outputDir);
|
|
11573
11925
|
if (missingDeps.length > 0) {
|
|
11574
11926
|
console.log(
|
|
11575
|
-
|
|
11927
|
+
chalk16.yellow(
|
|
11576
11928
|
"\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"
|
|
11577
11929
|
)
|
|
11578
11930
|
);
|
|
@@ -11588,7 +11940,7 @@ async function exportCommand(options = {}) {
|
|
|
11588
11940
|
spinner.succeed("Dependencies installed");
|
|
11589
11941
|
await ensureReadmeDeploySection(outputDir);
|
|
11590
11942
|
await patchNextConfigForExport(outputDir);
|
|
11591
|
-
console.log(
|
|
11943
|
+
console.log(chalk16.dim("\n Tip: run `coherent check` before export to catch quality issues.\n"));
|
|
11592
11944
|
let buildOk = false;
|
|
11593
11945
|
if (doBuild) {
|
|
11594
11946
|
spinner.start("Running next build...");
|
|
@@ -11598,7 +11950,7 @@ async function exportCommand(options = {}) {
|
|
|
11598
11950
|
spinner.succeed("Build: success");
|
|
11599
11951
|
} catch (e) {
|
|
11600
11952
|
spinner.fail("Build failed");
|
|
11601
|
-
if (e instanceof Error) console.error(
|
|
11953
|
+
if (e instanceof Error) console.error(chalk16.red(e.message));
|
|
11602
11954
|
}
|
|
11603
11955
|
} else {
|
|
11604
11956
|
buildOk = true;
|
|
@@ -11606,23 +11958,23 @@ async function exportCommand(options = {}) {
|
|
|
11606
11958
|
const pageCount = await countPages(outputDir);
|
|
11607
11959
|
const componentCount = countComponents(outputDir);
|
|
11608
11960
|
spinner.stop();
|
|
11609
|
-
console.log(
|
|
11610
|
-
console.log(
|
|
11611
|
-
console.log(
|
|
11612
|
-
console.log(
|
|
11961
|
+
console.log(chalk16.green("\n\u2705 Exported to " + outputDir + "\n"));
|
|
11962
|
+
console.log(chalk16.blue(" Pages: " + pageCount));
|
|
11963
|
+
console.log(chalk16.blue(" Components: " + componentCount + " (base + shared)"));
|
|
11964
|
+
console.log(chalk16.blue(" Build: " + (doBuild ? buildOk ? "success" : "failed" : "skipped (--no-build)")));
|
|
11613
11965
|
console.log("");
|
|
11614
|
-
console.log(
|
|
11615
|
-
console.log(
|
|
11966
|
+
console.log(chalk16.dim(" Deploy: npx vercel " + outputDir));
|
|
11967
|
+
console.log(chalk16.dim(" or: npx netlify deploy --dir " + outputDir + "/.next"));
|
|
11616
11968
|
console.log("");
|
|
11617
11969
|
} catch (error) {
|
|
11618
11970
|
spinner.fail("Export failed");
|
|
11619
|
-
if (error instanceof Error) console.error(
|
|
11971
|
+
if (error instanceof Error) console.error(chalk16.red("\n\u274C " + error.message));
|
|
11620
11972
|
process.exit(1);
|
|
11621
11973
|
}
|
|
11622
11974
|
}
|
|
11623
11975
|
|
|
11624
11976
|
// src/commands/status.ts
|
|
11625
|
-
import
|
|
11977
|
+
import chalk17 from "chalk";
|
|
11626
11978
|
import { basename } from "path";
|
|
11627
11979
|
import { DesignSystemManager as DesignSystemManager9 } from "@getcoherent/core";
|
|
11628
11980
|
function countTokens(tokens) {
|
|
@@ -11646,58 +11998,58 @@ async function statusCommand() {
|
|
|
11646
11998
|
try {
|
|
11647
11999
|
const project = findConfig();
|
|
11648
12000
|
if (!project) {
|
|
11649
|
-
console.log(
|
|
12001
|
+
console.log(chalk17.yellow("\u26A0\uFE0F Not in a Coherent project\n"));
|
|
11650
12002
|
console.log("Initialize a project:");
|
|
11651
|
-
console.log(
|
|
12003
|
+
console.log(chalk17.white(" $ coherent init\n"));
|
|
11652
12004
|
return;
|
|
11653
12005
|
}
|
|
11654
|
-
console.log(
|
|
11655
|
-
console.log(
|
|
11656
|
-
console.log(
|
|
12006
|
+
console.log(chalk17.cyan("\n\u2728 Current Project\n"));
|
|
12007
|
+
console.log(chalk17.gray("\u{1F4C1} Location: ") + chalk17.white(project.root));
|
|
12008
|
+
console.log(chalk17.gray("\u{1F4C4} Config: ") + chalk17.white(basename(project.configPath)));
|
|
11657
12009
|
console.log("");
|
|
11658
12010
|
try {
|
|
11659
12011
|
const manager = new DesignSystemManager9(project.configPath);
|
|
11660
12012
|
await manager.load();
|
|
11661
12013
|
const config2 = manager.getConfig();
|
|
11662
|
-
console.log(
|
|
12014
|
+
console.log(chalk17.cyan("\u{1F4CA} Statistics:\n"));
|
|
11663
12015
|
const pageCount = Array.isArray(config2.pages) ? config2.pages.length : Object.keys(config2.pages || {}).length;
|
|
11664
|
-
console.log(
|
|
12016
|
+
console.log(chalk17.gray(" Pages: ") + chalk17.white(String(pageCount)));
|
|
11665
12017
|
const componentCount = Array.isArray(config2.components) ? config2.components.length : Object.keys(config2.components || {}).length;
|
|
11666
|
-
console.log(
|
|
12018
|
+
console.log(chalk17.gray(" Components: ") + chalk17.white(String(componentCount)));
|
|
11667
12019
|
const tokenCount = countTokens(config2.tokens);
|
|
11668
|
-
console.log(
|
|
12020
|
+
console.log(chalk17.gray(" Design tokens: ") + chalk17.white(String(tokenCount)));
|
|
11669
12021
|
console.log("");
|
|
11670
12022
|
const recent = readRecentChanges(project.root);
|
|
11671
12023
|
if (recent.length > 0) {
|
|
11672
|
-
console.log(
|
|
12024
|
+
console.log(chalk17.cyan("\u{1F4DD} Recent changes:\n"));
|
|
11673
12025
|
recent.slice(0, 5).forEach((change) => {
|
|
11674
12026
|
const ago = formatTimeAgo(change.timestamp);
|
|
11675
|
-
console.log(
|
|
12027
|
+
console.log(chalk17.gray(" \u2022 ") + chalk17.white(change.description) + chalk17.gray(` (${ago})`));
|
|
11676
12028
|
});
|
|
11677
12029
|
console.log("");
|
|
11678
12030
|
}
|
|
11679
|
-
console.log(
|
|
11680
|
-
console.log(
|
|
11681
|
-
console.log(
|
|
11682
|
-
console.log(
|
|
12031
|
+
console.log(chalk17.cyan("\u{1F680} Quick actions:\n"));
|
|
12032
|
+
console.log(chalk17.white(' $ coherent chat "add new page"'));
|
|
12033
|
+
console.log(chalk17.white(" $ coherent preview"));
|
|
12034
|
+
console.log(chalk17.white(" $ coherent export"));
|
|
11683
12035
|
console.log("");
|
|
11684
12036
|
} catch (error) {
|
|
11685
|
-
console.error(
|
|
12037
|
+
console.error(chalk17.red("Error loading config:"));
|
|
11686
12038
|
if (error instanceof Error) {
|
|
11687
|
-
console.error(
|
|
12039
|
+
console.error(chalk17.red(` ${error.message}`));
|
|
11688
12040
|
} else {
|
|
11689
|
-
console.error(
|
|
12041
|
+
console.error(chalk17.red(" Unknown error"));
|
|
11690
12042
|
}
|
|
11691
12043
|
console.log("");
|
|
11692
12044
|
}
|
|
11693
12045
|
} catch (error) {
|
|
11694
|
-
console.error(
|
|
12046
|
+
console.error(chalk17.red("\u274C Command failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
11695
12047
|
process.exit(1);
|
|
11696
12048
|
}
|
|
11697
12049
|
}
|
|
11698
12050
|
|
|
11699
12051
|
// src/commands/regenerate-docs.ts
|
|
11700
|
-
import
|
|
12052
|
+
import chalk18 from "chalk";
|
|
11701
12053
|
import ora5 from "ora";
|
|
11702
12054
|
import { DesignSystemManager as DesignSystemManager10 } from "@getcoherent/core";
|
|
11703
12055
|
import { ProjectScaffolder as ProjectScaffolder2 } from "@getcoherent/core";
|
|
@@ -11705,9 +12057,9 @@ async function regenerateDocsCommand() {
|
|
|
11705
12057
|
try {
|
|
11706
12058
|
const project = findConfig();
|
|
11707
12059
|
if (!project) {
|
|
11708
|
-
console.log(
|
|
12060
|
+
console.log(chalk18.yellow("\u26A0\uFE0F Not in a Coherent project\n"));
|
|
11709
12061
|
console.log("Run this command from a project root that has design-system.config.ts");
|
|
11710
|
-
console.log(
|
|
12062
|
+
console.log(chalk18.white(" $ coherent init # in an empty folder first\n"));
|
|
11711
12063
|
process.exit(1);
|
|
11712
12064
|
}
|
|
11713
12065
|
const spinner = ora5("Regenerating documentation pages...").start();
|
|
@@ -11719,23 +12071,23 @@ async function regenerateDocsCommand() {
|
|
|
11719
12071
|
await scaffolder.generateDocsPages();
|
|
11720
12072
|
spinner.succeed("Documentation pages updated");
|
|
11721
12073
|
console.log(
|
|
11722
|
-
|
|
12074
|
+
chalk18.gray(
|
|
11723
12075
|
"\nUpdated: app/design-system/docs/ (layout, page, components, tokens, for-designers, recommendations)\n"
|
|
11724
12076
|
)
|
|
11725
12077
|
);
|
|
11726
12078
|
} catch (err) {
|
|
11727
12079
|
spinner.fail("Failed to regenerate docs");
|
|
11728
|
-
console.error(
|
|
12080
|
+
console.error(chalk18.red(err instanceof Error ? err.message : String(err)));
|
|
11729
12081
|
process.exit(1);
|
|
11730
12082
|
}
|
|
11731
12083
|
} catch (error) {
|
|
11732
|
-
console.error(
|
|
12084
|
+
console.error(chalk18.red("\u274C Command failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
11733
12085
|
process.exit(1);
|
|
11734
12086
|
}
|
|
11735
12087
|
}
|
|
11736
12088
|
|
|
11737
12089
|
// src/commands/fix.ts
|
|
11738
|
-
import
|
|
12090
|
+
import chalk19 from "chalk";
|
|
11739
12091
|
import { readdirSync as readdirSync7, readFileSync as readFileSync15, existsSync as existsSync21, writeFileSync as writeFileSync11, rmSync as rmSync5, mkdirSync as mkdirSync7 } from "fs";
|
|
11740
12092
|
import { resolve as resolve12, join as join16 } from "path";
|
|
11741
12093
|
import {
|
|
@@ -11787,31 +12139,31 @@ async function fixCommand(opts = {}) {
|
|
|
11787
12139
|
const fixes = [];
|
|
11788
12140
|
const remaining = [];
|
|
11789
12141
|
if (dryRun) {
|
|
11790
|
-
console.log(
|
|
12142
|
+
console.log(chalk19.cyan("\ncoherent fix --dry-run\n"));
|
|
11791
12143
|
} else {
|
|
11792
|
-
console.log(
|
|
12144
|
+
console.log(chalk19.cyan("\ncoherent fix\n"));
|
|
11793
12145
|
}
|
|
11794
12146
|
if (!skipCache) {
|
|
11795
12147
|
const nextDir = join16(projectRoot, ".next");
|
|
11796
12148
|
if (existsSync21(nextDir)) {
|
|
11797
12149
|
if (!dryRun) rmSync5(nextDir, { recursive: true, force: true });
|
|
11798
12150
|
fixes.push("Cleared build cache");
|
|
11799
|
-
console.log(
|
|
12151
|
+
console.log(chalk19.green(" \u2714 Cleared build cache"));
|
|
11800
12152
|
}
|
|
11801
12153
|
}
|
|
11802
12154
|
const missingPkgs = await findMissingPackages(projectRoot);
|
|
11803
12155
|
if (missingPkgs.length > 0) {
|
|
11804
12156
|
if (dryRun) {
|
|
11805
12157
|
fixes.push(`Would install packages: ${missingPkgs.join(", ")}`);
|
|
11806
|
-
console.log(
|
|
12158
|
+
console.log(chalk19.green(` \u2714 Would install packages: ${missingPkgs.join(", ")}`));
|
|
11807
12159
|
} else {
|
|
11808
12160
|
const ok = await installPackages(projectRoot, missingPkgs);
|
|
11809
12161
|
if (ok) {
|
|
11810
12162
|
fixes.push(`Installed missing packages: ${missingPkgs.join(", ")}`);
|
|
11811
|
-
console.log(
|
|
12163
|
+
console.log(chalk19.green(` \u2714 Installed missing packages: ${missingPkgs.join(", ")}`));
|
|
11812
12164
|
} else {
|
|
11813
12165
|
remaining.push(`Failed to install: ${missingPkgs.join(", ")}. Run: npm install ${missingPkgs.join(" ")}`);
|
|
11814
|
-
console.log(
|
|
12166
|
+
console.log(chalk19.yellow(` \u26A0 Could not install: ${missingPkgs.join(", ")}`));
|
|
11815
12167
|
}
|
|
11816
12168
|
}
|
|
11817
12169
|
}
|
|
@@ -11848,7 +12200,7 @@ async function fixCommand(opts = {}) {
|
|
|
11848
12200
|
if (toInstall.length > 0) {
|
|
11849
12201
|
if (dryRun) {
|
|
11850
12202
|
fixes.push(`Would install components: ${toInstall.join(", ")}`);
|
|
11851
|
-
console.log(
|
|
12203
|
+
console.log(chalk19.green(` \u2714 Would install components: ${toInstall.join(", ")}`));
|
|
11852
12204
|
} else {
|
|
11853
12205
|
let installed = 0;
|
|
11854
12206
|
for (const componentId of toInstall) {
|
|
@@ -11877,14 +12229,14 @@ async function fixCommand(opts = {}) {
|
|
|
11877
12229
|
installed++;
|
|
11878
12230
|
} catch (err) {
|
|
11879
12231
|
console.log(
|
|
11880
|
-
|
|
12232
|
+
chalk19.yellow(` \u26A0 Failed to install ${componentId}: ${err instanceof Error ? err.message : "unknown"}`)
|
|
11881
12233
|
);
|
|
11882
12234
|
}
|
|
11883
12235
|
}
|
|
11884
12236
|
if (installed > 0) {
|
|
11885
12237
|
await dsm.save();
|
|
11886
12238
|
fixes.push(`Installed missing components: ${toInstall.join(", ")}`);
|
|
11887
|
-
console.log(
|
|
12239
|
+
console.log(chalk19.green(` \u2714 Installed missing components: ${toInstall.join(", ")}`));
|
|
11888
12240
|
}
|
|
11889
12241
|
}
|
|
11890
12242
|
}
|
|
@@ -11904,7 +12256,7 @@ async function fixCommand(opts = {}) {
|
|
|
11904
12256
|
if (syntaxFixed > 0) {
|
|
11905
12257
|
const verb = dryRun ? "Would fix" : "Fixed";
|
|
11906
12258
|
fixes.push(`${verb} syntax in ${syntaxFixed} file(s)`);
|
|
11907
|
-
console.log(
|
|
12259
|
+
console.log(chalk19.green(` \u2714 ${verb} syntax: ${syntaxFixed} file(s) (use client, metadata, quotes)`));
|
|
11908
12260
|
}
|
|
11909
12261
|
if (!skipQuality) {
|
|
11910
12262
|
let qualityFixCount = 0;
|
|
@@ -11922,7 +12274,7 @@ async function fixCommand(opts = {}) {
|
|
|
11922
12274
|
const uniqueFixes = [...new Set(qualityFixDetails)];
|
|
11923
12275
|
const verb = dryRun ? "Would fix" : "Fixed";
|
|
11924
12276
|
fixes.push(`${verb} quality in ${qualityFixCount} file(s)`);
|
|
11925
|
-
console.log(
|
|
12277
|
+
console.log(chalk19.green(` \u2714 ${verb} ${uniqueFixes.length} quality issue type(s): ${uniqueFixes.join(", ")}`));
|
|
11926
12278
|
}
|
|
11927
12279
|
}
|
|
11928
12280
|
let totalErrors = 0;
|
|
@@ -11967,13 +12319,13 @@ async function fixCommand(opts = {}) {
|
|
|
11967
12319
|
if (dryRun) {
|
|
11968
12320
|
fixes.push(`Would update ${o.id} path to ${newPath}`);
|
|
11969
12321
|
} else {
|
|
11970
|
-
console.log(
|
|
12322
|
+
console.log(chalk19.green(` \u2714 Updated ${o.id} (${o.name}) path \u2192 ${newPath}`));
|
|
11971
12323
|
}
|
|
11972
12324
|
} else {
|
|
11973
12325
|
if (dryRun) {
|
|
11974
12326
|
fixes.push(`Would remove orphaned ${o.id} (${o.name})`);
|
|
11975
12327
|
} else {
|
|
11976
|
-
console.log(
|
|
12328
|
+
console.log(chalk19.green(` \u2714 Removed orphaned ${o.id} (${o.name}) \u2014 file missing`));
|
|
11977
12329
|
}
|
|
11978
12330
|
}
|
|
11979
12331
|
}
|
|
@@ -11986,7 +12338,7 @@ async function fixCommand(opts = {}) {
|
|
|
11986
12338
|
entry.usedIn = fullActual;
|
|
11987
12339
|
manifestModified = true;
|
|
11988
12340
|
if (!dryRun) {
|
|
11989
|
-
console.log(
|
|
12341
|
+
console.log(chalk19.green(` \u2714 Updated ${entry.id} usedIn: ${fullActual.join(", ") || "none"}`));
|
|
11990
12342
|
}
|
|
11991
12343
|
}
|
|
11992
12344
|
}
|
|
@@ -12004,7 +12356,7 @@ async function fixCommand(opts = {}) {
|
|
|
12004
12356
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
12005
12357
|
});
|
|
12006
12358
|
manifest.nextId++;
|
|
12007
|
-
console.log(
|
|
12359
|
+
console.log(chalk19.green(` \u2714 Registered ${id} (${comp.name}) from ${comp.file}`));
|
|
12008
12360
|
} else {
|
|
12009
12361
|
fixes.push(`Would register ${comp.name} from ${comp.file}`);
|
|
12010
12362
|
}
|
|
@@ -12024,33 +12376,33 @@ async function fixCommand(opts = {}) {
|
|
|
12024
12376
|
} catch {
|
|
12025
12377
|
}
|
|
12026
12378
|
if (fixes.length === 0 && totalErrors === 0 && totalWarnings === 0 && remaining.length === 0) {
|
|
12027
|
-
console.log(
|
|
12028
|
-
console.log(
|
|
12379
|
+
console.log(chalk19.green("\n \u2705 Everything looks good \u2014 no issues found\n"));
|
|
12380
|
+
console.log(chalk19.cyan(" Run: coherent preview\n"));
|
|
12029
12381
|
return;
|
|
12030
12382
|
}
|
|
12031
12383
|
if (fixes.length > 0) console.log("");
|
|
12032
12384
|
if (totalErrors > 0 || totalWarnings > 0 || remaining.length > 0) {
|
|
12033
|
-
console.log(
|
|
12034
|
-
console.log(
|
|
12385
|
+
console.log(chalk19.dim(" \u2500".repeat(25)));
|
|
12386
|
+
console.log(chalk19.yellow(`
|
|
12035
12387
|
Remaining (need manual fix or AI):`));
|
|
12036
12388
|
for (const { path: path4, report } of fileIssues) {
|
|
12037
|
-
console.log(
|
|
12389
|
+
console.log(chalk19.dim(` \u{1F4C4} ${path4}`));
|
|
12038
12390
|
console.log(report);
|
|
12039
12391
|
}
|
|
12040
12392
|
for (const r of remaining) {
|
|
12041
|
-
console.log(
|
|
12393
|
+
console.log(chalk19.yellow(` \u26A0 ${r}`));
|
|
12042
12394
|
}
|
|
12043
12395
|
console.log("");
|
|
12044
12396
|
const parts = [];
|
|
12045
|
-
if (totalErrors > 0) parts.push(
|
|
12046
|
-
if (totalWarnings > 0) parts.push(
|
|
12397
|
+
if (totalErrors > 0) parts.push(chalk19.red(`\u274C ${totalErrors} error(s)`));
|
|
12398
|
+
if (totalWarnings > 0) parts.push(chalk19.yellow(`\u26A0 ${totalWarnings} warning(s)`));
|
|
12047
12399
|
if (parts.length > 0) console.log(` ${parts.join(" ")}`);
|
|
12048
12400
|
}
|
|
12049
|
-
console.log(
|
|
12401
|
+
console.log(chalk19.cyan("\n Run: coherent preview\n"));
|
|
12050
12402
|
}
|
|
12051
12403
|
|
|
12052
12404
|
// src/commands/check.ts
|
|
12053
|
-
import
|
|
12405
|
+
import chalk20 from "chalk";
|
|
12054
12406
|
import { resolve as resolve13 } from "path";
|
|
12055
12407
|
import { readdirSync as readdirSync8, readFileSync as readFileSync16, statSync as statSync2, existsSync as existsSync22 } from "fs";
|
|
12056
12408
|
import { loadManifest as loadManifest11 } from "@getcoherent/core";
|
|
@@ -12098,7 +12450,7 @@ async function checkCommand(opts = {}) {
|
|
|
12098
12450
|
const appDir = resolve13(projectRoot, "app");
|
|
12099
12451
|
const files = findTsxFiles(appDir);
|
|
12100
12452
|
result.pages.total = files.length;
|
|
12101
|
-
if (!opts.json) console.log(
|
|
12453
|
+
if (!opts.json) console.log(chalk20.cyan("\n \u{1F4C4} Pages") + chalk20.dim(` (${files.length} scanned)
|
|
12102
12454
|
`));
|
|
12103
12455
|
const autoFixableTypes = /* @__PURE__ */ new Set([
|
|
12104
12456
|
"RAW_COLOR",
|
|
@@ -12128,7 +12480,7 @@ async function checkCommand(opts = {}) {
|
|
|
12128
12480
|
result.autoFixable += fileAutoFixable;
|
|
12129
12481
|
if (filteredIssues.length === 0) {
|
|
12130
12482
|
result.pages.clean++;
|
|
12131
|
-
if (!opts.json) console.log(
|
|
12483
|
+
if (!opts.json) console.log(chalk20.green(` \u2714 ${relativePath}`) + chalk20.dim(" \u2014 clean"));
|
|
12132
12484
|
continue;
|
|
12133
12485
|
}
|
|
12134
12486
|
if (errors > 0) result.pages.withErrors++;
|
|
@@ -12141,9 +12493,9 @@ async function checkCommand(opts = {}) {
|
|
|
12141
12493
|
});
|
|
12142
12494
|
if (!opts.json) {
|
|
12143
12495
|
const parts = [];
|
|
12144
|
-
if (errors > 0) parts.push(
|
|
12145
|
-
if (warnings > 0) parts.push(
|
|
12146
|
-
console.log(
|
|
12496
|
+
if (errors > 0) parts.push(chalk20.red(`${errors} error(s)`));
|
|
12497
|
+
if (warnings > 0) parts.push(chalk20.yellow(`${warnings} warning(s)`));
|
|
12498
|
+
console.log(chalk20.yellow(` \u26A0 ${relativePath}`) + chalk20.dim(` \u2014 ${parts.join(", ")}`));
|
|
12147
12499
|
console.log(formatIssues(filteredIssues));
|
|
12148
12500
|
}
|
|
12149
12501
|
}
|
|
@@ -12169,15 +12521,15 @@ async function checkCommand(opts = {}) {
|
|
|
12169
12521
|
}
|
|
12170
12522
|
}
|
|
12171
12523
|
if (!opts.json && result.links.broken.length > 0) {
|
|
12172
|
-
console.log(
|
|
12173
|
-
\u{1F517} Internal Links`) +
|
|
12524
|
+
console.log(chalk20.yellow(`
|
|
12525
|
+
\u{1F517} Internal Links`) + chalk20.dim(` (${result.links.total} scanned)
|
|
12174
12526
|
`));
|
|
12175
12527
|
for (const b of result.links.broken) {
|
|
12176
|
-
console.log(
|
|
12528
|
+
console.log(chalk20.red(` \u2717 ${b.file}:${b.line}`) + chalk20.dim(` \u2192 ${b.href} (route does not exist)`));
|
|
12177
12529
|
}
|
|
12178
12530
|
} else if (!opts.json && result.links.total > 0) {
|
|
12179
|
-
console.log(
|
|
12180
|
-
\u{1F517} Internal Links`) +
|
|
12531
|
+
console.log(chalk20.green(`
|
|
12532
|
+
\u{1F517} Internal Links`) + chalk20.dim(` \u2014 all ${result.links.total} links resolve \u2713`));
|
|
12181
12533
|
}
|
|
12182
12534
|
try {
|
|
12183
12535
|
const manifest = await loadManifest11(project.root);
|
|
@@ -12186,7 +12538,7 @@ async function checkCommand(opts = {}) {
|
|
|
12186
12538
|
const fullPath = resolve13(project.root, entry.file);
|
|
12187
12539
|
if (!existsSync22(fullPath)) {
|
|
12188
12540
|
result.pages.withErrors++;
|
|
12189
|
-
if (!opts.json) console.log(
|
|
12541
|
+
if (!opts.json) console.log(chalk20.red(`
|
|
12190
12542
|
\u2717 Missing shared component file: ${entry.id} (${entry.file})`));
|
|
12191
12543
|
}
|
|
12192
12544
|
}
|
|
@@ -12198,8 +12550,8 @@ async function checkCommand(opts = {}) {
|
|
|
12198
12550
|
try {
|
|
12199
12551
|
const manifest = await loadManifest11(projectRoot);
|
|
12200
12552
|
if (!opts.json && manifest.shared.length > 0) {
|
|
12201
|
-
console.log(
|
|
12202
|
-
\u{1F9E9} Shared Components`) +
|
|
12553
|
+
console.log(chalk20.cyan(`
|
|
12554
|
+
\u{1F9E9} Shared Components`) + chalk20.dim(` (${manifest.shared.length} registered)
|
|
12203
12555
|
`));
|
|
12204
12556
|
}
|
|
12205
12557
|
let consistent = 0;
|
|
@@ -12213,8 +12565,8 @@ async function checkCommand(opts = {}) {
|
|
|
12213
12565
|
if (!fileExists) {
|
|
12214
12566
|
_orphaned++;
|
|
12215
12567
|
if (!opts.json) {
|
|
12216
|
-
console.log(
|
|
12217
|
-
console.log(
|
|
12568
|
+
console.log(chalk20.red(` \u274C ${entry.id} (${entry.name}) \u2014 file missing: ${entry.file}`));
|
|
12569
|
+
console.log(chalk20.dim(` Fix: coherent fix or coherent sync`));
|
|
12218
12570
|
}
|
|
12219
12571
|
continue;
|
|
12220
12572
|
}
|
|
@@ -12225,11 +12577,11 @@ async function checkCommand(opts = {}) {
|
|
|
12225
12577
|
_nameMismatch++;
|
|
12226
12578
|
if (!opts.json) {
|
|
12227
12579
|
console.log(
|
|
12228
|
-
|
|
12580
|
+
chalk20.yellow(
|
|
12229
12581
|
` \u26A0 ${entry.id} \u2014 manifest name "${entry.name}" doesn't match export "${actualExports[0]}"`
|
|
12230
12582
|
)
|
|
12231
12583
|
);
|
|
12232
|
-
console.log(
|
|
12584
|
+
console.log(chalk20.dim(` Fix: coherent sync`));
|
|
12233
12585
|
}
|
|
12234
12586
|
}
|
|
12235
12587
|
} catch {
|
|
@@ -12244,35 +12596,35 @@ async function checkCommand(opts = {}) {
|
|
|
12244
12596
|
if (totalUsage === 0) {
|
|
12245
12597
|
unused++;
|
|
12246
12598
|
if (!opts.json) {
|
|
12247
|
-
console.log(
|
|
12248
|
-
console.log(
|
|
12599
|
+
console.log(chalk20.blue(` \u2139 ${entry.id} (${entry.name}) \u2014 registered but not used anywhere`));
|
|
12600
|
+
console.log(chalk20.dim(` Remove: coherent components shared remove ${entry.id}`));
|
|
12249
12601
|
}
|
|
12250
12602
|
} else {
|
|
12251
12603
|
consistent++;
|
|
12252
12604
|
const usageDesc = inLayout ? `layout + ${actualUsedIn.length} page(s)` : `${actualUsedIn.length} page(s)`;
|
|
12253
12605
|
if (!opts.json) {
|
|
12254
|
-
const staleNote = isStale ?
|
|
12255
|
-
console.log(
|
|
12606
|
+
const staleNote = isStale ? chalk20.yellow(" [usedIn stale]") : "";
|
|
12607
|
+
console.log(chalk20.green(` \u2714 ${entry.id} (${entry.name})`) + chalk20.dim(` \u2014 ${usageDesc}`) + staleNote);
|
|
12256
12608
|
}
|
|
12257
12609
|
}
|
|
12258
12610
|
}
|
|
12259
12611
|
const unregistered = findUnregisteredComponents(projectRoot, manifest);
|
|
12260
12612
|
if (unregistered.length > 0 && !opts.json) {
|
|
12261
|
-
console.log(
|
|
12613
|
+
console.log(chalk20.cyan(`
|
|
12262
12614
|
\u{1F4E6} Unregistered components found:`));
|
|
12263
12615
|
for (const comp of unregistered) {
|
|
12264
|
-
console.log(
|
|
12265
|
-
console.log(
|
|
12616
|
+
console.log(chalk20.blue(` \u2139 ${comp.name}`) + chalk20.dim(` \u2014 ${comp.file} (not in manifest)`));
|
|
12617
|
+
console.log(chalk20.dim(` Register: coherent sync`));
|
|
12266
12618
|
}
|
|
12267
12619
|
}
|
|
12268
12620
|
const inlineDupes = findInlineDuplicates(projectRoot, manifest);
|
|
12269
12621
|
if (inlineDupes.length > 0 && !opts.json) {
|
|
12270
|
-
console.log(
|
|
12622
|
+
console.log(chalk20.cyan(`
|
|
12271
12623
|
\u{1F50D} Inline duplicates:`));
|
|
12272
12624
|
for (const dup of inlineDupes) {
|
|
12273
|
-
console.log(
|
|
12625
|
+
console.log(chalk20.yellow(` \u26A0 ${dup.pageFile}`) + chalk20.dim(` has inline ${dup.componentName}`));
|
|
12274
12626
|
console.log(
|
|
12275
|
-
|
|
12627
|
+
chalk20.dim(
|
|
12276
12628
|
` Use shared: import { ${dup.componentName} } from "@/${dup.sharedFile.replace(".tsx", "")}"`
|
|
12277
12629
|
)
|
|
12278
12630
|
);
|
|
@@ -12299,24 +12651,24 @@ async function checkCommand(opts = {}) {
|
|
|
12299
12651
|
console.log(JSON.stringify(result, null, 2));
|
|
12300
12652
|
return;
|
|
12301
12653
|
}
|
|
12302
|
-
console.log(
|
|
12654
|
+
console.log(chalk20.dim("\n " + "\u2500".repeat(50)));
|
|
12303
12655
|
const summaryParts = [];
|
|
12304
12656
|
if (!skipPages) {
|
|
12305
|
-
summaryParts.push(`${
|
|
12306
|
-
if (result.pages.withErrors > 0) summaryParts.push(
|
|
12307
|
-
if (result.pages.withWarnings > 0) summaryParts.push(
|
|
12657
|
+
summaryParts.push(`${chalk20.green(`${result.pages.clean} clean`)} pages`);
|
|
12658
|
+
if (result.pages.withErrors > 0) summaryParts.push(chalk20.red(`${result.pages.withErrors} with errors`));
|
|
12659
|
+
if (result.pages.withWarnings > 0) summaryParts.push(chalk20.yellow(`${result.pages.withWarnings} with warnings`));
|
|
12308
12660
|
}
|
|
12309
12661
|
if (!skipShared && result.shared.total > 0) {
|
|
12310
12662
|
summaryParts.push(`${result.shared.consistent} healthy shared`);
|
|
12311
12663
|
if (result.shared.unused > 0) summaryParts.push(`${result.shared.unused} unused`);
|
|
12312
12664
|
}
|
|
12313
12665
|
if (result.links.broken.length > 0) {
|
|
12314
|
-
summaryParts.push(
|
|
12666
|
+
summaryParts.push(chalk20.red(`${result.links.broken.length} broken link(s)`));
|
|
12315
12667
|
}
|
|
12316
12668
|
console.log(`
|
|
12317
12669
|
${summaryParts.join(" | ")}`);
|
|
12318
12670
|
if (result.autoFixable > 0) {
|
|
12319
|
-
console.log(
|
|
12671
|
+
console.log(chalk20.cyan(`
|
|
12320
12672
|
Auto-fixable: ${result.autoFixable} issues. Run: coherent fix`));
|
|
12321
12673
|
}
|
|
12322
12674
|
console.log("");
|
|
@@ -12325,21 +12677,21 @@ async function checkCommand(opts = {}) {
|
|
|
12325
12677
|
}
|
|
12326
12678
|
|
|
12327
12679
|
// src/commands/repair.ts
|
|
12328
|
-
import
|
|
12680
|
+
import chalk21 from "chalk";
|
|
12329
12681
|
async function repairCommand() {
|
|
12330
|
-
console.log(
|
|
12682
|
+
console.log(chalk21.dim(" \u2139\uFE0F `coherent repair` is deprecated \u2014 use `coherent fix` instead\n"));
|
|
12331
12683
|
await fixCommand();
|
|
12332
12684
|
}
|
|
12333
12685
|
|
|
12334
12686
|
// src/commands/doctor.ts
|
|
12335
|
-
import
|
|
12687
|
+
import chalk22 from "chalk";
|
|
12336
12688
|
async function doctorCommand() {
|
|
12337
|
-
console.log(
|
|
12689
|
+
console.log(chalk22.dim(" \u2139\uFE0F `coherent doctor` is deprecated \u2014 use `coherent fix` instead\n"));
|
|
12338
12690
|
await fixCommand();
|
|
12339
12691
|
}
|
|
12340
12692
|
|
|
12341
12693
|
// src/commands/rules.ts
|
|
12342
|
-
import
|
|
12694
|
+
import chalk23 from "chalk";
|
|
12343
12695
|
async function rulesCommand() {
|
|
12344
12696
|
try {
|
|
12345
12697
|
const result = await regenerateCursorRules();
|
|
@@ -12350,31 +12702,31 @@ async function rulesCommand() {
|
|
|
12350
12702
|
if (result.sharedCount !== void 0) parts.push(`${result.sharedCount} shared components`);
|
|
12351
12703
|
if (result.tokenKeys !== void 0) parts.push(`${result.tokenKeys} design token keys`);
|
|
12352
12704
|
const summary = parts.length > 0 ? ` (${parts.join(", ")})` : "";
|
|
12353
|
-
console.log(
|
|
12705
|
+
console.log(chalk23.green(`\u2714 Updated .cursorrules and CLAUDE.md${summary}
|
|
12354
12706
|
`));
|
|
12355
12707
|
} catch (error) {
|
|
12356
|
-
console.error(
|
|
12708
|
+
console.error(chalk23.red("\u274C Command failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
12357
12709
|
process.exit(1);
|
|
12358
12710
|
}
|
|
12359
12711
|
}
|
|
12360
12712
|
|
|
12361
12713
|
// src/commands/validate.ts
|
|
12362
|
-
import
|
|
12714
|
+
import chalk24 from "chalk";
|
|
12363
12715
|
async function validateCommand() {
|
|
12364
|
-
console.log(
|
|
12716
|
+
console.log(chalk24.dim(" \u2139\uFE0F `coherent validate` is deprecated \u2014 use `coherent check` instead\n"));
|
|
12365
12717
|
await checkCommand({ pages: true });
|
|
12366
12718
|
}
|
|
12367
12719
|
|
|
12368
12720
|
// src/commands/audit.ts
|
|
12369
|
-
import
|
|
12721
|
+
import chalk25 from "chalk";
|
|
12370
12722
|
async function auditCommand(options) {
|
|
12371
|
-
console.log(
|
|
12723
|
+
console.log(chalk25.dim(" \u2139\uFE0F `coherent audit` is deprecated \u2014 use `coherent check` instead\n"));
|
|
12372
12724
|
await checkCommand({ shared: true, json: options.json });
|
|
12373
12725
|
}
|
|
12374
12726
|
|
|
12375
12727
|
// src/commands/components.ts
|
|
12376
12728
|
import { Command } from "commander";
|
|
12377
|
-
import
|
|
12729
|
+
import chalk26 from "chalk";
|
|
12378
12730
|
import {
|
|
12379
12731
|
DesignSystemManager as DesignSystemManager12,
|
|
12380
12732
|
ComponentManager as ComponentManager7,
|
|
@@ -12425,9 +12777,9 @@ function createComponentsCommand() {
|
|
|
12425
12777
|
console.log(JSON.stringify({ shared: manifest.shared, ui: installed2 }, null, 2));
|
|
12426
12778
|
return;
|
|
12427
12779
|
}
|
|
12428
|
-
console.log(
|
|
12780
|
+
console.log(chalk26.bold("\n\u{1F4E6} Shared Components"));
|
|
12429
12781
|
if (manifest.shared.length === 0) {
|
|
12430
|
-
console.log(
|
|
12782
|
+
console.log(chalk26.gray(" None yet. Generate pages with header/footer to create them.\n"));
|
|
12431
12783
|
} else {
|
|
12432
12784
|
const order = { layout: 0, section: 1, widget: 2 };
|
|
12433
12785
|
const sorted = [...manifest.shared].sort(
|
|
@@ -12435,9 +12787,9 @@ function createComponentsCommand() {
|
|
|
12435
12787
|
);
|
|
12436
12788
|
console.log("");
|
|
12437
12789
|
sorted.forEach((entry) => {
|
|
12438
|
-
const usage = entry.usedIn.length === 0 ?
|
|
12790
|
+
const usage = entry.usedIn.length === 0 ? chalk26.gray("unused") : entry.usedIn.includes("app/layout.tsx") ? chalk26.green("all pages") : chalk26.gray(entry.usedIn.join(", "));
|
|
12439
12791
|
console.log(
|
|
12440
|
-
` ${
|
|
12792
|
+
` ${chalk26.cyan(entry.id.padEnd(8))} ${chalk26.white(entry.name.padEnd(18))} ${chalk26.gray(entry.type.padEnd(9))} ${usage}`
|
|
12441
12793
|
);
|
|
12442
12794
|
});
|
|
12443
12795
|
console.log("");
|
|
@@ -12446,24 +12798,24 @@ function createComponentsCommand() {
|
|
|
12446
12798
|
const availableShadcn = listShadcnComponents();
|
|
12447
12799
|
const installedIds = new Set(installed.map((c) => c.id));
|
|
12448
12800
|
const notInstalled = availableShadcn.filter((id) => !installedIds.has(id));
|
|
12449
|
-
console.log(
|
|
12801
|
+
console.log(chalk26.bold("\u{1F9E9} UI Components (shadcn)"));
|
|
12450
12802
|
if (installed.length === 0) {
|
|
12451
|
-
console.log(
|
|
12803
|
+
console.log(chalk26.gray(" None installed yet.\n"));
|
|
12452
12804
|
} else {
|
|
12453
12805
|
const names = installed.map((c) => c.name).sort();
|
|
12454
|
-
console.log(
|
|
12806
|
+
console.log(chalk26.green(` Installed (${names.length}): `) + chalk26.white(names.join(", ")));
|
|
12455
12807
|
}
|
|
12456
12808
|
if (notInstalled.length > 0) {
|
|
12457
|
-
console.log(
|
|
12809
|
+
console.log(chalk26.gray(` Available (${notInstalled.length}): `) + chalk26.gray(notInstalled.join(", ")));
|
|
12458
12810
|
}
|
|
12459
12811
|
console.log("");
|
|
12460
|
-
console.log(
|
|
12461
|
-
console.log(
|
|
12462
|
-
console.log(
|
|
12812
|
+
console.log(chalk26.cyan("\u{1F4A1} Commands:"));
|
|
12813
|
+
console.log(chalk26.white(' coherent chat "add a testimonial component"'));
|
|
12814
|
+
console.log(chalk26.white(' coherent chat --component "Header" "add a search button"'));
|
|
12463
12815
|
console.log("");
|
|
12464
12816
|
});
|
|
12465
12817
|
cmd.command("add <name>").description("Install a specific component").action(async (name) => {
|
|
12466
|
-
console.log(
|
|
12818
|
+
console.log(chalk26.yellow(`
|
|
12467
12819
|
\u{1F4A1} Use: coherent chat "add ${name} component"
|
|
12468
12820
|
`));
|
|
12469
12821
|
});
|
|
@@ -12476,25 +12828,25 @@ function createComponentsCommand() {
|
|
|
12476
12828
|
console.log(JSON.stringify(manifest, null, 2));
|
|
12477
12829
|
return;
|
|
12478
12830
|
}
|
|
12479
|
-
console.log(
|
|
12831
|
+
console.log(chalk26.bold("\n\u{1F4E6} Shared Components\n"));
|
|
12480
12832
|
if (manifest.shared.length === 0) {
|
|
12481
|
-
console.log(
|
|
12482
|
-
console.log(
|
|
12833
|
+
console.log(chalk26.yellow(" No shared components yet.\n"));
|
|
12834
|
+
console.log(chalk26.gray(' Create via chat: coherent chat "add a page with header and footer"\n'));
|
|
12483
12835
|
return;
|
|
12484
12836
|
}
|
|
12485
12837
|
const order = { layout: 0, section: 1, widget: 2 };
|
|
12486
12838
|
const sorted = [...manifest.shared].sort((a, b) => order[a.type] - order[b.type] || a.name.localeCompare(b.name));
|
|
12487
12839
|
sorted.forEach((entry) => {
|
|
12488
|
-
const usedIn = entry.usedIn.length === 0 ?
|
|
12489
|
-
console.log(
|
|
12840
|
+
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(", ")}`);
|
|
12841
|
+
console.log(chalk26.cyan(` ${entry.id}`), chalk26.white(entry.name), chalk26.gray(entry.type));
|
|
12490
12842
|
if (opts.verbose) {
|
|
12491
|
-
console.log(
|
|
12492
|
-
if (entry.description) console.log(
|
|
12843
|
+
console.log(chalk26.gray(` file: ${entry.file}`));
|
|
12844
|
+
if (entry.description) console.log(chalk26.gray(` ${entry.description}`));
|
|
12493
12845
|
}
|
|
12494
|
-
console.log(
|
|
12846
|
+
console.log(chalk26.gray(` ${usedIn}`));
|
|
12495
12847
|
console.log("");
|
|
12496
12848
|
});
|
|
12497
|
-
console.log(
|
|
12849
|
+
console.log(chalk26.cyan("\u{1F4A1} Modify by ID:"), chalk26.white('coherent chat "in CID-001 add a search button"\n'));
|
|
12498
12850
|
});
|
|
12499
12851
|
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) => {
|
|
12500
12852
|
const project = findConfig();
|
|
@@ -12506,12 +12858,12 @@ function createComponentsCommand() {
|
|
|
12506
12858
|
description: opts.description,
|
|
12507
12859
|
usedIn: type === "layout" ? ["app/layout.tsx"] : []
|
|
12508
12860
|
});
|
|
12509
|
-
console.log(
|
|
12861
|
+
console.log(chalk26.green(`
|
|
12510
12862
|
\u2705 Created ${result.id} (${result.name}) at ${result.file}
|
|
12511
12863
|
`));
|
|
12512
12864
|
if (type === "layout") {
|
|
12513
12865
|
const updated = await integrateSharedLayoutIntoRootLayout3(project.root);
|
|
12514
|
-
if (updated) console.log(
|
|
12866
|
+
if (updated) console.log(chalk26.cyan(" Updated app/layout.tsx to use shared layout components.\n"));
|
|
12515
12867
|
}
|
|
12516
12868
|
const sharedPagePath = resolve14(project.root, "app/design-system/shared/page.tsx");
|
|
12517
12869
|
if (!existsSync23(sharedPagePath)) {
|
|
@@ -12521,23 +12873,23 @@ function createComponentsCommand() {
|
|
|
12521
12873
|
const config2 = dsm.getConfig();
|
|
12522
12874
|
const written = await writeDesignSystemFiles(project.root, config2, { sharedOnly: true });
|
|
12523
12875
|
if (written.length > 0) {
|
|
12524
|
-
console.log(
|
|
12876
|
+
console.log(chalk26.cyan(" Added Design System shared pages: /design-system/shared\n"));
|
|
12525
12877
|
}
|
|
12526
12878
|
} catch (e) {
|
|
12527
|
-
if (process.env.COHERENT_DEBUG === "1") console.error(
|
|
12879
|
+
if (process.env.COHERENT_DEBUG === "1") console.error(chalk26.dim("DS shared pages write failed:"), e);
|
|
12528
12880
|
}
|
|
12529
12881
|
}
|
|
12530
12882
|
try {
|
|
12531
12883
|
await writeCursorRules(project.root);
|
|
12532
12884
|
} catch (e) {
|
|
12533
|
-
if (process.env.COHERENT_DEBUG === "1") console.error(
|
|
12885
|
+
if (process.env.COHERENT_DEBUG === "1") console.error(chalk26.dim("Could not update .cursorrules:"), e);
|
|
12534
12886
|
}
|
|
12535
12887
|
});
|
|
12536
12888
|
return cmd;
|
|
12537
12889
|
}
|
|
12538
12890
|
|
|
12539
12891
|
// src/commands/import-cmd.ts
|
|
12540
|
-
import
|
|
12892
|
+
import chalk27 from "chalk";
|
|
12541
12893
|
import ora6 from "ora";
|
|
12542
12894
|
import { writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
|
|
12543
12895
|
import { resolve as resolve15, join as join18, dirname as dirname9 } from "path";
|
|
@@ -12642,25 +12994,25 @@ function createImportCommand() {
|
|
|
12642
12994
|
}
|
|
12643
12995
|
async function importFigmaAction(urlOrKey, opts) {
|
|
12644
12996
|
if (typeof urlOrKey !== "string" || !urlOrKey.trim()) {
|
|
12645
|
-
console.error(
|
|
12646
|
-
console.log(
|
|
12997
|
+
console.error(chalk27.red("\n\u274C Figma URL or file key is required.\n"));
|
|
12998
|
+
console.log(chalk27.dim(" Usage: coherent import figma <url-or-key> --token <your-token>\n"));
|
|
12647
12999
|
process.exit(1);
|
|
12648
13000
|
}
|
|
12649
13001
|
const token = opts.token ?? process.env.FIGMA_ACCESS_TOKEN ?? process.env.FIGMA_TOKEN;
|
|
12650
13002
|
if (!token || typeof token !== "string") {
|
|
12651
|
-
console.error(
|
|
12652
|
-
console.log(
|
|
12653
|
-
console.log(
|
|
12654
|
-
console.log(
|
|
13003
|
+
console.error(chalk27.red("\n\u274C Figma token required.\n"));
|
|
13004
|
+
console.log(chalk27.dim(" Use: coherent import figma <url-or-key> --token <your-token>"));
|
|
13005
|
+
console.log(chalk27.dim(" Or set FIGMA_ACCESS_TOKEN or FIGMA_TOKEN in your environment.\n"));
|
|
13006
|
+
console.log(chalk27.dim(" Get a token: Figma \u2192 Settings \u2192 Personal access tokens.\n"));
|
|
12655
13007
|
process.exit(1);
|
|
12656
13008
|
}
|
|
12657
13009
|
const generatePages = opts.pages !== false;
|
|
12658
13010
|
const dryRun = Boolean(opts.dryRun);
|
|
12659
13011
|
const fileKey = FigmaClient.extractFileKey(urlOrKey);
|
|
12660
13012
|
if (!fileKey) {
|
|
12661
|
-
console.error(
|
|
12662
|
-
console.log(
|
|
12663
|
-
console.log(
|
|
13013
|
+
console.error(chalk27.red("\n\u274C Invalid Figma URL or file key.\n"));
|
|
13014
|
+
console.log(chalk27.dim(" Use a URL like: https://www.figma.com/file/ABC123/MyDesign"));
|
|
13015
|
+
console.log(chalk27.dim(" Or the file key: ABC123\n"));
|
|
12664
13016
|
process.exit(1);
|
|
12665
13017
|
}
|
|
12666
13018
|
const project = findConfig();
|
|
@@ -12837,7 +13189,7 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
|
|
|
12837
13189
|
try {
|
|
12838
13190
|
await writeCursorRules(projectRoot);
|
|
12839
13191
|
} catch (e) {
|
|
12840
|
-
if (process.env.COHERENT_DEBUG === "1") console.error(
|
|
13192
|
+
if (process.env.COHERENT_DEBUG === "1") console.error(chalk27.dim("Could not update .cursorrules:"), e);
|
|
12841
13193
|
}
|
|
12842
13194
|
} else {
|
|
12843
13195
|
stats.filesWritten.push(DESIGN_SYSTEM_CONFIG_PATH);
|
|
@@ -12847,7 +13199,7 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
|
|
|
12847
13199
|
} catch (err) {
|
|
12848
13200
|
spinner.fail("Import failed");
|
|
12849
13201
|
const message = err instanceof Error ? err.message : String(err);
|
|
12850
|
-
console.error(
|
|
13202
|
+
console.error(chalk27.red("\n\u274C " + message + "\n"));
|
|
12851
13203
|
process.exit(1);
|
|
12852
13204
|
}
|
|
12853
13205
|
}
|
|
@@ -12855,36 +13207,36 @@ function printReport(stats, opts) {
|
|
|
12855
13207
|
const { dryRun, generatePages, fileName } = opts;
|
|
12856
13208
|
console.log("");
|
|
12857
13209
|
if (dryRun) {
|
|
12858
|
-
console.log(
|
|
13210
|
+
console.log(chalk27.yellow("\u2550\u2550\u2550 Dry run (no files written) \u2550\u2550\u2550"));
|
|
12859
13211
|
console.log("");
|
|
12860
13212
|
}
|
|
12861
|
-
console.log(
|
|
13213
|
+
console.log(chalk27.green("\u2705 Figma import complete"));
|
|
12862
13214
|
console.log("");
|
|
12863
|
-
console.log(
|
|
12864
|
-
console.log(
|
|
12865
|
-
console.log(
|
|
12866
|
-
console.log(
|
|
12867
|
-
console.log(
|
|
13215
|
+
console.log(chalk27.cyan(" Statistics"));
|
|
13216
|
+
console.log(chalk27.dim(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
13217
|
+
console.log(chalk27.blue(` File: ${fileName}`));
|
|
13218
|
+
console.log(chalk27.blue(` Color styles: ${stats.colorStyles}`));
|
|
13219
|
+
console.log(chalk27.blue(` Text styles: ${stats.textStyles}`));
|
|
12868
13220
|
console.log(
|
|
12869
|
-
|
|
13221
|
+
chalk27.blue(` Components: ${stats.componentsTotal} (${stats.baseCount} \u2192 base, ${stats.sharedCount} \u2192 shared)`)
|
|
12870
13222
|
);
|
|
12871
|
-
console.log(
|
|
12872
|
-
console.log(
|
|
12873
|
-
console.log(
|
|
12874
|
-
console.log(
|
|
13223
|
+
console.log(chalk27.blue(` Pages: ${stats.pagesGenerated}${!generatePages ? " (skipped by --no-pages)" : ""}`));
|
|
13224
|
+
console.log(chalk27.blue(` design-system.config: ${stats.configUpdated ? "updated" : "\u2014"}`));
|
|
13225
|
+
console.log(chalk27.blue(` Layout (Header/Footer): ${stats.layoutIntegrated ? "integrated" : "\u2014"}`));
|
|
13226
|
+
console.log(chalk27.blue(` DS viewer files: ${stats.dsFilesWritten}`));
|
|
12875
13227
|
console.log(
|
|
12876
|
-
|
|
13228
|
+
chalk27.blue(` Total files ${dryRun ? "that would be written" : "written"}: ${stats.filesWritten.length}`)
|
|
12877
13229
|
);
|
|
12878
13230
|
console.log("");
|
|
12879
13231
|
if (stats.filesWritten.length > 0 && stats.filesWritten.length <= 30) {
|
|
12880
|
-
console.log(
|
|
12881
|
-
stats.filesWritten.forEach((f) => console.log(
|
|
13232
|
+
console.log(chalk27.dim(" Files:"));
|
|
13233
|
+
stats.filesWritten.forEach((f) => console.log(chalk27.dim(` ${f}`)));
|
|
12882
13234
|
console.log("");
|
|
12883
13235
|
}
|
|
12884
13236
|
}
|
|
12885
13237
|
|
|
12886
13238
|
// src/commands/ds.ts
|
|
12887
|
-
import
|
|
13239
|
+
import chalk28 from "chalk";
|
|
12888
13240
|
import ora7 from "ora";
|
|
12889
13241
|
import { DesignSystemManager as DesignSystemManager14 } from "@getcoherent/core";
|
|
12890
13242
|
async function dsRegenerateCommand() {
|
|
@@ -12899,16 +13251,16 @@ async function dsRegenerateCommand() {
|
|
|
12899
13251
|
const config2 = dsm.getConfig();
|
|
12900
13252
|
const written = await writeDesignSystemFiles(project.root, config2);
|
|
12901
13253
|
spinner.succeed(`Regenerated ${written.length} Design System file(s)`);
|
|
12902
|
-
console.log(
|
|
12903
|
-
console.log(
|
|
13254
|
+
console.log(chalk28.gray(" app/design-system/* and app/api/design-system/*\n"));
|
|
13255
|
+
console.log(chalk28.cyan(" Open /design-system in the app to view.\n"));
|
|
12904
13256
|
} catch (error) {
|
|
12905
|
-
console.error(
|
|
13257
|
+
console.error(chalk28.red("\u274C Command failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
12906
13258
|
process.exit(1);
|
|
12907
13259
|
}
|
|
12908
13260
|
}
|
|
12909
13261
|
|
|
12910
13262
|
// src/commands/update.ts
|
|
12911
|
-
import
|
|
13263
|
+
import chalk29 from "chalk";
|
|
12912
13264
|
import ora8 from "ora";
|
|
12913
13265
|
import { readFileSync as readFileSync17, existsSync as existsSync25 } from "fs";
|
|
12914
13266
|
import { join as join19 } from "path";
|
|
@@ -12960,14 +13312,14 @@ async function updateCommand(opts) {
|
|
|
12960
13312
|
const projectVersion = config2.coherentVersion || "0.0.0";
|
|
12961
13313
|
if (compareSemver(projectVersion, CLI_VERSION4) === 0) {
|
|
12962
13314
|
spinner.succeed("Project is already up to date");
|
|
12963
|
-
console.log(
|
|
13315
|
+
console.log(chalk29.gray(` Version: v${CLI_VERSION4}
|
|
12964
13316
|
`));
|
|
12965
13317
|
return;
|
|
12966
13318
|
}
|
|
12967
13319
|
if (compareSemver(projectVersion, CLI_VERSION4) > 0) {
|
|
12968
13320
|
spinner.warn("Project was created with a newer CLI version");
|
|
12969
|
-
console.log(
|
|
12970
|
-
console.log(
|
|
13321
|
+
console.log(chalk29.yellow(` Project: v${projectVersion} \u2192 CLI: v${CLI_VERSION4}`));
|
|
13322
|
+
console.log(chalk29.yellow(" Update your CLI: npm install -g @getcoherent/cli@latest\n"));
|
|
12971
13323
|
return;
|
|
12972
13324
|
}
|
|
12973
13325
|
const report = {
|
|
@@ -13013,36 +13365,36 @@ async function updateCommand(opts) {
|
|
|
13013
13365
|
}
|
|
13014
13366
|
function printReport2(report) {
|
|
13015
13367
|
const from = report.fromVersion ? `v${report.fromVersion}` : "unknown";
|
|
13016
|
-
console.log(
|
|
13368
|
+
console.log(chalk29.green(`
|
|
13017
13369
|
\u2714 Project updated: ${from} \u2192 v${report.toVersion}
|
|
13018
13370
|
`));
|
|
13019
13371
|
if (report.overlayFiles > 0) {
|
|
13020
|
-
console.log(
|
|
13372
|
+
console.log(chalk29.white(` \u2714 Regenerated platform overlay (${report.overlayFiles} files)`));
|
|
13021
13373
|
}
|
|
13022
13374
|
if (report.migrationsApplied.length > 0) {
|
|
13023
13375
|
for (const desc of report.migrationsApplied) {
|
|
13024
|
-
console.log(
|
|
13376
|
+
console.log(chalk29.white(` \u2714 Migrated config: ${desc}`));
|
|
13025
13377
|
}
|
|
13026
13378
|
}
|
|
13027
13379
|
if (report.rulesUpdated) {
|
|
13028
|
-
console.log(
|
|
13380
|
+
console.log(chalk29.white(" \u2714 Updated .cursorrules and CLAUDE.md"));
|
|
13029
13381
|
}
|
|
13030
13382
|
if (report.missingCssVars.length > 0) {
|
|
13031
13383
|
console.log("");
|
|
13032
|
-
console.log(
|
|
13384
|
+
console.log(chalk29.yellow(" \u26A0 New CSS variables available in globals.css:"));
|
|
13033
13385
|
for (const v of report.missingCssVars.slice(0, 10)) {
|
|
13034
|
-
console.log(
|
|
13386
|
+
console.log(chalk29.gray(` ${v}`));
|
|
13035
13387
|
}
|
|
13036
13388
|
if (report.missingCssVars.length > 10) {
|
|
13037
|
-
console.log(
|
|
13389
|
+
console.log(chalk29.gray(` ... and ${report.missingCssVars.length - 10} more`));
|
|
13038
13390
|
}
|
|
13039
13391
|
console.log("");
|
|
13040
|
-
console.log(
|
|
13041
|
-
console.log(
|
|
13392
|
+
console.log(chalk29.cyan(" To add them automatically:"));
|
|
13393
|
+
console.log(chalk29.white(" coherent update --patch-globals\n"));
|
|
13042
13394
|
}
|
|
13043
13395
|
console.log("");
|
|
13044
|
-
console.log(
|
|
13045
|
-
console.log(
|
|
13396
|
+
console.log(chalk29.dim(" Your pages and components were NOT modified."));
|
|
13397
|
+
console.log(chalk29.dim(" Run `coherent check` to check existing pages against new rules.\n"));
|
|
13046
13398
|
}
|
|
13047
13399
|
var EXPECTED_CSS_VARS = [
|
|
13048
13400
|
"--background",
|
|
@@ -13126,7 +13478,7 @@ function patchGlobalsCss(projectRoot, missingVars) {
|
|
|
13126
13478
|
}
|
|
13127
13479
|
|
|
13128
13480
|
// src/commands/undo.ts
|
|
13129
|
-
import
|
|
13481
|
+
import chalk30 from "chalk";
|
|
13130
13482
|
async function undoCommand(options) {
|
|
13131
13483
|
try {
|
|
13132
13484
|
const project = findConfig();
|
|
@@ -13135,41 +13487,41 @@ async function undoCommand(options) {
|
|
|
13135
13487
|
const backups = listBackups(projectRoot);
|
|
13136
13488
|
if (options.list) {
|
|
13137
13489
|
if (backups.length === 0) {
|
|
13138
|
-
console.log(
|
|
13490
|
+
console.log(chalk30.yellow("No backups found."));
|
|
13139
13491
|
return;
|
|
13140
13492
|
}
|
|
13141
|
-
console.log(
|
|
13493
|
+
console.log(chalk30.bold("\n\u{1F4E6} Available backups:\n"));
|
|
13142
13494
|
for (const b of backups) {
|
|
13143
13495
|
const date = new Date(b.timestamp);
|
|
13144
13496
|
const timeStr = date.toLocaleString();
|
|
13145
|
-
console.log(
|
|
13146
|
-
console.log(
|
|
13497
|
+
console.log(chalk30.white(` ${b.name}`));
|
|
13498
|
+
console.log(chalk30.dim(` ${timeStr} \u2014 ${b.files} file(s)`));
|
|
13147
13499
|
console.log();
|
|
13148
13500
|
}
|
|
13149
13501
|
return;
|
|
13150
13502
|
}
|
|
13151
13503
|
if (backups.length === 0) {
|
|
13152
|
-
console.log(
|
|
13504
|
+
console.log(chalk30.yellow("No backups found. Nothing to undo."));
|
|
13153
13505
|
return;
|
|
13154
13506
|
}
|
|
13155
13507
|
const latest = backups[0];
|
|
13156
13508
|
const ok = restoreBackup(projectRoot, latest.name);
|
|
13157
13509
|
if (!ok) {
|
|
13158
|
-
console.log(
|
|
13510
|
+
console.log(chalk30.red("Failed to restore backup."));
|
|
13159
13511
|
return;
|
|
13160
13512
|
}
|
|
13161
|
-
console.log(
|
|
13162
|
-
console.log(
|
|
13163
|
-
console.log(
|
|
13164
|
-
console.log(
|
|
13513
|
+
console.log(chalk30.green("\n\u2705 Restored to previous state:\n"));
|
|
13514
|
+
console.log(chalk30.dim(` Snapshot: ${new Date(latest.timestamp).toLocaleString()}`));
|
|
13515
|
+
console.log(chalk30.dim(` Files: ${latest.files} restored`));
|
|
13516
|
+
console.log(chalk30.cyan("\n Run: coherent preview\n"));
|
|
13165
13517
|
} catch (error) {
|
|
13166
|
-
console.error(
|
|
13518
|
+
console.error(chalk30.red("\u274C Undo failed:"), error instanceof Error ? error.message : "Unknown error");
|
|
13167
13519
|
process.exit(1);
|
|
13168
13520
|
}
|
|
13169
13521
|
}
|
|
13170
13522
|
|
|
13171
13523
|
// src/commands/sync.ts
|
|
13172
|
-
import
|
|
13524
|
+
import chalk31 from "chalk";
|
|
13173
13525
|
import ora9 from "ora";
|
|
13174
13526
|
import { existsSync as existsSync26, readFileSync as readFileSync18 } from "fs";
|
|
13175
13527
|
import { join as join20, relative as relative5, dirname as dirname10 } from "path";
|
|
@@ -13407,7 +13759,7 @@ async function syncCommand(options = {}) {
|
|
|
13407
13759
|
const doTokens = runAll || options.tokens === true;
|
|
13408
13760
|
const doComponents = runAll || options.components === true;
|
|
13409
13761
|
const doPatterns = runAll || options.patterns === true;
|
|
13410
|
-
if (dryRun) console.log(
|
|
13762
|
+
if (dryRun) console.log(chalk31.yellow(" [dry-run] No files will be written\n"));
|
|
13411
13763
|
const spinner = ora9("Scanning project files...").start();
|
|
13412
13764
|
try {
|
|
13413
13765
|
const appDir = join20(project.root, "app");
|
|
@@ -13548,84 +13900,84 @@ async function syncCommand(options = {}) {
|
|
|
13548
13900
|
spinner.succeed("Updated .cursorrules and CLAUDE.md");
|
|
13549
13901
|
}
|
|
13550
13902
|
console.log("");
|
|
13551
|
-
console.log(
|
|
13903
|
+
console.log(chalk31.green(`\u2705 Design System ${dryRun ? "analyzed" : "synced"} with actual code
|
|
13552
13904
|
`));
|
|
13553
|
-
console.log(
|
|
13905
|
+
console.log(chalk31.blue("\u{1F4C4} Pages:"));
|
|
13554
13906
|
for (const page of discoveredPages) {
|
|
13555
13907
|
const a = analyzePageCode(page.code);
|
|
13556
13908
|
const comps = Object.entries(a.componentUsage || {}).filter(([, c]) => c > 0).map(([n]) => n);
|
|
13557
|
-
console.log(
|
|
13558
|
-
if (comps.length > 0) console.log(
|
|
13559
|
-
if (a.sections?.length) console.log(
|
|
13909
|
+
console.log(chalk31.gray(` ${page.route} \u2014 ${page.name}`));
|
|
13910
|
+
if (comps.length > 0) console.log(chalk31.gray(` Components: ${comps.join(", ")}`));
|
|
13911
|
+
if (a.sections?.length) console.log(chalk31.gray(` Sections: ${a.sections.map((s) => s.name).join(", ")}`));
|
|
13560
13912
|
}
|
|
13561
13913
|
if (doTokens && extractedTokens) {
|
|
13562
13914
|
console.log("");
|
|
13563
|
-
console.log(
|
|
13915
|
+
console.log(chalk31.blue("\u{1F3A8} Design Tokens (from globals.css):"));
|
|
13564
13916
|
const lc = Object.keys(extractedTokens.colors.light).length;
|
|
13565
13917
|
const dc = Object.keys(extractedTokens.colors.dark).length;
|
|
13566
|
-
console.log(
|
|
13567
|
-
console.log(
|
|
13568
|
-
if (extractedTokens.radius) console.log(
|
|
13918
|
+
console.log(chalk31.gray(` Light: ${lc} variables | Dark: ${dc} variables`));
|
|
13919
|
+
console.log(chalk31.gray(` Default mode: ${extractedTokens.defaultMode}`));
|
|
13920
|
+
if (extractedTokens.radius) console.log(chalk31.gray(` Border radius: ${extractedTokens.radius}`));
|
|
13569
13921
|
}
|
|
13570
13922
|
if (doComponents && reconcileResult) {
|
|
13571
13923
|
console.log("");
|
|
13572
|
-
console.log(
|
|
13924
|
+
console.log(chalk31.blue("\u{1F9E9} Shared Components:"));
|
|
13573
13925
|
for (const r of reconcileResult.removed) {
|
|
13574
|
-
console.log(
|
|
13926
|
+
console.log(chalk31.red(` \u{1F5D1} Removed ${r.id} (${r.name}) \u2014 ${r.reason}`));
|
|
13575
13927
|
}
|
|
13576
13928
|
for (const u of reconcileResult.updated) {
|
|
13577
|
-
console.log(
|
|
13929
|
+
console.log(chalk31.cyan(` \u{1F4DD} Updated ${u.id} ${u.field}: ${u.from} \u2192 ${u.to}`));
|
|
13578
13930
|
}
|
|
13579
13931
|
for (const a of reconcileResult.added) {
|
|
13580
|
-
console.log(
|
|
13932
|
+
console.log(chalk31.green(` \u2728 Added ${a.id} (${a.name}) \u2014 ${a.file} (${a.type})`));
|
|
13581
13933
|
}
|
|
13582
13934
|
for (const w of reconcileResult.warnings) {
|
|
13583
|
-
console.log(
|
|
13584
|
-
console.log(
|
|
13935
|
+
console.log(chalk31.yellow(` \u26A0 ${w.message}`));
|
|
13936
|
+
console.log(chalk31.dim(` ${w.suggestion}`));
|
|
13585
13937
|
}
|
|
13586
13938
|
if (reconcileResult.removed.length === 0 && reconcileResult.updated.length === 0 && reconcileResult.added.length === 0 && reconcileResult.warnings.length === 0) {
|
|
13587
|
-
console.log(
|
|
13939
|
+
console.log(chalk31.gray(" All components consistent \u2713"));
|
|
13588
13940
|
}
|
|
13589
13941
|
}
|
|
13590
13942
|
if (doPatterns && Object.keys(stylePatterns).length > 0) {
|
|
13591
13943
|
console.log("");
|
|
13592
|
-
console.log(
|
|
13593
|
-
if (stylePatterns.card) console.log(
|
|
13594
|
-
if (stylePatterns.section) console.log(
|
|
13595
|
-
if (stylePatterns.terminal) console.log(
|
|
13596
|
-
if (stylePatterns.iconContainer) console.log(
|
|
13944
|
+
console.log(chalk31.blue("\u{1F4D0} Style Patterns:"));
|
|
13945
|
+
if (stylePatterns.card) console.log(chalk31.gray(` Cards: ${stylePatterns.card.slice(0, 80)}`));
|
|
13946
|
+
if (stylePatterns.section) console.log(chalk31.gray(` Sections: ${stylePatterns.section}`));
|
|
13947
|
+
if (stylePatterns.terminal) console.log(chalk31.gray(` Terminal: ${stylePatterns.terminal.slice(0, 80)}`));
|
|
13948
|
+
if (stylePatterns.iconContainer) console.log(chalk31.gray(` Icons: ${stylePatterns.iconContainer.slice(0, 80)}`));
|
|
13597
13949
|
if (stylePatterns.heroHeadline)
|
|
13598
|
-
console.log(
|
|
13950
|
+
console.log(chalk31.gray(` Hero headline: ${stylePatterns.heroHeadline.slice(0, 80)}`));
|
|
13599
13951
|
if (stylePatterns.sectionTitle)
|
|
13600
|
-
console.log(
|
|
13952
|
+
console.log(chalk31.gray(` Section title: ${stylePatterns.sectionTitle.slice(0, 80)}`));
|
|
13601
13953
|
}
|
|
13602
13954
|
const tokenUsage = extractActualTokenUsage(allPageCode);
|
|
13603
13955
|
if (tokenUsage.colors.length > 0) {
|
|
13604
13956
|
console.log("");
|
|
13605
|
-
console.log(
|
|
13957
|
+
console.log(chalk31.blue("\u{1F3F7}\uFE0F Actual token usage (from classNames):"));
|
|
13606
13958
|
console.log(
|
|
13607
|
-
|
|
13959
|
+
chalk31.gray(
|
|
13608
13960
|
` Colors: ${tokenUsage.colors.slice(0, 12).join(", ")}${tokenUsage.colors.length > 12 ? ` (+${tokenUsage.colors.length - 12})` : ""}`
|
|
13609
13961
|
)
|
|
13610
13962
|
);
|
|
13611
13963
|
console.log(
|
|
13612
|
-
|
|
13964
|
+
chalk31.gray(
|
|
13613
13965
|
` Typography: ${tokenUsage.typography.slice(0, 8).join(", ")}${tokenUsage.typography.length > 8 ? ` (+${tokenUsage.typography.length - 8})` : ""}`
|
|
13614
13966
|
)
|
|
13615
13967
|
);
|
|
13616
|
-
console.log(
|
|
13968
|
+
console.log(chalk31.gray(` Radius: ${tokenUsage.borderRadius.join(", ")}`));
|
|
13617
13969
|
}
|
|
13618
13970
|
const reusable = extractReusablePatterns(allPageCode);
|
|
13619
13971
|
if (reusable.length > 0) {
|
|
13620
13972
|
console.log("");
|
|
13621
|
-
console.log(
|
|
13973
|
+
console.log(chalk31.blue(`\u{1F501} Repeating patterns (${reusable.length} \u2014 potential reusable components):`));
|
|
13622
13974
|
for (const p of reusable.slice(0, 5)) {
|
|
13623
|
-
console.log(
|
|
13975
|
+
console.log(chalk31.gray(` \xD7${p.count}: ${p.sample}${p.sample.length < p.pattern.length ? "..." : ""}`));
|
|
13624
13976
|
}
|
|
13625
13977
|
}
|
|
13626
13978
|
console.log("");
|
|
13627
13979
|
if (!dryRun) {
|
|
13628
|
-
console.log(
|
|
13980
|
+
console.log(chalk31.cyan(" Open /design-system in the app to see the updated view."));
|
|
13629
13981
|
}
|
|
13630
13982
|
console.log("");
|
|
13631
13983
|
} catch (err) {
|
|
@@ -13636,7 +13988,7 @@ async function syncCommand(options = {}) {
|
|
|
13636
13988
|
}
|
|
13637
13989
|
|
|
13638
13990
|
// src/commands/migrate.ts
|
|
13639
|
-
import
|
|
13991
|
+
import chalk32 from "chalk";
|
|
13640
13992
|
import ora10 from "ora";
|
|
13641
13993
|
import { existsSync as existsSync27, mkdirSync as mkdirSync8, cpSync, rmSync as rmSync6, writeFileSync as writeFileSync12, readFileSync as readFileSync19, readdirSync as readdirSync9 } from "fs";
|
|
13642
13994
|
import { join as join21 } from "path";
|
|
@@ -13713,13 +14065,13 @@ async function migrateAction(options) {
|
|
|
13713
14065
|
}
|
|
13714
14066
|
const guard = guardPath(projectRoot);
|
|
13715
14067
|
if (existsSync27(guard)) {
|
|
13716
|
-
console.log(
|
|
13717
|
-
console.log(
|
|
14068
|
+
console.log(chalk32.yellow("A migration is already in progress."));
|
|
14069
|
+
console.log(chalk32.dim("Run `coherent migrate --rollback` to undo, or delete .coherent/migration-in-progress"));
|
|
13718
14070
|
return;
|
|
13719
14071
|
}
|
|
13720
14072
|
const uiDir = join21(projectRoot, "components", "ui");
|
|
13721
14073
|
if (!existsSync27(uiDir)) {
|
|
13722
|
-
console.log(
|
|
14074
|
+
console.log(chalk32.yellow("No components/ui directory found. Nothing to migrate."));
|
|
13723
14075
|
return;
|
|
13724
14076
|
}
|
|
13725
14077
|
const provider = getComponentProvider();
|
|
@@ -13727,16 +14079,16 @@ async function migrateAction(options) {
|
|
|
13727
14079
|
const files = readdirSync9(uiDir).filter((f) => f.endsWith(".tsx"));
|
|
13728
14080
|
const migratable = files.map((f) => f.replace(".tsx", "")).filter((id) => managedIds.has(id));
|
|
13729
14081
|
if (migratable.length === 0) {
|
|
13730
|
-
console.log(
|
|
14082
|
+
console.log(chalk32.green("All components are already up to date."));
|
|
13731
14083
|
return;
|
|
13732
14084
|
}
|
|
13733
|
-
console.log(
|
|
14085
|
+
console.log(chalk32.cyan(`
|
|
13734
14086
|
Found ${migratable.length} component(s) to migrate:`));
|
|
13735
14087
|
for (const id of migratable) {
|
|
13736
|
-
console.log(
|
|
14088
|
+
console.log(chalk32.dim(` - ${id}`));
|
|
13737
14089
|
}
|
|
13738
14090
|
if (options.dryRun) {
|
|
13739
|
-
console.log(
|
|
14091
|
+
console.log(chalk32.yellow("\n[dry-run] No changes applied."));
|
|
13740
14092
|
return;
|
|
13741
14093
|
}
|
|
13742
14094
|
const spinner = ora10("Migrating components...").start();
|
|
@@ -13753,12 +14105,12 @@ Found ${migratable.length} component(s) to migrate:`));
|
|
|
13753
14105
|
if (result.success) {
|
|
13754
14106
|
migrated++;
|
|
13755
14107
|
} else {
|
|
13756
|
-
console.warn(
|
|
14108
|
+
console.warn(chalk32.yellow(` \u26A0 Failed to migrate ${id}`));
|
|
13757
14109
|
}
|
|
13758
14110
|
}
|
|
13759
14111
|
clearGuard(projectRoot);
|
|
13760
14112
|
spinner.succeed(`Migrated ${migrated}/${migratable.length} components to real shadcn/ui`);
|
|
13761
|
-
console.log(
|
|
14113
|
+
console.log(chalk32.dim(` Backup saved to: ${backup}`));
|
|
13762
14114
|
} catch (err) {
|
|
13763
14115
|
spinner.fail("Migration failed \u2014 rolling back");
|
|
13764
14116
|
rollback(projectRoot);
|
|
@@ -13770,7 +14122,7 @@ Found ${migratable.length} component(s) to migrate:`));
|
|
|
13770
14122
|
import { existsSync as existsSync28, mkdirSync as mkdirSync9, readFileSync as readFileSync20, writeFileSync as writeFileSync13 } from "fs";
|
|
13771
14123
|
import { join as join22 } from "path";
|
|
13772
14124
|
import { homedir } from "os";
|
|
13773
|
-
import
|
|
14125
|
+
import chalk33 from "chalk";
|
|
13774
14126
|
import { CLI_VERSION as CLI_VERSION5 } from "@getcoherent/core";
|
|
13775
14127
|
var DEBUG5 = process.env.COHERENT_DEBUG === "1";
|
|
13776
14128
|
var PACKAGE_NAME = "@getcoherent/cli";
|
|
@@ -13841,8 +14193,8 @@ async function checkForUpdates() {
|
|
|
13841
14193
|
}
|
|
13842
14194
|
function printUpdateNotice(latest) {
|
|
13843
14195
|
console.log(
|
|
13844
|
-
|
|
13845
|
-
\u2B06 Update available: v${CLI_VERSION5} \u2192 v${latest}`) +
|
|
14196
|
+
chalk33.yellow(`
|
|
14197
|
+
\u2B06 Update available: v${CLI_VERSION5} \u2192 v${latest}`) + chalk33.dim(`
|
|
13846
14198
|
Run: npm update -g ${PACKAGE_NAME}
|
|
13847
14199
|
`)
|
|
13848
14200
|
);
|