@getcoherent/cli 0.5.14 → 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 +280 -84
- package/package.json +2 -2
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,7 +3748,7 @@ 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
3754
|
ComponentManager as ComponentManager5,
|
|
@@ -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;
|
|
@@ -7397,7 +7495,10 @@ function applyDefaults(request) {
|
|
|
7397
7495
|
|
|
7398
7496
|
// src/commands/chat/split-generator.ts
|
|
7399
7497
|
import { z } from "zod";
|
|
7400
|
-
import {
|
|
7498
|
+
import {
|
|
7499
|
+
loadManifest as loadManifest5,
|
|
7500
|
+
generateSharedComponent as generateSharedComponent2
|
|
7501
|
+
} from "@getcoherent/core";
|
|
7401
7502
|
|
|
7402
7503
|
// src/utils/page-analyzer.ts
|
|
7403
7504
|
var FORM_COMPONENTS = /* @__PURE__ */ new Set(["Input", "Textarea", "Label", "Select", "Checkbox", "Switch"]);
|
|
@@ -7604,6 +7705,13 @@ function buildSharedComponentsSummary(manifest) {
|
|
|
7604
7705
|
Import: @/components/shared/${importPath}${propsLine}`;
|
|
7605
7706
|
}).join("\n");
|
|
7606
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
|
+
}
|
|
7607
7715
|
async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts) {
|
|
7608
7716
|
let pageNames = [];
|
|
7609
7717
|
spinner.start("Phase 1/5 \u2014 Planning pages...");
|
|
@@ -7667,7 +7775,8 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7667
7775
|
let homeRequest = null;
|
|
7668
7776
|
let homePageCode = "";
|
|
7669
7777
|
let reusedExistingAnchor = false;
|
|
7670
|
-
|
|
7778
|
+
const isPlaceholder = modCtx.config?.settings?.homePagePlaceholder === true;
|
|
7779
|
+
if (projectRoot && remainingPages.length > 0 && !isPlaceholder) {
|
|
7671
7780
|
const existingCode = readAnchorPageCodeFromDisk(projectRoot, homePage.route);
|
|
7672
7781
|
if (existingCode) {
|
|
7673
7782
|
reusedExistingAnchor = true;
|
|
@@ -7733,7 +7842,8 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7733
7842
|
return homeRequest ? [homeRequest] : [];
|
|
7734
7843
|
}
|
|
7735
7844
|
spinner.start(`Phase 4/5 \u2014 Generating ${remainingPages.length} pages in parallel...`);
|
|
7736
|
-
const
|
|
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);
|
|
7737
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="#".`;
|
|
7738
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.';
|
|
7739
7849
|
const existingPagesContext = buildExistingPagesContext(modCtx.config);
|
|
@@ -7742,13 +7852,17 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7742
7852
|
const remainingRequests = await pMap(
|
|
7743
7853
|
remainingPages,
|
|
7744
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;
|
|
7745
7857
|
const prompt = [
|
|
7746
7858
|
`Create ONE page called "${name}" at route "${route}".`,
|
|
7747
7859
|
`Context: ${message}.`,
|
|
7748
7860
|
`Generate complete pageCode for this single page only. Do not generate other pages.`,
|
|
7749
|
-
|
|
7861
|
+
sharedLayoutNote,
|
|
7862
|
+
sharedComponentsNote,
|
|
7750
7863
|
routeNote,
|
|
7751
7864
|
alignmentNote,
|
|
7865
|
+
authNote,
|
|
7752
7866
|
existingPagesContext,
|
|
7753
7867
|
styleContext
|
|
7754
7868
|
].filter(Boolean).join("\n\n");
|
|
@@ -7768,18 +7882,24 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
7768
7882
|
);
|
|
7769
7883
|
const allRequests = reusedExistingAnchor ? [...remainingRequests] : homeRequest ? [homeRequest, ...remainingRequests] : [...remainingRequests];
|
|
7770
7884
|
const emptyPages = allRequests.filter((r) => r.type === "add-page" && !r.changes?.pageCode);
|
|
7771
|
-
if (emptyPages.length > 0
|
|
7885
|
+
if (emptyPages.length > 0) {
|
|
7772
7886
|
spinner.text = `Retrying ${emptyPages.length} page(s) without code...`;
|
|
7773
7887
|
for (const req of emptyPages) {
|
|
7774
7888
|
const page = req.changes;
|
|
7775
7889
|
const pageName = page.name || page.id || "page";
|
|
7776
7890
|
const pageRoute = page.route || `/${pageName.toLowerCase()}`;
|
|
7777
7891
|
try {
|
|
7892
|
+
const lightweightPrompt = buildLightweightPagePrompt(
|
|
7893
|
+
pageName,
|
|
7894
|
+
pageRoute,
|
|
7895
|
+
styleContext || "",
|
|
7896
|
+
parseOpts.sharedComponentsSummary
|
|
7897
|
+
);
|
|
7778
7898
|
const retryResult = await parseModification(
|
|
7779
|
-
|
|
7899
|
+
lightweightPrompt,
|
|
7780
7900
|
modCtx,
|
|
7781
7901
|
provider,
|
|
7782
|
-
parseOpts
|
|
7902
|
+
{ ...parseOpts, lightweight: true }
|
|
7783
7903
|
);
|
|
7784
7904
|
const codePage = retryResult.requests.find((r) => r.type === "add-page");
|
|
7785
7905
|
if (codePage && codePage.changes?.pageCode) {
|
|
@@ -7825,7 +7945,9 @@ async function extractSharedComponents(homePageCode, projectRoot, aiProvider) {
|
|
|
7825
7945
|
} catch {
|
|
7826
7946
|
return { components: [], summary: buildSharedComponentsSummary(manifest) };
|
|
7827
7947
|
}
|
|
7828
|
-
const reservedSet = new Set(
|
|
7948
|
+
const reservedSet = new Set(
|
|
7949
|
+
getComponentProvider().listNames().map((n) => n.toLowerCase())
|
|
7950
|
+
);
|
|
7829
7951
|
const existingSet = new Set(manifest.shared.map((e) => e.name.toLowerCase()));
|
|
7830
7952
|
const seenNames = /* @__PURE__ */ new Set();
|
|
7831
7953
|
const filtered = rawItems.filter((item) => {
|
|
@@ -7916,7 +8038,7 @@ import {
|
|
|
7916
8038
|
|
|
7917
8039
|
// src/commands/chat/code-generator.ts
|
|
7918
8040
|
import { resolve as resolve6 } from "path";
|
|
7919
|
-
import { existsSync as existsSync14 } from "fs";
|
|
8041
|
+
import { existsSync as existsSync14, readdirSync as readdirSync2, readFileSync as readFileSync9 } from "fs";
|
|
7920
8042
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
7921
8043
|
import { dirname as dirname5 } from "path";
|
|
7922
8044
|
import {
|
|
@@ -8096,6 +8218,28 @@ async function regenerateLayout(config2, projectRoot, options = { navChanged: fa
|
|
|
8096
8218
|
}
|
|
8097
8219
|
}
|
|
8098
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
|
+
}
|
|
8099
8243
|
async function ensureAppRouteGroupLayout(projectRoot, navType, forceUpdate = false) {
|
|
8100
8244
|
const layoutPath = resolve6(projectRoot, "app", "(app)", "layout.tsx");
|
|
8101
8245
|
if (existsSync14(layoutPath) && !forceUpdate) return;
|
|
@@ -8153,6 +8297,10 @@ async function regenerateFiles(modified, config2, projectRoot, options = { navCh
|
|
|
8153
8297
|
navChanged: options.navChanged,
|
|
8154
8298
|
storedHashes: options.storedHashes
|
|
8155
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
|
+
}
|
|
8156
8304
|
}
|
|
8157
8305
|
if (componentIds.size > 0) {
|
|
8158
8306
|
const twGen = new TailwindConfigGenerator(config2);
|
|
@@ -8898,7 +9046,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
8898
9046
|
}
|
|
8899
9047
|
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
8900
9048
|
codeToWrite = layoutStripped;
|
|
8901
|
-
if (!isMarketingRoute(route)) {
|
|
9049
|
+
if (!isMarketingRoute(route) && !isAuthRoute(route)) {
|
|
8902
9050
|
const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
|
|
8903
9051
|
if (wrapperFixed) {
|
|
8904
9052
|
codeToWrite = normalized;
|
|
@@ -9098,7 +9246,7 @@ ${pagesCtx}`
|
|
|
9098
9246
|
}
|
|
9099
9247
|
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
9100
9248
|
codeToWrite = layoutStripped;
|
|
9101
|
-
if (!isMarketingRoute(route)) {
|
|
9249
|
+
if (!isMarketingRoute(route) && !isAuthRoute(route)) {
|
|
9102
9250
|
const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
|
|
9103
9251
|
if (wrapperFixed) {
|
|
9104
9252
|
codeToWrite = normalized;
|
|
@@ -9219,6 +9367,11 @@ function inferPageType(route, name) {
|
|
|
9219
9367
|
if (/\bchangelog\b/.test(key)) return "changelog";
|
|
9220
9368
|
if (/\babout\b/.test(key)) return "about";
|
|
9221
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";
|
|
9222
9375
|
return null;
|
|
9223
9376
|
}
|
|
9224
9377
|
function getDefaultContent(pageType, pageName) {
|
|
@@ -9262,6 +9415,26 @@ function getDefaultContent(pageType, pageName) {
|
|
|
9262
9415
|
settings: {
|
|
9263
9416
|
title: "Settings",
|
|
9264
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"
|
|
9265
9438
|
}
|
|
9266
9439
|
};
|
|
9267
9440
|
return defaults[pageType] || { title: pageName, description: "" };
|
|
@@ -9279,7 +9452,7 @@ function hasNavChanged(before, after) {
|
|
|
9279
9452
|
// src/commands/chat/interactive.ts
|
|
9280
9453
|
import chalk12 from "chalk";
|
|
9281
9454
|
import { resolve as resolve8 } from "path";
|
|
9282
|
-
import { existsSync as existsSync15, readFileSync as
|
|
9455
|
+
import { existsSync as existsSync15, readFileSync as readFileSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
9283
9456
|
import { DesignSystemManager as DesignSystemManager6, ComponentManager as ComponentManager4, loadManifest as loadManifest7 } from "@getcoherent/core";
|
|
9284
9457
|
var DEBUG3 = process.env.COHERENT_DEBUG === "1";
|
|
9285
9458
|
async function interactiveChat(options, chatCommandFn) {
|
|
@@ -9305,7 +9478,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
9305
9478
|
try {
|
|
9306
9479
|
mkdirSync5(historyDir, { recursive: true });
|
|
9307
9480
|
if (existsSync15(historyFile)) {
|
|
9308
|
-
history =
|
|
9481
|
+
history = readFileSync10(historyFile, "utf-8").split("\n").filter(Boolean).slice(-200);
|
|
9309
9482
|
}
|
|
9310
9483
|
} catch (e) {
|
|
9311
9484
|
if (DEBUG3) console.error("Failed to load REPL history:", e);
|
|
@@ -9772,7 +9945,7 @@ async function chatCommand(message, options) {
|
|
|
9772
9945
|
try {
|
|
9773
9946
|
const sharedPath = resolve9(projectRoot, entry.file);
|
|
9774
9947
|
if (existsSync16(sharedPath)) {
|
|
9775
|
-
const sharedCode =
|
|
9948
|
+
const sharedCode = readFileSync11(sharedPath, "utf-8");
|
|
9776
9949
|
const sharedImports = sharedCode.matchAll(/@\/components\/ui\/([a-z0-9-]+)/g);
|
|
9777
9950
|
for (const m of sharedImports) {
|
|
9778
9951
|
if (m[1]) allNeededComponentIds.add(m[1]);
|
|
@@ -9814,7 +9987,10 @@ async function chatCommand(message, options) {
|
|
|
9814
9987
|
if (DEBUG4) console.log(chalk13.gray(` [DEBUG] installComponent result: ${result.success}`));
|
|
9815
9988
|
if (result.success && result.componentDef) {
|
|
9816
9989
|
if (!cm.read(componentId)) {
|
|
9817
|
-
if (DEBUG4)
|
|
9990
|
+
if (DEBUG4)
|
|
9991
|
+
console.log(
|
|
9992
|
+
chalk13.gray(` [DEBUG] Registering ${result.componentDef.id} (${result.componentDef.name})`)
|
|
9993
|
+
);
|
|
9818
9994
|
const regResult = await cm.register(result.componentDef);
|
|
9819
9995
|
if (DEBUG4) {
|
|
9820
9996
|
console.log(
|
|
@@ -9894,6 +10070,17 @@ async function chatCommand(message, options) {
|
|
|
9894
10070
|
const result = await applyModification(request, dsm, cm, pm, projectRoot, provider, message);
|
|
9895
10071
|
results.push(result);
|
|
9896
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
|
+
}
|
|
9897
10084
|
const currentConfig = dsm.getConfig();
|
|
9898
10085
|
const autoScaffoldEnabled = currentConfig.settings.autoScaffold === true;
|
|
9899
10086
|
const scaffoldedPages = [];
|
|
@@ -9907,7 +10094,7 @@ async function chatCommand(message, options) {
|
|
|
9907
10094
|
let pageCode = "";
|
|
9908
10095
|
if (existsSync16(pageFilePath)) {
|
|
9909
10096
|
try {
|
|
9910
|
-
pageCode =
|
|
10097
|
+
pageCode = readFileSync11(pageFilePath, "utf-8");
|
|
9911
10098
|
} catch {
|
|
9912
10099
|
}
|
|
9913
10100
|
}
|
|
@@ -10018,7 +10205,7 @@ async function chatCommand(message, options) {
|
|
|
10018
10205
|
for (const mod of result.modified) {
|
|
10019
10206
|
if (mod.startsWith("app/") && mod.endsWith("/page.tsx")) {
|
|
10020
10207
|
try {
|
|
10021
|
-
const code =
|
|
10208
|
+
const code = readFileSync11(resolve9(projectRoot, mod), "utf-8");
|
|
10022
10209
|
const issues = validatePageQuality(code, allRoutes).filter(
|
|
10023
10210
|
(i) => i.type === "BROKEN_INTERNAL_LINK"
|
|
10024
10211
|
);
|
|
@@ -10088,6 +10275,10 @@ async function chatCommand(message, options) {
|
|
|
10088
10275
|
await regenerateFiles(Array.from(allModified), updatedConfig, projectRoot, { navChanged, storedHashes });
|
|
10089
10276
|
spinner.succeed("Files regenerated");
|
|
10090
10277
|
}
|
|
10278
|
+
const finalDeps = await scanAndInstallSharedDeps(projectRoot);
|
|
10279
|
+
if (finalDeps.length > 0) {
|
|
10280
|
+
console.log(chalk13.dim(` Auto-installed shared deps: ${finalDeps.join(", ")}`));
|
|
10281
|
+
}
|
|
10091
10282
|
try {
|
|
10092
10283
|
fixGlobalsCss(projectRoot, updatedConfig);
|
|
10093
10284
|
} catch {
|
|
@@ -10098,7 +10289,7 @@ async function chatCommand(message, options) {
|
|
|
10098
10289
|
const layoutFile = resolve9(projectRoot, "app", "layout.tsx");
|
|
10099
10290
|
const filesToHash = [layoutFile];
|
|
10100
10291
|
if (existsSync16(sharedDir)) {
|
|
10101
|
-
for (const f of
|
|
10292
|
+
for (const f of readdirSync3(sharedDir)) {
|
|
10102
10293
|
if (f.endsWith(".tsx")) filesToHash.push(resolve9(sharedDir, f));
|
|
10103
10294
|
}
|
|
10104
10295
|
}
|
|
@@ -10218,18 +10409,18 @@ ${uxRecommendations}
|
|
|
10218
10409
|
import chalk14 from "chalk";
|
|
10219
10410
|
import ora3 from "ora";
|
|
10220
10411
|
import { spawn } from "child_process";
|
|
10221
|
-
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";
|
|
10222
10413
|
import { resolve as resolve10, join as join14 } from "path";
|
|
10223
10414
|
import { readdir as readdir2 } from "fs/promises";
|
|
10224
10415
|
import { DesignSystemManager as DesignSystemManager8, ComponentGenerator as ComponentGenerator3 } from "@getcoherent/core";
|
|
10225
10416
|
|
|
10226
10417
|
// src/utils/file-watcher.ts
|
|
10227
|
-
import { readFileSync as
|
|
10418
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync18 } from "fs";
|
|
10228
10419
|
import { relative as relative4, join as join13 } from "path";
|
|
10229
10420
|
import { loadManifest as loadManifest9, saveManifest as saveManifest3 } from "@getcoherent/core";
|
|
10230
10421
|
|
|
10231
10422
|
// src/utils/component-integrity.ts
|
|
10232
|
-
import { existsSync as existsSync17, readFileSync as
|
|
10423
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12, readdirSync as readdirSync4 } from "fs";
|
|
10233
10424
|
import { join as join12, relative as relative3 } from "path";
|
|
10234
10425
|
function extractExportedComponentNames(code) {
|
|
10235
10426
|
const names = [];
|
|
@@ -10263,7 +10454,7 @@ function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
|
10263
10454
|
for (const absPath of pageFiles) {
|
|
10264
10455
|
if (absPath.includes("design-system")) continue;
|
|
10265
10456
|
try {
|
|
10266
|
-
const code =
|
|
10457
|
+
const code = readFileSync12(absPath, "utf-8");
|
|
10267
10458
|
const hasNamedImport = new RegExp(`import\\s+\\{[^}]*\\b${componentName}\\b[^}]*\\}\\s+from\\s+['"]`).test(code);
|
|
10268
10459
|
const hasDefaultImport = new RegExp(`import\\s+${componentName}\\s+from\\s+['"]`).test(code);
|
|
10269
10460
|
const hasPathImport = code.includes(`@/${componentImportPath}`);
|
|
@@ -10279,7 +10470,7 @@ function isUsedInLayout(projectRoot, componentName) {
|
|
|
10279
10470
|
const layoutPath = join12(projectRoot, "app", "layout.tsx");
|
|
10280
10471
|
if (!existsSync17(layoutPath)) return false;
|
|
10281
10472
|
try {
|
|
10282
|
-
const code =
|
|
10473
|
+
const code = readFileSync12(layoutPath, "utf-8");
|
|
10283
10474
|
return code.includes(componentName);
|
|
10284
10475
|
} catch {
|
|
10285
10476
|
return false;
|
|
@@ -10300,7 +10491,7 @@ function findUnregisteredComponents(projectRoot, manifest) {
|
|
|
10300
10491
|
const relFile = relative3(projectRoot, absPath);
|
|
10301
10492
|
if (registeredFiles.has(relFile)) continue;
|
|
10302
10493
|
try {
|
|
10303
|
-
const code =
|
|
10494
|
+
const code = readFileSync12(absPath, "utf-8");
|
|
10304
10495
|
const exports = extractExportedComponentNames(code);
|
|
10305
10496
|
for (const name of exports) {
|
|
10306
10497
|
if (registeredNames.has(name)) continue;
|
|
@@ -10322,7 +10513,7 @@ function findInlineDuplicates(projectRoot, manifest) {
|
|
|
10322
10513
|
if (absPath.includes("design-system")) continue;
|
|
10323
10514
|
let code;
|
|
10324
10515
|
try {
|
|
10325
|
-
code =
|
|
10516
|
+
code = readFileSync12(absPath, "utf-8");
|
|
10326
10517
|
} catch {
|
|
10327
10518
|
continue;
|
|
10328
10519
|
}
|
|
@@ -10354,7 +10545,7 @@ function findComponentFileByExportName(projectRoot, componentName) {
|
|
|
10354
10545
|
);
|
|
10355
10546
|
for (const absPath of files) {
|
|
10356
10547
|
try {
|
|
10357
|
-
const code =
|
|
10548
|
+
const code = readFileSync12(absPath, "utf-8");
|
|
10358
10549
|
const exports = extractExportedComponentNames(code);
|
|
10359
10550
|
if (exports.includes(componentName)) {
|
|
10360
10551
|
return relative3(projectRoot, absPath);
|
|
@@ -10399,7 +10590,7 @@ function reconcileComponents(projectRoot, manifest) {
|
|
|
10399
10590
|
}
|
|
10400
10591
|
let code;
|
|
10401
10592
|
try {
|
|
10402
|
-
code =
|
|
10593
|
+
code = readFileSync12(join12(projectRoot, entry.file), "utf-8");
|
|
10403
10594
|
} catch {
|
|
10404
10595
|
return true;
|
|
10405
10596
|
}
|
|
@@ -10476,7 +10667,7 @@ function collectFiles(dir, filter, skipDirs = []) {
|
|
|
10476
10667
|
function walk(d) {
|
|
10477
10668
|
let entries;
|
|
10478
10669
|
try {
|
|
10479
|
-
entries =
|
|
10670
|
+
entries = readdirSync4(d, { withFileTypes: true });
|
|
10480
10671
|
} catch {
|
|
10481
10672
|
return;
|
|
10482
10673
|
}
|
|
@@ -10522,7 +10713,7 @@ function getWatcherConfig(projectRoot) {
|
|
|
10522
10713
|
try {
|
|
10523
10714
|
const pkgPath = join13(projectRoot, "package.json");
|
|
10524
10715
|
if (!existsSync18(pkgPath)) return defaultWatcherConfig();
|
|
10525
|
-
const pkg = JSON.parse(
|
|
10716
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
10526
10717
|
const c = pkg?.coherent?.watcher ?? {};
|
|
10527
10718
|
return {
|
|
10528
10719
|
enabled: c.enabled !== false,
|
|
@@ -10550,7 +10741,7 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
10550
10741
|
if (relativePath.includes("node_modules") || relativePath.includes(".next")) return;
|
|
10551
10742
|
let content;
|
|
10552
10743
|
try {
|
|
10553
|
-
content =
|
|
10744
|
+
content = readFileSync13(filePath, "utf-8");
|
|
10554
10745
|
} catch {
|
|
10555
10746
|
return;
|
|
10556
10747
|
}
|
|
@@ -10623,7 +10814,7 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
10623
10814
|
const manifest = await loadManifest9(projectRoot);
|
|
10624
10815
|
const alreadyRegistered = manifest.shared.some((s) => s.file === relativePath);
|
|
10625
10816
|
if (alreadyRegistered) return;
|
|
10626
|
-
const code =
|
|
10817
|
+
const code = readFileSync13(filePath, "utf-8");
|
|
10627
10818
|
const exports = extractExportedComponentNames(code);
|
|
10628
10819
|
if (exports.length > 0) {
|
|
10629
10820
|
const alreadyByName = exports.every((n) => manifest.shared.some((s) => s.name === n));
|
|
@@ -10747,7 +10938,7 @@ async function validateSyntax(projectRoot) {
|
|
|
10747
10938
|
const appDir = join14(projectRoot, "app");
|
|
10748
10939
|
const pages = await listPageFiles(appDir);
|
|
10749
10940
|
for (const file of pages) {
|
|
10750
|
-
const content =
|
|
10941
|
+
const content = readFileSync14(file, "utf-8");
|
|
10751
10942
|
const fixed = fixUnescapedLtInJsx(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)));
|
|
10752
10943
|
if (fixed !== content) {
|
|
10753
10944
|
writeFileSync10(file, fixed, "utf-8");
|
|
@@ -10760,9 +10951,14 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10760
10951
|
const uiDir = join14(projectRoot, "components", "ui");
|
|
10761
10952
|
if (!existsSync19(appDir) || !existsSync19(uiDir)) return;
|
|
10762
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
|
+
}
|
|
10763
10959
|
const neededExports = /* @__PURE__ */ new Map();
|
|
10764
10960
|
for (const file of pages) {
|
|
10765
|
-
const content =
|
|
10961
|
+
const content = readFileSync14(file, "utf-8");
|
|
10766
10962
|
const importRe = /import\s*\{([^}]+)\}\s*from\s*['"]@\/components\/ui\/([^'"]+)['"]/g;
|
|
10767
10963
|
let m;
|
|
10768
10964
|
while ((m = importRe.exec(content)) !== null) {
|
|
@@ -10806,7 +11002,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10806
11002
|
}
|
|
10807
11003
|
continue;
|
|
10808
11004
|
}
|
|
10809
|
-
const content =
|
|
11005
|
+
const content = readFileSync14(componentFile, "utf-8");
|
|
10810
11006
|
const exportRe = /export\s+(?:const|function|class)\s+(\w+)|export\s*\{([^}]+)\}/g;
|
|
10811
11007
|
const existingExports = /* @__PURE__ */ new Set();
|
|
10812
11008
|
let em;
|
|
@@ -10859,7 +11055,7 @@ async function backfillPageAnalysis(projectRoot) {
|
|
|
10859
11055
|
filePath = join14(projectRoot, "app", route.slice(1), "page.tsx");
|
|
10860
11056
|
}
|
|
10861
11057
|
if (!existsSync19(filePath)) continue;
|
|
10862
|
-
const code =
|
|
11058
|
+
const code = readFileSync14(filePath, "utf-8");
|
|
10863
11059
|
if (code.length < 50) continue;
|
|
10864
11060
|
page.pageAnalysis = analyzePageCode(code);
|
|
10865
11061
|
changed = true;
|
|
@@ -11100,7 +11296,7 @@ async function previewCommand() {
|
|
|
11100
11296
|
import chalk15 from "chalk";
|
|
11101
11297
|
import ora4 from "ora";
|
|
11102
11298
|
import { spawn as spawn2 } from "child_process";
|
|
11103
|
-
import { existsSync as existsSync20, rmSync as rmSync4, readdirSync as
|
|
11299
|
+
import { existsSync as existsSync20, rmSync as rmSync4, readdirSync as readdirSync6 } from "fs";
|
|
11104
11300
|
import { resolve as resolve11, join as join15, dirname as dirname7 } from "path";
|
|
11105
11301
|
import { readdir as readdir3, readFile as readFile6, writeFile as writeFile5, mkdir as mkdir5, copyFile as copyFile2 } from "fs/promises";
|
|
11106
11302
|
var COPY_EXCLUDE = /* @__PURE__ */ new Set([
|
|
@@ -11221,7 +11417,7 @@ function countComponents(outRoot) {
|
|
|
11221
11417
|
const dir = join15(outRoot, "components", sub);
|
|
11222
11418
|
if (!existsSync20(dir)) continue;
|
|
11223
11419
|
try {
|
|
11224
|
-
n +=
|
|
11420
|
+
n += readdirSync6(dir).filter((f) => f.endsWith(".tsx") || f.endsWith(".jsx")).length;
|
|
11225
11421
|
} catch {
|
|
11226
11422
|
}
|
|
11227
11423
|
}
|
|
@@ -11540,7 +11736,7 @@ async function regenerateDocsCommand() {
|
|
|
11540
11736
|
|
|
11541
11737
|
// src/commands/fix.ts
|
|
11542
11738
|
import chalk18 from "chalk";
|
|
11543
|
-
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";
|
|
11544
11740
|
import { resolve as resolve12, join as join16 } from "path";
|
|
11545
11741
|
import {
|
|
11546
11742
|
DesignSystemManager as DesignSystemManager11,
|
|
@@ -11565,7 +11761,7 @@ function extractComponentIdsFromCode2(code) {
|
|
|
11565
11761
|
function listTsxFiles(dir) {
|
|
11566
11762
|
const files = [];
|
|
11567
11763
|
try {
|
|
11568
|
-
const entries =
|
|
11764
|
+
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
11569
11765
|
for (const e of entries) {
|
|
11570
11766
|
const full = join16(dir, e.name);
|
|
11571
11767
|
if (e.isDirectory() && e.name !== "node_modules" && !e.name.startsWith(".")) {
|
|
@@ -11624,7 +11820,7 @@ async function fixCommand(opts = {}) {
|
|
|
11624
11820
|
const componentsTsxFiles = listTsxFiles(resolve12(projectRoot, "components"));
|
|
11625
11821
|
const allComponentIds = /* @__PURE__ */ new Set();
|
|
11626
11822
|
for (const file of [...allTsxFiles, ...componentsTsxFiles]) {
|
|
11627
|
-
const content =
|
|
11823
|
+
const content = readFileSync15(file, "utf-8");
|
|
11628
11824
|
extractComponentIdsFromCode2(content).forEach((id) => allComponentIds.add(id));
|
|
11629
11825
|
}
|
|
11630
11826
|
let dsm = null;
|
|
@@ -11696,7 +11892,7 @@ async function fixCommand(opts = {}) {
|
|
|
11696
11892
|
const userTsxFiles = allTsxFiles.filter((f) => !f.includes("/design-system/"));
|
|
11697
11893
|
let syntaxFixed = 0;
|
|
11698
11894
|
for (const file of userTsxFiles) {
|
|
11699
|
-
const content =
|
|
11895
|
+
const content = readFileSync15(file, "utf-8");
|
|
11700
11896
|
const fixed = fixUnescapedLtInJsx(
|
|
11701
11897
|
fixEscapedClosingQuotes(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)))
|
|
11702
11898
|
);
|
|
@@ -11714,7 +11910,7 @@ async function fixCommand(opts = {}) {
|
|
|
11714
11910
|
let qualityFixCount = 0;
|
|
11715
11911
|
const qualityFixDetails = [];
|
|
11716
11912
|
for (const file of userTsxFiles) {
|
|
11717
|
-
const content =
|
|
11913
|
+
const content = readFileSync15(file, "utf-8");
|
|
11718
11914
|
const { code: autoFixed, fixes: fileFixes } = await autoFixCode(content);
|
|
11719
11915
|
if (autoFixed !== content) {
|
|
11720
11916
|
if (!dryRun) writeFileSync11(file, autoFixed, "utf-8");
|
|
@@ -11733,7 +11929,7 @@ async function fixCommand(opts = {}) {
|
|
|
11733
11929
|
let totalWarnings = 0;
|
|
11734
11930
|
const fileIssues = [];
|
|
11735
11931
|
for (const file of allTsxFiles) {
|
|
11736
|
-
const code = dryRun ?
|
|
11932
|
+
const code = dryRun ? readFileSync15(file, "utf-8") : readFileSync15(file, "utf-8");
|
|
11737
11933
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11738
11934
|
const baseName = file.split("/").pop() || "";
|
|
11739
11935
|
const isAuthPage = relativePath.includes("(auth)");
|
|
@@ -11856,13 +12052,13 @@ async function fixCommand(opts = {}) {
|
|
|
11856
12052
|
// src/commands/check.ts
|
|
11857
12053
|
import chalk19 from "chalk";
|
|
11858
12054
|
import { resolve as resolve13 } from "path";
|
|
11859
|
-
import { readdirSync as
|
|
12055
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync16, statSync as statSync2, existsSync as existsSync22 } from "fs";
|
|
11860
12056
|
import { loadManifest as loadManifest11 } from "@getcoherent/core";
|
|
11861
12057
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", "design-system"]);
|
|
11862
12058
|
function findTsxFiles(dir) {
|
|
11863
12059
|
const results = [];
|
|
11864
12060
|
try {
|
|
11865
|
-
const entries =
|
|
12061
|
+
const entries = readdirSync8(dir);
|
|
11866
12062
|
for (const entry of entries) {
|
|
11867
12063
|
const full = resolve13(dir, entry);
|
|
11868
12064
|
const stat = statSync2(full);
|
|
@@ -11913,7 +12109,7 @@ async function checkCommand(opts = {}) {
|
|
|
11913
12109
|
"NATIVE_TABLE"
|
|
11914
12110
|
]);
|
|
11915
12111
|
for (const file of files) {
|
|
11916
|
-
const code =
|
|
12112
|
+
const code = readFileSync16(file, "utf-8");
|
|
11917
12113
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11918
12114
|
const baseName = file.split("/").pop() || "";
|
|
11919
12115
|
const isAuthPage = relativePath.includes("(auth)");
|
|
@@ -11955,7 +12151,7 @@ async function checkCommand(opts = {}) {
|
|
|
11955
12151
|
routeSet.add("/");
|
|
11956
12152
|
routeSet.add("#");
|
|
11957
12153
|
for (const file of files) {
|
|
11958
|
-
const code =
|
|
12154
|
+
const code = readFileSync16(file, "utf-8");
|
|
11959
12155
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11960
12156
|
const lines = code.split("\n");
|
|
11961
12157
|
const linkHrefRe = /href\s*=\s*["'](\/[a-z0-9/-]*)["']/gi;
|
|
@@ -12023,7 +12219,7 @@ async function checkCommand(opts = {}) {
|
|
|
12023
12219
|
continue;
|
|
12024
12220
|
}
|
|
12025
12221
|
try {
|
|
12026
|
-
const code =
|
|
12222
|
+
const code = readFileSync16(filePath, "utf-8");
|
|
12027
12223
|
const actualExports = extractExportedComponentNames(code);
|
|
12028
12224
|
if (actualExports.length > 0 && !actualExports.includes(entry.name)) {
|
|
12029
12225
|
_nameMismatch++;
|
|
@@ -12714,7 +12910,7 @@ async function dsRegenerateCommand() {
|
|
|
12714
12910
|
// src/commands/update.ts
|
|
12715
12911
|
import chalk28 from "chalk";
|
|
12716
12912
|
import ora8 from "ora";
|
|
12717
|
-
import { readFileSync as
|
|
12913
|
+
import { readFileSync as readFileSync17, existsSync as existsSync25 } from "fs";
|
|
12718
12914
|
import { join as join19 } from "path";
|
|
12719
12915
|
import { DesignSystemManager as DesignSystemManager15, CLI_VERSION as CLI_VERSION4 } from "@getcoherent/core";
|
|
12720
12916
|
|
|
@@ -12887,7 +13083,7 @@ function checkMissingCssVars(projectRoot) {
|
|
|
12887
13083
|
const globalsPath = join19(projectRoot, "app", "globals.css");
|
|
12888
13084
|
if (!existsSync25(globalsPath)) return [];
|
|
12889
13085
|
try {
|
|
12890
|
-
const content =
|
|
13086
|
+
const content = readFileSync17(globalsPath, "utf-8");
|
|
12891
13087
|
return EXPECTED_CSS_VARS.filter((v) => !content.includes(v));
|
|
12892
13088
|
} catch {
|
|
12893
13089
|
return [];
|
|
@@ -12897,7 +13093,7 @@ function patchGlobalsCss(projectRoot, missingVars) {
|
|
|
12897
13093
|
const globalsPath = join19(projectRoot, "app", "globals.css");
|
|
12898
13094
|
if (!existsSync25(globalsPath) || missingVars.length === 0) return;
|
|
12899
13095
|
const { writeFileSync: writeFileSync14 } = __require("fs");
|
|
12900
|
-
let content =
|
|
13096
|
+
let content = readFileSync17(globalsPath, "utf-8");
|
|
12901
13097
|
const defaultValues = {
|
|
12902
13098
|
"--chart-1": "220 70% 50%",
|
|
12903
13099
|
"--chart-2": "160 60% 45%",
|
|
@@ -12975,7 +13171,7 @@ async function undoCommand(options) {
|
|
|
12975
13171
|
// src/commands/sync.ts
|
|
12976
13172
|
import chalk30 from "chalk";
|
|
12977
13173
|
import ora9 from "ora";
|
|
12978
|
-
import { existsSync as existsSync26, readFileSync as
|
|
13174
|
+
import { existsSync as existsSync26, readFileSync as readFileSync18 } from "fs";
|
|
12979
13175
|
import { join as join20, relative as relative5, dirname as dirname10 } from "path";
|
|
12980
13176
|
import { readdir as readdir4, readFile as readFile7 } from "fs/promises";
|
|
12981
13177
|
import { DesignSystemManager as DesignSystemManager16 } from "@getcoherent/core";
|
|
@@ -12985,7 +13181,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
12985
13181
|
const darkColors = {};
|
|
12986
13182
|
const globalsPath = join20(projectRoot, "app", "globals.css");
|
|
12987
13183
|
if (existsSync26(globalsPath)) {
|
|
12988
|
-
const css =
|
|
13184
|
+
const css = readFileSync18(globalsPath, "utf-8");
|
|
12989
13185
|
const rootMatch = css.match(/:root\s*\{([^}]+)\}/s);
|
|
12990
13186
|
if (rootMatch) parseVarsInto(rootMatch[1], lightColors);
|
|
12991
13187
|
const darkMatch = css.match(/\.dark\s*\{([^}]+)\}/s);
|
|
@@ -12994,7 +13190,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
12994
13190
|
const layoutPath = join20(projectRoot, "app", "layout.tsx");
|
|
12995
13191
|
let layoutCode = "";
|
|
12996
13192
|
if (existsSync26(layoutPath)) {
|
|
12997
|
-
layoutCode =
|
|
13193
|
+
layoutCode = readFileSync18(layoutPath, "utf-8");
|
|
12998
13194
|
const rootInline = layoutCode.match(/:root\s*\{([^}]+)\}/s);
|
|
12999
13195
|
if (rootInline && Object.keys(lightColors).length === 0) {
|
|
13000
13196
|
parseVarsInto(rootInline[1], lightColors);
|
|
@@ -13012,7 +13208,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
13012
13208
|
defaultMode = "dark";
|
|
13013
13209
|
}
|
|
13014
13210
|
let radius;
|
|
13015
|
-
const allCss = [existsSync26(globalsPath) ?
|
|
13211
|
+
const allCss = [existsSync26(globalsPath) ? readFileSync18(globalsPath, "utf-8") : "", layoutCode].join("\n");
|
|
13016
13212
|
const radiusMatch = allCss.match(/--radius:\s*([^;]+);/);
|
|
13017
13213
|
if (radiusMatch) radius = radiusMatch[1].trim();
|
|
13018
13214
|
return {
|
|
@@ -13442,7 +13638,7 @@ async function syncCommand(options = {}) {
|
|
|
13442
13638
|
// src/commands/migrate.ts
|
|
13443
13639
|
import chalk31 from "chalk";
|
|
13444
13640
|
import ora10 from "ora";
|
|
13445
|
-
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";
|
|
13446
13642
|
import { join as join21 } from "path";
|
|
13447
13643
|
function backupDir(projectRoot) {
|
|
13448
13644
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -13477,7 +13673,7 @@ function rollback(projectRoot) {
|
|
|
13477
13673
|
const guard = guardPath(projectRoot);
|
|
13478
13674
|
if (!existsSync27(guard)) return false;
|
|
13479
13675
|
try {
|
|
13480
|
-
const data = JSON.parse(
|
|
13676
|
+
const data = JSON.parse(readFileSync19(guard, "utf-8"));
|
|
13481
13677
|
const backup = data.backup;
|
|
13482
13678
|
if (!existsSync27(backup)) return false;
|
|
13483
13679
|
const uiBackup = join21(backup, "components-ui");
|
|
@@ -13528,7 +13724,7 @@ async function migrateAction(options) {
|
|
|
13528
13724
|
}
|
|
13529
13725
|
const provider = getComponentProvider();
|
|
13530
13726
|
const managedIds = new Set(provider.listNames());
|
|
13531
|
-
const files =
|
|
13727
|
+
const files = readdirSync9(uiDir).filter((f) => f.endsWith(".tsx"));
|
|
13532
13728
|
const migratable = files.map((f) => f.replace(".tsx", "")).filter((id) => managedIds.has(id));
|
|
13533
13729
|
if (migratable.length === 0) {
|
|
13534
13730
|
console.log(chalk31.green("All components are already up to date."));
|
|
@@ -13571,7 +13767,7 @@ Found ${migratable.length} component(s) to migrate:`));
|
|
|
13571
13767
|
}
|
|
13572
13768
|
|
|
13573
13769
|
// src/utils/update-notifier.ts
|
|
13574
|
-
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";
|
|
13575
13771
|
import { join as join22 } from "path";
|
|
13576
13772
|
import { homedir } from "os";
|
|
13577
13773
|
import chalk32 from "chalk";
|
|
@@ -13584,7 +13780,7 @@ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
|
13584
13780
|
function readCache() {
|
|
13585
13781
|
try {
|
|
13586
13782
|
if (!existsSync28(CACHE_FILE)) return null;
|
|
13587
|
-
const raw =
|
|
13783
|
+
const raw = readFileSync20(CACHE_FILE, "utf-8");
|
|
13588
13784
|
return JSON.parse(raw);
|
|
13589
13785
|
} catch (e) {
|
|
13590
13786
|
if (DEBUG5) console.error("Failed to read update cache:", e);
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.5.
|
|
6
|
+
"version": "0.5.15",
|
|
7
7
|
"description": "CLI interface for Coherent Design Method",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.js",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"ora": "^7.0.1",
|
|
44
44
|
"prompts": "^2.4.2",
|
|
45
45
|
"zod": "^3.22.4",
|
|
46
|
-
"@getcoherent/core": "0.5.
|
|
46
|
+
"@getcoherent/core": "0.5.15"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@types/node": "^20.11.0",
|