@decantr/cli 3.0.2 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +4 -4
- package/dist/{chunk-B2PJDAMS.js → chunk-EL4KSW7E.js} +1 -1
- package/dist/{chunk-7Z74ETDR.js → chunk-XINHWP4T.js} +141 -15
- package/dist/{chunk-4NDOHCYY.js → chunk-XUHPMETF.js} +547 -55
- package/dist/{chunk-GN63PIKK.js → chunk-ZBGL7RID.js} +44 -13
- package/dist/{heal-HHVVTVIH.js → heal-ZY4BX5HU.js} +1 -1
- package/dist/{health-DDT4RABT.js → health-H2256NIM.js} +2 -2
- package/dist/index.js +4 -4
- package/dist/{studio-WDJ3BOCU.js → studio-UKSCSRX7.js} +3 -3
- package/dist/{workspace-3XNCMZ5L.js → workspace-SR6ZGG2E.js} +3 -3
- package/package.json +6 -6
package/dist/bin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import "./chunk-
|
|
2
|
+
import "./chunk-ZBGL7RID.js";
|
|
3
3
|
import "./chunk-SIDKK73N.js";
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
import "./chunk-EL4KSW7E.js";
|
|
5
|
+
import "./chunk-XUHPMETF.js";
|
|
6
|
+
import "./chunk-XINHWP4T.js";
|
|
@@ -456,6 +456,8 @@ function walkSvelteKitRoutes(dir, baseDir, segments) {
|
|
|
456
456
|
return routes;
|
|
457
457
|
}
|
|
458
458
|
var ROUTER_FILE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
|
|
459
|
+
var ROUTE_VARIABLE_NAMES = "(?:path|pathname|route|currentPath|currentRoute|locationPath|activePath)";
|
|
460
|
+
var ROUTE_ASSET_EXTENSION_RE = /\.(?:avif|bmp|css|gif|ico|jpeg|jpg|js|json|map|mp4|pdf|png|svg|webp|woff2?)$/i;
|
|
459
461
|
function collectRouteCandidateFiles(dir, files, depth = 0) {
|
|
460
462
|
if (depth > 5) return;
|
|
461
463
|
let entries;
|
|
@@ -496,30 +498,72 @@ function scanReactRouter(projectRoot) {
|
|
|
496
498
|
continue;
|
|
497
499
|
}
|
|
498
500
|
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");
|
|
499
|
-
if (!isReactRouterFile) continue;
|
|
500
501
|
const relativePath = relative2(projectRoot, absolutePath);
|
|
501
502
|
const pathMatches = /* @__PURE__ */ new Set();
|
|
502
|
-
|
|
503
|
-
|
|
503
|
+
if (isReactRouterFile) {
|
|
504
|
+
for (const match of content.matchAll(/<Route\b[^>]*\bpath=["'`]([^"'`]+)["'`]/g)) {
|
|
505
|
+
pathMatches.add(match[1]);
|
|
506
|
+
}
|
|
507
|
+
for (const match of content.matchAll(/\bpath\s*:\s*["'`]([^"'`]+)["'`]/g)) {
|
|
508
|
+
pathMatches.add(match[1]);
|
|
509
|
+
}
|
|
504
510
|
}
|
|
505
|
-
for (const
|
|
506
|
-
pathMatches.add(
|
|
511
|
+
for (const route of detectPathnameBranchRoutes(content)) {
|
|
512
|
+
pathMatches.add(route);
|
|
507
513
|
}
|
|
514
|
+
if (!isReactRouterFile && pathMatches.size === 0) continue;
|
|
508
515
|
if (pathMatches.size === 0 && (content.includes("<Routes") || content.includes("RouterProvider"))) {
|
|
509
516
|
pathMatches.add("/");
|
|
510
517
|
}
|
|
511
518
|
for (const path of pathMatches) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
}
|
|
519
|
+
const routePath = normalizeDetectedRouteLiteral(path);
|
|
520
|
+
if (!routePath || routeMap.has(routePath)) continue;
|
|
521
|
+
routeMap.set(routePath, {
|
|
522
|
+
path: routePath,
|
|
523
|
+
file: relativePath,
|
|
524
|
+
hasLayout: false
|
|
525
|
+
});
|
|
519
526
|
}
|
|
520
527
|
}
|
|
521
528
|
return [...routeMap.values()];
|
|
522
529
|
}
|
|
530
|
+
function normalizeDetectedRouteLiteral(value) {
|
|
531
|
+
const cleaned = value.trim().split(/[?#]/)[0];
|
|
532
|
+
if (!cleaned || cleaned === "/") return "/";
|
|
533
|
+
if (cleaned === "*" || cleaned === "**" || cleaned.startsWith("#")) return null;
|
|
534
|
+
if (!cleaned.startsWith("/") || cleaned.startsWith("//")) return null;
|
|
535
|
+
if (ROUTE_ASSET_EXTENSION_RE.test(cleaned)) return null;
|
|
536
|
+
return cleaned.replace(/\/+$/g, "") || "/";
|
|
537
|
+
}
|
|
538
|
+
function collectRouteLiterals(pattern, content, routes) {
|
|
539
|
+
let count = 0;
|
|
540
|
+
for (const match of content.matchAll(pattern)) {
|
|
541
|
+
const route = normalizeDetectedRouteLiteral(match[1] ?? "");
|
|
542
|
+
if (!route) continue;
|
|
543
|
+
routes.add(route);
|
|
544
|
+
count += 1;
|
|
545
|
+
}
|
|
546
|
+
return count;
|
|
547
|
+
}
|
|
548
|
+
function detectPathnameBranchRoutes(content) {
|
|
549
|
+
const routes = /* @__PURE__ */ new Set();
|
|
550
|
+
const comparison = new RegExp(
|
|
551
|
+
`\\b${ROUTE_VARIABLE_NAMES}\\b\\s*(?:===|!==|==|!=)\\s*["'\`](\\/[^"'\`]+)["'\`]`,
|
|
552
|
+
"g"
|
|
553
|
+
);
|
|
554
|
+
const reversedComparison = new RegExp(
|
|
555
|
+
`["'\`](\\/[^"'\`]+)["'\`]\\s*(?:===|!==|==|!=)\\s*\\b${ROUTE_VARIABLE_NAMES}\\b`,
|
|
556
|
+
"g"
|
|
557
|
+
);
|
|
558
|
+
const strongMatches = collectRouteLiterals(comparison, content, routes) + collectRouteLiterals(reversedComparison, content, routes) + collectRouteLiterals(/\bcase\s+["'`](\/[^"'`]+)["'`]\s*:/g, content, routes);
|
|
559
|
+
const hasPathnameSignal = /\b(?:window\.|document\.)?location\.pathname\b|\bpathname\b/.test(
|
|
560
|
+
content
|
|
561
|
+
);
|
|
562
|
+
if (strongMatches > 0 || hasPathnameSignal) {
|
|
563
|
+
collectRouteLiterals(/\b(?:href|to)\s*=\s*["'`](\/[^"'`]+)["'`]/g, content, routes);
|
|
564
|
+
}
|
|
565
|
+
return [...routes];
|
|
566
|
+
}
|
|
523
567
|
function hasReactRouterDependency(projectRoot) {
|
|
524
568
|
return hasDependency(projectRoot, ["react-router", "react-router-dom"]);
|
|
525
569
|
}
|
|
@@ -693,8 +737,8 @@ function extractCSSVariables(content) {
|
|
|
693
737
|
const colors = {};
|
|
694
738
|
const variables = [];
|
|
695
739
|
const varRegex = /--([\w-]+)\s*:\s*([^;]+)/g;
|
|
696
|
-
let match;
|
|
697
|
-
while (
|
|
740
|
+
let match = varRegex.exec(content);
|
|
741
|
+
while (match !== null) {
|
|
698
742
|
const name = match[1];
|
|
699
743
|
const value = match[2].trim();
|
|
700
744
|
variables.push(`--${name}`);
|
|
@@ -714,6 +758,24 @@ function extractCSSVariables(content) {
|
|
|
714
758
|
if (value.startsWith("#") || value.startsWith("rgb") || value.startsWith("hsl") || colorPatterns.some((p) => name.includes(p))) {
|
|
715
759
|
colors[name] = value;
|
|
716
760
|
}
|
|
761
|
+
match = varRegex.exec(content);
|
|
762
|
+
}
|
|
763
|
+
return { colors, variables };
|
|
764
|
+
}
|
|
765
|
+
function extractTailwindThemeTokens(content) {
|
|
766
|
+
const colors = {};
|
|
767
|
+
const variables = [];
|
|
768
|
+
for (const match of content.matchAll(
|
|
769
|
+
/\b([a-zA-Z][\w-]*)\s*:\s*['"`](#[0-9a-fA-F]{3,8}|rgba?\([^)]+\)|hsla?\([^)]+\)|oklch\([^)]+\)|var\(--[^)]+\))['"`]/g
|
|
770
|
+
)) {
|
|
771
|
+
const name = match[1];
|
|
772
|
+
const value = match[2];
|
|
773
|
+
if (/color|bg|background|surface|text|foreground|border|primary|secondary|accent|muted|danger|error|success|warning/i.test(
|
|
774
|
+
name
|
|
775
|
+
) || /^#|^(?:rgba?|hsla?|oklch)\(/i.test(value) || /^var\(--/i.test(value)) {
|
|
776
|
+
colors[name] = value;
|
|
777
|
+
variables.push(`theme.colors.${name}`);
|
|
778
|
+
}
|
|
717
779
|
}
|
|
718
780
|
return { colors, variables };
|
|
719
781
|
}
|
|
@@ -875,6 +937,16 @@ function scanStyling(projectRoot) {
|
|
|
875
937
|
colors = { ...colors, ...extracted.colors };
|
|
876
938
|
cssVariables.push(...extracted.variables);
|
|
877
939
|
}
|
|
940
|
+
for (const cfg of TAILWIND_CONFIGS) {
|
|
941
|
+
const configPath = join3(projectRoot, cfg);
|
|
942
|
+
if (!existsSync3(configPath)) continue;
|
|
943
|
+
try {
|
|
944
|
+
const extracted = extractTailwindThemeTokens(readFileSync3(configPath, "utf-8"));
|
|
945
|
+
colors = { ...colors, ...extracted.colors };
|
|
946
|
+
cssVariables.push(...extracted.variables);
|
|
947
|
+
} catch {
|
|
948
|
+
}
|
|
949
|
+
}
|
|
878
950
|
cssVariables = [...new Set(cssVariables)];
|
|
879
951
|
const darkMode = detectDarkMode(projectRoot, cssContents);
|
|
880
952
|
if (approach === "unknown" && cssContents.length > 0) {
|
|
@@ -1091,9 +1163,37 @@ function routePathname(route) {
|
|
|
1091
1163
|
function declaredRouteObserved(route, observedRoutes) {
|
|
1092
1164
|
return observedRoutes.has(route) || route.includes("?") && observedRoutes.has(routePathname(route));
|
|
1093
1165
|
}
|
|
1166
|
+
function sectionForRoute(essence, route) {
|
|
1167
|
+
const mappedSectionId = essence.blueprint.routes?.[route]?.section;
|
|
1168
|
+
const sections = essence.blueprint.sections ?? [];
|
|
1169
|
+
if (mappedSectionId) {
|
|
1170
|
+
return sections.find((section) => section.id === mappedSectionId) ?? null;
|
|
1171
|
+
}
|
|
1172
|
+
return sections.find(
|
|
1173
|
+
(section) => section.pages?.some((page) => page.route === route || page.route === routePathname(route))
|
|
1174
|
+
) ?? null;
|
|
1175
|
+
}
|
|
1176
|
+
function readObservedRouteSource(projectRoot, file) {
|
|
1177
|
+
if (!file || file === ".") return "";
|
|
1178
|
+
try {
|
|
1179
|
+
return readFileSync5(join5(projectRoot, file), "utf-8");
|
|
1180
|
+
} catch {
|
|
1181
|
+
return "";
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
function routeLooksPublicFullBleed(path, file, code) {
|
|
1185
|
+
return /\b(?:pricing|plans|marketing|landing|public|hero|full-bleed|fullbleed|marketing-bleed)\b/i.test(
|
|
1186
|
+
`${path} ${file} ${code}`
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
function routeLooksAppShell(file, code) {
|
|
1190
|
+
return /\b(?:sidebar|side-nav|sidenav|app-frame|dashboard-shell|app-shell)\b|<\s*(?:Sidebar|AppShell|DashboardShell)\b/i.test(
|
|
1191
|
+
`${file} ${code}`
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1094
1194
|
function hasDoctrineEffect(essence, key) {
|
|
1095
1195
|
const effects = essence.dna.constraints?.effects;
|
|
1096
|
-
return Boolean(effects
|
|
1196
|
+
return Boolean(effects?.[key]);
|
|
1097
1197
|
}
|
|
1098
1198
|
function hasActionableDoctrineSource(doctrine, area) {
|
|
1099
1199
|
return doctrine.sources.some(
|
|
@@ -1168,6 +1268,32 @@ function scanBrownfieldIssues(projectRoot, essence) {
|
|
|
1168
1268
|
suggestion: "Confirm whether these are generated/dynamic routes or stale contract entries."
|
|
1169
1269
|
});
|
|
1170
1270
|
}
|
|
1271
|
+
const shellDriftRoutes = [];
|
|
1272
|
+
for (const route of routes.routes.slice(0, 80)) {
|
|
1273
|
+
const section = sectionForRoute(essence, route.path);
|
|
1274
|
+
const shell = section?.shell ?? "";
|
|
1275
|
+
if (!shell) continue;
|
|
1276
|
+
const source = readObservedRouteSource(projectRoot, route.file);
|
|
1277
|
+
const shellHasSidebar = /\bsidebar\b/i.test(shell);
|
|
1278
|
+
const shellIsPublicLight = /^(?:main-only|main-footer|topnav-main|topnav-main-footer)$/i.test(
|
|
1279
|
+
shell
|
|
1280
|
+
);
|
|
1281
|
+
if (shellHasSidebar && routeLooksPublicFullBleed(route.path, route.file, source)) {
|
|
1282
|
+
shellDriftRoutes.push(`${route.path} (${shell} vs public/full-bleed source)`);
|
|
1283
|
+
} else if (shellIsPublicLight && routeLooksAppShell(route.file, source)) {
|
|
1284
|
+
shellDriftRoutes.push(`${route.path} (${shell} vs app-shell source)`);
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
if (shellDriftRoutes.length > 0) {
|
|
1288
|
+
issues.push({
|
|
1289
|
+
type: "warning",
|
|
1290
|
+
rule: "brownfield-shell-drift",
|
|
1291
|
+
message: `Observed route shell signals disagree with the Decantr section shell: ${routeLabel(
|
|
1292
|
+
shellDriftRoutes
|
|
1293
|
+
)}.`,
|
|
1294
|
+
suggestion: "Regenerate and merge a brownfield proposal, or update the affected section shell after reviewing the route source."
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1171
1297
|
const adoptionMode = projectJson.initialized?.adoptionMode;
|
|
1172
1298
|
const themeId = essence.dna.theme.id;
|
|
1173
1299
|
if (adoptionMode === "contract-only" && themeId === "luminarum" && (styling.approach !== "unknown" || styling.cssVariables.length > 0)) {
|