@getcoherent/cli 0.5.13 → 0.5.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -1227,9 +1227,7 @@ var COMPONENT_APIS = {
|
|
|
1227
1227
|
</CommandGroup>
|
|
1228
1228
|
</CommandList>
|
|
1229
1229
|
</Command>`,
|
|
1230
|
-
antiPatterns: [
|
|
1231
|
-
"NEVER build custom search palette \u2014 use Command/CommandDialog"
|
|
1232
|
-
]
|
|
1230
|
+
antiPatterns: ["NEVER build custom search palette \u2014 use Command/CommandDialog"]
|
|
1233
1231
|
},
|
|
1234
1232
|
tabs: {
|
|
1235
1233
|
name: "Tabs",
|
|
@@ -1269,7 +1267,16 @@ var COMPONENT_APIS = {
|
|
|
1269
1267
|
},
|
|
1270
1268
|
table: {
|
|
1271
1269
|
name: "Table",
|
|
1272
|
-
subcomponents: [
|
|
1270
|
+
subcomponents: [
|
|
1271
|
+
"Table",
|
|
1272
|
+
"TableHeader",
|
|
1273
|
+
"TableBody",
|
|
1274
|
+
"TableFooter",
|
|
1275
|
+
"TableRow",
|
|
1276
|
+
"TableHead",
|
|
1277
|
+
"TableCell",
|
|
1278
|
+
"TableCaption"
|
|
1279
|
+
],
|
|
1273
1280
|
importPath: "@/components/ui/table",
|
|
1274
1281
|
keyProps: {},
|
|
1275
1282
|
usage: `<Table>
|
|
@@ -1280,9 +1287,7 @@ var COMPONENT_APIS = {
|
|
|
1280
1287
|
<TableRow><TableCell>Value</TableCell></TableRow>
|
|
1281
1288
|
</TableBody>
|
|
1282
1289
|
</Table>`,
|
|
1283
|
-
antiPatterns: [
|
|
1284
|
-
"NEVER use native <table> \u2014 use shadcn Table for consistent styling"
|
|
1285
|
-
]
|
|
1290
|
+
antiPatterns: ["NEVER use native <table> \u2014 use shadcn Table for consistent styling"]
|
|
1286
1291
|
},
|
|
1287
1292
|
accordion: {
|
|
1288
1293
|
name: "Accordion",
|
|
@@ -1349,17 +1354,15 @@ var ShadcnProvider = class {
|
|
|
1349
1354
|
if (!force && deps.existsSync(componentPath)) return;
|
|
1350
1355
|
try {
|
|
1351
1356
|
await new Promise((resolve16, reject) => {
|
|
1352
|
-
deps.exec(
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
if (err) reject(err);
|
|
1357
|
-
else resolve16();
|
|
1358
|
-
}
|
|
1359
|
-
);
|
|
1357
|
+
deps.exec(`npx shadcn@latest add ${name} --yes --overwrite`, { cwd: projectRoot, timeout: 15e3 }, (err) => {
|
|
1358
|
+
if (err) reject(err);
|
|
1359
|
+
else resolve16();
|
|
1360
|
+
});
|
|
1360
1361
|
});
|
|
1361
1362
|
} catch {
|
|
1362
|
-
console.warn(
|
|
1363
|
+
console.warn(
|
|
1364
|
+
`Could not install ${name} (network error or timeout). Run \`npx shadcn@latest add ${name}\` manually.`
|
|
1365
|
+
);
|
|
1363
1366
|
}
|
|
1364
1367
|
}
|
|
1365
1368
|
async installComponent(id, projectRoot, options) {
|
|
@@ -1452,10 +1455,7 @@ var ShadcnProvider = class {
|
|
|
1452
1455
|
"ring"
|
|
1453
1456
|
];
|
|
1454
1457
|
const lines = sidebarVars.map((v) => ` --color-sidebar-${v}: var(--sidebar-${v});`);
|
|
1455
|
-
const chartLines = Array.from(
|
|
1456
|
-
{ length: 5 },
|
|
1457
|
-
(_, i) => ` --color-chart-${i + 1}: var(--chart-${i + 1});`
|
|
1458
|
-
);
|
|
1458
|
+
const chartLines = Array.from({ length: 5 }, (_, i) => ` --color-chart-${i + 1}: var(--chart-${i + 1});`);
|
|
1459
1459
|
return `@theme inline {
|
|
1460
1460
|
${lines.join("\n")}
|
|
1461
1461
|
${chartLines.join("\n")}
|
|
@@ -1678,7 +1678,8 @@ function createMinimalConfig() {
|
|
|
1678
1678
|
framework: "next",
|
|
1679
1679
|
typescript: true,
|
|
1680
1680
|
cssFramework: "tailwind",
|
|
1681
|
-
autoScaffold: false
|
|
1681
|
+
autoScaffold: false,
|
|
1682
|
+
homePagePlaceholder: true
|
|
1682
1683
|
},
|
|
1683
1684
|
createdAt: now,
|
|
1684
1685
|
updatedAt: now
|
|
@@ -3747,14 +3748,14 @@ async function createAppRouteGroupLayout(projectPath) {
|
|
|
3747
3748
|
import chalk13 from "chalk";
|
|
3748
3749
|
import ora2 from "ora";
|
|
3749
3750
|
import { resolve as resolve9, relative as relative2, join as join11 } from "path";
|
|
3750
|
-
import { existsSync as existsSync16, readFileSync as
|
|
3751
|
+
import { existsSync as existsSync16, readFileSync as readFileSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync3 } from "fs";
|
|
3751
3752
|
import {
|
|
3752
3753
|
DesignSystemManager as DesignSystemManager7,
|
|
3753
|
-
ComponentManager as
|
|
3754
|
+
ComponentManager as ComponentManager5,
|
|
3754
3755
|
PageManager as PageManager3,
|
|
3755
3756
|
CLI_VERSION as CLI_VERSION2,
|
|
3756
3757
|
getTemplateForPageType as getTemplateForPageType2,
|
|
3757
|
-
loadManifest as
|
|
3758
|
+
loadManifest as loadManifest8,
|
|
3758
3759
|
saveManifest as saveManifest2
|
|
3759
3760
|
} from "@getcoherent/core";
|
|
3760
3761
|
|
|
@@ -3831,7 +3832,7 @@ Please set ${envVar} in your environment or .env file.`);
|
|
|
3831
3832
|
}
|
|
3832
3833
|
if (preferredProvider === "openai") {
|
|
3833
3834
|
try {
|
|
3834
|
-
const { OpenAIClient } = await import("./openai-provider-
|
|
3835
|
+
const { OpenAIClient } = await import("./openai-provider-FSXSVEYD.js");
|
|
3835
3836
|
return await OpenAIClient.create(apiKey2, config2?.model);
|
|
3836
3837
|
} catch (error) {
|
|
3837
3838
|
if (error.message?.includes("not installed")) {
|
|
@@ -3845,7 +3846,7 @@ Error: ${error.message}`
|
|
|
3845
3846
|
);
|
|
3846
3847
|
}
|
|
3847
3848
|
} else {
|
|
3848
|
-
const { ClaudeClient } = await import("./claude-
|
|
3849
|
+
const { ClaudeClient } = await import("./claude-RFHVT7RC.js");
|
|
3849
3850
|
return ClaudeClient.create(apiKey2, config2?.model);
|
|
3850
3851
|
}
|
|
3851
3852
|
}
|
|
@@ -3858,7 +3859,7 @@ Error: ${error.message}`
|
|
|
3858
3859
|
switch (provider) {
|
|
3859
3860
|
case "openai":
|
|
3860
3861
|
try {
|
|
3861
|
-
const { OpenAIClient } = await import("./openai-provider-
|
|
3862
|
+
const { OpenAIClient } = await import("./openai-provider-FSXSVEYD.js");
|
|
3862
3863
|
return await OpenAIClient.create(apiKey, config2?.model);
|
|
3863
3864
|
} catch (error) {
|
|
3864
3865
|
if (error.message?.includes("not installed")) {
|
|
@@ -3872,7 +3873,7 @@ Error: ${error.message}`
|
|
|
3872
3873
|
);
|
|
3873
3874
|
}
|
|
3874
3875
|
case "claude":
|
|
3875
|
-
const { ClaudeClient } = await import("./claude-
|
|
3876
|
+
const { ClaudeClient } = await import("./claude-RFHVT7RC.js");
|
|
3876
3877
|
return ClaudeClient.create(apiKey, config2?.model);
|
|
3877
3878
|
default:
|
|
3878
3879
|
throw new Error(`Unsupported AI provider: ${provider}`);
|
|
@@ -3911,6 +3912,27 @@ var PAGE_TEMPLATES = {
|
|
|
3911
3912
|
"Label"
|
|
3912
3913
|
]
|
|
3913
3914
|
},
|
|
3915
|
+
register: {
|
|
3916
|
+
description: "Registration page with centered card form",
|
|
3917
|
+
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-sm".',
|
|
3919
|
+
'Card with CardHeader: CardTitle "Create an account" (text-2xl font-bold), CardDescription "Enter your details to get started" (text-sm text-muted-foreground).',
|
|
3920
|
+
'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
|
+
'CardFooter: text "Already have an account?" with a Sign in link. All text is text-sm text-muted-foreground.',
|
|
3922
|
+
'This page uses "use client" (has useState for form state). Do NOT include export const metadata.'
|
|
3923
|
+
],
|
|
3924
|
+
components: [
|
|
3925
|
+
"Card",
|
|
3926
|
+
"CardHeader",
|
|
3927
|
+
"CardTitle",
|
|
3928
|
+
"CardDescription",
|
|
3929
|
+
"CardContent",
|
|
3930
|
+
"CardFooter",
|
|
3931
|
+
"Button",
|
|
3932
|
+
"Input",
|
|
3933
|
+
"Label"
|
|
3934
|
+
]
|
|
3935
|
+
},
|
|
3914
3936
|
pricing: {
|
|
3915
3937
|
description: "Pricing page with tier comparison cards",
|
|
3916
3938
|
sections: [
|
|
@@ -4037,6 +4059,43 @@ var PAGE_TEMPLATES = {
|
|
|
4037
4059
|
"Page header. Timeline: each version with version badge, date, list of entries (type: text). Border-left timeline pattern. Badge component for version/date."
|
|
4038
4060
|
],
|
|
4039
4061
|
components: ["Badge"]
|
|
4062
|
+
},
|
|
4063
|
+
team: {
|
|
4064
|
+
description: "Team page with member cards",
|
|
4065
|
+
sections: [
|
|
4066
|
+
'Page header: h1 "Our Team" className="text-2xl font-bold tracking-tight" + p className="text-sm text-muted-foreground"',
|
|
4067
|
+
'Grid of team member Cards (className="grid gap-4 md:grid-cols-2 lg:grid-cols-4"). Each card: avatar placeholder (bg-muted rounded-full size-12 with initials), name (text-sm font-semibold), role (text-sm text-muted-foreground).'
|
|
4068
|
+
],
|
|
4069
|
+
components: ["Card", "CardContent"]
|
|
4070
|
+
},
|
|
4071
|
+
tasks: {
|
|
4072
|
+
description: "Task list page with status badges and search",
|
|
4073
|
+
sections: [
|
|
4074
|
+
'Page header: h1 "Tasks" + description. Search input with Search icon.',
|
|
4075
|
+
"Task list: divide-y container. Each row: status badge (colored), task title, priority badge, assignee name. Use flex items-center justify-between.",
|
|
4076
|
+
'This page uses "use client" (has useState for search). Do NOT include export const metadata.'
|
|
4077
|
+
],
|
|
4078
|
+
components: ["Input", "Badge"]
|
|
4079
|
+
},
|
|
4080
|
+
"task-detail": {
|
|
4081
|
+
description: "Task detail page with info and activity",
|
|
4082
|
+
sections: [
|
|
4083
|
+
"Back button linking to /tasks. Page header with task title and description.",
|
|
4084
|
+
"Two-column layout (md:grid-cols-2): Left Card with task details (status, priority, assignee, due date). Right Card with activity timeline.",
|
|
4085
|
+
'This page uses "use client" (has useState). Do NOT include export const metadata.'
|
|
4086
|
+
],
|
|
4087
|
+
components: ["Card", "CardHeader", "CardTitle", "CardContent", "Button"]
|
|
4088
|
+
},
|
|
4089
|
+
"reset-password": {
|
|
4090
|
+
description: "Reset password page with centered card form",
|
|
4091
|
+
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-sm".',
|
|
4093
|
+
'Card with CardHeader: CardTitle "Reset Password" (text-xl), CardDescription.',
|
|
4094
|
+
'CardContent with form: new password Input (type="password"), confirm password Input (type="password"), Button "Reset password" (w-full).',
|
|
4095
|
+
'Footer text: "Remember your password?" with Sign in link.',
|
|
4096
|
+
'This page uses "use client" (has useState for form state). Do NOT include export const metadata.'
|
|
4097
|
+
],
|
|
4098
|
+
components: ["Card", "CardHeader", "CardTitle", "CardDescription", "CardContent", "Button", "Input", "Label"]
|
|
4040
4099
|
}
|
|
4041
4100
|
};
|
|
4042
4101
|
var AUTH_ROUTE_SEGMENTS = /* @__PURE__ */ new Set([
|
|
@@ -4057,8 +4116,9 @@ function detectPageType(pageName) {
|
|
|
4057
4116
|
const normalized = pageName.toLowerCase();
|
|
4058
4117
|
if (/dashboard|admin|overview/.test(normalized)) return "dashboard";
|
|
4059
4118
|
if (/login|signin|sign-in/.test(normalized)) return "login";
|
|
4119
|
+
if (/register|signup|sign.?up/.test(normalized)) return "register";
|
|
4060
4120
|
if (/pricing|plans|subscription/.test(normalized)) return "pricing";
|
|
4061
|
-
if (/about|
|
|
4121
|
+
if (/about|company/.test(normalized)) return "about";
|
|
4062
4122
|
if (/contact|support|help/.test(normalized)) return "contact";
|
|
4063
4123
|
if (/settings|preferences|account/.test(normalized)) return "settings";
|
|
4064
4124
|
if (/home|landing|hero/.test(normalized)) return "landing";
|
|
@@ -4069,6 +4129,10 @@ function detectPageType(pageName) {
|
|
|
4069
4129
|
if (/gallery|portfolio|images/.test(normalized)) return "gallery";
|
|
4070
4130
|
if (/faq|frequently|questions/.test(normalized)) return "faq";
|
|
4071
4131
|
if (/changelog|release|versions/.test(normalized)) return "changelog";
|
|
4132
|
+
if (/team|members/.test(normalized)) return "team";
|
|
4133
|
+
if (/tasks?/.test(normalized) && /detail|\[id\]/.test(normalized)) return "task-detail";
|
|
4134
|
+
if (/tasks?/.test(normalized)) return "tasks";
|
|
4135
|
+
if (/reset.?password/.test(normalized)) return "reset-password";
|
|
4072
4136
|
return null;
|
|
4073
4137
|
}
|
|
4074
4138
|
function expandPageRequest(pageName, userRequest) {
|
|
@@ -4154,6 +4218,8 @@ LINKS & INTERACTIVE STATES (consistency is critical):
|
|
|
4154
4218
|
- ALL links on the SAME page MUST use the SAME style. Never mix underlined and non-underlined text links.
|
|
4155
4219
|
- ALL Button variants MUST have: hover: state, focus-visible:ring-2 focus-visible:ring-ring, active: state, disabled:opacity-50.
|
|
4156
4220
|
- ALL interactive elements MUST have visible hover and focus-visible states.
|
|
4221
|
+
- CRITICAL: Every <Link> MUST have an href prop. Missing href causes runtime errors. Never use <Link className="..."> or <Button asChild><Link> without href.
|
|
4222
|
+
- When shared components exist (@/components/shared/*), ALWAYS import and use them instead of re-implementing similar patterns inline.
|
|
4157
4223
|
|
|
4158
4224
|
ICONS:
|
|
4159
4225
|
- Size: ALWAYS size-4 (16px). Color: ALWAYS text-muted-foreground. Import: ALWAYS from lucide-react.
|
|
@@ -4905,6 +4971,11 @@ async function parseModification(message, context, provider = "auto", options) {
|
|
|
4905
4971
|
const navigation = !Array.isArray(raw2) && raw2?.navigation ? raw2.navigation : void 0;
|
|
4906
4972
|
return { requests: requestsArray2, uxRecommendations: void 0, navigation };
|
|
4907
4973
|
}
|
|
4974
|
+
if (options?.lightweight) {
|
|
4975
|
+
const raw2 = await ai.parseModification(message);
|
|
4976
|
+
const requestsArray2 = Array.isArray(raw2) ? raw2 : raw2?.requests ?? [];
|
|
4977
|
+
return { requests: requestsArray2, uxRecommendations: void 0 };
|
|
4978
|
+
}
|
|
4908
4979
|
const componentRegistry = buildComponentRegistry(context.componentManager);
|
|
4909
4980
|
let enhancedMessage = message;
|
|
4910
4981
|
let isExpandedPageRequest = false;
|
|
@@ -5013,6 +5084,7 @@ For editing an existing shared component use type "modify-layout-block" with tar
|
|
|
5013
5084
|
return `You are a design-forward UI architect. Your goal is to create interfaces that are not just functional, but visually distinctive and memorable \u2014 while staying within shadcn/ui and Tailwind CSS.
|
|
5014
5085
|
|
|
5015
5086
|
Parse the user's natural language request into structured modification requests.
|
|
5087
|
+
${sharedSection}
|
|
5016
5088
|
${designThinking}
|
|
5017
5089
|
${coreRules}
|
|
5018
5090
|
${designQuality}
|
|
@@ -5037,7 +5109,6 @@ LINKING RULES (CRITICAL \u2014 prevents broken links):
|
|
|
5037
5109
|
- Navigation components should link to ALL existing page routes.
|
|
5038
5110
|
|
|
5039
5111
|
${componentRegistry}
|
|
5040
|
-
${sharedSection}
|
|
5041
5112
|
|
|
5042
5113
|
Available shadcn/ui components (can be auto-installed): ${availableShadcn.join(", ")}
|
|
5043
5114
|
|
|
@@ -5264,6 +5335,17 @@ Return valid JSON only, no markdown code fence. Use this shape:
|
|
|
5264
5335
|
{ "requests": [ ... array of ModificationRequest ... ], "uxRecommendations": "optional markdown or omit key" }
|
|
5265
5336
|
Legacy: returning only a JSON array of requests is still accepted.`;
|
|
5266
5337
|
}
|
|
5338
|
+
function buildLightweightPagePrompt(pageName, route, styleContext, sharedComponentsSummary) {
|
|
5339
|
+
return [
|
|
5340
|
+
`Generate complete pageCode for a page called "${pageName}" at route "${route}".`,
|
|
5341
|
+
`Output valid TSX with a default export React component.`,
|
|
5342
|
+
`Use shadcn/ui components (import from @/components/ui/*). Use Tailwind CSS semantic tokens only.`,
|
|
5343
|
+
styleContext ? `Follow this style context:
|
|
5344
|
+
${styleContext}` : "",
|
|
5345
|
+
sharedComponentsSummary ? `Available shared components:
|
|
5346
|
+
${sharedComponentsSummary}` : ""
|
|
5347
|
+
].filter(Boolean).join("\n\n");
|
|
5348
|
+
}
|
|
5267
5349
|
async function checkComponentReuse(requests, componentManager) {
|
|
5268
5350
|
const enhanced = [];
|
|
5269
5351
|
for (const request of requests) {
|
|
@@ -6251,6 +6333,17 @@ function validatePageQuality(code, validRoutes) {
|
|
|
6251
6333
|
severity: "error"
|
|
6252
6334
|
});
|
|
6253
6335
|
}
|
|
6336
|
+
const linkWithoutHrefRe = /<(?:Link|a)\b(?![^>]*\bhref\s*=)[^>]*>/g;
|
|
6337
|
+
let linkNoHrefMatch;
|
|
6338
|
+
while ((linkNoHrefMatch = linkWithoutHrefRe.exec(code)) !== null) {
|
|
6339
|
+
const matchLine = code.slice(0, linkNoHrefMatch.index).split("\n").length;
|
|
6340
|
+
issues.push({
|
|
6341
|
+
line: matchLine,
|
|
6342
|
+
type: "LINK_MISSING_HREF",
|
|
6343
|
+
message: "<Link> or <a> without href prop \u2014 causes Next.js runtime error. Add href attribute.",
|
|
6344
|
+
severity: "error"
|
|
6345
|
+
});
|
|
6346
|
+
}
|
|
6254
6347
|
issues.push(...detectComponentIssues(code));
|
|
6255
6348
|
return issues;
|
|
6256
6349
|
}
|
|
@@ -6707,6 +6800,11 @@ ${selectImport}`
|
|
|
6707
6800
|
if (fixed !== beforeAsChildFlex) {
|
|
6708
6801
|
fixes.push("added inline-flex to Button asChild children (base-ui compat)");
|
|
6709
6802
|
}
|
|
6803
|
+
const beforeLinkHrefFix = fixed;
|
|
6804
|
+
fixed = fixed.replace(/<(Link|a)\b(?![^>]*\bhref\s*=)([^>]*)>/g, '<$1 href="/"$2>');
|
|
6805
|
+
if (fixed !== beforeLinkHrefFix) {
|
|
6806
|
+
fixes.push('added href="/" to <Link>/<a> missing href');
|
|
6807
|
+
}
|
|
6710
6808
|
const { code: fixedByRules, fixes: ruleFixes } = applyComponentRules(fixed);
|
|
6711
6809
|
if (ruleFixes.length > 0) {
|
|
6712
6810
|
fixed = fixedByRules;
|
|
@@ -7395,6 +7493,13 @@ function applyDefaults(request) {
|
|
|
7395
7493
|
return request;
|
|
7396
7494
|
}
|
|
7397
7495
|
|
|
7496
|
+
// src/commands/chat/split-generator.ts
|
|
7497
|
+
import { z } from "zod";
|
|
7498
|
+
import {
|
|
7499
|
+
loadManifest as loadManifest5,
|
|
7500
|
+
generateSharedComponent as generateSharedComponent2
|
|
7501
|
+
} from "@getcoherent/core";
|
|
7502
|
+
|
|
7398
7503
|
// src/utils/page-analyzer.ts
|
|
7399
7504
|
var FORM_COMPONENTS = /* @__PURE__ */ new Set(["Input", "Textarea", "Label", "Select", "Checkbox", "Switch"]);
|
|
7400
7505
|
var VISUAL_WORDS = /\b(grid lines?|glow|radial|gradient|blur|shadow|overlay|animation|particles?|dots?|vertical|horizontal|decorat|behind|background|divider|spacer|wrapper|container|inner|outer|absolute|relative|translate|opacity|z-index|transition)\b/i;
|
|
@@ -7589,9 +7694,27 @@ function parseNavTypeFromPlan(planResult) {
|
|
|
7589
7694
|
}
|
|
7590
7695
|
return "header";
|
|
7591
7696
|
}
|
|
7697
|
+
function buildSharedComponentsSummary(manifest) {
|
|
7698
|
+
if (manifest.shared.length === 0) return void 0;
|
|
7699
|
+
return manifest.shared.map((e) => {
|
|
7700
|
+
const importPath = e.file.replace(/^components\/shared\//, "").replace(/\.tsx$/, "");
|
|
7701
|
+
const desc = e.description ? ` \u2014 ${e.description}` : "";
|
|
7702
|
+
const propsLine = e.propsInterface ? `
|
|
7703
|
+
Props: ${e.propsInterface}` : "";
|
|
7704
|
+
return ` ${e.id} ${e.name} (${e.type})${desc}
|
|
7705
|
+
Import: @/components/shared/${importPath}${propsLine}`;
|
|
7706
|
+
}).join("\n");
|
|
7707
|
+
}
|
|
7708
|
+
function buildSharedComponentsNote(sharedComponentsSummary) {
|
|
7709
|
+
if (!sharedComponentsSummary) return void 0;
|
|
7710
|
+
return `SHARED COMPONENTS \u2014 MANDATORY REUSE:
|
|
7711
|
+
Before implementing any section, check this list. Import and use matching components from @/components/shared/. Do NOT re-implement these patterns inline.
|
|
7712
|
+
|
|
7713
|
+
${sharedComponentsSummary}`;
|
|
7714
|
+
}
|
|
7592
7715
|
async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts) {
|
|
7593
7716
|
let pageNames = [];
|
|
7594
|
-
spinner.start("Phase 1/
|
|
7717
|
+
spinner.start("Phase 1/5 \u2014 Planning pages...");
|
|
7595
7718
|
try {
|
|
7596
7719
|
const planResult = await parseModification(message, modCtx, provider, { ...parseOpts, planOnly: true });
|
|
7597
7720
|
const pageReqs = planResult.requests.filter((r) => r.type === "add-page");
|
|
@@ -7644,7 +7767,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7644
7767
|
const allRoutes = pageNames.map((p) => p.route).join(", ");
|
|
7645
7768
|
const allPagesList = pageNames.map((p) => `${p.name} (${p.route})`).join(", ");
|
|
7646
7769
|
const inferredNote = inferred.length > 0 ? ` (${inferred.length} auto-inferred)` : "";
|
|
7647
|
-
spinner.succeed(`Phase 1/
|
|
7770
|
+
spinner.succeed(`Phase 1/5 \u2014 Found ${pageNames.length} pages${inferredNote}: ${allPagesList}`);
|
|
7648
7771
|
const homeIdx = pageNames.findIndex((p) => p.route === "/");
|
|
7649
7772
|
const homePage = homeIdx !== -1 ? pageNames[homeIdx] : pageNames[0];
|
|
7650
7773
|
const remainingPages = pageNames.filter((_, i) => i !== (homeIdx !== -1 ? homeIdx : 0));
|
|
@@ -7652,17 +7775,18 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7652
7775
|
let homeRequest = null;
|
|
7653
7776
|
let homePageCode = "";
|
|
7654
7777
|
let reusedExistingAnchor = false;
|
|
7655
|
-
|
|
7778
|
+
const isPlaceholder = modCtx.config?.settings?.homePagePlaceholder === true;
|
|
7779
|
+
if (projectRoot && remainingPages.length > 0 && !isPlaceholder) {
|
|
7656
7780
|
const existingCode = readAnchorPageCodeFromDisk(projectRoot, homePage.route);
|
|
7657
7781
|
if (existingCode) {
|
|
7658
7782
|
reusedExistingAnchor = true;
|
|
7659
7783
|
homePageCode = existingCode;
|
|
7660
|
-
spinner.start(`Phase 2/
|
|
7661
|
-
spinner.succeed(`Phase 2/
|
|
7784
|
+
spinner.start(`Phase 2/5 \u2014 Loading ${homePage.name} from disk (style anchor)...`);
|
|
7785
|
+
spinner.succeed(`Phase 2/5 \u2014 Reused existing ${homePage.name} page (skipped AI regeneration)`);
|
|
7662
7786
|
}
|
|
7663
7787
|
}
|
|
7664
7788
|
if (!reusedExistingAnchor) {
|
|
7665
|
-
spinner.start(`Phase 2/
|
|
7789
|
+
spinner.start(`Phase 2/5 \u2014 Generating ${homePage.name} page (sets design direction)...`);
|
|
7666
7790
|
try {
|
|
7667
7791
|
const homeResult = await parseModification(
|
|
7668
7792
|
`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.`,
|
|
@@ -7684,22 +7808,42 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7684
7808
|
changes: { id: homePage.id, name: homePage.name, route: homePage.route }
|
|
7685
7809
|
};
|
|
7686
7810
|
}
|
|
7687
|
-
spinner.succeed(`Phase 2/
|
|
7811
|
+
spinner.succeed(`Phase 2/5 \u2014 ${homePage.name} page generated`);
|
|
7688
7812
|
}
|
|
7689
|
-
spinner.start("Phase 3/
|
|
7813
|
+
spinner.start("Phase 3/5 \u2014 Extracting design patterns...");
|
|
7690
7814
|
const styleContext = homePageCode ? extractStyleContext(homePageCode) : "";
|
|
7691
7815
|
if (styleContext) {
|
|
7692
7816
|
const lineCount = styleContext.split("\n").length - 1;
|
|
7693
7817
|
const source = reusedExistingAnchor ? `${homePage.name} (existing file)` : homePage.name;
|
|
7694
|
-
spinner.succeed(`Phase 3/
|
|
7818
|
+
spinner.succeed(`Phase 3/5 \u2014 Extracted ${lineCount} style patterns from ${source}`);
|
|
7695
7819
|
} else {
|
|
7696
|
-
spinner.succeed("Phase 3/
|
|
7820
|
+
spinner.succeed("Phase 3/5 \u2014 No style patterns extracted (anchor page had no code)");
|
|
7821
|
+
}
|
|
7822
|
+
if (remainingPages.length >= 2 && homePageCode && projectRoot) {
|
|
7823
|
+
const manifest = await loadManifest5(projectRoot);
|
|
7824
|
+
const shouldSkip = reusedExistingAnchor && manifest.shared.some((e) => e.type !== "layout");
|
|
7825
|
+
if (!shouldSkip) {
|
|
7826
|
+
spinner.start("Phase 3.5/5 \u2014 Extracting shared components...");
|
|
7827
|
+
try {
|
|
7828
|
+
const extraction = await extractSharedComponents(homePageCode, projectRoot, provider ?? "auto");
|
|
7829
|
+
parseOpts.sharedComponentsSummary = extraction.summary;
|
|
7830
|
+
if (extraction.components.length > 0) {
|
|
7831
|
+
const names = extraction.components.map((c) => c.name).join(", ");
|
|
7832
|
+
spinner.succeed(`Phase 3.5/5 \u2014 Extracted ${extraction.components.length} shared components (${names})`);
|
|
7833
|
+
} else {
|
|
7834
|
+
spinner.succeed("Phase 3.5/5 \u2014 No shared components extracted");
|
|
7835
|
+
}
|
|
7836
|
+
} catch {
|
|
7837
|
+
spinner.warn("Phase 3.5/5 \u2014 Could not extract shared components (continuing without)");
|
|
7838
|
+
}
|
|
7839
|
+
}
|
|
7697
7840
|
}
|
|
7698
7841
|
if (remainingPages.length === 0) {
|
|
7699
7842
|
return homeRequest ? [homeRequest] : [];
|
|
7700
7843
|
}
|
|
7701
|
-
spinner.start(`Phase 4/
|
|
7702
|
-
const
|
|
7844
|
+
spinner.start(`Phase 4/5 \u2014 Generating ${remainingPages.length} pages in parallel...`);
|
|
7845
|
+
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
|
+
const sharedComponentsNote = buildSharedComponentsNote(parseOpts.sharedComponentsSummary);
|
|
7703
7847
|
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="#".`;
|
|
7704
7848
|
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.';
|
|
7705
7849
|
const existingPagesContext = buildExistingPagesContext(modCtx.config);
|
|
@@ -7708,25 +7852,29 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7708
7852
|
const remainingRequests = await pMap(
|
|
7709
7853
|
remainingPages,
|
|
7710
7854
|
async ({ name, id, route }) => {
|
|
7855
|
+
const isAuth = isAuthRoute(route) || isAuthRoute(name);
|
|
7856
|
+
const authNote = isAuth ? 'For this auth page: use centered card layout with outer div className="flex min-h-svh flex-col items-center justify-center p-6 md:p-10" and inner div className="w-full max-w-sm". Do NOT use section containers or full-width wrappers. The auth layout provides centering \u2014 just output the card content.' : void 0;
|
|
7711
7857
|
const prompt = [
|
|
7712
7858
|
`Create ONE page called "${name}" at route "${route}".`,
|
|
7713
7859
|
`Context: ${message}.`,
|
|
7714
7860
|
`Generate complete pageCode for this single page only. Do not generate other pages.`,
|
|
7715
|
-
|
|
7861
|
+
sharedLayoutNote,
|
|
7862
|
+
sharedComponentsNote,
|
|
7716
7863
|
routeNote,
|
|
7717
7864
|
alignmentNote,
|
|
7865
|
+
authNote,
|
|
7718
7866
|
existingPagesContext,
|
|
7719
7867
|
styleContext
|
|
7720
7868
|
].filter(Boolean).join("\n\n");
|
|
7721
7869
|
try {
|
|
7722
7870
|
const result = await parseModification(prompt, modCtx, provider, parseOpts);
|
|
7723
7871
|
phase4Done++;
|
|
7724
|
-
spinner.text = `Phase 4/
|
|
7872
|
+
spinner.text = `Phase 4/5 \u2014 ${phase4Done}/${remainingPages.length} pages generated...`;
|
|
7725
7873
|
const codePage = result.requests.find((r) => r.type === "add-page");
|
|
7726
7874
|
return codePage || { type: "add-page", target: "new", changes: { id, name, route } };
|
|
7727
7875
|
} catch {
|
|
7728
7876
|
phase4Done++;
|
|
7729
|
-
spinner.text = `Phase 4/
|
|
7877
|
+
spinner.text = `Phase 4/5 \u2014 ${phase4Done}/${remainingPages.length} pages generated...`;
|
|
7730
7878
|
return { type: "add-page", target: "new", changes: { id, name, route } };
|
|
7731
7879
|
}
|
|
7732
7880
|
},
|
|
@@ -7734,18 +7882,24 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7734
7882
|
);
|
|
7735
7883
|
const allRequests = reusedExistingAnchor ? [...remainingRequests] : homeRequest ? [homeRequest, ...remainingRequests] : [...remainingRequests];
|
|
7736
7884
|
const emptyPages = allRequests.filter((r) => r.type === "add-page" && !r.changes?.pageCode);
|
|
7737
|
-
if (emptyPages.length > 0
|
|
7885
|
+
if (emptyPages.length > 0) {
|
|
7738
7886
|
spinner.text = `Retrying ${emptyPages.length} page(s) without code...`;
|
|
7739
7887
|
for (const req of emptyPages) {
|
|
7740
7888
|
const page = req.changes;
|
|
7741
7889
|
const pageName = page.name || page.id || "page";
|
|
7742
7890
|
const pageRoute = page.route || `/${pageName.toLowerCase()}`;
|
|
7743
7891
|
try {
|
|
7892
|
+
const lightweightPrompt = buildLightweightPagePrompt(
|
|
7893
|
+
pageName,
|
|
7894
|
+
pageRoute,
|
|
7895
|
+
styleContext || "",
|
|
7896
|
+
parseOpts.sharedComponentsSummary
|
|
7897
|
+
);
|
|
7744
7898
|
const retryResult = await parseModification(
|
|
7745
|
-
|
|
7899
|
+
lightweightPrompt,
|
|
7746
7900
|
modCtx,
|
|
7747
7901
|
provider,
|
|
7748
|
-
parseOpts
|
|
7902
|
+
{ ...parseOpts, lightweight: true }
|
|
7749
7903
|
);
|
|
7750
7904
|
const codePage = retryResult.requests.find((r) => r.type === "add-page");
|
|
7751
7905
|
if (codePage && codePage.changes?.pageCode) {
|
|
@@ -7757,9 +7911,77 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7757
7911
|
}
|
|
7758
7912
|
}
|
|
7759
7913
|
const withCode = allRequests.filter((r) => r.changes?.pageCode).length;
|
|
7760
|
-
spinner.succeed(`Phase 4/
|
|
7914
|
+
spinner.succeed(`Phase 4/5 \u2014 Generated ${allRequests.length} pages (${withCode} with full code)`);
|
|
7761
7915
|
return allRequests;
|
|
7762
7916
|
}
|
|
7917
|
+
var SharedExtractionItemSchema = z.object({
|
|
7918
|
+
name: z.string().min(2).max(50),
|
|
7919
|
+
type: z.enum(["section", "widget"]),
|
|
7920
|
+
description: z.string().max(200).default(""),
|
|
7921
|
+
propsInterface: z.string().default("{}"),
|
|
7922
|
+
code: z.string()
|
|
7923
|
+
});
|
|
7924
|
+
var SharedExtractionResponseSchema = z.object({
|
|
7925
|
+
components: z.array(SharedExtractionItemSchema).max(5).default([])
|
|
7926
|
+
});
|
|
7927
|
+
async function extractSharedComponents(homePageCode, projectRoot, aiProvider) {
|
|
7928
|
+
const manifest = await loadManifest5(projectRoot);
|
|
7929
|
+
let ai;
|
|
7930
|
+
try {
|
|
7931
|
+
ai = await createAIProvider(aiProvider);
|
|
7932
|
+
} catch {
|
|
7933
|
+
return { components: [], summary: buildSharedComponentsSummary(manifest) };
|
|
7934
|
+
}
|
|
7935
|
+
if (!ai.extractSharedComponents) {
|
|
7936
|
+
return { components: [], summary: buildSharedComponentsSummary(manifest) };
|
|
7937
|
+
}
|
|
7938
|
+
let rawItems;
|
|
7939
|
+
try {
|
|
7940
|
+
const reservedNames = getComponentProvider().listNames();
|
|
7941
|
+
const existingNames = manifest.shared.map((e) => e.name);
|
|
7942
|
+
const result = await ai.extractSharedComponents(homePageCode, reservedNames, existingNames);
|
|
7943
|
+
const parsed = SharedExtractionResponseSchema.safeParse(result);
|
|
7944
|
+
rawItems = parsed.success ? parsed.data.components : [];
|
|
7945
|
+
} catch {
|
|
7946
|
+
return { components: [], summary: buildSharedComponentsSummary(manifest) };
|
|
7947
|
+
}
|
|
7948
|
+
const reservedSet = new Set(
|
|
7949
|
+
getComponentProvider().listNames().map((n) => n.toLowerCase())
|
|
7950
|
+
);
|
|
7951
|
+
const existingSet = new Set(manifest.shared.map((e) => e.name.toLowerCase()));
|
|
7952
|
+
const seenNames = /* @__PURE__ */ new Set();
|
|
7953
|
+
const filtered = rawItems.filter((item) => {
|
|
7954
|
+
if (item.code.split("\n").length < 10) return false;
|
|
7955
|
+
if (reservedSet.has(item.name.toLowerCase())) return false;
|
|
7956
|
+
if (existingSet.has(item.name.toLowerCase())) return false;
|
|
7957
|
+
if (seenNames.has(item.name.toLowerCase())) return false;
|
|
7958
|
+
seenNames.add(item.name.toLowerCase());
|
|
7959
|
+
return true;
|
|
7960
|
+
});
|
|
7961
|
+
const results = [];
|
|
7962
|
+
const provider = getComponentProvider();
|
|
7963
|
+
for (const item of filtered) {
|
|
7964
|
+
try {
|
|
7965
|
+
const { code: fixedCode } = await autoFixCode(item.code);
|
|
7966
|
+
const shadcnImports = [...fixedCode.matchAll(/from\s+["']@\/components\/ui\/(.+?)["']/g)];
|
|
7967
|
+
for (const match of shadcnImports) {
|
|
7968
|
+
await provider.installComponent(match[1], projectRoot);
|
|
7969
|
+
}
|
|
7970
|
+
const result = await generateSharedComponent2(projectRoot, {
|
|
7971
|
+
name: item.name,
|
|
7972
|
+
type: item.type,
|
|
7973
|
+
code: fixedCode,
|
|
7974
|
+
description: item.description,
|
|
7975
|
+
propsInterface: item.propsInterface,
|
|
7976
|
+
usedIn: []
|
|
7977
|
+
});
|
|
7978
|
+
results.push(result);
|
|
7979
|
+
} catch {
|
|
7980
|
+
}
|
|
7981
|
+
}
|
|
7982
|
+
const updatedManifest = await loadManifest5(projectRoot);
|
|
7983
|
+
return { components: results, summary: buildSharedComponentsSummary(updatedManifest) };
|
|
7984
|
+
}
|
|
7763
7985
|
function extractAppNameFromPrompt(prompt) {
|
|
7764
7986
|
const patterns = [
|
|
7765
7987
|
/(?:called|named|app\s+name)\s+["']([^"']+)["']/i,
|
|
@@ -7807,16 +8029,16 @@ import { dirname as dirname6 } from "path";
|
|
|
7807
8029
|
import chalk11 from "chalk";
|
|
7808
8030
|
import {
|
|
7809
8031
|
getTemplateForPageType,
|
|
7810
|
-
loadManifest as
|
|
8032
|
+
loadManifest as loadManifest6,
|
|
7811
8033
|
saveManifest,
|
|
7812
8034
|
updateUsedIn,
|
|
7813
8035
|
findSharedComponentByIdOrName,
|
|
7814
|
-
generateSharedComponent as
|
|
8036
|
+
generateSharedComponent as generateSharedComponent4
|
|
7815
8037
|
} from "@getcoherent/core";
|
|
7816
8038
|
|
|
7817
8039
|
// src/commands/chat/code-generator.ts
|
|
7818
8040
|
import { resolve as resolve6 } from "path";
|
|
7819
|
-
import { existsSync as existsSync14 } from "fs";
|
|
8041
|
+
import { existsSync as existsSync14, readdirSync as readdirSync2, readFileSync as readFileSync9 } from "fs";
|
|
7820
8042
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
7821
8043
|
import { dirname as dirname5 } from "path";
|
|
7822
8044
|
import {
|
|
@@ -7824,7 +8046,7 @@ import {
|
|
|
7824
8046
|
PageGenerator,
|
|
7825
8047
|
TailwindConfigGenerator
|
|
7826
8048
|
} from "@getcoherent/core";
|
|
7827
|
-
import { integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout2, generateSharedComponent as
|
|
8049
|
+
import { integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout2, generateSharedComponent as generateSharedComponent3 } from "@getcoherent/core";
|
|
7828
8050
|
import chalk9 from "chalk";
|
|
7829
8051
|
|
|
7830
8052
|
// src/utils/file-hashes.ts
|
|
@@ -7950,7 +8172,7 @@ async function regenerateLayout(config2, projectRoot, options = { navChanged: fa
|
|
|
7950
8172
|
if (navType === "header" || navType === "both") {
|
|
7951
8173
|
if (await canOverwriteShared(projectRoot, "components/shared/header.tsx", hashes)) {
|
|
7952
8174
|
const headerCode = generator.generateSharedHeaderCode();
|
|
7953
|
-
await
|
|
8175
|
+
await generateSharedComponent3(projectRoot, {
|
|
7954
8176
|
name: "Header",
|
|
7955
8177
|
type: "layout",
|
|
7956
8178
|
code: headerCode,
|
|
@@ -7962,7 +8184,7 @@ async function regenerateLayout(config2, projectRoot, options = { navChanged: fa
|
|
|
7962
8184
|
}
|
|
7963
8185
|
if (await canOverwriteShared(projectRoot, "components/shared/footer.tsx", hashes)) {
|
|
7964
8186
|
const footerCode = generator.generateSharedFooterCode();
|
|
7965
|
-
await
|
|
8187
|
+
await generateSharedComponent3(projectRoot, {
|
|
7966
8188
|
name: "Footer",
|
|
7967
8189
|
type: "layout",
|
|
7968
8190
|
code: footerCode,
|
|
@@ -7974,7 +8196,7 @@ async function regenerateLayout(config2, projectRoot, options = { navChanged: fa
|
|
|
7974
8196
|
if (navType === "sidebar" || navType === "both") {
|
|
7975
8197
|
if (await canOverwriteShared(projectRoot, "components/shared/sidebar.tsx", hashes)) {
|
|
7976
8198
|
const sidebarCode = generator.generateSharedSidebarCode();
|
|
7977
|
-
await
|
|
8199
|
+
await generateSharedComponent3(projectRoot, {
|
|
7978
8200
|
name: "AppSidebar",
|
|
7979
8201
|
type: "layout",
|
|
7980
8202
|
code: sidebarCode,
|
|
@@ -7996,6 +8218,28 @@ async function regenerateLayout(config2, projectRoot, options = { navChanged: fa
|
|
|
7996
8218
|
}
|
|
7997
8219
|
}
|
|
7998
8220
|
}
|
|
8221
|
+
async function scanAndInstallSharedDeps(projectRoot) {
|
|
8222
|
+
const sharedDir = resolve6(projectRoot, "components", "shared");
|
|
8223
|
+
if (!existsSync14(sharedDir)) return [];
|
|
8224
|
+
const files = readdirSync2(sharedDir).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts"));
|
|
8225
|
+
const installed = [];
|
|
8226
|
+
const provider = getComponentProvider();
|
|
8227
|
+
for (const file of files) {
|
|
8228
|
+
const code = readFileSync9(resolve6(sharedDir, file), "utf-8");
|
|
8229
|
+
const importMatches = [...code.matchAll(/@\/components\/ui\/([a-z0-9-]+)/g)];
|
|
8230
|
+
for (const [, componentId] of importMatches) {
|
|
8231
|
+
const uiPath = resolve6(projectRoot, "components", "ui", `${componentId}.tsx`);
|
|
8232
|
+
if (!existsSync14(uiPath) && provider.has(componentId)) {
|
|
8233
|
+
try {
|
|
8234
|
+
await provider.installComponent(componentId, projectRoot);
|
|
8235
|
+
installed.push(componentId);
|
|
8236
|
+
} catch {
|
|
8237
|
+
}
|
|
8238
|
+
}
|
|
8239
|
+
}
|
|
8240
|
+
}
|
|
8241
|
+
return [...new Set(installed)];
|
|
8242
|
+
}
|
|
7999
8243
|
async function ensureAppRouteGroupLayout(projectRoot, navType, forceUpdate = false) {
|
|
8000
8244
|
const layoutPath = resolve6(projectRoot, "app", "(app)", "layout.tsx");
|
|
8001
8245
|
if (existsSync14(layoutPath) && !forceUpdate) return;
|
|
@@ -8053,6 +8297,10 @@ async function regenerateFiles(modified, config2, projectRoot, options = { navCh
|
|
|
8053
8297
|
navChanged: options.navChanged,
|
|
8054
8298
|
storedHashes: options.storedHashes
|
|
8055
8299
|
});
|
|
8300
|
+
const sharedInstalled = await scanAndInstallSharedDeps(projectRoot);
|
|
8301
|
+
if (sharedInstalled.length > 0 && process.env.COHERENT_DEBUG === "1") {
|
|
8302
|
+
console.log(chalk9.dim(` Auto-installed shared deps: ${sharedInstalled.join(", ")}`));
|
|
8303
|
+
}
|
|
8056
8304
|
}
|
|
8057
8305
|
if (componentIds.size > 0) {
|
|
8058
8306
|
const twGen = new TailwindConfigGenerator(config2);
|
|
@@ -8526,7 +8774,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8526
8774
|
fixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
8527
8775
|
}
|
|
8528
8776
|
await writeFile(pageFilePath, fixedCode);
|
|
8529
|
-
const manifest = await
|
|
8777
|
+
const manifest = await loadManifest6(projectRoot);
|
|
8530
8778
|
const usedIn = manifest.shared.find((e) => e.id === resolved.id)?.usedIn ?? [];
|
|
8531
8779
|
const routePath = route.replace(/^\//, "");
|
|
8532
8780
|
const filePathRel = routePath ? `app/${routePath}/page.tsx` : "app/page.tsx";
|
|
@@ -8596,7 +8844,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8596
8844
|
};
|
|
8597
8845
|
}
|
|
8598
8846
|
const extractedCode = await ai.extractBlockAsComponent(sourceCode, blockHint, componentName);
|
|
8599
|
-
const created = await
|
|
8847
|
+
const created = await generateSharedComponent4(projectRoot, {
|
|
8600
8848
|
name: componentName,
|
|
8601
8849
|
type: "section",
|
|
8602
8850
|
code: extractedCode,
|
|
@@ -8631,7 +8879,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8631
8879
|
await writeFile(fullPath, fixedCode);
|
|
8632
8880
|
usedInFiles.push(relPath);
|
|
8633
8881
|
}
|
|
8634
|
-
const manifest = await
|
|
8882
|
+
const manifest = await loadManifest6(projectRoot);
|
|
8635
8883
|
const nextManifest = updateUsedIn(manifest, created.id, usedInFiles);
|
|
8636
8884
|
await saveManifest(projectRoot, nextManifest);
|
|
8637
8885
|
printPromoteAndLinkReport({
|
|
@@ -8798,7 +9046,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8798
9046
|
}
|
|
8799
9047
|
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
8800
9048
|
codeToWrite = layoutStripped;
|
|
8801
|
-
if (!isMarketingRoute(route)) {
|
|
9049
|
+
if (!isMarketingRoute(route) && !isAuthRoute(route)) {
|
|
8802
9050
|
const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
|
|
8803
9051
|
if (wrapperFixed) {
|
|
8804
9052
|
codeToWrite = normalized;
|
|
@@ -8820,7 +9068,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8820
9068
|
cm.updateConfig(cfg);
|
|
8821
9069
|
pm.updateConfig(cfg);
|
|
8822
9070
|
}
|
|
8823
|
-
const manifestForAudit = await
|
|
9071
|
+
const manifestForAudit = await loadManifest6(projectRoot);
|
|
8824
9072
|
await warnInlineDuplicates(projectRoot, page.name || page.id || route.slice(1), codeToWrite, manifestForAudit);
|
|
8825
9073
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
8826
9074
|
printPostGenerationReport({
|
|
@@ -8998,7 +9246,7 @@ ${pagesCtx}`
|
|
|
8998
9246
|
}
|
|
8999
9247
|
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
9000
9248
|
codeToWrite = layoutStripped;
|
|
9001
|
-
if (!isMarketingRoute(route)) {
|
|
9249
|
+
if (!isMarketingRoute(route) && !isAuthRoute(route)) {
|
|
9002
9250
|
const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
|
|
9003
9251
|
if (wrapperFixed) {
|
|
9004
9252
|
codeToWrite = normalized;
|
|
@@ -9020,7 +9268,7 @@ ${pagesCtx}`
|
|
|
9020
9268
|
cm.updateConfig(cfg);
|
|
9021
9269
|
pm.updateConfig(cfg);
|
|
9022
9270
|
}
|
|
9023
|
-
const manifestForAudit = await
|
|
9271
|
+
const manifestForAudit = await loadManifest6(projectRoot);
|
|
9024
9272
|
await warnInlineDuplicates(
|
|
9025
9273
|
projectRoot,
|
|
9026
9274
|
pageDef.name || pageDef.id || route.slice(1),
|
|
@@ -9063,7 +9311,7 @@ ${pagesCtx}`
|
|
|
9063
9311
|
fixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
9064
9312
|
}
|
|
9065
9313
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
9066
|
-
const manifest = await
|
|
9314
|
+
const manifest = await loadManifest6(projectRoot);
|
|
9067
9315
|
printPostGenerationReport({
|
|
9068
9316
|
action: "updated",
|
|
9069
9317
|
pageTitle: pageDef.name || pageDef.id || "Page",
|
|
@@ -9119,6 +9367,11 @@ function inferPageType(route, name) {
|
|
|
9119
9367
|
if (/\bchangelog\b/.test(key)) return "changelog";
|
|
9120
9368
|
if (/\babout\b/.test(key)) return "about";
|
|
9121
9369
|
if (/\bsettings?\b/.test(key)) return "settings";
|
|
9370
|
+
if (/\bteam\b|\bmember\b/.test(key)) return "team";
|
|
9371
|
+
if (/\btasks?\b/.test(key) && /\[id\]|\bdetail\b/.test(key)) return "task-detail";
|
|
9372
|
+
if (/\btasks?\b/.test(key)) return "tasks";
|
|
9373
|
+
if (/\breset.?password\b/.test(key)) return "reset-password";
|
|
9374
|
+
if (/\bprofile\b|\baccount\b/.test(key)) return "profile";
|
|
9122
9375
|
return null;
|
|
9123
9376
|
}
|
|
9124
9377
|
function getDefaultContent(pageType, pageName) {
|
|
@@ -9162,6 +9415,26 @@ function getDefaultContent(pageType, pageName) {
|
|
|
9162
9415
|
settings: {
|
|
9163
9416
|
title: "Settings",
|
|
9164
9417
|
description: "Manage your account and preferences"
|
|
9418
|
+
},
|
|
9419
|
+
team: {
|
|
9420
|
+
title: "Our Team",
|
|
9421
|
+
description: "Meet the people behind the product"
|
|
9422
|
+
},
|
|
9423
|
+
tasks: {
|
|
9424
|
+
title: "Tasks",
|
|
9425
|
+
description: "Manage and track your tasks"
|
|
9426
|
+
},
|
|
9427
|
+
"task-detail": {
|
|
9428
|
+
title: "Task Detail",
|
|
9429
|
+
description: "View task details and activity"
|
|
9430
|
+
},
|
|
9431
|
+
"reset-password": {
|
|
9432
|
+
title: "Reset Password",
|
|
9433
|
+
description: "Enter your new password"
|
|
9434
|
+
},
|
|
9435
|
+
profile: {
|
|
9436
|
+
title: "Profile",
|
|
9437
|
+
description: "View and edit your profile"
|
|
9165
9438
|
}
|
|
9166
9439
|
};
|
|
9167
9440
|
return defaults[pageType] || { title: pageName, description: "" };
|
|
@@ -9179,8 +9452,8 @@ function hasNavChanged(before, after) {
|
|
|
9179
9452
|
// src/commands/chat/interactive.ts
|
|
9180
9453
|
import chalk12 from "chalk";
|
|
9181
9454
|
import { resolve as resolve8 } from "path";
|
|
9182
|
-
import { existsSync as existsSync15, readFileSync as
|
|
9183
|
-
import { DesignSystemManager as DesignSystemManager6, ComponentManager as
|
|
9455
|
+
import { existsSync as existsSync15, readFileSync as readFileSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
9456
|
+
import { DesignSystemManager as DesignSystemManager6, ComponentManager as ComponentManager4, loadManifest as loadManifest7 } from "@getcoherent/core";
|
|
9184
9457
|
var DEBUG3 = process.env.COHERENT_DEBUG === "1";
|
|
9185
9458
|
async function interactiveChat(options, chatCommandFn) {
|
|
9186
9459
|
const { createInterface } = await import("readline");
|
|
@@ -9191,7 +9464,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9191
9464
|
const config2 = await loadConfig(configPath);
|
|
9192
9465
|
const dsm = new DesignSystemManager6(configPath);
|
|
9193
9466
|
await dsm.load();
|
|
9194
|
-
const cm = new
|
|
9467
|
+
const cm = new ComponentManager4(config2);
|
|
9195
9468
|
const validProviders = ["claude", "openai", "auto"];
|
|
9196
9469
|
const provider = (options.provider || "auto").toLowerCase();
|
|
9197
9470
|
if (!validProviders.includes(provider)) {
|
|
@@ -9205,7 +9478,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9205
9478
|
try {
|
|
9206
9479
|
mkdirSync5(historyDir, { recursive: true });
|
|
9207
9480
|
if (existsSync15(historyFile)) {
|
|
9208
|
-
history =
|
|
9481
|
+
history = readFileSync10(historyFile, "utf-8").split("\n").filter(Boolean).slice(-200);
|
|
9209
9482
|
}
|
|
9210
9483
|
} catch (e) {
|
|
9211
9484
|
if (DEBUG3) console.error("Failed to load REPL history:", e);
|
|
@@ -9250,7 +9523,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9250
9523
|
return;
|
|
9251
9524
|
}
|
|
9252
9525
|
if (lower === "components" || lower === "list components" || lower.includes("what components")) {
|
|
9253
|
-
const manifest = await
|
|
9526
|
+
const manifest = await loadManifest7(projectRoot);
|
|
9254
9527
|
if (manifest.shared.length === 0) {
|
|
9255
9528
|
console.log(chalk12.gray("\n No shared components yet.\n"));
|
|
9256
9529
|
} else {
|
|
@@ -9284,7 +9557,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9284
9557
|
}
|
|
9285
9558
|
if (lower === "status") {
|
|
9286
9559
|
const currentConfig = dsm.getConfig();
|
|
9287
|
-
const manifest = await
|
|
9560
|
+
const manifest = await loadManifest7(projectRoot);
|
|
9288
9561
|
console.log(chalk12.bold(`
|
|
9289
9562
|
${currentConfig.name || "Coherent Project"}`));
|
|
9290
9563
|
console.log(
|
|
@@ -9417,7 +9690,7 @@ async function chatCommand(message, options) {
|
|
|
9417
9690
|
const storedHashes = await loadHashes(projectRoot);
|
|
9418
9691
|
const dsm = new DesignSystemManager7(configPath);
|
|
9419
9692
|
await dsm.load();
|
|
9420
|
-
const cm = new
|
|
9693
|
+
const cm = new ComponentManager5(config2);
|
|
9421
9694
|
const pm = new PageManager3(config2, cm);
|
|
9422
9695
|
spinner.succeed("Configuration loaded");
|
|
9423
9696
|
message = await resolveTargetFlags(message, options, config2, projectRoot);
|
|
@@ -9473,7 +9746,7 @@ async function chatCommand(message, options) {
|
|
|
9473
9746
|
}
|
|
9474
9747
|
}
|
|
9475
9748
|
spinner.start("Parsing your request...");
|
|
9476
|
-
let manifest = await
|
|
9749
|
+
let manifest = await loadManifest8(project.root);
|
|
9477
9750
|
const validShared = manifest.shared.filter((s) => {
|
|
9478
9751
|
const fp = resolve9(project.root, s.file);
|
|
9479
9752
|
return existsSync16(fp);
|
|
@@ -9486,12 +9759,7 @@ async function chatCommand(message, options) {
|
|
|
9486
9759
|
console.log(chalk13.dim(`[pre-gen] Cleaned ${cleaned} orphaned component(s) from manifest`));
|
|
9487
9760
|
}
|
|
9488
9761
|
}
|
|
9489
|
-
const sharedComponentsSummary = manifest
|
|
9490
|
-
const importPath = e.file.replace(/^components\/shared\//, "").replace(/\.tsx$/, "");
|
|
9491
|
-
const desc = e.description ? ` \u2014 ${e.description}` : "";
|
|
9492
|
-
return ` ${e.id} ${e.name} (${e.type})${desc}
|
|
9493
|
-
Import: @/components/shared/${importPath}`;
|
|
9494
|
-
}).join("\n") : void 0;
|
|
9762
|
+
const sharedComponentsSummary = buildSharedComponentsSummary(manifest);
|
|
9495
9763
|
if (DEBUG4 && sharedComponentsSummary) {
|
|
9496
9764
|
console.log(chalk13.dim("[add-page] sharedComponentsSummary in prompt:\n" + sharedComponentsSummary));
|
|
9497
9765
|
}
|
|
@@ -9677,7 +9945,7 @@ async function chatCommand(message, options) {
|
|
|
9677
9945
|
try {
|
|
9678
9946
|
const sharedPath = resolve9(projectRoot, entry.file);
|
|
9679
9947
|
if (existsSync16(sharedPath)) {
|
|
9680
|
-
const sharedCode =
|
|
9948
|
+
const sharedCode = readFileSync11(sharedPath, "utf-8");
|
|
9681
9949
|
const sharedImports = sharedCode.matchAll(/@\/components\/ui\/([a-z0-9-]+)/g);
|
|
9682
9950
|
for (const m of sharedImports) {
|
|
9683
9951
|
if (m[1]) allNeededComponentIds.add(m[1]);
|
|
@@ -9719,7 +9987,10 @@ async function chatCommand(message, options) {
|
|
|
9719
9987
|
if (DEBUG4) console.log(chalk13.gray(` [DEBUG] installComponent result: ${result.success}`));
|
|
9720
9988
|
if (result.success && result.componentDef) {
|
|
9721
9989
|
if (!cm.read(componentId)) {
|
|
9722
|
-
if (DEBUG4)
|
|
9990
|
+
if (DEBUG4)
|
|
9991
|
+
console.log(
|
|
9992
|
+
chalk13.gray(` [DEBUG] Registering ${result.componentDef.id} (${result.componentDef.name})`)
|
|
9993
|
+
);
|
|
9723
9994
|
const regResult = await cm.register(result.componentDef);
|
|
9724
9995
|
if (DEBUG4) {
|
|
9725
9996
|
console.log(
|
|
@@ -9799,6 +10070,17 @@ async function chatCommand(message, options) {
|
|
|
9799
10070
|
const result = await applyModification(request, dsm, cm, pm, projectRoot, provider, message);
|
|
9800
10071
|
results.push(result);
|
|
9801
10072
|
}
|
|
10073
|
+
for (const request of normalizedRequests) {
|
|
10074
|
+
const changes = request.changes;
|
|
10075
|
+
if ((request.type === "add-page" || request.type === "update-page") && changes?.route === "/" && changes?.pageCode) {
|
|
10076
|
+
const cfg = dsm.getConfig();
|
|
10077
|
+
if (cfg.settings.homePagePlaceholder) {
|
|
10078
|
+
cfg.settings.homePagePlaceholder = false;
|
|
10079
|
+
dsm.updateConfig(cfg);
|
|
10080
|
+
}
|
|
10081
|
+
break;
|
|
10082
|
+
}
|
|
10083
|
+
}
|
|
9802
10084
|
const currentConfig = dsm.getConfig();
|
|
9803
10085
|
const autoScaffoldEnabled = currentConfig.settings.autoScaffold === true;
|
|
9804
10086
|
const scaffoldedPages = [];
|
|
@@ -9812,7 +10094,7 @@ async function chatCommand(message, options) {
|
|
|
9812
10094
|
let pageCode = "";
|
|
9813
10095
|
if (existsSync16(pageFilePath)) {
|
|
9814
10096
|
try {
|
|
9815
|
-
pageCode =
|
|
10097
|
+
pageCode = readFileSync11(pageFilePath, "utf-8");
|
|
9816
10098
|
} catch {
|
|
9817
10099
|
}
|
|
9818
10100
|
}
|
|
@@ -9923,7 +10205,7 @@ async function chatCommand(message, options) {
|
|
|
9923
10205
|
for (const mod of result.modified) {
|
|
9924
10206
|
if (mod.startsWith("app/") && mod.endsWith("/page.tsx")) {
|
|
9925
10207
|
try {
|
|
9926
|
-
const code =
|
|
10208
|
+
const code = readFileSync11(resolve9(projectRoot, mod), "utf-8");
|
|
9927
10209
|
const issues = validatePageQuality(code, allRoutes).filter(
|
|
9928
10210
|
(i) => i.type === "BROKEN_INTERNAL_LINK"
|
|
9929
10211
|
);
|
|
@@ -9993,6 +10275,10 @@ async function chatCommand(message, options) {
|
|
|
9993
10275
|
await regenerateFiles(Array.from(allModified), updatedConfig, projectRoot, { navChanged, storedHashes });
|
|
9994
10276
|
spinner.succeed("Files regenerated");
|
|
9995
10277
|
}
|
|
10278
|
+
const finalDeps = await scanAndInstallSharedDeps(projectRoot);
|
|
10279
|
+
if (finalDeps.length > 0) {
|
|
10280
|
+
console.log(chalk13.dim(` Auto-installed shared deps: ${finalDeps.join(", ")}`));
|
|
10281
|
+
}
|
|
9996
10282
|
try {
|
|
9997
10283
|
fixGlobalsCss(projectRoot, updatedConfig);
|
|
9998
10284
|
} catch {
|
|
@@ -10003,7 +10289,7 @@ async function chatCommand(message, options) {
|
|
|
10003
10289
|
const layoutFile = resolve9(projectRoot, "app", "layout.tsx");
|
|
10004
10290
|
const filesToHash = [layoutFile];
|
|
10005
10291
|
if (existsSync16(sharedDir)) {
|
|
10006
|
-
for (const f of
|
|
10292
|
+
for (const f of readdirSync3(sharedDir)) {
|
|
10007
10293
|
if (f.endsWith(".tsx")) filesToHash.push(resolve9(sharedDir, f));
|
|
10008
10294
|
}
|
|
10009
10295
|
}
|
|
@@ -10123,18 +10409,18 @@ ${uxRecommendations}
|
|
|
10123
10409
|
import chalk14 from "chalk";
|
|
10124
10410
|
import ora3 from "ora";
|
|
10125
10411
|
import { spawn } from "child_process";
|
|
10126
|
-
import { existsSync as existsSync19, rmSync as rmSync3, readFileSync as
|
|
10412
|
+
import { existsSync as existsSync19, rmSync as rmSync3, readFileSync as readFileSync14, writeFileSync as writeFileSync10, readdirSync as readdirSync5 } from "fs";
|
|
10127
10413
|
import { resolve as resolve10, join as join14 } from "path";
|
|
10128
10414
|
import { readdir as readdir2 } from "fs/promises";
|
|
10129
10415
|
import { DesignSystemManager as DesignSystemManager8, ComponentGenerator as ComponentGenerator3 } from "@getcoherent/core";
|
|
10130
10416
|
|
|
10131
10417
|
// src/utils/file-watcher.ts
|
|
10132
|
-
import { readFileSync as
|
|
10418
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync18 } from "fs";
|
|
10133
10419
|
import { relative as relative4, join as join13 } from "path";
|
|
10134
|
-
import { loadManifest as
|
|
10420
|
+
import { loadManifest as loadManifest9, saveManifest as saveManifest3 } from "@getcoherent/core";
|
|
10135
10421
|
|
|
10136
10422
|
// src/utils/component-integrity.ts
|
|
10137
|
-
import { existsSync as existsSync17, readFileSync as
|
|
10423
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12, readdirSync as readdirSync4 } from "fs";
|
|
10138
10424
|
import { join as join12, relative as relative3 } from "path";
|
|
10139
10425
|
function extractExportedComponentNames(code) {
|
|
10140
10426
|
const names = [];
|
|
@@ -10168,7 +10454,7 @@ function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
|
10168
10454
|
for (const absPath of pageFiles) {
|
|
10169
10455
|
if (absPath.includes("design-system")) continue;
|
|
10170
10456
|
try {
|
|
10171
|
-
const code =
|
|
10457
|
+
const code = readFileSync12(absPath, "utf-8");
|
|
10172
10458
|
const hasNamedImport = new RegExp(`import\\s+\\{[^}]*\\b${componentName}\\b[^}]*\\}\\s+from\\s+['"]`).test(code);
|
|
10173
10459
|
const hasDefaultImport = new RegExp(`import\\s+${componentName}\\s+from\\s+['"]`).test(code);
|
|
10174
10460
|
const hasPathImport = code.includes(`@/${componentImportPath}`);
|
|
@@ -10184,7 +10470,7 @@ function isUsedInLayout(projectRoot, componentName) {
|
|
|
10184
10470
|
const layoutPath = join12(projectRoot, "app", "layout.tsx");
|
|
10185
10471
|
if (!existsSync17(layoutPath)) return false;
|
|
10186
10472
|
try {
|
|
10187
|
-
const code =
|
|
10473
|
+
const code = readFileSync12(layoutPath, "utf-8");
|
|
10188
10474
|
return code.includes(componentName);
|
|
10189
10475
|
} catch {
|
|
10190
10476
|
return false;
|
|
@@ -10205,7 +10491,7 @@ function findUnregisteredComponents(projectRoot, manifest) {
|
|
|
10205
10491
|
const relFile = relative3(projectRoot, absPath);
|
|
10206
10492
|
if (registeredFiles.has(relFile)) continue;
|
|
10207
10493
|
try {
|
|
10208
|
-
const code =
|
|
10494
|
+
const code = readFileSync12(absPath, "utf-8");
|
|
10209
10495
|
const exports = extractExportedComponentNames(code);
|
|
10210
10496
|
for (const name of exports) {
|
|
10211
10497
|
if (registeredNames.has(name)) continue;
|
|
@@ -10227,7 +10513,7 @@ function findInlineDuplicates(projectRoot, manifest) {
|
|
|
10227
10513
|
if (absPath.includes("design-system")) continue;
|
|
10228
10514
|
let code;
|
|
10229
10515
|
try {
|
|
10230
|
-
code =
|
|
10516
|
+
code = readFileSync12(absPath, "utf-8");
|
|
10231
10517
|
} catch {
|
|
10232
10518
|
continue;
|
|
10233
10519
|
}
|
|
@@ -10259,7 +10545,7 @@ function findComponentFileByExportName(projectRoot, componentName) {
|
|
|
10259
10545
|
);
|
|
10260
10546
|
for (const absPath of files) {
|
|
10261
10547
|
try {
|
|
10262
|
-
const code =
|
|
10548
|
+
const code = readFileSync12(absPath, "utf-8");
|
|
10263
10549
|
const exports = extractExportedComponentNames(code);
|
|
10264
10550
|
if (exports.includes(componentName)) {
|
|
10265
10551
|
return relative3(projectRoot, absPath);
|
|
@@ -10304,7 +10590,7 @@ function reconcileComponents(projectRoot, manifest) {
|
|
|
10304
10590
|
}
|
|
10305
10591
|
let code;
|
|
10306
10592
|
try {
|
|
10307
|
-
code =
|
|
10593
|
+
code = readFileSync12(join12(projectRoot, entry.file), "utf-8");
|
|
10308
10594
|
} catch {
|
|
10309
10595
|
return true;
|
|
10310
10596
|
}
|
|
@@ -10381,7 +10667,7 @@ function collectFiles(dir, filter, skipDirs = []) {
|
|
|
10381
10667
|
function walk(d) {
|
|
10382
10668
|
let entries;
|
|
10383
10669
|
try {
|
|
10384
|
-
entries =
|
|
10670
|
+
entries = readdirSync4(d, { withFileTypes: true });
|
|
10385
10671
|
} catch {
|
|
10386
10672
|
return;
|
|
10387
10673
|
}
|
|
@@ -10427,7 +10713,7 @@ function getWatcherConfig(projectRoot) {
|
|
|
10427
10713
|
try {
|
|
10428
10714
|
const pkgPath = join13(projectRoot, "package.json");
|
|
10429
10715
|
if (!existsSync18(pkgPath)) return defaultWatcherConfig();
|
|
10430
|
-
const pkg = JSON.parse(
|
|
10716
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
10431
10717
|
const c = pkg?.coherent?.watcher ?? {};
|
|
10432
10718
|
return {
|
|
10433
10719
|
enabled: c.enabled !== false,
|
|
@@ -10455,7 +10741,7 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
10455
10741
|
if (relativePath.includes("node_modules") || relativePath.includes(".next")) return;
|
|
10456
10742
|
let content;
|
|
10457
10743
|
try {
|
|
10458
|
-
content =
|
|
10744
|
+
content = readFileSync13(filePath, "utf-8");
|
|
10459
10745
|
} catch {
|
|
10460
10746
|
return;
|
|
10461
10747
|
}
|
|
@@ -10485,7 +10771,7 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
10485
10771
|
if (config2.warnSharedReuse) {
|
|
10486
10772
|
let manifest;
|
|
10487
10773
|
try {
|
|
10488
|
-
manifest = await
|
|
10774
|
+
manifest = await loadManifest9(projectRoot);
|
|
10489
10775
|
} catch {
|
|
10490
10776
|
manifest = { shared: [], nextId: 1 };
|
|
10491
10777
|
}
|
|
@@ -10504,7 +10790,7 @@ async function handleFileDelete(projectRoot, filePath) {
|
|
|
10504
10790
|
if (!relativePath.startsWith("components/") || relativePath.startsWith("components/ui/")) return;
|
|
10505
10791
|
try {
|
|
10506
10792
|
const chalk33 = (await import("chalk")).default;
|
|
10507
|
-
const manifest = await
|
|
10793
|
+
const manifest = await loadManifest9(projectRoot);
|
|
10508
10794
|
const orphaned = manifest.shared.find((s) => s.file === relativePath);
|
|
10509
10795
|
if (orphaned) {
|
|
10510
10796
|
const cleaned = {
|
|
@@ -10525,10 +10811,10 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
10525
10811
|
if (!relativePath.endsWith(".tsx") && !relativePath.endsWith(".jsx")) return;
|
|
10526
10812
|
try {
|
|
10527
10813
|
const chalk33 = (await import("chalk")).default;
|
|
10528
|
-
const manifest = await
|
|
10814
|
+
const manifest = await loadManifest9(projectRoot);
|
|
10529
10815
|
const alreadyRegistered = manifest.shared.some((s) => s.file === relativePath);
|
|
10530
10816
|
if (alreadyRegistered) return;
|
|
10531
|
-
const code =
|
|
10817
|
+
const code = readFileSync13(filePath, "utf-8");
|
|
10532
10818
|
const exports = extractExportedComponentNames(code);
|
|
10533
10819
|
if (exports.length > 0) {
|
|
10534
10820
|
const alreadyByName = exports.every((n) => manifest.shared.some((s) => s.name === n));
|
|
@@ -10652,7 +10938,7 @@ async function validateSyntax(projectRoot) {
|
|
|
10652
10938
|
const appDir = join14(projectRoot, "app");
|
|
10653
10939
|
const pages = await listPageFiles(appDir);
|
|
10654
10940
|
for (const file of pages) {
|
|
10655
|
-
const content =
|
|
10941
|
+
const content = readFileSync14(file, "utf-8");
|
|
10656
10942
|
const fixed = fixUnescapedLtInJsx(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)));
|
|
10657
10943
|
if (fixed !== content) {
|
|
10658
10944
|
writeFileSync10(file, fixed, "utf-8");
|
|
@@ -10665,9 +10951,14 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10665
10951
|
const uiDir = join14(projectRoot, "components", "ui");
|
|
10666
10952
|
if (!existsSync19(appDir) || !existsSync19(uiDir)) return;
|
|
10667
10953
|
const pages = await listPageFiles(appDir);
|
|
10954
|
+
const sharedDir = join14(projectRoot, "components", "shared");
|
|
10955
|
+
if (existsSync19(sharedDir)) {
|
|
10956
|
+
const sharedFiles = readdirSync5(sharedDir).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts")).map((f) => join14(sharedDir, f));
|
|
10957
|
+
pages.push(...sharedFiles);
|
|
10958
|
+
}
|
|
10668
10959
|
const neededExports = /* @__PURE__ */ new Map();
|
|
10669
10960
|
for (const file of pages) {
|
|
10670
|
-
const content =
|
|
10961
|
+
const content = readFileSync14(file, "utf-8");
|
|
10671
10962
|
const importRe = /import\s*\{([^}]+)\}\s*from\s*['"]@\/components\/ui\/([^'"]+)['"]/g;
|
|
10672
10963
|
let m;
|
|
10673
10964
|
while ((m = importRe.exec(content)) !== null) {
|
|
@@ -10711,7 +11002,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10711
11002
|
}
|
|
10712
11003
|
continue;
|
|
10713
11004
|
}
|
|
10714
|
-
const content =
|
|
11005
|
+
const content = readFileSync14(componentFile, "utf-8");
|
|
10715
11006
|
const exportRe = /export\s+(?:const|function|class)\s+(\w+)|export\s*\{([^}]+)\}/g;
|
|
10716
11007
|
const existingExports = /* @__PURE__ */ new Set();
|
|
10717
11008
|
let em;
|
|
@@ -10764,7 +11055,7 @@ async function backfillPageAnalysis(projectRoot) {
|
|
|
10764
11055
|
filePath = join14(projectRoot, "app", route.slice(1), "page.tsx");
|
|
10765
11056
|
}
|
|
10766
11057
|
if (!existsSync19(filePath)) continue;
|
|
10767
|
-
const code =
|
|
11058
|
+
const code = readFileSync14(filePath, "utf-8");
|
|
10768
11059
|
if (code.length < 50) continue;
|
|
10769
11060
|
page.pageAnalysis = analyzePageCode(code);
|
|
10770
11061
|
changed = true;
|
|
@@ -11005,7 +11296,7 @@ async function previewCommand() {
|
|
|
11005
11296
|
import chalk15 from "chalk";
|
|
11006
11297
|
import ora4 from "ora";
|
|
11007
11298
|
import { spawn as spawn2 } from "child_process";
|
|
11008
|
-
import { existsSync as existsSync20, rmSync as rmSync4, readdirSync as
|
|
11299
|
+
import { existsSync as existsSync20, rmSync as rmSync4, readdirSync as readdirSync6 } from "fs";
|
|
11009
11300
|
import { resolve as resolve11, join as join15, dirname as dirname7 } from "path";
|
|
11010
11301
|
import { readdir as readdir3, readFile as readFile6, writeFile as writeFile5, mkdir as mkdir5, copyFile as copyFile2 } from "fs/promises";
|
|
11011
11302
|
var COPY_EXCLUDE = /* @__PURE__ */ new Set([
|
|
@@ -11126,7 +11417,7 @@ function countComponents(outRoot) {
|
|
|
11126
11417
|
const dir = join15(outRoot, "components", sub);
|
|
11127
11418
|
if (!existsSync20(dir)) continue;
|
|
11128
11419
|
try {
|
|
11129
|
-
n +=
|
|
11420
|
+
n += readdirSync6(dir).filter((f) => f.endsWith(".tsx") || f.endsWith(".jsx")).length;
|
|
11130
11421
|
} catch {
|
|
11131
11422
|
}
|
|
11132
11423
|
}
|
|
@@ -11445,14 +11736,14 @@ async function regenerateDocsCommand() {
|
|
|
11445
11736
|
|
|
11446
11737
|
// src/commands/fix.ts
|
|
11447
11738
|
import chalk18 from "chalk";
|
|
11448
|
-
import { readdirSync as
|
|
11739
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync15, existsSync as existsSync21, writeFileSync as writeFileSync11, rmSync as rmSync5, mkdirSync as mkdirSync7 } from "fs";
|
|
11449
11740
|
import { resolve as resolve12, join as join16 } from "path";
|
|
11450
11741
|
import {
|
|
11451
11742
|
DesignSystemManager as DesignSystemManager11,
|
|
11452
|
-
ComponentManager as
|
|
11743
|
+
ComponentManager as ComponentManager6,
|
|
11453
11744
|
PageManager as PageManager4,
|
|
11454
11745
|
ComponentGenerator as ComponentGenerator4,
|
|
11455
|
-
loadManifest as
|
|
11746
|
+
loadManifest as loadManifest10,
|
|
11456
11747
|
saveManifest as saveManifest4
|
|
11457
11748
|
} from "@getcoherent/core";
|
|
11458
11749
|
function extractComponentIdsFromCode2(code) {
|
|
@@ -11470,7 +11761,7 @@ function extractComponentIdsFromCode2(code) {
|
|
|
11470
11761
|
function listTsxFiles(dir) {
|
|
11471
11762
|
const files = [];
|
|
11472
11763
|
try {
|
|
11473
|
-
const entries =
|
|
11764
|
+
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
11474
11765
|
for (const e of entries) {
|
|
11475
11766
|
const full = join16(dir, e.name);
|
|
11476
11767
|
if (e.isDirectory() && e.name !== "node_modules" && !e.name.startsWith(".")) {
|
|
@@ -11529,7 +11820,7 @@ async function fixCommand(opts = {}) {
|
|
|
11529
11820
|
const componentsTsxFiles = listTsxFiles(resolve12(projectRoot, "components"));
|
|
11530
11821
|
const allComponentIds = /* @__PURE__ */ new Set();
|
|
11531
11822
|
for (const file of [...allTsxFiles, ...componentsTsxFiles]) {
|
|
11532
|
-
const content =
|
|
11823
|
+
const content = readFileSync15(file, "utf-8");
|
|
11533
11824
|
extractComponentIdsFromCode2(content).forEach((id) => allComponentIds.add(id));
|
|
11534
11825
|
}
|
|
11535
11826
|
let dsm = null;
|
|
@@ -11539,7 +11830,7 @@ async function fixCommand(opts = {}) {
|
|
|
11539
11830
|
dsm = new DesignSystemManager11(project.configPath);
|
|
11540
11831
|
await dsm.load();
|
|
11541
11832
|
const config2 = dsm.getConfig();
|
|
11542
|
-
cm = new
|
|
11833
|
+
cm = new ComponentManager6(config2);
|
|
11543
11834
|
pm = new PageManager4(config2, cm);
|
|
11544
11835
|
const missingComponents = [];
|
|
11545
11836
|
const missingFiles = [];
|
|
@@ -11601,7 +11892,7 @@ async function fixCommand(opts = {}) {
|
|
|
11601
11892
|
const userTsxFiles = allTsxFiles.filter((f) => !f.includes("/design-system/"));
|
|
11602
11893
|
let syntaxFixed = 0;
|
|
11603
11894
|
for (const file of userTsxFiles) {
|
|
11604
|
-
const content =
|
|
11895
|
+
const content = readFileSync15(file, "utf-8");
|
|
11605
11896
|
const fixed = fixUnescapedLtInJsx(
|
|
11606
11897
|
fixEscapedClosingQuotes(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)))
|
|
11607
11898
|
);
|
|
@@ -11619,7 +11910,7 @@ async function fixCommand(opts = {}) {
|
|
|
11619
11910
|
let qualityFixCount = 0;
|
|
11620
11911
|
const qualityFixDetails = [];
|
|
11621
11912
|
for (const file of userTsxFiles) {
|
|
11622
|
-
const content =
|
|
11913
|
+
const content = readFileSync15(file, "utf-8");
|
|
11623
11914
|
const { code: autoFixed, fixes: fileFixes } = await autoFixCode(content);
|
|
11624
11915
|
if (autoFixed !== content) {
|
|
11625
11916
|
if (!dryRun) writeFileSync11(file, autoFixed, "utf-8");
|
|
@@ -11638,7 +11929,7 @@ async function fixCommand(opts = {}) {
|
|
|
11638
11929
|
let totalWarnings = 0;
|
|
11639
11930
|
const fileIssues = [];
|
|
11640
11931
|
for (const file of allTsxFiles) {
|
|
11641
|
-
const code = dryRun ?
|
|
11932
|
+
const code = dryRun ? readFileSync15(file, "utf-8") : readFileSync15(file, "utf-8");
|
|
11642
11933
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11643
11934
|
const baseName = file.split("/").pop() || "";
|
|
11644
11935
|
const isAuthPage = relativePath.includes("(auth)");
|
|
@@ -11662,7 +11953,7 @@ async function fixCommand(opts = {}) {
|
|
|
11662
11953
|
fileIssues.push({ path: relativePath, report });
|
|
11663
11954
|
}
|
|
11664
11955
|
try {
|
|
11665
|
-
let manifest = await
|
|
11956
|
+
let manifest = await loadManifest10(project.root);
|
|
11666
11957
|
let manifestModified = false;
|
|
11667
11958
|
const { manifest: cleaned, removed: orphaned } = removeOrphanedEntries(project.root, manifest);
|
|
11668
11959
|
if (orphaned.length > 0) {
|
|
@@ -11761,13 +12052,13 @@ async function fixCommand(opts = {}) {
|
|
|
11761
12052
|
// src/commands/check.ts
|
|
11762
12053
|
import chalk19 from "chalk";
|
|
11763
12054
|
import { resolve as resolve13 } from "path";
|
|
11764
|
-
import { readdirSync as
|
|
11765
|
-
import { loadManifest as
|
|
12055
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync16, statSync as statSync2, existsSync as existsSync22 } from "fs";
|
|
12056
|
+
import { loadManifest as loadManifest11 } from "@getcoherent/core";
|
|
11766
12057
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", "design-system"]);
|
|
11767
12058
|
function findTsxFiles(dir) {
|
|
11768
12059
|
const results = [];
|
|
11769
12060
|
try {
|
|
11770
|
-
const entries =
|
|
12061
|
+
const entries = readdirSync8(dir);
|
|
11771
12062
|
for (const entry of entries) {
|
|
11772
12063
|
const full = resolve13(dir, entry);
|
|
11773
12064
|
const stat = statSync2(full);
|
|
@@ -11818,7 +12109,7 @@ async function checkCommand(opts = {}) {
|
|
|
11818
12109
|
"NATIVE_TABLE"
|
|
11819
12110
|
]);
|
|
11820
12111
|
for (const file of files) {
|
|
11821
|
-
const code =
|
|
12112
|
+
const code = readFileSync16(file, "utf-8");
|
|
11822
12113
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11823
12114
|
const baseName = file.split("/").pop() || "";
|
|
11824
12115
|
const isAuthPage = relativePath.includes("(auth)");
|
|
@@ -11860,7 +12151,7 @@ async function checkCommand(opts = {}) {
|
|
|
11860
12151
|
routeSet.add("/");
|
|
11861
12152
|
routeSet.add("#");
|
|
11862
12153
|
for (const file of files) {
|
|
11863
|
-
const code =
|
|
12154
|
+
const code = readFileSync16(file, "utf-8");
|
|
11864
12155
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11865
12156
|
const lines = code.split("\n");
|
|
11866
12157
|
const linkHrefRe = /href\s*=\s*["'](\/[a-z0-9/-]*)["']/gi;
|
|
@@ -11889,7 +12180,7 @@ async function checkCommand(opts = {}) {
|
|
|
11889
12180
|
\u{1F517} Internal Links`) + chalk19.dim(` \u2014 all ${result.links.total} links resolve \u2713`));
|
|
11890
12181
|
}
|
|
11891
12182
|
try {
|
|
11892
|
-
const manifest = await
|
|
12183
|
+
const manifest = await loadManifest11(project.root);
|
|
11893
12184
|
if (manifest.shared.length > 0) {
|
|
11894
12185
|
for (const entry of manifest.shared) {
|
|
11895
12186
|
const fullPath = resolve13(project.root, entry.file);
|
|
@@ -11905,7 +12196,7 @@ async function checkCommand(opts = {}) {
|
|
|
11905
12196
|
}
|
|
11906
12197
|
if (!skipShared) {
|
|
11907
12198
|
try {
|
|
11908
|
-
const manifest = await
|
|
12199
|
+
const manifest = await loadManifest11(projectRoot);
|
|
11909
12200
|
if (!opts.json && manifest.shared.length > 0) {
|
|
11910
12201
|
console.log(chalk19.cyan(`
|
|
11911
12202
|
\u{1F9E9} Shared Components`) + chalk19.dim(` (${manifest.shared.length} registered)
|
|
@@ -11928,7 +12219,7 @@ async function checkCommand(opts = {}) {
|
|
|
11928
12219
|
continue;
|
|
11929
12220
|
}
|
|
11930
12221
|
try {
|
|
11931
|
-
const code =
|
|
12222
|
+
const code = readFileSync16(filePath, "utf-8");
|
|
11932
12223
|
const actualExports = extractExportedComponentNames(code);
|
|
11933
12224
|
if (actualExports.length > 0 && !actualExports.includes(entry.name)) {
|
|
11934
12225
|
_nameMismatch++;
|
|
@@ -12086,9 +12377,9 @@ import { Command } from "commander";
|
|
|
12086
12377
|
import chalk25 from "chalk";
|
|
12087
12378
|
import {
|
|
12088
12379
|
DesignSystemManager as DesignSystemManager12,
|
|
12089
|
-
ComponentManager as
|
|
12090
|
-
loadManifest as
|
|
12091
|
-
generateSharedComponent as
|
|
12380
|
+
ComponentManager as ComponentManager7,
|
|
12381
|
+
loadManifest as loadManifest12,
|
|
12382
|
+
generateSharedComponent as generateSharedComponent5,
|
|
12092
12383
|
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout3
|
|
12093
12384
|
} from "@getcoherent/core";
|
|
12094
12385
|
import { existsSync as existsSync23 } from "fs";
|
|
@@ -12127,8 +12418,8 @@ function createComponentsCommand() {
|
|
|
12127
12418
|
const dsm = new DesignSystemManager12(project.configPath);
|
|
12128
12419
|
await dsm.load();
|
|
12129
12420
|
const config2 = dsm.getConfig();
|
|
12130
|
-
const cm = new
|
|
12131
|
-
const manifest = await
|
|
12421
|
+
const cm = new ComponentManager7(config2);
|
|
12422
|
+
const manifest = await loadManifest12(project.root);
|
|
12132
12423
|
if (opts.json) {
|
|
12133
12424
|
const installed2 = cm.getAllComponents();
|
|
12134
12425
|
console.log(JSON.stringify({ shared: manifest.shared, ui: installed2 }, null, 2));
|
|
@@ -12180,7 +12471,7 @@ function createComponentsCommand() {
|
|
|
12180
12471
|
sharedCmd.option("--json", "Machine-readable JSON output").option("--verbose", "Show file paths and usage details").action(async (opts) => {
|
|
12181
12472
|
const project = findConfig();
|
|
12182
12473
|
if (!project) exitNotCoherent();
|
|
12183
|
-
const manifest = await
|
|
12474
|
+
const manifest = await loadManifest12(project.root);
|
|
12184
12475
|
if (opts.json) {
|
|
12185
12476
|
console.log(JSON.stringify(manifest, null, 2));
|
|
12186
12477
|
return;
|
|
@@ -12209,7 +12500,7 @@ function createComponentsCommand() {
|
|
|
12209
12500
|
const project = findConfig();
|
|
12210
12501
|
if (!project) exitNotCoherent();
|
|
12211
12502
|
const type = opts.type === "section" || opts.type === "widget" ? opts.type : "layout";
|
|
12212
|
-
const result = await
|
|
12503
|
+
const result = await generateSharedComponent5(project.root, {
|
|
12213
12504
|
name: name.trim(),
|
|
12214
12505
|
type,
|
|
12215
12506
|
description: opts.description,
|
|
@@ -12260,7 +12551,7 @@ import {
|
|
|
12260
12551
|
EXAMPLE_MULTIPAGE_CONFIG,
|
|
12261
12552
|
normalizeFigmaComponents,
|
|
12262
12553
|
setSharedMapping,
|
|
12263
|
-
generateSharedComponent as
|
|
12554
|
+
generateSharedComponent as generateSharedComponent6,
|
|
12264
12555
|
generatePagesFromFigma,
|
|
12265
12556
|
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout4,
|
|
12266
12557
|
DesignSystemManager as DesignSystemManager13,
|
|
@@ -12457,7 +12748,7 @@ async function importFigmaAction(urlOrKey, opts) {
|
|
|
12457
12748
|
else {
|
|
12458
12749
|
stats.sharedCount++;
|
|
12459
12750
|
if (!dryRun) {
|
|
12460
|
-
const { id, name, file } = await
|
|
12751
|
+
const { id, name, file } = await generateSharedComponent6(projectRoot, {
|
|
12461
12752
|
name: entry.suggestedName,
|
|
12462
12753
|
type: "widget",
|
|
12463
12754
|
code: entry.suggestedTsx,
|
|
@@ -12619,7 +12910,7 @@ async function dsRegenerateCommand() {
|
|
|
12619
12910
|
// src/commands/update.ts
|
|
12620
12911
|
import chalk28 from "chalk";
|
|
12621
12912
|
import ora8 from "ora";
|
|
12622
|
-
import { readFileSync as
|
|
12913
|
+
import { readFileSync as readFileSync17, existsSync as existsSync25 } from "fs";
|
|
12623
12914
|
import { join as join19 } from "path";
|
|
12624
12915
|
import { DesignSystemManager as DesignSystemManager15, CLI_VERSION as CLI_VERSION4 } from "@getcoherent/core";
|
|
12625
12916
|
|
|
@@ -12792,7 +13083,7 @@ function checkMissingCssVars(projectRoot) {
|
|
|
12792
13083
|
const globalsPath = join19(projectRoot, "app", "globals.css");
|
|
12793
13084
|
if (!existsSync25(globalsPath)) return [];
|
|
12794
13085
|
try {
|
|
12795
|
-
const content =
|
|
13086
|
+
const content = readFileSync17(globalsPath, "utf-8");
|
|
12796
13087
|
return EXPECTED_CSS_VARS.filter((v) => !content.includes(v));
|
|
12797
13088
|
} catch {
|
|
12798
13089
|
return [];
|
|
@@ -12802,7 +13093,7 @@ function patchGlobalsCss(projectRoot, missingVars) {
|
|
|
12802
13093
|
const globalsPath = join19(projectRoot, "app", "globals.css");
|
|
12803
13094
|
if (!existsSync25(globalsPath) || missingVars.length === 0) return;
|
|
12804
13095
|
const { writeFileSync: writeFileSync14 } = __require("fs");
|
|
12805
|
-
let content =
|
|
13096
|
+
let content = readFileSync17(globalsPath, "utf-8");
|
|
12806
13097
|
const defaultValues = {
|
|
12807
13098
|
"--chart-1": "220 70% 50%",
|
|
12808
13099
|
"--chart-2": "160 60% 45%",
|
|
@@ -12880,17 +13171,17 @@ async function undoCommand(options) {
|
|
|
12880
13171
|
// src/commands/sync.ts
|
|
12881
13172
|
import chalk30 from "chalk";
|
|
12882
13173
|
import ora9 from "ora";
|
|
12883
|
-
import { existsSync as existsSync26, readFileSync as
|
|
13174
|
+
import { existsSync as existsSync26, readFileSync as readFileSync18 } from "fs";
|
|
12884
13175
|
import { join as join20, relative as relative5, dirname as dirname10 } from "path";
|
|
12885
13176
|
import { readdir as readdir4, readFile as readFile7 } from "fs/promises";
|
|
12886
13177
|
import { DesignSystemManager as DesignSystemManager16 } from "@getcoherent/core";
|
|
12887
|
-
import { loadManifest as
|
|
13178
|
+
import { loadManifest as loadManifest13, saveManifest as saveManifest5, findSharedComponent } from "@getcoherent/core";
|
|
12888
13179
|
function extractTokensFromProject(projectRoot) {
|
|
12889
13180
|
const lightColors = {};
|
|
12890
13181
|
const darkColors = {};
|
|
12891
13182
|
const globalsPath = join20(projectRoot, "app", "globals.css");
|
|
12892
13183
|
if (existsSync26(globalsPath)) {
|
|
12893
|
-
const css =
|
|
13184
|
+
const css = readFileSync18(globalsPath, "utf-8");
|
|
12894
13185
|
const rootMatch = css.match(/:root\s*\{([^}]+)\}/s);
|
|
12895
13186
|
if (rootMatch) parseVarsInto(rootMatch[1], lightColors);
|
|
12896
13187
|
const darkMatch = css.match(/\.dark\s*\{([^}]+)\}/s);
|
|
@@ -12899,7 +13190,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
12899
13190
|
const layoutPath = join20(projectRoot, "app", "layout.tsx");
|
|
12900
13191
|
let layoutCode = "";
|
|
12901
13192
|
if (existsSync26(layoutPath)) {
|
|
12902
|
-
layoutCode =
|
|
13193
|
+
layoutCode = readFileSync18(layoutPath, "utf-8");
|
|
12903
13194
|
const rootInline = layoutCode.match(/:root\s*\{([^}]+)\}/s);
|
|
12904
13195
|
if (rootInline && Object.keys(lightColors).length === 0) {
|
|
12905
13196
|
parseVarsInto(rootInline[1], lightColors);
|
|
@@ -12917,7 +13208,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
12917
13208
|
defaultMode = "dark";
|
|
12918
13209
|
}
|
|
12919
13210
|
let radius;
|
|
12920
|
-
const allCss = [existsSync26(globalsPath) ?
|
|
13211
|
+
const allCss = [existsSync26(globalsPath) ? readFileSync18(globalsPath, "utf-8") : "", layoutCode].join("\n");
|
|
12921
13212
|
const radiusMatch = allCss.match(/--radius:\s*([^;]+);/);
|
|
12922
13213
|
if (radiusMatch) radius = radiusMatch[1].trim();
|
|
12923
13214
|
return {
|
|
@@ -13164,7 +13455,7 @@ async function syncCommand(options = {}) {
|
|
|
13164
13455
|
let reconcileResult = null;
|
|
13165
13456
|
if (doComponents) {
|
|
13166
13457
|
spinner.start("Reconciling shared components...");
|
|
13167
|
-
const manifest = await
|
|
13458
|
+
const manifest = await loadManifest13(project.root);
|
|
13168
13459
|
const { manifest: reconciledManifest, result: rr } = reconcileComponents(project.root, manifest);
|
|
13169
13460
|
reconcileResult = rr;
|
|
13170
13461
|
if (!dryRun) {
|
|
@@ -13347,7 +13638,7 @@ async function syncCommand(options = {}) {
|
|
|
13347
13638
|
// src/commands/migrate.ts
|
|
13348
13639
|
import chalk31 from "chalk";
|
|
13349
13640
|
import ora10 from "ora";
|
|
13350
|
-
import { existsSync as existsSync27, mkdirSync as mkdirSync8, cpSync, rmSync as rmSync6, writeFileSync as writeFileSync12, readFileSync as
|
|
13641
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync8, cpSync, rmSync as rmSync6, writeFileSync as writeFileSync12, readFileSync as readFileSync19, readdirSync as readdirSync9 } from "fs";
|
|
13351
13642
|
import { join as join21 } from "path";
|
|
13352
13643
|
function backupDir(projectRoot) {
|
|
13353
13644
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -13382,7 +13673,7 @@ function rollback(projectRoot) {
|
|
|
13382
13673
|
const guard = guardPath(projectRoot);
|
|
13383
13674
|
if (!existsSync27(guard)) return false;
|
|
13384
13675
|
try {
|
|
13385
|
-
const data = JSON.parse(
|
|
13676
|
+
const data = JSON.parse(readFileSync19(guard, "utf-8"));
|
|
13386
13677
|
const backup = data.backup;
|
|
13387
13678
|
if (!existsSync27(backup)) return false;
|
|
13388
13679
|
const uiBackup = join21(backup, "components-ui");
|
|
@@ -13433,7 +13724,7 @@ async function migrateAction(options) {
|
|
|
13433
13724
|
}
|
|
13434
13725
|
const provider = getComponentProvider();
|
|
13435
13726
|
const managedIds = new Set(provider.listNames());
|
|
13436
|
-
const files =
|
|
13727
|
+
const files = readdirSync9(uiDir).filter((f) => f.endsWith(".tsx"));
|
|
13437
13728
|
const migratable = files.map((f) => f.replace(".tsx", "")).filter((id) => managedIds.has(id));
|
|
13438
13729
|
if (migratable.length === 0) {
|
|
13439
13730
|
console.log(chalk31.green("All components are already up to date."));
|
|
@@ -13476,7 +13767,7 @@ Found ${migratable.length} component(s) to migrate:`));
|
|
|
13476
13767
|
}
|
|
13477
13768
|
|
|
13478
13769
|
// src/utils/update-notifier.ts
|
|
13479
|
-
import { existsSync as existsSync28, mkdirSync as mkdirSync9, readFileSync as
|
|
13770
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync9, readFileSync as readFileSync20, writeFileSync as writeFileSync13 } from "fs";
|
|
13480
13771
|
import { join as join22 } from "path";
|
|
13481
13772
|
import { homedir } from "os";
|
|
13482
13773
|
import chalk32 from "chalk";
|
|
@@ -13489,7 +13780,7 @@ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
|
13489
13780
|
function readCache() {
|
|
13490
13781
|
try {
|
|
13491
13782
|
if (!existsSync28(CACHE_FILE)) return null;
|
|
13492
|
-
const raw =
|
|
13783
|
+
const raw = readFileSync20(CACHE_FILE, "utf-8");
|
|
13493
13784
|
return JSON.parse(raw);
|
|
13494
13785
|
} catch (e) {
|
|
13495
13786
|
if (DEBUG5) console.error("Failed to read update cache:", e);
|