@getcoherent/cli 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.js +141 -118
- package/package.json +10 -10
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sergei Kovtun
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.js
CHANGED
|
@@ -698,10 +698,7 @@ function generateWelcomeComponent(_markdown) {
|
|
|
698
698
|
import { useState } from 'react'
|
|
699
699
|
import {
|
|
700
700
|
ArrowRight,
|
|
701
|
-
Blocks,
|
|
702
701
|
ClipboardCopy,
|
|
703
|
-
Eye,
|
|
704
|
-
Globe,
|
|
705
702
|
LayoutDashboard,
|
|
706
703
|
LogIn,
|
|
707
704
|
Monitor,
|
|
@@ -709,7 +706,6 @@ import {
|
|
|
709
706
|
Rocket,
|
|
710
707
|
Settings,
|
|
711
708
|
ShoppingBag,
|
|
712
|
-
Sparkles,
|
|
713
709
|
} from 'lucide-react'
|
|
714
710
|
|
|
715
711
|
export default function HomePage() {
|
|
@@ -1020,47 +1016,7 @@ export default function HomePage() {
|
|
|
1020
1016
|
</div>
|
|
1021
1017
|
</section>
|
|
1022
1018
|
|
|
1023
|
-
|
|
1024
|
-
<div className="border-t pt-8 pb-8 mt-auto">
|
|
1025
|
-
<div className="flex flex-col items-center gap-5 px-4">
|
|
1026
|
-
<div className="flex items-center gap-3">
|
|
1027
|
-
<div className="flex size-8 items-center justify-center rounded-lg bg-primary text-primary-foreground shrink-0">
|
|
1028
|
-
<Blocks className="size-4" />
|
|
1029
|
-
</div>
|
|
1030
|
-
<span className="text-sm font-semibold">Coherent Design Method</span>
|
|
1031
|
-
</div>
|
|
1032
|
-
<div className="flex flex-wrap items-center justify-center gap-x-6 gap-y-2">
|
|
1033
|
-
<a
|
|
1034
|
-
href="https://getcoherent.design"
|
|
1035
|
-
target="_blank"
|
|
1036
|
-
rel="noopener noreferrer"
|
|
1037
|
-
className="text-xs text-muted-foreground hover:text-foreground transition-colors"
|
|
1038
|
-
>
|
|
1039
|
-
getcoherent.design
|
|
1040
|
-
</a>
|
|
1041
|
-
<a href="https://github.com/skovtun/coherent-design-method" target="_blank" rel="noopener noreferrer" className="text-xs text-muted-foreground hover:text-foreground transition-colors">
|
|
1042
|
-
GitHub
|
|
1043
|
-
</a>
|
|
1044
|
-
<a href="#" className="text-xs text-muted-foreground hover:text-foreground transition-colors">
|
|
1045
|
-
Terms of Use
|
|
1046
|
-
</a>
|
|
1047
|
-
<a href="#" className="text-xs text-muted-foreground hover:text-foreground transition-colors">
|
|
1048
|
-
Privacy Policy
|
|
1049
|
-
</a>
|
|
1050
|
-
</div>
|
|
1051
|
-
<p className="text-xs text-muted-foreground">
|
|
1052
|
-
\xA9 {new Date().getFullYear()}{' '}
|
|
1053
|
-
<a
|
|
1054
|
-
href="https://www.linkedin.com/in/sergeikovtun/"
|
|
1055
|
-
target="_blank"
|
|
1056
|
-
rel="noopener noreferrer"
|
|
1057
|
-
className="underline underline-offset-2 hover:text-foreground transition-colors"
|
|
1058
|
-
>
|
|
1059
|
-
Sergei Kovtun
|
|
1060
|
-
</a>
|
|
1061
|
-
</p>
|
|
1062
|
-
</div>
|
|
1063
|
-
</div>
|
|
1019
|
+
|
|
1064
1020
|
</div>
|
|
1065
1021
|
)
|
|
1066
1022
|
}
|
|
@@ -2459,7 +2415,7 @@ async function createAppRouteGroupLayout(projectPath) {
|
|
|
2459
2415
|
children: React.ReactNode
|
|
2460
2416
|
}) {
|
|
2461
2417
|
return (
|
|
2462
|
-
<main className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
|
|
2418
|
+
<main className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
|
|
2463
2419
|
{children}
|
|
2464
2420
|
</main>
|
|
2465
2421
|
)
|
|
@@ -2479,7 +2435,7 @@ import {
|
|
|
2479
2435
|
PageManager as PageManager3,
|
|
2480
2436
|
CLI_VERSION as CLI_VERSION2,
|
|
2481
2437
|
getTemplateForPageType as getTemplateForPageType2,
|
|
2482
|
-
loadManifest as
|
|
2438
|
+
loadManifest as loadManifest7,
|
|
2483
2439
|
saveManifest as saveManifest2
|
|
2484
2440
|
} from "@getcoherent/core";
|
|
2485
2441
|
|
|
@@ -4534,15 +4490,19 @@ LAYOUT CONTRACT (CRITICAL \u2014 prevents duplicate navigation and footer):
|
|
|
4534
4490
|
- Do NOT add any navigation bars, logo headers, site-wide menus, or site footers to pages. The layout provides all of these.
|
|
4535
4491
|
|
|
4536
4492
|
PAGE WRAPPER (CRITICAL \u2014 the layout provides width/padding automatically):
|
|
4537
|
-
- App pages
|
|
4538
|
-
-
|
|
4539
|
-
-
|
|
4540
|
-
-
|
|
4493
|
+
- App pages are rendered inside a route group layout that ALREADY provides: <main className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
|
|
4494
|
+
- Your outermost element MUST be exactly: <div className="space-y-6">
|
|
4495
|
+
- FORBIDDEN on the outermost element: <main>, max-w-*, mx-auto, px-*, py-*, p-*, flex-1, min-h-*
|
|
4496
|
+
- FORBIDDEN anywhere: <div className="max-w-4xl mx-auto">, <div className="max-w-2xl mx-auto">, or any inner centering wrapper
|
|
4497
|
+
- The first child inside <div className="space-y-6"> should be the page header (h1 + description)
|
|
4498
|
+
- ALL app pages must follow this exact same structure so content aligns consistently across pages
|
|
4541
4499
|
- Landing/marketing pages are an exception: they render outside the app layout and should use full-width <section> elements with inner "mx-auto max-w-6xl" for content.
|
|
4542
4500
|
|
|
4543
4501
|
PAGE CONTENT (CRITICAL \u2014 prevents empty or duplicate pages):
|
|
4544
4502
|
- Every page MUST have substantial content. NEVER generate a page with only metadata and an empty <main> element.
|
|
4545
4503
|
- NEVER create an inline preview/demo of another page (e.g., embedding a "dashboard view" inside the landing page with a toggle). Each page should be its own route.
|
|
4504
|
+
- NEVER create a single-page app (SPA) that renders multiple views via useState. Each view must be a separate Next.js page with its own route.
|
|
4505
|
+
- The home page (route "/") should be a simple redirect using next/navigation redirect('/dashboard') \u2014 OR a standalone landing page. NEVER a multi-view SPA.
|
|
4546
4506
|
- Landing pages should link to app pages via <Link href="/dashboard">, NOT via useState toggles that render inline content.
|
|
4547
4507
|
|
|
4548
4508
|
pageCode rules (shadcn/ui blocks quality):
|
|
@@ -5930,7 +5890,7 @@ import { dirname as dirname6 } from "path";
|
|
|
5930
5890
|
import chalk11 from "chalk";
|
|
5931
5891
|
import {
|
|
5932
5892
|
getTemplateForPageType,
|
|
5933
|
-
loadManifest as
|
|
5893
|
+
loadManifest as loadManifest5,
|
|
5934
5894
|
saveManifest,
|
|
5935
5895
|
updateUsedIn,
|
|
5936
5896
|
findSharedComponentByIdOrName,
|
|
@@ -6724,7 +6684,7 @@ import {
|
|
|
6724
6684
|
PageGenerator,
|
|
6725
6685
|
TailwindConfigGenerator
|
|
6726
6686
|
} from "@getcoherent/core";
|
|
6727
|
-
import { integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout2,
|
|
6687
|
+
import { integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout2, generateSharedComponent as generateSharedComponent2 } from "@getcoherent/core";
|
|
6728
6688
|
import chalk9 from "chalk";
|
|
6729
6689
|
async function validateAndFixGeneratedCode(projectRoot, code, options = {}) {
|
|
6730
6690
|
const fixes = [];
|
|
@@ -6795,34 +6755,39 @@ async function regenerateLayout(config2, projectRoot) {
|
|
|
6795
6755
|
const layout = config2.pages[0]?.layout || "centered";
|
|
6796
6756
|
const appType = config2.settings.appType || "multi-page";
|
|
6797
6757
|
const generator = new PageGenerator(config2);
|
|
6798
|
-
let manifest = null;
|
|
6799
|
-
try {
|
|
6800
|
-
manifest = await loadManifest5(projectRoot);
|
|
6801
|
-
} catch {
|
|
6802
|
-
}
|
|
6803
|
-
const hasSharedHeader = manifest?.shared.some((c) => c.type === "layout" && /header|nav/i.test(c.name)) ?? false;
|
|
6804
|
-
const hasSharedFooter = manifest?.shared.some((c) => c.type === "layout" && /footer/i.test(c.name)) ?? false;
|
|
6805
6758
|
const code = await generator.generateLayout(layout, appType, { skipNav: true });
|
|
6806
6759
|
const layoutPath = resolve6(projectRoot, "app", "layout.tsx");
|
|
6807
6760
|
await writeFile(layoutPath, code);
|
|
6808
6761
|
if (config2.navigation?.enabled && appType === "multi-page") {
|
|
6809
|
-
const
|
|
6762
|
+
const navType = config2.navigation.type || "header";
|
|
6763
|
+
if (navType === "header" || navType === "both") {
|
|
6764
|
+
const headerCode = generator.generateSharedHeaderCode();
|
|
6765
|
+
await generateSharedComponent2(projectRoot, {
|
|
6766
|
+
name: "Header",
|
|
6767
|
+
type: "layout",
|
|
6768
|
+
code: headerCode,
|
|
6769
|
+
description: "Main site header with navigation and theme toggle",
|
|
6770
|
+
usedIn: ["app/layout.tsx"],
|
|
6771
|
+
overwrite: true
|
|
6772
|
+
});
|
|
6773
|
+
}
|
|
6774
|
+
const footerCode = generator.generateSharedFooterCode();
|
|
6810
6775
|
await generateSharedComponent2(projectRoot, {
|
|
6811
|
-
name: "
|
|
6776
|
+
name: "Footer",
|
|
6812
6777
|
type: "layout",
|
|
6813
|
-
code:
|
|
6814
|
-
description: "
|
|
6778
|
+
code: footerCode,
|
|
6779
|
+
description: "Site footer",
|
|
6815
6780
|
usedIn: ["app/layout.tsx"],
|
|
6816
6781
|
overwrite: true
|
|
6817
6782
|
});
|
|
6818
|
-
if (
|
|
6819
|
-
const
|
|
6783
|
+
if (navType === "sidebar" || navType === "both") {
|
|
6784
|
+
const sidebarCode = generator.generateSharedSidebarCode();
|
|
6820
6785
|
await generateSharedComponent2(projectRoot, {
|
|
6821
|
-
name: "
|
|
6786
|
+
name: "Sidebar",
|
|
6822
6787
|
type: "layout",
|
|
6823
|
-
code:
|
|
6824
|
-
description: "
|
|
6825
|
-
usedIn: ["app/layout.tsx"],
|
|
6788
|
+
code: sidebarCode,
|
|
6789
|
+
description: "Vertical sidebar navigation with collapsible sections",
|
|
6790
|
+
usedIn: ["app/(app)/layout.tsx"],
|
|
6826
6791
|
overwrite: true
|
|
6827
6792
|
});
|
|
6828
6793
|
}
|
|
@@ -6830,31 +6795,54 @@ async function regenerateLayout(config2, projectRoot) {
|
|
|
6830
6795
|
try {
|
|
6831
6796
|
await integrateSharedLayoutIntoRootLayout2(projectRoot);
|
|
6832
6797
|
await ensureAuthRouteGroup(projectRoot);
|
|
6833
|
-
await ensureAppRouteGroupLayout(projectRoot);
|
|
6798
|
+
await ensureAppRouteGroupLayout(projectRoot, config2.navigation?.type);
|
|
6834
6799
|
} catch (err) {
|
|
6835
6800
|
if (process.env.COHERENT_DEBUG === "1") {
|
|
6836
6801
|
console.log(chalk9.dim("Layout integration warning:", err));
|
|
6837
6802
|
}
|
|
6838
6803
|
}
|
|
6839
6804
|
}
|
|
6840
|
-
async function ensureAppRouteGroupLayout(projectRoot) {
|
|
6805
|
+
async function ensureAppRouteGroupLayout(projectRoot, navType) {
|
|
6841
6806
|
const layoutPath = resolve6(projectRoot, "app", "(app)", "layout.tsx");
|
|
6842
6807
|
if (existsSync14(layoutPath)) return;
|
|
6843
6808
|
const { mkdir: mkdirAsync } = await import("fs/promises");
|
|
6844
6809
|
await mkdirAsync(resolve6(projectRoot, "app", "(app)"), { recursive: true });
|
|
6845
|
-
const code =
|
|
6810
|
+
const code = buildAppLayoutCode(navType);
|
|
6811
|
+
await writeFile(layoutPath, code);
|
|
6812
|
+
}
|
|
6813
|
+
function buildAppLayoutCode(navType) {
|
|
6814
|
+
const hasSidebar = navType === "sidebar" || navType === "both";
|
|
6815
|
+
if (hasSidebar) {
|
|
6816
|
+
return `import { Sidebar } from '@/components/shared/sidebar'
|
|
6817
|
+
|
|
6818
|
+
export default function AppLayout({
|
|
6819
|
+
children,
|
|
6820
|
+
}: {
|
|
6821
|
+
children: React.ReactNode
|
|
6822
|
+
}) {
|
|
6823
|
+
return (
|
|
6824
|
+
<div className="flex min-h-[calc(100vh-3.5rem)]">
|
|
6825
|
+
<Sidebar />
|
|
6826
|
+
<main className="flex-1 px-4 sm:px-6 lg:px-8 py-6">
|
|
6827
|
+
{children}
|
|
6828
|
+
</main>
|
|
6829
|
+
</div>
|
|
6830
|
+
)
|
|
6831
|
+
}
|
|
6832
|
+
`;
|
|
6833
|
+
}
|
|
6834
|
+
return `export default function AppLayout({
|
|
6846
6835
|
children,
|
|
6847
6836
|
}: {
|
|
6848
6837
|
children: React.ReactNode
|
|
6849
6838
|
}) {
|
|
6850
6839
|
return (
|
|
6851
|
-
<main className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
|
|
6840
|
+
<main className="mx-auto w-full max-w-7xl px-4 sm:px-6 lg:px-8 py-6">
|
|
6852
6841
|
{children}
|
|
6853
6842
|
</main>
|
|
6854
6843
|
)
|
|
6855
6844
|
}
|
|
6856
6845
|
`;
|
|
6857
|
-
await writeFile(layoutPath, code);
|
|
6858
6846
|
}
|
|
6859
6847
|
async function regenerateFiles(modified, config2, projectRoot) {
|
|
6860
6848
|
const componentIds = /* @__PURE__ */ new Set();
|
|
@@ -7189,26 +7177,41 @@ function stripInlineLayoutElements(code) {
|
|
|
7189
7177
|
}
|
|
7190
7178
|
return { code: result, stripped };
|
|
7191
7179
|
}
|
|
7192
|
-
|
|
7193
|
-
|
|
7194
|
-
|
|
7195
|
-
|
|
7196
|
-
|
|
7197
|
-
|
|
7198
|
-
|
|
7199
|
-
|
|
7200
|
-
|
|
7201
|
-
|
|
7202
|
-
|
|
7180
|
+
var STANDARD_PAGE_WRAPPER = "space-y-6";
|
|
7181
|
+
var HOME_REDIRECT_CODE = `import { redirect } from 'next/navigation'
|
|
7182
|
+
|
|
7183
|
+
export default function Home() {
|
|
7184
|
+
redirect('/dashboard')
|
|
7185
|
+
}
|
|
7186
|
+
`;
|
|
7187
|
+
function detectAndFixSpaHomePage(code, route) {
|
|
7188
|
+
if (route !== "/" && route !== "") return { code, fixed: false };
|
|
7189
|
+
const hasMultipleRenders = (code.match(/const render\w+\s*=\s*\(\)/g) || []).length >= 2;
|
|
7190
|
+
const hasPageToggle = /useState\s*\(\s*['"](?:dashboard|home|page)/i.test(code);
|
|
7191
|
+
const isMassive = code.split("\n").length > 200;
|
|
7192
|
+
if (hasMultipleRenders && hasPageToggle || isMassive && hasPageToggle) {
|
|
7193
|
+
return { code: HOME_REDIRECT_CODE, fixed: true };
|
|
7203
7194
|
}
|
|
7195
|
+
return { code, fixed: false };
|
|
7196
|
+
}
|
|
7197
|
+
function normalizePageWrapper(code) {
|
|
7204
7198
|
let result = code;
|
|
7205
|
-
let
|
|
7199
|
+
let fixed = false;
|
|
7206
7200
|
if (/<main\s+className="[^"]*">/.test(result)) {
|
|
7207
|
-
result = result.replace(/<main\s+className="[^"]*"
|
|
7201
|
+
result = result.replace(/<main\s+className="[^"]*">/g, `<div className="${STANDARD_PAGE_WRAPPER}">`);
|
|
7208
7202
|
result = result.replace(/<\/main>/g, "</div>");
|
|
7209
|
-
|
|
7203
|
+
fixed = true;
|
|
7210
7204
|
}
|
|
7211
|
-
|
|
7205
|
+
const outerDivRe = /(return\s*\(\s*\n?\s*)<div\s+className="([^"]*)">/;
|
|
7206
|
+
const match = result.match(outerDivRe);
|
|
7207
|
+
if (match) {
|
|
7208
|
+
const cls = match[2];
|
|
7209
|
+
if (cls !== STANDARD_PAGE_WRAPPER) {
|
|
7210
|
+
result = result.replace(match[0], `${match[1]}<div className="${STANDARD_PAGE_WRAPPER}">`);
|
|
7211
|
+
fixed = true;
|
|
7212
|
+
}
|
|
7213
|
+
}
|
|
7214
|
+
return { code: result, fixed };
|
|
7212
7215
|
}
|
|
7213
7216
|
async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider, originalMessage) {
|
|
7214
7217
|
switch (request.type) {
|
|
@@ -7326,7 +7329,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7326
7329
|
fixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
7327
7330
|
}
|
|
7328
7331
|
await writeFile(pageFilePath, fixedCode);
|
|
7329
|
-
const manifest = await
|
|
7332
|
+
const manifest = await loadManifest5(projectRoot);
|
|
7330
7333
|
const usedIn = manifest.shared.find((e) => e.id === resolved.id)?.usedIn ?? [];
|
|
7331
7334
|
const routePath = route.replace(/^\//, "");
|
|
7332
7335
|
const filePathRel = routePath ? `app/${routePath}/page.tsx` : "app/page.tsx";
|
|
@@ -7431,7 +7434,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7431
7434
|
await writeFile(fullPath, fixedCode);
|
|
7432
7435
|
usedInFiles.push(relPath);
|
|
7433
7436
|
}
|
|
7434
|
-
const manifest = await
|
|
7437
|
+
const manifest = await loadManifest5(projectRoot);
|
|
7435
7438
|
const nextManifest = updateUsedIn(manifest, created.id, usedInFiles);
|
|
7436
7439
|
await saveManifest(projectRoot, nextManifest);
|
|
7437
7440
|
printPromoteAndLinkReport({
|
|
@@ -7541,7 +7544,11 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7541
7544
|
console.log(chalk11.yellow(`
|
|
7542
7545
|
\u26A0\uFE0F Page "${page.name || page.id}" has no generated code \u2014 it will appear empty.`));
|
|
7543
7546
|
console.log(chalk11.dim(" This usually means the AI did not produce pageCode for this page."));
|
|
7544
|
-
console.log(
|
|
7547
|
+
console.log(
|
|
7548
|
+
chalk11.dim(
|
|
7549
|
+
' Try running: coherent chat "regenerate the ' + (page.name || page.id) + ' page with full content"'
|
|
7550
|
+
)
|
|
7551
|
+
);
|
|
7545
7552
|
}
|
|
7546
7553
|
const pageForConfig = {
|
|
7547
7554
|
...page,
|
|
@@ -7582,11 +7589,19 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7582
7589
|
let codeToWrite = fixedCode;
|
|
7583
7590
|
const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite);
|
|
7584
7591
|
codeToWrite = autoFixed;
|
|
7592
|
+
const { code: spaFixed, fixed: spaWasFixed } = detectAndFixSpaHomePage(codeToWrite, route);
|
|
7593
|
+
if (spaWasFixed) {
|
|
7594
|
+
codeToWrite = spaFixed;
|
|
7595
|
+
autoFixes.push("replaced SPA-style home page with redirect to /dashboard");
|
|
7596
|
+
}
|
|
7585
7597
|
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
7586
7598
|
codeToWrite = layoutStripped;
|
|
7587
7599
|
if (!isMarketingRoute(route)) {
|
|
7588
|
-
const { code:
|
|
7589
|
-
if (
|
|
7600
|
+
const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
|
|
7601
|
+
if (wrapperFixed) {
|
|
7602
|
+
codeToWrite = normalized;
|
|
7603
|
+
autoFixes.push("normalized page wrapper to standard spacing");
|
|
7604
|
+
}
|
|
7590
7605
|
}
|
|
7591
7606
|
const allFixes = [...postFixes, ...autoFixes];
|
|
7592
7607
|
if (stripped.length > 0) allFixes.push(`stripped inline ${stripped.join(", ")} (layout owns these)`);
|
|
@@ -7603,7 +7618,7 @@ async function applyModification(request, dsm, cm, pm, projectRoot, aiProvider,
|
|
|
7603
7618
|
cm.updateConfig(cfg);
|
|
7604
7619
|
pm.updateConfig(cfg);
|
|
7605
7620
|
}
|
|
7606
|
-
const manifestForAudit = await
|
|
7621
|
+
const manifestForAudit = await loadManifest5(projectRoot);
|
|
7607
7622
|
await warnInlineDuplicates(projectRoot, page.name || page.id || route.slice(1), codeToWrite, manifestForAudit);
|
|
7608
7623
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
7609
7624
|
printPostGenerationReport({
|
|
@@ -7753,11 +7768,19 @@ ${pagesCtx}`
|
|
|
7753
7768
|
let codeToWrite = fixedCode;
|
|
7754
7769
|
const { code: autoFixed, fixes: autoFixes } = await autoFixCode(codeToWrite);
|
|
7755
7770
|
codeToWrite = autoFixed;
|
|
7771
|
+
const { code: spaFixed, fixed: spaWasFixed } = detectAndFixSpaHomePage(codeToWrite, route);
|
|
7772
|
+
if (spaWasFixed) {
|
|
7773
|
+
codeToWrite = spaFixed;
|
|
7774
|
+
autoFixes.push("replaced SPA-style home page with redirect to /dashboard");
|
|
7775
|
+
}
|
|
7756
7776
|
const { code: layoutStripped, stripped } = stripInlineLayoutElements(codeToWrite);
|
|
7757
7777
|
codeToWrite = layoutStripped;
|
|
7758
7778
|
if (!isMarketingRoute(route)) {
|
|
7759
|
-
const { code:
|
|
7760
|
-
if (
|
|
7779
|
+
const { code: normalized, fixed: wrapperFixed } = normalizePageWrapper(codeToWrite);
|
|
7780
|
+
if (wrapperFixed) {
|
|
7781
|
+
codeToWrite = normalized;
|
|
7782
|
+
autoFixes.push("normalized page wrapper to standard spacing");
|
|
7783
|
+
}
|
|
7761
7784
|
}
|
|
7762
7785
|
const allFixes = [...postFixes, ...autoFixes];
|
|
7763
7786
|
if (stripped.length > 0) allFixes.push(`stripped inline ${stripped.join(", ")} (layout owns these)`);
|
|
@@ -7774,7 +7797,7 @@ ${pagesCtx}`
|
|
|
7774
7797
|
cm.updateConfig(cfg);
|
|
7775
7798
|
pm.updateConfig(cfg);
|
|
7776
7799
|
}
|
|
7777
|
-
const manifestForAudit = await
|
|
7800
|
+
const manifestForAudit = await loadManifest5(projectRoot);
|
|
7778
7801
|
await warnInlineDuplicates(
|
|
7779
7802
|
projectRoot,
|
|
7780
7803
|
pageDef.name || pageDef.id || route.slice(1),
|
|
@@ -7811,7 +7834,7 @@ ${pagesCtx}`
|
|
|
7811
7834
|
fixes.forEach((f) => console.log(chalk11.dim(` ${f}`)));
|
|
7812
7835
|
}
|
|
7813
7836
|
const relFilePath = routeToRelPath(route, isAuth);
|
|
7814
|
-
const manifest = await
|
|
7837
|
+
const manifest = await loadManifest5(projectRoot);
|
|
7815
7838
|
printPostGenerationReport({
|
|
7816
7839
|
action: "updated",
|
|
7817
7840
|
pageTitle: pageDef.name || pageDef.id || "Page",
|
|
@@ -7860,7 +7883,7 @@ ${pagesCtx}`
|
|
|
7860
7883
|
import chalk12 from "chalk";
|
|
7861
7884
|
import { resolve as resolve8 } from "path";
|
|
7862
7885
|
import { existsSync as existsSync15, readFileSync as readFileSync9, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
7863
|
-
import { DesignSystemManager as DesignSystemManager6, ComponentManager as ComponentManager3, loadManifest as
|
|
7886
|
+
import { DesignSystemManager as DesignSystemManager6, ComponentManager as ComponentManager3, loadManifest as loadManifest6 } from "@getcoherent/core";
|
|
7864
7887
|
var DEBUG3 = process.env.COHERENT_DEBUG === "1";
|
|
7865
7888
|
async function interactiveChat(options, chatCommandFn) {
|
|
7866
7889
|
const { createInterface } = await import("readline");
|
|
@@ -7930,7 +7953,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
7930
7953
|
return;
|
|
7931
7954
|
}
|
|
7932
7955
|
if (lower === "components" || lower === "list components" || lower.includes("what components")) {
|
|
7933
|
-
const manifest = await
|
|
7956
|
+
const manifest = await loadManifest6(projectRoot);
|
|
7934
7957
|
if (manifest.shared.length === 0) {
|
|
7935
7958
|
console.log(chalk12.gray("\n No shared components yet.\n"));
|
|
7936
7959
|
} else {
|
|
@@ -7964,7 +7987,7 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
7964
7987
|
}
|
|
7965
7988
|
if (lower === "status") {
|
|
7966
7989
|
const currentConfig = dsm.getConfig();
|
|
7967
|
-
const manifest = await
|
|
7990
|
+
const manifest = await loadManifest6(projectRoot);
|
|
7968
7991
|
console.log(chalk12.bold(`
|
|
7969
7992
|
${currentConfig.name || "Coherent Project"}`));
|
|
7970
7993
|
console.log(
|
|
@@ -8146,7 +8169,7 @@ async function chatCommand(message, options) {
|
|
|
8146
8169
|
}
|
|
8147
8170
|
}
|
|
8148
8171
|
spinner.start("Parsing your request...");
|
|
8149
|
-
let manifest = await
|
|
8172
|
+
let manifest = await loadManifest7(project.root);
|
|
8150
8173
|
const validShared = manifest.shared.filter((s) => {
|
|
8151
8174
|
const fp = resolve9(project.root, s.file);
|
|
8152
8175
|
return existsSync16(fp);
|
|
@@ -8717,7 +8740,7 @@ import { DesignSystemManager as DesignSystemManager8, ComponentGenerator as Comp
|
|
|
8717
8740
|
// src/utils/file-watcher.ts
|
|
8718
8741
|
import { readFileSync as readFileSync12, writeFileSync as writeFileSync9, existsSync as existsSync18 } from "fs";
|
|
8719
8742
|
import { relative as relative3, join as join10 } from "path";
|
|
8720
|
-
import { loadManifest as
|
|
8743
|
+
import { loadManifest as loadManifest8, saveManifest as saveManifest3 } from "@getcoherent/core";
|
|
8721
8744
|
|
|
8722
8745
|
// src/utils/component-integrity.ts
|
|
8723
8746
|
import { existsSync as existsSync17, readFileSync as readFileSync11, readdirSync as readdirSync2 } from "fs";
|
|
@@ -9071,7 +9094,7 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
9071
9094
|
if (config2.warnSharedReuse) {
|
|
9072
9095
|
let manifest;
|
|
9073
9096
|
try {
|
|
9074
|
-
manifest = await
|
|
9097
|
+
manifest = await loadManifest8(projectRoot);
|
|
9075
9098
|
} catch {
|
|
9076
9099
|
manifest = { shared: [], nextId: 1 };
|
|
9077
9100
|
}
|
|
@@ -9090,7 +9113,7 @@ async function handleFileDelete(projectRoot, filePath) {
|
|
|
9090
9113
|
if (!relativePath.startsWith("components/") || relativePath.startsWith("components/ui/")) return;
|
|
9091
9114
|
try {
|
|
9092
9115
|
const chalk32 = (await import("chalk")).default;
|
|
9093
|
-
const manifest = await
|
|
9116
|
+
const manifest = await loadManifest8(projectRoot);
|
|
9094
9117
|
const orphaned = manifest.shared.find((s) => s.file === relativePath);
|
|
9095
9118
|
if (orphaned) {
|
|
9096
9119
|
const cleaned = {
|
|
@@ -9111,7 +9134,7 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
9111
9134
|
if (!relativePath.endsWith(".tsx") && !relativePath.endsWith(".jsx")) return;
|
|
9112
9135
|
try {
|
|
9113
9136
|
const chalk32 = (await import("chalk")).default;
|
|
9114
|
-
const manifest = await
|
|
9137
|
+
const manifest = await loadManifest8(projectRoot);
|
|
9115
9138
|
const alreadyRegistered = manifest.shared.some((s) => s.file === relativePath);
|
|
9116
9139
|
if (alreadyRegistered) return;
|
|
9117
9140
|
const code = readFileSync12(filePath, "utf-8");
|
|
@@ -10028,7 +10051,7 @@ import {
|
|
|
10028
10051
|
ComponentManager as ComponentManager5,
|
|
10029
10052
|
PageManager as PageManager4,
|
|
10030
10053
|
ComponentGenerator as ComponentGenerator4,
|
|
10031
|
-
loadManifest as
|
|
10054
|
+
loadManifest as loadManifest9,
|
|
10032
10055
|
saveManifest as saveManifest4
|
|
10033
10056
|
} from "@getcoherent/core";
|
|
10034
10057
|
function extractComponentIdsFromCode2(code) {
|
|
@@ -10235,7 +10258,7 @@ async function fixCommand(opts = {}) {
|
|
|
10235
10258
|
fileIssues.push({ path: relativePath, report });
|
|
10236
10259
|
}
|
|
10237
10260
|
try {
|
|
10238
|
-
let manifest = await
|
|
10261
|
+
let manifest = await loadManifest9(project.root);
|
|
10239
10262
|
let manifestModified = false;
|
|
10240
10263
|
const { manifest: cleaned, removed: orphaned } = removeOrphanedEntries(project.root, manifest);
|
|
10241
10264
|
if (orphaned.length > 0) {
|
|
@@ -10335,7 +10358,7 @@ async function fixCommand(opts = {}) {
|
|
|
10335
10358
|
import chalk19 from "chalk";
|
|
10336
10359
|
import { resolve as resolve13 } from "path";
|
|
10337
10360
|
import { readdirSync as readdirSync5, readFileSync as readFileSync15, statSync as statSync2, existsSync as existsSync22 } from "fs";
|
|
10338
|
-
import { loadManifest as
|
|
10361
|
+
import { loadManifest as loadManifest10 } from "@getcoherent/core";
|
|
10339
10362
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", "design-system"]);
|
|
10340
10363
|
function findTsxFiles(dir) {
|
|
10341
10364
|
const results = [];
|
|
@@ -10462,7 +10485,7 @@ async function checkCommand(opts = {}) {
|
|
|
10462
10485
|
\u{1F517} Internal Links`) + chalk19.dim(` \u2014 all ${result.links.total} links resolve \u2713`));
|
|
10463
10486
|
}
|
|
10464
10487
|
try {
|
|
10465
|
-
const manifest = await
|
|
10488
|
+
const manifest = await loadManifest10(project.root);
|
|
10466
10489
|
if (manifest.shared.length > 0) {
|
|
10467
10490
|
for (const entry of manifest.shared) {
|
|
10468
10491
|
const fullPath = resolve13(project.root, entry.file);
|
|
@@ -10478,7 +10501,7 @@ async function checkCommand(opts = {}) {
|
|
|
10478
10501
|
}
|
|
10479
10502
|
if (!skipShared) {
|
|
10480
10503
|
try {
|
|
10481
|
-
const manifest = await
|
|
10504
|
+
const manifest = await loadManifest10(projectRoot);
|
|
10482
10505
|
if (!opts.json && manifest.shared.length > 0) {
|
|
10483
10506
|
console.log(chalk19.cyan(`
|
|
10484
10507
|
\u{1F9E9} Shared Components`) + chalk19.dim(` (${manifest.shared.length} registered)
|
|
@@ -10660,7 +10683,7 @@ import chalk25 from "chalk";
|
|
|
10660
10683
|
import {
|
|
10661
10684
|
DesignSystemManager as DesignSystemManager12,
|
|
10662
10685
|
ComponentManager as ComponentManager6,
|
|
10663
|
-
loadManifest as
|
|
10686
|
+
loadManifest as loadManifest11,
|
|
10664
10687
|
generateSharedComponent as generateSharedComponent4,
|
|
10665
10688
|
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout3
|
|
10666
10689
|
} from "@getcoherent/core";
|
|
@@ -10701,7 +10724,7 @@ function createComponentsCommand() {
|
|
|
10701
10724
|
await dsm.load();
|
|
10702
10725
|
const config2 = dsm.getConfig();
|
|
10703
10726
|
const cm = new ComponentManager6(config2);
|
|
10704
|
-
const manifest = await
|
|
10727
|
+
const manifest = await loadManifest11(project.root);
|
|
10705
10728
|
if (opts.json) {
|
|
10706
10729
|
const installed2 = cm.getAllComponents();
|
|
10707
10730
|
console.log(JSON.stringify({ shared: manifest.shared, ui: installed2 }, null, 2));
|
|
@@ -10753,7 +10776,7 @@ function createComponentsCommand() {
|
|
|
10753
10776
|
sharedCmd.option("--json", "Machine-readable JSON output").option("--verbose", "Show file paths and usage details").action(async (opts) => {
|
|
10754
10777
|
const project = findConfig();
|
|
10755
10778
|
if (!project) exitNotCoherent();
|
|
10756
|
-
const manifest = await
|
|
10779
|
+
const manifest = await loadManifest11(project.root);
|
|
10757
10780
|
if (opts.json) {
|
|
10758
10781
|
console.log(JSON.stringify(manifest, null, 2));
|
|
10759
10782
|
return;
|
|
@@ -11457,7 +11480,7 @@ import { existsSync as existsSync26, readFileSync as readFileSync17 } from "fs";
|
|
|
11457
11480
|
import { join as join17, relative as relative4, dirname as dirname10 } from "path";
|
|
11458
11481
|
import { readdir as readdir4, readFile as readFile6 } from "fs/promises";
|
|
11459
11482
|
import { DesignSystemManager as DesignSystemManager16 } from "@getcoherent/core";
|
|
11460
|
-
import { loadManifest as
|
|
11483
|
+
import { loadManifest as loadManifest12, saveManifest as saveManifest5, findSharedComponent } from "@getcoherent/core";
|
|
11461
11484
|
function extractTokensFromProject(projectRoot) {
|
|
11462
11485
|
const lightColors = {};
|
|
11463
11486
|
const darkColors = {};
|
|
@@ -11737,7 +11760,7 @@ async function syncCommand(options = {}) {
|
|
|
11737
11760
|
let reconcileResult = null;
|
|
11738
11761
|
if (doComponents) {
|
|
11739
11762
|
spinner.start("Reconciling shared components...");
|
|
11740
|
-
const manifest = await
|
|
11763
|
+
const manifest = await loadManifest12(project.root);
|
|
11741
11764
|
const { manifest: reconciledManifest, result: rr } = reconcileComponents(project.root, manifest);
|
|
11742
11765
|
reconcileResult = rr;
|
|
11743
11766
|
if (!dryRun) {
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.5.1",
|
|
7
7
|
"description": "CLI interface for Coherent Design Method",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.js",
|
|
@@ -33,15 +33,8 @@
|
|
|
33
33
|
],
|
|
34
34
|
"author": "Coherent Design Method",
|
|
35
35
|
"license": "MIT",
|
|
36
|
-
"scripts": {
|
|
37
|
-
"dev": "tsup --watch",
|
|
38
|
-
"build": "tsup",
|
|
39
|
-
"typecheck": "tsc --noEmit",
|
|
40
|
-
"test": "vitest"
|
|
41
|
-
},
|
|
42
36
|
"dependencies": {
|
|
43
37
|
"@anthropic-ai/sdk": "^0.32.0",
|
|
44
|
-
"@getcoherent/core": "workspace:*",
|
|
45
38
|
"chalk": "^5.3.0",
|
|
46
39
|
"chokidar": "^4.0.1",
|
|
47
40
|
"commander": "^11.1.0",
|
|
@@ -49,12 +42,19 @@
|
|
|
49
42
|
"open": "^10.1.0",
|
|
50
43
|
"ora": "^7.0.1",
|
|
51
44
|
"prompts": "^2.4.2",
|
|
52
|
-
"zod": "^3.22.4"
|
|
45
|
+
"zod": "^3.22.4",
|
|
46
|
+
"@getcoherent/core": "0.5.1"
|
|
53
47
|
},
|
|
54
48
|
"devDependencies": {
|
|
55
49
|
"@types/node": "^20.11.0",
|
|
56
50
|
"@types/prompts": "^2.4.9",
|
|
57
51
|
"tsup": "^8.0.1",
|
|
58
52
|
"typescript": "^5.3.3"
|
|
53
|
+
},
|
|
54
|
+
"scripts": {
|
|
55
|
+
"dev": "tsup --watch",
|
|
56
|
+
"build": "tsup",
|
|
57
|
+
"typecheck": "tsc --noEmit",
|
|
58
|
+
"test": "vitest"
|
|
59
59
|
}
|
|
60
|
-
}
|
|
60
|
+
}
|