@decantr/cli 1.7.6 → 1.7.8
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/README.md +51 -3
- package/dist/bin.js +2 -2
- package/dist/{chunk-H4H3IQJK.js → chunk-74G2RQDO.js} +100 -13
- package/dist/{chunk-KAEQTVAM.js → chunk-ODJQZXHQ.js} +702 -352
- package/dist/index.js +2 -2
- package/dist/{upgrade-XNUAON3G.js → upgrade-UXY2WUJH.js} +1 -1
- package/package.json +4 -2
- package/src/templates/DECANTR.md.template +6 -6
|
@@ -14,14 +14,14 @@ import {
|
|
|
14
14
|
scaffoldProject,
|
|
15
15
|
syncRegistry,
|
|
16
16
|
writeExecutionPackBundleArtifacts
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-74G2RQDO.js";
|
|
18
18
|
import {
|
|
19
19
|
buildGuardRegistryContext
|
|
20
20
|
} from "./chunk-KUDAVJOR.js";
|
|
21
21
|
|
|
22
22
|
// src/index.ts
|
|
23
|
-
import { mkdirSync as
|
|
24
|
-
import { basename, join as
|
|
23
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync18, writeFileSync as writeFileSync13, existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
|
|
24
|
+
import { basename as basename2, join as join26, dirname as dirname2, isAbsolute, resolve as resolve3 } from "path";
|
|
25
25
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
26
26
|
import { validateEssence as validateEssence2, evaluateGuard, isV3 as isV36 } from "@decantr/essence-spec";
|
|
27
27
|
import {
|
|
@@ -238,7 +238,7 @@ ${CYAN}Detected project configuration:${RESET}`);
|
|
|
238
238
|
console.log(` Existing essence: ${YELLOW}yes${RESET}`);
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
|
-
async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
|
|
241
|
+
async function runInteractivePrompts(detected, archetypes, blueprints, themes, workflowSeed) {
|
|
242
242
|
showDetection(detected);
|
|
243
243
|
const blueprintOptions = [
|
|
244
244
|
{ value: "none", label: "none", description: "Start from scratch (blank canvas)" },
|
|
@@ -255,7 +255,8 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
|
|
|
255
255
|
label: t.id,
|
|
256
256
|
description: t.description
|
|
257
257
|
}));
|
|
258
|
-
const
|
|
258
|
+
const desiredTheme = workflowSeed?.theme || "luminarum";
|
|
259
|
+
const defaultThemeIdx = Math.max(0, themeOptions.findIndex((t) => t.value === desiredTheme));
|
|
259
260
|
const theme = await select("Choose a theme", themeOptions, Math.max(0, defaultThemeIdx), true);
|
|
260
261
|
const mode = await select(
|
|
261
262
|
"Color mode",
|
|
@@ -264,7 +265,7 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
|
|
|
264
265
|
{ value: "light", label: "light", description: "Light background" },
|
|
265
266
|
{ value: "auto", label: "auto", description: "Follow system preference" }
|
|
266
267
|
],
|
|
267
|
-
0
|
|
268
|
+
workflowSeed?.mode === "light" ? 1 : workflowSeed?.mode === "auto" ? 2 : 0
|
|
268
269
|
);
|
|
269
270
|
const shape = await select(
|
|
270
271
|
"Border shape",
|
|
@@ -286,7 +287,7 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
|
|
|
286
287
|
{ value: "angular", label: "angular", description: "Angular" },
|
|
287
288
|
{ value: "html", label: "html", description: "Plain HTML/CSS/JS" }
|
|
288
289
|
];
|
|
289
|
-
let defaultFrameworkIdx = frameworkOptions.findIndex((f) => f.value === detected.framework);
|
|
290
|
+
let defaultFrameworkIdx = frameworkOptions.findIndex((f) => f.value === (workflowSeed?.target || detected.framework));
|
|
290
291
|
if (defaultFrameworkIdx < 0) defaultFrameworkIdx = 0;
|
|
291
292
|
const target = await select("Target framework", frameworkOptions, defaultFrameworkIdx, true);
|
|
292
293
|
if (detected.framework !== "unknown" && target !== detected.framework) {
|
|
@@ -303,8 +304,7 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
|
|
|
303
304
|
{ value: "guided", label: "guided", description: "Style, structure, density enforced" },
|
|
304
305
|
{ value: "strict", label: "strict", description: "All 5 rules enforced exactly" }
|
|
305
306
|
],
|
|
306
|
-
detected.existingEssence ? 1 : 2
|
|
307
|
-
// Default to guided for existing, strict for new
|
|
307
|
+
workflowSeed?.guard === "creative" ? 0 : workflowSeed?.guard === "strict" ? 2 : workflowSeed?.guard === "guided" ? 1 : detected.existingEssence ? 1 : 2
|
|
308
308
|
);
|
|
309
309
|
const density = await select(
|
|
310
310
|
"Spacing density",
|
|
@@ -313,7 +313,7 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
|
|
|
313
313
|
{ value: "comfortable", label: "comfortable", description: "Balanced spacing" },
|
|
314
314
|
{ value: "spacious", label: "spacious", description: "Generous whitespace" }
|
|
315
315
|
],
|
|
316
|
-
1
|
|
316
|
+
workflowSeed?.density === "compact" ? 0 : workflowSeed?.density === "spacious" ? 2 : 1
|
|
317
317
|
);
|
|
318
318
|
const shellOptions = [
|
|
319
319
|
{ value: "sidebar-main", label: "sidebar-main", description: "Collapsible sidebar with main content" },
|
|
@@ -322,8 +322,11 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
|
|
|
322
322
|
{ value: "full-bleed", label: "full-bleed", description: "No persistent nav (landing pages)" },
|
|
323
323
|
{ value: "minimal-header", label: "minimal-header", description: "Slim header with centered content" }
|
|
324
324
|
];
|
|
325
|
-
let defaultShellIdx =
|
|
326
|
-
if (
|
|
325
|
+
let defaultShellIdx = shellOptions.findIndex((s) => s.value === workflowSeed?.shell);
|
|
326
|
+
if (defaultShellIdx < 0) {
|
|
327
|
+
defaultShellIdx = 0;
|
|
328
|
+
}
|
|
329
|
+
if (defaultShellIdx === 0 && ["nextjs", "nuxt", "astro"].includes(target)) {
|
|
327
330
|
defaultShellIdx = shellOptions.findIndex((s) => s.value === "top-nav-main");
|
|
328
331
|
}
|
|
329
332
|
const shell = await select("Default page shell (layout)", shellOptions, Math.max(0, defaultShellIdx), true);
|
|
@@ -340,7 +343,8 @@ async function runInteractivePrompts(detected, archetypes, blueprints, themes) {
|
|
|
340
343
|
shell,
|
|
341
344
|
personality: ["professional"],
|
|
342
345
|
features: [],
|
|
343
|
-
existing: detected.existingEssence
|
|
346
|
+
existing: workflowSeed?.existing || detected.existingEssence,
|
|
347
|
+
workflowMode: workflowSeed?.workflowMode
|
|
344
348
|
};
|
|
345
349
|
}
|
|
346
350
|
function parseFlags(args, detected) {
|
|
@@ -359,20 +363,21 @@ function parseFlags(args, detected) {
|
|
|
359
363
|
if (args.existing === true) options.existing = true;
|
|
360
364
|
return options;
|
|
361
365
|
}
|
|
362
|
-
function mergeWithDefaults(flags, detected) {
|
|
366
|
+
function mergeWithDefaults(flags, detected, workflowSeed) {
|
|
363
367
|
return {
|
|
364
368
|
blueprint: flags.blueprint,
|
|
365
369
|
archetype: flags.archetype,
|
|
366
|
-
theme: flags.theme || "luminarum",
|
|
367
|
-
mode: flags.mode || "dark",
|
|
370
|
+
theme: flags.theme || workflowSeed?.theme || "luminarum",
|
|
371
|
+
mode: flags.mode || workflowSeed?.mode || "dark",
|
|
368
372
|
shape: flags.shape || "rounded",
|
|
369
|
-
target: flags.target || (detected.framework !== "unknown" ? detected.framework : "react"),
|
|
370
|
-
guard: flags.guard || (detected.existingEssence ? "guided" : "strict"),
|
|
371
|
-
density: flags.density || "comfortable",
|
|
372
|
-
shell: flags.shell || "sidebar-main",
|
|
373
|
+
target: flags.target || workflowSeed?.target || (detected.framework !== "unknown" ? detected.framework : "react"),
|
|
374
|
+
guard: flags.guard || workflowSeed?.guard || (detected.existingEssence ? "guided" : "strict"),
|
|
375
|
+
density: flags.density || workflowSeed?.density || "comfortable",
|
|
376
|
+
shell: flags.shell || workflowSeed?.shell || "sidebar-main",
|
|
373
377
|
personality: flags.personality || ["professional"],
|
|
374
378
|
features: flags.features || [],
|
|
375
|
-
existing: flags.existing || detected.existingEssence
|
|
379
|
+
existing: flags.existing || workflowSeed?.existing || detected.existingEssence,
|
|
380
|
+
workflowMode: flags.workflowMode || workflowSeed?.workflowMode
|
|
376
381
|
};
|
|
377
382
|
}
|
|
378
383
|
async function runSimplifiedInit(blueprints) {
|
|
@@ -1532,11 +1537,11 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
1532
1537
|
}
|
|
1533
1538
|
|
|
1534
1539
|
// src/commands/analyze.ts
|
|
1535
|
-
import { existsSync as
|
|
1536
|
-
import { join as
|
|
1540
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
1541
|
+
import { join as join19 } from "path";
|
|
1537
1542
|
|
|
1538
1543
|
// src/analyzers/routes.ts
|
|
1539
|
-
import { existsSync as existsSync12, readdirSync as readdirSync2, statSync } from "fs";
|
|
1544
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11, readdirSync as readdirSync2, statSync } from "fs";
|
|
1540
1545
|
import { join as join12, relative } from "path";
|
|
1541
1546
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "api", "_app", "_document"]);
|
|
1542
1547
|
function shouldSkipDir(name) {
|
|
@@ -1627,6 +1632,75 @@ function walkPagesDir(dir, baseDir, segments) {
|
|
|
1627
1632
|
}
|
|
1628
1633
|
return routes;
|
|
1629
1634
|
}
|
|
1635
|
+
var ROUTER_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
|
|
1636
|
+
function collectRouteCandidateFiles(dir, files, depth = 0) {
|
|
1637
|
+
if (depth > 5) return;
|
|
1638
|
+
let entries;
|
|
1639
|
+
try {
|
|
1640
|
+
entries = readdirSync2(dir);
|
|
1641
|
+
} catch {
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
for (const entry of entries) {
|
|
1645
|
+
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
1646
|
+
const fullPath = join12(dir, entry);
|
|
1647
|
+
try {
|
|
1648
|
+
const stat = statSync(fullPath);
|
|
1649
|
+
if (stat.isDirectory()) {
|
|
1650
|
+
collectRouteCandidateFiles(fullPath, files, depth + 1);
|
|
1651
|
+
} else if (stat.isFile()) {
|
|
1652
|
+
const ext = entry.slice(entry.lastIndexOf("."));
|
|
1653
|
+
if (ROUTER_FILE_EXTENSIONS.has(ext)) {
|
|
1654
|
+
files.push(fullPath);
|
|
1655
|
+
}
|
|
1656
|
+
}
|
|
1657
|
+
} catch {
|
|
1658
|
+
continue;
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
function scanReactRouter(projectRoot) {
|
|
1663
|
+
const candidateDirs = [
|
|
1664
|
+
join12(projectRoot, "src"),
|
|
1665
|
+
projectRoot
|
|
1666
|
+
];
|
|
1667
|
+
const candidateFiles = [];
|
|
1668
|
+
for (const dir of candidateDirs) {
|
|
1669
|
+
if (existsSync12(dir)) collectRouteCandidateFiles(dir, candidateFiles);
|
|
1670
|
+
}
|
|
1671
|
+
const routeMap = /* @__PURE__ */ new Map();
|
|
1672
|
+
for (const absolutePath of candidateFiles) {
|
|
1673
|
+
let content;
|
|
1674
|
+
try {
|
|
1675
|
+
content = readFileSync11(absolutePath, "utf-8");
|
|
1676
|
+
} catch {
|
|
1677
|
+
continue;
|
|
1678
|
+
}
|
|
1679
|
+
const isReactRouterFile = content.includes("react-router-dom") || content.includes("react-router") || content.includes("<Routes") || content.includes("createBrowserRouter") || content.includes("createHashRouter") || content.includes("RouterProvider") || content.includes("HashRouter") || content.includes("BrowserRouter");
|
|
1680
|
+
if (!isReactRouterFile) continue;
|
|
1681
|
+
const relativePath = relative(projectRoot, absolutePath);
|
|
1682
|
+
const pathMatches = /* @__PURE__ */ new Set();
|
|
1683
|
+
for (const match of content.matchAll(/<Route\b[^>]*\bpath=["'`]([^"'`]+)["'`]/g)) {
|
|
1684
|
+
pathMatches.add(match[1]);
|
|
1685
|
+
}
|
|
1686
|
+
for (const match of content.matchAll(/\bpath\s*:\s*["'`]([^"'`]+)["'`]/g)) {
|
|
1687
|
+
pathMatches.add(match[1]);
|
|
1688
|
+
}
|
|
1689
|
+
if (pathMatches.size === 0 && (content.includes("<Routes") || content.includes("RouterProvider"))) {
|
|
1690
|
+
pathMatches.add("/");
|
|
1691
|
+
}
|
|
1692
|
+
for (const path of pathMatches) {
|
|
1693
|
+
if (!routeMap.has(path)) {
|
|
1694
|
+
routeMap.set(path, {
|
|
1695
|
+
path,
|
|
1696
|
+
file: relativePath,
|
|
1697
|
+
hasLayout: false
|
|
1698
|
+
});
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
return [...routeMap.values()];
|
|
1703
|
+
}
|
|
1630
1704
|
function scanRoutes(projectRoot) {
|
|
1631
1705
|
const appDirs = [
|
|
1632
1706
|
join12(projectRoot, "src", "app"),
|
|
@@ -1652,6 +1726,10 @@ function scanRoutes(projectRoot) {
|
|
|
1652
1726
|
}
|
|
1653
1727
|
}
|
|
1654
1728
|
}
|
|
1729
|
+
const reactRouterRoutes = scanReactRouter(projectRoot);
|
|
1730
|
+
if (reactRouterRoutes.length > 0) {
|
|
1731
|
+
return { strategy: "react-router", routes: reactRouterRoutes };
|
|
1732
|
+
}
|
|
1655
1733
|
return { strategy: "none", routes: [] };
|
|
1656
1734
|
}
|
|
1657
1735
|
|
|
@@ -1659,6 +1737,16 @@ function scanRoutes(projectRoot) {
|
|
|
1659
1737
|
import { existsSync as existsSync13, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
1660
1738
|
import { join as join13 } from "path";
|
|
1661
1739
|
var PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
|
|
1740
|
+
var ROOT_COMPONENT_CANDIDATES = [
|
|
1741
|
+
"src/App.tsx",
|
|
1742
|
+
"src/App.ts",
|
|
1743
|
+
"src/App.jsx",
|
|
1744
|
+
"src/App.js",
|
|
1745
|
+
"App.tsx",
|
|
1746
|
+
"App.ts",
|
|
1747
|
+
"App.jsx",
|
|
1748
|
+
"App.js"
|
|
1749
|
+
];
|
|
1662
1750
|
function countFilesRecursive(dir, extensions) {
|
|
1663
1751
|
let count = 0;
|
|
1664
1752
|
let entries;
|
|
@@ -1750,6 +1838,18 @@ function scanComponents(projectRoot) {
|
|
|
1750
1838
|
}
|
|
1751
1839
|
}
|
|
1752
1840
|
}
|
|
1841
|
+
const hasRootAppComponent = ROOT_COMPONENT_CANDIDATES.some(
|
|
1842
|
+
(relativePath) => existsSync13(join13(projectRoot, relativePath))
|
|
1843
|
+
);
|
|
1844
|
+
if (pageCount === 0 && hasRootAppComponent) {
|
|
1845
|
+
pageCount = 1;
|
|
1846
|
+
}
|
|
1847
|
+
if (componentCount === 0 && hasRootAppComponent) {
|
|
1848
|
+
componentCount = 1;
|
|
1849
|
+
if (!componentDirs.includes("src")) {
|
|
1850
|
+
componentDirs.push("src");
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1753
1853
|
return {
|
|
1754
1854
|
pageCount,
|
|
1755
1855
|
componentCount,
|
|
@@ -1758,7 +1858,7 @@ function scanComponents(projectRoot) {
|
|
|
1758
1858
|
}
|
|
1759
1859
|
|
|
1760
1860
|
// src/analyzers/styling.ts
|
|
1761
|
-
import { existsSync as existsSync14, readFileSync as
|
|
1861
|
+
import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
|
|
1762
1862
|
import { join as join14 } from "path";
|
|
1763
1863
|
var TAILWIND_CONFIGS = [
|
|
1764
1864
|
"tailwind.config.js",
|
|
@@ -1769,11 +1869,17 @@ var TAILWIND_CONFIGS = [
|
|
|
1769
1869
|
var GLOBALS_CSS_PATHS = [
|
|
1770
1870
|
"src/app/globals.css",
|
|
1771
1871
|
"app/globals.css",
|
|
1872
|
+
"src/styles/global.css",
|
|
1772
1873
|
"src/styles/globals.css",
|
|
1773
1874
|
"styles/globals.css",
|
|
1774
1875
|
"src/index.css",
|
|
1775
1876
|
"src/global.css"
|
|
1776
1877
|
];
|
|
1878
|
+
var DECANTR_STYLE_PATHS = [
|
|
1879
|
+
"src/styles/tokens.css",
|
|
1880
|
+
"src/styles/treatments.css",
|
|
1881
|
+
"src/styles/global.css"
|
|
1882
|
+
];
|
|
1777
1883
|
function extractCSSVariables(content) {
|
|
1778
1884
|
const colors = {};
|
|
1779
1885
|
const variables = [];
|
|
@@ -1790,8 +1896,8 @@ function extractCSSVariables(content) {
|
|
|
1790
1896
|
}
|
|
1791
1897
|
return { colors, variables };
|
|
1792
1898
|
}
|
|
1793
|
-
function detectDarkMode(projectRoot,
|
|
1794
|
-
|
|
1899
|
+
function detectDarkMode(projectRoot, cssContents) {
|
|
1900
|
+
for (const cssContent of cssContents) {
|
|
1795
1901
|
if (cssContent.includes(".dark") || cssContent.includes('[data-theme="dark"]') || cssContent.includes("prefers-color-scheme: dark") || cssContent.includes("color-scheme: dark")) {
|
|
1796
1902
|
return true;
|
|
1797
1903
|
}
|
|
@@ -1806,7 +1912,7 @@ function detectDarkMode(projectRoot, cssContent) {
|
|
|
1806
1912
|
const fullPath = join14(projectRoot, rel);
|
|
1807
1913
|
if (existsSync14(fullPath)) {
|
|
1808
1914
|
try {
|
|
1809
|
-
const layoutContent =
|
|
1915
|
+
const layoutContent = readFileSync12(fullPath, "utf-8");
|
|
1810
1916
|
if (layoutContent.includes('className="dark"') || layoutContent.includes("className='dark'") || layoutContent.includes('class="dark"')) {
|
|
1811
1917
|
return true;
|
|
1812
1918
|
}
|
|
@@ -1817,7 +1923,7 @@ function detectDarkMode(projectRoot, cssContent) {
|
|
|
1817
1923
|
const pkgPath = join14(projectRoot, "package.json");
|
|
1818
1924
|
if (existsSync14(pkgPath)) {
|
|
1819
1925
|
try {
|
|
1820
|
-
const pkg = JSON.parse(
|
|
1926
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
1821
1927
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1822
1928
|
if (allDeps["next-themes"] || allDeps["theme-toggle"] || allDeps["use-dark-mode"]) {
|
|
1823
1929
|
return true;
|
|
@@ -1825,6 +1931,17 @@ function detectDarkMode(projectRoot, cssContent) {
|
|
|
1825
1931
|
} catch {
|
|
1826
1932
|
}
|
|
1827
1933
|
}
|
|
1934
|
+
const essencePath = join14(projectRoot, "decantr.essence.json");
|
|
1935
|
+
if (existsSync14(essencePath)) {
|
|
1936
|
+
try {
|
|
1937
|
+
const essence = JSON.parse(readFileSync12(essencePath, "utf-8"));
|
|
1938
|
+
const mode = essence.dna?.theme?.mode;
|
|
1939
|
+
if (mode === "dark" || mode === "auto") {
|
|
1940
|
+
return true;
|
|
1941
|
+
}
|
|
1942
|
+
} catch {
|
|
1943
|
+
}
|
|
1944
|
+
}
|
|
1828
1945
|
return false;
|
|
1829
1946
|
}
|
|
1830
1947
|
function scanStyling(projectRoot) {
|
|
@@ -1841,8 +1958,12 @@ function scanStyling(projectRoot) {
|
|
|
1841
1958
|
const pkgPath = join14(projectRoot, "package.json");
|
|
1842
1959
|
if (existsSync14(pkgPath)) {
|
|
1843
1960
|
try {
|
|
1844
|
-
const pkg = JSON.parse(
|
|
1961
|
+
const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
|
|
1845
1962
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1963
|
+
if (allDeps["@decantr/css"]) {
|
|
1964
|
+
approach = "decantr-css";
|
|
1965
|
+
configFile = "src/styles/tokens.css";
|
|
1966
|
+
}
|
|
1846
1967
|
if (allDeps.tailwindcss || allDeps["@tailwindcss/postcss"] || allDeps["@tailwindcss/vite"]) {
|
|
1847
1968
|
approach = "tailwind";
|
|
1848
1969
|
}
|
|
@@ -1850,25 +1971,44 @@ function scanStyling(projectRoot) {
|
|
|
1850
1971
|
}
|
|
1851
1972
|
}
|
|
1852
1973
|
}
|
|
1853
|
-
|
|
1974
|
+
const decantrStyleFiles = DECANTR_STYLE_PATHS.filter((rel) => existsSync14(join14(projectRoot, rel)));
|
|
1975
|
+
if (decantrStyleFiles.length >= 2) {
|
|
1976
|
+
approach = "decantr-css";
|
|
1977
|
+
configFile = decantrStyleFiles.join(" + ");
|
|
1978
|
+
}
|
|
1979
|
+
const cssContents = [];
|
|
1854
1980
|
for (const rel of GLOBALS_CSS_PATHS) {
|
|
1855
1981
|
const fullPath = join14(projectRoot, rel);
|
|
1856
1982
|
if (existsSync14(fullPath)) {
|
|
1857
1983
|
try {
|
|
1858
|
-
|
|
1984
|
+
cssContents.push(readFileSync12(fullPath, "utf-8"));
|
|
1985
|
+
} catch {
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
for (const rel of DECANTR_STYLE_PATHS) {
|
|
1990
|
+
if (GLOBALS_CSS_PATHS.includes(rel)) continue;
|
|
1991
|
+
const fullPath = join14(projectRoot, rel);
|
|
1992
|
+
if (existsSync14(fullPath)) {
|
|
1993
|
+
try {
|
|
1994
|
+
cssContents.push(readFileSync12(fullPath, "utf-8"));
|
|
1859
1995
|
} catch {
|
|
1860
1996
|
}
|
|
1861
|
-
break;
|
|
1862
1997
|
}
|
|
1863
1998
|
}
|
|
1864
1999
|
let colors = {};
|
|
1865
2000
|
let cssVariables = [];
|
|
1866
|
-
|
|
2001
|
+
for (const cssContent of cssContents) {
|
|
1867
2002
|
const extracted = extractCSSVariables(cssContent);
|
|
1868
|
-
colors = extracted.colors;
|
|
1869
|
-
cssVariables
|
|
2003
|
+
colors = { ...colors, ...extracted.colors };
|
|
2004
|
+
cssVariables.push(...extracted.variables);
|
|
2005
|
+
}
|
|
2006
|
+
cssVariables = [...new Set(cssVariables)];
|
|
2007
|
+
const darkMode = detectDarkMode(projectRoot, cssContents);
|
|
2008
|
+
if (approach === "unknown" && cssContents.length > 0) {
|
|
2009
|
+
approach = "css";
|
|
2010
|
+
configFile = GLOBALS_CSS_PATHS.find((rel) => existsSync14(join14(projectRoot, rel)));
|
|
1870
2011
|
}
|
|
1871
|
-
const darkMode = detectDarkMode(projectRoot, cssContent);
|
|
1872
2012
|
return {
|
|
1873
2013
|
approach,
|
|
1874
2014
|
configFile,
|
|
@@ -1879,7 +2019,7 @@ function scanStyling(projectRoot) {
|
|
|
1879
2019
|
}
|
|
1880
2020
|
|
|
1881
2021
|
// src/analyzers/layout.ts
|
|
1882
|
-
import { existsSync as existsSync15, readFileSync as
|
|
2022
|
+
import { existsSync as existsSync15, readFileSync as readFileSync13, readdirSync as readdirSync4 } from "fs";
|
|
1883
2023
|
import { join as join15 } from "path";
|
|
1884
2024
|
var SIDEBAR_PATTERNS = ["sidebar", "side-bar", "sidenav", "side-nav", "drawer", "aside"];
|
|
1885
2025
|
var NAV_PATTERNS = ["nav", "navbar", "header", "top-bar", "topbar", "app-bar", "appbar"];
|
|
@@ -1924,7 +2064,7 @@ function checkLayoutFiles(projectRoot) {
|
|
|
1924
2064
|
if (!existsSync15(layoutPath)) continue;
|
|
1925
2065
|
let content;
|
|
1926
2066
|
try {
|
|
1927
|
-
content =
|
|
2067
|
+
content = readFileSync13(layoutPath, "utf-8");
|
|
1928
2068
|
} catch {
|
|
1929
2069
|
continue;
|
|
1930
2070
|
}
|
|
@@ -1944,7 +2084,7 @@ function checkLayoutFiles(projectRoot) {
|
|
|
1944
2084
|
if (!existsSync15(layoutPath)) continue;
|
|
1945
2085
|
let content;
|
|
1946
2086
|
try {
|
|
1947
|
-
content =
|
|
2087
|
+
content = readFileSync13(layoutPath, "utf-8");
|
|
1948
2088
|
} catch {
|
|
1949
2089
|
continue;
|
|
1950
2090
|
}
|
|
@@ -1965,17 +2105,40 @@ function inferShellPattern(hasSidebar, hasTopNav, hasFooter) {
|
|
|
1965
2105
|
if (hasFooter) return "main-footer";
|
|
1966
2106
|
return "main-only";
|
|
1967
2107
|
}
|
|
2108
|
+
function inferShellPatternFromDecantrContract(projectRoot) {
|
|
2109
|
+
const essencePath = join15(projectRoot, "decantr.essence.json");
|
|
2110
|
+
if (!existsSync15(essencePath)) return null;
|
|
2111
|
+
try {
|
|
2112
|
+
const essence = JSON.parse(readFileSync13(essencePath, "utf-8"));
|
|
2113
|
+
const sectionShells = essence.blueprint?.sections?.map((section) => section.shell).filter((shell) => typeof shell === "string" && shell.length > 0) ?? [];
|
|
2114
|
+
if (sectionShells.length === 0 && essence.blueprint?.shell) {
|
|
2115
|
+
return `${essence.blueprint.shell} (contract)`;
|
|
2116
|
+
}
|
|
2117
|
+
const uniqueShells = [...new Set(sectionShells)];
|
|
2118
|
+
if (uniqueShells.length === 0) return null;
|
|
2119
|
+
if (uniqueShells.length === 1) return `${uniqueShells[0]} (contract)`;
|
|
2120
|
+
const primarySection = essence.blueprint?.sections?.find((section) => section.role === "primary" && section.shell);
|
|
2121
|
+
if (primarySection?.shell) {
|
|
2122
|
+
return `${primarySection.shell} (primary contract)`;
|
|
2123
|
+
}
|
|
2124
|
+
return `${uniqueShells[0]} (+${uniqueShells.length - 1} shells, contract)`;
|
|
2125
|
+
} catch {
|
|
2126
|
+
return null;
|
|
2127
|
+
}
|
|
2128
|
+
}
|
|
1968
2129
|
function scanLayout(projectRoot) {
|
|
1969
2130
|
const fromComponents = checkComponentDirs(projectRoot);
|
|
1970
2131
|
const fromLayouts = checkLayoutFiles(projectRoot);
|
|
1971
2132
|
const hasSidebar = fromComponents.sidebar || fromLayouts.sidebar;
|
|
1972
2133
|
const hasTopNav = fromComponents.nav || fromLayouts.nav;
|
|
1973
2134
|
const hasFooter = fromComponents.footer || fromLayouts.footer;
|
|
2135
|
+
const runtimeShellPattern = inferShellPattern(hasSidebar, hasTopNav, hasFooter);
|
|
2136
|
+
const contractShellPattern = inferShellPatternFromDecantrContract(projectRoot);
|
|
1974
2137
|
return {
|
|
1975
2138
|
hasSidebar,
|
|
1976
2139
|
hasTopNav,
|
|
1977
2140
|
hasFooter,
|
|
1978
|
-
shellPattern:
|
|
2141
|
+
shellPattern: runtimeShellPattern === "main-only" && contractShellPattern ? contractShellPattern : runtimeShellPattern
|
|
1979
2142
|
};
|
|
1980
2143
|
}
|
|
1981
2144
|
|
|
@@ -2059,12 +2222,14 @@ function scanFeatures(projectRoot) {
|
|
|
2059
2222
|
}
|
|
2060
2223
|
|
|
2061
2224
|
// src/analyzers/dependencies.ts
|
|
2062
|
-
import { existsSync as existsSync17, readFileSync as
|
|
2225
|
+
import { existsSync as existsSync17, readFileSync as readFileSync14 } from "fs";
|
|
2063
2226
|
import { join as join17 } from "path";
|
|
2064
2227
|
var CATEGORIES = {
|
|
2065
2228
|
ui: [
|
|
2066
2229
|
"react",
|
|
2067
2230
|
"react-dom",
|
|
2231
|
+
"react-router",
|
|
2232
|
+
"react-router-dom",
|
|
2068
2233
|
"vue",
|
|
2069
2234
|
"svelte",
|
|
2070
2235
|
"@angular/core",
|
|
@@ -2212,7 +2377,7 @@ function scanDependencies(projectRoot) {
|
|
|
2212
2377
|
if (!existsSync17(pkgPath)) return result;
|
|
2213
2378
|
let pkg;
|
|
2214
2379
|
try {
|
|
2215
|
-
pkg = JSON.parse(
|
|
2380
|
+
pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
|
|
2216
2381
|
} catch {
|
|
2217
2382
|
return result;
|
|
2218
2383
|
}
|
|
@@ -2231,6 +2396,54 @@ function scanDependencies(projectRoot) {
|
|
|
2231
2396
|
return result;
|
|
2232
2397
|
}
|
|
2233
2398
|
|
|
2399
|
+
// src/workflow-model.ts
|
|
2400
|
+
import { existsSync as existsSync18, readFileSync as readFileSync15 } from "fs";
|
|
2401
|
+
import { join as join18 } from "path";
|
|
2402
|
+
function inferSuggestedShell(layout) {
|
|
2403
|
+
if (layout.hasSidebar) return "sidebar-main";
|
|
2404
|
+
if (layout.hasTopNav) return "top-nav-main";
|
|
2405
|
+
return "full-bleed";
|
|
2406
|
+
}
|
|
2407
|
+
function hasExistingProjectFootprint(detected) {
|
|
2408
|
+
return detected.framework !== "unknown" || detected.packageManager !== "unknown" || detected.hasTypeScript || detected.hasTailwind || detected.existingRuleFiles.length > 0;
|
|
2409
|
+
}
|
|
2410
|
+
function createBrownfieldInitSeed(detected, layout, styling) {
|
|
2411
|
+
return {
|
|
2412
|
+
version: 1,
|
|
2413
|
+
workflow: "brownfield-adoption",
|
|
2414
|
+
contractOnly: true,
|
|
2415
|
+
registryOptional: true,
|
|
2416
|
+
workflowMode: "brownfield-attach",
|
|
2417
|
+
target: detected.framework !== "unknown" ? detected.framework : "react",
|
|
2418
|
+
shell: inferSuggestedShell(layout),
|
|
2419
|
+
guard: "guided",
|
|
2420
|
+
density: "comfortable",
|
|
2421
|
+
theme: "luminarum",
|
|
2422
|
+
mode: styling.darkMode ? "dark" : "auto",
|
|
2423
|
+
existing: true,
|
|
2424
|
+
notes: [
|
|
2425
|
+
"Use decantr init --existing to attach Decantr contract and context files to this project.",
|
|
2426
|
+
"Registry content is optional during brownfield adoption.",
|
|
2427
|
+
"Use decantr add/remove, decantr theme switch, and registry commands later for hybrid composition."
|
|
2428
|
+
]
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
function readBrownfieldInitSeed(projectRoot) {
|
|
2432
|
+
const seedPath = join18(projectRoot, ".decantr", "init-seed.json");
|
|
2433
|
+
if (!existsSync18(seedPath)) {
|
|
2434
|
+
return null;
|
|
2435
|
+
}
|
|
2436
|
+
try {
|
|
2437
|
+
const parsed = JSON.parse(readFileSync15(seedPath, "utf-8"));
|
|
2438
|
+
if (parsed.workflow !== "brownfield-adoption") {
|
|
2439
|
+
return null;
|
|
2440
|
+
}
|
|
2441
|
+
return parsed;
|
|
2442
|
+
} catch {
|
|
2443
|
+
return null;
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2234
2447
|
// src/commands/analyze.ts
|
|
2235
2448
|
var BOLD3 = "\x1B[1m";
|
|
2236
2449
|
var DIM8 = "\x1B[2m";
|
|
@@ -2256,6 +2469,7 @@ ${BOLD3}Analyzing project...${RESET8}
|
|
|
2256
2469
|
const features = scanFeatures(projectRoot);
|
|
2257
2470
|
console.log(`${DIM8}Scanning dependencies...${RESET8}`);
|
|
2258
2471
|
const dependencies = scanDependencies(projectRoot);
|
|
2472
|
+
const initSeed = createBrownfieldInitSeed(project, layout, styling);
|
|
2259
2473
|
const analysis = {
|
|
2260
2474
|
version: 1,
|
|
2261
2475
|
analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2271,14 +2485,29 @@ ${BOLD3}Analyzing project...${RESET8}
|
|
|
2271
2485
|
styling,
|
|
2272
2486
|
layout,
|
|
2273
2487
|
features,
|
|
2274
|
-
dependencies
|
|
2488
|
+
dependencies,
|
|
2489
|
+
decantr: {
|
|
2490
|
+
workflow: "brownfield-adoption",
|
|
2491
|
+
registryOptional: true,
|
|
2492
|
+
attach: {
|
|
2493
|
+
entrypoint: "decantr analyze",
|
|
2494
|
+
contractOnly: true,
|
|
2495
|
+
initSeedPath: ".decantr/init-seed.json",
|
|
2496
|
+
recommendedCommand: "decantr init --existing --yes"
|
|
2497
|
+
},
|
|
2498
|
+
hybrid: {
|
|
2499
|
+
ownerCommands: ["decantr add", "decantr remove", "decantr theme switch", "decantr registry", "decantr upgrade"]
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2275
2502
|
};
|
|
2276
|
-
const decantrDir =
|
|
2277
|
-
if (!
|
|
2503
|
+
const decantrDir = join19(projectRoot, ".decantr");
|
|
2504
|
+
if (!existsSync19(decantrDir)) {
|
|
2278
2505
|
mkdirSync4(decantrDir, { recursive: true });
|
|
2279
2506
|
}
|
|
2280
|
-
const outputPath =
|
|
2507
|
+
const outputPath = join19(decantrDir, "analysis.json");
|
|
2508
|
+
const initSeedPath = join19(decantrDir, "init-seed.json");
|
|
2281
2509
|
writeFileSync9(outputPath, JSON.stringify(analysis, null, 2) + "\n", "utf-8");
|
|
2510
|
+
writeFileSync9(initSeedPath, JSON.stringify(initSeed, null, 2) + "\n", "utf-8");
|
|
2282
2511
|
console.log(`
|
|
2283
2512
|
${GREEN8}Analysis complete.${RESET8}
|
|
2284
2513
|
`);
|
|
@@ -2303,14 +2532,15 @@ ${GREEN8}Analysis complete.${RESET8}
|
|
|
2303
2532
|
console.log(` Dependencies: ${depCounts || "none categorized"}`);
|
|
2304
2533
|
console.log(`
|
|
2305
2534
|
${DIM8}Written to:${RESET8} ${outputPath}`);
|
|
2535
|
+
console.log(`${DIM8}Init seed:${RESET8} ${initSeedPath}`);
|
|
2306
2536
|
console.log(`
|
|
2307
|
-
${YELLOW5}Next step:${RESET8}
|
|
2537
|
+
${YELLOW5}Next step:${RESET8} Run ${BOLD3}decantr init --existing --yes${RESET8} to attach Decantr using the generated brownfield seed.
|
|
2308
2538
|
`);
|
|
2309
2539
|
}
|
|
2310
2540
|
|
|
2311
2541
|
// src/commands/magic.ts
|
|
2312
|
-
import { join as
|
|
2313
|
-
import { existsSync as
|
|
2542
|
+
import { join as join20 } from "path";
|
|
2543
|
+
import { existsSync as existsSync20 } from "fs";
|
|
2314
2544
|
import * as fs from "fs/promises";
|
|
2315
2545
|
var BOLD4 = "\x1B[1m";
|
|
2316
2546
|
var DIM9 = "\x1B[2m";
|
|
@@ -2538,15 +2768,25 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
2538
2768
|
console.log(` Archetype: ${intent.archetype}`);
|
|
2539
2769
|
}
|
|
2540
2770
|
console.log("");
|
|
2541
|
-
const essencePath =
|
|
2542
|
-
if (
|
|
2771
|
+
const essencePath = join20(projectRoot, "decantr.essence.json");
|
|
2772
|
+
if (existsSync20(essencePath)) {
|
|
2543
2773
|
console.log(error(" decantr.essence.json already exists in this directory."));
|
|
2544
2774
|
console.log(dim(" Remove it first or use a different directory."));
|
|
2545
2775
|
process.exitCode = 1;
|
|
2546
2776
|
return;
|
|
2547
2777
|
}
|
|
2778
|
+
const detected = detectProject(projectRoot);
|
|
2779
|
+
if (hasExistingProjectFootprint(detected)) {
|
|
2780
|
+
console.log(`${YELLOW6} Existing project detected.${RESET9}`);
|
|
2781
|
+
console.log(dim(" decantr magic stays greenfield-first and will not silently bootstrap over an existing app."));
|
|
2782
|
+
console.log(dim(" Running brownfield analysis instead so you can attach Decantr deliberately.\n"));
|
|
2783
|
+
cmdAnalyze(projectRoot);
|
|
2784
|
+
console.log(`${BOLD4}Recommended next step:${RESET9} ${cyan("decantr init --existing --yes")}`);
|
|
2785
|
+
console.log("");
|
|
2786
|
+
return;
|
|
2787
|
+
}
|
|
2548
2788
|
const registryClient = new RegistryClient({
|
|
2549
|
-
cacheDir:
|
|
2789
|
+
cacheDir: join20(projectRoot, ".decantr", "cache"),
|
|
2550
2790
|
apiUrl: options.registry,
|
|
2551
2791
|
offline: options.offline
|
|
2552
2792
|
});
|
|
@@ -2647,7 +2887,6 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
2647
2887
|
return;
|
|
2648
2888
|
}
|
|
2649
2889
|
console.log("");
|
|
2650
|
-
const detected = detectProject(projectRoot);
|
|
2651
2890
|
let archetypeData;
|
|
2652
2891
|
let composedSections;
|
|
2653
2892
|
let routeMap;
|
|
@@ -2809,14 +3048,14 @@ async function cmdMagic(prompt, projectRoot, options) {
|
|
|
2809
3048
|
if (result.gitignoreUpdated) {
|
|
2810
3049
|
console.log(` ${dim(".gitignore updated")}`);
|
|
2811
3050
|
}
|
|
2812
|
-
const contextDir =
|
|
3051
|
+
const contextDir = join20(projectRoot, ".decantr", "context");
|
|
2813
3052
|
let sectionCount = 0;
|
|
2814
3053
|
try {
|
|
2815
3054
|
const files = await fs.readdir(contextDir);
|
|
2816
3055
|
sectionCount = files.filter((f) => f.startsWith("section-")).length;
|
|
2817
3056
|
} catch {
|
|
2818
3057
|
}
|
|
2819
|
-
const treatmentsPath =
|
|
3058
|
+
const treatmentsPath = join20(projectRoot, "src", "styles", "treatments.css");
|
|
2820
3059
|
let hasLayers = false;
|
|
2821
3060
|
try {
|
|
2822
3061
|
const css = await fs.readFile(treatmentsPath, "utf-8");
|
|
@@ -2841,8 +3080,8 @@ ${GREEN9}${BOLD4}Quality summary:${RESET9}`);
|
|
|
2841
3080
|
}
|
|
2842
3081
|
|
|
2843
3082
|
// src/commands/export.ts
|
|
2844
|
-
import { readFileSync as
|
|
2845
|
-
import { join as
|
|
3083
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync10, existsSync as existsSync21, mkdirSync as mkdirSync5 } from "fs";
|
|
3084
|
+
import { join as join21, dirname } from "path";
|
|
2846
3085
|
var GREEN10 = "\x1B[32m";
|
|
2847
3086
|
var RED8 = "\x1B[31m";
|
|
2848
3087
|
var DIM10 = "\x1B[2m";
|
|
@@ -2988,19 +3227,19 @@ function generateCSSVars(tokens) {
|
|
|
2988
3227
|
return lines.join("\n");
|
|
2989
3228
|
}
|
|
2990
3229
|
async function cmdExport(target, projectRoot, options = {}) {
|
|
2991
|
-
const essencePath =
|
|
2992
|
-
const tokensPath =
|
|
2993
|
-
if (!
|
|
3230
|
+
const essencePath = join21(projectRoot, "decantr.essence.json");
|
|
3231
|
+
const tokensPath = join21(projectRoot, "src", "styles", "tokens.css");
|
|
3232
|
+
if (!existsSync21(essencePath)) {
|
|
2994
3233
|
console.error(`${RED8}No decantr.essence.json found. Run \`decantr init\` first.${RESET10}`);
|
|
2995
3234
|
process.exitCode = 1;
|
|
2996
3235
|
return;
|
|
2997
3236
|
}
|
|
2998
|
-
if (!
|
|
3237
|
+
if (!existsSync21(tokensPath)) {
|
|
2999
3238
|
console.error(`${RED8}No src/styles/tokens.css found. Run \`decantr refresh\` to generate tokens.${RESET10}`);
|
|
3000
3239
|
process.exitCode = 1;
|
|
3001
3240
|
return;
|
|
3002
3241
|
}
|
|
3003
|
-
const tokensCSS =
|
|
3242
|
+
const tokensCSS = readFileSync16(tokensPath, "utf-8");
|
|
3004
3243
|
const tokens = parseTokensCSS(tokensCSS);
|
|
3005
3244
|
if (tokens.size === 0) {
|
|
3006
3245
|
console.error(`${RED8}No --d-* tokens found in tokens.css.${RESET10}`);
|
|
@@ -3009,8 +3248,8 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
3009
3248
|
}
|
|
3010
3249
|
switch (target) {
|
|
3011
3250
|
case "shadcn": {
|
|
3012
|
-
const cssOut = options.output ??
|
|
3013
|
-
const jsonOut =
|
|
3251
|
+
const cssOut = options.output ?? join21(projectRoot, "src", "styles", "shadcn-theme.css");
|
|
3252
|
+
const jsonOut = join21(projectRoot, "components.json");
|
|
3014
3253
|
ensureDir(cssOut);
|
|
3015
3254
|
writeFileSync10(cssOut, generateShadcnCSS(tokens), "utf-8");
|
|
3016
3255
|
writeFileSync10(jsonOut, generateShadcnComponentsJSON(), "utf-8");
|
|
@@ -3020,7 +3259,7 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
3020
3259
|
break;
|
|
3021
3260
|
}
|
|
3022
3261
|
case "tailwind": {
|
|
3023
|
-
const out = options.output ??
|
|
3262
|
+
const out = options.output ?? join21(projectRoot, "tailwind.decantr.config.ts");
|
|
3024
3263
|
ensureDir(out);
|
|
3025
3264
|
writeFileSync10(out, generateTailwindConfig(tokens), "utf-8");
|
|
3026
3265
|
console.log(`${GREEN10}Exported Tailwind config:${RESET10}`);
|
|
@@ -3028,7 +3267,7 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
3028
3267
|
break;
|
|
3029
3268
|
}
|
|
3030
3269
|
case "css-vars": {
|
|
3031
|
-
const out = options.output ??
|
|
3270
|
+
const out = options.output ?? join21(projectRoot, "decantr-tokens.css");
|
|
3032
3271
|
ensureDir(out);
|
|
3033
3272
|
writeFileSync10(out, generateCSSVars(tokens), "utf-8");
|
|
3034
3273
|
console.log(`${GREEN10}Exported CSS variables:${RESET10}`);
|
|
@@ -3039,14 +3278,14 @@ async function cmdExport(target, projectRoot, options = {}) {
|
|
|
3039
3278
|
}
|
|
3040
3279
|
function ensureDir(filePath) {
|
|
3041
3280
|
const dir = dirname(filePath);
|
|
3042
|
-
if (!
|
|
3281
|
+
if (!existsSync21(dir)) {
|
|
3043
3282
|
mkdirSync5(dir, { recursive: true });
|
|
3044
3283
|
}
|
|
3045
3284
|
}
|
|
3046
3285
|
|
|
3047
3286
|
// src/commands/registry-mirror.ts
|
|
3048
3287
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync11 } from "fs";
|
|
3049
|
-
import { join as
|
|
3288
|
+
import { join as join22 } from "path";
|
|
3050
3289
|
import { RegistryAPIClient as RegistryAPIClient2, API_CONTENT_TYPES } from "@decantr/registry";
|
|
3051
3290
|
var GREEN11 = "\x1B[32m";
|
|
3052
3291
|
var RED9 = "\x1B[31m";
|
|
@@ -3074,7 +3313,7 @@ async function cmdRegistryMirror(projectRoot, options = {}) {
|
|
|
3074
3313
|
process.exitCode = 1;
|
|
3075
3314
|
return;
|
|
3076
3315
|
}
|
|
3077
|
-
const cacheDir =
|
|
3316
|
+
const cacheDir = join22(projectRoot, ".decantr", "cache");
|
|
3078
3317
|
const counts = {};
|
|
3079
3318
|
const failed = [];
|
|
3080
3319
|
console.log(`
|
|
@@ -3084,19 +3323,19 @@ Mirroring registry content to ${DIM11}.decantr/cache/${RESET11}
|
|
|
3084
3323
|
try {
|
|
3085
3324
|
const result = await apiClient.listContent(type, { namespace: "@official" });
|
|
3086
3325
|
const items = result.items;
|
|
3087
|
-
const typeDir =
|
|
3326
|
+
const typeDir = join22(cacheDir, "@official", type);
|
|
3088
3327
|
mkdirSync6(typeDir, { recursive: true });
|
|
3089
|
-
writeFileSync11(
|
|
3328
|
+
writeFileSync11(join22(typeDir, "index.json"), JSON.stringify(result, null, 2));
|
|
3090
3329
|
let itemCount = 0;
|
|
3091
3330
|
for (const item of items) {
|
|
3092
3331
|
const slug = item.slug || item.id;
|
|
3093
3332
|
if (!slug) continue;
|
|
3094
3333
|
try {
|
|
3095
3334
|
const fullItem = await apiClient.getContent(type, "@official", slug);
|
|
3096
|
-
writeFileSync11(
|
|
3335
|
+
writeFileSync11(join22(typeDir, `${slug}.json`), JSON.stringify(fullItem, null, 2));
|
|
3097
3336
|
itemCount++;
|
|
3098
3337
|
} catch {
|
|
3099
|
-
writeFileSync11(
|
|
3338
|
+
writeFileSync11(join22(typeDir, `${slug}.json`), JSON.stringify(item, null, 2));
|
|
3100
3339
|
itemCount++;
|
|
3101
3340
|
}
|
|
3102
3341
|
}
|
|
@@ -3111,8 +3350,8 @@ Mirroring registry content to ${DIM11}.decantr/cache/${RESET11}
|
|
|
3111
3350
|
mirrored_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3112
3351
|
counts
|
|
3113
3352
|
};
|
|
3114
|
-
mkdirSync6(
|
|
3115
|
-
writeFileSync11(
|
|
3353
|
+
mkdirSync6(join22(cacheDir), { recursive: true });
|
|
3354
|
+
writeFileSync11(join22(cacheDir, "mirror-manifest.json"), JSON.stringify(manifest, null, 2));
|
|
3116
3355
|
const totalItems = Object.values(counts).reduce((a, b) => a + b, 0);
|
|
3117
3356
|
console.log("");
|
|
3118
3357
|
if (failed.length > 0) {
|
|
@@ -3125,98 +3364,160 @@ Mirroring registry content to ${DIM11}.decantr/cache/${RESET11}
|
|
|
3125
3364
|
}
|
|
3126
3365
|
|
|
3127
3366
|
// src/commands/new-project.ts
|
|
3128
|
-
import { existsSync as
|
|
3129
|
-
import { join as
|
|
3367
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync9 } from "fs";
|
|
3368
|
+
import { join as join25, resolve as resolve2 } from "path";
|
|
3130
3369
|
import { execSync } from "child_process";
|
|
3131
3370
|
import { fileURLToPath } from "url";
|
|
3132
3371
|
|
|
3133
3372
|
// src/offline-content.ts
|
|
3134
|
-
import { cpSync, existsSync as
|
|
3135
|
-
import { join as
|
|
3373
|
+
import { cpSync, existsSync as existsSync23, mkdirSync as mkdirSync7 } from "fs";
|
|
3374
|
+
import { join as join23, resolve } from "path";
|
|
3136
3375
|
var CONTENT_TYPES2 = ["archetypes", "blueprints", "patterns", "themes", "shells"];
|
|
3137
3376
|
function copyIfExists(source, target) {
|
|
3138
|
-
if (!
|
|
3377
|
+
if (!existsSync23(source)) return false;
|
|
3139
3378
|
if (resolve(source) === resolve(target)) return true;
|
|
3140
3379
|
cpSync(source, target, { recursive: true });
|
|
3141
3380
|
return true;
|
|
3142
3381
|
}
|
|
3143
3382
|
function hydrateContentRoot(projectDir, contentRoot) {
|
|
3144
|
-
if (!
|
|
3145
|
-
const customRoot =
|
|
3146
|
-
const cacheRoot =
|
|
3383
|
+
if (!existsSync23(contentRoot)) return false;
|
|
3384
|
+
const customRoot = join23(projectDir, ".decantr", "custom");
|
|
3385
|
+
const cacheRoot = join23(projectDir, ".decantr", "cache", "@official");
|
|
3147
3386
|
mkdirSync7(customRoot, { recursive: true });
|
|
3148
3387
|
mkdirSync7(cacheRoot, { recursive: true });
|
|
3149
3388
|
let copiedAny = false;
|
|
3150
3389
|
for (const type of CONTENT_TYPES2) {
|
|
3151
|
-
const sourceDir =
|
|
3152
|
-
if (!
|
|
3153
|
-
cpSync(sourceDir,
|
|
3154
|
-
cpSync(sourceDir,
|
|
3390
|
+
const sourceDir = join23(contentRoot, type);
|
|
3391
|
+
if (!existsSync23(sourceDir)) continue;
|
|
3392
|
+
cpSync(sourceDir, join23(customRoot, type), { recursive: true });
|
|
3393
|
+
cpSync(sourceDir, join23(cacheRoot, type), { recursive: true });
|
|
3155
3394
|
copiedAny = true;
|
|
3156
3395
|
}
|
|
3157
3396
|
return copiedAny;
|
|
3158
3397
|
}
|
|
3159
3398
|
function seedOfflineRegistry(projectDir, workspaceRoot) {
|
|
3160
|
-
const projectDecantrRoot =
|
|
3399
|
+
const projectDecantrRoot = join23(projectDir, ".decantr");
|
|
3161
3400
|
mkdirSync7(projectDecantrRoot, { recursive: true });
|
|
3162
|
-
const
|
|
3163
|
-
|
|
3401
|
+
const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve(process.env.DECANTR_CONTENT_DIR) : null;
|
|
3402
|
+
if (configuredContentRoot && hydrateContentRoot(projectDir, configuredContentRoot)) {
|
|
3403
|
+
return { seeded: true, strategy: "configured-content-root" };
|
|
3404
|
+
}
|
|
3405
|
+
const copiedCache = copyIfExists(join23(workspaceRoot, ".decantr", "cache"), join23(projectDecantrRoot, "cache"));
|
|
3406
|
+
const copiedCustom = copyIfExists(join23(workspaceRoot, ".decantr", "custom"), join23(projectDecantrRoot, "custom"));
|
|
3164
3407
|
if (copiedCache || copiedCustom) {
|
|
3165
3408
|
return { seeded: true, strategy: "workspace-cache" };
|
|
3166
3409
|
}
|
|
3167
|
-
const configuredContentRoot = process.env.DECANTR_CONTENT_DIR ? resolve(process.env.DECANTR_CONTENT_DIR) : null;
|
|
3168
3410
|
const siblingContentRoot = resolve(workspaceRoot, "..", "decantr-content");
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
return {
|
|
3172
|
-
seeded: true,
|
|
3173
|
-
strategy: configuredContentRoot && existsSync22(configuredContentRoot) ? "configured-content-root" : "sibling-content-root"
|
|
3174
|
-
};
|
|
3411
|
+
if (hydrateContentRoot(projectDir, siblingContentRoot)) {
|
|
3412
|
+
return { seeded: true, strategy: "sibling-content-root" };
|
|
3175
3413
|
}
|
|
3176
3414
|
return { seeded: false, strategy: null };
|
|
3177
3415
|
}
|
|
3178
3416
|
|
|
3179
|
-
// src/
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
var
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3417
|
+
// src/bootstrap.ts
|
|
3418
|
+
import { mkdirSync as mkdirSync8, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "fs";
|
|
3419
|
+
import { basename, join as join24 } from "path";
|
|
3420
|
+
import { resolvePackAdapter } from "@decantr/core";
|
|
3421
|
+
var reactViteBootstrapAdapter = {
|
|
3422
|
+
id: "react-vite",
|
|
3423
|
+
label: "React + Vite starter",
|
|
3424
|
+
writeProjectFiles(projectDir, title, routingMode) {
|
|
3425
|
+
const srcDir = join24(projectDir, "src");
|
|
3426
|
+
const routerImport = routingMode === "hash" ? "HashRouter" : "BrowserRouter";
|
|
3427
|
+
const packageJson = {
|
|
3428
|
+
name: basename(projectDir) || "decantr-app",
|
|
3429
|
+
private: true,
|
|
3430
|
+
version: "0.0.0",
|
|
3431
|
+
type: "module",
|
|
3432
|
+
scripts: {
|
|
3433
|
+
dev: "vite",
|
|
3434
|
+
build: "tsc -b && vite build",
|
|
3435
|
+
preview: "vite preview"
|
|
3436
|
+
},
|
|
3437
|
+
dependencies: {
|
|
3438
|
+
react: "^19.0.0",
|
|
3439
|
+
"react-dom": "^19.0.0",
|
|
3440
|
+
"react-router-dom": "^7.0.0",
|
|
3441
|
+
"@decantr/css": "^1.0.0"
|
|
3442
|
+
},
|
|
3443
|
+
devDependencies: {
|
|
3444
|
+
"@types/react": "^19.0.0",
|
|
3445
|
+
"@types/react-dom": "^19.0.0",
|
|
3446
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
3447
|
+
typescript: "^5.7.0",
|
|
3448
|
+
vite: "^6.0.0"
|
|
3449
|
+
}
|
|
3450
|
+
};
|
|
3451
|
+
writeFileSync12(join24(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
|
|
3452
|
+
const viteConfig = `import { defineConfig } from 'vite';
|
|
3453
|
+
import react from '@vitejs/plugin-react';
|
|
3454
|
+
|
|
3455
|
+
export default defineConfig({
|
|
3456
|
+
plugins: [react()],
|
|
3457
|
+
});
|
|
3190
3458
|
`;
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3459
|
+
writeFileSync12(join24(projectDir, "vite.config.ts"), viteConfig);
|
|
3460
|
+
const tsconfig = {
|
|
3461
|
+
compilerOptions: {
|
|
3462
|
+
target: "ES2020",
|
|
3463
|
+
useDefineForClassFields: true,
|
|
3464
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
3465
|
+
module: "ESNext",
|
|
3466
|
+
skipLibCheck: true,
|
|
3467
|
+
moduleResolution: "bundler",
|
|
3468
|
+
allowImportingTsExtensions: true,
|
|
3469
|
+
isolatedModules: true,
|
|
3470
|
+
moduleDetection: "force",
|
|
3471
|
+
noEmit: true,
|
|
3472
|
+
jsx: "react-jsx",
|
|
3473
|
+
strict: true,
|
|
3474
|
+
noUnusedLocals: true,
|
|
3475
|
+
noUnusedParameters: true,
|
|
3476
|
+
noFallthroughCasesInSwitch: true,
|
|
3477
|
+
noUncheckedSideEffectImports: true
|
|
3478
|
+
},
|
|
3479
|
+
include: ["src"]
|
|
3480
|
+
};
|
|
3481
|
+
writeFileSync12(join24(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
|
|
3482
|
+
const tsconfigApp = {
|
|
3483
|
+
compilerOptions: {
|
|
3484
|
+
tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
3485
|
+
target: "ES2020",
|
|
3486
|
+
useDefineForClassFields: true,
|
|
3487
|
+
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
3488
|
+
module: "ESNext",
|
|
3489
|
+
skipLibCheck: true,
|
|
3490
|
+
moduleResolution: "bundler",
|
|
3491
|
+
allowImportingTsExtensions: true,
|
|
3492
|
+
isolatedModules: true,
|
|
3493
|
+
moduleDetection: "force",
|
|
3494
|
+
noEmit: true,
|
|
3495
|
+
jsx: "react-jsx",
|
|
3496
|
+
strict: true,
|
|
3497
|
+
noUnusedLocals: true,
|
|
3498
|
+
noUnusedParameters: true,
|
|
3499
|
+
noFallthroughCasesInSwitch: true,
|
|
3500
|
+
noUncheckedSideEffectImports: true
|
|
3501
|
+
},
|
|
3502
|
+
include: ["src"]
|
|
3503
|
+
};
|
|
3504
|
+
writeFileSync12(join24(projectDir, "tsconfig.app.json"), JSON.stringify(tsconfigApp, null, 2) + "\n");
|
|
3505
|
+
const indexHtml = `<!doctype html>
|
|
3506
|
+
<html lang="en">
|
|
3507
|
+
<head>
|
|
3508
|
+
<meta charset="UTF-8" />
|
|
3509
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
3510
|
+
<title>${title}</title>
|
|
3511
|
+
</head>
|
|
3512
|
+
<body>
|
|
3513
|
+
<div id="root"></div>
|
|
3514
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
3515
|
+
</body>
|
|
3516
|
+
</html>
|
|
3517
|
+
`;
|
|
3518
|
+
writeFileSync12(join24(projectDir, "index.html"), indexHtml);
|
|
3519
|
+
mkdirSync8(srcDir, { recursive: true });
|
|
3520
|
+
const mainTsx = `import { StrictMode } from 'react';
|
|
3220
3521
|
import { createRoot } from 'react-dom/client';
|
|
3221
3522
|
import { ${routerImport} } from 'react-router-dom';
|
|
3222
3523
|
import { App } from './App';
|
|
@@ -3232,8 +3533,8 @@ createRoot(document.getElementById('root')!).render(
|
|
|
3232
3533
|
</StrictMode>,
|
|
3233
3534
|
);
|
|
3234
3535
|
`;
|
|
3235
|
-
|
|
3236
|
-
|
|
3536
|
+
writeFileSync12(join24(srcDir, "main.tsx"), mainTsx);
|
|
3537
|
+
const appTsx = `import { css } from '@decantr/css';
|
|
3237
3538
|
import { Routes, Route } from 'react-router-dom';
|
|
3238
3539
|
|
|
3239
3540
|
function WelcomePage() {
|
|
@@ -3267,137 +3568,105 @@ export function App() {
|
|
|
3267
3568
|
);
|
|
3268
3569
|
}
|
|
3269
3570
|
`;
|
|
3270
|
-
|
|
3571
|
+
writeFileSync12(join24(srcDir, "App.tsx"), appTsx);
|
|
3572
|
+
writeFileSync12(join24(srcDir, "vite-env.d.ts"), '/// <reference types="vite/client" />\n');
|
|
3573
|
+
mkdirSync8(join24(srcDir, "styles"), { recursive: true });
|
|
3574
|
+
}
|
|
3575
|
+
};
|
|
3576
|
+
var BOOTSTRAP_ADAPTERS = {
|
|
3577
|
+
"react-vite": reactViteBootstrapAdapter
|
|
3578
|
+
};
|
|
3579
|
+
function resolveBootstrapTarget(target) {
|
|
3580
|
+
const normalizedTarget = (target || "react").toLowerCase();
|
|
3581
|
+
const platformType = "spa";
|
|
3582
|
+
const packAdapter = resolvePackAdapter(normalizedTarget, platformType);
|
|
3583
|
+
return {
|
|
3584
|
+
target: normalizedTarget,
|
|
3585
|
+
platformType,
|
|
3586
|
+
packAdapter,
|
|
3587
|
+
bootstrapAdapterId: BOOTSTRAP_ADAPTERS[packAdapter]?.id ?? null
|
|
3588
|
+
};
|
|
3589
|
+
}
|
|
3590
|
+
function getBootstrapAdapter(resolution) {
|
|
3591
|
+
if (!resolution.bootstrapAdapterId) {
|
|
3592
|
+
return null;
|
|
3593
|
+
}
|
|
3594
|
+
return BOOTSTRAP_ADAPTERS[resolution.bootstrapAdapterId] ?? null;
|
|
3595
|
+
}
|
|
3596
|
+
function detectRoutingMode(projectDir) {
|
|
3597
|
+
try {
|
|
3598
|
+
const essence = JSON.parse(readFileSync17(join24(projectDir, "decantr.essence.json"), "utf-8"));
|
|
3599
|
+
const routing = essence.meta?.platform?.routing;
|
|
3600
|
+
if (routing === "history" || routing === "pathname") {
|
|
3601
|
+
return routing;
|
|
3602
|
+
}
|
|
3603
|
+
return "hash";
|
|
3604
|
+
} catch {
|
|
3605
|
+
return "hash";
|
|
3606
|
+
}
|
|
3607
|
+
}
|
|
3608
|
+
|
|
3609
|
+
// src/commands/new-project.ts
|
|
3610
|
+
var BOLD5 = "\x1B[1m";
|
|
3611
|
+
var DIM12 = "\x1B[2m";
|
|
3612
|
+
var RESET12 = "\x1B[0m";
|
|
3613
|
+
var RED10 = "\x1B[31m";
|
|
3614
|
+
var GREEN12 = "\x1B[32m";
|
|
3615
|
+
var CYAN6 = "\x1B[36m";
|
|
3616
|
+
var YELLOW8 = "\x1B[33m";
|
|
3617
|
+
function heading(text) {
|
|
3618
|
+
return `
|
|
3619
|
+
${BOLD5}${text}${RESET12}
|
|
3620
|
+
`;
|
|
3621
|
+
}
|
|
3622
|
+
function success2(text) {
|
|
3623
|
+
return `${GREEN12}${text}${RESET12}`;
|
|
3624
|
+
}
|
|
3625
|
+
function error2(text) {
|
|
3626
|
+
return `${RED10}${text}${RESET12}`;
|
|
3271
3627
|
}
|
|
3272
|
-
function
|
|
3273
|
-
return
|
|
3628
|
+
function dim2(text) {
|
|
3629
|
+
return `${DIM12}${text}${RESET12}`;
|
|
3630
|
+
}
|
|
3631
|
+
function cyan2(text) {
|
|
3632
|
+
return `${CYAN6}${text}${RESET12}`;
|
|
3274
3633
|
}
|
|
3275
3634
|
async function cmdNewProject(projectName, options) {
|
|
3276
3635
|
const workspaceRoot = process.cwd();
|
|
3277
3636
|
const projectDir = resolve2(workspaceRoot, projectName);
|
|
3637
|
+
const bootstrapTarget = resolveBootstrapTarget(options.target);
|
|
3638
|
+
const bootstrapAdapter = getBootstrapAdapter(bootstrapTarget);
|
|
3639
|
+
const hasRunnableBootstrap = Boolean(bootstrapAdapter);
|
|
3278
3640
|
if (!/^[a-z0-9][a-z0-9._-]*$/i.test(projectName)) {
|
|
3279
3641
|
console.error(error2("Invalid project name. Use alphanumeric characters, hyphens, dots, or underscores."));
|
|
3280
3642
|
process.exitCode = 1;
|
|
3281
3643
|
return;
|
|
3282
3644
|
}
|
|
3283
|
-
if (
|
|
3645
|
+
if (existsSync24(projectDir)) {
|
|
3284
3646
|
console.error(error2(`Directory "${projectName}" already exists.`));
|
|
3285
3647
|
process.exitCode = 1;
|
|
3286
3648
|
return;
|
|
3287
3649
|
}
|
|
3288
3650
|
console.log(heading(`Creating ${projectName}...`));
|
|
3289
|
-
|
|
3651
|
+
mkdirSync9(projectDir, { recursive: true });
|
|
3290
3652
|
console.log(dim2(` Created ${projectName}/`));
|
|
3291
|
-
const packageJson = {
|
|
3292
|
-
name: projectName,
|
|
3293
|
-
private: true,
|
|
3294
|
-
version: "0.0.0",
|
|
3295
|
-
type: "module",
|
|
3296
|
-
scripts: {
|
|
3297
|
-
dev: "vite",
|
|
3298
|
-
build: "tsc -b && vite build",
|
|
3299
|
-
preview: "vite preview"
|
|
3300
|
-
},
|
|
3301
|
-
dependencies: {
|
|
3302
|
-
"react": "^19.0.0",
|
|
3303
|
-
"react-dom": "^19.0.0",
|
|
3304
|
-
"react-router-dom": "^7.0.0",
|
|
3305
|
-
"@decantr/css": "^1.0.0"
|
|
3306
|
-
},
|
|
3307
|
-
devDependencies: {
|
|
3308
|
-
"@types/react": "^19.0.0",
|
|
3309
|
-
"@types/react-dom": "^19.0.0",
|
|
3310
|
-
"@vitejs/plugin-react": "^4.0.0",
|
|
3311
|
-
"typescript": "^5.7.0",
|
|
3312
|
-
"vite": "^6.0.0"
|
|
3313
|
-
}
|
|
3314
|
-
};
|
|
3315
|
-
writeFileSync12(join23(projectDir, "package.json"), JSON.stringify(packageJson, null, 2) + "\n");
|
|
3316
|
-
console.log(dim2(" Created package.json"));
|
|
3317
|
-
const viteConfig = `import { defineConfig } from 'vite';
|
|
3318
|
-
import react from '@vitejs/plugin-react';
|
|
3319
|
-
|
|
3320
|
-
export default defineConfig({
|
|
3321
|
-
plugins: [react()],
|
|
3322
|
-
});
|
|
3323
|
-
`;
|
|
3324
|
-
writeFileSync12(join23(projectDir, "vite.config.ts"), viteConfig);
|
|
3325
|
-
console.log(dim2(" Created vite.config.ts"));
|
|
3326
|
-
const tsconfig = {
|
|
3327
|
-
compilerOptions: {
|
|
3328
|
-
target: "ES2020",
|
|
3329
|
-
useDefineForClassFields: true,
|
|
3330
|
-
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
3331
|
-
module: "ESNext",
|
|
3332
|
-
skipLibCheck: true,
|
|
3333
|
-
moduleResolution: "bundler",
|
|
3334
|
-
allowImportingTsExtensions: true,
|
|
3335
|
-
isolatedModules: true,
|
|
3336
|
-
moduleDetection: "force",
|
|
3337
|
-
noEmit: true,
|
|
3338
|
-
jsx: "react-jsx",
|
|
3339
|
-
strict: true,
|
|
3340
|
-
noUnusedLocals: true,
|
|
3341
|
-
noUnusedParameters: true,
|
|
3342
|
-
noFallthroughCasesInSwitch: true,
|
|
3343
|
-
noUncheckedSideEffectImports: true
|
|
3344
|
-
},
|
|
3345
|
-
include: ["src"]
|
|
3346
|
-
};
|
|
3347
|
-
writeFileSync12(join23(projectDir, "tsconfig.json"), JSON.stringify(tsconfig, null, 2) + "\n");
|
|
3348
|
-
const tsconfigApp = {
|
|
3349
|
-
compilerOptions: {
|
|
3350
|
-
tsBuildInfoFile: "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
3351
|
-
target: "ES2020",
|
|
3352
|
-
useDefineForClassFields: true,
|
|
3353
|
-
lib: ["ES2020", "DOM", "DOM.Iterable"],
|
|
3354
|
-
module: "ESNext",
|
|
3355
|
-
skipLibCheck: true,
|
|
3356
|
-
moduleResolution: "bundler",
|
|
3357
|
-
allowImportingTsExtensions: true,
|
|
3358
|
-
isolatedModules: true,
|
|
3359
|
-
moduleDetection: "force",
|
|
3360
|
-
noEmit: true,
|
|
3361
|
-
jsx: "react-jsx",
|
|
3362
|
-
strict: true,
|
|
3363
|
-
noUnusedLocals: true,
|
|
3364
|
-
noUnusedParameters: true,
|
|
3365
|
-
noFallthroughCasesInSwitch: true,
|
|
3366
|
-
noUncheckedSideEffectImports: true
|
|
3367
|
-
},
|
|
3368
|
-
include: ["src"]
|
|
3369
|
-
};
|
|
3370
|
-
writeFileSync12(join23(projectDir, "tsconfig.app.json"), JSON.stringify(tsconfigApp, null, 2) + "\n");
|
|
3371
|
-
console.log(dim2(" Created tsconfig.json"));
|
|
3372
3653
|
const title = projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
<body>
|
|
3381
|
-
<div id="root"></div>
|
|
3382
|
-
<script type="module" src="/src/main.tsx"></script>
|
|
3383
|
-
</body>
|
|
3384
|
-
</html>
|
|
3385
|
-
`;
|
|
3386
|
-
writeFileSync12(join23(projectDir, "index.html"), indexHtml);
|
|
3387
|
-
console.log(dim2(" Created index.html"));
|
|
3388
|
-
const srcDir = join23(projectDir, "src");
|
|
3389
|
-
mkdirSync8(srcDir, { recursive: true });
|
|
3390
|
-
writeStarterRuntimeFiles(projectDir, title, getTargetRoutingMode(options.target));
|
|
3391
|
-
writeFileSync12(join23(srcDir, "vite-env.d.ts"), '/// <reference types="vite/client" />\n');
|
|
3392
|
-
mkdirSync8(join23(srcDir, "styles"), { recursive: true });
|
|
3393
|
-
console.log(dim2(" Created src/"));
|
|
3394
|
-
console.log(heading("Installing dependencies..."));
|
|
3654
|
+
if (bootstrapAdapter) {
|
|
3655
|
+
bootstrapAdapter.writeProjectFiles(projectDir, title, "hash");
|
|
3656
|
+
console.log(dim2(` Bootstrapped ${bootstrapAdapter.label}`));
|
|
3657
|
+
} else {
|
|
3658
|
+
console.log(`${YELLOW8} No greenfield bootstrap adapter is available yet for target "${bootstrapTarget.target}" (${bootstrapTarget.packAdapter}).${RESET12}`);
|
|
3659
|
+
console.log(dim2(" Continuing with a contract-only Decantr workspace so the command stays target-honest instead of writing the wrong runtime."));
|
|
3660
|
+
}
|
|
3395
3661
|
const packageManager = detectPackageManager();
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3662
|
+
if (hasRunnableBootstrap) {
|
|
3663
|
+
console.log(heading("Installing dependencies..."));
|
|
3664
|
+
try {
|
|
3665
|
+
execSync(`${packageManager} install`, { cwd: projectDir, stdio: "inherit" });
|
|
3666
|
+
} catch {
|
|
3667
|
+
console.log(`
|
|
3400
3668
|
${YELLOW8}Dependency install failed. Run \`${packageManager} install\` manually.${RESET12}`);
|
|
3669
|
+
}
|
|
3401
3670
|
}
|
|
3402
3671
|
const requiresOfflineContent = Boolean(options.offline && (options.blueprint || options.archetype));
|
|
3403
3672
|
const seeded = options.offline ? seedOfflineRegistry(projectDir, workspaceRoot) : { seeded: false, strategy: null };
|
|
@@ -3429,10 +3698,12 @@ ${YELLOW8}Dependency install failed. Run \`${packageManager} install\` manually.
|
|
|
3429
3698
|
if (options.registry) initFlags.push(`--registry=${options.registry}`);
|
|
3430
3699
|
try {
|
|
3431
3700
|
const bundledCliEntrypoint = fileURLToPath(new URL("./bin.js", import.meta.url));
|
|
3432
|
-
const cliEntrypoint =
|
|
3701
|
+
const cliEntrypoint = existsSync24(bundledCliEntrypoint) ? bundledCliEntrypoint : process.argv[1] && existsSync24(process.argv[1]) ? process.argv[1] : null;
|
|
3433
3702
|
const cliPath = cliEntrypoint ? `"${process.execPath}" "${cliEntrypoint}"` : "npx decantr";
|
|
3434
3703
|
execSync(`${cliPath} init ${initFlags.join(" ")}`, { cwd: projectDir, stdio: "inherit" });
|
|
3435
|
-
|
|
3704
|
+
if (bootstrapAdapter) {
|
|
3705
|
+
bootstrapAdapter.writeProjectFiles(projectDir, title, detectRoutingMode(projectDir));
|
|
3706
|
+
}
|
|
3436
3707
|
} catch {
|
|
3437
3708
|
console.log(`
|
|
3438
3709
|
${YELLOW8}Decantr init encountered issues. Run \`decantr init\` manually inside ${projectName}/.${RESET12}`);
|
|
@@ -3441,17 +3712,21 @@ ${YELLOW8}Decantr init encountered issues. Run \`decantr init\` manually inside
|
|
|
3441
3712
|
\u2713 Project "${projectName}" created!
|
|
3442
3713
|
`));
|
|
3443
3714
|
console.log(` ${cyan2("cd " + projectName)}`);
|
|
3444
|
-
|
|
3715
|
+
if (bootstrapAdapter) {
|
|
3716
|
+
console.log(` ${cyan2(packageManager + " run dev")}`);
|
|
3717
|
+
} else {
|
|
3718
|
+
console.log(dim2(` Contract-only mode for target ${bootstrapTarget.target}. Bring your own runtime, or rerun ${cyan2(`decantr new ${projectName} --target=react`)} for the current starter adapter.`));
|
|
3719
|
+
}
|
|
3445
3720
|
console.log("");
|
|
3446
3721
|
}
|
|
3447
3722
|
function detectPackageManager() {
|
|
3448
|
-
if (
|
|
3723
|
+
if (existsSync24(join25(process.cwd(), "pnpm-lock.yaml")) || existsSync24(join25(process.cwd(), "pnpm-workspace.yaml"))) {
|
|
3449
3724
|
return "pnpm";
|
|
3450
3725
|
}
|
|
3451
|
-
if (
|
|
3726
|
+
if (existsSync24(join25(process.cwd(), "yarn.lock"))) {
|
|
3452
3727
|
return "yarn";
|
|
3453
3728
|
}
|
|
3454
|
-
if (
|
|
3729
|
+
if (existsSync24(join25(process.cwd(), "bun.lockb")) || existsSync24(join25(process.cwd(), "bun.lock"))) {
|
|
3455
3730
|
return "bun";
|
|
3456
3731
|
}
|
|
3457
3732
|
return "npm";
|
|
@@ -3551,9 +3826,11 @@ function extractPatternName(item) {
|
|
|
3551
3826
|
}
|
|
3552
3827
|
return "custom";
|
|
3553
3828
|
}
|
|
3554
|
-
function
|
|
3829
|
+
function generateGreenfieldPrompt(ctx) {
|
|
3555
3830
|
const lines = [];
|
|
3556
|
-
lines.push("Build this application using the Decantr design system.");
|
|
3831
|
+
lines.push("Build this greenfield application using the Decantr design system.");
|
|
3832
|
+
lines.push("");
|
|
3833
|
+
lines.push("This workspace is a new Decantr scaffold. Use the contract to create or extend the runtime deliberately, not to reverse-engineer a hidden starter.");
|
|
3557
3834
|
lines.push("");
|
|
3558
3835
|
lines.push("Treat the compiled execution-pack files as the primary source of truth.");
|
|
3559
3836
|
lines.push("Use narrative docs only as secondary explanation when the compiled packs are not enough.");
|
|
@@ -3572,6 +3849,12 @@ function generateCuratedPrompt(ctx) {
|
|
|
3572
3849
|
lines.push("- Start with the shell layouts and route structure first, then build section pages route by route.");
|
|
3573
3850
|
lines.push("- Import src/styles/global.css, src/styles/tokens.css, and src/styles/treatments.css.");
|
|
3574
3851
|
lines.push("- Use the existing Decantr tokens, treatments, and decorators instead of inventing a new visual system.");
|
|
3852
|
+
lines.push("- If package.json, app entry files, or router/runtime files are absent, create them explicitly for the declared target instead of assuming a hidden starter already exists in the workspace.");
|
|
3853
|
+
lines.push("- Do not use inline visual style values or component-scoped <style> tags as the primary styling path. Colors, spacing, borders, shadows, gradients, and transitions should come from atoms, treatments, decorators, or CSS variables. Inline styles are only acceptable for truly dynamic geometry that cannot be expressed through the contract.");
|
|
3854
|
+
lines.push("- Let shells own spacing, centering, and scroll containers. Pages should not duplicate shell responsibilities with extra full-height wrappers, max-width wrappers, or page-local padding unless the route contract explicitly requires it.");
|
|
3855
|
+
lines.push("- If command_palette or hotkeys are declared in the generated context, implement them as real features. Do not merely acknowledge them in copy or comments.");
|
|
3856
|
+
lines.push("- Treat declared hotkeys as interaction bindings by default, not visible navigation label text, unless the shell or route contract explicitly calls for shown shortcut hints.");
|
|
3857
|
+
lines.push("- If a required decorator class is referenced in the contract but missing from generated CSS, report the contract gap instead of inventing a parallel visual system.");
|
|
3575
3858
|
lines.push("- Do not modify generated context files unless the task is explicitly to regenerate or refresh Decantr context.");
|
|
3576
3859
|
lines.push("");
|
|
3577
3860
|
lines.push("Execution flow:");
|
|
@@ -3581,6 +3864,50 @@ function generateCuratedPrompt(ctx) {
|
|
|
3581
3864
|
lines.push("- If a required context file is missing or inconsistent, stop and report exactly which file is missing before continuing.");
|
|
3582
3865
|
return lines.join("\n");
|
|
3583
3866
|
}
|
|
3867
|
+
function generateBrownfieldPrompt(ctx) {
|
|
3868
|
+
const lines = [];
|
|
3869
|
+
lines.push("Attach Decantr to this existing application without rebuilding it from scratch.");
|
|
3870
|
+
lines.push("");
|
|
3871
|
+
lines.push("Preserve the current framework, package manager, router, build tooling, and working runtime structure unless the generated Decantr contract gives you a reviewed reason to change them.");
|
|
3872
|
+
lines.push("");
|
|
3873
|
+
lines.push("Treat .decantr/analysis.json as the factual inventory of the current app.");
|
|
3874
|
+
lines.push("Treat .decantr/init-seed.json as the recommended Decantr attach defaults.");
|
|
3875
|
+
lines.push("Treat the compiled execution-pack files as the Decantr contract you are layering onto the app.");
|
|
3876
|
+
lines.push("Use only files present in this workspace as the source of truth. If the runtime and contract disagree, call out the drift explicitly instead of improvising a rewrite.");
|
|
3877
|
+
lines.push("");
|
|
3878
|
+
lines.push("Read in this order:");
|
|
3879
|
+
lines.push("1. .decantr/analysis.json for the detected framework, routes, styling, layout, and dependencies.");
|
|
3880
|
+
lines.push("2. .decantr/init-seed.json for the intended attach defaults and workflow lane.");
|
|
3881
|
+
lines.push("3. DECANTR.md for guard rules, CSS expectations, and Decantr operating rules.");
|
|
3882
|
+
lines.push("4. .decantr/context/scaffold-pack.md for the compact compiled shell, theme, feature, and route contract.");
|
|
3883
|
+
lines.push("5. .decantr/context/scaffold.md for broader topology, route map, and voice guidance.");
|
|
3884
|
+
lines.push("6. The matching section and page pack files only when you are working on those specific surfaces.");
|
|
3885
|
+
lines.push("");
|
|
3886
|
+
lines.push("Implementation rules:");
|
|
3887
|
+
lines.push("- Preserve existing files and working flows whenever possible. Prefer incremental attachment over whole-app rewrites.");
|
|
3888
|
+
lines.push("- Map existing routes and components onto the declared Decantr sections/pages before creating new files.");
|
|
3889
|
+
lines.push("- If package.json, router files, or style files already exist, extend them deliberately instead of replacing them with a different starter shape.");
|
|
3890
|
+
lines.push("- If Decantr style files are absent, add src/styles/global.css, src/styles/tokens.css, and src/styles/treatments.css in a way that fits the current app structure.");
|
|
3891
|
+
lines.push("- Use the existing Decantr tokens, treatments, and decorators instead of inventing a parallel visual system.");
|
|
3892
|
+
lines.push("- Registry content is optional in this workflow unless the task explicitly asks for blueprint/theme/pattern enrichment.");
|
|
3893
|
+
lines.push("- Do not invent routes, sections, shells, themes, or features that are not present in the compiled packs.");
|
|
3894
|
+
lines.push("- Do not use inline visual style values or component-scoped <style> tags as the primary styling path. Colors, spacing, borders, shadows, gradients, and transitions should come from atoms, treatments, decorators, or CSS variables.");
|
|
3895
|
+
lines.push("- Let shells own spacing, centering, and scroll containers. Preserve app structure, but remove duplicated shell responsibilities when the contract makes them explicit.");
|
|
3896
|
+
lines.push("- If command_palette or hotkeys are declared in the generated context, implement them as real features.");
|
|
3897
|
+
lines.push("- If a required decorator class is referenced in the contract but missing from generated CSS, report the contract gap instead of inventing a parallel visual system.");
|
|
3898
|
+
lines.push("- Do not modify generated context files unless the task is explicitly to regenerate or refresh Decantr context.");
|
|
3899
|
+
lines.push("");
|
|
3900
|
+
lines.push("Execution flow:");
|
|
3901
|
+
lines.push("- Start by inventorying the current runtime and identifying the safest route/component anchors for attachment.");
|
|
3902
|
+
lines.push("- Align the shared shell and route structure incrementally instead of replacing the app shell wholesale.");
|
|
3903
|
+
lines.push("- Then attach or refine section pages using the matching section and page packs.");
|
|
3904
|
+
lines.push("- After implementation, run decantr check and decantr audit and fix contract or drift issues.");
|
|
3905
|
+
lines.push("- If a required context file or runtime anchor is missing, stop and report exactly what is missing before continuing.");
|
|
3906
|
+
return lines.join("\n");
|
|
3907
|
+
}
|
|
3908
|
+
function generateCuratedPrompt(ctx) {
|
|
3909
|
+
return ctx.workflow === "brownfield-attach" ? generateBrownfieldPrompt(ctx) : generateGreenfieldPrompt(ctx);
|
|
3910
|
+
}
|
|
3584
3911
|
function getAPIClient() {
|
|
3585
3912
|
return new RegistryAPIClient3({
|
|
3586
3913
|
baseUrl: process.env.DECANTR_API_URL || void 0,
|
|
@@ -3606,18 +3933,18 @@ function extractHostedAssetPaths(indexHtml) {
|
|
|
3606
3933
|
return [...assetPaths];
|
|
3607
3934
|
}
|
|
3608
3935
|
function readHostedDistSnapshot(distPath) {
|
|
3609
|
-
const resolvedDistPath = distPath ? resolveUserPath(distPath) :
|
|
3610
|
-
const indexPath =
|
|
3611
|
-
if (!
|
|
3936
|
+
const resolvedDistPath = distPath ? resolveUserPath(distPath) : join26(process.cwd(), "dist");
|
|
3937
|
+
const indexPath = join26(resolvedDistPath, "index.html");
|
|
3938
|
+
if (!existsSync25(indexPath)) {
|
|
3612
3939
|
return void 0;
|
|
3613
3940
|
}
|
|
3614
|
-
const indexHtml =
|
|
3941
|
+
const indexHtml = readFileSync18(indexPath, "utf-8");
|
|
3615
3942
|
const assetPaths = extractHostedAssetPaths(indexHtml);
|
|
3616
3943
|
const assets = {};
|
|
3617
3944
|
for (const assetPath of assetPaths) {
|
|
3618
|
-
const assetFilePath =
|
|
3619
|
-
if (
|
|
3620
|
-
assets[assetPath] =
|
|
3945
|
+
const assetFilePath = join26(resolvedDistPath, assetPath.replace(/^[/\\]+/, ""));
|
|
3946
|
+
if (existsSync25(assetFilePath)) {
|
|
3947
|
+
assets[assetPath] = readFileSync18(assetFilePath, "utf-8");
|
|
3621
3948
|
}
|
|
3622
3949
|
}
|
|
3623
3950
|
return {
|
|
@@ -3632,24 +3959,24 @@ function isHostedSourceSnapshotFile(path) {
|
|
|
3632
3959
|
function readHostedSourceSnapshot(sourcePath) {
|
|
3633
3960
|
if (!sourcePath) return void 0;
|
|
3634
3961
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
3635
|
-
if (!
|
|
3962
|
+
if (!existsSync25(resolvedSourcePath)) {
|
|
3636
3963
|
return void 0;
|
|
3637
3964
|
}
|
|
3638
3965
|
const files = {};
|
|
3639
3966
|
const ignoredDirNames = /* @__PURE__ */ new Set(["node_modules", ".git", ".decantr", "dist", "build", "coverage"]);
|
|
3640
|
-
const rootPrefix =
|
|
3967
|
+
const rootPrefix = basename2(resolvedSourcePath);
|
|
3641
3968
|
const walk = (absoluteDir, relativeDir) => {
|
|
3642
3969
|
for (const entry of readdirSync6(absoluteDir, { withFileTypes: true })) {
|
|
3643
3970
|
if (ignoredDirNames.has(entry.name)) continue;
|
|
3644
|
-
const absolutePath =
|
|
3645
|
-
const relativePath =
|
|
3971
|
+
const absolutePath = join26(absoluteDir, entry.name);
|
|
3972
|
+
const relativePath = join26(relativeDir, entry.name).replace(/\\/g, "/");
|
|
3646
3973
|
if (entry.isDirectory()) {
|
|
3647
3974
|
walk(absolutePath, relativePath);
|
|
3648
3975
|
continue;
|
|
3649
3976
|
}
|
|
3650
3977
|
if (!entry.isFile()) continue;
|
|
3651
3978
|
if (!isHostedSourceSnapshotFile(relativePath)) continue;
|
|
3652
|
-
files[relativePath] =
|
|
3979
|
+
files[relativePath] = readFileSync18(absolutePath, "utf-8");
|
|
3653
3980
|
}
|
|
3654
3981
|
};
|
|
3655
3982
|
walk(resolvedSourcePath, rootPrefix);
|
|
@@ -3766,19 +4093,19 @@ async function printRegistryIntelligenceSummary(namespace, jsonOutput = false) {
|
|
|
3766
4093
|
}
|
|
3767
4094
|
async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
3768
4095
|
const client = getPublicAPIClient();
|
|
3769
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
3770
|
-
if (!
|
|
4096
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
|
|
4097
|
+
if (!existsSync25(resolvedPath)) {
|
|
3771
4098
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
3772
4099
|
}
|
|
3773
|
-
const essence = JSON.parse(
|
|
4100
|
+
const essence = JSON.parse(readFileSync18(resolvedPath, "utf-8"));
|
|
3774
4101
|
const bundle = await client.compileExecutionPacks(
|
|
3775
4102
|
essence,
|
|
3776
4103
|
namespace ? { namespace } : void 0
|
|
3777
4104
|
);
|
|
3778
4105
|
let writtenContextPaths = [];
|
|
3779
4106
|
if (writeContext) {
|
|
3780
|
-
const contextDir =
|
|
3781
|
-
|
|
4107
|
+
const contextDir = join26(process.cwd(), ".decantr", "context");
|
|
4108
|
+
mkdirSync10(contextDir, { recursive: true });
|
|
3782
4109
|
const written = writeExecutionPackBundleArtifacts(
|
|
3783
4110
|
contextDir,
|
|
3784
4111
|
bundle
|
|
@@ -3801,7 +4128,7 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
|
|
|
3801
4128
|
console.log(` Sections: ${typedBundle.sections.length}`);
|
|
3802
4129
|
console.log(` Mutations: ${typedBundle.mutations.length}`);
|
|
3803
4130
|
if (writeContext) {
|
|
3804
|
-
console.log(` Context bundle: ${
|
|
4131
|
+
console.log(` Context bundle: ${join26(process.cwd(), ".decantr", "context")}`);
|
|
3805
4132
|
console.log(` Files written: ${writtenContextPaths.length}`);
|
|
3806
4133
|
}
|
|
3807
4134
|
console.log("");
|
|
@@ -3813,14 +4140,14 @@ async function printHostedExecutionPackBundle(essencePath, namespace, jsonOutput
|
|
|
3813
4140
|
}
|
|
3814
4141
|
async function printHostedSelectedExecutionPack(packType, id, essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
3815
4142
|
const client = getPublicAPIClient();
|
|
3816
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
3817
|
-
if (!
|
|
4143
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
|
|
4144
|
+
if (!existsSync25(resolvedPath)) {
|
|
3818
4145
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
3819
4146
|
}
|
|
3820
4147
|
if ((packType === "section" || packType === "page" || packType === "mutation") && !id) {
|
|
3821
4148
|
throw new Error(`Pack type "${packType}" requires an id.`);
|
|
3822
4149
|
}
|
|
3823
|
-
const essence = JSON.parse(
|
|
4150
|
+
const essence = JSON.parse(readFileSync18(resolvedPath, "utf-8"));
|
|
3824
4151
|
const selected = await client.selectExecutionPack(
|
|
3825
4152
|
{
|
|
3826
4153
|
essence,
|
|
@@ -3831,14 +4158,14 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
3831
4158
|
);
|
|
3832
4159
|
let writtenContextDir = null;
|
|
3833
4160
|
if (writeContext) {
|
|
3834
|
-
const contextDir =
|
|
3835
|
-
|
|
3836
|
-
writeFileSync13(
|
|
4161
|
+
const contextDir = join26(process.cwd(), ".decantr", "context");
|
|
4162
|
+
mkdirSync10(contextDir, { recursive: true });
|
|
4163
|
+
writeFileSync13(join26(contextDir, "pack-manifest.json"), JSON.stringify(selected.manifest, null, 2) + "\n");
|
|
3837
4164
|
const manifestEntry = selected.selector.packType === "scaffold" ? selected.manifest.scaffold : selected.selector.packType === "review" ? selected.manifest.review : selected.selector.packType === "section" ? selected.manifest.sections.find((entry) => entry.id === selected.selector.id) : selected.selector.packType === "page" ? selected.manifest.pages.find((entry) => entry.id === selected.selector.id) : selected.manifest.mutations.find((entry) => entry.id === selected.selector.id);
|
|
3838
4165
|
const markdownFile = manifestEntry?.markdown ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.md`;
|
|
3839
4166
|
const jsonFile = manifestEntry?.json ?? `${selected.selector.packType}${selected.selector.id ? `-${selected.selector.id}` : ""}-pack.json`;
|
|
3840
|
-
writeFileSync13(
|
|
3841
|
-
writeFileSync13(
|
|
4167
|
+
writeFileSync13(join26(contextDir, markdownFile), selected.pack.renderedMarkdown);
|
|
4168
|
+
writeFileSync13(join26(contextDir, jsonFile), JSON.stringify(selected.pack, null, 2) + "\n");
|
|
3842
4169
|
writtenContextDir = contextDir;
|
|
3843
4170
|
}
|
|
3844
4171
|
if (jsonOutput) {
|
|
@@ -3863,20 +4190,20 @@ async function printHostedSelectedExecutionPack(packType, id, essencePath, names
|
|
|
3863
4190
|
}
|
|
3864
4191
|
async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutput = false, writeContext = false) {
|
|
3865
4192
|
const client = getPublicAPIClient();
|
|
3866
|
-
const resolvedPath = essencePath ? resolveUserPath(essencePath) :
|
|
3867
|
-
if (!
|
|
4193
|
+
const resolvedPath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
|
|
4194
|
+
if (!existsSync25(resolvedPath)) {
|
|
3868
4195
|
throw new Error(`Essence file not found at ${resolvedPath}`);
|
|
3869
4196
|
}
|
|
3870
|
-
const essence = JSON.parse(
|
|
4197
|
+
const essence = JSON.parse(readFileSync18(resolvedPath, "utf-8"));
|
|
3871
4198
|
const manifest = await client.getExecutionPackManifest(
|
|
3872
4199
|
essence,
|
|
3873
4200
|
namespace ? { namespace } : void 0
|
|
3874
4201
|
);
|
|
3875
4202
|
let writtenContextDir = null;
|
|
3876
4203
|
if (writeContext) {
|
|
3877
|
-
const contextDir =
|
|
3878
|
-
|
|
3879
|
-
writeFileSync13(
|
|
4204
|
+
const contextDir = join26(process.cwd(), ".decantr", "context");
|
|
4205
|
+
mkdirSync10(contextDir, { recursive: true });
|
|
4206
|
+
writeFileSync13(join26(contextDir, "pack-manifest.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
3880
4207
|
writtenContextDir = contextDir;
|
|
3881
4208
|
}
|
|
3882
4209
|
if (jsonOutput) {
|
|
@@ -3897,14 +4224,14 @@ async function printHostedExecutionPackManifest(essencePath, namespace, jsonOutp
|
|
|
3897
4224
|
}
|
|
3898
4225
|
}
|
|
3899
4226
|
async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@official") {
|
|
3900
|
-
const contextDir =
|
|
3901
|
-
const reviewPackPath =
|
|
3902
|
-
const manifestPath =
|
|
3903
|
-
if (
|
|
4227
|
+
const contextDir = join26(projectRoot, ".decantr", "context");
|
|
4228
|
+
const reviewPackPath = join26(contextDir, "review-pack.json");
|
|
4229
|
+
const manifestPath = join26(contextDir, "pack-manifest.json");
|
|
4230
|
+
if (existsSync25(reviewPackPath) && existsSync25(manifestPath)) {
|
|
3904
4231
|
return { attempted: false, hydrated: false };
|
|
3905
4232
|
}
|
|
3906
|
-
const essencePath =
|
|
3907
|
-
if (!
|
|
4233
|
+
const essencePath = join26(projectRoot, "decantr.essence.json");
|
|
4234
|
+
if (!existsSync25(essencePath)) {
|
|
3908
4235
|
return { attempted: false, hydrated: false };
|
|
3909
4236
|
}
|
|
3910
4237
|
const reviewHydration = await hydrateHostedReviewPackIfMissing(projectRoot, namespace);
|
|
@@ -3913,9 +4240,9 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
3913
4240
|
}
|
|
3914
4241
|
try {
|
|
3915
4242
|
const client = getPublicAPIClient();
|
|
3916
|
-
const essence = JSON.parse(
|
|
4243
|
+
const essence = JSON.parse(readFileSync18(essencePath, "utf-8"));
|
|
3917
4244
|
const bundle = await client.compileExecutionPacks(essence, { namespace });
|
|
3918
|
-
|
|
4245
|
+
mkdirSync10(contextDir, { recursive: true });
|
|
3919
4246
|
writeExecutionPackBundleArtifacts(
|
|
3920
4247
|
contextDir,
|
|
3921
4248
|
bundle
|
|
@@ -3926,19 +4253,19 @@ async function hydrateHostedExecutionPacksIfMissing(projectRoot, namespace = "@o
|
|
|
3926
4253
|
}
|
|
3927
4254
|
}
|
|
3928
4255
|
async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@official") {
|
|
3929
|
-
const contextDir =
|
|
3930
|
-
const reviewPackPath =
|
|
3931
|
-
const manifestPath =
|
|
3932
|
-
if (
|
|
4256
|
+
const contextDir = join26(projectRoot, ".decantr", "context");
|
|
4257
|
+
const reviewPackPath = join26(contextDir, "review-pack.json");
|
|
4258
|
+
const manifestPath = join26(contextDir, "pack-manifest.json");
|
|
4259
|
+
if (existsSync25(reviewPackPath) && existsSync25(manifestPath)) {
|
|
3933
4260
|
return { attempted: false, hydrated: false };
|
|
3934
4261
|
}
|
|
3935
|
-
const essencePath =
|
|
3936
|
-
if (!
|
|
4262
|
+
const essencePath = join26(projectRoot, "decantr.essence.json");
|
|
4263
|
+
if (!existsSync25(essencePath)) {
|
|
3937
4264
|
return { attempted: false, hydrated: false };
|
|
3938
4265
|
}
|
|
3939
4266
|
try {
|
|
3940
4267
|
const client = getPublicAPIClient();
|
|
3941
|
-
const essence = JSON.parse(
|
|
4268
|
+
const essence = JSON.parse(readFileSync18(essencePath, "utf-8"));
|
|
3942
4269
|
const selected = await client.selectExecutionPack(
|
|
3943
4270
|
{
|
|
3944
4271
|
essence,
|
|
@@ -3946,10 +4273,10 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
3946
4273
|
},
|
|
3947
4274
|
{ namespace }
|
|
3948
4275
|
);
|
|
3949
|
-
|
|
3950
|
-
writeFileSync13(
|
|
3951
|
-
writeFileSync13(
|
|
3952
|
-
if (!
|
|
4276
|
+
mkdirSync10(contextDir, { recursive: true });
|
|
4277
|
+
writeFileSync13(join26(contextDir, "review-pack.md"), selected.pack.renderedMarkdown);
|
|
4278
|
+
writeFileSync13(join26(contextDir, "review-pack.json"), JSON.stringify(selected.pack, null, 2) + "\n");
|
|
4279
|
+
if (!existsSync25(manifestPath)) {
|
|
3953
4280
|
writeFileSync13(manifestPath, JSON.stringify(selected.manifest, null, 2) + "\n");
|
|
3954
4281
|
}
|
|
3955
4282
|
return { attempted: true, hydrated: true, scope: "review" };
|
|
@@ -3960,17 +4287,17 @@ async function hydrateHostedReviewPackIfMissing(projectRoot, namespace = "@offic
|
|
|
3960
4287
|
async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false, essencePath, treatmentsPath) {
|
|
3961
4288
|
const client = getPublicAPIClient();
|
|
3962
4289
|
const resolvedSourcePath = resolveUserPath(sourcePath);
|
|
3963
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
3964
|
-
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) :
|
|
3965
|
-
if (!
|
|
4290
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
|
|
4291
|
+
const resolvedTreatmentsPath = treatmentsPath ? resolveUserPath(treatmentsPath) : join26(process.cwd(), "src", "styles", "treatments.css");
|
|
4292
|
+
if (!existsSync25(resolvedSourcePath)) {
|
|
3966
4293
|
throw new Error(`Source file not found at ${resolvedSourcePath}`);
|
|
3967
4294
|
}
|
|
3968
|
-
if (!
|
|
4295
|
+
if (!existsSync25(resolvedEssencePath)) {
|
|
3969
4296
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
3970
4297
|
}
|
|
3971
|
-
const code =
|
|
3972
|
-
const essence = JSON.parse(
|
|
3973
|
-
const treatmentsCss =
|
|
4298
|
+
const code = readFileSync18(resolvedSourcePath, "utf-8");
|
|
4299
|
+
const essence = JSON.parse(readFileSync18(resolvedEssencePath, "utf-8"));
|
|
4300
|
+
const treatmentsCss = existsSync25(resolvedTreatmentsPath) ? readFileSync18(resolvedTreatmentsPath, "utf-8") : void 0;
|
|
3974
4301
|
const report = await client.critiqueFile(
|
|
3975
4302
|
{
|
|
3976
4303
|
essence,
|
|
@@ -3994,11 +4321,11 @@ async function printHostedFileCritique(sourcePath, namespace, jsonOutput = false
|
|
|
3994
4321
|
}
|
|
3995
4322
|
async function printHostedProjectAudit(namespace, jsonOutput = false, essencePath, distPath, sourcesPath) {
|
|
3996
4323
|
const client = getPublicAPIClient();
|
|
3997
|
-
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) :
|
|
3998
|
-
if (!
|
|
4324
|
+
const resolvedEssencePath = essencePath ? resolveUserPath(essencePath) : join26(process.cwd(), "decantr.essence.json");
|
|
4325
|
+
if (!existsSync25(resolvedEssencePath)) {
|
|
3999
4326
|
throw new Error(`Essence file not found at ${resolvedEssencePath}`);
|
|
4000
4327
|
}
|
|
4001
|
-
const essence = JSON.parse(
|
|
4328
|
+
const essence = JSON.parse(readFileSync18(resolvedEssencePath, "utf-8"));
|
|
4002
4329
|
const dist = readHostedDistSnapshot(distPath);
|
|
4003
4330
|
const sources = readHostedSourceSnapshot(sourcesPath);
|
|
4004
4331
|
const report = await client.auditProject(
|
|
@@ -4015,7 +4342,7 @@ async function printHostedProjectAudit(namespace, jsonOutput = false, essencePat
|
|
|
4015
4342
|
}
|
|
4016
4343
|
console.log(heading2("Hosted Project Audit"));
|
|
4017
4344
|
console.log(` Essence: ${resolvedEssencePath}`);
|
|
4018
|
-
console.log(` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) :
|
|
4345
|
+
console.log(` Dist snapshot: ${dist ? distPath ? resolveUserPath(distPath) : join26(process.cwd(), "dist") : "none"}`);
|
|
4019
4346
|
console.log(` Source snapshot: ${sources && sourcesPath ? resolveUserPath(sourcesPath) : "none"}`);
|
|
4020
4347
|
printProjectAuditReport(report);
|
|
4021
4348
|
}
|
|
@@ -4093,7 +4420,7 @@ async function cmdGet(type, id) {
|
|
|
4093
4420
|
}
|
|
4094
4421
|
const apiType = CONTENT_TYPE_TO_API_CONTENT_TYPE3[type];
|
|
4095
4422
|
const registryClient = new RegistryClient({
|
|
4096
|
-
cacheDir:
|
|
4423
|
+
cacheDir: join26(process.cwd(), ".decantr", "cache")
|
|
4097
4424
|
});
|
|
4098
4425
|
const result = await registryClient.fetchContentItem(apiType, id);
|
|
4099
4426
|
if (result) {
|
|
@@ -4102,16 +4429,16 @@ async function cmdGet(type, id) {
|
|
|
4102
4429
|
}
|
|
4103
4430
|
const currentDir = dirname2(fileURLToPath2(import.meta.url));
|
|
4104
4431
|
const bundledCandidates = [
|
|
4105
|
-
|
|
4432
|
+
join26(currentDir, "bundled", apiType, `${id}.json`),
|
|
4106
4433
|
// Running from src/
|
|
4107
|
-
|
|
4434
|
+
join26(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
|
|
4108
4435
|
// Running from dist/
|
|
4109
|
-
|
|
4436
|
+
join26(currentDir, "..", "bundled", apiType, `${id}.json`)
|
|
4110
4437
|
// Alternative dist layout
|
|
4111
4438
|
];
|
|
4112
|
-
const bundledPath = bundledCandidates.find((p) =>
|
|
4439
|
+
const bundledPath = bundledCandidates.find((p) => existsSync25(p)) || null;
|
|
4113
4440
|
if (bundledPath) {
|
|
4114
|
-
const data = JSON.parse(
|
|
4441
|
+
const data = JSON.parse(readFileSync18(bundledPath, "utf-8"));
|
|
4115
4442
|
console.log(JSON.stringify(data, null, 2));
|
|
4116
4443
|
return;
|
|
4117
4444
|
}
|
|
@@ -4120,10 +4447,10 @@ async function cmdGet(type, id) {
|
|
|
4120
4447
|
return;
|
|
4121
4448
|
}
|
|
4122
4449
|
async function cmdValidate(path) {
|
|
4123
|
-
const essencePath = path ||
|
|
4450
|
+
const essencePath = path || join26(process.cwd(), "decantr.essence.json");
|
|
4124
4451
|
let raw;
|
|
4125
4452
|
try {
|
|
4126
|
-
raw =
|
|
4453
|
+
raw = readFileSync18(essencePath, "utf-8");
|
|
4127
4454
|
} catch {
|
|
4128
4455
|
console.error(error3(`Could not read ${essencePath}`));
|
|
4129
4456
|
process.exitCode = 1;
|
|
@@ -4177,7 +4504,7 @@ async function cmdList(type, sort, recommended, intelligenceSource) {
|
|
|
4177
4504
|
return;
|
|
4178
4505
|
}
|
|
4179
4506
|
const registryClient = new RegistryClient({
|
|
4180
|
-
cacheDir:
|
|
4507
|
+
cacheDir: join26(process.cwd(), ".decantr", "cache")
|
|
4181
4508
|
});
|
|
4182
4509
|
const result = await registryClient.fetchContentList(
|
|
4183
4510
|
type,
|
|
@@ -4227,6 +4554,11 @@ async function cmdInit(args) {
|
|
|
4227
4554
|
const projectRoot = process.cwd();
|
|
4228
4555
|
console.log(heading2("Decantr Project Setup"));
|
|
4229
4556
|
const detected = detectProject(projectRoot);
|
|
4557
|
+
const workflowSeed = readBrownfieldInitSeed(projectRoot);
|
|
4558
|
+
const brownfieldAttach = Boolean(workflowSeed) || hasExistingProjectFootprint(detected);
|
|
4559
|
+
if (workflowSeed) {
|
|
4560
|
+
console.log(dim3(" Found .decantr/init-seed.json brownfield guidance."));
|
|
4561
|
+
}
|
|
4230
4562
|
if (detected.existingEssence && !args.existing) {
|
|
4231
4563
|
console.log(`${YELLOW9}Warning: decantr.essence.json already exists.${RESET13}`);
|
|
4232
4564
|
const overwrite = await confirm("Overwrite existing configuration?", false);
|
|
@@ -4251,7 +4583,7 @@ async function cmdInit(args) {
|
|
|
4251
4583
|
}
|
|
4252
4584
|
}
|
|
4253
4585
|
const registryClient = new RegistryClient({
|
|
4254
|
-
cacheDir:
|
|
4586
|
+
cacheDir: join26(projectRoot, ".decantr", "cache"),
|
|
4255
4587
|
apiUrl: args.registry,
|
|
4256
4588
|
offline: args.offline
|
|
4257
4589
|
});
|
|
@@ -4265,6 +4597,8 @@ async function cmdInit(args) {
|
|
|
4265
4597
|
}
|
|
4266
4598
|
let selectedBlueprint = "default";
|
|
4267
4599
|
let registrySource = "cache";
|
|
4600
|
+
const preferContractOnly = brownfieldAttach && !requestedBlueprint && !requestedArchetype;
|
|
4601
|
+
const workflowMode = workflowSeed || brownfieldAttach && !requestedBlueprint && !requestedArchetype ? "brownfield-attach" : "greenfield-scaffold";
|
|
4268
4602
|
if (args.yes) {
|
|
4269
4603
|
selectedBlueprint = args.blueprint || "default";
|
|
4270
4604
|
} else if (!apiAvailable) {
|
|
@@ -4299,7 +4633,7 @@ ${YELLOW9}You're offline. Scaffolding minimal Decantr project.${RESET13}`);
|
|
|
4299
4633
|
${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
4300
4634
|
console.log(dim3("Run `decantr upgrade` when online, or visit decantr.ai/registry\n"));
|
|
4301
4635
|
selectedBlueprint = "default";
|
|
4302
|
-
} else {
|
|
4636
|
+
} else if (!preferContractOnly) {
|
|
4303
4637
|
console.log(dim3("Fetching registry content..."));
|
|
4304
4638
|
const blueprintsResult2 = await registryClient.fetchBlueprints();
|
|
4305
4639
|
registrySource = blueprintsResult2.source.type === "api" ? "api" : "cache";
|
|
@@ -4327,14 +4661,15 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
4327
4661
|
if (args.yes || selectedBlueprint !== "default") {
|
|
4328
4662
|
const flags = parseFlags(args, detected);
|
|
4329
4663
|
flags.blueprint = selectedBlueprint !== "default" ? selectedBlueprint : flags.blueprint;
|
|
4330
|
-
options = mergeWithDefaults(flags, detected);
|
|
4664
|
+
options = mergeWithDefaults(flags, detected, workflowSeed ?? void 0);
|
|
4331
4665
|
} else {
|
|
4332
|
-
options = await runInteractivePrompts(detected, archetypes, blueprints, themes);
|
|
4666
|
+
options = await runInteractivePrompts(detected, archetypes, blueprints, themes, workflowSeed ?? void 0);
|
|
4333
4667
|
userExplicit.theme = true;
|
|
4334
4668
|
userExplicit.mode = true;
|
|
4335
4669
|
userExplicit.shape = true;
|
|
4336
4670
|
userExplicit.personality = true;
|
|
4337
4671
|
}
|
|
4672
|
+
options.workflowMode = workflowMode;
|
|
4338
4673
|
let topologyMarkdown = "";
|
|
4339
4674
|
let archetypeData;
|
|
4340
4675
|
let composedSections;
|
|
@@ -4512,6 +4847,11 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
4512
4847
|
if (result.gitignoreUpdated) {
|
|
4513
4848
|
console.log(` ${dim3(".gitignore updated")}`);
|
|
4514
4849
|
}
|
|
4850
|
+
if (!existsSync25(join26(projectRoot, "package.json"))) {
|
|
4851
|
+
console.log("");
|
|
4852
|
+
console.log(dim3(` Note: ${cyan3("decantr init")} created Decantr contract/context files only.`));
|
|
4853
|
+
console.log(dim3(` For a runnable starter in a new directory, prefer ${cyan3("decantr new <name> --blueprint=...")}.`));
|
|
4854
|
+
}
|
|
4515
4855
|
console.log("");
|
|
4516
4856
|
console.log(" Next steps:");
|
|
4517
4857
|
console.log(" 1. Read DECANTR.md for methodology, CSS approach, and guard rules");
|
|
@@ -4529,7 +4869,7 @@ ${YELLOW9}You're offline. Scaffolding Decantr default.${RESET13}`);
|
|
|
4529
4869
|
console.log(` ${cyan3("decantr upgrade")} Update to latest patterns`);
|
|
4530
4870
|
console.log(` ${cyan3("decantr check")} Detect drift issues`);
|
|
4531
4871
|
console.log(` ${cyan3("decantr migrate")} Migrate v2 essence to v3`);
|
|
4532
|
-
const essenceContent =
|
|
4872
|
+
const essenceContent = readFileSync18(result.essencePath, "utf-8");
|
|
4533
4873
|
const essence = JSON.parse(essenceContent);
|
|
4534
4874
|
if (essence.version !== "3.1.0") {
|
|
4535
4875
|
const validation = validateEssence2(essence);
|
|
@@ -4551,6 +4891,7 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
4551
4891
|
promptPages = essence.structure || [{ id: "home", shell: options.shell, layout: ["hero"] }];
|
|
4552
4892
|
}
|
|
4553
4893
|
const promptCtx = {
|
|
4894
|
+
workflow: options.workflowMode === "brownfield-attach" ? "brownfield-attach" : "greenfield-scaffold",
|
|
4554
4895
|
archetype: options.archetype || "custom",
|
|
4555
4896
|
blueprint: options.blueprint,
|
|
4556
4897
|
theme: options.theme,
|
|
@@ -4576,16 +4917,16 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
4576
4917
|
}
|
|
4577
4918
|
async function cmdStatus() {
|
|
4578
4919
|
const projectRoot = process.cwd();
|
|
4579
|
-
const essencePath =
|
|
4580
|
-
const projectJsonPath =
|
|
4920
|
+
const essencePath = join26(projectRoot, "decantr.essence.json");
|
|
4921
|
+
const projectJsonPath = join26(projectRoot, ".decantr", "project.json");
|
|
4581
4922
|
console.log(heading2("Decantr Project Status"));
|
|
4582
|
-
if (!
|
|
4923
|
+
if (!existsSync25(essencePath)) {
|
|
4583
4924
|
console.log(`${RED11}No decantr.essence.json found.${RESET13}`);
|
|
4584
4925
|
console.log(dim3('Run "decantr init" to create one.'));
|
|
4585
4926
|
return;
|
|
4586
4927
|
}
|
|
4587
4928
|
try {
|
|
4588
|
-
const essence = JSON.parse(
|
|
4929
|
+
const essence = JSON.parse(readFileSync18(essencePath, "utf-8"));
|
|
4589
4930
|
const validation = validateEssence2(essence);
|
|
4590
4931
|
const essenceVersion = isV36(essence) ? "v3" : "v2";
|
|
4591
4932
|
console.log(`${BOLD6}Essence:${RESET13}`);
|
|
@@ -4634,9 +4975,9 @@ async function cmdStatus() {
|
|
|
4634
4975
|
}
|
|
4635
4976
|
console.log("");
|
|
4636
4977
|
console.log(`${BOLD6}Sync Status:${RESET13}`);
|
|
4637
|
-
if (
|
|
4978
|
+
if (existsSync25(projectJsonPath)) {
|
|
4638
4979
|
try {
|
|
4639
|
-
const projectJson = JSON.parse(
|
|
4980
|
+
const projectJson = JSON.parse(readFileSync18(projectJsonPath, "utf-8"));
|
|
4640
4981
|
const syncStatus = projectJson.sync?.status || "unknown";
|
|
4641
4982
|
const lastSync = projectJson.sync?.lastSync || "never";
|
|
4642
4983
|
const source = projectJson.sync?.registrySource || "unknown";
|
|
@@ -4654,7 +4995,7 @@ async function cmdStatus() {
|
|
|
4654
4995
|
}
|
|
4655
4996
|
async function cmdSync() {
|
|
4656
4997
|
const projectRoot = process.cwd();
|
|
4657
|
-
const cacheDir =
|
|
4998
|
+
const cacheDir = join26(projectRoot, ".decantr", "cache");
|
|
4658
4999
|
console.log(heading2("Syncing registry content..."));
|
|
4659
5000
|
const result = await syncRegistry(cacheDir);
|
|
4660
5001
|
if (result.synced.length > 0) {
|
|
@@ -4843,14 +5184,14 @@ ${BOLD6}Examples:${RESET13}
|
|
|
4843
5184
|
process.exitCode = 1;
|
|
4844
5185
|
return;
|
|
4845
5186
|
}
|
|
4846
|
-
const themePath =
|
|
4847
|
-
if (!
|
|
5187
|
+
const themePath = join26(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
|
|
5188
|
+
if (!existsSync25(themePath)) {
|
|
4848
5189
|
console.error(error3(`Theme "${name}" not found at ${themePath}`));
|
|
4849
5190
|
process.exitCode = 1;
|
|
4850
5191
|
return;
|
|
4851
5192
|
}
|
|
4852
5193
|
try {
|
|
4853
|
-
const theme = JSON.parse(
|
|
5194
|
+
const theme = JSON.parse(readFileSync18(themePath, "utf-8"));
|
|
4854
5195
|
const result = validateCustomTheme(theme);
|
|
4855
5196
|
if (result.valid) {
|
|
4856
5197
|
console.log(success3(`Custom theme "${name}" is valid`));
|
|
@@ -4966,9 +5307,9 @@ ${BOLD6}Init Options:${RESET13}
|
|
|
4966
5307
|
--registry Custom registry URL
|
|
4967
5308
|
|
|
4968
5309
|
${BOLD6}Commands:${RESET13}
|
|
4969
|
-
${cyan3("new")} Create a new
|
|
4970
|
-
${cyan3("magic")}
|
|
4971
|
-
${cyan3("init")}
|
|
5310
|
+
${cyan3("new")} Create a new greenfield workspace and bootstrap the available starter adapter
|
|
5311
|
+
${cyan3("magic")} Greenfield-first intent flow; steers existing apps into analyze + init
|
|
5312
|
+
${cyan3("init")} Attach Decantr contract/context files to an existing project or empty workspace
|
|
4972
5313
|
${cyan3("status")} Show project status, DNA axioms, and blueprint info
|
|
4973
5314
|
${cyan3("sync")} Sync registry content from API
|
|
4974
5315
|
${cyan3("audit")} Audit the project or critique a specific file against compiled packs
|
|
@@ -4986,7 +5327,7 @@ ${BOLD6}Commands:${RESET13}
|
|
|
4986
5327
|
${cyan3("publish")} Publish a custom content item to the community registry
|
|
4987
5328
|
${cyan3("login")} Authenticate with the Decantr registry
|
|
4988
5329
|
${cyan3("logout")} Remove stored credentials
|
|
4989
|
-
${cyan3("analyze")}
|
|
5330
|
+
${cyan3("analyze")} Brownfield entrypoint: scan an existing project and emit attach guidance
|
|
4990
5331
|
${cyan3("export")} Export design tokens to framework format (shadcn, tailwind, css-vars)
|
|
4991
5332
|
${cyan3("registry")} Registry management and intelligence summary
|
|
4992
5333
|
${cyan3("upgrade")} Check for content updates from registry
|
|
@@ -4996,7 +5337,7 @@ ${BOLD6}Examples:${RESET13}
|
|
|
4996
5337
|
decantr new my-app --blueprint=carbon-ai-portal
|
|
4997
5338
|
decantr magic "AI chatbot with dark cyber theme \u2014 bold and futuristic"
|
|
4998
5339
|
decantr init
|
|
4999
|
-
decantr init --blueprint=saas-dashboard --theme=luminarum --yes
|
|
5340
|
+
decantr init --existing --blueprint=saas-dashboard --theme=luminarum --yes
|
|
5000
5341
|
decantr status
|
|
5001
5342
|
decantr audit
|
|
5002
5343
|
decantr audit src/pages/HomePage.tsx
|
|
@@ -5017,6 +5358,15 @@ ${BOLD6}Examples:${RESET13}
|
|
|
5017
5358
|
decantr registry audit-project --namespace @official --json
|
|
5018
5359
|
decantr registry audit-project --namespace @official --dist dist --sources src
|
|
5019
5360
|
decantr create pattern my-card
|
|
5361
|
+
|
|
5362
|
+
${BOLD6}Workflow Model:${RESET13}
|
|
5363
|
+
${cyan3("Greenfield blueprint")} decantr new / decantr magic
|
|
5364
|
+
${cyan3("Brownfield adoption")} decantr analyze -> decantr init --existing
|
|
5365
|
+
${cyan3("Hybrid composition")} decantr add/remove, decantr theme switch, decantr registry, decantr upgrade
|
|
5366
|
+
|
|
5367
|
+
${BOLD6}Bootstrap adapters:${RESET13}
|
|
5368
|
+
Current runnable starter adapter: ${cyan3("react-vite")}
|
|
5369
|
+
Other contract targets stay framework-agnostic, but currently initialize in contract-only mode until their starter adapters land.
|
|
5020
5370
|
`);
|
|
5021
5371
|
}
|
|
5022
5372
|
async function main() {
|
|
@@ -5095,7 +5445,7 @@ async function main() {
|
|
|
5095
5445
|
break;
|
|
5096
5446
|
}
|
|
5097
5447
|
case "upgrade": {
|
|
5098
|
-
const { cmdUpgrade } = await import("./upgrade-
|
|
5448
|
+
const { cmdUpgrade } = await import("./upgrade-UXY2WUJH.js");
|
|
5099
5449
|
const applyFlag = args.includes("--apply");
|
|
5100
5450
|
await cmdUpgrade(process.cwd(), { apply: applyFlag });
|
|
5101
5451
|
break;
|