@getcoherent/cli 0.6.19 → 0.6.21
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.
|
@@ -613,12 +613,12 @@ function resolveHref(linkText, context) {
|
|
|
613
613
|
function replaceRawColors(classes, colorMap) {
|
|
614
614
|
let changed = false;
|
|
615
615
|
let result = classes;
|
|
616
|
-
const accentColorRe = /\b((?:(?:hover|focus|active|group-hover|focus-visible|focus-within):)?)(bg|text|border|ring|outline|from|to|via)-(emerald|blue|violet|indigo|purple|teal|cyan|sky|rose|amber|red|green|yellow|pink|orange|fuchsia|lime)-(\d+)
|
|
616
|
+
const accentColorRe = /\b((?:(?:hover|focus|active|group-hover|focus-visible|focus-within):)?)(bg|text|border|ring|outline|from|to|via)-(emerald|blue|violet|indigo|purple|teal|cyan|sky|rose|amber|red|green|yellow|pink|orange|fuchsia|lime)-(\d+)(?:\/\d+)?\b/g;
|
|
617
617
|
result = result.replace(accentColorRe, (m, statePrefix, prefix, color, shade) => {
|
|
618
|
-
const
|
|
619
|
-
if (colorMap[
|
|
618
|
+
const bareNoOpacity = m.replace(statePrefix, "").replace(/\/\d+$/, "");
|
|
619
|
+
if (colorMap[bareNoOpacity]) {
|
|
620
620
|
changed = true;
|
|
621
|
-
return statePrefix + colorMap[
|
|
621
|
+
return statePrefix + colorMap[bareNoOpacity];
|
|
622
622
|
}
|
|
623
623
|
const n = parseInt(shade);
|
|
624
624
|
const isDestructive = color === "red";
|
|
@@ -648,12 +648,12 @@ function replaceRawColors(classes, colorMap) {
|
|
|
648
648
|
}
|
|
649
649
|
return m;
|
|
650
650
|
});
|
|
651
|
-
const neutralColorRe = /\b((?:(?:hover|focus|active|group-hover|focus-visible|focus-within):)?)(bg|text|border|ring|outline)-(zinc|slate|gray|neutral|stone)-(\d+)
|
|
651
|
+
const neutralColorRe = /\b((?:(?:hover|focus|active|group-hover|focus-visible|focus-within):)?)(bg|text|border|ring|outline)-(zinc|slate|gray|neutral|stone)-(\d+)(?:\/\d+)?\b/g;
|
|
652
652
|
result = result.replace(neutralColorRe, (m, statePrefix, prefix, _color, shade) => {
|
|
653
|
-
const
|
|
654
|
-
if (colorMap[
|
|
653
|
+
const bareNoOpacity = m.replace(statePrefix, "").replace(/\/\d+$/, "");
|
|
654
|
+
if (colorMap[bareNoOpacity]) {
|
|
655
655
|
changed = true;
|
|
656
|
-
return statePrefix + colorMap[
|
|
656
|
+
return statePrefix + colorMap[bareNoOpacity];
|
|
657
657
|
}
|
|
658
658
|
const n = parseInt(shade);
|
|
659
659
|
if (prefix === "bg") {
|
|
@@ -936,11 +936,19 @@ ${selectImport}`
|
|
|
936
936
|
for (const m of fixed.matchAll(/import\s*\{([^}]+)\}\s*from\s*["'](?!lucide-react)([^"']+)["']/g)) {
|
|
937
937
|
m[1].split(",").map((s) => s.trim()).filter(Boolean).forEach((n) => nonLucideImports.add(n));
|
|
938
938
|
}
|
|
939
|
-
const
|
|
940
|
-
const
|
|
939
|
+
const rawIconEntries = lucideImportMatch[1].split(",").map((s) => s.trim()).filter(Boolean);
|
|
940
|
+
const iconNames = rawIconEntries.map((entry) => {
|
|
941
|
+
const parts = entry.split(/\s+as\s+/);
|
|
942
|
+
return parts[0].trim();
|
|
943
|
+
});
|
|
944
|
+
const duplicates = rawIconEntries.filter((entry) => {
|
|
945
|
+
const alias = entry.split(/\s+as\s+/).pop().trim();
|
|
946
|
+
return nonLucideImports.has(alias);
|
|
947
|
+
});
|
|
941
948
|
let newImport = lucideImportMatch[1];
|
|
942
949
|
for (const dup of duplicates) {
|
|
943
|
-
|
|
950
|
+
const escaped = dup.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
951
|
+
newImport = newImport.replace(new RegExp(`${escaped},?\\s*`), "");
|
|
944
952
|
fixes.push(`removed ${dup} from lucide import (conflicts with UI component import)`);
|
|
945
953
|
}
|
|
946
954
|
const ICON_ALIASES = {
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
formatIssues,
|
|
8
8
|
validatePageQuality,
|
|
9
9
|
verifyIncrementalEdit
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-VKGZRKWD.js";
|
|
11
11
|
import {
|
|
12
12
|
generateArchitecturePlan,
|
|
13
13
|
getPageGroup,
|
|
@@ -5879,11 +5879,13 @@ function verifyReusePlan(generatedCode, plan) {
|
|
|
5879
5879
|
}
|
|
5880
5880
|
|
|
5881
5881
|
// src/commands/chat/split-generator.ts
|
|
5882
|
+
var MAX_EXISTING_PAGES_CONTEXT = 10;
|
|
5882
5883
|
function buildExistingPagesContext(config2) {
|
|
5883
5884
|
const pages = config2.pages || [];
|
|
5884
5885
|
const analyzed = pages.filter((p) => p.pageAnalysis);
|
|
5885
5886
|
if (analyzed.length === 0) return "";
|
|
5886
|
-
const
|
|
5887
|
+
const capped = analyzed.slice(0, MAX_EXISTING_PAGES_CONTEXT);
|
|
5888
|
+
const lines = capped.map((p) => {
|
|
5887
5889
|
return summarizePageAnalysis(p.name || p.id, p.route, p.pageAnalysis);
|
|
5888
5890
|
});
|
|
5889
5891
|
let ctx = `EXISTING PAGES CONTEXT:
|
|
@@ -5983,12 +5985,15 @@ var RELEVANT_TYPES = {
|
|
|
5983
5985
|
function buildTieredComponentsPrompt(manifest, pageType) {
|
|
5984
5986
|
if (manifest.shared.length === 0) return void 0;
|
|
5985
5987
|
const relevantTypes = new Set(RELEVANT_TYPES[pageType] || RELEVANT_TYPES.app);
|
|
5986
|
-
const level1Lines = manifest.shared.map((e) => {
|
|
5988
|
+
const level1Lines = manifest.shared.slice(0, 20).map((e) => {
|
|
5987
5989
|
const desc = e.description ? ` \u2014 ${e.description}` : "";
|
|
5988
5990
|
return `- ${e.id} ${e.name} (${e.type})${desc}`;
|
|
5989
5991
|
});
|
|
5992
|
+
if (manifest.shared.length > 20) {
|
|
5993
|
+
level1Lines.push(`- ... and ${manifest.shared.length - 20} more (import by name)`);
|
|
5994
|
+
}
|
|
5990
5995
|
const relevantComponents = manifest.shared.filter((e) => relevantTypes.has(e.type));
|
|
5991
|
-
const level2Blocks = relevantComponents.filter((e) => e.propsInterface || e.usageExample).map((e) => {
|
|
5996
|
+
const level2Blocks = relevantComponents.filter((e) => e.propsInterface || e.usageExample).slice(0, 8).map((e) => {
|
|
5992
5997
|
const importPath = e.file.replace(/^components\/shared\//, "").replace(/\.tsx$/, "");
|
|
5993
5998
|
const lines = [`### ${e.name} (${e.id})`];
|
|
5994
5999
|
if (e.propsInterface) lines.push(`Props: ${e.propsInterface}`);
|
|
@@ -6063,10 +6068,14 @@ function readExistingAppPageForReference(projectRoot, plan) {
|
|
|
6063
6068
|
if (note.type !== "app") continue;
|
|
6064
6069
|
for (const group of ["(app)", "(admin)", "(dashboard)"]) {
|
|
6065
6070
|
const filePath = resolve6(projectRoot, "app", group, key, "page.tsx");
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6071
|
+
try {
|
|
6072
|
+
if (existsSync14(filePath)) {
|
|
6073
|
+
const code = readFileSync9(filePath, "utf-8");
|
|
6074
|
+
const lines = code.split("\n");
|
|
6075
|
+
return lines.slice(0, 60).join("\n");
|
|
6076
|
+
}
|
|
6077
|
+
} catch {
|
|
6078
|
+
continue;
|
|
6070
6079
|
}
|
|
6071
6080
|
}
|
|
6072
6081
|
}
|
|
@@ -6281,7 +6290,11 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
6281
6290
|
changes: { id: homePage.id, name: homePage.name, route: homePage.route }
|
|
6282
6291
|
};
|
|
6283
6292
|
}
|
|
6284
|
-
|
|
6293
|
+
if (homePageCode) {
|
|
6294
|
+
spinner.succeed(`Phase 3/6 \u2014 ${homePage.name} page generated`);
|
|
6295
|
+
} else {
|
|
6296
|
+
spinner.warn(`Phase 3/6 \u2014 ${homePage.name} page generated (no code \u2014 AI may have failed)`);
|
|
6297
|
+
}
|
|
6285
6298
|
}
|
|
6286
6299
|
spinner.start("Phase 4/6 \u2014 Extracting design patterns...");
|
|
6287
6300
|
const styleContext = homePageCode ? extractStyleContext(homePageCode) : "";
|
|
@@ -6411,6 +6424,7 @@ ${existingAppPageCode}
|
|
|
6411
6424
|
`Create ONE page called "${name}" at route "${route}".`,
|
|
6412
6425
|
`Context: ${message}.`,
|
|
6413
6426
|
`Generate complete pageCode for this single page only. Do not generate other pages.`,
|
|
6427
|
+
`FORBIDDEN in pageCode: <header>, <nav>, <footer>, site-wide navigation, copyright footers. The layout provides all of these.`,
|
|
6414
6428
|
`PAGE TYPE: ${pageType}`,
|
|
6415
6429
|
designConstraints,
|
|
6416
6430
|
layoutNote,
|
|
@@ -6521,7 +6535,11 @@ Keep all existing functionality. Only add imports and replace inline duplicates.
|
|
|
6521
6535
|
}
|
|
6522
6536
|
}
|
|
6523
6537
|
const withCode = allRequests.filter((r) => r.changes?.pageCode).length;
|
|
6524
|
-
|
|
6538
|
+
if (withCode === 0) {
|
|
6539
|
+
spinner.warn(`Phase 5/6 \u2014 Generated ${allRequests.length} pages (0 with full code \u2014 AI may have failed)`);
|
|
6540
|
+
} else {
|
|
6541
|
+
spinner.succeed(`Phase 5/6 \u2014 Generated ${allRequests.length} pages (${withCode} with full code)`);
|
|
6542
|
+
}
|
|
6525
6543
|
return { requests: allRequests, plan };
|
|
6526
6544
|
}
|
|
6527
6545
|
var SharedExtractionItemSchema = z.object({
|
|
@@ -7083,7 +7101,8 @@ function printPostGenerationReport(opts) {
|
|
|
7083
7101
|
if (inCodeShared.length > 0) {
|
|
7084
7102
|
console.log(chalk10.dim(` Shared: ${inCodeShared.map((s) => `${s.id} (${s.name})`).join(", ")}`));
|
|
7085
7103
|
}
|
|
7086
|
-
|
|
7104
|
+
const isAuthPage = route && (/^\/(login|signin|signup|register|forgot-password|reset-password)\b/.test(route) || filePath.includes("(auth)"));
|
|
7105
|
+
if (layoutShared.length > 0 && !isAuthPage) {
|
|
7087
7106
|
console.log(chalk10.dim(` Layout: ${layoutShared.map((l) => `${l.id} (${l.name})`).join(", ")} via layout.tsx`));
|
|
7088
7107
|
}
|
|
7089
7108
|
if (iconCount > 0) {
|
|
@@ -7287,21 +7306,30 @@ function getChangeDescription(request, config2) {
|
|
|
7287
7306
|
|
|
7288
7307
|
// src/commands/chat/modification-handler.ts
|
|
7289
7308
|
var DEBUG2 = process.env.COHERENT_DEBUG === "1";
|
|
7309
|
+
function isSiteWideHeader(block) {
|
|
7310
|
+
return /<Link\b/.test(block) || /<a\s+href/.test(block) || /<nav\b/.test(block);
|
|
7311
|
+
}
|
|
7312
|
+
function isSiteWideFooter(block) {
|
|
7313
|
+
return /©|©|All rights|<Link\b/.test(block) || /<a\s+href/.test(block) && block.split("<a ").length >= 3;
|
|
7314
|
+
}
|
|
7315
|
+
function isSiteWideNav(block) {
|
|
7316
|
+
return /<Link\b/.test(block) || /<a\s+href/.test(block) && block.split(/<a\s+href/).length >= 3;
|
|
7317
|
+
}
|
|
7290
7318
|
function stripInlineLayoutElements(code) {
|
|
7291
7319
|
let result = code;
|
|
7292
7320
|
const stripped = [];
|
|
7293
7321
|
const headerBlock = extractBalancedTag(result, "header");
|
|
7294
|
-
if (headerBlock) {
|
|
7322
|
+
if (headerBlock && isSiteWideHeader(headerBlock)) {
|
|
7295
7323
|
result = result.replace(headerBlock, "");
|
|
7296
7324
|
stripped.push("header");
|
|
7297
7325
|
}
|
|
7298
7326
|
const navBlock = extractBalancedTag(result, "nav");
|
|
7299
|
-
if (navBlock) {
|
|
7327
|
+
if (navBlock && isSiteWideNav(navBlock)) {
|
|
7300
7328
|
result = result.replace(navBlock, "");
|
|
7301
7329
|
stripped.push("nav");
|
|
7302
7330
|
}
|
|
7303
7331
|
const footerBlock = extractBalancedTag(result, "footer");
|
|
7304
|
-
if (footerBlock) {
|
|
7332
|
+
if (footerBlock && isSiteWideFooter(footerBlock)) {
|
|
7305
7333
|
result = result.replace(footerBlock, "");
|
|
7306
7334
|
stripped.push("footer");
|
|
7307
7335
|
}
|
|
@@ -7444,7 +7472,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7444
7472
|
};
|
|
7445
7473
|
}
|
|
7446
7474
|
const readPlan = projectRoot ? loadPlan(projectRoot) : null;
|
|
7447
|
-
const pageFilePath = routeToFsPath(projectRoot, route, readPlan ||
|
|
7475
|
+
const pageFilePath = routeToFsPath(projectRoot, route, readPlan || isAuthRoute(route));
|
|
7448
7476
|
let pageCode;
|
|
7449
7477
|
try {
|
|
7450
7478
|
pageCode = await readFile(pageFilePath);
|
|
@@ -7475,8 +7503,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7475
7503
|
await writeFile(pageFilePath, fixedCode);
|
|
7476
7504
|
const manifest = await loadManifest6(projectRoot);
|
|
7477
7505
|
const usedIn = manifest.shared.find((e) => e.id === resolved.id)?.usedIn ?? [];
|
|
7478
|
-
const
|
|
7479
|
-
const filePathRel = routePath ? `app/${routePath}/page.tsx` : "app/page.tsx";
|
|
7506
|
+
const filePathRel = routeToRelPath(route, readPlan || isAuthRoute(route));
|
|
7480
7507
|
if (!usedIn.includes(filePathRel)) {
|
|
7481
7508
|
const nextManifest = updateUsedIn(manifest, resolved.id, [...usedIn, filePathRel]);
|
|
7482
7509
|
await saveManifest2(projectRoot, nextManifest);
|
|
@@ -7516,13 +7543,11 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7516
7543
|
};
|
|
7517
7544
|
}
|
|
7518
7545
|
const allPagesToLink = [sourcePageName, ...targetPages];
|
|
7546
|
+
const promotePlan = projectRoot ? loadPlan(projectRoot) : null;
|
|
7519
7547
|
const routeToPath = (nameOrRoute) => {
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
|
|
7523
|
-
const p = config2.pages.find((x) => x.name?.toLowerCase() === nameOrRoute.toLowerCase() || x.id === nameOrRoute);
|
|
7524
|
-
if (!p?.route) return null;
|
|
7525
|
-
return routeToRelPath(p.route, false);
|
|
7548
|
+
const route = nameOrRoute.startsWith("/") ? nameOrRoute : config2.pages.find((x) => x.name?.toLowerCase() === nameOrRoute.toLowerCase() || x.id === nameOrRoute)?.route;
|
|
7549
|
+
if (!route) return null;
|
|
7550
|
+
return routeToRelPath(route, promotePlan || isAuthRoute(route));
|
|
7526
7551
|
};
|
|
7527
7552
|
const sourcePath = routeToPath(sourcePageName);
|
|
7528
7553
|
if (!sourcePath) {
|
|
@@ -8503,7 +8528,7 @@ async function chatCommand(message, options) {
|
|
|
8503
8528
|
spinner.start(`Creating shared component: ${componentName}...`);
|
|
8504
8529
|
const { createAIProvider: createAIProvider2 } = await import("./ai-provider-CGSIYFZT.js");
|
|
8505
8530
|
const { generateSharedComponent: generateSharedComponent7 } = await import("@getcoherent/core");
|
|
8506
|
-
const { autoFixCode: autoFixCode2 } = await import("./quality-validator-
|
|
8531
|
+
const { autoFixCode: autoFixCode2 } = await import("./quality-validator-P572ZUW5.js");
|
|
8507
8532
|
const { extractPropsInterface, extractDependencies } = await import("./component-extractor-VYJLT5NR.js");
|
|
8508
8533
|
const aiProvider = await createAIProvider2(provider ?? "auto");
|
|
8509
8534
|
const prompt = `Generate a React component called "${componentName}". Description: ${message}.
|