@getcoherent/cli 0.6.25 → 0.6.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -2,21 +2,24 @@ import {
|
|
|
2
2
|
createAIProvider
|
|
3
3
|
} from "./chunk-SPQZBQYY.js";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
COHERENT_REQUIRED_PACKAGES,
|
|
6
|
+
ensureUseClientIfNeeded,
|
|
7
|
+
extractNpmPackagesFromCode,
|
|
8
|
+
findMissingPackages,
|
|
9
|
+
findMissingPackagesInCode,
|
|
10
|
+
fixEscapedClosingQuotes,
|
|
11
|
+
fixUnescapedLtInJsx,
|
|
12
12
|
generateArchitecturePlan,
|
|
13
|
+
getInstalledPackages,
|
|
13
14
|
getPageGroup,
|
|
14
15
|
getPageType,
|
|
16
|
+
installPackages,
|
|
15
17
|
loadPlan,
|
|
16
18
|
routeToKey,
|
|
19
|
+
sanitizeMetadataStrings,
|
|
17
20
|
savePlan,
|
|
18
21
|
updateArchitecturePlan
|
|
19
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-CC3ARMGS.js";
|
|
20
23
|
import {
|
|
21
24
|
CORE_CONSTRAINTS,
|
|
22
25
|
DESIGN_QUALITY,
|
|
@@ -28,6 +31,13 @@ import {
|
|
|
28
31
|
inferPageTypeFromRoute,
|
|
29
32
|
selectContextualRules
|
|
30
33
|
} from "./chunk-5AHG4NNX.js";
|
|
34
|
+
import {
|
|
35
|
+
autoFixCode,
|
|
36
|
+
checkDesignConsistency,
|
|
37
|
+
formatIssues,
|
|
38
|
+
validatePageQuality,
|
|
39
|
+
verifyIncrementalEdit
|
|
40
|
+
} from "./chunk-H644LLXJ.js";
|
|
31
41
|
import {
|
|
32
42
|
__require
|
|
33
43
|
} from "./chunk-3RG5ZIWI.js";
|
|
@@ -41,9 +51,9 @@ import { CLI_VERSION as CLI_VERSION6 } from "@getcoherent/core";
|
|
|
41
51
|
import chalk4 from "chalk";
|
|
42
52
|
import ora from "ora";
|
|
43
53
|
import prompts2 from "prompts";
|
|
44
|
-
import { existsSync as
|
|
45
|
-
import { join as
|
|
46
|
-
import { execSync
|
|
54
|
+
import { existsSync as existsSync7, readFileSync as readFileSync4, mkdirSync as mkdirSync3, rmSync, writeFileSync as writeFileSync5 } from "fs";
|
|
55
|
+
import { join as join5 } from "path";
|
|
56
|
+
import { execSync } from "child_process";
|
|
47
57
|
|
|
48
58
|
// src/utils/find-config.ts
|
|
49
59
|
import { existsSync } from "fs";
|
|
@@ -1803,222 +1813,6 @@ function formatTimeAgo(iso) {
|
|
|
1803
1813
|
return date.toLocaleDateString();
|
|
1804
1814
|
}
|
|
1805
1815
|
|
|
1806
|
-
// src/utils/self-heal.ts
|
|
1807
|
-
import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
|
|
1808
|
-
import { join as join3 } from "path";
|
|
1809
|
-
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
1810
|
-
import { execSync } from "child_process";
|
|
1811
|
-
var COHERENT_REQUIRED_PACKAGES = [
|
|
1812
|
-
"lucide-react",
|
|
1813
|
-
"class-variance-authority",
|
|
1814
|
-
"clsx",
|
|
1815
|
-
"tailwind-merge",
|
|
1816
|
-
"@radix-ui/react-slot"
|
|
1817
|
-
];
|
|
1818
|
-
var IMPORT_FROM_REGEX = /from\s+['"]([^'"]+)['"]/g;
|
|
1819
|
-
var NODE_BUILTINS = /* @__PURE__ */ new Set([
|
|
1820
|
-
"assert",
|
|
1821
|
-
"async_hooks",
|
|
1822
|
-
"buffer",
|
|
1823
|
-
"child_process",
|
|
1824
|
-
"cluster",
|
|
1825
|
-
"console",
|
|
1826
|
-
"constants",
|
|
1827
|
-
"crypto",
|
|
1828
|
-
"dgram",
|
|
1829
|
-
"diagnostics_channel",
|
|
1830
|
-
"dns",
|
|
1831
|
-
"domain",
|
|
1832
|
-
"events",
|
|
1833
|
-
"fs",
|
|
1834
|
-
"http",
|
|
1835
|
-
"http2",
|
|
1836
|
-
"https",
|
|
1837
|
-
"inspector",
|
|
1838
|
-
"module",
|
|
1839
|
-
"net",
|
|
1840
|
-
"os",
|
|
1841
|
-
"path",
|
|
1842
|
-
"perf_hooks",
|
|
1843
|
-
"process",
|
|
1844
|
-
"punycode",
|
|
1845
|
-
"querystring",
|
|
1846
|
-
"readline",
|
|
1847
|
-
"repl",
|
|
1848
|
-
"stream",
|
|
1849
|
-
"string_decoder",
|
|
1850
|
-
"sys",
|
|
1851
|
-
"test",
|
|
1852
|
-
"timers",
|
|
1853
|
-
"tls",
|
|
1854
|
-
"trace_events",
|
|
1855
|
-
"tty",
|
|
1856
|
-
"url",
|
|
1857
|
-
"util",
|
|
1858
|
-
"v8",
|
|
1859
|
-
"vm",
|
|
1860
|
-
"wasi",
|
|
1861
|
-
"worker_threads",
|
|
1862
|
-
"zlib",
|
|
1863
|
-
"fs/promises",
|
|
1864
|
-
"path/posix",
|
|
1865
|
-
"path/win32",
|
|
1866
|
-
"stream/promises",
|
|
1867
|
-
"stream/web",
|
|
1868
|
-
"timers/promises",
|
|
1869
|
-
"util/types"
|
|
1870
|
-
]);
|
|
1871
|
-
function extractNpmPackagesFromCode(code) {
|
|
1872
|
-
if (typeof code !== "string") return /* @__PURE__ */ new Set();
|
|
1873
|
-
const pkgs = /* @__PURE__ */ new Set();
|
|
1874
|
-
let m;
|
|
1875
|
-
IMPORT_FROM_REGEX.lastIndex = 0;
|
|
1876
|
-
while ((m = IMPORT_FROM_REGEX.exec(code)) !== null) {
|
|
1877
|
-
const spec = m[1];
|
|
1878
|
-
if (spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("@/") || spec === "next") continue;
|
|
1879
|
-
if (spec.startsWith("node:") || NODE_BUILTINS.has(spec)) continue;
|
|
1880
|
-
const pkg = spec.startsWith("@") ? spec.split("/").slice(0, 2).join("/") : spec.split("/")[0];
|
|
1881
|
-
if (pkg && !NODE_BUILTINS.has(pkg)) pkgs.add(pkg);
|
|
1882
|
-
}
|
|
1883
|
-
return pkgs;
|
|
1884
|
-
}
|
|
1885
|
-
function getInstalledPackages(projectRoot) {
|
|
1886
|
-
const pkgPath = join3(projectRoot, "package.json");
|
|
1887
|
-
if (!existsSync4(pkgPath)) return /* @__PURE__ */ new Set();
|
|
1888
|
-
try {
|
|
1889
|
-
const json = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
1890
|
-
const deps = { ...json.dependencies ?? {}, ...json.devDependencies ?? {} };
|
|
1891
|
-
return new Set(Object.keys(deps));
|
|
1892
|
-
} catch (e) {
|
|
1893
|
-
if (process.env.COHERENT_DEBUG === "1") console.error("Failed to read package.json:", e);
|
|
1894
|
-
return /* @__PURE__ */ new Set();
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
async function collectImportedPackages(dir, extensions) {
|
|
1898
|
-
const packages = /* @__PURE__ */ new Set();
|
|
1899
|
-
if (!existsSync4(dir)) return packages;
|
|
1900
|
-
async function walk(d) {
|
|
1901
|
-
let entries;
|
|
1902
|
-
try {
|
|
1903
|
-
entries = await readdir(d, { withFileTypes: true });
|
|
1904
|
-
} catch {
|
|
1905
|
-
return;
|
|
1906
|
-
}
|
|
1907
|
-
for (const e of entries) {
|
|
1908
|
-
const full = join3(d, e.name);
|
|
1909
|
-
if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules") {
|
|
1910
|
-
await walk(full);
|
|
1911
|
-
continue;
|
|
1912
|
-
}
|
|
1913
|
-
if (!e.isFile()) continue;
|
|
1914
|
-
const ext = e.name.replace(/^.*\./, "");
|
|
1915
|
-
if (!extensions.has(ext)) continue;
|
|
1916
|
-
const content = await readFile2(full, "utf-8").catch(() => "");
|
|
1917
|
-
extractNpmPackagesFromCode(content).forEach((p) => packages.add(p));
|
|
1918
|
-
}
|
|
1919
|
-
}
|
|
1920
|
-
await walk(dir);
|
|
1921
|
-
return packages;
|
|
1922
|
-
}
|
|
1923
|
-
async function findMissingPackages(projectRoot, dirs = ["app", "components"]) {
|
|
1924
|
-
const installed = getInstalledPackages(projectRoot);
|
|
1925
|
-
const required = new Set(COHERENT_REQUIRED_PACKAGES);
|
|
1926
|
-
const imported = /* @__PURE__ */ new Set();
|
|
1927
|
-
const extensions = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx"]);
|
|
1928
|
-
for (const d of dirs) {
|
|
1929
|
-
const abs = join3(projectRoot, d);
|
|
1930
|
-
const pkgs = await collectImportedPackages(abs, extensions);
|
|
1931
|
-
pkgs.forEach((p) => imported.add(p));
|
|
1932
|
-
}
|
|
1933
|
-
const needed = /* @__PURE__ */ new Set([...required, ...imported]);
|
|
1934
|
-
return [...needed].filter((p) => !installed.has(p)).sort();
|
|
1935
|
-
}
|
|
1936
|
-
function findMissingPackagesInCode(code, projectRoot) {
|
|
1937
|
-
const installed = getInstalledPackages(projectRoot);
|
|
1938
|
-
const required = new Set(COHERENT_REQUIRED_PACKAGES);
|
|
1939
|
-
const fromCode = extractNpmPackagesFromCode(code);
|
|
1940
|
-
const needed = /* @__PURE__ */ new Set([...required, ...fromCode]);
|
|
1941
|
-
return [...needed].filter((p) => !installed.has(p)).sort();
|
|
1942
|
-
}
|
|
1943
|
-
var SAFE_PKG_NAME = /^(@[a-z0-9._-]+\/)?[a-z0-9._-]+$/;
|
|
1944
|
-
function installPackages(projectRoot, packages) {
|
|
1945
|
-
if (packages.length === 0) return Promise.resolve(true);
|
|
1946
|
-
const safe = packages.filter((p) => SAFE_PKG_NAME.test(p));
|
|
1947
|
-
if (safe.length === 0) return Promise.resolve(true);
|
|
1948
|
-
return new Promise((resolve17) => {
|
|
1949
|
-
try {
|
|
1950
|
-
const hasPnpm = existsSync4(join3(projectRoot, "pnpm-lock.yaml"));
|
|
1951
|
-
if (hasPnpm) {
|
|
1952
|
-
execSync(`pnpm add ${safe.join(" ")}`, { cwd: projectRoot, stdio: "pipe" });
|
|
1953
|
-
} else {
|
|
1954
|
-
execSync(`npm install --legacy-peer-deps ${safe.join(" ")}`, {
|
|
1955
|
-
cwd: projectRoot,
|
|
1956
|
-
stdio: "pipe"
|
|
1957
|
-
});
|
|
1958
|
-
}
|
|
1959
|
-
resolve17(true);
|
|
1960
|
-
} catch (e) {
|
|
1961
|
-
if (process.env.COHERENT_DEBUG === "1") console.error("Failed to install packages:", e);
|
|
1962
|
-
resolve17(false);
|
|
1963
|
-
}
|
|
1964
|
-
});
|
|
1965
|
-
}
|
|
1966
|
-
var CLIENT_HOOKS = /\b(useState|useEffect|useRef|useContext|useReducer|useCallback|useMemo|useId|useTransition|useDeferredValue)\s*\(/;
|
|
1967
|
-
var CLIENT_EVENTS = /\b(onClick|onChange|onSubmit|onBlur|onFocus|onKeyDown|onKeyUp|onMouseEnter|onMouseLeave|onScroll|onInput)\s*[={]/;
|
|
1968
|
-
function stripMetadataFromCode(code) {
|
|
1969
|
-
const match = code.match(/\bexport\s+const\s+metadata\s*:\s*Metadata\s*=\s*\{/);
|
|
1970
|
-
if (!match) return code;
|
|
1971
|
-
const start = code.indexOf(match[0]);
|
|
1972
|
-
const open = code.indexOf("{", start);
|
|
1973
|
-
if (open === -1) return code;
|
|
1974
|
-
let depth = 1;
|
|
1975
|
-
let i = open + 1;
|
|
1976
|
-
while (i < code.length && depth > 0) {
|
|
1977
|
-
const c = code[i];
|
|
1978
|
-
if (c === "{") depth++;
|
|
1979
|
-
else if (c === "}") depth--;
|
|
1980
|
-
i++;
|
|
1981
|
-
}
|
|
1982
|
-
const end = i;
|
|
1983
|
-
const tail = code.slice(end);
|
|
1984
|
-
const semicolon = tail.match(/^\s*;/);
|
|
1985
|
-
const removeEnd = semicolon ? end + (semicolon.index + semicolon[0].length) : end;
|
|
1986
|
-
return (code.slice(0, start) + code.slice(removeEnd)).replace(/\n{3,}/g, "\n\n").trim();
|
|
1987
|
-
}
|
|
1988
|
-
function ensureUseClientIfNeeded(code) {
|
|
1989
|
-
const trimmed = code.trimStart();
|
|
1990
|
-
const hasUseClient = trimmed.startsWith("'use client'") || trimmed.startsWith('"use client"');
|
|
1991
|
-
const needsUseClient = CLIENT_HOOKS.test(code) || CLIENT_EVENTS.test(code);
|
|
1992
|
-
let out = code;
|
|
1993
|
-
if (hasUseClient || needsUseClient) {
|
|
1994
|
-
out = stripMetadataFromCode(out);
|
|
1995
|
-
if (needsUseClient && !hasUseClient) out = `'use client'
|
|
1996
|
-
|
|
1997
|
-
${out}`;
|
|
1998
|
-
}
|
|
1999
|
-
return out;
|
|
2000
|
-
}
|
|
2001
|
-
function sanitizeMetadataStrings(code) {
|
|
2002
|
-
let out = code.replace(/\\'(\s*[}\],])/g, "'$1");
|
|
2003
|
-
out = out.replace(/(:\s*'.+)\\'(\s*)$/gm, "$1'$2");
|
|
2004
|
-
for (const key of ["description", "title"]) {
|
|
2005
|
-
const re = new RegExp(`\\b${key}:\\s*'((?:[^'\\\\]|'(?![,}]))*)'`, "gs");
|
|
2006
|
-
out = out.replace(re, (_, inner) => `${key}: '${inner.replace(/'/g, "\\'")}'`);
|
|
2007
|
-
}
|
|
2008
|
-
return out;
|
|
2009
|
-
}
|
|
2010
|
-
function fixEscapedClosingQuotes(code) {
|
|
2011
|
-
let out = code.replace(/\\'(\s*[}\],])/g, "'$1");
|
|
2012
|
-
out = out.replace(/(:\s*'.+)\\'(\s*)$/gm, "$1'$2");
|
|
2013
|
-
return out;
|
|
2014
|
-
}
|
|
2015
|
-
function fixUnescapedLtInJsx(code) {
|
|
2016
|
-
let out = code;
|
|
2017
|
-
out = out.replace(/>([^<\n]*)<(\d)/g, ">$1<$2");
|
|
2018
|
-
out = out.replace(/>([^<\n]*)<([^/a-zA-Z!{>\n])/g, ">$1<$2");
|
|
2019
|
-
return out;
|
|
2020
|
-
}
|
|
2021
|
-
|
|
2022
1816
|
// src/utils/welcome-content.ts
|
|
2023
1817
|
import fs from "fs";
|
|
2024
1818
|
import path2 from "path";
|
|
@@ -2453,14 +2247,14 @@ function hasApiKey() {
|
|
|
2453
2247
|
}
|
|
2454
2248
|
|
|
2455
2249
|
// src/utils/cursor-rules.ts
|
|
2456
|
-
import { writeFileSync as writeFileSync4, existsSync as
|
|
2457
|
-
import { join as
|
|
2250
|
+
import { writeFileSync as writeFileSync4, existsSync as existsSync5 } from "fs";
|
|
2251
|
+
import { join as join4 } from "path";
|
|
2458
2252
|
import { loadManifest as loadManifest2 } from "@getcoherent/core";
|
|
2459
2253
|
import { DesignSystemManager as DesignSystemManager2 } from "@getcoherent/core";
|
|
2460
2254
|
|
|
2461
2255
|
// src/utils/claude-code.ts
|
|
2462
|
-
import { writeFileSync as writeFileSync3, existsSync as
|
|
2463
|
-
import { join as
|
|
2256
|
+
import { writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync2 } from "fs";
|
|
2257
|
+
import { join as join3 } from "path";
|
|
2464
2258
|
import { loadManifest } from "@getcoherent/core";
|
|
2465
2259
|
import { DesignSystemManager } from "@getcoherent/core";
|
|
2466
2260
|
function buildSharedComponentsListForClaude(manifest) {
|
|
@@ -2905,28 +2699,28 @@ var SETTINGS_JSON = `{
|
|
|
2905
2699
|
`;
|
|
2906
2700
|
function writeClaudeMd(projectRoot, manifest, config2) {
|
|
2907
2701
|
const content = buildClaudeMdContent(manifest, config2);
|
|
2908
|
-
const outPath =
|
|
2702
|
+
const outPath = join3(projectRoot, "CLAUDE.md");
|
|
2909
2703
|
writeFileSync3(outPath, content, "utf-8");
|
|
2910
2704
|
}
|
|
2911
2705
|
function writeClaudeCommands(projectRoot) {
|
|
2912
|
-
const dir =
|
|
2706
|
+
const dir = join3(projectRoot, ".claude", "commands");
|
|
2913
2707
|
ensureDir(dir);
|
|
2914
2708
|
for (const [name, body] of Object.entries(COMMANDS)) {
|
|
2915
|
-
writeFileSync3(
|
|
2709
|
+
writeFileSync3(join3(dir, name), body, "utf-8");
|
|
2916
2710
|
}
|
|
2917
2711
|
}
|
|
2918
2712
|
function writeClaudeSkills(projectRoot) {
|
|
2919
|
-
const dirCoherent =
|
|
2920
|
-
const dirFrontend =
|
|
2713
|
+
const dirCoherent = join3(projectRoot, ".claude", "skills", "coherent-project");
|
|
2714
|
+
const dirFrontend = join3(projectRoot, ".claude", "skills", "frontend-ux");
|
|
2921
2715
|
ensureDir(dirCoherent);
|
|
2922
2716
|
ensureDir(dirFrontend);
|
|
2923
|
-
writeFileSync3(
|
|
2924
|
-
writeFileSync3(
|
|
2717
|
+
writeFileSync3(join3(dirCoherent, "SKILL.md"), SKILL_COHERENT, "utf-8");
|
|
2718
|
+
writeFileSync3(join3(dirFrontend, "SKILL.md"), SKILL_FRONTEND_UX, "utf-8");
|
|
2925
2719
|
}
|
|
2926
2720
|
function writeClaudeSettings(projectRoot) {
|
|
2927
|
-
const dir =
|
|
2721
|
+
const dir = join3(projectRoot, ".claude");
|
|
2928
2722
|
ensureDir(dir);
|
|
2929
|
-
writeFileSync3(
|
|
2723
|
+
writeFileSync3(join3(dir, "settings.json"), SETTINGS_JSON.trim(), "utf-8");
|
|
2930
2724
|
}
|
|
2931
2725
|
async function loadManifestAndConfig(projectRoot) {
|
|
2932
2726
|
let manifest;
|
|
@@ -2936,8 +2730,8 @@ async function loadManifestAndConfig(projectRoot) {
|
|
|
2936
2730
|
manifest = { shared: [], nextId: 1 };
|
|
2937
2731
|
}
|
|
2938
2732
|
let config2 = null;
|
|
2939
|
-
const configPath =
|
|
2940
|
-
if (
|
|
2733
|
+
const configPath = join3(projectRoot, "design-system.config.ts");
|
|
2734
|
+
if (existsSync4(configPath)) {
|
|
2941
2735
|
try {
|
|
2942
2736
|
const dsm = new DesignSystemManager(configPath);
|
|
2943
2737
|
await dsm.load();
|
|
@@ -3316,8 +3110,8 @@ async function writeCursorRules(projectRoot) {
|
|
|
3316
3110
|
manifest = { shared: [], nextId: 1 };
|
|
3317
3111
|
}
|
|
3318
3112
|
let config2 = null;
|
|
3319
|
-
const configPath =
|
|
3320
|
-
if (
|
|
3113
|
+
const configPath = join4(projectRoot, "design-system.config.ts");
|
|
3114
|
+
if (existsSync5(configPath)) {
|
|
3321
3115
|
try {
|
|
3322
3116
|
const dsm = new DesignSystemManager2(configPath);
|
|
3323
3117
|
await dsm.load();
|
|
@@ -3326,7 +3120,7 @@ async function writeCursorRules(projectRoot) {
|
|
|
3326
3120
|
}
|
|
3327
3121
|
}
|
|
3328
3122
|
const content = buildCursorRules(manifest, config2);
|
|
3329
|
-
const outPath =
|
|
3123
|
+
const outPath = join4(projectRoot, ".cursorrules");
|
|
3330
3124
|
writeFileSync4(outPath, content, "utf-8");
|
|
3331
3125
|
writeClaudeMd(projectRoot, manifest, config2);
|
|
3332
3126
|
const tokenKeys = config2?.tokens ? [
|
|
@@ -3350,13 +3144,13 @@ async function regenerateCursorRules() {
|
|
|
3350
3144
|
}
|
|
3351
3145
|
|
|
3352
3146
|
// src/utils/tailwind-version.ts
|
|
3353
|
-
import { existsSync as
|
|
3147
|
+
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "fs";
|
|
3354
3148
|
import { resolve as resolve3 } from "path";
|
|
3355
3149
|
function isTailwindV4(projectRoot) {
|
|
3356
3150
|
const pkgPath = resolve3(projectRoot, "package.json");
|
|
3357
|
-
if (
|
|
3151
|
+
if (existsSync6(pkgPath)) {
|
|
3358
3152
|
try {
|
|
3359
|
-
const pkg = JSON.parse(
|
|
3153
|
+
const pkg = JSON.parse(readFileSync3(pkgPath, "utf-8"));
|
|
3360
3154
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3361
3155
|
if (allDeps["@tailwindcss/postcss"]) return true;
|
|
3362
3156
|
const twVersion = allDeps["tailwindcss"] || "";
|
|
@@ -3365,8 +3159,8 @@ function isTailwindV4(projectRoot) {
|
|
|
3365
3159
|
}
|
|
3366
3160
|
}
|
|
3367
3161
|
const globalsPath = resolve3(projectRoot, "app", "globals.css");
|
|
3368
|
-
if (
|
|
3369
|
-
const content =
|
|
3162
|
+
if (existsSync6(globalsPath)) {
|
|
3163
|
+
const content = readFileSync3(globalsPath, "utf-8");
|
|
3370
3164
|
if (content.includes('@import "tailwindcss"') || content.includes("@import 'tailwindcss'")) return true;
|
|
3371
3165
|
}
|
|
3372
3166
|
return false;
|
|
@@ -3550,10 +3344,10 @@ function toKebabCase(str) {
|
|
|
3550
3344
|
|
|
3551
3345
|
// src/commands/init.ts
|
|
3552
3346
|
function hasNextInPackageJson(projectPath) {
|
|
3553
|
-
const pkgPath =
|
|
3554
|
-
if (!
|
|
3347
|
+
const pkgPath = join5(projectPath, "package.json");
|
|
3348
|
+
if (!existsSync7(pkgPath)) return false;
|
|
3555
3349
|
try {
|
|
3556
|
-
const json = JSON.parse(
|
|
3350
|
+
const json = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
|
3557
3351
|
const deps = { ...json.dependencies, ...json.devDependencies };
|
|
3558
3352
|
return typeof deps?.next === "string";
|
|
3559
3353
|
} catch {
|
|
@@ -3563,30 +3357,30 @@ function hasNextInPackageJson(projectPath) {
|
|
|
3563
3357
|
function cleanConflictingFiles(projectPath) {
|
|
3564
3358
|
const conflicts = [".next", ".coherent", ".cursorrules", ".eslintrc.json", "CLAUDE.md", ".claude", ".vscode"];
|
|
3565
3359
|
for (const name of conflicts) {
|
|
3566
|
-
const fullPath =
|
|
3567
|
-
if (
|
|
3360
|
+
const fullPath = join5(projectPath, name);
|
|
3361
|
+
if (existsSync7(fullPath)) {
|
|
3568
3362
|
rmSync(fullPath, { recursive: true, force: true });
|
|
3569
3363
|
}
|
|
3570
3364
|
}
|
|
3571
3365
|
}
|
|
3572
3366
|
function runCreateNextApp(projectPath) {
|
|
3573
3367
|
cleanConflictingFiles(projectPath);
|
|
3574
|
-
const envPath =
|
|
3575
|
-
const envBackup =
|
|
3368
|
+
const envPath = join5(projectPath, ".env");
|
|
3369
|
+
const envBackup = existsSync7(envPath) ? readFileSync4(envPath, "utf-8") : null;
|
|
3576
3370
|
if (envBackup !== null) rmSync(envPath, { force: true });
|
|
3577
3371
|
const cmd = "npx --yes create-next-app@15.2.4 . --typescript --tailwind --eslint --app --no-src-dir --no-turbopack --yes";
|
|
3578
|
-
|
|
3372
|
+
execSync(cmd, { cwd: projectPath, stdio: "inherit" });
|
|
3579
3373
|
if (envBackup !== null) {
|
|
3580
|
-
const existing =
|
|
3374
|
+
const existing = existsSync7(envPath) ? readFileSync4(envPath, "utf-8") : "";
|
|
3581
3375
|
writeFileSync5(envPath, existing ? existing + "\n" + envBackup : envBackup, "utf-8");
|
|
3582
3376
|
}
|
|
3583
3377
|
}
|
|
3584
3378
|
async function ensureCoherentPrerequisites(projectPath) {
|
|
3585
|
-
const libPath =
|
|
3586
|
-
const utilsPath =
|
|
3587
|
-
const componentsUiPath =
|
|
3588
|
-
if (!
|
|
3589
|
-
if (!
|
|
3379
|
+
const libPath = join5(projectPath, "lib");
|
|
3380
|
+
const utilsPath = join5(projectPath, "lib", "utils.ts");
|
|
3381
|
+
const componentsUiPath = join5(projectPath, "components", "ui");
|
|
3382
|
+
if (!existsSync7(utilsPath)) {
|
|
3383
|
+
if (!existsSync7(libPath)) mkdirSync3(libPath, { recursive: true });
|
|
3590
3384
|
const cnContent = `import { type ClassValue, clsx } from 'clsx'
|
|
3591
3385
|
import { twMerge } from 'tailwind-merge'
|
|
3592
3386
|
|
|
@@ -3596,20 +3390,20 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
3596
3390
|
`;
|
|
3597
3391
|
await writeFile(utilsPath, cnContent);
|
|
3598
3392
|
}
|
|
3599
|
-
if (!
|
|
3393
|
+
if (!existsSync7(componentsUiPath)) mkdirSync3(componentsUiPath, { recursive: true });
|
|
3600
3394
|
}
|
|
3601
3395
|
async function ensureRegistryComponents(config2, projectPath) {
|
|
3602
3396
|
const provider = getComponentProvider();
|
|
3603
3397
|
const baseComponents = ["button", "card", "input", "label", "switch"];
|
|
3604
3398
|
await provider.installBatch(baseComponents, projectPath);
|
|
3605
3399
|
const generator = new ComponentGenerator(config2);
|
|
3606
|
-
const uiDir =
|
|
3607
|
-
if (!
|
|
3400
|
+
const uiDir = join5(projectPath, "components", "ui");
|
|
3401
|
+
if (!existsSync7(uiDir)) mkdirSync3(uiDir, { recursive: true });
|
|
3608
3402
|
for (const comp of config2.components) {
|
|
3609
3403
|
if (comp.source === "shadcn") continue;
|
|
3610
3404
|
const fileName = toKebabCase(comp.name) + ".tsx";
|
|
3611
|
-
const filePath =
|
|
3612
|
-
if (
|
|
3405
|
+
const filePath = join5(uiDir, fileName);
|
|
3406
|
+
if (existsSync7(filePath)) continue;
|
|
3613
3407
|
const code = await generator.generate(comp);
|
|
3614
3408
|
await writeFile(filePath, code);
|
|
3615
3409
|
}
|
|
@@ -3637,8 +3431,8 @@ async function initCommand(name) {
|
|
|
3637
3431
|
);
|
|
3638
3432
|
process.exit(1);
|
|
3639
3433
|
}
|
|
3640
|
-
const targetDir =
|
|
3641
|
-
if (!
|
|
3434
|
+
const targetDir = join5(cwd2(), name);
|
|
3435
|
+
if (!existsSync7(targetDir)) {
|
|
3642
3436
|
mkdirSync3(targetDir, { recursive: true });
|
|
3643
3437
|
}
|
|
3644
3438
|
process.chdir(targetDir);
|
|
@@ -3646,7 +3440,7 @@ async function initCommand(name) {
|
|
|
3646
3440
|
let projectPath;
|
|
3647
3441
|
try {
|
|
3648
3442
|
projectPath = cwd2();
|
|
3649
|
-
if (!
|
|
3443
|
+
if (!existsSync7(projectPath)) {
|
|
3650
3444
|
throw new Error("ENOENT");
|
|
3651
3445
|
}
|
|
3652
3446
|
} catch (err) {
|
|
@@ -3717,7 +3511,7 @@ async function initCommand(name) {
|
|
|
3717
3511
|
await ensureCoherentPrerequisites(projectPath);
|
|
3718
3512
|
const depsSpinner = ora("Installing component dependencies...").start();
|
|
3719
3513
|
try {
|
|
3720
|
-
|
|
3514
|
+
execSync(`npm install --legacy-peer-deps ${COHERENT_REQUIRED_PACKAGES.join(" ")}`, {
|
|
3721
3515
|
cwd: projectPath,
|
|
3722
3516
|
stdio: "pipe"
|
|
3723
3517
|
});
|
|
@@ -3730,7 +3524,7 @@ async function initCommand(name) {
|
|
|
3730
3524
|
const usesV4 = isTailwindV4(projectPath);
|
|
3731
3525
|
if (usesV4) {
|
|
3732
3526
|
const v4Css = generateV4GlobalsCss(config2);
|
|
3733
|
-
await writeFile(
|
|
3527
|
+
await writeFile(join5(projectPath, "app", "globals.css"), v4Css);
|
|
3734
3528
|
} else {
|
|
3735
3529
|
await scaffolder.generateGlobalsCss();
|
|
3736
3530
|
await scaffolder.generateTailwindConfigTs();
|
|
@@ -3744,14 +3538,14 @@ const config = {
|
|
|
3744
3538
|
|
|
3745
3539
|
export default config
|
|
3746
3540
|
`;
|
|
3747
|
-
await writeFile(
|
|
3541
|
+
await writeFile(join5(projectPath, "postcss.config.mjs"), postcssContent);
|
|
3748
3542
|
}
|
|
3749
3543
|
await scaffolder.generateRootLayout();
|
|
3750
3544
|
await configureNextImages(projectPath);
|
|
3751
3545
|
await createAppRouteGroupLayout(projectPath);
|
|
3752
3546
|
const welcomeMarkdown = getWelcomeMarkdown();
|
|
3753
3547
|
const homePageContent = generateWelcomeComponent(welcomeMarkdown);
|
|
3754
|
-
await writeFile(
|
|
3548
|
+
await writeFile(join5(projectPath, "app", "page.tsx"), homePageContent);
|
|
3755
3549
|
const designSystemSpinner = ora("Creating design system pages...").start();
|
|
3756
3550
|
await scaffolder.generateDesignSystemPages();
|
|
3757
3551
|
designSystemSpinner.succeed("Design system pages created");
|
|
@@ -3766,7 +3560,7 @@ export default config
|
|
|
3766
3560
|
scaffoldSpinner.succeed("Project structure created");
|
|
3767
3561
|
const depsSpinner = ora("Installing component dependencies...").start();
|
|
3768
3562
|
try {
|
|
3769
|
-
|
|
3563
|
+
execSync(`npm install --legacy-peer-deps ${COHERENT_REQUIRED_PACKAGES.join(" ")}`, {
|
|
3770
3564
|
cwd: projectPath,
|
|
3771
3565
|
stdio: "pipe"
|
|
3772
3566
|
});
|
|
@@ -3804,13 +3598,13 @@ export default config
|
|
|
3804
3598
|
}
|
|
3805
3599
|
}
|
|
3806
3600
|
async function configureNextImages(projectPath) {
|
|
3807
|
-
const tsPath =
|
|
3808
|
-
const jsPath =
|
|
3809
|
-
const mjsPath =
|
|
3601
|
+
const tsPath = join5(projectPath, "next.config.ts");
|
|
3602
|
+
const jsPath = join5(projectPath, "next.config.js");
|
|
3603
|
+
const mjsPath = join5(projectPath, "next.config.mjs");
|
|
3810
3604
|
let configPath = "";
|
|
3811
|
-
if (
|
|
3812
|
-
else if (
|
|
3813
|
-
else if (
|
|
3605
|
+
if (existsSync7(tsPath)) configPath = tsPath;
|
|
3606
|
+
else if (existsSync7(mjsPath)) configPath = mjsPath;
|
|
3607
|
+
else if (existsSync7(jsPath)) configPath = jsPath;
|
|
3814
3608
|
else return;
|
|
3815
3609
|
const content = `import type { NextConfig } from "next";
|
|
3816
3610
|
|
|
@@ -3829,7 +3623,7 @@ export default nextConfig;
|
|
|
3829
3623
|
await writeFile(configPath, content);
|
|
3830
3624
|
}
|
|
3831
3625
|
async function createAppRouteGroupLayout(projectPath) {
|
|
3832
|
-
const dir =
|
|
3626
|
+
const dir = join5(projectPath, "app", "(app)");
|
|
3833
3627
|
mkdirSync3(dir, { recursive: true });
|
|
3834
3628
|
const layoutCode = `export default function AppLayout({
|
|
3835
3629
|
children,
|
|
@@ -3843,14 +3637,14 @@ async function createAppRouteGroupLayout(projectPath) {
|
|
|
3843
3637
|
)
|
|
3844
3638
|
}
|
|
3845
3639
|
`;
|
|
3846
|
-
await writeFile(
|
|
3640
|
+
await writeFile(join5(dir, "layout.tsx"), layoutCode);
|
|
3847
3641
|
}
|
|
3848
3642
|
|
|
3849
3643
|
// src/commands/chat.ts
|
|
3850
3644
|
import chalk13 from "chalk";
|
|
3851
3645
|
import ora2 from "ora";
|
|
3852
|
-
import { resolve as resolve10, relative as relative2, join as
|
|
3853
|
-
import { existsSync as
|
|
3646
|
+
import { resolve as resolve10, relative as relative2, join as join10 } from "path";
|
|
3647
|
+
import { existsSync as existsSync16, readFileSync as readFileSync11, mkdirSync as mkdirSync6, readdirSync as readdirSync4 } from "fs";
|
|
3854
3648
|
import {
|
|
3855
3649
|
DesignSystemManager as DesignSystemManager7,
|
|
3856
3650
|
ComponentManager as ComponentManager5,
|
|
@@ -4612,9 +4406,9 @@ function extractComponentSpec(changes) {
|
|
|
4612
4406
|
}
|
|
4613
4407
|
|
|
4614
4408
|
// src/utils/auth-route-group.ts
|
|
4615
|
-
import { join as
|
|
4616
|
-
import { readFile as
|
|
4617
|
-
import { existsSync as
|
|
4409
|
+
import { join as join6 } from "path";
|
|
4410
|
+
import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
|
|
4411
|
+
import { existsSync as existsSync8 } from "fs";
|
|
4618
4412
|
var AUTH_LAYOUT = `export default function AuthLayout({
|
|
4619
4413
|
children,
|
|
4620
4414
|
}: {
|
|
@@ -4646,20 +4440,20 @@ export default function ShowWhenNotAuthRoute({
|
|
|
4646
4440
|
}
|
|
4647
4441
|
`;
|
|
4648
4442
|
async function ensureAuthRouteGroup(projectRoot) {
|
|
4649
|
-
const authLayoutPath =
|
|
4650
|
-
const guardPath2 =
|
|
4651
|
-
const rootLayoutPath =
|
|
4652
|
-
if (!
|
|
4443
|
+
const authLayoutPath = join6(projectRoot, "app", "(auth)", "layout.tsx");
|
|
4444
|
+
const guardPath2 = join6(projectRoot, "app", "ShowWhenNotAuthRoute.tsx");
|
|
4445
|
+
const rootLayoutPath = join6(projectRoot, "app", "layout.tsx");
|
|
4446
|
+
if (!existsSync8(authLayoutPath)) {
|
|
4653
4447
|
const { mkdir: mkdir8 } = await import("fs/promises");
|
|
4654
|
-
await mkdir8(
|
|
4448
|
+
await mkdir8(join6(projectRoot, "app", "(auth)"), { recursive: true });
|
|
4655
4449
|
await writeFile2(authLayoutPath, AUTH_LAYOUT, "utf-8");
|
|
4656
4450
|
}
|
|
4657
|
-
if (!
|
|
4451
|
+
if (!existsSync8(guardPath2)) {
|
|
4658
4452
|
await writeFile2(guardPath2, SHOW_WHEN_NOT_AUTH, "utf-8");
|
|
4659
4453
|
}
|
|
4660
4454
|
let layoutContent;
|
|
4661
4455
|
try {
|
|
4662
|
-
layoutContent = await
|
|
4456
|
+
layoutContent = await readFile2(rootLayoutPath, "utf-8");
|
|
4663
4457
|
} catch {
|
|
4664
4458
|
return;
|
|
4665
4459
|
}
|
|
@@ -4688,9 +4482,9 @@ async function ensureAuthRouteGroup(projectRoot) {
|
|
|
4688
4482
|
}
|
|
4689
4483
|
|
|
4690
4484
|
// src/utils/dark-mode.ts
|
|
4691
|
-
import { readFile as
|
|
4692
|
-
import { join as
|
|
4693
|
-
import { existsSync as
|
|
4485
|
+
import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
|
|
4486
|
+
import { join as join7 } from "path";
|
|
4487
|
+
import { existsSync as existsSync9 } from "fs";
|
|
4694
4488
|
import { generateSharedComponent, loadManifest as loadManifest3, integrateSharedLayoutIntoRootLayout } from "@getcoherent/core";
|
|
4695
4489
|
var THEME_TOGGLE_CODE = `'use client'
|
|
4696
4490
|
|
|
@@ -4716,9 +4510,9 @@ export function ThemeToggle() {
|
|
|
4716
4510
|
}
|
|
4717
4511
|
`;
|
|
4718
4512
|
async function setDefaultDarkTheme(projectRoot) {
|
|
4719
|
-
const layoutPath =
|
|
4720
|
-
if (!
|
|
4721
|
-
let content = await
|
|
4513
|
+
const layoutPath = join7(projectRoot, "app", "layout.tsx");
|
|
4514
|
+
if (!existsSync9(layoutPath)) return false;
|
|
4515
|
+
let content = await readFile3(layoutPath, "utf-8");
|
|
4722
4516
|
if (content.includes('<html className="dark"') || content.includes("<html className='dark'")) return true;
|
|
4723
4517
|
content = content.replace(/<html(\s|>)/, '<html className="dark"$1');
|
|
4724
4518
|
await writeFile3(layoutPath, content, "utf-8");
|
|
@@ -4745,8 +4539,8 @@ async function ensureThemeToggle(projectRoot) {
|
|
|
4745
4539
|
import { appendFile } from "fs/promises";
|
|
4746
4540
|
|
|
4747
4541
|
// src/utils/backup.ts
|
|
4748
|
-
import { existsSync as
|
|
4749
|
-
import { join as
|
|
4542
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync6, readdirSync, rmSync as rmSync2, statSync } from "fs";
|
|
4543
|
+
import { join as join8, relative, dirname as dirname4 } from "path";
|
|
4750
4544
|
import chalk6 from "chalk";
|
|
4751
4545
|
var DEBUG = process.env.COHERENT_DEBUG === "1";
|
|
4752
4546
|
var BACKUP_DIR = ".coherent/backups";
|
|
@@ -4761,28 +4555,28 @@ var CRITICAL_FILES = [
|
|
|
4761
4555
|
var CRITICAL_DIRS = ["app", "components"];
|
|
4762
4556
|
function createBackup(projectRoot) {
|
|
4763
4557
|
try {
|
|
4764
|
-
const backupBase =
|
|
4558
|
+
const backupBase = join8(projectRoot, BACKUP_DIR);
|
|
4765
4559
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
|
|
4766
|
-
const backupPath =
|
|
4560
|
+
const backupPath = join8(backupBase, timestamp);
|
|
4767
4561
|
mkdirSync4(backupPath, { recursive: true });
|
|
4768
4562
|
let fileCount = 0;
|
|
4769
4563
|
for (const file of CRITICAL_FILES) {
|
|
4770
|
-
const src =
|
|
4771
|
-
if (
|
|
4772
|
-
const dest =
|
|
4564
|
+
const src = join8(projectRoot, file);
|
|
4565
|
+
if (existsSync10(src)) {
|
|
4566
|
+
const dest = join8(backupPath, file);
|
|
4773
4567
|
mkdirSync4(dirname4(dest), { recursive: true });
|
|
4774
|
-
writeFileSync6(dest,
|
|
4568
|
+
writeFileSync6(dest, readFileSync5(src));
|
|
4775
4569
|
fileCount++;
|
|
4776
4570
|
}
|
|
4777
4571
|
}
|
|
4778
4572
|
for (const dir of CRITICAL_DIRS) {
|
|
4779
|
-
const srcDir =
|
|
4780
|
-
if (!
|
|
4573
|
+
const srcDir = join8(projectRoot, dir);
|
|
4574
|
+
if (!existsSync10(srcDir)) continue;
|
|
4781
4575
|
backupDirectory(srcDir, projectRoot, backupPath);
|
|
4782
4576
|
fileCount += countFiles(srcDir);
|
|
4783
4577
|
}
|
|
4784
4578
|
writeFileSync6(
|
|
4785
|
-
|
|
4579
|
+
join8(backupPath, ".backup-meta.json"),
|
|
4786
4580
|
JSON.stringify(
|
|
4787
4581
|
{
|
|
4788
4582
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -4803,15 +4597,15 @@ function backupDirectory(srcDir, projectRoot, backupPath) {
|
|
|
4803
4597
|
const entries = readdirSync(srcDir, { withFileTypes: true });
|
|
4804
4598
|
for (const entry of entries) {
|
|
4805
4599
|
if (entry.name === "node_modules" || entry.name === ".next" || entry.name === ".git") continue;
|
|
4806
|
-
const fullPath =
|
|
4600
|
+
const fullPath = join8(srcDir, entry.name);
|
|
4807
4601
|
const relPath = relative(projectRoot, fullPath);
|
|
4808
|
-
const destPath =
|
|
4602
|
+
const destPath = join8(backupPath, relPath);
|
|
4809
4603
|
if (entry.isDirectory()) {
|
|
4810
4604
|
mkdirSync4(destPath, { recursive: true });
|
|
4811
4605
|
backupDirectory(fullPath, projectRoot, backupPath);
|
|
4812
4606
|
} else if (entry.isFile()) {
|
|
4813
4607
|
mkdirSync4(dirname4(destPath), { recursive: true });
|
|
4814
|
-
writeFileSync6(destPath,
|
|
4608
|
+
writeFileSync6(destPath, readFileSync5(fullPath));
|
|
4815
4609
|
}
|
|
4816
4610
|
}
|
|
4817
4611
|
}
|
|
@@ -4821,7 +4615,7 @@ function countFiles(dir) {
|
|
|
4821
4615
|
const entries = readdirSync(dir, { withFileTypes: true });
|
|
4822
4616
|
for (const entry of entries) {
|
|
4823
4617
|
if (entry.name === "node_modules" || entry.name === ".next") continue;
|
|
4824
|
-
const fullPath =
|
|
4618
|
+
const fullPath = join8(dir, entry.name);
|
|
4825
4619
|
if (entry.isDirectory()) {
|
|
4826
4620
|
count += countFiles(fullPath);
|
|
4827
4621
|
} else if (entry.isFile()) {
|
|
@@ -4835,24 +4629,24 @@ function countFiles(dir) {
|
|
|
4835
4629
|
}
|
|
4836
4630
|
function pruneOldBackups(backupBase) {
|
|
4837
4631
|
try {
|
|
4838
|
-
const entries = readdirSync(backupBase).filter((e) => e !== ".gitkeep" && !e.startsWith(".")).map((name) => ({ name, time: statSync(
|
|
4632
|
+
const entries = readdirSync(backupBase).filter((e) => e !== ".gitkeep" && !e.startsWith(".")).map((name) => ({ name, time: statSync(join8(backupBase, name)).mtimeMs })).sort((a, b) => b.time - a.time);
|
|
4839
4633
|
for (const old of entries.slice(MAX_BACKUPS)) {
|
|
4840
|
-
rmSync2(
|
|
4634
|
+
rmSync2(join8(backupBase, old.name), { recursive: true, force: true });
|
|
4841
4635
|
}
|
|
4842
4636
|
} catch (e) {
|
|
4843
4637
|
if (DEBUG) console.error("Failed to prune old backups:", e);
|
|
4844
4638
|
}
|
|
4845
4639
|
}
|
|
4846
4640
|
function listBackups(projectRoot) {
|
|
4847
|
-
const backupBase =
|
|
4848
|
-
if (!
|
|
4641
|
+
const backupBase = join8(projectRoot, BACKUP_DIR);
|
|
4642
|
+
if (!existsSync10(backupBase)) return [];
|
|
4849
4643
|
try {
|
|
4850
4644
|
return readdirSync(backupBase).filter((e) => !e.startsWith(".")).map((name) => {
|
|
4851
|
-
const metaPath =
|
|
4645
|
+
const metaPath = join8(backupBase, name, ".backup-meta.json");
|
|
4852
4646
|
let meta = { timestamp: name, files: 0 };
|
|
4853
|
-
if (
|
|
4647
|
+
if (existsSync10(metaPath)) {
|
|
4854
4648
|
try {
|
|
4855
|
-
meta = JSON.parse(
|
|
4649
|
+
meta = JSON.parse(readFileSync5(metaPath, "utf-8"));
|
|
4856
4650
|
} catch (e) {
|
|
4857
4651
|
if (DEBUG) console.error("Bad backup meta:", metaPath, e);
|
|
4858
4652
|
}
|
|
@@ -4865,8 +4659,8 @@ function listBackups(projectRoot) {
|
|
|
4865
4659
|
}
|
|
4866
4660
|
}
|
|
4867
4661
|
function restoreBackup(projectRoot, backupName) {
|
|
4868
|
-
const backupPath =
|
|
4869
|
-
if (!
|
|
4662
|
+
const backupPath = join8(projectRoot, BACKUP_DIR, backupName);
|
|
4663
|
+
if (!existsSync10(backupPath)) return false;
|
|
4870
4664
|
try {
|
|
4871
4665
|
restoreDirectory(backupPath, backupPath, projectRoot);
|
|
4872
4666
|
return true;
|
|
@@ -4879,15 +4673,15 @@ function restoreDirectory(currentDir, backupRoot, projectRoot) {
|
|
|
4879
4673
|
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
4880
4674
|
for (const entry of entries) {
|
|
4881
4675
|
if (entry.name === ".backup-meta.json") continue;
|
|
4882
|
-
const fullPath =
|
|
4676
|
+
const fullPath = join8(currentDir, entry.name);
|
|
4883
4677
|
const relPath = relative(backupRoot, fullPath);
|
|
4884
|
-
const destPath =
|
|
4678
|
+
const destPath = join8(projectRoot, relPath);
|
|
4885
4679
|
if (entry.isDirectory()) {
|
|
4886
4680
|
mkdirSync4(destPath, { recursive: true });
|
|
4887
4681
|
restoreDirectory(fullPath, backupRoot, projectRoot);
|
|
4888
4682
|
} else if (entry.isFile()) {
|
|
4889
4683
|
mkdirSync4(dirname4(destPath), { recursive: true });
|
|
4890
|
-
writeFileSync6(destPath,
|
|
4684
|
+
writeFileSync6(destPath, readFileSync5(fullPath));
|
|
4891
4685
|
}
|
|
4892
4686
|
}
|
|
4893
4687
|
}
|
|
@@ -4899,15 +4693,15 @@ function logBackupCreated(backupPath) {
|
|
|
4899
4693
|
}
|
|
4900
4694
|
|
|
4901
4695
|
// src/utils/fix-globals-css.ts
|
|
4902
|
-
import { existsSync as
|
|
4696
|
+
import { existsSync as existsSync11, readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
4903
4697
|
import { resolve as resolve4 } from "path";
|
|
4904
4698
|
import { buildCssVariables as buildCssVariables2 } from "@getcoherent/core";
|
|
4905
4699
|
function needsGlobalsFix(projectRoot) {
|
|
4906
4700
|
const globalsPath = resolve4(projectRoot, "app", "globals.css");
|
|
4907
|
-
if (!
|
|
4701
|
+
if (!existsSync11(globalsPath)) {
|
|
4908
4702
|
return false;
|
|
4909
4703
|
}
|
|
4910
|
-
const content =
|
|
4704
|
+
const content = readFileSync6(globalsPath, "utf-8");
|
|
4911
4705
|
if (isTailwindV4(projectRoot)) {
|
|
4912
4706
|
if (!content.includes("@theme inline")) return true;
|
|
4913
4707
|
if (content.includes("@tailwind base")) return true;
|
|
@@ -4932,7 +4726,7 @@ function needsGlobalsFix(projectRoot) {
|
|
|
4932
4726
|
function fixGlobalsCss(projectRoot, config2) {
|
|
4933
4727
|
const globalsPath = resolve4(projectRoot, "app", "globals.css");
|
|
4934
4728
|
const layoutPath = resolve4(projectRoot, "app", "layout.tsx");
|
|
4935
|
-
if (!
|
|
4729
|
+
if (!existsSync11(globalsPath)) {
|
|
4936
4730
|
return;
|
|
4937
4731
|
}
|
|
4938
4732
|
if (isTailwindV4(projectRoot)) {
|
|
@@ -4955,10 +4749,10 @@ function fixGlobalsCss(projectRoot, config2) {
|
|
|
4955
4749
|
}
|
|
4956
4750
|
`;
|
|
4957
4751
|
writeFileSync7(globalsPath, minimalCss, "utf-8");
|
|
4958
|
-
if (!
|
|
4752
|
+
if (!existsSync11(layoutPath)) {
|
|
4959
4753
|
return;
|
|
4960
4754
|
}
|
|
4961
|
-
let layoutContent =
|
|
4755
|
+
let layoutContent = readFileSync6(layoutPath, "utf-8");
|
|
4962
4756
|
if (layoutContent.includes("dangerouslySetInnerHTML")) {
|
|
4963
4757
|
return;
|
|
4964
4758
|
}
|
|
@@ -4972,7 +4766,7 @@ function fixGlobalsCss(projectRoot, config2) {
|
|
|
4972
4766
|
|
|
4973
4767
|
// src/commands/chat/utils.ts
|
|
4974
4768
|
import { resolve as resolve5 } from "path";
|
|
4975
|
-
import { existsSync as
|
|
4769
|
+
import { existsSync as existsSync12, readFileSync as readFileSync7 } from "fs";
|
|
4976
4770
|
import { DesignSystemManager as DesignSystemManager3, loadManifest as loadManifest4 } from "@getcoherent/core";
|
|
4977
4771
|
import chalk7 from "chalk";
|
|
4978
4772
|
var MARKETING_ROUTES = /* @__PURE__ */ new Set(["", "landing", "pricing", "about", "contact", "blog", "features"]);
|
|
@@ -4994,10 +4788,10 @@ function inferRouteUsesAuthSegment(route) {
|
|
|
4994
4788
|
function readAnchorPageCodeFromDisk(projectRoot, route) {
|
|
4995
4789
|
const useAuthSegment = inferRouteUsesAuthSegment(route);
|
|
4996
4790
|
const abs = routeToFsPath(projectRoot, route, useAuthSegment);
|
|
4997
|
-
if (!
|
|
4791
|
+
if (!existsSync12(abs)) return null;
|
|
4998
4792
|
let code;
|
|
4999
4793
|
try {
|
|
5000
|
-
code =
|
|
4794
|
+
code = readFileSync7(abs, "utf-8");
|
|
5001
4795
|
} catch {
|
|
5002
4796
|
return null;
|
|
5003
4797
|
}
|
|
@@ -5013,11 +4807,11 @@ function routeToFsPath(projectRoot, route, isAuthOrPlan) {
|
|
|
5013
4807
|
const isAuth = typeof isAuthOrPlan === "boolean" ? isAuthOrPlan : false;
|
|
5014
4808
|
const slug = route.replace(/^\//, "");
|
|
5015
4809
|
if (!slug) return resolve5(projectRoot, "app", "page.tsx");
|
|
4810
|
+
if (isAuth || isAuthRoute(route)) return resolve5(projectRoot, "app", "(auth)", slug || "login", "page.tsx");
|
|
5016
4811
|
if (plan) {
|
|
5017
4812
|
const group = getPageGroup(route, plan);
|
|
5018
4813
|
if (group) return resolve5(projectRoot, "app", `(${group.id})`, slug, "page.tsx");
|
|
5019
4814
|
}
|
|
5020
|
-
if (isAuth) return resolve5(projectRoot, "app", "(auth)", slug || "login", "page.tsx");
|
|
5021
4815
|
if (isMarketingRoute(route)) return resolve5(projectRoot, "app", slug, "page.tsx");
|
|
5022
4816
|
return resolve5(projectRoot, "app", "(app)", slug, "page.tsx");
|
|
5023
4817
|
}
|
|
@@ -5026,11 +4820,11 @@ function routeToRelPath(route, isAuthOrPlan) {
|
|
|
5026
4820
|
const isAuth = typeof isAuthOrPlan === "boolean" ? isAuthOrPlan : false;
|
|
5027
4821
|
const slug = route.replace(/^\//, "");
|
|
5028
4822
|
if (!slug) return "app/page.tsx";
|
|
4823
|
+
if (isAuth || isAuthRoute(route)) return `app/(auth)/${slug || "login"}/page.tsx`;
|
|
5029
4824
|
if (plan) {
|
|
5030
4825
|
const group = getPageGroup(route, plan);
|
|
5031
4826
|
if (group) return `app/(${group.id})/${slug}/page.tsx`;
|
|
5032
4827
|
}
|
|
5033
|
-
if (isAuth) return `app/(auth)/${slug || "login"}/page.tsx`;
|
|
5034
4828
|
if (isMarketingRoute(route)) return `app/${slug}/page.tsx`;
|
|
5035
4829
|
return `app/(app)/${slug}/page.tsx`;
|
|
5036
4830
|
}
|
|
@@ -5115,7 +4909,7 @@ async function warnInlineDuplicates(projectRoot, pageName, route, pageCode, mani
|
|
|
5115
4909
|
}
|
|
5116
4910
|
}
|
|
5117
4911
|
async function loadConfig(configPath) {
|
|
5118
|
-
if (!
|
|
4912
|
+
if (!existsSync12(configPath)) {
|
|
5119
4913
|
throw new Error(
|
|
5120
4914
|
`Design system config not found at ${configPath}
|
|
5121
4915
|
Run "coherent init" first to create a project.`
|
|
@@ -5143,8 +4937,8 @@ async function resolveTargetFlags(message, options, config2, projectRoot) {
|
|
|
5143
4937
|
if (entry) {
|
|
5144
4938
|
const filePath = resolve5(projectRoot, entry.file);
|
|
5145
4939
|
let currentCode = "";
|
|
5146
|
-
if (
|
|
5147
|
-
currentCode =
|
|
4940
|
+
if (existsSync12(filePath)) {
|
|
4941
|
+
currentCode = readFileSync7(filePath, "utf-8");
|
|
5148
4942
|
}
|
|
5149
4943
|
const codeSnippet = currentCode ? `
|
|
5150
4944
|
|
|
@@ -5168,8 +4962,8 @@ ${currentCode}
|
|
|
5168
4962
|
const relPath = page.route === "/" ? "app/page.tsx" : `app${page.route}/page.tsx`;
|
|
5169
4963
|
const filePath = resolve5(projectRoot, relPath);
|
|
5170
4964
|
let currentCode = "";
|
|
5171
|
-
if (
|
|
5172
|
-
currentCode =
|
|
4965
|
+
if (existsSync12(filePath)) {
|
|
4966
|
+
currentCode = readFileSync7(filePath, "utf-8");
|
|
5173
4967
|
}
|
|
5174
4968
|
const codeSnippet = currentCode ? `
|
|
5175
4969
|
|
|
@@ -5624,7 +5418,7 @@ function applyDefaults(request) {
|
|
|
5624
5418
|
}
|
|
5625
5419
|
|
|
5626
5420
|
// src/commands/chat/split-generator.ts
|
|
5627
|
-
import { existsSync as
|
|
5421
|
+
import { existsSync as existsSync13, readFileSync as readFileSync8, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
5628
5422
|
import { resolve as resolve6 } from "path";
|
|
5629
5423
|
import { z } from "zod";
|
|
5630
5424
|
import {
|
|
@@ -6075,8 +5869,8 @@ function readExistingAppPageForReference(projectRoot, plan) {
|
|
|
6075
5869
|
for (const group of ["(app)", "(admin)", "(dashboard)"]) {
|
|
6076
5870
|
const filePath = resolve6(projectRoot, "app", group, key, "page.tsx");
|
|
6077
5871
|
try {
|
|
6078
|
-
if (
|
|
6079
|
-
const code =
|
|
5872
|
+
if (existsSync13(filePath)) {
|
|
5873
|
+
const code = readFileSync8(filePath, "utf-8");
|
|
6080
5874
|
const lines = code.split("\n");
|
|
6081
5875
|
return lines.slice(0, 60).join("\n");
|
|
6082
5876
|
}
|
|
@@ -6087,7 +5881,7 @@ function readExistingAppPageForReference(projectRoot, plan) {
|
|
|
6087
5881
|
}
|
|
6088
5882
|
}
|
|
6089
5883
|
const appDir = resolve6(projectRoot, "app");
|
|
6090
|
-
if (!
|
|
5884
|
+
if (!existsSync13(appDir)) return null;
|
|
6091
5885
|
try {
|
|
6092
5886
|
const entries = readdirSync2(appDir);
|
|
6093
5887
|
for (const entry of entries) {
|
|
@@ -6097,8 +5891,8 @@ function readExistingAppPageForReference(projectRoot, plan) {
|
|
|
6097
5891
|
const subDirs = readdirSync2(groupDir);
|
|
6098
5892
|
for (const sub of subDirs) {
|
|
6099
5893
|
const pagePath = resolve6(groupDir, sub, "page.tsx");
|
|
6100
|
-
if (
|
|
6101
|
-
const code =
|
|
5894
|
+
if (existsSync13(pagePath)) {
|
|
5895
|
+
const code = readFileSync8(pagePath, "utf-8");
|
|
6102
5896
|
const lines = code.split("\n");
|
|
6103
5897
|
return lines.slice(0, 60).join("\n");
|
|
6104
5898
|
}
|
|
@@ -6315,7 +6109,7 @@ async function splitGeneratePages(spinner, message, modCtx, provider, parseOpts)
|
|
|
6315
6109
|
if (plan && plan.sharedComponents.length > 0) {
|
|
6316
6110
|
spinner.start(`Phase 4.5/6 \u2014 Generating ${plan.sharedComponents.length} shared components from plan...`);
|
|
6317
6111
|
try {
|
|
6318
|
-
const { generateSharedComponentsFromPlan } = await import("./plan-generator-
|
|
6112
|
+
const { generateSharedComponentsFromPlan } = await import("./plan-generator-WPYWUA7U.js");
|
|
6319
6113
|
const generated = await generateSharedComponentsFromPlan(
|
|
6320
6114
|
plan,
|
|
6321
6115
|
styleContext,
|
|
@@ -6372,13 +6166,13 @@ ${existingAppPageCode}
|
|
|
6372
6166
|
const existingPageCode = {};
|
|
6373
6167
|
if (projectRoot) {
|
|
6374
6168
|
const appDir = resolve6(projectRoot, "app");
|
|
6375
|
-
if (
|
|
6169
|
+
if (existsSync13(appDir)) {
|
|
6376
6170
|
const pageFiles = readdirSync2(appDir, { recursive: true }).filter(
|
|
6377
6171
|
(f) => typeof f === "string" && f.endsWith("page.tsx")
|
|
6378
6172
|
);
|
|
6379
6173
|
for (const pf of pageFiles) {
|
|
6380
6174
|
try {
|
|
6381
|
-
const code =
|
|
6175
|
+
const code = readFileSync8(resolve6(appDir, pf), "utf-8");
|
|
6382
6176
|
const route = "/" + pf.replace(/\/page\.tsx$/, "").replace(/\(.*?\)\//g, "");
|
|
6383
6177
|
existingPageCode[route === "/" ? "/" : route] = code;
|
|
6384
6178
|
} catch {
|
|
@@ -6673,7 +6467,7 @@ import {
|
|
|
6673
6467
|
|
|
6674
6468
|
// src/commands/chat/code-generator.ts
|
|
6675
6469
|
import { resolve as resolve7 } from "path";
|
|
6676
|
-
import { existsSync as
|
|
6470
|
+
import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
|
|
6677
6471
|
import { mkdir as mkdir3 } from "fs/promises";
|
|
6678
6472
|
import { dirname as dirname5 } from "path";
|
|
6679
6473
|
import {
|
|
@@ -6686,25 +6480,25 @@ import chalk9 from "chalk";
|
|
|
6686
6480
|
|
|
6687
6481
|
// src/utils/file-hashes.ts
|
|
6688
6482
|
import { createHash } from "crypto";
|
|
6689
|
-
import { readFile as
|
|
6690
|
-
import { join as
|
|
6483
|
+
import { readFile as readFile4, writeFile as writeFile4, mkdir as mkdir2 } from "fs/promises";
|
|
6484
|
+
import { join as join9 } from "path";
|
|
6691
6485
|
var HASHES_FILE = ".coherent/file-hashes.json";
|
|
6692
6486
|
async function computeFileHash(filePath) {
|
|
6693
|
-
const content = await
|
|
6487
|
+
const content = await readFile4(filePath, "utf-8");
|
|
6694
6488
|
return createHash("sha256").update(content).digest("hex");
|
|
6695
6489
|
}
|
|
6696
6490
|
async function loadHashes(projectRoot) {
|
|
6697
6491
|
try {
|
|
6698
|
-
const raw = await
|
|
6492
|
+
const raw = await readFile4(join9(projectRoot, HASHES_FILE), "utf-8");
|
|
6699
6493
|
return JSON.parse(raw);
|
|
6700
6494
|
} catch {
|
|
6701
6495
|
return {};
|
|
6702
6496
|
}
|
|
6703
6497
|
}
|
|
6704
6498
|
async function saveHashes(projectRoot, hashes) {
|
|
6705
|
-
const dir =
|
|
6499
|
+
const dir = join9(projectRoot, ".coherent");
|
|
6706
6500
|
await mkdir2(dir, { recursive: true });
|
|
6707
|
-
await writeFile4(
|
|
6501
|
+
await writeFile4(join9(projectRoot, HASHES_FILE), JSON.stringify(hashes, null, 2) + "\n");
|
|
6708
6502
|
}
|
|
6709
6503
|
async function isManuallyEdited(filePath, storedHash) {
|
|
6710
6504
|
try {
|
|
@@ -6739,7 +6533,7 @@ async function ensureComponentsInstalled(componentIds, cm, dsm, pm, projectRoot)
|
|
|
6739
6533
|
const isRegistered = !!cm.read(componentId);
|
|
6740
6534
|
const fileName = toKebabCase(componentId) + ".tsx";
|
|
6741
6535
|
const filePath = resolve7(projectRoot, "components", "ui", fileName);
|
|
6742
|
-
const fileExists =
|
|
6536
|
+
const fileExists = existsSync14(filePath);
|
|
6743
6537
|
if (isRegistered && fileExists) continue;
|
|
6744
6538
|
const result = await provider.installComponent(componentId, projectRoot);
|
|
6745
6539
|
if (result.success && result.componentDef) {
|
|
@@ -6775,7 +6569,7 @@ async function regeneratePage(pageId, config2, projectRoot) {
|
|
|
6775
6569
|
const code = await generator.generate(page, appType);
|
|
6776
6570
|
const route = page.route || "/";
|
|
6777
6571
|
const isAuth = isAuthRoute(route) || isAuthRoute(page.name || page.id || "");
|
|
6778
|
-
const { loadPlan: loadPlanForPath } = await import("./plan-generator-
|
|
6572
|
+
const { loadPlan: loadPlanForPath } = await import("./plan-generator-WPYWUA7U.js");
|
|
6779
6573
|
const planForPath = loadPlanForPath(projectRoot);
|
|
6780
6574
|
const filePath = routeToFsPath(projectRoot, route, planForPath || isAuth);
|
|
6781
6575
|
await mkdir3(dirname5(filePath), { recursive: true });
|
|
@@ -6783,7 +6577,7 @@ async function regeneratePage(pageId, config2, projectRoot) {
|
|
|
6783
6577
|
}
|
|
6784
6578
|
async function canOverwriteShared(projectRoot, componentFile, storedHashes) {
|
|
6785
6579
|
const filePath = resolve7(projectRoot, componentFile);
|
|
6786
|
-
if (!
|
|
6580
|
+
if (!existsSync14(filePath)) return true;
|
|
6787
6581
|
const storedHash = storedHashes[componentFile];
|
|
6788
6582
|
if (!storedHash) return true;
|
|
6789
6583
|
const edited = await isManuallyEdited(filePath, storedHash);
|
|
@@ -6859,16 +6653,16 @@ async function regenerateLayout(config2, projectRoot, options = {
|
|
|
6859
6653
|
}
|
|
6860
6654
|
async function scanAndInstallSharedDeps(projectRoot) {
|
|
6861
6655
|
const sharedDir = resolve7(projectRoot, "components", "shared");
|
|
6862
|
-
if (!
|
|
6656
|
+
if (!existsSync14(sharedDir)) return [];
|
|
6863
6657
|
const files = readdirSync3(sharedDir).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts"));
|
|
6864
6658
|
const installed = [];
|
|
6865
6659
|
const provider = getComponentProvider();
|
|
6866
6660
|
for (const file of files) {
|
|
6867
|
-
const code =
|
|
6661
|
+
const code = readFileSync9(resolve7(sharedDir, file), "utf-8");
|
|
6868
6662
|
const importMatches = [...code.matchAll(/@\/components\/ui\/([a-z0-9-]+)/g)];
|
|
6869
6663
|
for (const [, componentId] of importMatches) {
|
|
6870
6664
|
const uiPath = resolve7(projectRoot, "components", "ui", `${componentId}.tsx`);
|
|
6871
|
-
if (!
|
|
6665
|
+
if (!existsSync14(uiPath) && provider.has(componentId)) {
|
|
6872
6666
|
try {
|
|
6873
6667
|
await provider.installComponent(componentId, projectRoot);
|
|
6874
6668
|
installed.push(componentId);
|
|
@@ -6882,7 +6676,7 @@ async function scanAndInstallSharedDeps(projectRoot) {
|
|
|
6882
6676
|
async function ensureAppRouteGroupLayout(projectRoot, navType, forceUpdate = false, groupLayouts) {
|
|
6883
6677
|
const effectiveNavType = groupLayouts?.["app"] || navType;
|
|
6884
6678
|
const layoutPath = resolve7(projectRoot, "app", "(app)", "layout.tsx");
|
|
6885
|
-
if (
|
|
6679
|
+
if (existsSync14(layoutPath) && !forceUpdate) return;
|
|
6886
6680
|
const { mkdir: mkdirAsync } = await import("fs/promises");
|
|
6887
6681
|
await mkdirAsync(resolve7(projectRoot, "app", "(app)"), { recursive: true });
|
|
6888
6682
|
const code = buildAppLayoutCode(effectiveNavType);
|
|
@@ -6983,8 +6777,8 @@ async function ensurePlanGroupLayouts(projectRoot, plan, storedHashes = {}, conf
|
|
|
6983
6777
|
await mkdirAsync(groupDir, { recursive: true });
|
|
6984
6778
|
const layoutPath = resolve7(groupDir, "layout.tsx");
|
|
6985
6779
|
const relPath = `app/(${group.id})/layout.tsx`;
|
|
6986
|
-
if (
|
|
6987
|
-
const currentContent =
|
|
6780
|
+
if (existsSync14(layoutPath)) {
|
|
6781
|
+
const currentContent = readFileSync9(layoutPath, "utf-8");
|
|
6988
6782
|
const currentHash = createHash2("md5").update(currentContent).digest("hex");
|
|
6989
6783
|
const storedHash = storedHashes[relPath];
|
|
6990
6784
|
if (storedHash && storedHash !== currentHash) {
|
|
@@ -7026,9 +6820,9 @@ async function regenerateFiles(modified, config2, projectRoot, options = { navCh
|
|
|
7026
6820
|
const twGen = new TailwindConfigGenerator(config2);
|
|
7027
6821
|
const twPath = resolve7(projectRoot, "tailwind.config.ts");
|
|
7028
6822
|
const twCjsPath = resolve7(projectRoot, "tailwind.config.cjs");
|
|
7029
|
-
if (
|
|
6823
|
+
if (existsSync14(twPath)) {
|
|
7030
6824
|
await writeFile(twPath, await twGen.generate());
|
|
7031
|
-
} else if (
|
|
6825
|
+
} else if (existsSync14(twCjsPath)) {
|
|
7032
6826
|
await writeFile(twCjsPath, await twGen.generateCjs());
|
|
7033
6827
|
}
|
|
7034
6828
|
}
|
|
@@ -8291,7 +8085,7 @@ function hasNavChanged(before, after) {
|
|
|
8291
8085
|
// src/commands/chat/interactive.ts
|
|
8292
8086
|
import chalk12 from "chalk";
|
|
8293
8087
|
import { resolve as resolve9 } from "path";
|
|
8294
|
-
import { existsSync as
|
|
8088
|
+
import { existsSync as existsSync15, readFileSync as readFileSync10, writeFileSync as writeFileSync8, mkdirSync as mkdirSync5 } from "fs";
|
|
8295
8089
|
import { DesignSystemManager as DesignSystemManager6, ComponentManager as ComponentManager4, loadManifest as loadManifest7 } from "@getcoherent/core";
|
|
8296
8090
|
var DEBUG3 = process.env.COHERENT_DEBUG === "1";
|
|
8297
8091
|
async function interactiveChat(options, chatCommandFn) {
|
|
@@ -8316,8 +8110,8 @@ async function interactiveChat(options, chatCommandFn) {
|
|
|
8316
8110
|
let history = [];
|
|
8317
8111
|
try {
|
|
8318
8112
|
mkdirSync5(historyDir, { recursive: true });
|
|
8319
|
-
if (
|
|
8320
|
-
history =
|
|
8113
|
+
if (existsSync15(historyFile)) {
|
|
8114
|
+
history = readFileSync10(historyFile, "utf-8").split("\n").filter(Boolean).slice(-200);
|
|
8321
8115
|
}
|
|
8322
8116
|
} catch (e) {
|
|
8323
8117
|
if (DEBUG3) console.error("Failed to load REPL history:", e);
|
|
@@ -8495,8 +8289,8 @@ async function chatCommand(message, options) {
|
|
|
8495
8289
|
const project = requireProject();
|
|
8496
8290
|
const projectRoot = project.root;
|
|
8497
8291
|
const configPath = project.configPath;
|
|
8498
|
-
const migrationGuard =
|
|
8499
|
-
if (
|
|
8292
|
+
const migrationGuard = join10(projectRoot, ".coherent", "migration-in-progress");
|
|
8293
|
+
if (existsSync16(migrationGuard)) {
|
|
8500
8294
|
spinner.fail("Migration in progress");
|
|
8501
8295
|
console.error(chalk13.red("\n\u274C A migration is in progress. Run `coherent migrate --rollback` to undo first."));
|
|
8502
8296
|
bail("Migration in progress");
|
|
@@ -8654,7 +8448,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
8654
8448
|
let manifest = await loadManifest8(project.root);
|
|
8655
8449
|
const validShared = manifest.shared.filter((s) => {
|
|
8656
8450
|
const fp = resolve10(project.root, s.file);
|
|
8657
|
-
return
|
|
8451
|
+
return existsSync16(fp);
|
|
8658
8452
|
});
|
|
8659
8453
|
if (validShared.length !== manifest.shared.length) {
|
|
8660
8454
|
const cleaned = manifest.shared.length - validShared.length;
|
|
@@ -8875,8 +8669,8 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
8875
8669
|
for (const entry of manifest.shared) {
|
|
8876
8670
|
try {
|
|
8877
8671
|
const sharedPath = resolve10(projectRoot, entry.file);
|
|
8878
|
-
if (
|
|
8879
|
-
const sharedCode =
|
|
8672
|
+
if (existsSync16(sharedPath)) {
|
|
8673
|
+
const sharedCode = readFileSync11(sharedPath, "utf-8");
|
|
8880
8674
|
const sharedImports = sharedCode.matchAll(/@\/components\/ui\/([a-z0-9-]+)/g);
|
|
8881
8675
|
for (const m of sharedImports) {
|
|
8882
8676
|
if (m[1]) allNeededComponentIds.add(m[1]);
|
|
@@ -8896,8 +8690,8 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
8896
8690
|
const missingComponents = [];
|
|
8897
8691
|
for (const componentId of allNeededComponentIds) {
|
|
8898
8692
|
const isRegistered = !!cm.read(componentId);
|
|
8899
|
-
const filePath =
|
|
8900
|
-
const fileExists =
|
|
8693
|
+
const filePath = join10(projectRoot, "components", "ui", `${componentId}.tsx`);
|
|
8694
|
+
const fileExists = existsSync16(filePath);
|
|
8901
8695
|
if (DEBUG4) console.log(chalk13.gray(` Checking ${componentId}: registered=${isRegistered} file=${fileExists}`));
|
|
8902
8696
|
if (!isRegistered || !fileExists) {
|
|
8903
8697
|
missingComponents.push(componentId);
|
|
@@ -9045,9 +8839,9 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
9045
8839
|
const route = page.route || `/${page.id || "page"}`;
|
|
9046
8840
|
const pageFilePath = routeToFsPath(projectRoot, route, false);
|
|
9047
8841
|
let pageCode = "";
|
|
9048
|
-
if (
|
|
8842
|
+
if (existsSync16(pageFilePath)) {
|
|
9049
8843
|
try {
|
|
9050
|
-
pageCode =
|
|
8844
|
+
pageCode = readFileSync11(pageFilePath, "utf-8");
|
|
9051
8845
|
} catch {
|
|
9052
8846
|
}
|
|
9053
8847
|
}
|
|
@@ -9067,8 +8861,8 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
9067
8861
|
}
|
|
9068
8862
|
const missingRoutes = [...allLinkedRoutes].filter((route) => {
|
|
9069
8863
|
if (expandedExisting.has(route)) return false;
|
|
9070
|
-
if (
|
|
9071
|
-
if (
|
|
8864
|
+
if (existsSync16(routeToFsPath(projectRoot, route, false))) return false;
|
|
8865
|
+
if (existsSync16(routeToFsPath(projectRoot, route, true))) return false;
|
|
9072
8866
|
return true;
|
|
9073
8867
|
});
|
|
9074
8868
|
const SCAFFOLD_AI_LIMIT = 10;
|
|
@@ -9133,7 +8927,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
9133
8927
|
const filePath = routeToFsPath(projectRoot, linkedRoute, isAuth);
|
|
9134
8928
|
if (isAuth) await ensureAuthRouteGroup(projectRoot);
|
|
9135
8929
|
const dir = resolve10(filePath, "..");
|
|
9136
|
-
if (!
|
|
8930
|
+
if (!existsSync16(dir)) {
|
|
9137
8931
|
mkdirSync6(dir, { recursive: true });
|
|
9138
8932
|
}
|
|
9139
8933
|
const placeholderCode = `export default function ${pageName.replace(/\s/g, "")}Page() {
|
|
@@ -9166,7 +8960,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
9166
8960
|
for (const mod of result.modified) {
|
|
9167
8961
|
if (mod.startsWith("app/") && mod.endsWith("/page.tsx")) {
|
|
9168
8962
|
try {
|
|
9169
|
-
const code =
|
|
8963
|
+
const code = readFileSync11(resolve10(projectRoot, mod), "utf-8");
|
|
9170
8964
|
const issues = validatePageQuality(code, allRoutes).filter(
|
|
9171
8965
|
(i) => i.type === "BROKEN_INTERNAL_LINK"
|
|
9172
8966
|
);
|
|
@@ -9249,13 +9043,13 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
9249
9043
|
const sharedDir = resolve10(projectRoot, "components", "shared");
|
|
9250
9044
|
const layoutFile = resolve10(projectRoot, "app", "layout.tsx");
|
|
9251
9045
|
const filesToHash = [layoutFile];
|
|
9252
|
-
if (
|
|
9046
|
+
if (existsSync16(sharedDir)) {
|
|
9253
9047
|
for (const f of readdirSync4(sharedDir)) {
|
|
9254
9048
|
if (f.endsWith(".tsx")) filesToHash.push(resolve10(sharedDir, f));
|
|
9255
9049
|
}
|
|
9256
9050
|
}
|
|
9257
9051
|
for (const filePath of filesToHash) {
|
|
9258
|
-
if (
|
|
9052
|
+
if (existsSync16(filePath)) {
|
|
9259
9053
|
const rel = relative2(projectRoot, filePath);
|
|
9260
9054
|
updatedHashes[rel] = await computeFileHash(filePath);
|
|
9261
9055
|
}
|
|
@@ -9270,8 +9064,8 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
9270
9064
|
let manifestChanged = false;
|
|
9271
9065
|
for (const entry of currentManifest.shared) {
|
|
9272
9066
|
const fullPath = resolve10(projectRoot, entry.file);
|
|
9273
|
-
if (!
|
|
9274
|
-
const code =
|
|
9067
|
+
if (!existsSync16(fullPath)) continue;
|
|
9068
|
+
const code = readFileSync11(fullPath, "utf-8");
|
|
9275
9069
|
const props = extractPropsInterface(code);
|
|
9276
9070
|
const deps = extractDependencies(code);
|
|
9277
9071
|
if (props && props !== entry.propsInterface || deps.length !== (entry.dependencies?.length ?? 0)) {
|
|
@@ -9285,8 +9079,8 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
9285
9079
|
const pageFiles = Array.from(allModified).filter((f) => f.startsWith("app/") && f.endsWith("page.tsx"));
|
|
9286
9080
|
for (const pageFile of pageFiles) {
|
|
9287
9081
|
const fullPath = resolve10(projectRoot, pageFile);
|
|
9288
|
-
if (!
|
|
9289
|
-
const pageCode =
|
|
9082
|
+
if (!existsSync16(fullPath)) continue;
|
|
9083
|
+
const pageCode = readFileSync11(fullPath, "utf-8");
|
|
9290
9084
|
for (const entry of currentManifest.shared) {
|
|
9291
9085
|
const isUsed = pageCode.includes(`from '@/components/shared/`) && (pageCode.includes(`{ ${entry.name} }`) || pageCode.includes(`{ ${entry.name},`));
|
|
9292
9086
|
if (isUsed && !entry.usedIn.includes(pageFile)) {
|
|
@@ -9343,7 +9137,7 @@ Return JSON: { "requests": [{ "type": "add-page", "changes": { "name": "${compon
|
|
|
9343
9137
|
${uxRecommendations}
|
|
9344
9138
|
`;
|
|
9345
9139
|
try {
|
|
9346
|
-
if (!
|
|
9140
|
+
if (!existsSync16(recPath)) {
|
|
9347
9141
|
await writeFile(
|
|
9348
9142
|
recPath,
|
|
9349
9143
|
"# UX/UI Recommendations\n\nRecommendations are added here when you use `coherent chat` and the AI suggests improvements.\n"
|
|
@@ -9417,9 +9211,9 @@ ${uxRecommendations}
|
|
|
9417
9211
|
import chalk14 from "chalk";
|
|
9418
9212
|
import ora3 from "ora";
|
|
9419
9213
|
import { spawn } from "child_process";
|
|
9420
|
-
import { existsSync as
|
|
9421
|
-
import { resolve as resolve11, join as
|
|
9422
|
-
import { readdir
|
|
9214
|
+
import { existsSync as existsSync19, rmSync as rmSync3, readFileSync as readFileSync14, writeFileSync as writeFileSync10, readdirSync as readdirSync6 } from "fs";
|
|
9215
|
+
import { resolve as resolve11, join as join13 } from "path";
|
|
9216
|
+
import { readdir } from "fs/promises";
|
|
9423
9217
|
|
|
9424
9218
|
// src/utils/css-validator.ts
|
|
9425
9219
|
var REQUIRED_THEME_TOKENS = [
|
|
@@ -9473,13 +9267,13 @@ function validateV4GlobalsCss(css) {
|
|
|
9473
9267
|
import { DesignSystemManager as DesignSystemManager8, ComponentGenerator as ComponentGenerator3 } from "@getcoherent/core";
|
|
9474
9268
|
|
|
9475
9269
|
// src/utils/file-watcher.ts
|
|
9476
|
-
import { readFileSync as
|
|
9477
|
-
import { relative as relative4, join as
|
|
9270
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9, existsSync as existsSync18 } from "fs";
|
|
9271
|
+
import { relative as relative4, join as join12 } from "path";
|
|
9478
9272
|
import { loadManifest as loadManifest9, saveManifest as saveManifest4 } from "@getcoherent/core";
|
|
9479
9273
|
|
|
9480
9274
|
// src/utils/component-integrity.ts
|
|
9481
|
-
import { existsSync as
|
|
9482
|
-
import { join as
|
|
9275
|
+
import { existsSync as existsSync17, readFileSync as readFileSync12, readdirSync as readdirSync5 } from "fs";
|
|
9276
|
+
import { join as join11, relative as relative3 } from "path";
|
|
9483
9277
|
function extractExportedComponentNames(code) {
|
|
9484
9278
|
const names = [];
|
|
9485
9279
|
let m;
|
|
@@ -9511,14 +9305,14 @@ function arraysEqual(a, b) {
|
|
|
9511
9305
|
}
|
|
9512
9306
|
function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
9513
9307
|
const results = [];
|
|
9514
|
-
const appDir =
|
|
9515
|
-
if (!
|
|
9308
|
+
const appDir = join11(projectRoot, "app");
|
|
9309
|
+
if (!existsSync17(appDir)) return results;
|
|
9516
9310
|
const pageFiles = collectFiles(appDir, (name) => name === "page.tsx" || name === "page.jsx");
|
|
9517
9311
|
const componentImportPath = componentFile.replace(/\.tsx$/, "").replace(/\.jsx$/, "");
|
|
9518
9312
|
for (const absPath of pageFiles) {
|
|
9519
9313
|
if (absPath.includes("design-system")) continue;
|
|
9520
9314
|
try {
|
|
9521
|
-
const code =
|
|
9315
|
+
const code = readFileSync12(absPath, "utf-8");
|
|
9522
9316
|
const hasNamedImport = new RegExp(`import\\s+\\{[^}]*\\b${componentName}\\b[^}]*\\}\\s+from\\s+['"]`).test(code);
|
|
9523
9317
|
const hasDefaultImport = new RegExp(`import\\s+${componentName}\\s+from\\s+['"]`).test(code);
|
|
9524
9318
|
const hasPathImport = code.includes(`@/${componentImportPath}`);
|
|
@@ -9531,10 +9325,10 @@ function findPagesImporting(projectRoot, componentName, componentFile) {
|
|
|
9531
9325
|
return results;
|
|
9532
9326
|
}
|
|
9533
9327
|
function isUsedInLayout(projectRoot, componentName) {
|
|
9534
|
-
const layoutPath =
|
|
9535
|
-
if (!
|
|
9328
|
+
const layoutPath = join11(projectRoot, "app", "layout.tsx");
|
|
9329
|
+
if (!existsSync17(layoutPath)) return false;
|
|
9536
9330
|
try {
|
|
9537
|
-
const code =
|
|
9331
|
+
const code = readFileSync12(layoutPath, "utf-8");
|
|
9538
9332
|
return code.includes(componentName);
|
|
9539
9333
|
} catch {
|
|
9540
9334
|
return false;
|
|
@@ -9542,8 +9336,8 @@ function isUsedInLayout(projectRoot, componentName) {
|
|
|
9542
9336
|
}
|
|
9543
9337
|
function findUnregisteredComponents(projectRoot, manifest) {
|
|
9544
9338
|
const results = [];
|
|
9545
|
-
const componentsDir =
|
|
9546
|
-
if (!
|
|
9339
|
+
const componentsDir = join11(projectRoot, "components");
|
|
9340
|
+
if (!existsSync17(componentsDir)) return results;
|
|
9547
9341
|
const registeredFiles = new Set(manifest.shared.map((s) => s.file));
|
|
9548
9342
|
const registeredNames = new Set(manifest.shared.map((s) => s.name));
|
|
9549
9343
|
const files = collectFiles(
|
|
@@ -9555,7 +9349,7 @@ function findUnregisteredComponents(projectRoot, manifest) {
|
|
|
9555
9349
|
const relFile = relative3(projectRoot, absPath);
|
|
9556
9350
|
if (registeredFiles.has(relFile)) continue;
|
|
9557
9351
|
try {
|
|
9558
|
-
const code =
|
|
9352
|
+
const code = readFileSync12(absPath, "utf-8");
|
|
9559
9353
|
const exports = extractExportedComponentNames(code);
|
|
9560
9354
|
for (const name of exports) {
|
|
9561
9355
|
if (registeredNames.has(name)) continue;
|
|
@@ -9570,14 +9364,14 @@ function findUnregisteredComponents(projectRoot, manifest) {
|
|
|
9570
9364
|
}
|
|
9571
9365
|
function findInlineDuplicates(projectRoot, manifest) {
|
|
9572
9366
|
const results = [];
|
|
9573
|
-
const appDir =
|
|
9574
|
-
if (!
|
|
9367
|
+
const appDir = join11(projectRoot, "app");
|
|
9368
|
+
if (!existsSync17(appDir)) return results;
|
|
9575
9369
|
const pageFiles = collectFiles(appDir, (name) => name === "page.tsx" || name === "page.jsx");
|
|
9576
9370
|
for (const absPath of pageFiles) {
|
|
9577
9371
|
if (absPath.includes("design-system")) continue;
|
|
9578
9372
|
let code;
|
|
9579
9373
|
try {
|
|
9580
|
-
code =
|
|
9374
|
+
code = readFileSync12(absPath, "utf-8");
|
|
9581
9375
|
} catch {
|
|
9582
9376
|
continue;
|
|
9583
9377
|
}
|
|
@@ -9600,8 +9394,8 @@ function findInlineDuplicates(projectRoot, manifest) {
|
|
|
9600
9394
|
return results;
|
|
9601
9395
|
}
|
|
9602
9396
|
function findComponentFileByExportName(projectRoot, componentName) {
|
|
9603
|
-
const componentsDir =
|
|
9604
|
-
if (!
|
|
9397
|
+
const componentsDir = join11(projectRoot, "components");
|
|
9398
|
+
if (!existsSync17(componentsDir)) return null;
|
|
9605
9399
|
const files = collectFiles(
|
|
9606
9400
|
componentsDir,
|
|
9607
9401
|
(name) => (name.endsWith(".tsx") || name.endsWith(".jsx")) && !name.startsWith("."),
|
|
@@ -9609,7 +9403,7 @@ function findComponentFileByExportName(projectRoot, componentName) {
|
|
|
9609
9403
|
);
|
|
9610
9404
|
for (const absPath of files) {
|
|
9611
9405
|
try {
|
|
9612
|
-
const code =
|
|
9406
|
+
const code = readFileSync12(absPath, "utf-8");
|
|
9613
9407
|
const exports = extractExportedComponentNames(code);
|
|
9614
9408
|
if (exports.includes(componentName)) {
|
|
9615
9409
|
return relative3(projectRoot, absPath);
|
|
@@ -9622,8 +9416,8 @@ function findComponentFileByExportName(projectRoot, componentName) {
|
|
|
9622
9416
|
function removeOrphanedEntries(projectRoot, manifest) {
|
|
9623
9417
|
const removed = [];
|
|
9624
9418
|
const valid = manifest.shared.filter((entry) => {
|
|
9625
|
-
const filePath =
|
|
9626
|
-
if (
|
|
9419
|
+
const filePath = join11(projectRoot, entry.file);
|
|
9420
|
+
if (existsSync17(filePath)) return true;
|
|
9627
9421
|
removed.push({ id: entry.id, name: entry.name });
|
|
9628
9422
|
return false;
|
|
9629
9423
|
});
|
|
@@ -9641,8 +9435,8 @@ function reconcileComponents(projectRoot, manifest) {
|
|
|
9641
9435
|
};
|
|
9642
9436
|
const m = { ...manifest, shared: [...manifest.shared], nextId: manifest.nextId };
|
|
9643
9437
|
m.shared = m.shared.filter((entry) => {
|
|
9644
|
-
const filePath =
|
|
9645
|
-
if (!
|
|
9438
|
+
const filePath = join11(projectRoot, entry.file);
|
|
9439
|
+
if (!existsSync17(filePath)) {
|
|
9646
9440
|
const newPath = findComponentFileByExportName(projectRoot, entry.name);
|
|
9647
9441
|
if (newPath) {
|
|
9648
9442
|
result.updated.push({ id: entry.id, field: "file", from: entry.file, to: newPath });
|
|
@@ -9654,7 +9448,7 @@ function reconcileComponents(projectRoot, manifest) {
|
|
|
9654
9448
|
}
|
|
9655
9449
|
let code;
|
|
9656
9450
|
try {
|
|
9657
|
-
code =
|
|
9451
|
+
code = readFileSync12(join11(projectRoot, entry.file), "utf-8");
|
|
9658
9452
|
} catch {
|
|
9659
9453
|
return true;
|
|
9660
9454
|
}
|
|
@@ -9738,7 +9532,7 @@ function collectFiles(dir, filter, skipDirs = []) {
|
|
|
9738
9532
|
return;
|
|
9739
9533
|
}
|
|
9740
9534
|
for (const e of entries) {
|
|
9741
|
-
const full =
|
|
9535
|
+
const full = join11(d, e.name);
|
|
9742
9536
|
if (e.isDirectory()) {
|
|
9743
9537
|
if (skipDirs.includes(e.name) || e.name.startsWith(".")) continue;
|
|
9744
9538
|
walk(full);
|
|
@@ -9777,9 +9571,9 @@ function findInlineDuplicatesOfShared(content, manifest) {
|
|
|
9777
9571
|
}
|
|
9778
9572
|
function getWatcherConfig(projectRoot) {
|
|
9779
9573
|
try {
|
|
9780
|
-
const pkgPath =
|
|
9781
|
-
if (!
|
|
9782
|
-
const pkg = JSON.parse(
|
|
9574
|
+
const pkgPath = join12(projectRoot, "package.json");
|
|
9575
|
+
if (!existsSync18(pkgPath)) return defaultWatcherConfig();
|
|
9576
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
9783
9577
|
const c = pkg?.coherent?.watcher ?? {};
|
|
9784
9578
|
return {
|
|
9785
9579
|
enabled: c.enabled !== false,
|
|
@@ -9807,7 +9601,7 @@ async function handleFileChange(projectRoot, filePath) {
|
|
|
9807
9601
|
if (relativePath.includes("node_modules") || relativePath.includes(".next")) return;
|
|
9808
9602
|
let content;
|
|
9809
9603
|
try {
|
|
9810
|
-
content =
|
|
9604
|
+
content = readFileSync13(filePath, "utf-8");
|
|
9811
9605
|
} catch {
|
|
9812
9606
|
return;
|
|
9813
9607
|
}
|
|
@@ -9880,7 +9674,7 @@ async function detectNewComponent(projectRoot, filePath) {
|
|
|
9880
9674
|
const manifest = await loadManifest9(projectRoot);
|
|
9881
9675
|
const alreadyRegistered = manifest.shared.some((s) => s.file === relativePath);
|
|
9882
9676
|
if (alreadyRegistered) return;
|
|
9883
|
-
const code =
|
|
9677
|
+
const code = readFileSync13(filePath, "utf-8");
|
|
9884
9678
|
const exports = extractExportedComponentNames(code);
|
|
9885
9679
|
if (exports.length > 0) {
|
|
9886
9680
|
const alreadyByName = exports.every((n) => manifest.shared.some((s) => s.name === n));
|
|
@@ -9906,8 +9700,8 @@ function startFileWatcher(projectRoot) {
|
|
|
9906
9700
|
let watcher = null;
|
|
9907
9701
|
let manifestWatcher = null;
|
|
9908
9702
|
import("chokidar").then((chokidar) => {
|
|
9909
|
-
const appGlob =
|
|
9910
|
-
const compGlob =
|
|
9703
|
+
const appGlob = join12(projectRoot, "app", "**", "*.tsx");
|
|
9704
|
+
const compGlob = join12(projectRoot, "components", "**", "*.tsx");
|
|
9911
9705
|
watcher = chokidar.default.watch([appGlob, compGlob], {
|
|
9912
9706
|
ignoreInitial: true,
|
|
9913
9707
|
awaitWriteFinish: { stabilityThreshold: 500 }
|
|
@@ -9919,8 +9713,8 @@ function startFileWatcher(projectRoot) {
|
|
|
9919
9713
|
});
|
|
9920
9714
|
watcher.on("unlink", (fp) => handleFileDelete(projectRoot, fp));
|
|
9921
9715
|
});
|
|
9922
|
-
const manifestPath =
|
|
9923
|
-
if (
|
|
9716
|
+
const manifestPath = join12(projectRoot, "coherent.components.json");
|
|
9717
|
+
if (existsSync18(manifestPath)) {
|
|
9924
9718
|
import("chokidar").then((chokidar) => {
|
|
9925
9719
|
manifestWatcher = chokidar.default.watch(manifestPath, { ignoreInitial: true });
|
|
9926
9720
|
manifestWatcher.on("change", () => handleManifestChange(projectRoot));
|
|
@@ -9934,7 +9728,7 @@ function startFileWatcher(projectRoot) {
|
|
|
9934
9728
|
|
|
9935
9729
|
// src/commands/preview.ts
|
|
9936
9730
|
function getPackageManager(projectRoot) {
|
|
9937
|
-
const hasPnpm =
|
|
9731
|
+
const hasPnpm = existsSync19(resolve11(projectRoot, "pnpm-lock.yaml"));
|
|
9938
9732
|
return hasPnpm ? "pnpm" : "npm";
|
|
9939
9733
|
}
|
|
9940
9734
|
function runInstall(projectRoot) {
|
|
@@ -9958,21 +9752,21 @@ function runInstall(projectRoot) {
|
|
|
9958
9752
|
function checkProjectInitialized(projectRoot) {
|
|
9959
9753
|
const configPath = resolve11(projectRoot, "design-system.config.ts");
|
|
9960
9754
|
const packageJsonPath = resolve11(projectRoot, "package.json");
|
|
9961
|
-
if (!
|
|
9755
|
+
if (!existsSync19(configPath)) {
|
|
9962
9756
|
return false;
|
|
9963
9757
|
}
|
|
9964
|
-
if (!
|
|
9758
|
+
if (!existsSync19(packageJsonPath)) {
|
|
9965
9759
|
return false;
|
|
9966
9760
|
}
|
|
9967
9761
|
return true;
|
|
9968
9762
|
}
|
|
9969
9763
|
function checkDependenciesInstalled(projectRoot) {
|
|
9970
9764
|
const nodeModulesPath = resolve11(projectRoot, "node_modules");
|
|
9971
|
-
return
|
|
9765
|
+
return existsSync19(nodeModulesPath);
|
|
9972
9766
|
}
|
|
9973
9767
|
function clearStaleCache(projectRoot) {
|
|
9974
|
-
const nextDir =
|
|
9975
|
-
if (
|
|
9768
|
+
const nextDir = join13(projectRoot, ".next");
|
|
9769
|
+
if (existsSync19(nextDir)) {
|
|
9976
9770
|
rmSync3(nextDir, { recursive: true, force: true });
|
|
9977
9771
|
console.log(chalk14.dim(" \u2714 Cleared stale build cache"));
|
|
9978
9772
|
}
|
|
@@ -9988,11 +9782,11 @@ async function preflightDependencyCheck(projectRoot) {
|
|
|
9988
9782
|
}
|
|
9989
9783
|
async function listPageFiles(appDir) {
|
|
9990
9784
|
const out = [];
|
|
9991
|
-
if (!
|
|
9785
|
+
if (!existsSync19(appDir)) return out;
|
|
9992
9786
|
async function walk(dir) {
|
|
9993
|
-
const entries = await
|
|
9787
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
9994
9788
|
for (const e of entries) {
|
|
9995
|
-
const full =
|
|
9789
|
+
const full = join13(dir, e.name);
|
|
9996
9790
|
if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "api" && e.name !== "design-system") await walk(full);
|
|
9997
9791
|
else if (e.isFile() && e.name === "page.tsx") out.push(full);
|
|
9998
9792
|
}
|
|
@@ -10001,10 +9795,10 @@ async function listPageFiles(appDir) {
|
|
|
10001
9795
|
return out;
|
|
10002
9796
|
}
|
|
10003
9797
|
async function validateSyntax(projectRoot) {
|
|
10004
|
-
const appDir =
|
|
9798
|
+
const appDir = join13(projectRoot, "app");
|
|
10005
9799
|
const pages = await listPageFiles(appDir);
|
|
10006
9800
|
for (const file of pages) {
|
|
10007
|
-
const content =
|
|
9801
|
+
const content = readFileSync14(file, "utf-8");
|
|
10008
9802
|
const fixed = fixUnescapedLtInJsx(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)));
|
|
10009
9803
|
if (fixed !== content) {
|
|
10010
9804
|
writeFileSync10(file, fixed, "utf-8");
|
|
@@ -10013,18 +9807,18 @@ async function validateSyntax(projectRoot) {
|
|
|
10013
9807
|
}
|
|
10014
9808
|
}
|
|
10015
9809
|
async function fixMissingComponentExports(projectRoot) {
|
|
10016
|
-
const appDir =
|
|
10017
|
-
const uiDir =
|
|
10018
|
-
if (!
|
|
9810
|
+
const appDir = join13(projectRoot, "app");
|
|
9811
|
+
const uiDir = join13(projectRoot, "components", "ui");
|
|
9812
|
+
if (!existsSync19(appDir) || !existsSync19(uiDir)) return;
|
|
10019
9813
|
const pages = await listPageFiles(appDir);
|
|
10020
|
-
const sharedDir =
|
|
10021
|
-
if (
|
|
10022
|
-
const sharedFiles = readdirSync6(sharedDir).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts")).map((f) =>
|
|
9814
|
+
const sharedDir = join13(projectRoot, "components", "shared");
|
|
9815
|
+
if (existsSync19(sharedDir)) {
|
|
9816
|
+
const sharedFiles = readdirSync6(sharedDir).filter((f) => f.endsWith(".tsx") || f.endsWith(".ts")).map((f) => join13(sharedDir, f));
|
|
10023
9817
|
pages.push(...sharedFiles);
|
|
10024
9818
|
}
|
|
10025
9819
|
const neededExports = /* @__PURE__ */ new Map();
|
|
10026
9820
|
for (const file of pages) {
|
|
10027
|
-
const content =
|
|
9821
|
+
const content = readFileSync14(file, "utf-8");
|
|
10028
9822
|
const importRe = /import\s*\{([^}]+)\}\s*from\s*['"]@\/components\/ui\/([^'"]+)['"]/g;
|
|
10029
9823
|
let m;
|
|
10030
9824
|
while ((m = importRe.exec(content)) !== null) {
|
|
@@ -10034,7 +9828,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10034
9828
|
for (const name of names) neededExports.get(componentId).add(name);
|
|
10035
9829
|
}
|
|
10036
9830
|
}
|
|
10037
|
-
const configPath =
|
|
9831
|
+
const configPath = join13(projectRoot, "design-system.config.ts");
|
|
10038
9832
|
let config2 = null;
|
|
10039
9833
|
try {
|
|
10040
9834
|
const mgr = new DesignSystemManager8(configPath);
|
|
@@ -10044,8 +9838,8 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10044
9838
|
const generator = new ComponentGenerator3(config2 || { components: [], pages: [], tokens: {} });
|
|
10045
9839
|
const provider = getComponentProvider();
|
|
10046
9840
|
for (const [componentId, needed] of neededExports) {
|
|
10047
|
-
const componentFile =
|
|
10048
|
-
if (!
|
|
9841
|
+
const componentFile = join13(uiDir, `${componentId}.tsx`);
|
|
9842
|
+
if (!existsSync19(componentFile)) {
|
|
10049
9843
|
if (provider.has(componentId)) {
|
|
10050
9844
|
try {
|
|
10051
9845
|
const result = await provider.installComponent(componentId, projectRoot);
|
|
@@ -10068,7 +9862,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10068
9862
|
}
|
|
10069
9863
|
continue;
|
|
10070
9864
|
}
|
|
10071
|
-
const content =
|
|
9865
|
+
const content = readFileSync14(componentFile, "utf-8");
|
|
10072
9866
|
const exportRe = /export\s+(?:const|function|class)\s+(\w+)|export\s*\{([^}]+)\}/g;
|
|
10073
9867
|
const existingExports = /* @__PURE__ */ new Set();
|
|
10074
9868
|
let em;
|
|
@@ -10102,7 +9896,7 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10102
9896
|
}
|
|
10103
9897
|
const neededSharedExports = /* @__PURE__ */ new Map();
|
|
10104
9898
|
for (const file of pages) {
|
|
10105
|
-
const content =
|
|
9899
|
+
const content = readFileSync14(file, "utf-8");
|
|
10106
9900
|
const sharedImportRe = /import\s*\{([^}]+)\}\s*from\s*['"]@\/components\/shared\/([^'"]+)['"]/g;
|
|
10107
9901
|
let sm;
|
|
10108
9902
|
while ((sm = sharedImportRe.exec(content)) !== null) {
|
|
@@ -10113,9 +9907,9 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10113
9907
|
}
|
|
10114
9908
|
}
|
|
10115
9909
|
for (const [componentId, needed] of neededSharedExports) {
|
|
10116
|
-
const componentFile =
|
|
10117
|
-
if (!
|
|
10118
|
-
let content =
|
|
9910
|
+
const componentFile = join13(sharedDir, `${componentId}.tsx`);
|
|
9911
|
+
if (!existsSync19(componentFile)) continue;
|
|
9912
|
+
let content = readFileSync14(componentFile, "utf-8");
|
|
10119
9913
|
const exportRe = /export\s+(?:const|function|class)\s+(\w+)|export\s*\{([^}]+)\}/g;
|
|
10120
9914
|
const existingExports = /* @__PURE__ */ new Set();
|
|
10121
9915
|
let em;
|
|
@@ -10137,8 +9931,8 @@ async function fixMissingComponentExports(projectRoot) {
|
|
|
10137
9931
|
}
|
|
10138
9932
|
}
|
|
10139
9933
|
async function backfillPageAnalysis(projectRoot) {
|
|
10140
|
-
const configPath =
|
|
10141
|
-
if (!
|
|
9934
|
+
const configPath = join13(projectRoot, "design-system.config.ts");
|
|
9935
|
+
if (!existsSync19(configPath)) return;
|
|
10142
9936
|
try {
|
|
10143
9937
|
const mgr = new DesignSystemManager8(configPath);
|
|
10144
9938
|
const config2 = mgr.getConfig();
|
|
@@ -10149,14 +9943,14 @@ async function backfillPageAnalysis(projectRoot) {
|
|
|
10149
9943
|
const isAuth = route.includes("login") || route.includes("register") || route.includes("signup") || route.includes("sign-up");
|
|
10150
9944
|
let filePath;
|
|
10151
9945
|
if (route === "/") {
|
|
10152
|
-
filePath =
|
|
9946
|
+
filePath = join13(projectRoot, "app", "page.tsx");
|
|
10153
9947
|
} else if (isAuth) {
|
|
10154
|
-
filePath =
|
|
9948
|
+
filePath = join13(projectRoot, "app", "(auth)", route.slice(1), "page.tsx");
|
|
10155
9949
|
} else {
|
|
10156
|
-
filePath =
|
|
9950
|
+
filePath = join13(projectRoot, "app", route.slice(1), "page.tsx");
|
|
10157
9951
|
}
|
|
10158
|
-
if (!
|
|
10159
|
-
const code =
|
|
9952
|
+
if (!existsSync19(filePath)) continue;
|
|
9953
|
+
const code = readFileSync14(filePath, "utf-8");
|
|
10160
9954
|
if (code.length < 50) continue;
|
|
10161
9955
|
page.pageAnalysis = analyzePageCode(code);
|
|
10162
9956
|
changed = true;
|
|
@@ -10307,11 +10101,11 @@ async function openBrowser(url) {
|
|
|
10307
10101
|
}
|
|
10308
10102
|
function startDevServer(projectRoot) {
|
|
10309
10103
|
const packageJsonPath = resolve11(projectRoot, "package.json");
|
|
10310
|
-
if (!
|
|
10104
|
+
if (!existsSync19(packageJsonPath)) {
|
|
10311
10105
|
throw new Error('package.json not found. Run "coherent init" first.');
|
|
10312
10106
|
}
|
|
10313
|
-
const hasPnpm =
|
|
10314
|
-
const hasNpm =
|
|
10107
|
+
const hasPnpm = existsSync19(resolve11(projectRoot, "pnpm-lock.yaml"));
|
|
10108
|
+
const hasNpm = existsSync19(resolve11(projectRoot, "package-lock.json"));
|
|
10315
10109
|
const command = hasPnpm ? "pnpm" : hasNpm ? "npm" : "npx";
|
|
10316
10110
|
const args = hasPnpm ? ["dev", "--turbo"] : hasNpm ? ["run", "dev", "--", "--turbo"] : ["next", "dev", "--turbo"];
|
|
10317
10111
|
const child = spawn(command, args, {
|
|
@@ -10369,8 +10163,8 @@ async function previewCommand() {
|
|
|
10369
10163
|
}
|
|
10370
10164
|
if (isTailwindV4(projectRoot)) {
|
|
10371
10165
|
const globalsPath = resolve11(projectRoot, "app", "globals.css");
|
|
10372
|
-
if (
|
|
10373
|
-
const globalsContent =
|
|
10166
|
+
if (existsSync19(globalsPath)) {
|
|
10167
|
+
const globalsContent = readFileSync14(globalsPath, "utf-8");
|
|
10374
10168
|
const cssIssues = validateV4GlobalsCss(globalsContent);
|
|
10375
10169
|
if (cssIssues.length > 0) {
|
|
10376
10170
|
console.log(chalk14.yellow("\n\u26A0\uFE0F globals.css validation warnings:"));
|
|
@@ -10411,9 +10205,9 @@ async function previewCommand() {
|
|
|
10411
10205
|
import chalk15 from "chalk";
|
|
10412
10206
|
import ora4 from "ora";
|
|
10413
10207
|
import { spawn as spawn2 } from "child_process";
|
|
10414
|
-
import { existsSync as
|
|
10415
|
-
import { resolve as resolve12, join as
|
|
10416
|
-
import { readdir as
|
|
10208
|
+
import { existsSync as existsSync20, rmSync as rmSync4, readdirSync as readdirSync7 } from "fs";
|
|
10209
|
+
import { resolve as resolve12, join as join14, dirname as dirname7 } from "path";
|
|
10210
|
+
import { readdir as readdir2, readFile as readFile5, writeFile as writeFile5, mkdir as mkdir5, copyFile as copyFile2 } from "fs/promises";
|
|
10417
10211
|
var COPY_EXCLUDE = /* @__PURE__ */ new Set([
|
|
10418
10212
|
"node_modules",
|
|
10419
10213
|
".next",
|
|
@@ -10432,10 +10226,10 @@ var COPY_EXCLUDE = /* @__PURE__ */ new Set([
|
|
|
10432
10226
|
]);
|
|
10433
10227
|
async function copyDir(src, dest) {
|
|
10434
10228
|
await mkdir5(dest, { recursive: true });
|
|
10435
|
-
const entries = await
|
|
10229
|
+
const entries = await readdir2(src, { withFileTypes: true });
|
|
10436
10230
|
for (const e of entries) {
|
|
10437
|
-
const srcPath =
|
|
10438
|
-
const destPath =
|
|
10231
|
+
const srcPath = join14(src, e.name);
|
|
10232
|
+
const destPath = join14(dest, e.name);
|
|
10439
10233
|
if (COPY_EXCLUDE.has(e.name)) continue;
|
|
10440
10234
|
if (e.isDirectory()) {
|
|
10441
10235
|
await copyDir(srcPath, destPath);
|
|
@@ -10446,18 +10240,18 @@ async function copyDir(src, dest) {
|
|
|
10446
10240
|
}
|
|
10447
10241
|
}
|
|
10448
10242
|
function checkProjectInitialized2(projectRoot) {
|
|
10449
|
-
return
|
|
10243
|
+
return existsSync20(resolve12(projectRoot, "design-system.config.ts")) && existsSync20(resolve12(projectRoot, "package.json"));
|
|
10450
10244
|
}
|
|
10451
10245
|
function getPackageManager2(projectRoot) {
|
|
10452
|
-
if (
|
|
10453
|
-
if (
|
|
10246
|
+
if (existsSync20(resolve12(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
|
|
10247
|
+
if (existsSync20(resolve12(projectRoot, "package-lock.json"))) return "npm";
|
|
10454
10248
|
return "npx";
|
|
10455
10249
|
}
|
|
10456
10250
|
async function patchNextConfigForExport(outRoot) {
|
|
10457
10251
|
for (const name of ["next.config.ts", "next.config.mjs", "next.config.js"]) {
|
|
10458
|
-
const p =
|
|
10459
|
-
if (!
|
|
10460
|
-
let content = await
|
|
10252
|
+
const p = join14(outRoot, name);
|
|
10253
|
+
if (!existsSync20(p)) continue;
|
|
10254
|
+
let content = await readFile5(p, "utf-8");
|
|
10461
10255
|
if (content.includes("ignoreDuringBuilds")) return;
|
|
10462
10256
|
content = content.replace(
|
|
10463
10257
|
/(const\s+nextConfig\s*(?::\s*\w+)?\s*=\s*\{)/,
|
|
@@ -10497,10 +10291,10 @@ EXPOSE 3000
|
|
|
10497
10291
|
\`\`\`
|
|
10498
10292
|
`;
|
|
10499
10293
|
async function ensureReadmeDeploySection(outRoot) {
|
|
10500
|
-
const readmePath =
|
|
10501
|
-
if (!
|
|
10294
|
+
const readmePath = join14(outRoot, "README.md");
|
|
10295
|
+
if (!existsSync20(readmePath)) return;
|
|
10502
10296
|
try {
|
|
10503
|
-
let content = await
|
|
10297
|
+
let content = await readFile5(readmePath, "utf-8");
|
|
10504
10298
|
if (/##\s+Deploy\b/m.test(content)) return;
|
|
10505
10299
|
content = content.trimEnd() + DEPLOY_SECTION + "\n";
|
|
10506
10300
|
await writeFile5(readmePath, content);
|
|
@@ -10512,25 +10306,25 @@ async function countPages(outRoot) {
|
|
|
10512
10306
|
async function walk(dir) {
|
|
10513
10307
|
let entries;
|
|
10514
10308
|
try {
|
|
10515
|
-
entries = await
|
|
10309
|
+
entries = await readdir2(dir, { withFileTypes: true });
|
|
10516
10310
|
} catch {
|
|
10517
10311
|
return;
|
|
10518
10312
|
}
|
|
10519
10313
|
for (const e of entries) {
|
|
10520
|
-
const full =
|
|
10314
|
+
const full = join14(dir, e.name);
|
|
10521
10315
|
if (e.isFile() && e.name === "page.tsx") n++;
|
|
10522
10316
|
else if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "api") await walk(full);
|
|
10523
10317
|
}
|
|
10524
10318
|
}
|
|
10525
|
-
const appDir =
|
|
10526
|
-
if (
|
|
10319
|
+
const appDir = join14(outRoot, "app");
|
|
10320
|
+
if (existsSync20(appDir)) await walk(appDir);
|
|
10527
10321
|
return n;
|
|
10528
10322
|
}
|
|
10529
10323
|
function countComponents(outRoot) {
|
|
10530
10324
|
let n = 0;
|
|
10531
10325
|
for (const sub of ["ui", "shared"]) {
|
|
10532
|
-
const dir =
|
|
10533
|
-
if (!
|
|
10326
|
+
const dir = join14(outRoot, "components", sub);
|
|
10327
|
+
if (!existsSync20(dir)) continue;
|
|
10534
10328
|
try {
|
|
10535
10329
|
n += readdirSync7(dir).filter((f) => f.endsWith(".tsx") || f.endsWith(".jsx")).length;
|
|
10536
10330
|
} catch {
|
|
@@ -10538,14 +10332,14 @@ function countComponents(outRoot) {
|
|
|
10538
10332
|
}
|
|
10539
10333
|
return n;
|
|
10540
10334
|
}
|
|
10541
|
-
var
|
|
10542
|
-
async function
|
|
10335
|
+
var IMPORT_FROM_REGEX = /from\s+['"]([^'"]+)['"]/g;
|
|
10336
|
+
async function collectImportedPackages(dir, extensions) {
|
|
10543
10337
|
const packages = /* @__PURE__ */ new Set();
|
|
10544
|
-
if (!
|
|
10338
|
+
if (!existsSync20(dir)) return packages;
|
|
10545
10339
|
async function walk(d) {
|
|
10546
|
-
const entries = await
|
|
10340
|
+
const entries = await readdir2(d, { withFileTypes: true });
|
|
10547
10341
|
for (const e of entries) {
|
|
10548
|
-
const full =
|
|
10342
|
+
const full = join14(d, e.name);
|
|
10549
10343
|
if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules") {
|
|
10550
10344
|
await walk(full);
|
|
10551
10345
|
continue;
|
|
@@ -10553,10 +10347,10 @@ async function collectImportedPackages2(dir, extensions) {
|
|
|
10553
10347
|
if (!e.isFile()) continue;
|
|
10554
10348
|
const ext = e.name.replace(/^.*\./, "");
|
|
10555
10349
|
if (!extensions.has(ext)) continue;
|
|
10556
|
-
const content = await
|
|
10350
|
+
const content = await readFile5(full, "utf-8").catch(() => "");
|
|
10557
10351
|
let m;
|
|
10558
|
-
|
|
10559
|
-
while ((m =
|
|
10352
|
+
IMPORT_FROM_REGEX.lastIndex = 0;
|
|
10353
|
+
while ((m = IMPORT_FROM_REGEX.exec(content)) !== null) {
|
|
10560
10354
|
const spec = m[1];
|
|
10561
10355
|
if (spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("@/")) continue;
|
|
10562
10356
|
const pkg = spec.startsWith("@") ? spec.split("/").slice(0, 2).join("/") : spec.split("/")[0];
|
|
@@ -10568,64 +10362,64 @@ async function collectImportedPackages2(dir, extensions) {
|
|
|
10568
10362
|
return packages;
|
|
10569
10363
|
}
|
|
10570
10364
|
async function findMissingDepsInExport(outRoot) {
|
|
10571
|
-
const pkgPath =
|
|
10572
|
-
if (!
|
|
10365
|
+
const pkgPath = join14(outRoot, "package.json");
|
|
10366
|
+
if (!existsSync20(pkgPath)) return [];
|
|
10573
10367
|
let pkg;
|
|
10574
10368
|
try {
|
|
10575
|
-
pkg = JSON.parse(await
|
|
10369
|
+
pkg = JSON.parse(await readFile5(pkgPath, "utf-8"));
|
|
10576
10370
|
} catch {
|
|
10577
10371
|
return [];
|
|
10578
10372
|
}
|
|
10579
10373
|
const inDeps = /* @__PURE__ */ new Set([...Object.keys(pkg.dependencies ?? {}), ...Object.keys(pkg.devDependencies ?? {})]);
|
|
10580
|
-
const codeDirs = [
|
|
10374
|
+
const codeDirs = [join14(outRoot, "app"), join14(outRoot, "components")];
|
|
10581
10375
|
const extensions = /* @__PURE__ */ new Set(["ts", "tsx", "js", "jsx"]);
|
|
10582
10376
|
const imported = /* @__PURE__ */ new Set();
|
|
10583
10377
|
for (const dir of codeDirs) {
|
|
10584
10378
|
;
|
|
10585
|
-
(await
|
|
10379
|
+
(await collectImportedPackages(dir, extensions)).forEach((p) => imported.add(p));
|
|
10586
10380
|
}
|
|
10587
10381
|
return [...imported].filter((p) => !inDeps.has(p)).sort();
|
|
10588
10382
|
}
|
|
10589
10383
|
async function stripCoherentArtifacts(outputDir) {
|
|
10590
10384
|
const removed = [];
|
|
10591
10385
|
for (const p of ["app/design-system", "app/api/design-system"]) {
|
|
10592
|
-
const full =
|
|
10593
|
-
if (
|
|
10386
|
+
const full = join14(outputDir, p);
|
|
10387
|
+
if (existsSync20(full)) {
|
|
10594
10388
|
rmSync4(full, { recursive: true, force: true });
|
|
10595
10389
|
removed.push(p);
|
|
10596
10390
|
}
|
|
10597
10391
|
}
|
|
10598
|
-
const appNavPath =
|
|
10599
|
-
if (
|
|
10392
|
+
const appNavPath = join14(outputDir, "app", "AppNav.tsx");
|
|
10393
|
+
if (existsSync20(appNavPath)) {
|
|
10600
10394
|
rmSync4(appNavPath, { force: true });
|
|
10601
10395
|
removed.push("app/AppNav.tsx");
|
|
10602
10396
|
}
|
|
10603
|
-
const layoutPath =
|
|
10604
|
-
if (
|
|
10605
|
-
let layout = await
|
|
10397
|
+
const layoutPath = join14(outputDir, "app", "layout.tsx");
|
|
10398
|
+
if (existsSync20(layoutPath)) {
|
|
10399
|
+
let layout = await readFile5(layoutPath, "utf-8");
|
|
10606
10400
|
layout = layout.replace(/import\s*\{?\s*AppNav\s*\}?\s*from\s*['"][^'"]+['"]\s*\n?/g, "");
|
|
10607
10401
|
layout = layout.replace(/\s*<AppNav\s*\/?\s*>\s*/g, "\n");
|
|
10608
10402
|
await writeFile5(layoutPath, layout, "utf-8");
|
|
10609
10403
|
}
|
|
10610
|
-
const sharedHeaderPath =
|
|
10611
|
-
if (
|
|
10612
|
-
let header = await
|
|
10404
|
+
const sharedHeaderPath = join14(outputDir, "components", "shared", "header.tsx");
|
|
10405
|
+
if (existsSync20(sharedHeaderPath)) {
|
|
10406
|
+
let header = await readFile5(sharedHeaderPath, "utf-8");
|
|
10613
10407
|
header = header.replace(/<Link\s[^>]*href="\/design-system"[^>]*>[\s\S]*?<\/Link>/g, "");
|
|
10614
10408
|
header = header.replace(/\n\s*<>\s*\n/, "\n");
|
|
10615
10409
|
header = header.replace(/\n\s*<\/>\s*\n/, "\n");
|
|
10616
10410
|
await writeFile5(sharedHeaderPath, header, "utf-8");
|
|
10617
10411
|
}
|
|
10618
|
-
const guardPath2 =
|
|
10619
|
-
if (
|
|
10620
|
-
let guard = await
|
|
10412
|
+
const guardPath2 = join14(outputDir, "app", "ShowWhenNotAuthRoute.tsx");
|
|
10413
|
+
if (existsSync20(guardPath2)) {
|
|
10414
|
+
let guard = await readFile5(guardPath2, "utf-8");
|
|
10621
10415
|
guard = guard.replace(/['"],?\s*'\/design-system['"],?\s*/g, "");
|
|
10622
10416
|
const pathsMatch = guard.match(/HIDDEN_PATHS\s*=\s*\[([^\]]*)\]/);
|
|
10623
10417
|
const remaining = pathsMatch ? pathsMatch[1].replace(/['"]/g, "").split(",").map((s) => s.trim()).filter(Boolean) : [];
|
|
10624
10418
|
if (remaining.length === 0) {
|
|
10625
10419
|
rmSync4(guardPath2, { force: true });
|
|
10626
10420
|
removed.push("app/ShowWhenNotAuthRoute.tsx");
|
|
10627
|
-
if (
|
|
10628
|
-
let layout = await
|
|
10421
|
+
if (existsSync20(layoutPath)) {
|
|
10422
|
+
let layout = await readFile5(layoutPath, "utf-8");
|
|
10629
10423
|
layout = layout.replace(/import\s+\w+\s+from\s*['"]\.\/ShowWhenNotAuthRoute['"]\s*\n?/g, "");
|
|
10630
10424
|
layout = layout.replace(/\s*<ShowWhenNotAuthRoute>\s*\n?/g, "\n");
|
|
10631
10425
|
layout = layout.replace(/\s*<\/ShowWhenNotAuthRoute>\s*\n?/g, "\n");
|
|
@@ -10644,15 +10438,15 @@ async function stripCoherentArtifacts(outputDir) {
|
|
|
10644
10438
|
".env.local",
|
|
10645
10439
|
"recommendations.md"
|
|
10646
10440
|
]) {
|
|
10647
|
-
const full =
|
|
10648
|
-
if (
|
|
10441
|
+
const full = join14(outputDir, name);
|
|
10442
|
+
if (existsSync20(full)) {
|
|
10649
10443
|
rmSync4(full, { force: true });
|
|
10650
10444
|
removed.push(name);
|
|
10651
10445
|
}
|
|
10652
10446
|
}
|
|
10653
10447
|
for (const dir of [".claude", ".coherent"]) {
|
|
10654
|
-
const full =
|
|
10655
|
-
if (
|
|
10448
|
+
const full = join14(outputDir, dir);
|
|
10449
|
+
if (existsSync20(full)) {
|
|
10656
10450
|
rmSync4(full, { recursive: true, force: true });
|
|
10657
10451
|
removed.push(dir + "/");
|
|
10658
10452
|
}
|
|
@@ -10676,7 +10470,7 @@ async function exportCommand(options = {}) {
|
|
|
10676
10470
|
process.exit(1);
|
|
10677
10471
|
}
|
|
10678
10472
|
spinner.text = "Copying project...";
|
|
10679
|
-
if (
|
|
10473
|
+
if (existsSync20(outputDir)) rmSync4(outputDir, { recursive: true, force: true });
|
|
10680
10474
|
await copyDir(projectRoot, outputDir);
|
|
10681
10475
|
spinner.succeed("Project copied");
|
|
10682
10476
|
if (!keepDs) {
|
|
@@ -10851,8 +10645,8 @@ async function regenerateDocsCommand() {
|
|
|
10851
10645
|
|
|
10852
10646
|
// src/commands/fix.ts
|
|
10853
10647
|
import chalk18 from "chalk";
|
|
10854
|
-
import { readdirSync as readdirSync8, readFileSync as
|
|
10855
|
-
import { resolve as resolve13, join as
|
|
10648
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync15, existsSync as existsSync21, writeFileSync as writeFileSync11, rmSync as rmSync5, mkdirSync as mkdirSync7 } from "fs";
|
|
10649
|
+
import { resolve as resolve13, join as join15 } from "path";
|
|
10856
10650
|
import {
|
|
10857
10651
|
DesignSystemManager as DesignSystemManager11,
|
|
10858
10652
|
ComponentManager as ComponentManager6,
|
|
@@ -10878,7 +10672,7 @@ function listTsxFiles(dir) {
|
|
|
10878
10672
|
try {
|
|
10879
10673
|
const entries = readdirSync8(dir, { withFileTypes: true });
|
|
10880
10674
|
for (const e of entries) {
|
|
10881
|
-
const full =
|
|
10675
|
+
const full = join15(dir, e.name);
|
|
10882
10676
|
if (e.isDirectory() && e.name !== "node_modules" && !e.name.startsWith(".")) {
|
|
10883
10677
|
files.push(...listTsxFiles(full));
|
|
10884
10678
|
} else if (e.isFile() && e.name.endsWith(".tsx")) {
|
|
@@ -10907,8 +10701,8 @@ async function fixCommand(opts = {}) {
|
|
|
10907
10701
|
console.log(chalk18.cyan("\ncoherent fix\n"));
|
|
10908
10702
|
}
|
|
10909
10703
|
if (!skipCache) {
|
|
10910
|
-
const nextDir =
|
|
10911
|
-
if (
|
|
10704
|
+
const nextDir = join15(projectRoot, ".next");
|
|
10705
|
+
if (existsSync21(nextDir)) {
|
|
10912
10706
|
if (!dryRun) rmSync5(nextDir, { recursive: true, force: true });
|
|
10913
10707
|
fixes.push("Cleared build cache");
|
|
10914
10708
|
console.log(chalk18.green(" \u2714 Cleared build cache"));
|
|
@@ -10935,7 +10729,7 @@ async function fixCommand(opts = {}) {
|
|
|
10935
10729
|
const componentsTsxFiles = listTsxFiles(resolve13(projectRoot, "components"));
|
|
10936
10730
|
const allComponentIds = /* @__PURE__ */ new Set();
|
|
10937
10731
|
for (const file of [...allTsxFiles, ...componentsTsxFiles]) {
|
|
10938
|
-
const content =
|
|
10732
|
+
const content = readFileSync15(file, "utf-8");
|
|
10939
10733
|
extractComponentIdsFromCode2(content).forEach((id) => allComponentIds.add(id));
|
|
10940
10734
|
}
|
|
10941
10735
|
let dsm = null;
|
|
@@ -10955,7 +10749,7 @@ async function fixCommand(opts = {}) {
|
|
|
10955
10749
|
} else {
|
|
10956
10750
|
const fileName = toKebabCase(id) + ".tsx";
|
|
10957
10751
|
const filePath = resolve13(projectRoot, "components", "ui", fileName);
|
|
10958
|
-
if (!
|
|
10752
|
+
if (!existsSync21(filePath)) missingFiles.push(id);
|
|
10959
10753
|
}
|
|
10960
10754
|
}
|
|
10961
10755
|
const provider = getComponentProvider();
|
|
@@ -11007,7 +10801,7 @@ async function fixCommand(opts = {}) {
|
|
|
11007
10801
|
const userTsxFiles = allTsxFiles.filter((f) => !f.includes("/design-system/"));
|
|
11008
10802
|
let syntaxFixed = 0;
|
|
11009
10803
|
for (const file of userTsxFiles) {
|
|
11010
|
-
const content =
|
|
10804
|
+
const content = readFileSync15(file, "utf-8");
|
|
11011
10805
|
const fixed = fixUnescapedLtInJsx(
|
|
11012
10806
|
fixEscapedClosingQuotes(sanitizeMetadataStrings(ensureUseClientIfNeeded(content)))
|
|
11013
10807
|
);
|
|
@@ -11025,7 +10819,7 @@ async function fixCommand(opts = {}) {
|
|
|
11025
10819
|
let qualityFixCount = 0;
|
|
11026
10820
|
const qualityFixDetails = [];
|
|
11027
10821
|
for (const file of userTsxFiles) {
|
|
11028
|
-
const content =
|
|
10822
|
+
const content = readFileSync15(file, "utf-8");
|
|
11029
10823
|
const { code: autoFixed, fixes: fileFixes } = await autoFixCode(content);
|
|
11030
10824
|
if (autoFixed !== content) {
|
|
11031
10825
|
if (!dryRun) writeFileSync11(file, autoFixed, "utf-8");
|
|
@@ -11044,7 +10838,7 @@ async function fixCommand(opts = {}) {
|
|
|
11044
10838
|
let totalWarnings = 0;
|
|
11045
10839
|
const fileIssues = [];
|
|
11046
10840
|
for (const file of allTsxFiles) {
|
|
11047
|
-
const code = dryRun ?
|
|
10841
|
+
const code = dryRun ? readFileSync15(file, "utf-8") : readFileSync15(file, "utf-8");
|
|
11048
10842
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11049
10843
|
const baseName = file.split("/").pop() || "";
|
|
11050
10844
|
const isAuthPage = relativePath.includes("(auth)");
|
|
@@ -11169,7 +10963,7 @@ async function fixCommand(opts = {}) {
|
|
|
11169
10963
|
// src/commands/check.ts
|
|
11170
10964
|
import chalk19 from "chalk";
|
|
11171
10965
|
import { resolve as resolve14 } from "path";
|
|
11172
|
-
import { readdirSync as readdirSync9, readFileSync as
|
|
10966
|
+
import { readdirSync as readdirSync9, readFileSync as readFileSync16, statSync as statSync3, existsSync as existsSync22 } from "fs";
|
|
11173
10967
|
import { loadManifest as loadManifest11 } from "@getcoherent/core";
|
|
11174
10968
|
var EXCLUDED_DIRS = /* @__PURE__ */ new Set(["node_modules", "design-system"]);
|
|
11175
10969
|
function findTsxFiles(dir) {
|
|
@@ -11226,7 +11020,7 @@ async function checkCommand(opts = {}) {
|
|
|
11226
11020
|
"NATIVE_TABLE"
|
|
11227
11021
|
]);
|
|
11228
11022
|
for (const file of files) {
|
|
11229
|
-
const code =
|
|
11023
|
+
const code = readFileSync16(file, "utf-8");
|
|
11230
11024
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11231
11025
|
const baseName = file.split("/").pop() || "";
|
|
11232
11026
|
const isAuthPage = relativePath.includes("(auth)");
|
|
@@ -11268,7 +11062,7 @@ async function checkCommand(opts = {}) {
|
|
|
11268
11062
|
routeSet.add("/");
|
|
11269
11063
|
routeSet.add("#");
|
|
11270
11064
|
for (const file of files) {
|
|
11271
|
-
const code =
|
|
11065
|
+
const code = readFileSync16(file, "utf-8");
|
|
11272
11066
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11273
11067
|
const lines = code.split("\n");
|
|
11274
11068
|
const linkHrefRe = /href\s*=\s*["'](\/[a-z0-9/-]*)["']/gi;
|
|
@@ -11301,7 +11095,7 @@ async function checkCommand(opts = {}) {
|
|
|
11301
11095
|
if (manifest.shared.length > 0) {
|
|
11302
11096
|
for (const entry of manifest.shared) {
|
|
11303
11097
|
const fullPath = resolve14(project.root, entry.file);
|
|
11304
|
-
if (!
|
|
11098
|
+
if (!existsSync22(fullPath)) {
|
|
11305
11099
|
result.pages.withErrors++;
|
|
11306
11100
|
if (!opts.json) console.log(chalk19.red(`
|
|
11307
11101
|
\u2717 Missing shared component file: ${entry.id} (${entry.file})`));
|
|
@@ -11326,7 +11120,7 @@ async function checkCommand(opts = {}) {
|
|
|
11326
11120
|
let _nameMismatch = 0;
|
|
11327
11121
|
for (const entry of manifest.shared) {
|
|
11328
11122
|
const filePath = resolve14(projectRoot, entry.file);
|
|
11329
|
-
const fileExists =
|
|
11123
|
+
const fileExists = existsSync22(filePath);
|
|
11330
11124
|
if (!fileExists) {
|
|
11331
11125
|
_orphaned++;
|
|
11332
11126
|
if (!opts.json) {
|
|
@@ -11336,7 +11130,7 @@ async function checkCommand(opts = {}) {
|
|
|
11336
11130
|
continue;
|
|
11337
11131
|
}
|
|
11338
11132
|
try {
|
|
11339
|
-
const code =
|
|
11133
|
+
const code = readFileSync16(filePath, "utf-8");
|
|
11340
11134
|
const actualExports = extractExportedComponentNames(code);
|
|
11341
11135
|
if (actualExports.length > 0 && !actualExports.includes(entry.name)) {
|
|
11342
11136
|
_nameMismatch++;
|
|
@@ -11404,7 +11198,7 @@ async function checkCommand(opts = {}) {
|
|
|
11404
11198
|
id: e.id,
|
|
11405
11199
|
name: e.name,
|
|
11406
11200
|
type: e.type,
|
|
11407
|
-
status:
|
|
11201
|
+
status: existsSync22(resolve14(projectRoot, e.file)) ? "ok" : "unused",
|
|
11408
11202
|
message: "",
|
|
11409
11203
|
suggestions: void 0
|
|
11410
11204
|
}))
|
|
@@ -11418,11 +11212,11 @@ async function checkCommand(opts = {}) {
|
|
|
11418
11212
|
const { inferPageTypeFromRoute: inferPageTypeFromRoute2 } = await import("./design-constraints-EIP2XM7T.js");
|
|
11419
11213
|
const manifest = await loadManifest11(projectRoot);
|
|
11420
11214
|
const appDir = resolve14(projectRoot, "app");
|
|
11421
|
-
const pageFiles =
|
|
11215
|
+
const pageFiles = existsSync22(appDir) ? findTsxFiles(appDir) : [];
|
|
11422
11216
|
if (manifest.shared.length > 0 && pageFiles.length > 0) {
|
|
11423
11217
|
const reuseWarnings = [];
|
|
11424
11218
|
for (const file of pageFiles) {
|
|
11425
|
-
const code =
|
|
11219
|
+
const code = readFileSync16(file, "utf-8");
|
|
11426
11220
|
const relativePath = file.replace(projectRoot + "/", "");
|
|
11427
11221
|
const route = "/" + relativePath.replace(/^app\//, "").replace(/\/page\.tsx$/, "").replace(/^\(.*?\)\//, "");
|
|
11428
11222
|
const pageType = inferPageTypeFromRoute2(route);
|
|
@@ -11533,12 +11327,12 @@ import {
|
|
|
11533
11327
|
generateSharedComponent as generateSharedComponent5,
|
|
11534
11328
|
integrateSharedLayoutIntoRootLayout as integrateSharedLayoutIntoRootLayout3
|
|
11535
11329
|
} from "@getcoherent/core";
|
|
11536
|
-
import { existsSync as
|
|
11330
|
+
import { existsSync as existsSync23 } from "fs";
|
|
11537
11331
|
import { resolve as resolve15 } from "path";
|
|
11538
11332
|
|
|
11539
11333
|
// src/utils/ds-files.ts
|
|
11540
11334
|
import { mkdir as mkdir6, writeFile as writeFile6 } from "fs/promises";
|
|
11541
|
-
import { join as
|
|
11335
|
+
import { join as join16, dirname as dirname8 } from "path";
|
|
11542
11336
|
import { DesignSystemGenerator } from "@getcoherent/core";
|
|
11543
11337
|
var SHARED_DS_KEYS = [
|
|
11544
11338
|
"app/design-system/shared/page.tsx",
|
|
@@ -11552,7 +11346,7 @@ async function writeDesignSystemFiles(projectRoot, config2, options) {
|
|
|
11552
11346
|
const toWrite = options?.sharedOnly ? new Map([...files].filter(([path4]) => SHARED_DS_KEYS.includes(path4))) : files;
|
|
11553
11347
|
const written = [];
|
|
11554
11348
|
for (const [relativePath, content] of toWrite) {
|
|
11555
|
-
const fullPath =
|
|
11349
|
+
const fullPath = join16(projectRoot, relativePath);
|
|
11556
11350
|
await mkdir6(dirname8(fullPath), { recursive: true });
|
|
11557
11351
|
await writeFile6(fullPath, content, "utf-8");
|
|
11558
11352
|
written.push(relativePath);
|
|
@@ -11688,7 +11482,7 @@ function createComponentsCommand() {
|
|
|
11688
11482
|
if (updated) console.log(chalk25.cyan(" Updated app/layout.tsx to use shared layout components.\n"));
|
|
11689
11483
|
}
|
|
11690
11484
|
const sharedPagePath = resolve15(project.root, "app/design-system/shared/page.tsx");
|
|
11691
|
-
if (!
|
|
11485
|
+
if (!existsSync23(sharedPagePath)) {
|
|
11692
11486
|
try {
|
|
11693
11487
|
const dsm = new DesignSystemManager12(project.configPath);
|
|
11694
11488
|
await dsm.load();
|
|
@@ -11714,8 +11508,8 @@ function createComponentsCommand() {
|
|
|
11714
11508
|
import chalk26 from "chalk";
|
|
11715
11509
|
import ora6 from "ora";
|
|
11716
11510
|
import { writeFile as writeFile7, mkdir as mkdir7 } from "fs/promises";
|
|
11717
|
-
import { resolve as resolve16, join as
|
|
11718
|
-
import { existsSync as
|
|
11511
|
+
import { resolve as resolve16, join as join17, dirname as dirname9 } from "path";
|
|
11512
|
+
import { existsSync as existsSync24 } from "fs";
|
|
11719
11513
|
import {
|
|
11720
11514
|
FigmaClient,
|
|
11721
11515
|
parseFigmaFileResponse,
|
|
@@ -11862,7 +11656,7 @@ async function importFigmaAction(urlOrKey, opts) {
|
|
|
11862
11656
|
stats.filesWritten.push(filePath);
|
|
11863
11657
|
return;
|
|
11864
11658
|
}
|
|
11865
|
-
const fullPath =
|
|
11659
|
+
const fullPath = join17(projectRoot, filePath);
|
|
11866
11660
|
await mkdir7(dirname9(fullPath), { recursive: true });
|
|
11867
11661
|
await writeFile7(fullPath, content, "utf-8");
|
|
11868
11662
|
stats.filesWritten.push(filePath);
|
|
@@ -11964,7 +11758,7 @@ async function importFigmaAction(urlOrKey, opts) {
|
|
|
11964
11758
|
spinner.start("Updating design-system.config.ts...");
|
|
11965
11759
|
const configPath = resolve16(projectRoot, DESIGN_SYSTEM_CONFIG_PATH);
|
|
11966
11760
|
const dsm = new DesignSystemManager13(configPath);
|
|
11967
|
-
if (
|
|
11761
|
+
if (existsSync24(configPath)) {
|
|
11968
11762
|
await dsm.load();
|
|
11969
11763
|
const existing = dsm.getConfig();
|
|
11970
11764
|
dsm.updateConfig({
|
|
@@ -11993,8 +11787,8 @@ export const config = ${JSON.stringify(fullConfig, null, 2)} as const
|
|
|
11993
11787
|
stats.configUpdated = true;
|
|
11994
11788
|
spinner.succeed("design-system.config.ts updated");
|
|
11995
11789
|
spinner.start("Ensuring root layout...");
|
|
11996
|
-
const layoutPath =
|
|
11997
|
-
if (!
|
|
11790
|
+
const layoutPath = join17(projectRoot, "app/layout.tsx");
|
|
11791
|
+
if (!existsSync24(layoutPath)) {
|
|
11998
11792
|
await mkdir7(dirname9(layoutPath), { recursive: true });
|
|
11999
11793
|
await writeFile7(layoutPath, MINIMAL_ROOT_LAYOUT, "utf-8");
|
|
12000
11794
|
stats.filesWritten.push("app/layout.tsx");
|
|
@@ -12084,8 +11878,8 @@ async function dsRegenerateCommand() {
|
|
|
12084
11878
|
// src/commands/update.ts
|
|
12085
11879
|
import chalk28 from "chalk";
|
|
12086
11880
|
import ora8 from "ora";
|
|
12087
|
-
import { readFileSync as
|
|
12088
|
-
import { join as
|
|
11881
|
+
import { readFileSync as readFileSync17, existsSync as existsSync25 } from "fs";
|
|
11882
|
+
import { join as join18 } from "path";
|
|
12089
11883
|
import { DesignSystemManager as DesignSystemManager15, CLI_VERSION as CLI_VERSION4 } from "@getcoherent/core";
|
|
12090
11884
|
|
|
12091
11885
|
// src/utils/migrations.ts
|
|
@@ -12254,20 +12048,20 @@ var EXPECTED_CSS_VARS = [
|
|
|
12254
12048
|
"--sidebar-ring"
|
|
12255
12049
|
];
|
|
12256
12050
|
function checkMissingCssVars(projectRoot) {
|
|
12257
|
-
const globalsPath =
|
|
12258
|
-
if (!
|
|
12051
|
+
const globalsPath = join18(projectRoot, "app", "globals.css");
|
|
12052
|
+
if (!existsSync25(globalsPath)) return [];
|
|
12259
12053
|
try {
|
|
12260
|
-
const content =
|
|
12054
|
+
const content = readFileSync17(globalsPath, "utf-8");
|
|
12261
12055
|
return EXPECTED_CSS_VARS.filter((v) => !content.includes(v));
|
|
12262
12056
|
} catch {
|
|
12263
12057
|
return [];
|
|
12264
12058
|
}
|
|
12265
12059
|
}
|
|
12266
12060
|
function patchGlobalsCss(projectRoot, missingVars) {
|
|
12267
|
-
const globalsPath =
|
|
12268
|
-
if (!
|
|
12061
|
+
const globalsPath = join18(projectRoot, "app", "globals.css");
|
|
12062
|
+
if (!existsSync25(globalsPath) || missingVars.length === 0) return;
|
|
12269
12063
|
const { writeFileSync: writeFileSync14 } = __require("fs");
|
|
12270
|
-
let content =
|
|
12064
|
+
let content = readFileSync17(globalsPath, "utf-8");
|
|
12271
12065
|
const defaultValues = {
|
|
12272
12066
|
"--chart-1": "220 70% 50%",
|
|
12273
12067
|
"--chart-2": "160 60% 45%",
|
|
@@ -12345,26 +12139,26 @@ async function undoCommand(options) {
|
|
|
12345
12139
|
// src/commands/sync.ts
|
|
12346
12140
|
import chalk30 from "chalk";
|
|
12347
12141
|
import ora9 from "ora";
|
|
12348
|
-
import { existsSync as
|
|
12349
|
-
import { join as
|
|
12350
|
-
import { readdir as
|
|
12142
|
+
import { existsSync as existsSync26, readFileSync as readFileSync18 } from "fs";
|
|
12143
|
+
import { join as join19, relative as relative5, dirname as dirname10 } from "path";
|
|
12144
|
+
import { readdir as readdir3, readFile as readFile6 } from "fs/promises";
|
|
12351
12145
|
import { DesignSystemManager as DesignSystemManager16 } from "@getcoherent/core";
|
|
12352
12146
|
import { loadManifest as loadManifest13, saveManifest as saveManifest6, findSharedComponent } from "@getcoherent/core";
|
|
12353
12147
|
function extractTokensFromProject(projectRoot) {
|
|
12354
12148
|
const lightColors = {};
|
|
12355
12149
|
const darkColors = {};
|
|
12356
|
-
const globalsPath =
|
|
12357
|
-
if (
|
|
12358
|
-
const css =
|
|
12150
|
+
const globalsPath = join19(projectRoot, "app", "globals.css");
|
|
12151
|
+
if (existsSync26(globalsPath)) {
|
|
12152
|
+
const css = readFileSync18(globalsPath, "utf-8");
|
|
12359
12153
|
const rootMatch = css.match(/:root\s*\{([^}]+)\}/s);
|
|
12360
12154
|
if (rootMatch) parseVarsInto(rootMatch[1], lightColors);
|
|
12361
12155
|
const darkMatch = css.match(/\.dark\s*\{([^}]+)\}/s);
|
|
12362
12156
|
if (darkMatch) parseVarsInto(darkMatch[1], darkColors);
|
|
12363
12157
|
}
|
|
12364
|
-
const layoutPath =
|
|
12158
|
+
const layoutPath = join19(projectRoot, "app", "layout.tsx");
|
|
12365
12159
|
let layoutCode = "";
|
|
12366
|
-
if (
|
|
12367
|
-
layoutCode =
|
|
12160
|
+
if (existsSync26(layoutPath)) {
|
|
12161
|
+
layoutCode = readFileSync18(layoutPath, "utf-8");
|
|
12368
12162
|
const rootInline = layoutCode.match(/:root\s*\{([^}]+)\}/s);
|
|
12369
12163
|
if (rootInline && Object.keys(lightColors).length === 0) {
|
|
12370
12164
|
parseVarsInto(rootInline[1], lightColors);
|
|
@@ -12382,7 +12176,7 @@ function extractTokensFromProject(projectRoot) {
|
|
|
12382
12176
|
defaultMode = "dark";
|
|
12383
12177
|
}
|
|
12384
12178
|
let radius;
|
|
12385
|
-
const allCss = [
|
|
12179
|
+
const allCss = [existsSync26(globalsPath) ? readFileSync18(globalsPath, "utf-8") : "", layoutCode].join("\n");
|
|
12386
12180
|
const radiusMatch = allCss.match(/--radius:\s*([^;]+);/);
|
|
12387
12181
|
if (radiusMatch) radius = radiusMatch[1].trim();
|
|
12388
12182
|
return {
|
|
@@ -12405,13 +12199,13 @@ function parseVarsInto(block, target) {
|
|
|
12405
12199
|
}
|
|
12406
12200
|
async function detectCustomComponents(projectRoot, allPageCode) {
|
|
12407
12201
|
const results = [];
|
|
12408
|
-
const componentsDir =
|
|
12409
|
-
if (!
|
|
12202
|
+
const componentsDir = join19(projectRoot, "components");
|
|
12203
|
+
if (!existsSync26(componentsDir)) return results;
|
|
12410
12204
|
const files = [];
|
|
12411
12205
|
await walkForTsx(componentsDir, files, ["ui"]);
|
|
12412
12206
|
const fileResults = await Promise.all(
|
|
12413
12207
|
files.map(async (filePath) => {
|
|
12414
|
-
const code = await
|
|
12208
|
+
const code = await readFile6(filePath, "utf-8");
|
|
12415
12209
|
const relFile = relative5(projectRoot, filePath);
|
|
12416
12210
|
const exportedNames = extractExportedComponentNames2(code);
|
|
12417
12211
|
return exportedNames.map((name) => ({
|
|
@@ -12428,12 +12222,12 @@ async function detectCustomComponents(projectRoot, allPageCode) {
|
|
|
12428
12222
|
async function walkForTsx(dir, files, skipDirs) {
|
|
12429
12223
|
let entries;
|
|
12430
12224
|
try {
|
|
12431
|
-
entries = await
|
|
12225
|
+
entries = await readdir3(dir, { withFileTypes: true });
|
|
12432
12226
|
} catch {
|
|
12433
12227
|
return;
|
|
12434
12228
|
}
|
|
12435
12229
|
for (const e of entries) {
|
|
12436
|
-
const full =
|
|
12230
|
+
const full = join19(dir, e.name);
|
|
12437
12231
|
if (e.isDirectory()) {
|
|
12438
12232
|
if (skipDirs.includes(e.name) || e.name.startsWith(".")) continue;
|
|
12439
12233
|
await walkForTsx(full, files, skipDirs);
|
|
@@ -12502,18 +12296,18 @@ async function discoverPages(appDir) {
|
|
|
12502
12296
|
async function walk(dir) {
|
|
12503
12297
|
let entries;
|
|
12504
12298
|
try {
|
|
12505
|
-
entries = await
|
|
12299
|
+
entries = await readdir3(dir, { withFileTypes: true });
|
|
12506
12300
|
} catch {
|
|
12507
12301
|
return;
|
|
12508
12302
|
}
|
|
12509
12303
|
for (const entry of entries) {
|
|
12510
|
-
const full =
|
|
12304
|
+
const full = join19(dir, entry.name);
|
|
12511
12305
|
if (entry.isDirectory()) {
|
|
12512
12306
|
if (["design-system", "api", "_not-found"].includes(entry.name)) continue;
|
|
12513
12307
|
if (entry.name.startsWith(".")) continue;
|
|
12514
12308
|
await walk(full);
|
|
12515
12309
|
} else if (entry.name === "page.tsx" || entry.name === "page.jsx") {
|
|
12516
|
-
const code = await
|
|
12310
|
+
const code = await readFile6(full, "utf-8");
|
|
12517
12311
|
const routeDir = dirname10(relative5(appDir, full));
|
|
12518
12312
|
let route = routeDir === "." ? "/" : "/" + routeDir;
|
|
12519
12313
|
route = route.replace(/\/\([^)]+\)/g, "");
|
|
@@ -12596,8 +12390,8 @@ async function syncCommand(options = {}) {
|
|
|
12596
12390
|
if (dryRun) console.log(chalk30.yellow(" [dry-run] No files will be written\n"));
|
|
12597
12391
|
const spinner = ora9("Scanning project files...").start();
|
|
12598
12392
|
try {
|
|
12599
|
-
const appDir =
|
|
12600
|
-
if (!
|
|
12393
|
+
const appDir = join19(project.root, "app");
|
|
12394
|
+
if (!existsSync26(appDir)) {
|
|
12601
12395
|
spinner.fail("No app/ directory found");
|
|
12602
12396
|
process.exit(1);
|
|
12603
12397
|
}
|
|
@@ -12827,53 +12621,53 @@ async function syncCommand(options = {}) {
|
|
|
12827
12621
|
// src/commands/migrate.ts
|
|
12828
12622
|
import chalk31 from "chalk";
|
|
12829
12623
|
import ora10 from "ora";
|
|
12830
|
-
import { existsSync as
|
|
12831
|
-
import { join as
|
|
12624
|
+
import { existsSync as existsSync27, mkdirSync as mkdirSync8, cpSync, rmSync as rmSync6, writeFileSync as writeFileSync12, readFileSync as readFileSync19, readdirSync as readdirSync10 } from "fs";
|
|
12625
|
+
import { join as join20 } from "path";
|
|
12832
12626
|
function backupDir(projectRoot) {
|
|
12833
12627
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
12834
|
-
return
|
|
12628
|
+
return join20(projectRoot, ".coherent", "backups", `pre-migrate-${ts}`);
|
|
12835
12629
|
}
|
|
12836
12630
|
function guardPath(projectRoot) {
|
|
12837
|
-
return
|
|
12631
|
+
return join20(projectRoot, ".coherent", "migration-in-progress");
|
|
12838
12632
|
}
|
|
12839
12633
|
function createBackup2(projectRoot) {
|
|
12840
|
-
const uiDir =
|
|
12634
|
+
const uiDir = join20(projectRoot, "components", "ui");
|
|
12841
12635
|
const dest = backupDir(projectRoot);
|
|
12842
12636
|
mkdirSync8(dest, { recursive: true });
|
|
12843
|
-
if (
|
|
12844
|
-
cpSync(uiDir,
|
|
12637
|
+
if (existsSync27(uiDir)) {
|
|
12638
|
+
cpSync(uiDir, join20(dest, "components-ui"), { recursive: true });
|
|
12845
12639
|
}
|
|
12846
|
-
const configPath =
|
|
12847
|
-
if (
|
|
12848
|
-
cpSync(configPath,
|
|
12640
|
+
const configPath = join20(projectRoot, "design-system.config.ts");
|
|
12641
|
+
if (existsSync27(configPath)) {
|
|
12642
|
+
cpSync(configPath, join20(dest, "design-system.config.ts"));
|
|
12849
12643
|
}
|
|
12850
12644
|
return dest;
|
|
12851
12645
|
}
|
|
12852
12646
|
function setGuard(projectRoot, backupPath) {
|
|
12853
12647
|
const guard = guardPath(projectRoot);
|
|
12854
|
-
mkdirSync8(
|
|
12648
|
+
mkdirSync8(join20(projectRoot, ".coherent"), { recursive: true });
|
|
12855
12649
|
writeFileSync12(guard, JSON.stringify({ backup: backupPath, startedAt: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
12856
12650
|
}
|
|
12857
12651
|
function clearGuard(projectRoot) {
|
|
12858
12652
|
const guard = guardPath(projectRoot);
|
|
12859
|
-
if (
|
|
12653
|
+
if (existsSync27(guard)) rmSync6(guard);
|
|
12860
12654
|
}
|
|
12861
12655
|
function rollback(projectRoot) {
|
|
12862
12656
|
const guard = guardPath(projectRoot);
|
|
12863
|
-
if (!
|
|
12657
|
+
if (!existsSync27(guard)) return false;
|
|
12864
12658
|
try {
|
|
12865
|
-
const data = JSON.parse(
|
|
12659
|
+
const data = JSON.parse(readFileSync19(guard, "utf-8"));
|
|
12866
12660
|
const backup = data.backup;
|
|
12867
|
-
if (!
|
|
12868
|
-
const uiBackup =
|
|
12869
|
-
const uiDir =
|
|
12870
|
-
if (
|
|
12871
|
-
if (
|
|
12661
|
+
if (!existsSync27(backup)) return false;
|
|
12662
|
+
const uiBackup = join20(backup, "components-ui");
|
|
12663
|
+
const uiDir = join20(projectRoot, "components", "ui");
|
|
12664
|
+
if (existsSync27(uiBackup)) {
|
|
12665
|
+
if (existsSync27(uiDir)) rmSync6(uiDir, { recursive: true });
|
|
12872
12666
|
cpSync(uiBackup, uiDir, { recursive: true });
|
|
12873
12667
|
}
|
|
12874
|
-
const configBackup =
|
|
12875
|
-
const configDest =
|
|
12876
|
-
if (
|
|
12668
|
+
const configBackup = join20(backup, "design-system.config.ts");
|
|
12669
|
+
const configDest = join20(projectRoot, "design-system.config.ts");
|
|
12670
|
+
if (existsSync27(configBackup)) {
|
|
12877
12671
|
cpSync(configBackup, configDest);
|
|
12878
12672
|
}
|
|
12879
12673
|
clearGuard(projectRoot);
|
|
@@ -12901,13 +12695,13 @@ async function migrateAction(options) {
|
|
|
12901
12695
|
return;
|
|
12902
12696
|
}
|
|
12903
12697
|
const guard = guardPath(projectRoot);
|
|
12904
|
-
if (
|
|
12698
|
+
if (existsSync27(guard)) {
|
|
12905
12699
|
console.log(chalk31.yellow("A migration is already in progress."));
|
|
12906
12700
|
console.log(chalk31.dim("Run `coherent migrate --rollback` to undo, or delete .coherent/migration-in-progress"));
|
|
12907
12701
|
return;
|
|
12908
12702
|
}
|
|
12909
|
-
const uiDir =
|
|
12910
|
-
if (!
|
|
12703
|
+
const uiDir = join20(projectRoot, "components", "ui");
|
|
12704
|
+
if (!existsSync27(uiDir)) {
|
|
12911
12705
|
console.log(chalk31.yellow("No components/ui directory found. Nothing to migrate."));
|
|
12912
12706
|
return;
|
|
12913
12707
|
}
|
|
@@ -12933,8 +12727,8 @@ Found ${migratable.length} component(s) to migrate:`));
|
|
|
12933
12727
|
setGuard(projectRoot, backup);
|
|
12934
12728
|
try {
|
|
12935
12729
|
for (const id of migratable) {
|
|
12936
|
-
const filePath =
|
|
12937
|
-
if (
|
|
12730
|
+
const filePath = join20(uiDir, `${id}.tsx`);
|
|
12731
|
+
if (existsSync27(filePath)) rmSync6(filePath);
|
|
12938
12732
|
}
|
|
12939
12733
|
const results = await provider.installBatch(migratable, projectRoot, { force: true });
|
|
12940
12734
|
let migrated = 0;
|
|
@@ -12956,20 +12750,20 @@ Found ${migratable.length} component(s) to migrate:`));
|
|
|
12956
12750
|
}
|
|
12957
12751
|
|
|
12958
12752
|
// src/utils/update-notifier.ts
|
|
12959
|
-
import { existsSync as
|
|
12960
|
-
import { join as
|
|
12753
|
+
import { existsSync as existsSync28, mkdirSync as mkdirSync9, readFileSync as readFileSync20, writeFileSync as writeFileSync13 } from "fs";
|
|
12754
|
+
import { join as join21 } from "path";
|
|
12961
12755
|
import { homedir } from "os";
|
|
12962
12756
|
import chalk32 from "chalk";
|
|
12963
12757
|
import { CLI_VERSION as CLI_VERSION5 } from "@getcoherent/core";
|
|
12964
12758
|
var DEBUG5 = process.env.COHERENT_DEBUG === "1";
|
|
12965
12759
|
var PACKAGE_NAME = "@getcoherent/cli";
|
|
12966
|
-
var CACHE_DIR =
|
|
12967
|
-
var CACHE_FILE =
|
|
12760
|
+
var CACHE_DIR = join21(homedir(), ".coherent");
|
|
12761
|
+
var CACHE_FILE = join21(CACHE_DIR, "update-check.json");
|
|
12968
12762
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
12969
12763
|
function readCache() {
|
|
12970
12764
|
try {
|
|
12971
|
-
if (!
|
|
12972
|
-
const raw =
|
|
12765
|
+
if (!existsSync28(CACHE_FILE)) return null;
|
|
12766
|
+
const raw = readFileSync20(CACHE_FILE, "utf-8");
|
|
12973
12767
|
return JSON.parse(raw);
|
|
12974
12768
|
} catch (e) {
|
|
12975
12769
|
if (DEBUG5) console.error("Failed to read update cache:", e);
|
|
@@ -12978,7 +12772,7 @@ function readCache() {
|
|
|
12978
12772
|
}
|
|
12979
12773
|
function writeCache(data) {
|
|
12980
12774
|
try {
|
|
12981
|
-
if (!
|
|
12775
|
+
if (!existsSync28(CACHE_DIR)) mkdirSync9(CACHE_DIR, { recursive: true });
|
|
12982
12776
|
writeFileSync13(CACHE_FILE, JSON.stringify(data), "utf-8");
|
|
12983
12777
|
} catch (e) {
|
|
12984
12778
|
if (DEBUG5) console.error("Failed to write update cache:", e);
|