@decantr/cli 1.3.0 → 1.5.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 +2 -2
- package/dist/chunk-6K6ZPDT4.js +2249 -0
- package/dist/{chunk-2F6Q5S5W.js → chunk-SUNMRG3P.js} +257 -2038
- package/dist/index.js +2 -2
- package/dist/upgrade-I2RUTNAT.js +131 -0
- package/package.json +3 -3
- package/dist/chunk-CT5XHG6I.js +0 -203
- package/dist/upgrade-ZSLT32KN.js +0 -66
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
2
|
RegistryClient,
|
|
3
|
+
composeArchetypes,
|
|
4
|
+
composeSections,
|
|
5
|
+
deriveTransitions,
|
|
6
|
+
deriveZones,
|
|
7
|
+
generateTopologySection,
|
|
8
|
+
refreshDerivedFiles,
|
|
9
|
+
scaffoldMinimal,
|
|
10
|
+
scaffoldProject,
|
|
3
11
|
syncRegistry
|
|
4
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-6K6ZPDT4.js";
|
|
5
13
|
|
|
6
14
|
// src/index.ts
|
|
7
|
-
import { readFileSync as
|
|
8
|
-
import { join as
|
|
9
|
-
import { fileURLToPath
|
|
15
|
+
import { readFileSync as readFileSync14, existsSync as existsSync19, readdirSync as readdirSync6 } from "fs";
|
|
16
|
+
import { join as join19, dirname } from "path";
|
|
17
|
+
import { fileURLToPath } from "url";
|
|
10
18
|
import { validateEssence as validateEssence2, evaluateGuard, isV3 as isV36 } from "@decantr/essence-spec";
|
|
11
19
|
import { RegistryAPIClient as RegistryAPIClient2 } from "@decantr/registry";
|
|
12
20
|
|
|
@@ -384,1801 +392,9 @@ async function runSimplifiedInit(blueprints) {
|
|
|
384
392
|
return { choice: "default" };
|
|
385
393
|
}
|
|
386
394
|
|
|
387
|
-
// src/scaffold.ts
|
|
388
|
-
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync, appendFileSync } from "fs";
|
|
389
|
-
import { join as join2, dirname } from "path";
|
|
390
|
-
import { fileURLToPath } from "url";
|
|
391
|
-
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
392
|
-
function composeArchetypes(composeEntries, archetypeResults) {
|
|
393
|
-
if (composeEntries.length === 0) {
|
|
394
|
-
return {
|
|
395
|
-
pages: [{ id: "home", layout: ["hero"] }],
|
|
396
|
-
features: [],
|
|
397
|
-
defaultShell: "sidebar-main"
|
|
398
|
-
};
|
|
399
|
-
}
|
|
400
|
-
const allPages = [];
|
|
401
|
-
const allFeatures = [];
|
|
402
|
-
let defaultShell = "sidebar-main";
|
|
403
|
-
for (let i = 0; i < composeEntries.length; i++) {
|
|
404
|
-
const entry = composeEntries[i];
|
|
405
|
-
const archetypeId = typeof entry === "string" ? entry : entry.archetype;
|
|
406
|
-
const data = archetypeResults.get(archetypeId);
|
|
407
|
-
if (!data?.pages) continue;
|
|
408
|
-
const isPrimary = i === 0;
|
|
409
|
-
if (isPrimary) {
|
|
410
|
-
defaultShell = data.pages[0]?.shell || defaultShell;
|
|
411
|
-
for (const page of data.pages) {
|
|
412
|
-
allPages.push({
|
|
413
|
-
id: page.id,
|
|
414
|
-
layout: page.default_layout?.length ? page.default_layout : ["hero"],
|
|
415
|
-
...page.shell !== defaultShell ? { shell_override: page.shell } : {}
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
|
-
} else {
|
|
419
|
-
const prefix = typeof entry === "string" ? entry : entry.prefix;
|
|
420
|
-
for (const page of data.pages) {
|
|
421
|
-
allPages.push({
|
|
422
|
-
id: `${prefix}-${page.id}`,
|
|
423
|
-
layout: page.default_layout?.length ? page.default_layout : ["hero"],
|
|
424
|
-
...page.shell !== defaultShell ? { shell_override: page.shell } : {}
|
|
425
|
-
});
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
if (data.features) {
|
|
429
|
-
allFeatures.push(...data.features);
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
if (allPages.length === 0) {
|
|
433
|
-
allPages.push({ id: "home", layout: ["hero"] });
|
|
434
|
-
}
|
|
435
|
-
return {
|
|
436
|
-
pages: allPages,
|
|
437
|
-
features: [...new Set(allFeatures)],
|
|
438
|
-
defaultShell
|
|
439
|
-
};
|
|
440
|
-
}
|
|
441
|
-
function composeSections(composeEntries, archetypeResults, overrides) {
|
|
442
|
-
if (composeEntries.length === 0) {
|
|
443
|
-
return {
|
|
444
|
-
sections: [{
|
|
445
|
-
id: "default",
|
|
446
|
-
role: "primary",
|
|
447
|
-
shell: "sidebar-main",
|
|
448
|
-
features: [],
|
|
449
|
-
description: "Default section",
|
|
450
|
-
pages: [{ id: "home", layout: ["hero"] }]
|
|
451
|
-
}],
|
|
452
|
-
features: [],
|
|
453
|
-
defaultShell: "sidebar-main"
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
const sections = [];
|
|
457
|
-
const allFeatures = [];
|
|
458
|
-
let defaultShell = "sidebar-main";
|
|
459
|
-
const pagesRemoveSet = new Set(overrides?.pages_remove ?? []);
|
|
460
|
-
for (let i = 0; i < composeEntries.length; i++) {
|
|
461
|
-
const entry = composeEntries[i];
|
|
462
|
-
const archetypeId = typeof entry === "string" ? entry : entry.archetype;
|
|
463
|
-
const data = archetypeResults.get(archetypeId);
|
|
464
|
-
if (!data?.pages) continue;
|
|
465
|
-
const isPrimary = i === 0;
|
|
466
|
-
if (isPrimary) {
|
|
467
|
-
defaultShell = data.pages[0]?.shell || defaultShell;
|
|
468
|
-
}
|
|
469
|
-
const pages = [];
|
|
470
|
-
for (const page of data.pages) {
|
|
471
|
-
if (pagesRemoveSet.has(page.id)) continue;
|
|
472
|
-
const overriddenPage = overrides?.pages?.[page.id];
|
|
473
|
-
pages.push({
|
|
474
|
-
id: page.id,
|
|
475
|
-
layout: page.default_layout?.length ? page.default_layout : ["hero"],
|
|
476
|
-
...overriddenPage
|
|
477
|
-
});
|
|
478
|
-
}
|
|
479
|
-
sections.push({
|
|
480
|
-
id: archetypeId,
|
|
481
|
-
role: data.role ?? "primary",
|
|
482
|
-
shell: data.pages[0]?.shell || "sidebar-main",
|
|
483
|
-
features: data.features ?? [],
|
|
484
|
-
description: data.description ?? "",
|
|
485
|
-
pages
|
|
486
|
-
});
|
|
487
|
-
if (data.features) {
|
|
488
|
-
allFeatures.push(...data.features);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
if (sections.length === 0) {
|
|
492
|
-
sections.push({
|
|
493
|
-
id: "default",
|
|
494
|
-
role: "primary",
|
|
495
|
-
shell: "sidebar-main",
|
|
496
|
-
features: [],
|
|
497
|
-
description: "Default section",
|
|
498
|
-
pages: [{ id: "home", layout: ["hero"] }]
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
let features = [...new Set(allFeatures)];
|
|
502
|
-
if (overrides?.features_add) {
|
|
503
|
-
for (const f of overrides.features_add) {
|
|
504
|
-
if (!features.includes(f)) features.push(f);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
if (overrides?.features_remove) {
|
|
508
|
-
const removeSet = new Set(overrides.features_remove);
|
|
509
|
-
features = features.filter((f) => !removeSet.has(f));
|
|
510
|
-
}
|
|
511
|
-
return { sections, features, defaultShell };
|
|
512
|
-
}
|
|
513
|
-
var ZONE_ORDER = ["public", "gateway", "primary", "auxiliary"];
|
|
514
|
-
function deriveZones(inputs) {
|
|
515
|
-
const zoneMap = /* @__PURE__ */ new Map();
|
|
516
|
-
for (const input of inputs) {
|
|
517
|
-
const existing = zoneMap.get(input.role);
|
|
518
|
-
if (existing) {
|
|
519
|
-
existing.archetypes.push(input.archetypeId);
|
|
520
|
-
existing.features.push(...input.features);
|
|
521
|
-
existing.descriptions.push(input.description);
|
|
522
|
-
} else {
|
|
523
|
-
zoneMap.set(input.role, {
|
|
524
|
-
role: input.role,
|
|
525
|
-
archetypes: [input.archetypeId],
|
|
526
|
-
shell: input.shell,
|
|
527
|
-
features: [...input.features],
|
|
528
|
-
descriptions: [input.description]
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
for (const zone of zoneMap.values()) {
|
|
533
|
-
zone.features = [...new Set(zone.features)];
|
|
534
|
-
}
|
|
535
|
-
return ZONE_ORDER.filter((role) => zoneMap.has(role)).map((role) => zoneMap.get(role));
|
|
536
|
-
}
|
|
537
|
-
var GATEWAY_TRIGGER_MAP = {
|
|
538
|
-
auth: "authentication",
|
|
539
|
-
login: "authentication",
|
|
540
|
-
mfa: "authentication",
|
|
541
|
-
payment: "payment",
|
|
542
|
-
subscription: "payment",
|
|
543
|
-
checkout: "payment",
|
|
544
|
-
onboarding: "onboarding",
|
|
545
|
-
"setup-wizard": "onboarding",
|
|
546
|
-
welcome: "onboarding",
|
|
547
|
-
invite: "invitation",
|
|
548
|
-
"access-code": "invitation"
|
|
549
|
-
};
|
|
550
|
-
function resolveGatewayTrigger(features) {
|
|
551
|
-
for (const feature of features) {
|
|
552
|
-
const trigger = GATEWAY_TRIGGER_MAP[feature];
|
|
553
|
-
if (trigger) return trigger;
|
|
554
|
-
}
|
|
555
|
-
return "authentication";
|
|
556
|
-
}
|
|
557
|
-
function deriveTransitions(zones) {
|
|
558
|
-
const transitions = [];
|
|
559
|
-
const roles = new Set(zones.map((z) => z.role));
|
|
560
|
-
const gateway = zones.find((z) => z.role === "gateway");
|
|
561
|
-
const gatewayTrigger = gateway ? resolveGatewayTrigger(gateway.features) : "authentication";
|
|
562
|
-
const hasApp = roles.has("primary") || roles.has("auxiliary");
|
|
563
|
-
const hasGateway = roles.has("gateway");
|
|
564
|
-
const hasPublic = roles.has("public");
|
|
565
|
-
if (hasPublic && hasGateway) {
|
|
566
|
-
transitions.push({ from: "public", to: "gateway", type: "conversion", trigger: gatewayTrigger });
|
|
567
|
-
}
|
|
568
|
-
if (hasPublic && hasApp && !hasGateway) {
|
|
569
|
-
transitions.push({ from: "public", to: "app", type: "conversion", trigger: "navigation" });
|
|
570
|
-
}
|
|
571
|
-
if (hasGateway && hasApp) {
|
|
572
|
-
transitions.push({ from: "gateway", to: "app", type: "gate-pass", trigger: gatewayTrigger });
|
|
573
|
-
transitions.push({ from: "app", to: "gateway", type: "gate-return", trigger: gatewayTrigger });
|
|
574
|
-
}
|
|
575
|
-
if (hasApp && hasPublic) {
|
|
576
|
-
transitions.push({ from: "app", to: "public", type: "navigation", trigger: "external" });
|
|
577
|
-
}
|
|
578
|
-
return transitions;
|
|
579
|
-
}
|
|
580
|
-
var ZONE_LABELS = {
|
|
581
|
-
public: "Public",
|
|
582
|
-
gateway: "Gateway",
|
|
583
|
-
primary: "App",
|
|
584
|
-
auxiliary: "App (auxiliary)"
|
|
585
|
-
};
|
|
586
|
-
function generateTopologySection(data, personality) {
|
|
587
|
-
const lines = [];
|
|
588
|
-
lines.push("## Composition Topology");
|
|
589
|
-
lines.push("");
|
|
590
|
-
lines.push(`**Intent:** ${data.intent}`);
|
|
591
|
-
lines.push("");
|
|
592
|
-
lines.push("### Zones");
|
|
593
|
-
lines.push("");
|
|
594
|
-
for (const zone of data.zones) {
|
|
595
|
-
const label = ZONE_LABELS[zone.role] || zone.role;
|
|
596
|
-
lines.push(`**${label}** \u2014 ${zone.shell} shell`);
|
|
597
|
-
lines.push(` Archetypes: ${zone.archetypes.join(", ")}`);
|
|
598
|
-
lines.push(` Purpose: ${zone.descriptions.join(" ")}`);
|
|
599
|
-
if (personality.length > 0) {
|
|
600
|
-
lines.push(` Tone: ${personality.join(", ")}`);
|
|
601
|
-
}
|
|
602
|
-
if (zone.features.length > 0) {
|
|
603
|
-
lines.push(` Features: ${zone.features.join(", ")}`);
|
|
604
|
-
}
|
|
605
|
-
lines.push("");
|
|
606
|
-
}
|
|
607
|
-
if (data.transitions.length > 0) {
|
|
608
|
-
lines.push("### Zone Transitions");
|
|
609
|
-
lines.push("");
|
|
610
|
-
for (const t of data.transitions) {
|
|
611
|
-
const fromLabel = t.from.charAt(0).toUpperCase() + t.from.slice(1);
|
|
612
|
-
const toLabel = t.to.charAt(0).toUpperCase() + t.to.slice(1);
|
|
613
|
-
lines.push(` ${fromLabel} \u2192 ${toLabel}: ${t.type} (${t.trigger})`);
|
|
614
|
-
}
|
|
615
|
-
lines.push("");
|
|
616
|
-
}
|
|
617
|
-
lines.push("### Default Entry Points");
|
|
618
|
-
lines.push("");
|
|
619
|
-
lines.push(` Anonymous users enter: ${data.entryPoints.anonymous}`);
|
|
620
|
-
lines.push(` Authenticated users enter: ${data.entryPoints.authenticated}`);
|
|
621
|
-
lines.push(` Auth redirect target: ${data.entryPoints.authenticated}`);
|
|
622
|
-
lines.push("");
|
|
623
|
-
return lines.join("\n");
|
|
624
|
-
}
|
|
625
|
-
var CLI_VERSION = "1.0.0";
|
|
626
|
-
function generateTokensCSS(themeData, mode) {
|
|
627
|
-
if (!themeData) {
|
|
628
|
-
return `/* No theme data available */
|
|
629
|
-
:root {
|
|
630
|
-
--d-primary: #6366f1;
|
|
631
|
-
--d-secondary: #a1a1aa;
|
|
632
|
-
--d-accent: #f59e0b;
|
|
633
|
-
--d-bg: #18181b;
|
|
634
|
-
--d-surface: #1f1f23;
|
|
635
|
-
--d-surface-raised: #27272a;
|
|
636
|
-
--d-border: #3f3f46;
|
|
637
|
-
--d-text: #fafafa;
|
|
638
|
-
--d-text-muted: #a1a1aa;
|
|
639
|
-
}
|
|
640
|
-
`;
|
|
641
|
-
}
|
|
642
|
-
const seed = themeData.seed || {};
|
|
643
|
-
const palette = themeData.palette || {};
|
|
644
|
-
const resolvedMode = mode === "auto" ? "dark" : mode;
|
|
645
|
-
function buildTokens(tokenMode) {
|
|
646
|
-
return {
|
|
647
|
-
// Seed colors
|
|
648
|
-
"--d-primary": seed.primary || "#6366f1",
|
|
649
|
-
"--d-secondary": seed.secondary || "#a1a1aa",
|
|
650
|
-
"--d-accent": seed.accent || "#f59e0b",
|
|
651
|
-
// Palette colors (mode-aware)
|
|
652
|
-
"--d-bg": palette.background?.[tokenMode] || "#18181b",
|
|
653
|
-
"--d-surface": palette.surface?.[tokenMode] || "#1f1f23",
|
|
654
|
-
"--d-surface-raised": palette["surface-raised"]?.[tokenMode] || "#27272a",
|
|
655
|
-
"--d-border": palette.border?.[tokenMode] || "#3f3f46",
|
|
656
|
-
"--d-text": palette.text?.[tokenMode] || "#fafafa",
|
|
657
|
-
"--d-text-muted": palette["text-muted"]?.[tokenMode] || "#a1a1aa",
|
|
658
|
-
"--d-primary-hover": palette["primary-hover"]?.[tokenMode] || seed.primary || "#6366f1",
|
|
659
|
-
// Spacing scale
|
|
660
|
-
"--d-gap-1": "0.25rem",
|
|
661
|
-
"--d-gap-2": "0.5rem",
|
|
662
|
-
"--d-gap-3": "0.75rem",
|
|
663
|
-
"--d-gap-4": "1rem",
|
|
664
|
-
"--d-gap-6": "1.5rem",
|
|
665
|
-
"--d-gap-8": "2rem",
|
|
666
|
-
"--d-gap-12": "3rem",
|
|
667
|
-
// Radii
|
|
668
|
-
"--d-radius": "0.5rem",
|
|
669
|
-
"--d-radius-sm": "0.25rem",
|
|
670
|
-
"--d-radius-lg": "0.75rem",
|
|
671
|
-
"--d-radius-xl": "1rem",
|
|
672
|
-
"--d-radius-full": "9999px",
|
|
673
|
-
// Shadows
|
|
674
|
-
"--d-shadow-sm": "0 1px 2px rgba(0,0,0,0.05)",
|
|
675
|
-
"--d-shadow": "0 1px 3px rgba(0,0,0,0.1)",
|
|
676
|
-
"--d-shadow-md": "0 4px 6px rgba(0,0,0,0.1)",
|
|
677
|
-
"--d-shadow-lg": "0 10px 15px rgba(0,0,0,0.1)",
|
|
678
|
-
// Status colors
|
|
679
|
-
"--d-success": themeData.tokens?.base?.success || "#22c55e",
|
|
680
|
-
"--d-error": themeData.tokens?.base?.danger || "#ef4444",
|
|
681
|
-
"--d-warning": themeData.tokens?.base?.warning || "#f59e0b",
|
|
682
|
-
"--d-info": "#3b82f6"
|
|
683
|
-
};
|
|
684
|
-
}
|
|
685
|
-
const tokens = buildTokens(resolvedMode);
|
|
686
|
-
const lines = Object.entries(tokens).map(([key, value]) => ` ${key}: ${value};`).join("\n");
|
|
687
|
-
let css = `/* Generated by @decantr/cli */
|
|
688
|
-
:root {
|
|
689
|
-
${lines}
|
|
690
|
-
}
|
|
691
|
-
`;
|
|
692
|
-
if (mode === "auto") {
|
|
693
|
-
const lightTokens = buildTokens("light");
|
|
694
|
-
const paletteKeys = ["--d-bg", "--d-surface", "--d-surface-raised", "--d-border", "--d-text", "--d-text-muted", "--d-primary-hover"];
|
|
695
|
-
const lightLines = Object.entries(lightTokens).filter(([key]) => paletteKeys.includes(key)).map(([key, value]) => ` ${key}: ${value};`).join("\n");
|
|
696
|
-
css += `
|
|
697
|
-
@media (prefers-color-scheme: light) {
|
|
698
|
-
:root {
|
|
699
|
-
${lightLines}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
`;
|
|
703
|
-
}
|
|
704
|
-
return css;
|
|
705
|
-
}
|
|
706
|
-
function generateDecoratorsCSS(recipeData, themeName) {
|
|
707
|
-
if (!recipeData?.decorators) {
|
|
708
|
-
return `/* No recipe decorators available */`;
|
|
709
|
-
}
|
|
710
|
-
const decorators = recipeData.decorators;
|
|
711
|
-
const css = [
|
|
712
|
-
`/* Generated by @decantr/cli from recipe: ${themeName} */`,
|
|
713
|
-
""
|
|
714
|
-
];
|
|
715
|
-
for (const [name, description] of Object.entries(decorators)) {
|
|
716
|
-
css.push(generateDecoratorRule(name, description));
|
|
717
|
-
css.push("");
|
|
718
|
-
}
|
|
719
|
-
css.push(`/* Animation keyframes */
|
|
720
|
-
@keyframes decantr-fade-in {
|
|
721
|
-
from { opacity: 0; transform: translateY(8px); }
|
|
722
|
-
to { opacity: 1; transform: translateY(0); }
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
@keyframes decantr-pulse {
|
|
726
|
-
0%, 100% { opacity: 1; }
|
|
727
|
-
50% { opacity: 0.5; }
|
|
728
|
-
}
|
|
729
|
-
`);
|
|
730
|
-
return css.join("\n");
|
|
731
|
-
}
|
|
732
|
-
function generateDecoratorRule(name, description) {
|
|
733
|
-
const rules = [];
|
|
734
|
-
const descLower = description.toLowerCase();
|
|
735
|
-
if (descLower.includes("surface background") || descLower.includes("surface elevation")) {
|
|
736
|
-
rules.push("background: var(--d-surface)");
|
|
737
|
-
} else if (descLower.includes("background") && descLower.includes("theme")) {
|
|
738
|
-
rules.push("background: var(--d-bg)");
|
|
739
|
-
} else if (descLower.includes("primary-tinted") || descLower.includes("primary background")) {
|
|
740
|
-
rules.push("background: color-mix(in srgb, var(--d-primary) 15%, var(--d-surface))");
|
|
741
|
-
}
|
|
742
|
-
if (descLower.includes("1px border") || descLower.includes("subtle border")) {
|
|
743
|
-
rules.push("border: 1px solid var(--d-border)");
|
|
744
|
-
} else if (descLower.includes("border") && !descLower.includes("radius")) {
|
|
745
|
-
rules.push("border: 1px solid var(--d-border)");
|
|
746
|
-
}
|
|
747
|
-
const radiusMatch = descLower.match(/(\d+)px radius/);
|
|
748
|
-
if (radiusMatch) {
|
|
749
|
-
rules.push(`border-radius: ${radiusMatch[1]}px`);
|
|
750
|
-
} else if (descLower.includes("radius") || descLower.includes("rounded")) {
|
|
751
|
-
rules.push("border-radius: var(--d-radius)");
|
|
752
|
-
}
|
|
753
|
-
if (descLower.includes("hover shadow") || descLower.includes("shadow transition")) {
|
|
754
|
-
rules.push("transition: box-shadow 0.15s ease");
|
|
755
|
-
}
|
|
756
|
-
if (descLower.includes("elevation") || descLower.includes("shadow")) {
|
|
757
|
-
rules.push("box-shadow: var(--d-shadow)");
|
|
758
|
-
}
|
|
759
|
-
if (descLower.includes("entrance animation") || descLower.includes("fade")) {
|
|
760
|
-
rules.push("animation: decantr-fade-in 0.2s ease-out");
|
|
761
|
-
}
|
|
762
|
-
if (descLower.includes("pulse animation") || descLower.includes("skeleton")) {
|
|
763
|
-
rules.push("animation: decantr-pulse 1.5s ease-in-out infinite");
|
|
764
|
-
}
|
|
765
|
-
if (descLower.includes("blur") || descLower.includes("glass")) {
|
|
766
|
-
rules.push("backdrop-filter: blur(8px)");
|
|
767
|
-
}
|
|
768
|
-
if (descLower.includes("right-aligned")) {
|
|
769
|
-
rules.push("margin-left: auto");
|
|
770
|
-
} else if (descLower.includes("left-aligned")) {
|
|
771
|
-
rules.push("margin-right: auto");
|
|
772
|
-
}
|
|
773
|
-
if (descLower.includes("message bubble") || descLower.includes("bubble")) {
|
|
774
|
-
rules.push("padding: var(--d-gap-3) var(--d-gap-4)");
|
|
775
|
-
rules.push("border-radius: var(--d-radius-lg)");
|
|
776
|
-
rules.push("max-width: 80%");
|
|
777
|
-
}
|
|
778
|
-
if (rules.length === 0) {
|
|
779
|
-
return `/* .${name}: ${description} */`;
|
|
780
|
-
}
|
|
781
|
-
return `.${name} {
|
|
782
|
-
${rules.join(";\n ")};
|
|
783
|
-
}`;
|
|
784
|
-
}
|
|
785
|
-
function serializeLayoutItem(item) {
|
|
786
|
-
if (typeof item === "string") {
|
|
787
|
-
return item;
|
|
788
|
-
}
|
|
789
|
-
if (typeof item === "object" && item !== null) {
|
|
790
|
-
const obj = item;
|
|
791
|
-
if (typeof obj.pattern === "string") {
|
|
792
|
-
const preset = obj.preset ? ` (${obj.preset})` : "";
|
|
793
|
-
const alias = obj.as ? ` as ${obj.as}` : "";
|
|
794
|
-
return `${obj.pattern}${preset}${alias}`;
|
|
795
|
-
}
|
|
796
|
-
if (Array.isArray(obj.cols)) {
|
|
797
|
-
const cols = obj.cols.map(serializeLayoutItem).join(" | ");
|
|
798
|
-
const breakpoint = obj.at ? ` @${obj.at}` : "";
|
|
799
|
-
return `[${cols}]${breakpoint}`;
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
return "custom";
|
|
803
|
-
}
|
|
804
|
-
function extractPatternNames(item) {
|
|
805
|
-
if (typeof item === "string") {
|
|
806
|
-
return [item];
|
|
807
|
-
}
|
|
808
|
-
if (typeof item === "object" && item !== null) {
|
|
809
|
-
const obj = item;
|
|
810
|
-
if (typeof obj.pattern === "string") {
|
|
811
|
-
return [obj.pattern];
|
|
812
|
-
}
|
|
813
|
-
if (Array.isArray(obj.cols)) {
|
|
814
|
-
return obj.cols.flatMap(extractPatternNames);
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
return [];
|
|
818
|
-
}
|
|
819
|
-
function loadTemplate(name) {
|
|
820
|
-
const fromDist = join2(__dirname, "..", "src", "templates", name);
|
|
821
|
-
if (existsSync2(fromDist)) {
|
|
822
|
-
return readFileSync2(fromDist, "utf-8");
|
|
823
|
-
}
|
|
824
|
-
const fromSrc = join2(__dirname, "templates", name);
|
|
825
|
-
if (existsSync2(fromSrc)) {
|
|
826
|
-
return readFileSync2(fromSrc, "utf-8");
|
|
827
|
-
}
|
|
828
|
-
throw new Error(`Template not found: ${name}`);
|
|
829
|
-
}
|
|
830
|
-
function renderTemplate(template, vars) {
|
|
831
|
-
let result = template;
|
|
832
|
-
for (const [key, value] of Object.entries(vars)) {
|
|
833
|
-
result = result.replace(new RegExp(`\\{\\{${key}\\}\\}`, "g"), value);
|
|
834
|
-
}
|
|
835
|
-
return result;
|
|
836
|
-
}
|
|
837
|
-
function resolvePatternAlias(item, patterns) {
|
|
838
|
-
if (!patterns) return item;
|
|
839
|
-
if (typeof item === "string") {
|
|
840
|
-
const patternDef = patterns.find((p) => p.as === item);
|
|
841
|
-
if (patternDef) {
|
|
842
|
-
if (patternDef.preset) {
|
|
843
|
-
return { pattern: patternDef.pattern, preset: patternDef.preset };
|
|
844
|
-
}
|
|
845
|
-
return patternDef.pattern;
|
|
846
|
-
}
|
|
847
|
-
return item;
|
|
848
|
-
}
|
|
849
|
-
if (typeof item === "object" && item !== null) {
|
|
850
|
-
const obj = item;
|
|
851
|
-
if (Array.isArray(obj.cols)) {
|
|
852
|
-
return {
|
|
853
|
-
...obj,
|
|
854
|
-
cols: obj.cols.map((col) => resolvePatternAlias(col, patterns))
|
|
855
|
-
};
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
return item;
|
|
859
|
-
}
|
|
860
|
-
function buildEssence(options, archetypeData) {
|
|
861
|
-
let structure = [
|
|
862
|
-
{ id: "home", shell: options.shell, layout: ["hero"] }
|
|
863
|
-
];
|
|
864
|
-
let features = options.features;
|
|
865
|
-
if (archetypeData?.pages) {
|
|
866
|
-
structure = archetypeData.pages.map((p) => {
|
|
867
|
-
const resolvedLayout = (p.default_layout?.length ? p.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, p.patterns));
|
|
868
|
-
return {
|
|
869
|
-
id: p.id,
|
|
870
|
-
shell: p.shell || options.shell,
|
|
871
|
-
layout: resolvedLayout
|
|
872
|
-
};
|
|
873
|
-
});
|
|
874
|
-
}
|
|
875
|
-
if (archetypeData?.features) {
|
|
876
|
-
features = [.../* @__PURE__ */ new Set([...features, ...archetypeData.features])];
|
|
877
|
-
}
|
|
878
|
-
const contentGapMap = {
|
|
879
|
-
compact: "_gap2",
|
|
880
|
-
comfortable: "_gap4",
|
|
881
|
-
spacious: "_gap6"
|
|
882
|
-
};
|
|
883
|
-
const archetype = options.archetype || "custom";
|
|
884
|
-
const essence = {
|
|
885
|
-
version: "2.0.0",
|
|
886
|
-
archetype,
|
|
887
|
-
theme: {
|
|
888
|
-
style: options.theme,
|
|
889
|
-
mode: options.mode,
|
|
890
|
-
recipe: options.theme,
|
|
891
|
-
// Recipe defaults to theme
|
|
892
|
-
shape: options.shape
|
|
893
|
-
},
|
|
894
|
-
personality: options.personality,
|
|
895
|
-
platform: {
|
|
896
|
-
type: "spa",
|
|
897
|
-
routing: "hash"
|
|
898
|
-
},
|
|
899
|
-
structure,
|
|
900
|
-
features,
|
|
901
|
-
guard: {
|
|
902
|
-
enforce_style: true,
|
|
903
|
-
enforce_recipe: true,
|
|
904
|
-
mode: options.guard
|
|
905
|
-
},
|
|
906
|
-
density: {
|
|
907
|
-
level: options.density,
|
|
908
|
-
content_gap: contentGapMap[options.density] || "_gap4"
|
|
909
|
-
},
|
|
910
|
-
target: options.target
|
|
911
|
-
};
|
|
912
|
-
if (options.accessibility) {
|
|
913
|
-
essence.accessibility = options.accessibility;
|
|
914
|
-
}
|
|
915
|
-
return essence;
|
|
916
|
-
}
|
|
917
|
-
function buildEssenceV3(options, archetypeData, themeHints, recipeHints) {
|
|
918
|
-
let pages = [
|
|
919
|
-
{ id: "home", layout: ["hero"] }
|
|
920
|
-
];
|
|
921
|
-
let features = options.features;
|
|
922
|
-
let defaultShell = options.shell || "sidebar-main";
|
|
923
|
-
if (archetypeData?.pages) {
|
|
924
|
-
defaultShell = archetypeData.pages[0]?.shell || defaultShell;
|
|
925
|
-
pages = archetypeData.pages.map((p) => {
|
|
926
|
-
const resolvedLayout = (p.default_layout?.length ? p.default_layout : ["hero"]).map((item) => resolvePatternAlias(item, p.patterns));
|
|
927
|
-
return {
|
|
928
|
-
id: p.id,
|
|
929
|
-
...p.shell !== defaultShell ? { shell_override: p.shell } : {},
|
|
930
|
-
layout: resolvedLayout
|
|
931
|
-
};
|
|
932
|
-
});
|
|
933
|
-
}
|
|
934
|
-
if (archetypeData?.features) {
|
|
935
|
-
features = [.../* @__PURE__ */ new Set([...features, ...archetypeData.features])];
|
|
936
|
-
}
|
|
937
|
-
const densityLevelMap = {
|
|
938
|
-
compact: "_gap2",
|
|
939
|
-
comfortable: "_gap4",
|
|
940
|
-
spacious: "_gap6"
|
|
941
|
-
};
|
|
942
|
-
const shapeRadiusMap = {
|
|
943
|
-
pill: 12,
|
|
944
|
-
rounded: 8,
|
|
945
|
-
sharp: 2
|
|
946
|
-
};
|
|
947
|
-
const guardModeMap = {
|
|
948
|
-
strict: { mode: "strict", dna_enforcement: "error", blueprint_enforcement: "warn" },
|
|
949
|
-
guided: { mode: "guided", dna_enforcement: "error", blueprint_enforcement: "off" },
|
|
950
|
-
creative: { mode: "creative", dna_enforcement: "off", blueprint_enforcement: "off" }
|
|
951
|
-
};
|
|
952
|
-
const dna = {
|
|
953
|
-
theme: {
|
|
954
|
-
style: options.theme,
|
|
955
|
-
mode: options.mode,
|
|
956
|
-
recipe: options.theme,
|
|
957
|
-
shape: options.shape
|
|
958
|
-
},
|
|
959
|
-
spacing: {
|
|
960
|
-
base_unit: 4,
|
|
961
|
-
scale: "linear",
|
|
962
|
-
density: options.density,
|
|
963
|
-
content_gap: densityLevelMap[options.density] || "_gap4"
|
|
964
|
-
},
|
|
965
|
-
typography: {
|
|
966
|
-
scale: themeHints?.typography_hints?.scale || "modular",
|
|
967
|
-
heading_weight: themeHints?.typography_hints?.heading_weight || 600,
|
|
968
|
-
body_weight: themeHints?.typography_hints?.body_weight || 400
|
|
969
|
-
},
|
|
970
|
-
color: {
|
|
971
|
-
palette: "semantic",
|
|
972
|
-
accent_count: 1,
|
|
973
|
-
cvd_preference: options.accessibility?.cvd_preference || "auto"
|
|
974
|
-
},
|
|
975
|
-
radius: {
|
|
976
|
-
philosophy: recipeHints?.radius_hints?.philosophy || options.shape,
|
|
977
|
-
base: recipeHints?.radius_hints?.base || shapeRadiusMap[options.shape] || 8
|
|
978
|
-
},
|
|
979
|
-
elevation: {
|
|
980
|
-
system: "layered",
|
|
981
|
-
max_levels: 3
|
|
982
|
-
},
|
|
983
|
-
motion: {
|
|
984
|
-
preference: recipeHints?.animation?.preference || themeHints?.motion_hints?.preference || "subtle",
|
|
985
|
-
duration_scale: 1,
|
|
986
|
-
reduce_motion: themeHints?.motion_hints?.reduce_motion_default ?? true
|
|
987
|
-
},
|
|
988
|
-
accessibility: {
|
|
989
|
-
wcag_level: options.accessibility?.wcag_level || "AA",
|
|
990
|
-
focus_visible: true,
|
|
991
|
-
skip_nav: true
|
|
992
|
-
},
|
|
993
|
-
personality: options.personality
|
|
994
|
-
};
|
|
995
|
-
const blueprint = {
|
|
996
|
-
shell: defaultShell,
|
|
997
|
-
pages,
|
|
998
|
-
features
|
|
999
|
-
};
|
|
1000
|
-
const meta = {
|
|
1001
|
-
archetype: options.archetype || "custom",
|
|
1002
|
-
target: options.target,
|
|
1003
|
-
platform: {
|
|
1004
|
-
type: "spa",
|
|
1005
|
-
routing: "hash"
|
|
1006
|
-
},
|
|
1007
|
-
guard: guardModeMap[options.guard] || guardModeMap.guided
|
|
1008
|
-
};
|
|
1009
|
-
return {
|
|
1010
|
-
version: "3.0.0",
|
|
1011
|
-
dna,
|
|
1012
|
-
blueprint,
|
|
1013
|
-
meta
|
|
1014
|
-
};
|
|
1015
|
-
}
|
|
1016
|
-
var CSS_APPROACH_CONTENT = `## CSS Implementation
|
|
1017
|
-
|
|
1018
|
-
This project uses **@decantr/css** for layout atoms and the generated CSS files for theme tokens and recipe decorators.
|
|
1019
|
-
|
|
1020
|
-
### Setup
|
|
1021
|
-
|
|
1022
|
-
\`\`\`javascript
|
|
1023
|
-
// 1. Import the atoms runtime
|
|
1024
|
-
import { css } from '@decantr/css';
|
|
1025
|
-
|
|
1026
|
-
// 2. Import generated CSS files (created by decantr init)
|
|
1027
|
-
import './styles/tokens.css'; // Theme tokens (--d-primary, --d-surface, etc.)
|
|
1028
|
-
import './styles/decorators.css'; // Recipe decorators
|
|
1029
|
-
\`\`\`
|
|
1030
|
-
|
|
1031
|
-
### Using Atoms
|
|
1032
|
-
|
|
1033
|
-
The \`css()\` function processes atom strings and injects CSS at runtime:
|
|
1034
|
-
|
|
1035
|
-
\`\`\`jsx
|
|
1036
|
-
// Layout atoms
|
|
1037
|
-
<div className={css('_flex _col _gap4 _p4')}>
|
|
1038
|
-
<h1 className={css('_heading1')}>Title</h1>
|
|
1039
|
-
<p className={css('_textsm _fgmuted')}>Description</p>
|
|
1040
|
-
</div>
|
|
1041
|
-
|
|
1042
|
-
// Responsive prefixes (mobile-first)
|
|
1043
|
-
<div className={css('_gc1 _sm:gc2 _lg:gc4')}>
|
|
1044
|
-
{/* 1 col -> 2 cols at 640px -> 4 cols at 1024px */}
|
|
1045
|
-
</div>
|
|
1046
|
-
\`\`\`
|
|
1047
|
-
|
|
1048
|
-
### Atom Reference
|
|
1049
|
-
|
|
1050
|
-
#### Display
|
|
1051
|
-
| Atom | CSS |
|
|
1052
|
-
|------|-----|
|
|
1053
|
-
| \`_flex\` | \`display:flex\` |
|
|
1054
|
-
| \`_grid\` | \`display:grid\` |
|
|
1055
|
-
| \`_block\` | \`display:block\` |
|
|
1056
|
-
| \`_inline\` | \`display:inline\` |
|
|
1057
|
-
| \`_inlineflex\` | \`display:inline-flex\` |
|
|
1058
|
-
| \`_none\` | \`display:none\` |
|
|
1059
|
-
| \`_contents\` | \`display:contents\` |
|
|
1060
|
-
|
|
1061
|
-
#### Flexbox
|
|
1062
|
-
| Atom | CSS |
|
|
1063
|
-
|------|-----|
|
|
1064
|
-
| \`_col\` | \`flex-direction:column\` |
|
|
1065
|
-
| \`_row\` | \`flex-direction:row\` |
|
|
1066
|
-
| \`_colrev\` | \`flex-direction:column-reverse\` |
|
|
1067
|
-
| \`_wrap\` | \`flex-wrap:wrap\` |
|
|
1068
|
-
| \`_nowrap\` | \`flex-wrap:nowrap\` |
|
|
1069
|
-
| \`_flex1\` | \`flex:1\` |
|
|
1070
|
-
| \`_flex0\` | \`flex:none\` |
|
|
1071
|
-
| \`_flexauto\` | \`flex:auto\` |
|
|
1072
|
-
| \`_grow\` | \`flex-grow:1\` |
|
|
1073
|
-
| \`_grow0\` | \`flex-grow:0\` |
|
|
1074
|
-
| \`_shrink0\` | \`flex-shrink:0\` |
|
|
1075
|
-
|
|
1076
|
-
#### Alignment
|
|
1077
|
-
| Atom | CSS |
|
|
1078
|
-
|------|-----|
|
|
1079
|
-
| \`_aic\` | \`align-items:center\` |
|
|
1080
|
-
| \`_aifs\` | \`align-items:flex-start\` |
|
|
1081
|
-
| \`_aife\` | \`align-items:flex-end\` |
|
|
1082
|
-
| \`_aist\` | \`align-items:stretch\` |
|
|
1083
|
-
| \`_aibl\` | \`align-items:baseline\` |
|
|
1084
|
-
| \`_jcc\` | \`justify-content:center\` |
|
|
1085
|
-
| \`_jcfs\` | \`justify-content:flex-start\` |
|
|
1086
|
-
| \`_jcfe\` | \`justify-content:flex-end\` |
|
|
1087
|
-
| \`_jcsb\` | \`justify-content:space-between\` |
|
|
1088
|
-
| \`_jcsa\` | \`justify-content:space-around\` |
|
|
1089
|
-
| \`_jcse\` | \`justify-content:space-evenly\` |
|
|
1090
|
-
| \`_pic\` | \`place-items:center\` |
|
|
1091
|
-
| \`_pcc\` | \`place-content:center\` |
|
|
1092
|
-
|
|
1093
|
-
#### Spacing (scale: 0, 1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, ...)
|
|
1094
|
-
| Atom | CSS | Notes |
|
|
1095
|
-
|------|-----|-------|
|
|
1096
|
-
| \`_gap{n}\` | \`gap:{scale}\` | e.g. \`_gap4\` = \`gap:1rem\` |
|
|
1097
|
-
| \`_gx{n}\` | \`column-gap:{scale}\` | horizontal gap |
|
|
1098
|
-
| \`_gy{n}\` | \`row-gap:{scale}\` | vertical gap |
|
|
1099
|
-
| \`_p{n}\` | \`padding:{scale}\` | all sides |
|
|
1100
|
-
| \`_pt{n}\`, \`_pr{n}\`, \`_pb{n}\`, \`_pl{n}\` | directional padding | top/right/bottom/left |
|
|
1101
|
-
| \`_px{n}\` | \`padding-inline:{scale}\` | horizontal |
|
|
1102
|
-
| \`_py{n}\` | \`padding-block:{scale}\` | vertical |
|
|
1103
|
-
| \`_m{n}\` | \`margin:{scale}\` | same as padding variants |
|
|
1104
|
-
| \`_mx{n}\`, \`_my{n}\` | inline/block margin | horizontal/vertical |
|
|
1105
|
-
|
|
1106
|
-
#### Sizing
|
|
1107
|
-
| Atom | CSS |
|
|
1108
|
-
|------|-----|
|
|
1109
|
-
| \`_wfull\` / \`_w100\` | \`width:100%\` |
|
|
1110
|
-
| \`_hfull\` / \`_h100\` | \`height:100%\` |
|
|
1111
|
-
| \`_wscreen\` | \`width:100vw\` |
|
|
1112
|
-
| \`_hscreen\` | \`height:100vh\` |
|
|
1113
|
-
| \`_wfit\` | \`width:fit-content\` |
|
|
1114
|
-
| \`_hfit\` | \`height:fit-content\` |
|
|
1115
|
-
| \`_wauto\` | \`width:auto\` |
|
|
1116
|
-
| \`_minw0\` | \`min-width:0\` |
|
|
1117
|
-
| \`_minh0\` | \`min-height:0\` |
|
|
1118
|
-
| \`_w{n}\`, \`_h{n}\` | width/height from spacing scale |
|
|
1119
|
-
| \`_minw{n}\`, \`_maxw{n}\` | min/max width from scale |
|
|
1120
|
-
|
|
1121
|
-
#### Text Size
|
|
1122
|
-
| Atom | Size | Line-height |
|
|
1123
|
-
|------|------|-------------|
|
|
1124
|
-
| \`_textxs\` | 0.75rem | 1rem |
|
|
1125
|
-
| \`_textsm\` | 0.875rem | 1.25rem |
|
|
1126
|
-
| \`_textbase\` | 1rem | 1.5rem |
|
|
1127
|
-
| \`_textlg\` | 1.125rem | 1.75rem |
|
|
1128
|
-
| \`_textxl\` | 1.25rem | 1.75rem |
|
|
1129
|
-
| \`_text2xl\` | 1.5rem | 2rem |
|
|
1130
|
-
| \`_text3xl\` | 1.875rem | 2.25rem |
|
|
1131
|
-
| \`_heading1\`-\`_heading6\` | Heading presets (size + weight) |
|
|
1132
|
-
|
|
1133
|
-
#### Text Style
|
|
1134
|
-
| Atom | CSS |
|
|
1135
|
-
|------|-----|
|
|
1136
|
-
| \`_fontbold\` | \`font-weight:700\` |
|
|
1137
|
-
| \`_fontsemi\` | \`font-weight:600\` |
|
|
1138
|
-
| \`_fontmedium\` | \`font-weight:500\` |
|
|
1139
|
-
| \`_fontlight\` | \`font-weight:300\` |
|
|
1140
|
-
| \`_italic\` | \`font-style:italic\` |
|
|
1141
|
-
| \`_underline\` | \`text-decoration:underline\` |
|
|
1142
|
-
| \`_uppercase\` | \`text-transform:uppercase\` |
|
|
1143
|
-
| \`_truncate\` | overflow ellipsis + nowrap |
|
|
1144
|
-
| \`_textl\`, \`_textc\`, \`_textr\` | text-align left/center/right |
|
|
1145
|
-
|
|
1146
|
-
#### Color (theme variable based)
|
|
1147
|
-
| Atom | CSS |
|
|
1148
|
-
|------|-----|
|
|
1149
|
-
| \`_bgprimary\` | \`background:var(--d-primary)\` |
|
|
1150
|
-
| \`_bgsurface\` | \`background:var(--d-surface)\` |
|
|
1151
|
-
| \`_bgsurface0\`-\`_bgsurface2\` | surface elevation layers |
|
|
1152
|
-
| \`_bgmuted\` | \`background:var(--d-muted)\` |
|
|
1153
|
-
| \`_bgbg\` | \`background:var(--d-bg)\` |
|
|
1154
|
-
| \`_bgsuccess\`, \`_bgerror\`, \`_bgwarning\`, \`_bginfo\` | status backgrounds |
|
|
1155
|
-
| \`_fgprimary\` | \`color:var(--d-primary)\` |
|
|
1156
|
-
| \`_fgtext\` | \`color:var(--d-text)\` |
|
|
1157
|
-
| \`_fgmuted\` | \`color:var(--d-text-muted)\` |
|
|
1158
|
-
| \`_fgsuccess\`, \`_fgerror\`, \`_fgwarning\`, \`_fginfo\` | status text |
|
|
1159
|
-
| \`_bcborder\` | \`border-color:var(--d-border)\` |
|
|
1160
|
-
|
|
1161
|
-
#### Overflow & Whitespace
|
|
1162
|
-
| Atom | CSS |
|
|
1163
|
-
|------|-----|
|
|
1164
|
-
| \`_overhidden\` | \`overflow:hidden\` |
|
|
1165
|
-
| \`_overauto\` | \`overflow:auto\` |
|
|
1166
|
-
| \`_overscroll\` | \`overflow:scroll\` |
|
|
1167
|
-
| \`_overxauto\`, \`_overyauto\` | axis-specific overflow |
|
|
1168
|
-
| \`_nowraptext\` | \`white-space:nowrap\` |
|
|
1169
|
-
| \`_prewrap\` | \`white-space:pre-wrap\` |
|
|
1170
|
-
| \`_breakword\` | \`overflow-wrap:break-word\` |
|
|
1171
|
-
|
|
1172
|
-
#### Cursor & Interaction
|
|
1173
|
-
| Atom | CSS |
|
|
1174
|
-
|------|-----|
|
|
1175
|
-
| \`_pointer\` | \`cursor:pointer\` |
|
|
1176
|
-
| \`_cursordefault\` | \`cursor:default\` |
|
|
1177
|
-
| \`_notallowed\` | \`cursor:not-allowed\` |
|
|
1178
|
-
| \`_grab\` | \`cursor:grab\` |
|
|
1179
|
-
| \`_selectnone\` | \`user-select:none\` |
|
|
1180
|
-
| \`_ptrnone\` | \`pointer-events:none\` |
|
|
1181
|
-
|
|
1182
|
-
#### Position & Layout
|
|
1183
|
-
| Atom | CSS |
|
|
1184
|
-
|------|-----|
|
|
1185
|
-
| \`_rel\` | \`position:relative\` |
|
|
1186
|
-
| \`_abs\` | \`position:absolute\` |
|
|
1187
|
-
| \`_fixed\` | \`position:fixed\` |
|
|
1188
|
-
| \`_sticky\` | \`position:sticky\` |
|
|
1189
|
-
| \`_inset0\` | \`inset:0\` |
|
|
1190
|
-
| \`_top0\`, \`_right0\`, \`_bottom0\`, \`_left0\` | edge positioning |
|
|
1191
|
-
| \`_z10\`-\`_z50\` | z-index scale |
|
|
1192
|
-
|
|
1193
|
-
#### Grid
|
|
1194
|
-
| Atom | CSS |
|
|
1195
|
-
|------|-----|
|
|
1196
|
-
| \`_gc1\`-\`_gc12\` | \`grid-template-columns:repeat(N,...)\` |
|
|
1197
|
-
| \`_gr1\`-\`_gr6\` | \`grid-template-rows:repeat(N,...)\` |
|
|
1198
|
-
| \`_span1\`-\`_span12\`, \`_spanfull\` | column span |
|
|
1199
|
-
| \`_rowspan1\`-\`_rowspan6\` | row span |
|
|
1200
|
-
|
|
1201
|
-
#### Visual
|
|
1202
|
-
| Atom | CSS |
|
|
1203
|
-
|------|-----|
|
|
1204
|
-
| \`_rounded\` | \`border-radius:var(--d-radius)\` |
|
|
1205
|
-
| \`_roundedfull\` | \`border-radius:9999px\` |
|
|
1206
|
-
| \`_roundedsm\`, \`_roundedlg\`, \`_roundedxl\` | radius variants |
|
|
1207
|
-
| \`_shadow\`, \`_shadowmd\`, \`_shadowlg\` | box-shadow presets |
|
|
1208
|
-
| \`_bordernone\` | \`border:none\` |
|
|
1209
|
-
| \`_bw{n}\` | \`border-width:{n}px\` |
|
|
1210
|
-
| \`_op0\`-\`_op100\` | opacity (0, 25, 50, 75, 100) |
|
|
1211
|
-
| \`_trans\` | \`transition:all 0.15s ease\` |
|
|
1212
|
-
| \`_visible\`, \`_invisible\` | visibility |
|
|
1213
|
-
|
|
1214
|
-
### CSS Architecture
|
|
1215
|
-
|
|
1216
|
-
The CSS is organized into two parts:
|
|
1217
|
-
|
|
1218
|
-
1. **Atoms (@decantr/css)** - Layout utilities injected at runtime into \`@layer d.atoms\`
|
|
1219
|
-
2. **Generated CSS files** - Theme tokens and recipe decorators created during scaffold
|
|
1220
|
-
|
|
1221
|
-
\`\`\`
|
|
1222
|
-
src/styles/
|
|
1223
|
-
tokens.css # :root { --d-primary: #...; --d-surface: #...; }
|
|
1224
|
-
decorators.css # .recipe-card { ... }
|
|
1225
|
-
\`\`\`
|
|
1226
|
-
|
|
1227
|
-
### Variable Naming Convention
|
|
1228
|
-
|
|
1229
|
-
| Prefix | Purpose | Example |
|
|
1230
|
-
|--------|---------|---------|
|
|
1231
|
-
| \`--d-\` | Core Decantr tokens | \`--d-primary\`, \`--d-bg\` |
|
|
1232
|
-
| \`--d-gap-{n}\` | Spacing tokens | \`--d-gap-4\`, \`--d-gap-8\` |
|
|
1233
|
-
| \`--d-radius\` | Border radius | \`--d-radius\`, \`--d-radius-lg\` |`;
|
|
1234
|
-
function generateDecantrMdV31(guardMode, cssApproach) {
|
|
1235
|
-
const template = loadTemplate("DECANTR.md.template");
|
|
1236
|
-
return renderTemplate(template, {
|
|
1237
|
-
GUARD_MODE: guardMode,
|
|
1238
|
-
CSS_APPROACH: cssApproach
|
|
1239
|
-
});
|
|
1240
|
-
}
|
|
1241
|
-
function generateProjectJson(detected, options, registrySource) {
|
|
1242
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1243
|
-
const data = {
|
|
1244
|
-
detected: {
|
|
1245
|
-
framework: detected.framework,
|
|
1246
|
-
version: detected.version || null,
|
|
1247
|
-
packageManager: detected.packageManager,
|
|
1248
|
-
hasTypeScript: detected.hasTypeScript,
|
|
1249
|
-
hasTailwind: detected.hasTailwind,
|
|
1250
|
-
existingRuleFiles: detected.existingRuleFiles
|
|
1251
|
-
},
|
|
1252
|
-
overrides: {
|
|
1253
|
-
framework: options.target !== detected.framework ? options.target : null
|
|
1254
|
-
},
|
|
1255
|
-
sync: {
|
|
1256
|
-
status: registrySource === "api" ? "synced" : "needs-sync",
|
|
1257
|
-
lastSync: now,
|
|
1258
|
-
registrySource,
|
|
1259
|
-
cachedContent: {
|
|
1260
|
-
archetypes: [],
|
|
1261
|
-
patterns: [],
|
|
1262
|
-
themes: [],
|
|
1263
|
-
recipes: []
|
|
1264
|
-
}
|
|
1265
|
-
},
|
|
1266
|
-
initialized: {
|
|
1267
|
-
at: now,
|
|
1268
|
-
via: "cli",
|
|
1269
|
-
version: CLI_VERSION,
|
|
1270
|
-
flags: buildFlagsString(options)
|
|
1271
|
-
}
|
|
1272
|
-
};
|
|
1273
|
-
return JSON.stringify(data, null, 2);
|
|
1274
|
-
}
|
|
1275
|
-
function buildFlagsString(options) {
|
|
1276
|
-
const flags = [];
|
|
1277
|
-
if (options.blueprint) flags.push(`--blueprint=${options.blueprint}`);
|
|
1278
|
-
if (options.theme) flags.push(`--theme=${options.theme}`);
|
|
1279
|
-
if (options.mode) flags.push(`--mode=${options.mode}`);
|
|
1280
|
-
if (options.guard) flags.push(`--guard=${options.guard}`);
|
|
1281
|
-
return flags.join(" ");
|
|
1282
|
-
}
|
|
1283
|
-
function generateTaskContext(templateName, essence) {
|
|
1284
|
-
const template = loadTemplate(templateName);
|
|
1285
|
-
const defaultShell = essence.structure[0]?.shell || "sidebar-main";
|
|
1286
|
-
const layout = essence.structure[0]?.layout.map(serializeLayoutItem).join(", ") || "none";
|
|
1287
|
-
const scaffoldStructure = essence.structure.map((p) => {
|
|
1288
|
-
const patterns = p.layout.length > 0 ? `
|
|
1289
|
-
- Patterns: ${p.layout.map(serializeLayoutItem).join(", ")}` : "";
|
|
1290
|
-
return `- **${p.id}** (${p.shell})${patterns}`;
|
|
1291
|
-
}).join("\n");
|
|
1292
|
-
const vars = {
|
|
1293
|
-
TARGET: essence.target,
|
|
1294
|
-
THEME_STYLE: essence.theme.style,
|
|
1295
|
-
THEME_MODE: essence.theme.mode,
|
|
1296
|
-
THEME_RECIPE: essence.theme.recipe,
|
|
1297
|
-
DEFAULT_SHELL: defaultShell,
|
|
1298
|
-
GUARD_MODE: essence.guard.mode,
|
|
1299
|
-
LAYOUT: layout,
|
|
1300
|
-
DENSITY: essence.density.level,
|
|
1301
|
-
CONTENT_GAP: essence.density.content_gap,
|
|
1302
|
-
SCAFFOLD_STRUCTURE: scaffoldStructure
|
|
1303
|
-
};
|
|
1304
|
-
return renderTemplate(template, vars);
|
|
1305
|
-
}
|
|
1306
|
-
function generateEssenceSummary(essence) {
|
|
1307
|
-
const template = loadTemplate("essence-summary.md.template");
|
|
1308
|
-
const pagesTable = `| Page | Shell | Layout |
|
|
1309
|
-
|------|-------|--------|
|
|
1310
|
-
${essence.structure.map((p) => `| ${p.id} | ${p.shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`).join("\n")}`;
|
|
1311
|
-
const featuresList = essence.features.length > 0 ? essence.features.map((f) => `- ${f}`).join("\n") : "- No features specified";
|
|
1312
|
-
const dnaEnforcement = essence.guard.enforce_style ? "error" : "off";
|
|
1313
|
-
const blueprintEnforcement = essence.guard.enforce_recipe ? "warn" : "off";
|
|
1314
|
-
const vars = {
|
|
1315
|
-
ARCHETYPE: essence.archetype || "custom",
|
|
1316
|
-
BLUEPRINT: essence.blueprint || "none",
|
|
1317
|
-
PERSONALITY: essence.personality.join(", "),
|
|
1318
|
-
TARGET: essence.target,
|
|
1319
|
-
THEME_STYLE: essence.theme.style,
|
|
1320
|
-
THEME_MODE: essence.theme.mode,
|
|
1321
|
-
THEME_RECIPE: essence.theme.recipe,
|
|
1322
|
-
SHAPE: essence.theme.shape,
|
|
1323
|
-
PAGES_TABLE: pagesTable,
|
|
1324
|
-
FEATURES_LIST: featuresList,
|
|
1325
|
-
GUARD_MODE: essence.guard.mode,
|
|
1326
|
-
ENFORCE_STYLE: String(essence.guard.enforce_style),
|
|
1327
|
-
ENFORCE_RECIPE: String(essence.guard.enforce_recipe),
|
|
1328
|
-
DNA_ENFORCEMENT: dnaEnforcement,
|
|
1329
|
-
BLUEPRINT_ENFORCEMENT: blueprintEnforcement,
|
|
1330
|
-
DENSITY: essence.density.level,
|
|
1331
|
-
CONTENT_GAP: essence.density.content_gap,
|
|
1332
|
-
LAST_UPDATED: (/* @__PURE__ */ new Date()).toISOString()
|
|
1333
|
-
};
|
|
1334
|
-
return renderTemplate(template, vars);
|
|
1335
|
-
}
|
|
1336
|
-
function generateEssenceSummaryV3(essence) {
|
|
1337
|
-
const template = loadTemplate("essence-summary.md.template");
|
|
1338
|
-
const blueprint = essence.blueprint;
|
|
1339
|
-
const sections = blueprint.sections || [];
|
|
1340
|
-
const flatPages = blueprint.pages || [];
|
|
1341
|
-
let pagesTable;
|
|
1342
|
-
if (sections.length > 0) {
|
|
1343
|
-
const rows = sections.flatMap(
|
|
1344
|
-
(s) => s.pages.map((p) => `| ${p.id} | ${s.shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`)
|
|
1345
|
-
);
|
|
1346
|
-
pagesTable = `| Page | Shell | Layout |
|
|
1347
|
-
|------|-------|--------|
|
|
1348
|
-
${rows.join("\n")}`;
|
|
1349
|
-
} else {
|
|
1350
|
-
const shell = blueprint.shell ?? "sidebar-main";
|
|
1351
|
-
const rows = flatPages.map((p) => `| ${p.id} | ${shell} | ${p.layout.map(serializeLayoutItem).join(", ") || "none"} |`);
|
|
1352
|
-
pagesTable = `| Page | Shell | Layout |
|
|
1353
|
-
|------|-------|--------|
|
|
1354
|
-
${rows.join("\n")}`;
|
|
1355
|
-
}
|
|
1356
|
-
const features = blueprint.features || [];
|
|
1357
|
-
const featuresList = features.length > 0 ? features.map((f) => `- ${f}`).join("\n") : "- No features specified";
|
|
1358
|
-
const vars = {
|
|
1359
|
-
ARCHETYPE: essence.meta.archetype || "custom",
|
|
1360
|
-
BLUEPRINT: "",
|
|
1361
|
-
PERSONALITY: (essence.dna.personality || []).join(", "),
|
|
1362
|
-
TARGET: essence.meta.target ?? "",
|
|
1363
|
-
THEME_STYLE: essence.dna.theme.style,
|
|
1364
|
-
THEME_MODE: essence.dna.theme.mode,
|
|
1365
|
-
THEME_RECIPE: essence.dna.theme.recipe ?? "",
|
|
1366
|
-
SHAPE: essence.dna.theme.shape ?? "",
|
|
1367
|
-
PAGES_TABLE: pagesTable,
|
|
1368
|
-
FEATURES_LIST: featuresList,
|
|
1369
|
-
GUARD_MODE: essence.meta.guard.mode,
|
|
1370
|
-
ENFORCE_STYLE: essence.meta.guard.dna_enforcement || "error",
|
|
1371
|
-
ENFORCE_RECIPE: essence.meta.guard.blueprint_enforcement || "warn",
|
|
1372
|
-
DNA_ENFORCEMENT: essence.meta.guard.dna_enforcement || "error",
|
|
1373
|
-
BLUEPRINT_ENFORCEMENT: essence.meta.guard.blueprint_enforcement || "warn",
|
|
1374
|
-
DENSITY: essence.dna.spacing?.density || "comfortable",
|
|
1375
|
-
CONTENT_GAP: essence.dna.spacing?.content_gap || "_gap4",
|
|
1376
|
-
LAST_UPDATED: (/* @__PURE__ */ new Date()).toISOString()
|
|
1377
|
-
};
|
|
1378
|
-
return renderTemplate(template, vars);
|
|
1379
|
-
}
|
|
1380
|
-
function updateGitignore(projectRoot) {
|
|
1381
|
-
const gitignorePath = join2(projectRoot, ".gitignore");
|
|
1382
|
-
const cacheEntry = ".decantr/cache/";
|
|
1383
|
-
if (existsSync2(gitignorePath)) {
|
|
1384
|
-
const content = readFileSync2(gitignorePath, "utf-8");
|
|
1385
|
-
if (!content.includes(cacheEntry)) {
|
|
1386
|
-
appendFileSync(gitignorePath, `
|
|
1387
|
-
# Decantr cache
|
|
1388
|
-
${cacheEntry}
|
|
1389
|
-
`);
|
|
1390
|
-
return true;
|
|
1391
|
-
}
|
|
1392
|
-
return false;
|
|
1393
|
-
} else {
|
|
1394
|
-
writeFileSync(gitignorePath, `# Decantr cache
|
|
1395
|
-
${cacheEntry}
|
|
1396
|
-
`);
|
|
1397
|
-
return true;
|
|
1398
|
-
}
|
|
1399
|
-
}
|
|
1400
|
-
async function scaffoldProject(projectRoot, options, detected, registry, archetypeData, registrySource = "cache", themeData, recipeData, topologyMarkdown, composedSections, routeMap, patternSpecs, blueprintData) {
|
|
1401
|
-
const essenceV3 = buildEssenceV3(options, archetypeData, themeData, recipeData);
|
|
1402
|
-
const essence = buildEssence(options, archetypeData);
|
|
1403
|
-
const decantrDir = join2(projectRoot, ".decantr");
|
|
1404
|
-
const contextDir = join2(decantrDir, "context");
|
|
1405
|
-
const cacheDir = join2(decantrDir, "cache");
|
|
1406
|
-
mkdirSync(contextDir, { recursive: true });
|
|
1407
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
1408
|
-
const essencePath = join2(projectRoot, "decantr.essence.json");
|
|
1409
|
-
writeFileSync(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
|
|
1410
|
-
const projectJsonPath = join2(decantrDir, "project.json");
|
|
1411
|
-
writeFileSync(projectJsonPath, generateProjectJson(detected, options, registrySource));
|
|
1412
|
-
const contextFiles = [];
|
|
1413
|
-
const scaffoldPath = join2(contextDir, "task-scaffold.md");
|
|
1414
|
-
writeFileSync(scaffoldPath, generateTaskContext("task-scaffold.md.template", essence));
|
|
1415
|
-
contextFiles.push(scaffoldPath);
|
|
1416
|
-
const addPagePath = join2(contextDir, "task-add-page.md");
|
|
1417
|
-
writeFileSync(addPagePath, generateTaskContext("task-add-page.md.template", essence));
|
|
1418
|
-
contextFiles.push(addPagePath);
|
|
1419
|
-
const modifyPath = join2(contextDir, "task-modify.md");
|
|
1420
|
-
writeFileSync(modifyPath, generateTaskContext("task-modify.md.template", essence));
|
|
1421
|
-
contextFiles.push(modifyPath);
|
|
1422
|
-
const summaryPath = join2(contextDir, "essence-summary.md");
|
|
1423
|
-
writeFileSync(summaryPath, generateEssenceSummary(essence));
|
|
1424
|
-
contextFiles.push(summaryPath);
|
|
1425
|
-
if (composedSections) {
|
|
1426
|
-
essenceV3.version = "3.1.0";
|
|
1427
|
-
essenceV3.blueprint = {
|
|
1428
|
-
sections: composedSections.sections,
|
|
1429
|
-
features: composedSections.features,
|
|
1430
|
-
routes: routeMap || {}
|
|
1431
|
-
};
|
|
1432
|
-
if (blueprintData?.personality?.length) {
|
|
1433
|
-
essenceV3.dna.personality = blueprintData.personality;
|
|
1434
|
-
}
|
|
1435
|
-
if (blueprintData?.design_constraints) {
|
|
1436
|
-
essenceV3.dna.constraints = blueprintData.design_constraints;
|
|
1437
|
-
}
|
|
1438
|
-
if (blueprintData?.seo_hints) {
|
|
1439
|
-
essenceV3.meta.seo = blueprintData.seo_hints;
|
|
1440
|
-
}
|
|
1441
|
-
if (blueprintData?.navigation) {
|
|
1442
|
-
essenceV3.meta.navigation = blueprintData.navigation;
|
|
1443
|
-
}
|
|
1444
|
-
writeFileSync(essencePath, JSON.stringify(essenceV3, null, 2) + "\n");
|
|
1445
|
-
}
|
|
1446
|
-
const refreshResult = await refreshDerivedFiles(projectRoot, essenceV3, registry);
|
|
1447
|
-
contextFiles.push(...refreshResult.contextFiles);
|
|
1448
|
-
const gitignoreUpdated = updateGitignore(projectRoot);
|
|
1449
|
-
return {
|
|
1450
|
-
essencePath,
|
|
1451
|
-
decantrMdPath: refreshResult.decantrMdPath,
|
|
1452
|
-
projectJsonPath,
|
|
1453
|
-
contextFiles,
|
|
1454
|
-
cssFiles: refreshResult.cssFiles,
|
|
1455
|
-
gitignoreUpdated
|
|
1456
|
-
};
|
|
1457
|
-
}
|
|
1458
|
-
function scaffoldMinimal(projectRoot) {
|
|
1459
|
-
const decantrDir = join2(projectRoot, ".decantr");
|
|
1460
|
-
const customDir = join2(decantrDir, "custom");
|
|
1461
|
-
const contentTypes = ["patterns", "recipes", "themes", "blueprints", "archetypes", "shells"];
|
|
1462
|
-
for (const type of contentTypes) {
|
|
1463
|
-
mkdirSync(join2(customDir, type), { recursive: true });
|
|
1464
|
-
}
|
|
1465
|
-
const essence = {
|
|
1466
|
-
version: "3.0.0",
|
|
1467
|
-
dna: {
|
|
1468
|
-
theme: {
|
|
1469
|
-
style: "default",
|
|
1470
|
-
mode: "dark",
|
|
1471
|
-
recipe: "default",
|
|
1472
|
-
shape: "rounded"
|
|
1473
|
-
},
|
|
1474
|
-
spacing: {
|
|
1475
|
-
base_unit: 4,
|
|
1476
|
-
scale: "linear",
|
|
1477
|
-
density: "comfortable",
|
|
1478
|
-
content_gap: "_gap4"
|
|
1479
|
-
},
|
|
1480
|
-
typography: {
|
|
1481
|
-
scale: "modular",
|
|
1482
|
-
heading_weight: 600,
|
|
1483
|
-
body_weight: 400
|
|
1484
|
-
},
|
|
1485
|
-
color: {
|
|
1486
|
-
palette: "semantic",
|
|
1487
|
-
accent_count: 1,
|
|
1488
|
-
cvd_preference: "auto"
|
|
1489
|
-
},
|
|
1490
|
-
radius: {
|
|
1491
|
-
philosophy: "rounded",
|
|
1492
|
-
base: 8
|
|
1493
|
-
},
|
|
1494
|
-
elevation: {
|
|
1495
|
-
system: "layered",
|
|
1496
|
-
max_levels: 3
|
|
1497
|
-
},
|
|
1498
|
-
motion: {
|
|
1499
|
-
preference: "subtle",
|
|
1500
|
-
duration_scale: 1,
|
|
1501
|
-
reduce_motion: true
|
|
1502
|
-
},
|
|
1503
|
-
accessibility: {
|
|
1504
|
-
wcag_level: "AA",
|
|
1505
|
-
focus_visible: true,
|
|
1506
|
-
skip_nav: true
|
|
1507
|
-
},
|
|
1508
|
-
personality: ["clean", "modern"]
|
|
1509
|
-
},
|
|
1510
|
-
blueprint: {
|
|
1511
|
-
shell: "sidebar-main",
|
|
1512
|
-
pages: [
|
|
1513
|
-
{ id: "home", layout: ["hero"] }
|
|
1514
|
-
],
|
|
1515
|
-
features: []
|
|
1516
|
-
},
|
|
1517
|
-
meta: {
|
|
1518
|
-
archetype: "custom",
|
|
1519
|
-
target: "react",
|
|
1520
|
-
platform: {
|
|
1521
|
-
type: "spa",
|
|
1522
|
-
routing: "hash"
|
|
1523
|
-
},
|
|
1524
|
-
guard: {
|
|
1525
|
-
mode: "guided",
|
|
1526
|
-
dna_enforcement: "error",
|
|
1527
|
-
blueprint_enforcement: "off"
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
};
|
|
1531
|
-
const essencePath = join2(projectRoot, "decantr.essence.json");
|
|
1532
|
-
writeFileSync(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
1533
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1534
|
-
const projectJson = {
|
|
1535
|
-
detected: {
|
|
1536
|
-
framework: "unknown",
|
|
1537
|
-
version: null,
|
|
1538
|
-
packageManager: "npm",
|
|
1539
|
-
hasTypeScript: false,
|
|
1540
|
-
hasTailwind: false,
|
|
1541
|
-
existingRuleFiles: []
|
|
1542
|
-
},
|
|
1543
|
-
overrides: {
|
|
1544
|
-
framework: null
|
|
1545
|
-
},
|
|
1546
|
-
sync: {
|
|
1547
|
-
status: "needs-sync",
|
|
1548
|
-
lastSync: now,
|
|
1549
|
-
registrySource: "cache",
|
|
1550
|
-
cachedContent: {
|
|
1551
|
-
archetypes: [],
|
|
1552
|
-
patterns: [],
|
|
1553
|
-
themes: [],
|
|
1554
|
-
recipes: []
|
|
1555
|
-
}
|
|
1556
|
-
},
|
|
1557
|
-
initialized: {
|
|
1558
|
-
at: now,
|
|
1559
|
-
via: "cli",
|
|
1560
|
-
version: CLI_VERSION,
|
|
1561
|
-
flags: "--offline --minimal"
|
|
1562
|
-
}
|
|
1563
|
-
};
|
|
1564
|
-
const projectJsonPath = join2(decantrDir, "project.json");
|
|
1565
|
-
writeFileSync(projectJsonPath, JSON.stringify(projectJson, null, 2));
|
|
1566
|
-
const decantrMdPath = join2(projectRoot, "DECANTR.md");
|
|
1567
|
-
const decantrMdContent = `# DECANTR.md
|
|
1568
|
-
|
|
1569
|
-
> This file was generated by \`decantr init\` in offline/minimal mode.
|
|
1570
|
-
> Run \`decantr upgrade\` when online to pull full registry content.
|
|
1571
|
-
|
|
1572
|
-
## Two-Layer Model
|
|
1573
|
-
|
|
1574
|
-
This project uses the v3 Essence format with two layers:
|
|
1575
|
-
|
|
1576
|
-
### DNA (Immutable Design Axioms)
|
|
1577
|
-
DNA defines the foundational design rules that must never be violated. DNA violations are **errors**.
|
|
1578
|
-
|
|
1579
|
-
- **Theme:** default (dark mode)
|
|
1580
|
-
- **Spacing:** comfortable density, _gap4
|
|
1581
|
-
- **Radius:** rounded (8px base)
|
|
1582
|
-
- **Accessibility:** WCAG AA
|
|
1583
|
-
- **Personality:** clean, modern
|
|
1584
|
-
|
|
1585
|
-
### Blueprint (Structural Layout)
|
|
1586
|
-
Blueprint defines pages, shells, and patterns. Blueprint deviations are **warnings**.
|
|
1587
|
-
|
|
1588
|
-
| Page | Shell | Layout |
|
|
1589
|
-
|------|-------|--------|
|
|
1590
|
-
| home | sidebar-main | hero |
|
|
1591
|
-
|
|
1592
|
-
## Guard Mode: guided
|
|
1593
|
-
|
|
1594
|
-
- **DNA enforcement:** error (violations block generation)
|
|
1595
|
-
- **Blueprint enforcement:** off (deviations are advisory)
|
|
1596
|
-
|
|
1597
|
-
## MCP Tools
|
|
1598
|
-
|
|
1599
|
-
When available, use these tools:
|
|
1600
|
-
- \`decantr_read_essence\` \u2014 Read the current essence
|
|
1601
|
-
- \`decantr_check_drift\` \u2014 Check for guard violations
|
|
1602
|
-
- \`decantr_accept_drift\` \u2014 Accept a detected drift as intentional
|
|
1603
|
-
- \`decantr_update_essence\` \u2014 Update the essence spec
|
|
1604
|
-
|
|
1605
|
-
## Quick Start
|
|
1606
|
-
|
|
1607
|
-
1. Edit \`decantr.essence.json\` to define your project structure.
|
|
1608
|
-
2. Run \`decantr sync\` when online to fetch registry content.
|
|
1609
|
-
3. Use \`decantr create <type> <name>\` to create custom content.
|
|
1610
|
-
4. Use \`decantr validate\` to check your essence file.
|
|
1611
|
-
|
|
1612
|
-
## Commands
|
|
1613
|
-
|
|
1614
|
-
- \`decantr init\` \u2014 Initialize a new Decantr project
|
|
1615
|
-
- \`decantr status\` \u2014 Project health and DNA/Blueprint overview
|
|
1616
|
-
- \`decantr sync\` \u2014 Sync registry content
|
|
1617
|
-
- \`decantr audit\` \u2014 Audit project for issues
|
|
1618
|
-
- \`decantr migrate\` \u2014 Migrate v2 essence to v3
|
|
1619
|
-
- \`decantr check\` \u2014 Detect drift issues
|
|
1620
|
-
- \`decantr sync-drift\` \u2014 Review and resolve drift entries
|
|
1621
|
-
- \`decantr validate\` \u2014 Validate essence file
|
|
1622
|
-
- \`decantr search\` \u2014 Search the registry
|
|
1623
|
-
- \`decantr suggest\` \u2014 Suggest patterns or alternatives
|
|
1624
|
-
- \`decantr get\` \u2014 Get full details of a registry item
|
|
1625
|
-
- \`decantr list\` \u2014 List items by type
|
|
1626
|
-
- \`decantr theme\` \u2014 Manage custom themes
|
|
1627
|
-
- \`decantr create\` \u2014 Create custom content items
|
|
1628
|
-
- \`decantr publish\` \u2014 Publish custom content to registry
|
|
1629
|
-
- \`decantr login\` \u2014 Authenticate with registry
|
|
1630
|
-
- \`decantr logout\` \u2014 Remove stored credentials
|
|
1631
|
-
- \`decantr upgrade\` \u2014 Check for content updates
|
|
1632
|
-
|
|
1633
|
-
---
|
|
1634
|
-
|
|
1635
|
-
*Generated by @decantr/cli v${CLI_VERSION}*
|
|
1636
|
-
`;
|
|
1637
|
-
writeFileSync(decantrMdPath, decantrMdContent);
|
|
1638
|
-
const gitignoreUpdated = updateGitignore(projectRoot);
|
|
1639
|
-
return {
|
|
1640
|
-
essencePath,
|
|
1641
|
-
decantrMdPath,
|
|
1642
|
-
projectJsonPath,
|
|
1643
|
-
contextFiles: [],
|
|
1644
|
-
cssFiles: [],
|
|
1645
|
-
gitignoreUpdated
|
|
1646
|
-
};
|
|
1647
|
-
}
|
|
1648
|
-
async function refreshDerivedFiles(projectRoot, essence, registry) {
|
|
1649
|
-
const decantrDir = join2(projectRoot, ".decantr");
|
|
1650
|
-
const contextDir = join2(decantrDir, "context");
|
|
1651
|
-
mkdirSync(contextDir, { recursive: true });
|
|
1652
|
-
const themeName = essence.dna.theme.style;
|
|
1653
|
-
const recipeName = essence.dna.theme.recipe ?? themeName;
|
|
1654
|
-
const mode = essence.dna.theme.mode;
|
|
1655
|
-
const guardMode = essence.meta.guard.mode;
|
|
1656
|
-
const guardConfig = {
|
|
1657
|
-
mode: guardMode,
|
|
1658
|
-
dna_enforcement: essence.meta.guard.dna_enforcement || "error",
|
|
1659
|
-
blueprint_enforcement: essence.meta.guard.blueprint_enforcement || "warn"
|
|
1660
|
-
};
|
|
1661
|
-
const personality = essence.dna.personality || [];
|
|
1662
|
-
let themeData;
|
|
1663
|
-
let recipeData;
|
|
1664
|
-
try {
|
|
1665
|
-
const themeResult = await registry.fetchTheme(themeName);
|
|
1666
|
-
if (themeResult?.data) {
|
|
1667
|
-
const t = themeResult.data;
|
|
1668
|
-
themeData = {
|
|
1669
|
-
seed: t.seed,
|
|
1670
|
-
palette: t.palette,
|
|
1671
|
-
cvd_support: t.cvd_support,
|
|
1672
|
-
tokens: t.tokens,
|
|
1673
|
-
typography_hints: t.typography_hints,
|
|
1674
|
-
motion_hints: t.motion_hints
|
|
1675
|
-
};
|
|
1676
|
-
}
|
|
1677
|
-
} catch {
|
|
1678
|
-
}
|
|
1679
|
-
try {
|
|
1680
|
-
const recipeResult = await registry.fetchRecipe(recipeName);
|
|
1681
|
-
if (recipeResult?.data) {
|
|
1682
|
-
const r = recipeResult.data;
|
|
1683
|
-
recipeData = {
|
|
1684
|
-
decorators: r.decorators,
|
|
1685
|
-
spatial_hints: r.spatial_hints,
|
|
1686
|
-
radius_hints: r.radius_hints
|
|
1687
|
-
};
|
|
1688
|
-
}
|
|
1689
|
-
} catch {
|
|
1690
|
-
}
|
|
1691
|
-
const stylesDir = join2(projectRoot, "src", "styles");
|
|
1692
|
-
mkdirSync(stylesDir, { recursive: true });
|
|
1693
|
-
const tokensPath = join2(stylesDir, "tokens.css");
|
|
1694
|
-
const hasRealThemeData = themeData?.seed?.primary || themeData?.palette?.background;
|
|
1695
|
-
if (hasRealThemeData || !existsSync2(tokensPath)) {
|
|
1696
|
-
writeFileSync(tokensPath, generateTokensCSS(themeData, mode));
|
|
1697
|
-
}
|
|
1698
|
-
const decoratorsPath = join2(stylesDir, "decorators.css");
|
|
1699
|
-
const hasRealRecipeData = recipeData?.decorators && Object.keys(recipeData.decorators).length > 0;
|
|
1700
|
-
if (hasRealRecipeData || !existsSync2(decoratorsPath)) {
|
|
1701
|
-
writeFileSync(decoratorsPath, generateDecoratorsCSS(recipeData, themeName));
|
|
1702
|
-
}
|
|
1703
|
-
const cssFiles = [tokensPath, decoratorsPath];
|
|
1704
|
-
const decantrMdPath = join2(projectRoot, "DECANTR.md");
|
|
1705
|
-
writeFileSync(decantrMdPath, generateDecantrMdV31(guardMode, CSS_APPROACH_CONTENT));
|
|
1706
|
-
const summaryPath = join2(contextDir, "essence-summary.md");
|
|
1707
|
-
writeFileSync(summaryPath, generateEssenceSummaryV3(essence));
|
|
1708
|
-
const contextFiles = [summaryPath];
|
|
1709
|
-
const blueprint = essence.blueprint;
|
|
1710
|
-
const sections = blueprint.sections && blueprint.sections.length > 0 ? blueprint.sections : [];
|
|
1711
|
-
if (sections.length > 0) {
|
|
1712
|
-
const patternSpecs = {};
|
|
1713
|
-
const seenPatterns = /* @__PURE__ */ new Set();
|
|
1714
|
-
for (const section of sections) {
|
|
1715
|
-
for (const page of section.pages) {
|
|
1716
|
-
for (const item of page.layout) {
|
|
1717
|
-
const names = extractPatternNames(item);
|
|
1718
|
-
for (const name of names) {
|
|
1719
|
-
if (!seenPatterns.has(name)) {
|
|
1720
|
-
seenPatterns.add(name);
|
|
1721
|
-
try {
|
|
1722
|
-
const patResult = await registry.fetchPattern(name);
|
|
1723
|
-
if (patResult?.data) {
|
|
1724
|
-
const raw = patResult.data;
|
|
1725
|
-
const inner = raw.data ?? raw;
|
|
1726
|
-
const defaultPreset = inner.default_preset || "standard";
|
|
1727
|
-
const preset = inner.presets?.[defaultPreset];
|
|
1728
|
-
patternSpecs[name] = {
|
|
1729
|
-
description: inner.description || "",
|
|
1730
|
-
components: inner.components || [],
|
|
1731
|
-
slots: preset?.layout?.slots || {}
|
|
1732
|
-
};
|
|
1733
|
-
}
|
|
1734
|
-
} catch {
|
|
1735
|
-
}
|
|
1736
|
-
}
|
|
1737
|
-
}
|
|
1738
|
-
}
|
|
1739
|
-
}
|
|
1740
|
-
}
|
|
1741
|
-
const zoneInputs = sections.map((s) => ({
|
|
1742
|
-
archetypeId: s.id,
|
|
1743
|
-
role: s.role,
|
|
1744
|
-
shell: s.shell,
|
|
1745
|
-
features: s.features,
|
|
1746
|
-
description: s.description
|
|
1747
|
-
}));
|
|
1748
|
-
const zones = deriveZones(zoneInputs);
|
|
1749
|
-
const transitions = deriveTransitions(zones);
|
|
1750
|
-
const hasPublic = zones.some((z) => z.role === "public");
|
|
1751
|
-
const hasPrimary = zones.some((z) => z.role === "primary");
|
|
1752
|
-
const topologyData = {
|
|
1753
|
-
intent: sections.map((s) => s.id).join(" + "),
|
|
1754
|
-
zones,
|
|
1755
|
-
transitions,
|
|
1756
|
-
entryPoints: {
|
|
1757
|
-
anonymous: hasPublic ? "public zone" : "gateway",
|
|
1758
|
-
authenticated: hasPrimary ? "primary zone" : "first section"
|
|
1759
|
-
}
|
|
1760
|
-
};
|
|
1761
|
-
const topologyMarkdown = generateTopologySection(topologyData, personality);
|
|
1762
|
-
const themeTokensCss = existsSync2(tokensPath) ? readFileSync2(tokensPath, "utf-8") : "";
|
|
1763
|
-
const decoratorList = [];
|
|
1764
|
-
if (recipeData?.decorators) {
|
|
1765
|
-
for (const [name, desc] of Object.entries(recipeData.decorators)) {
|
|
1766
|
-
decoratorList.push({ name, description: desc });
|
|
1767
|
-
}
|
|
1768
|
-
} else if (existsSync2(decoratorsPath)) {
|
|
1769
|
-
const decoratorsCss = readFileSync2(decoratorsPath, "utf-8");
|
|
1770
|
-
const classRegex = /\/\*\s*(.+?)\s*\*\/\s*\n\s*\.([\w-]+)\s*\{/g;
|
|
1771
|
-
let match;
|
|
1772
|
-
while ((match = classRegex.exec(decoratorsCss)) !== null) {
|
|
1773
|
-
decoratorList.push({ name: match[2], description: match[1] });
|
|
1774
|
-
}
|
|
1775
|
-
if (decoratorList.length === 0) {
|
|
1776
|
-
const simpleClassRegex = /^\.([\w-]+)\s*\{/gm;
|
|
1777
|
-
while ((match = simpleClassRegex.exec(decoratorsCss)) !== null) {
|
|
1778
|
-
decoratorList.push({ name: match[1], description: "" });
|
|
1779
|
-
}
|
|
1780
|
-
}
|
|
1781
|
-
}
|
|
1782
|
-
const shellInfoCache = {};
|
|
1783
|
-
const seenShells = /* @__PURE__ */ new Set();
|
|
1784
|
-
for (const section of sections) {
|
|
1785
|
-
const shellId = section.shell;
|
|
1786
|
-
if (!seenShells.has(shellId)) {
|
|
1787
|
-
seenShells.add(shellId);
|
|
1788
|
-
try {
|
|
1789
|
-
const shellResult = await registry.fetchShell(shellId);
|
|
1790
|
-
if (shellResult?.data) {
|
|
1791
|
-
const raw = shellResult.data;
|
|
1792
|
-
const inner = raw.data ?? raw;
|
|
1793
|
-
shellInfoCache[shellId] = {
|
|
1794
|
-
description: inner.description || "",
|
|
1795
|
-
regions: inner.config?.regions || [],
|
|
1796
|
-
layout: inner.layout || void 0
|
|
1797
|
-
};
|
|
1798
|
-
}
|
|
1799
|
-
} catch {
|
|
1800
|
-
}
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
for (const section of sections) {
|
|
1804
|
-
const zoneLabel = section.role === "primary" || section.role === "auxiliary" ? "App" : section.role.charAt(0).toUpperCase() + section.role.slice(1);
|
|
1805
|
-
const zoneContext = `This section is in the **${zoneLabel}** zone (${section.shell} shell).` + (topologyMarkdown ? "\n\n" + topologyMarkdown : "");
|
|
1806
|
-
const sectionPatterns = {};
|
|
1807
|
-
for (const page of section.pages) {
|
|
1808
|
-
for (const item of page.layout) {
|
|
1809
|
-
const names = extractPatternNames(item);
|
|
1810
|
-
for (const name of names) {
|
|
1811
|
-
if (patternSpecs[name]) {
|
|
1812
|
-
sectionPatterns[name] = patternSpecs[name];
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
const contextContent = generateSectionContext({
|
|
1818
|
-
section,
|
|
1819
|
-
themeTokens: themeTokensCss,
|
|
1820
|
-
decorators: decoratorList,
|
|
1821
|
-
guardConfig,
|
|
1822
|
-
personality,
|
|
1823
|
-
themeName,
|
|
1824
|
-
recipeName,
|
|
1825
|
-
zoneContext,
|
|
1826
|
-
patternSpecs: sectionPatterns,
|
|
1827
|
-
constraints: essence.dna.constraints,
|
|
1828
|
-
shellInfo: shellInfoCache[section.shell]
|
|
1829
|
-
});
|
|
1830
|
-
const sectionContextPath = join2(contextDir, `section-${section.id}.md`);
|
|
1831
|
-
writeFileSync(sectionContextPath, contextContent);
|
|
1832
|
-
contextFiles.push(sectionContextPath);
|
|
1833
|
-
}
|
|
1834
|
-
const routes = blueprint.routes || {};
|
|
1835
|
-
const scaffoldContent = generateScaffoldContext({
|
|
1836
|
-
appName: essence.meta.archetype || "Application",
|
|
1837
|
-
blueprintId: "",
|
|
1838
|
-
themeName,
|
|
1839
|
-
recipeName,
|
|
1840
|
-
personality,
|
|
1841
|
-
topologyMarkdown,
|
|
1842
|
-
sections,
|
|
1843
|
-
routes,
|
|
1844
|
-
constraints: essence.dna.constraints,
|
|
1845
|
-
seo: essence.meta.seo,
|
|
1846
|
-
navigation: essence.meta.navigation
|
|
1847
|
-
});
|
|
1848
|
-
const scaffoldMdPath = join2(contextDir, "scaffold.md");
|
|
1849
|
-
writeFileSync(scaffoldMdPath, scaffoldContent);
|
|
1850
|
-
contextFiles.push(scaffoldMdPath);
|
|
1851
|
-
} else {
|
|
1852
|
-
const pages = blueprint.pages || [{ id: "home", layout: ["hero"] }];
|
|
1853
|
-
const shell = blueprint.shell ?? "sidebar-main";
|
|
1854
|
-
const syntheticSection = {
|
|
1855
|
-
id: essence.meta.archetype || "default",
|
|
1856
|
-
role: "primary",
|
|
1857
|
-
shell,
|
|
1858
|
-
features: blueprint.features || [],
|
|
1859
|
-
description: `${essence.meta.archetype || "Application"} section`,
|
|
1860
|
-
pages
|
|
1861
|
-
};
|
|
1862
|
-
const patternSpecs = {};
|
|
1863
|
-
const seenPatterns = /* @__PURE__ */ new Set();
|
|
1864
|
-
for (const page of pages) {
|
|
1865
|
-
for (const item of page.layout) {
|
|
1866
|
-
const names = extractPatternNames(item);
|
|
1867
|
-
for (const name of names) {
|
|
1868
|
-
if (!seenPatterns.has(name)) {
|
|
1869
|
-
seenPatterns.add(name);
|
|
1870
|
-
try {
|
|
1871
|
-
const patResult = await registry.fetchPattern(name);
|
|
1872
|
-
if (patResult?.data) {
|
|
1873
|
-
const raw = patResult.data;
|
|
1874
|
-
const inner = raw.data ?? raw;
|
|
1875
|
-
const defaultPreset = inner.default_preset || "standard";
|
|
1876
|
-
const preset = inner.presets?.[defaultPreset];
|
|
1877
|
-
patternSpecs[name] = {
|
|
1878
|
-
description: inner.description || "",
|
|
1879
|
-
components: inner.components || [],
|
|
1880
|
-
slots: preset?.layout?.slots || {}
|
|
1881
|
-
};
|
|
1882
|
-
}
|
|
1883
|
-
} catch {
|
|
1884
|
-
}
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
const themeTokensCss = existsSync2(tokensPath) ? readFileSync2(tokensPath, "utf-8") : "";
|
|
1890
|
-
const decoratorList = [];
|
|
1891
|
-
if (recipeData?.decorators) {
|
|
1892
|
-
for (const [name, desc] of Object.entries(recipeData.decorators)) {
|
|
1893
|
-
decoratorList.push({ name, description: desc });
|
|
1894
|
-
}
|
|
1895
|
-
} else if (existsSync2(decoratorsPath)) {
|
|
1896
|
-
const decoratorsCss = readFileSync2(decoratorsPath, "utf-8");
|
|
1897
|
-
const classRegex = /\/\*\s*(.+?)\s*\*\/\s*\n\s*\.([\w-]+)\s*\{/g;
|
|
1898
|
-
let match;
|
|
1899
|
-
while ((match = classRegex.exec(decoratorsCss)) !== null) {
|
|
1900
|
-
decoratorList.push({ name: match[2], description: match[1] });
|
|
1901
|
-
}
|
|
1902
|
-
if (decoratorList.length === 0) {
|
|
1903
|
-
const simpleClassRegex = /^\.([\w-]+)\s*\{/gm;
|
|
1904
|
-
while ((match = simpleClassRegex.exec(decoratorsCss)) !== null) {
|
|
1905
|
-
decoratorList.push({ name: match[1], description: "" });
|
|
1906
|
-
}
|
|
1907
|
-
}
|
|
1908
|
-
}
|
|
1909
|
-
let v30ShellInfo;
|
|
1910
|
-
try {
|
|
1911
|
-
const shellResult = await registry.fetchShell(shell);
|
|
1912
|
-
if (shellResult?.data) {
|
|
1913
|
-
const raw = shellResult.data;
|
|
1914
|
-
const inner = raw.data ?? raw;
|
|
1915
|
-
v30ShellInfo = {
|
|
1916
|
-
description: inner.description || "",
|
|
1917
|
-
regions: inner.config?.regions || [],
|
|
1918
|
-
layout: inner.layout || void 0
|
|
1919
|
-
};
|
|
1920
|
-
}
|
|
1921
|
-
} catch {
|
|
1922
|
-
}
|
|
1923
|
-
const contextContent = generateSectionContext({
|
|
1924
|
-
section: syntheticSection,
|
|
1925
|
-
themeTokens: themeTokensCss,
|
|
1926
|
-
decorators: decoratorList,
|
|
1927
|
-
guardConfig,
|
|
1928
|
-
personality,
|
|
1929
|
-
themeName,
|
|
1930
|
-
recipeName,
|
|
1931
|
-
zoneContext: `This is the primary section (${shell} shell).`,
|
|
1932
|
-
patternSpecs,
|
|
1933
|
-
constraints: essence.dna.constraints,
|
|
1934
|
-
shellInfo: v30ShellInfo
|
|
1935
|
-
});
|
|
1936
|
-
const sectionContextPath = join2(contextDir, `section-${syntheticSection.id}.md`);
|
|
1937
|
-
writeFileSync(sectionContextPath, contextContent);
|
|
1938
|
-
contextFiles.push(sectionContextPath);
|
|
1939
|
-
}
|
|
1940
|
-
return {
|
|
1941
|
-
decantrMdPath,
|
|
1942
|
-
contextFiles,
|
|
1943
|
-
cssFiles
|
|
1944
|
-
};
|
|
1945
|
-
}
|
|
1946
|
-
function generateSectionContext(input) {
|
|
1947
|
-
const { section, themeTokens, decorators, guardConfig, personality, themeName, recipeName, zoneContext, patternSpecs, recipeHints, constraints, shellInfo } = input;
|
|
1948
|
-
const lines = [];
|
|
1949
|
-
lines.push(`# Section: ${section.id}`);
|
|
1950
|
-
lines.push("");
|
|
1951
|
-
lines.push(`**Role:** ${section.role} | **Shell:** ${section.shell} | **Archetype:** ${section.id}`);
|
|
1952
|
-
lines.push(`**Description:** ${section.description}`);
|
|
1953
|
-
if (shellInfo) {
|
|
1954
|
-
lines.push(`**Shell structure:** ${shellInfo.description}`);
|
|
1955
|
-
lines.push(`**Regions:** ${shellInfo.regions.join(", ")}`);
|
|
1956
|
-
}
|
|
1957
|
-
lines.push("");
|
|
1958
|
-
lines.push("---");
|
|
1959
|
-
lines.push("");
|
|
1960
|
-
lines.push("## Guard Rules");
|
|
1961
|
-
lines.push("");
|
|
1962
|
-
lines.push(`| Rule | Scope | Severity | Description |`);
|
|
1963
|
-
lines.push(`|------|-------|----------|-------------|`);
|
|
1964
|
-
lines.push(`| Style guard | DNA | ${guardConfig.dna_enforcement} | Code must use the ${themeName} theme |`);
|
|
1965
|
-
lines.push(`| Recipe guard | DNA | ${guardConfig.dna_enforcement} | Visual recipe must match ${recipeName} |`);
|
|
1966
|
-
lines.push(`| Density guard | DNA | ${guardConfig.dna_enforcement} | Content gap must match essence density |`);
|
|
1967
|
-
lines.push(`| Accessibility guard | DNA | ${guardConfig.dna_enforcement} | Must meet WCAG level from essence |`);
|
|
1968
|
-
lines.push(`| Structure guard | Blueprint | ${guardConfig.blueprint_enforcement} | Pages must exist in essence structure |`);
|
|
1969
|
-
lines.push(`| Layout guard | Blueprint | ${guardConfig.blueprint_enforcement} | Pattern order must match essence layout |`);
|
|
1970
|
-
lines.push(`| Pattern existence | Blueprint | ${guardConfig.blueprint_enforcement} | All patterns must exist in registry |`);
|
|
1971
|
-
lines.push("");
|
|
1972
|
-
lines.push(`**Guard mode:** ${guardConfig.mode}`);
|
|
1973
|
-
lines.push("");
|
|
1974
|
-
lines.push("---");
|
|
1975
|
-
lines.push("");
|
|
1976
|
-
lines.push(`## Theme: ${themeName}`);
|
|
1977
|
-
lines.push("");
|
|
1978
|
-
lines.push("```css");
|
|
1979
|
-
lines.push(themeTokens);
|
|
1980
|
-
lines.push("```");
|
|
1981
|
-
lines.push("");
|
|
1982
|
-
lines.push("---");
|
|
1983
|
-
lines.push("");
|
|
1984
|
-
lines.push(`## Decorators (${recipeName} recipe)`);
|
|
1985
|
-
lines.push("");
|
|
1986
|
-
if (decorators.length > 0) {
|
|
1987
|
-
lines.push("| Decorator | Description |");
|
|
1988
|
-
lines.push("|-----------|-------------|");
|
|
1989
|
-
for (const dec of decorators) {
|
|
1990
|
-
lines.push(`| ${dec.name} | ${dec.description} |`);
|
|
1991
|
-
}
|
|
1992
|
-
} else {
|
|
1993
|
-
lines.push("No decorators defined.");
|
|
1994
|
-
}
|
|
1995
|
-
lines.push("");
|
|
1996
|
-
if (recipeHints) {
|
|
1997
|
-
if (recipeHints.preferred && recipeHints.preferred.length > 0) {
|
|
1998
|
-
lines.push(`**Preferred:** ${recipeHints.preferred.join(", ")}`);
|
|
1999
|
-
}
|
|
2000
|
-
if (recipeHints.compositions) {
|
|
2001
|
-
lines.push(`**Compositions:** ${recipeHints.compositions}`);
|
|
2002
|
-
}
|
|
2003
|
-
if (recipeHints.spatialHints) {
|
|
2004
|
-
lines.push(`**Spatial hints:** ${recipeHints.spatialHints}`);
|
|
2005
|
-
}
|
|
2006
|
-
lines.push("");
|
|
2007
|
-
}
|
|
2008
|
-
lines.push("---");
|
|
2009
|
-
lines.push("");
|
|
2010
|
-
if (zoneContext) {
|
|
2011
|
-
lines.push("## Zone Context");
|
|
2012
|
-
lines.push("");
|
|
2013
|
-
lines.push(zoneContext);
|
|
2014
|
-
lines.push("");
|
|
2015
|
-
lines.push("---");
|
|
2016
|
-
lines.push("");
|
|
2017
|
-
}
|
|
2018
|
-
if (section.features.length > 0) {
|
|
2019
|
-
lines.push("## Features");
|
|
2020
|
-
lines.push("");
|
|
2021
|
-
lines.push(section.features.join(", "));
|
|
2022
|
-
lines.push("");
|
|
2023
|
-
lines.push("---");
|
|
2024
|
-
lines.push("");
|
|
2025
|
-
}
|
|
2026
|
-
if (personality.length > 0) {
|
|
2027
|
-
lines.push("## Personality");
|
|
2028
|
-
lines.push("");
|
|
2029
|
-
lines.push(personality.join(", "));
|
|
2030
|
-
lines.push("");
|
|
2031
|
-
lines.push("---");
|
|
2032
|
-
lines.push("");
|
|
2033
|
-
}
|
|
2034
|
-
if (constraints && Object.keys(constraints).length > 0) {
|
|
2035
|
-
lines.push("## Constraints");
|
|
2036
|
-
lines.push("");
|
|
2037
|
-
for (const [key, value] of Object.entries(constraints)) {
|
|
2038
|
-
lines.push(`- **${key}:** ${typeof value === "object" && value !== null ? JSON.stringify(value) : value}`);
|
|
2039
|
-
}
|
|
2040
|
-
lines.push("");
|
|
2041
|
-
lines.push("---");
|
|
2042
|
-
lines.push("");
|
|
2043
|
-
}
|
|
2044
|
-
lines.push("## Pages");
|
|
2045
|
-
lines.push("");
|
|
2046
|
-
for (const page of section.pages) {
|
|
2047
|
-
const route = page.route || `/${section.id}/${page.id}`;
|
|
2048
|
-
const layoutStr = page.layout.map(serializeLayoutItem).join(" \u2192 ");
|
|
2049
|
-
lines.push(`### ${page.id} (${route})`);
|
|
2050
|
-
lines.push("");
|
|
2051
|
-
lines.push(`Layout: ${layoutStr}`);
|
|
2052
|
-
lines.push("");
|
|
2053
|
-
if (page.patterns && page.patterns.length > 0) {
|
|
2054
|
-
lines.push("**Pattern mapping:**");
|
|
2055
|
-
lines.push("| Alias | Pattern | Preset |");
|
|
2056
|
-
lines.push("|-------|---------|--------|");
|
|
2057
|
-
for (const ref of page.patterns) {
|
|
2058
|
-
const alias = ref.as || ref.pattern;
|
|
2059
|
-
const preset = ref.preset || "standard";
|
|
2060
|
-
lines.push(`| ${alias} | ${ref.pattern} | ${preset} |`);
|
|
2061
|
-
}
|
|
2062
|
-
lines.push("");
|
|
2063
|
-
}
|
|
2064
|
-
const patternNames = page.layout.flatMap(extractPatternNames);
|
|
2065
|
-
for (const patternName of patternNames) {
|
|
2066
|
-
const spec = patternSpecs[patternName];
|
|
2067
|
-
if (!spec) continue;
|
|
2068
|
-
lines.push(`#### Pattern: ${patternName}`);
|
|
2069
|
-
lines.push("");
|
|
2070
|
-
lines.push(spec.description);
|
|
2071
|
-
lines.push("");
|
|
2072
|
-
lines.push(`**Components:** ${spec.components.join(", ")}`);
|
|
2073
|
-
lines.push("");
|
|
2074
|
-
lines.push("**Layout slots:**");
|
|
2075
|
-
for (const [slot, desc] of Object.entries(spec.slots)) {
|
|
2076
|
-
lines.push(`- \`${slot}\`: ${desc}`);
|
|
2077
|
-
}
|
|
2078
|
-
lines.push("");
|
|
2079
|
-
}
|
|
2080
|
-
}
|
|
2081
|
-
return lines.join("\n");
|
|
2082
|
-
}
|
|
2083
|
-
function generateScaffoldContext(input) {
|
|
2084
|
-
const { appName, blueprintId, themeName, recipeName, personality, topologyMarkdown, sections, routes, constraints, seo, navigation } = input;
|
|
2085
|
-
const lines = [];
|
|
2086
|
-
lines.push(`# Scaffold: ${appName}`);
|
|
2087
|
-
lines.push("");
|
|
2088
|
-
lines.push(`**Blueprint:** ${blueprintId}`);
|
|
2089
|
-
lines.push(`**Theme:** ${themeName} | **Recipe:** ${recipeName}`);
|
|
2090
|
-
lines.push(`**Personality:** ${personality.join(", ")}`);
|
|
2091
|
-
lines.push("**Guard mode:** creative (no enforcement during initial scaffolding)");
|
|
2092
|
-
lines.push("");
|
|
2093
|
-
lines.push("## App Topology");
|
|
2094
|
-
lines.push("");
|
|
2095
|
-
lines.push(topologyMarkdown);
|
|
2096
|
-
lines.push("");
|
|
2097
|
-
lines.push("## Sections Overview");
|
|
2098
|
-
lines.push("");
|
|
2099
|
-
lines.push("| Section | Role | Shell | Pages | Features |");
|
|
2100
|
-
lines.push("|---------|------|-------|-------|----------|");
|
|
2101
|
-
for (const section of sections) {
|
|
2102
|
-
const pageIds = section.pages.map((p) => p.id).join(", ");
|
|
2103
|
-
const feats = section.features.join(", ") || "none";
|
|
2104
|
-
lines.push(`| ${section.id} | ${section.role} | ${section.shell} | ${pageIds} | ${feats} |`);
|
|
2105
|
-
}
|
|
2106
|
-
lines.push("");
|
|
2107
|
-
lines.push("## Route Map");
|
|
2108
|
-
lines.push("");
|
|
2109
|
-
lines.push("| Route | Section | Page |");
|
|
2110
|
-
lines.push("|-------|---------|------|");
|
|
2111
|
-
for (const [route, entry] of Object.entries(routes)) {
|
|
2112
|
-
lines.push(`| ${route} | ${entry.section} | ${entry.page} |`);
|
|
2113
|
-
}
|
|
2114
|
-
lines.push("");
|
|
2115
|
-
lines.push("## Section Contexts");
|
|
2116
|
-
lines.push("");
|
|
2117
|
-
lines.push("For detailed pattern specs per section, read:");
|
|
2118
|
-
for (const section of sections) {
|
|
2119
|
-
lines.push(`- .decantr/context/section-${section.id}.md`);
|
|
2120
|
-
}
|
|
2121
|
-
lines.push("");
|
|
2122
|
-
const patternUsage = {};
|
|
2123
|
-
for (const section of sections) {
|
|
2124
|
-
for (const page of section.pages) {
|
|
2125
|
-
const names = page.layout.flatMap(extractPatternNames);
|
|
2126
|
-
for (const name of names) {
|
|
2127
|
-
if (!patternUsage[name]) patternUsage[name] = [];
|
|
2128
|
-
const pageLabel = sections.length > 1 ? `${section.id}/${page.id}` : page.id;
|
|
2129
|
-
patternUsage[name].push(pageLabel);
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
const sharedPatterns = Object.entries(patternUsage).filter(([, pages]) => pages.length >= 2);
|
|
2134
|
-
if (sharedPatterns.length > 0) {
|
|
2135
|
-
lines.push("## Shared Components");
|
|
2136
|
-
lines.push("");
|
|
2137
|
-
lines.push("These patterns appear on multiple pages. Consider creating shared components:");
|
|
2138
|
-
lines.push("");
|
|
2139
|
-
lines.push("| Pattern | Used by |");
|
|
2140
|
-
lines.push("|---------|---------|");
|
|
2141
|
-
for (const [pattern, pages] of sharedPatterns) {
|
|
2142
|
-
lines.push(`| ${pattern} | ${pages.join(", ")} |`);
|
|
2143
|
-
}
|
|
2144
|
-
lines.push("");
|
|
2145
|
-
}
|
|
2146
|
-
if (constraints && Object.keys(constraints).length > 0) {
|
|
2147
|
-
lines.push("## Design Constraints");
|
|
2148
|
-
lines.push("");
|
|
2149
|
-
for (const [key, value] of Object.entries(constraints)) {
|
|
2150
|
-
lines.push(`- **${key}:** ${typeof value === "object" && value !== null ? JSON.stringify(value) : value}`);
|
|
2151
|
-
}
|
|
2152
|
-
lines.push("");
|
|
2153
|
-
}
|
|
2154
|
-
if (seo && (seo.schema_org?.length || seo.meta_priorities?.length)) {
|
|
2155
|
-
lines.push("## SEO Hints");
|
|
2156
|
-
lines.push("");
|
|
2157
|
-
if (seo.schema_org && seo.schema_org.length > 0) {
|
|
2158
|
-
lines.push(`**Schema.org types:** ${seo.schema_org.join(", ")}`);
|
|
2159
|
-
}
|
|
2160
|
-
if (seo.meta_priorities && seo.meta_priorities.length > 0) {
|
|
2161
|
-
lines.push(`**Meta priorities:** ${seo.meta_priorities.join(", ")}`);
|
|
2162
|
-
}
|
|
2163
|
-
lines.push("");
|
|
2164
|
-
}
|
|
2165
|
-
if (navigation && (navigation.hotkeys?.length || navigation.command_palette)) {
|
|
2166
|
-
lines.push("## Navigation");
|
|
2167
|
-
lines.push("");
|
|
2168
|
-
if (navigation.command_palette) {
|
|
2169
|
-
lines.push("- Command palette: enabled");
|
|
2170
|
-
}
|
|
2171
|
-
if (navigation.hotkeys && navigation.hotkeys.length > 0) {
|
|
2172
|
-
lines.push(`- Hotkeys: ${navigation.hotkeys.length} configured`);
|
|
2173
|
-
}
|
|
2174
|
-
lines.push("");
|
|
2175
|
-
}
|
|
2176
|
-
return lines.join("\n");
|
|
2177
|
-
}
|
|
2178
|
-
|
|
2179
395
|
// src/theme-commands.ts
|
|
2180
|
-
import { existsSync as
|
|
2181
|
-
import { join as
|
|
396
|
+
import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync, readdirSync, rmSync } from "fs";
|
|
397
|
+
import { join as join2 } from "path";
|
|
2182
398
|
|
|
2183
399
|
// src/theme-templates.ts
|
|
2184
400
|
function getThemeSkeleton(id, name) {
|
|
@@ -2296,20 +512,20 @@ function validateCustomTheme(theme) {
|
|
|
2296
512
|
};
|
|
2297
513
|
}
|
|
2298
514
|
function createTheme(projectRoot, id, name) {
|
|
2299
|
-
const customThemesDir =
|
|
2300
|
-
const themePath =
|
|
2301
|
-
const howToPath =
|
|
2302
|
-
|
|
2303
|
-
if (
|
|
515
|
+
const customThemesDir = join2(projectRoot, ".decantr", "custom", "themes");
|
|
516
|
+
const themePath = join2(customThemesDir, `${id}.json`);
|
|
517
|
+
const howToPath = join2(customThemesDir, "how-to-theme.md");
|
|
518
|
+
mkdirSync(customThemesDir, { recursive: true });
|
|
519
|
+
if (existsSync2(themePath)) {
|
|
2304
520
|
return {
|
|
2305
521
|
success: false,
|
|
2306
522
|
error: `Theme "${id}" already exists at ${themePath}`
|
|
2307
523
|
};
|
|
2308
524
|
}
|
|
2309
525
|
const skeleton = getThemeSkeleton(id, name);
|
|
2310
|
-
|
|
2311
|
-
if (!
|
|
2312
|
-
|
|
526
|
+
writeFileSync(themePath, JSON.stringify(skeleton, null, 2));
|
|
527
|
+
if (!existsSync2(howToPath)) {
|
|
528
|
+
writeFileSync(howToPath, getHowToThemeDoc());
|
|
2313
529
|
}
|
|
2314
530
|
return {
|
|
2315
531
|
success: true,
|
|
@@ -2317,17 +533,17 @@ function createTheme(projectRoot, id, name) {
|
|
|
2317
533
|
};
|
|
2318
534
|
}
|
|
2319
535
|
function listCustomThemes(projectRoot) {
|
|
2320
|
-
const customThemesDir =
|
|
2321
|
-
if (!
|
|
536
|
+
const customThemesDir = join2(projectRoot, ".decantr", "custom", "themes");
|
|
537
|
+
if (!existsSync2(customThemesDir)) {
|
|
2322
538
|
return [];
|
|
2323
539
|
}
|
|
2324
540
|
const themes = [];
|
|
2325
541
|
try {
|
|
2326
542
|
const files = readdirSync(customThemesDir).filter((f) => f.endsWith(".json"));
|
|
2327
543
|
for (const file of files) {
|
|
2328
|
-
const filePath =
|
|
544
|
+
const filePath = join2(customThemesDir, file);
|
|
2329
545
|
try {
|
|
2330
|
-
const data = JSON.parse(
|
|
546
|
+
const data = JSON.parse(readFileSync2(filePath, "utf-8"));
|
|
2331
547
|
themes.push({
|
|
2332
548
|
id: data.id || file.replace(".json", ""),
|
|
2333
549
|
name: data.name || data.id,
|
|
@@ -2342,8 +558,8 @@ function listCustomThemes(projectRoot) {
|
|
|
2342
558
|
return themes;
|
|
2343
559
|
}
|
|
2344
560
|
function deleteTheme(projectRoot, id) {
|
|
2345
|
-
const themePath =
|
|
2346
|
-
if (!
|
|
561
|
+
const themePath = join2(projectRoot, ".decantr", "custom", "themes", `${id}.json`);
|
|
562
|
+
if (!existsSync2(themePath)) {
|
|
2347
563
|
return {
|
|
2348
564
|
success: false,
|
|
2349
565
|
error: `Theme "${id}" not found at ${themePath}`
|
|
@@ -2360,7 +576,7 @@ function deleteTheme(projectRoot, id) {
|
|
|
2360
576
|
}
|
|
2361
577
|
}
|
|
2362
578
|
function importTheme(projectRoot, sourcePath) {
|
|
2363
|
-
if (!
|
|
579
|
+
if (!existsSync2(sourcePath)) {
|
|
2364
580
|
return {
|
|
2365
581
|
success: false,
|
|
2366
582
|
errors: [`Source file not found: ${sourcePath}`]
|
|
@@ -2368,7 +584,7 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
2368
584
|
}
|
|
2369
585
|
let theme;
|
|
2370
586
|
try {
|
|
2371
|
-
theme = JSON.parse(
|
|
587
|
+
theme = JSON.parse(readFileSync2(sourcePath, "utf-8"));
|
|
2372
588
|
} catch (e) {
|
|
2373
589
|
return {
|
|
2374
590
|
success: false,
|
|
@@ -2384,14 +600,14 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
2384
600
|
}
|
|
2385
601
|
theme.source = "custom";
|
|
2386
602
|
const id = theme.id;
|
|
2387
|
-
const customThemesDir =
|
|
2388
|
-
const destPath =
|
|
2389
|
-
|
|
2390
|
-
const howToPath =
|
|
2391
|
-
if (!
|
|
2392
|
-
|
|
2393
|
-
}
|
|
2394
|
-
|
|
603
|
+
const customThemesDir = join2(projectRoot, ".decantr", "custom", "themes");
|
|
604
|
+
const destPath = join2(customThemesDir, `${id}.json`);
|
|
605
|
+
mkdirSync(customThemesDir, { recursive: true });
|
|
606
|
+
const howToPath = join2(customThemesDir, "how-to-theme.md");
|
|
607
|
+
if (!existsSync2(howToPath)) {
|
|
608
|
+
writeFileSync(howToPath, getHowToThemeDoc());
|
|
609
|
+
}
|
|
610
|
+
writeFileSync(destPath, JSON.stringify(theme, null, 2));
|
|
2395
611
|
return {
|
|
2396
612
|
success: true,
|
|
2397
613
|
path: destPath
|
|
@@ -2399,25 +615,25 @@ function importTheme(projectRoot, sourcePath) {
|
|
|
2399
615
|
}
|
|
2400
616
|
|
|
2401
617
|
// src/auth.ts
|
|
2402
|
-
import { existsSync as
|
|
2403
|
-
import { join as
|
|
618
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2, rmSync as rmSync2 } from "fs";
|
|
619
|
+
import { join as join3 } from "path";
|
|
2404
620
|
import { homedir } from "os";
|
|
2405
|
-
var CONFIG_DIR =
|
|
2406
|
-
var AUTH_FILE =
|
|
621
|
+
var CONFIG_DIR = join3(homedir(), ".config", "decantr");
|
|
622
|
+
var AUTH_FILE = join3(CONFIG_DIR, "auth.json");
|
|
2407
623
|
function getCredentials() {
|
|
2408
|
-
if (!
|
|
624
|
+
if (!existsSync3(AUTH_FILE)) return null;
|
|
2409
625
|
try {
|
|
2410
|
-
return JSON.parse(
|
|
626
|
+
return JSON.parse(readFileSync3(AUTH_FILE, "utf-8"));
|
|
2411
627
|
} catch {
|
|
2412
628
|
return null;
|
|
2413
629
|
}
|
|
2414
630
|
}
|
|
2415
631
|
function saveCredentials(creds) {
|
|
2416
|
-
|
|
2417
|
-
|
|
632
|
+
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
633
|
+
writeFileSync2(AUTH_FILE, JSON.stringify(creds, null, 2));
|
|
2418
634
|
}
|
|
2419
635
|
function clearCredentials() {
|
|
2420
|
-
if (
|
|
636
|
+
if (existsSync3(AUTH_FILE)) {
|
|
2421
637
|
rmSync2(AUTH_FILE);
|
|
2422
638
|
}
|
|
2423
639
|
}
|
|
@@ -2428,8 +644,8 @@ function getApiKeyOrToken() {
|
|
|
2428
644
|
}
|
|
2429
645
|
|
|
2430
646
|
// src/commands/publish.ts
|
|
2431
|
-
import { existsSync as
|
|
2432
|
-
import { join as
|
|
647
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
648
|
+
import { join as join4 } from "path";
|
|
2433
649
|
import { RegistryAPIClient } from "@decantr/registry";
|
|
2434
650
|
var PLURAL_TO_SINGULAR = {
|
|
2435
651
|
patterns: "pattern",
|
|
@@ -2456,8 +672,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
2456
672
|
}
|
|
2457
673
|
const singularType = PLURAL_TO_SINGULAR[type] || type;
|
|
2458
674
|
const pluralType = SINGULAR_TO_PLURAL[type] || SINGULAR_TO_PLURAL[singularType] || `${type}s`;
|
|
2459
|
-
const customPath =
|
|
2460
|
-
if (!
|
|
675
|
+
const customPath = join4(projectRoot, ".decantr", "custom", pluralType, `${name}.json`);
|
|
676
|
+
if (!existsSync4(customPath)) {
|
|
2461
677
|
console.error(`Custom ${singularType} "${name}" not found at ${customPath}`);
|
|
2462
678
|
console.error(`Create one first: decantr create ${singularType} ${name}`);
|
|
2463
679
|
process.exitCode = 1;
|
|
@@ -2465,7 +681,7 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
2465
681
|
}
|
|
2466
682
|
let data;
|
|
2467
683
|
try {
|
|
2468
|
-
data = JSON.parse(
|
|
684
|
+
data = JSON.parse(readFileSync4(customPath, "utf-8"));
|
|
2469
685
|
} catch {
|
|
2470
686
|
console.error(`Failed to parse ${customPath}`);
|
|
2471
687
|
process.exitCode = 1;
|
|
@@ -2492,8 +708,8 @@ async function cmdPublish(type, name, projectRoot = process.cwd()) {
|
|
|
2492
708
|
}
|
|
2493
709
|
|
|
2494
710
|
// src/commands/create.ts
|
|
2495
|
-
import { existsSync as
|
|
2496
|
-
import { join as
|
|
711
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
712
|
+
import { join as join5 } from "path";
|
|
2497
713
|
var CONTENT_TYPES = ["pattern", "recipe", "theme", "blueprint", "archetype", "shell"];
|
|
2498
714
|
var PLURAL = {
|
|
2499
715
|
pattern: "patterns",
|
|
@@ -2533,23 +749,23 @@ function cmdCreate(type, name, projectRoot = process.cwd()) {
|
|
|
2533
749
|
return;
|
|
2534
750
|
}
|
|
2535
751
|
const plural = PLURAL[type];
|
|
2536
|
-
const customDir =
|
|
2537
|
-
const filePath =
|
|
2538
|
-
if (
|
|
752
|
+
const customDir = join5(projectRoot, ".decantr", "custom", plural);
|
|
753
|
+
const filePath = join5(customDir, `${name}.json`);
|
|
754
|
+
if (existsSync5(filePath)) {
|
|
2539
755
|
console.error(`${type} "${name}" already exists at ${filePath}`);
|
|
2540
756
|
process.exitCode = 1;
|
|
2541
757
|
return;
|
|
2542
758
|
}
|
|
2543
|
-
|
|
759
|
+
mkdirSync3(customDir, { recursive: true });
|
|
2544
760
|
const skeleton = getSkeleton(type, name, name.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()));
|
|
2545
|
-
|
|
761
|
+
writeFileSync3(filePath, JSON.stringify(skeleton, null, 2));
|
|
2546
762
|
console.log(`Created ${type} "${name}" at ${filePath}`);
|
|
2547
763
|
console.log(`Edit it, then publish with: decantr publish ${type} ${name}`);
|
|
2548
764
|
}
|
|
2549
765
|
|
|
2550
766
|
// src/commands/migrate.ts
|
|
2551
|
-
import { readFileSync as
|
|
2552
|
-
import { join as
|
|
767
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync6, copyFileSync } from "fs";
|
|
768
|
+
import { join as join6 } from "path";
|
|
2553
769
|
import { validateEssence, migrateV2ToV3, isV3 } from "@decantr/essence-spec";
|
|
2554
770
|
var GREEN2 = "\x1B[32m";
|
|
2555
771
|
var RED = "\x1B[31m";
|
|
@@ -2557,12 +773,12 @@ var YELLOW2 = "\x1B[33m";
|
|
|
2557
773
|
var RESET2 = "\x1B[0m";
|
|
2558
774
|
var DIM2 = "\x1B[2m";
|
|
2559
775
|
function migrateEssenceFile(essencePath) {
|
|
2560
|
-
if (!
|
|
776
|
+
if (!existsSync6(essencePath)) {
|
|
2561
777
|
return { success: false, error: `File not found: ${essencePath}` };
|
|
2562
778
|
}
|
|
2563
779
|
let raw;
|
|
2564
780
|
try {
|
|
2565
|
-
raw =
|
|
781
|
+
raw = readFileSync5(essencePath, "utf-8");
|
|
2566
782
|
} catch (e) {
|
|
2567
783
|
return { success: false, error: `Could not read ${essencePath}: ${e.message}` };
|
|
2568
784
|
}
|
|
@@ -2603,15 +819,15 @@ function migrateEssenceFile(essencePath) {
|
|
|
2603
819
|
};
|
|
2604
820
|
}
|
|
2605
821
|
try {
|
|
2606
|
-
|
|
822
|
+
writeFileSync4(essencePath, JSON.stringify(v3, null, 2) + "\n");
|
|
2607
823
|
} catch (e) {
|
|
2608
824
|
return { success: false, backupPath, error: `Could not write migrated file: ${e.message}` };
|
|
2609
825
|
}
|
|
2610
826
|
return { success: true, backupPath };
|
|
2611
827
|
}
|
|
2612
828
|
async function cmdMigrate(projectRoot = process.cwd()) {
|
|
2613
|
-
const essencePath =
|
|
2614
|
-
if (!
|
|
829
|
+
const essencePath = join6(projectRoot, "decantr.essence.json");
|
|
830
|
+
if (!existsSync6(essencePath)) {
|
|
2615
831
|
console.error(`${RED}No decantr.essence.json found. Run \`decantr init\` first.${RESET2}`);
|
|
2616
832
|
process.exitCode = 1;
|
|
2617
833
|
return;
|
|
@@ -2636,8 +852,8 @@ async function cmdMigrate(projectRoot = process.cwd()) {
|
|
|
2636
852
|
}
|
|
2637
853
|
|
|
2638
854
|
// src/commands/sync-drift.ts
|
|
2639
|
-
import { readFileSync as
|
|
2640
|
-
import { join as
|
|
855
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, existsSync as existsSync7 } from "fs";
|
|
856
|
+
import { join as join7 } from "path";
|
|
2641
857
|
var GREEN3 = "\x1B[32m";
|
|
2642
858
|
var RED2 = "\x1B[31m";
|
|
2643
859
|
var YELLOW3 = "\x1B[33m";
|
|
@@ -2646,15 +862,15 @@ var DIM3 = "\x1B[2m";
|
|
|
2646
862
|
var BOLD2 = "\x1B[1m";
|
|
2647
863
|
var CYAN2 = "\x1B[36m";
|
|
2648
864
|
async function cmdSyncDrift(projectRoot = process.cwd()) {
|
|
2649
|
-
const driftLogPath =
|
|
2650
|
-
if (!
|
|
865
|
+
const driftLogPath = join7(projectRoot, ".decantr", "drift-log.json");
|
|
866
|
+
if (!existsSync7(driftLogPath)) {
|
|
2651
867
|
console.log(`${GREEN3}No drift log found \u2014 no drift recorded.${RESET3}`);
|
|
2652
868
|
console.log(`${DIM3}Drift is logged when guard violations are detected during development.${RESET3}`);
|
|
2653
869
|
return;
|
|
2654
870
|
}
|
|
2655
871
|
let entries;
|
|
2656
872
|
try {
|
|
2657
|
-
entries = JSON.parse(
|
|
873
|
+
entries = JSON.parse(readFileSync6(driftLogPath, "utf-8"));
|
|
2658
874
|
} catch {
|
|
2659
875
|
console.error(`${RED2}Could not parse drift-log.json${RESET3}`);
|
|
2660
876
|
process.exitCode = 1;
|
|
@@ -2698,13 +914,13 @@ ${BOLD2}Unresolved Drift Entries (${unresolved.length})${RESET3}
|
|
|
2698
914
|
console.log("");
|
|
2699
915
|
}
|
|
2700
916
|
function resolveDriftEntries(projectRoot, options) {
|
|
2701
|
-
const driftLogPath =
|
|
2702
|
-
if (!
|
|
917
|
+
const driftLogPath = join7(projectRoot, ".decantr", "drift-log.json");
|
|
918
|
+
if (!existsSync7(driftLogPath)) {
|
|
2703
919
|
return { success: true };
|
|
2704
920
|
}
|
|
2705
921
|
if (options.clear) {
|
|
2706
922
|
try {
|
|
2707
|
-
|
|
923
|
+
writeFileSync5(driftLogPath, "[]");
|
|
2708
924
|
return { success: true };
|
|
2709
925
|
} catch (e) {
|
|
2710
926
|
return { success: false, error: `Could not clear drift log: ${e.message}` };
|
|
@@ -2712,7 +928,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
2712
928
|
}
|
|
2713
929
|
let entries;
|
|
2714
930
|
try {
|
|
2715
|
-
entries = JSON.parse(
|
|
931
|
+
entries = JSON.parse(readFileSync6(driftLogPath, "utf-8"));
|
|
2716
932
|
} catch {
|
|
2717
933
|
return { success: false, error: "Could not parse drift-log.json" };
|
|
2718
934
|
}
|
|
@@ -2731,7 +947,7 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
2731
947
|
}
|
|
2732
948
|
}
|
|
2733
949
|
try {
|
|
2734
|
-
|
|
950
|
+
writeFileSync5(driftLogPath, JSON.stringify(entries, null, 2));
|
|
2735
951
|
return { success: true };
|
|
2736
952
|
} catch (e) {
|
|
2737
953
|
return { success: false, error: `Could not write drift log: ${e.message}` };
|
|
@@ -2739,23 +955,23 @@ function resolveDriftEntries(projectRoot, options) {
|
|
|
2739
955
|
}
|
|
2740
956
|
|
|
2741
957
|
// src/commands/refresh.ts
|
|
2742
|
-
import { readFileSync as
|
|
2743
|
-
import { join as
|
|
958
|
+
import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
|
|
959
|
+
import { join as join8 } from "path";
|
|
2744
960
|
import { isV3 as isV32 } from "@decantr/essence-spec";
|
|
2745
961
|
var GREEN4 = "\x1B[32m";
|
|
2746
962
|
var RED3 = "\x1B[31m";
|
|
2747
963
|
var DIM4 = "\x1B[2m";
|
|
2748
964
|
var RESET4 = "\x1B[0m";
|
|
2749
965
|
async function cmdRefresh(projectRoot = process.cwd()) {
|
|
2750
|
-
const essencePath =
|
|
2751
|
-
if (!
|
|
966
|
+
const essencePath = join8(projectRoot, "decantr.essence.json");
|
|
967
|
+
if (!existsSync8(essencePath)) {
|
|
2752
968
|
console.error(`${RED3}No decantr.essence.json found. Run \`decantr init\` first.${RESET4}`);
|
|
2753
969
|
process.exitCode = 1;
|
|
2754
970
|
return;
|
|
2755
971
|
}
|
|
2756
972
|
let essence;
|
|
2757
973
|
try {
|
|
2758
|
-
const raw =
|
|
974
|
+
const raw = readFileSync7(essencePath, "utf-8");
|
|
2759
975
|
const parsed = JSON.parse(raw);
|
|
2760
976
|
if (!isV32(parsed)) {
|
|
2761
977
|
console.error(`${RED3}Essence is not v3. Run \`decantr migrate\` first.${RESET4}`);
|
|
@@ -2769,7 +985,7 @@ async function cmdRefresh(projectRoot = process.cwd()) {
|
|
|
2769
985
|
return;
|
|
2770
986
|
}
|
|
2771
987
|
const registryClient = new RegistryClient({
|
|
2772
|
-
cacheDir:
|
|
988
|
+
cacheDir: join8(projectRoot, ".decantr", "cache")
|
|
2773
989
|
});
|
|
2774
990
|
console.log("Regenerating derived files...\n");
|
|
2775
991
|
const result = await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
@@ -2788,23 +1004,23 @@ async function cmdRefresh(projectRoot = process.cwd()) {
|
|
|
2788
1004
|
}
|
|
2789
1005
|
|
|
2790
1006
|
// src/commands/add.ts
|
|
2791
|
-
import { readFileSync as
|
|
2792
|
-
import { join as
|
|
1007
|
+
import { readFileSync as readFileSync8, writeFileSync as writeFileSync6, existsSync as existsSync9 } from "fs";
|
|
1008
|
+
import { join as join9 } from "path";
|
|
2793
1009
|
import { isV3 as isV33, migrateV30ToV31 } from "@decantr/essence-spec";
|
|
2794
1010
|
var GREEN5 = "\x1B[32m";
|
|
2795
1011
|
var RED4 = "\x1B[31m";
|
|
2796
1012
|
var DIM5 = "\x1B[2m";
|
|
2797
1013
|
var RESET5 = "\x1B[0m";
|
|
2798
1014
|
function readAndMigrate(projectRoot) {
|
|
2799
|
-
const essencePath =
|
|
2800
|
-
if (!
|
|
1015
|
+
const essencePath = join9(projectRoot, "decantr.essence.json");
|
|
1016
|
+
if (!existsSync9(essencePath)) {
|
|
2801
1017
|
console.error(`${RED4}No decantr.essence.json found. Run \`decantr init\` first.${RESET5}`);
|
|
2802
1018
|
process.exitCode = 1;
|
|
2803
1019
|
return null;
|
|
2804
1020
|
}
|
|
2805
1021
|
let parsed;
|
|
2806
1022
|
try {
|
|
2807
|
-
parsed = JSON.parse(
|
|
1023
|
+
parsed = JSON.parse(readFileSync8(essencePath, "utf-8"));
|
|
2808
1024
|
} catch (e) {
|
|
2809
1025
|
console.error(`${RED4}Could not read essence: ${e.message}${RESET5}`);
|
|
2810
1026
|
process.exitCode = 1;
|
|
@@ -2819,7 +1035,7 @@ function readAndMigrate(projectRoot) {
|
|
|
2819
1035
|
return { essence, essencePath };
|
|
2820
1036
|
}
|
|
2821
1037
|
function writeEssence(essencePath, essence) {
|
|
2822
|
-
|
|
1038
|
+
writeFileSync6(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
2823
1039
|
}
|
|
2824
1040
|
async function cmdAddSection(archetypeId, args, projectRoot = process.cwd()) {
|
|
2825
1041
|
if (!archetypeId) {
|
|
@@ -2838,7 +1054,7 @@ async function cmdAddSection(archetypeId, args, projectRoot = process.cwd()) {
|
|
|
2838
1054
|
return;
|
|
2839
1055
|
}
|
|
2840
1056
|
const registryClient = new RegistryClient({
|
|
2841
|
-
cacheDir:
|
|
1057
|
+
cacheDir: join9(projectRoot, ".decantr", "cache")
|
|
2842
1058
|
});
|
|
2843
1059
|
const result = await registryClient.fetchArchetype(archetypeId);
|
|
2844
1060
|
if (!result) {
|
|
@@ -2902,7 +1118,7 @@ async function cmdAddPage(path, args, projectRoot = process.cwd()) {
|
|
|
2902
1118
|
writeEssence(essencePath, essence);
|
|
2903
1119
|
console.log(`${GREEN5}Added page "${pageId}" to section "${sectionId}".${RESET5}`);
|
|
2904
1120
|
const registryClient = new RegistryClient({
|
|
2905
|
-
cacheDir:
|
|
1121
|
+
cacheDir: join9(projectRoot, ".decantr", "cache")
|
|
2906
1122
|
});
|
|
2907
1123
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
2908
1124
|
console.log(`${GREEN5}Derived files refreshed.${RESET5}`);
|
|
@@ -2941,30 +1157,30 @@ async function cmdAddFeature(feature, args, projectRoot = process.cwd()) {
|
|
|
2941
1157
|
const target = sectionId ? `section "${sectionId}" and global` : "global";
|
|
2942
1158
|
console.log(`${GREEN5}Added feature "${feature}" to ${target} features.${RESET5}`);
|
|
2943
1159
|
const registryClient = new RegistryClient({
|
|
2944
|
-
cacheDir:
|
|
1160
|
+
cacheDir: join9(projectRoot, ".decantr", "cache")
|
|
2945
1161
|
});
|
|
2946
1162
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
2947
1163
|
console.log(`${GREEN5}Derived files refreshed.${RESET5}`);
|
|
2948
1164
|
}
|
|
2949
1165
|
|
|
2950
1166
|
// src/commands/remove.ts
|
|
2951
|
-
import { readFileSync as
|
|
2952
|
-
import { join as
|
|
1167
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, existsSync as existsSync10, rmSync as rmSync3 } from "fs";
|
|
1168
|
+
import { join as join10 } from "path";
|
|
2953
1169
|
import { isV3 as isV34, migrateV30ToV31 as migrateV30ToV312 } from "@decantr/essence-spec";
|
|
2954
1170
|
var GREEN6 = "\x1B[32m";
|
|
2955
1171
|
var RED5 = "\x1B[31m";
|
|
2956
1172
|
var DIM6 = "\x1B[2m";
|
|
2957
1173
|
var RESET6 = "\x1B[0m";
|
|
2958
1174
|
function readAndMigrate2(projectRoot) {
|
|
2959
|
-
const essencePath =
|
|
2960
|
-
if (!
|
|
1175
|
+
const essencePath = join10(projectRoot, "decantr.essence.json");
|
|
1176
|
+
if (!existsSync10(essencePath)) {
|
|
2961
1177
|
console.error(`${RED5}No decantr.essence.json found. Run \`decantr init\` first.${RESET6}`);
|
|
2962
1178
|
process.exitCode = 1;
|
|
2963
1179
|
return null;
|
|
2964
1180
|
}
|
|
2965
1181
|
let parsed;
|
|
2966
1182
|
try {
|
|
2967
|
-
parsed = JSON.parse(
|
|
1183
|
+
parsed = JSON.parse(readFileSync9(essencePath, "utf-8"));
|
|
2968
1184
|
} catch (e) {
|
|
2969
1185
|
console.error(`${RED5}Could not read essence: ${e.message}${RESET6}`);
|
|
2970
1186
|
process.exitCode = 1;
|
|
@@ -2979,7 +1195,7 @@ function readAndMigrate2(projectRoot) {
|
|
|
2979
1195
|
return { essence, essencePath };
|
|
2980
1196
|
}
|
|
2981
1197
|
function writeEssence2(essencePath, essence) {
|
|
2982
|
-
|
|
1198
|
+
writeFileSync7(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
2983
1199
|
}
|
|
2984
1200
|
function recomputeGlobalFeatures(essence) {
|
|
2985
1201
|
const all = /* @__PURE__ */ new Set();
|
|
@@ -3021,14 +1237,14 @@ async function cmdRemoveSection(sectionId, args, projectRoot = process.cwd()) {
|
|
|
3021
1237
|
sections.splice(idx, 1);
|
|
3022
1238
|
recomputeGlobalFeatures(essence);
|
|
3023
1239
|
removeRoutes(essence, sectionId);
|
|
3024
|
-
const contextFile =
|
|
3025
|
-
if (
|
|
1240
|
+
const contextFile = join10(projectRoot, ".decantr", "context", `${sectionId}.md`);
|
|
1241
|
+
if (existsSync10(contextFile)) {
|
|
3026
1242
|
rmSync3(contextFile);
|
|
3027
1243
|
}
|
|
3028
1244
|
writeEssence2(essencePath, essence);
|
|
3029
1245
|
console.log(`${GREEN6}Removed section "${sectionId}".${RESET6}`);
|
|
3030
1246
|
const registryClient = new RegistryClient({
|
|
3031
|
-
cacheDir:
|
|
1247
|
+
cacheDir: join10(projectRoot, ".decantr", "cache")
|
|
3032
1248
|
});
|
|
3033
1249
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
3034
1250
|
console.log(`${GREEN6}Derived files refreshed.${RESET6}`);
|
|
@@ -3064,7 +1280,7 @@ async function cmdRemovePage(path, args, projectRoot = process.cwd()) {
|
|
|
3064
1280
|
writeEssence2(essencePath, essence);
|
|
3065
1281
|
console.log(`${GREEN6}Removed page "${pageId}" from section "${sectionId}".${RESET6}`);
|
|
3066
1282
|
const registryClient = new RegistryClient({
|
|
3067
|
-
cacheDir:
|
|
1283
|
+
cacheDir: join10(projectRoot, ".decantr", "cache")
|
|
3068
1284
|
});
|
|
3069
1285
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
3070
1286
|
console.log(`${GREEN6}Derived files refreshed.${RESET6}`);
|
|
@@ -3105,15 +1321,15 @@ async function cmdRemoveFeature(feature, args, projectRoot = process.cwd()) {
|
|
|
3105
1321
|
const target = sectionId ? `section "${sectionId}" and global` : "global";
|
|
3106
1322
|
console.log(`${GREEN6}Removed feature "${feature}" from ${target} features.${RESET6}`);
|
|
3107
1323
|
const registryClient = new RegistryClient({
|
|
3108
|
-
cacheDir:
|
|
1324
|
+
cacheDir: join10(projectRoot, ".decantr", "cache")
|
|
3109
1325
|
});
|
|
3110
1326
|
await refreshDerivedFiles(projectRoot, essence, registryClient);
|
|
3111
1327
|
console.log(`${GREEN6}Derived files refreshed.${RESET6}`);
|
|
3112
1328
|
}
|
|
3113
1329
|
|
|
3114
1330
|
// src/commands/theme-switch.ts
|
|
3115
|
-
import { readFileSync as
|
|
3116
|
-
import { join as
|
|
1331
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync8, existsSync as existsSync11 } from "fs";
|
|
1332
|
+
import { join as join11 } from "path";
|
|
3117
1333
|
import { isV3 as isV35, migrateV30ToV31 as migrateV30ToV313 } from "@decantr/essence-spec";
|
|
3118
1334
|
var GREEN7 = "\x1B[32m";
|
|
3119
1335
|
var RED6 = "\x1B[31m";
|
|
@@ -3126,15 +1342,15 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
3126
1342
|
process.exitCode = 1;
|
|
3127
1343
|
return;
|
|
3128
1344
|
}
|
|
3129
|
-
const essencePath =
|
|
3130
|
-
if (!
|
|
1345
|
+
const essencePath = join11(projectRoot, "decantr.essence.json");
|
|
1346
|
+
if (!existsSync11(essencePath)) {
|
|
3131
1347
|
console.error(`${RED6}No decantr.essence.json found. Run \`decantr init\` first.${RESET7}`);
|
|
3132
1348
|
process.exitCode = 1;
|
|
3133
1349
|
return;
|
|
3134
1350
|
}
|
|
3135
1351
|
let parsed;
|
|
3136
1352
|
try {
|
|
3137
|
-
parsed = JSON.parse(
|
|
1353
|
+
parsed = JSON.parse(readFileSync10(essencePath, "utf-8"));
|
|
3138
1354
|
} catch (e) {
|
|
3139
1355
|
console.error(`${RED6}Could not read essence: ${e.message}${RESET7}`);
|
|
3140
1356
|
process.exitCode = 1;
|
|
@@ -3179,7 +1395,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
3179
1395
|
essence.dna.theme.mode = mode;
|
|
3180
1396
|
}
|
|
3181
1397
|
const registryClient = new RegistryClient({
|
|
3182
|
-
cacheDir:
|
|
1398
|
+
cacheDir: join11(projectRoot, ".decantr", "cache")
|
|
3183
1399
|
});
|
|
3184
1400
|
const recipeName = essence.dna.theme.recipe;
|
|
3185
1401
|
try {
|
|
@@ -3197,7 +1413,7 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
3197
1413
|
}
|
|
3198
1414
|
} catch {
|
|
3199
1415
|
}
|
|
3200
|
-
|
|
1416
|
+
writeFileSync8(essencePath, JSON.stringify(essence, null, 2) + "\n");
|
|
3201
1417
|
console.log(`${GREEN7}Switched theme: ${oldStyle} \u2192 ${themeName}${RESET7}`);
|
|
3202
1418
|
if (recipe) console.log(` ${DIM7}Recipe: ${recipe}${RESET7}`);
|
|
3203
1419
|
if (shape) console.log(` ${DIM7}Shape: ${shape}${RESET7}`);
|
|
@@ -3208,12 +1424,12 @@ async function cmdThemeSwitch(themeName, args, projectRoot = process.cwd()) {
|
|
|
3208
1424
|
}
|
|
3209
1425
|
|
|
3210
1426
|
// src/commands/analyze.ts
|
|
3211
|
-
import { existsSync as
|
|
3212
|
-
import { join as
|
|
1427
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync4, writeFileSync as writeFileSync9 } from "fs";
|
|
1428
|
+
import { join as join18 } from "path";
|
|
3213
1429
|
|
|
3214
1430
|
// src/analyzers/routes.ts
|
|
3215
|
-
import { existsSync as
|
|
3216
|
-
import { join as
|
|
1431
|
+
import { existsSync as existsSync12, readdirSync as readdirSync2, statSync } from "fs";
|
|
1432
|
+
import { join as join12, relative } from "path";
|
|
3217
1433
|
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".next", ".git", "api", "_app", "_document"]);
|
|
3218
1434
|
function shouldSkipDir(name) {
|
|
3219
1435
|
return name.startsWith("_") || name.startsWith(".") || SKIP_DIRS.has(name);
|
|
@@ -3249,13 +1465,13 @@ function walkAppDir(dir, baseDir, segments) {
|
|
|
3249
1465
|
const pageFile = entries.find((e) => e.startsWith("page."));
|
|
3250
1466
|
routes.push({
|
|
3251
1467
|
path: routePath || "/",
|
|
3252
|
-
file: relative(baseDir,
|
|
1468
|
+
file: relative(baseDir, join12(dir, pageFile)),
|
|
3253
1469
|
hasLayout
|
|
3254
1470
|
});
|
|
3255
1471
|
}
|
|
3256
1472
|
for (const entry of entries) {
|
|
3257
1473
|
if (shouldSkipDir(entry)) continue;
|
|
3258
|
-
const fullPath =
|
|
1474
|
+
const fullPath = join12(dir, entry);
|
|
3259
1475
|
try {
|
|
3260
1476
|
if (!statSync(fullPath).isDirectory()) continue;
|
|
3261
1477
|
} catch {
|
|
@@ -3277,7 +1493,7 @@ function walkPagesDir(dir, baseDir, segments) {
|
|
|
3277
1493
|
}
|
|
3278
1494
|
for (const entry of entries) {
|
|
3279
1495
|
if (shouldSkipDir(entry)) continue;
|
|
3280
|
-
const fullPath =
|
|
1496
|
+
const fullPath = join12(dir, entry);
|
|
3281
1497
|
try {
|
|
3282
1498
|
const stat = statSync(fullPath);
|
|
3283
1499
|
if (stat.isDirectory()) {
|
|
@@ -3305,11 +1521,11 @@ function walkPagesDir(dir, baseDir, segments) {
|
|
|
3305
1521
|
}
|
|
3306
1522
|
function scanRoutes(projectRoot) {
|
|
3307
1523
|
const appDirs = [
|
|
3308
|
-
|
|
3309
|
-
|
|
1524
|
+
join12(projectRoot, "src", "app"),
|
|
1525
|
+
join12(projectRoot, "app")
|
|
3310
1526
|
];
|
|
3311
1527
|
for (const appDir of appDirs) {
|
|
3312
|
-
if (
|
|
1528
|
+
if (existsSync12(appDir)) {
|
|
3313
1529
|
const routes = walkAppDir(appDir, projectRoot, []);
|
|
3314
1530
|
if (routes.length > 0) {
|
|
3315
1531
|
return { strategy: "app-router", routes };
|
|
@@ -3317,11 +1533,11 @@ function scanRoutes(projectRoot) {
|
|
|
3317
1533
|
}
|
|
3318
1534
|
}
|
|
3319
1535
|
const pagesDirs = [
|
|
3320
|
-
|
|
3321
|
-
|
|
1536
|
+
join12(projectRoot, "src", "pages"),
|
|
1537
|
+
join12(projectRoot, "pages")
|
|
3322
1538
|
];
|
|
3323
1539
|
for (const pagesDir of pagesDirs) {
|
|
3324
|
-
if (
|
|
1540
|
+
if (existsSync12(pagesDir)) {
|
|
3325
1541
|
const routes = walkPagesDir(pagesDir, projectRoot, []);
|
|
3326
1542
|
if (routes.length > 0) {
|
|
3327
1543
|
return { strategy: "pages-router", routes };
|
|
@@ -3332,8 +1548,8 @@ function scanRoutes(projectRoot) {
|
|
|
3332
1548
|
}
|
|
3333
1549
|
|
|
3334
1550
|
// src/analyzers/components.ts
|
|
3335
|
-
import { existsSync as
|
|
3336
|
-
import { join as
|
|
1551
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
1552
|
+
import { join as join13 } from "path";
|
|
3337
1553
|
var PAGE_EXTENSIONS = /* @__PURE__ */ new Set([".tsx", ".ts", ".jsx", ".js"]);
|
|
3338
1554
|
function countFilesRecursive(dir, extensions) {
|
|
3339
1555
|
let count = 0;
|
|
@@ -3345,7 +1561,7 @@ function countFilesRecursive(dir, extensions) {
|
|
|
3345
1561
|
}
|
|
3346
1562
|
for (const entry of entries) {
|
|
3347
1563
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
3348
|
-
const fullPath =
|
|
1564
|
+
const fullPath = join13(dir, entry);
|
|
3349
1565
|
try {
|
|
3350
1566
|
const stat = statSync2(fullPath);
|
|
3351
1567
|
if (stat.isDirectory()) {
|
|
@@ -3372,7 +1588,7 @@ function countPageFiles(dir) {
|
|
|
3372
1588
|
}
|
|
3373
1589
|
for (const entry of entries) {
|
|
3374
1590
|
if (entry.startsWith(".") || entry === "node_modules") continue;
|
|
3375
|
-
const fullPath =
|
|
1591
|
+
const fullPath = join13(dir, entry);
|
|
3376
1592
|
try {
|
|
3377
1593
|
const stat = statSync2(fullPath);
|
|
3378
1594
|
if (stat.isDirectory()) {
|
|
@@ -3392,32 +1608,32 @@ function scanComponents(projectRoot) {
|
|
|
3392
1608
|
let pageCount = 0;
|
|
3393
1609
|
const componentDirs = [];
|
|
3394
1610
|
const appDirs = [
|
|
3395
|
-
|
|
3396
|
-
|
|
1611
|
+
join13(projectRoot, "src", "app"),
|
|
1612
|
+
join13(projectRoot, "app")
|
|
3397
1613
|
];
|
|
3398
1614
|
for (const dir of appDirs) {
|
|
3399
|
-
if (
|
|
1615
|
+
if (existsSync13(dir)) {
|
|
3400
1616
|
pageCount += countPageFiles(dir);
|
|
3401
1617
|
}
|
|
3402
1618
|
}
|
|
3403
1619
|
const pagesDirs = [
|
|
3404
|
-
|
|
3405
|
-
|
|
1620
|
+
join13(projectRoot, "src", "pages"),
|
|
1621
|
+
join13(projectRoot, "pages")
|
|
3406
1622
|
];
|
|
3407
1623
|
for (const dir of pagesDirs) {
|
|
3408
|
-
if (
|
|
1624
|
+
if (existsSync13(dir)) {
|
|
3409
1625
|
pageCount += countFilesRecursive(dir, PAGE_EXTENSIONS);
|
|
3410
1626
|
}
|
|
3411
1627
|
}
|
|
3412
1628
|
let componentCount = 0;
|
|
3413
1629
|
const componentPaths = [
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
1630
|
+
join13(projectRoot, "src", "components"),
|
|
1631
|
+
join13(projectRoot, "components"),
|
|
1632
|
+
join13(projectRoot, "src", "ui"),
|
|
1633
|
+
join13(projectRoot, "ui")
|
|
3418
1634
|
];
|
|
3419
1635
|
for (const dir of componentPaths) {
|
|
3420
|
-
if (
|
|
1636
|
+
if (existsSync13(dir)) {
|
|
3421
1637
|
const count = countFilesRecursive(dir, PAGE_EXTENSIONS);
|
|
3422
1638
|
if (count > 0) {
|
|
3423
1639
|
componentCount += count;
|
|
@@ -3434,8 +1650,8 @@ function scanComponents(projectRoot) {
|
|
|
3434
1650
|
}
|
|
3435
1651
|
|
|
3436
1652
|
// src/analyzers/styling.ts
|
|
3437
|
-
import { existsSync as
|
|
3438
|
-
import { join as
|
|
1653
|
+
import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
|
|
1654
|
+
import { join as join14 } from "path";
|
|
3439
1655
|
var TAILWIND_CONFIGS = [
|
|
3440
1656
|
"tailwind.config.js",
|
|
3441
1657
|
"tailwind.config.ts",
|
|
@@ -3479,10 +1695,10 @@ function detectDarkMode(projectRoot, cssContent) {
|
|
|
3479
1695
|
"app/layout.jsx"
|
|
3480
1696
|
];
|
|
3481
1697
|
for (const rel of layoutPaths) {
|
|
3482
|
-
const fullPath =
|
|
3483
|
-
if (
|
|
1698
|
+
const fullPath = join14(projectRoot, rel);
|
|
1699
|
+
if (existsSync14(fullPath)) {
|
|
3484
1700
|
try {
|
|
3485
|
-
const layoutContent =
|
|
1701
|
+
const layoutContent = readFileSync11(fullPath, "utf-8");
|
|
3486
1702
|
if (layoutContent.includes('className="dark"') || layoutContent.includes("className='dark'") || layoutContent.includes('class="dark"')) {
|
|
3487
1703
|
return true;
|
|
3488
1704
|
}
|
|
@@ -3490,10 +1706,10 @@ function detectDarkMode(projectRoot, cssContent) {
|
|
|
3490
1706
|
}
|
|
3491
1707
|
}
|
|
3492
1708
|
}
|
|
3493
|
-
const pkgPath =
|
|
3494
|
-
if (
|
|
1709
|
+
const pkgPath = join14(projectRoot, "package.json");
|
|
1710
|
+
if (existsSync14(pkgPath)) {
|
|
3495
1711
|
try {
|
|
3496
|
-
const pkg = JSON.parse(
|
|
1712
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
3497
1713
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3498
1714
|
if (allDeps["next-themes"] || allDeps["theme-toggle"] || allDeps["use-dark-mode"]) {
|
|
3499
1715
|
return true;
|
|
@@ -3507,17 +1723,17 @@ function scanStyling(projectRoot) {
|
|
|
3507
1723
|
let approach = "unknown";
|
|
3508
1724
|
let configFile;
|
|
3509
1725
|
for (const cfg of TAILWIND_CONFIGS) {
|
|
3510
|
-
if (
|
|
1726
|
+
if (existsSync14(join14(projectRoot, cfg))) {
|
|
3511
1727
|
approach = "tailwind";
|
|
3512
1728
|
configFile = cfg;
|
|
3513
1729
|
break;
|
|
3514
1730
|
}
|
|
3515
1731
|
}
|
|
3516
1732
|
if (approach === "unknown") {
|
|
3517
|
-
const pkgPath =
|
|
3518
|
-
if (
|
|
1733
|
+
const pkgPath = join14(projectRoot, "package.json");
|
|
1734
|
+
if (existsSync14(pkgPath)) {
|
|
3519
1735
|
try {
|
|
3520
|
-
const pkg = JSON.parse(
|
|
1736
|
+
const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
|
|
3521
1737
|
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3522
1738
|
if (allDeps.tailwindcss || allDeps["@tailwindcss/postcss"] || allDeps["@tailwindcss/vite"]) {
|
|
3523
1739
|
approach = "tailwind";
|
|
@@ -3528,10 +1744,10 @@ function scanStyling(projectRoot) {
|
|
|
3528
1744
|
}
|
|
3529
1745
|
let cssContent = null;
|
|
3530
1746
|
for (const rel of GLOBALS_CSS_PATHS) {
|
|
3531
|
-
const fullPath =
|
|
3532
|
-
if (
|
|
1747
|
+
const fullPath = join14(projectRoot, rel);
|
|
1748
|
+
if (existsSync14(fullPath)) {
|
|
3533
1749
|
try {
|
|
3534
|
-
cssContent =
|
|
1750
|
+
cssContent = readFileSync11(fullPath, "utf-8");
|
|
3535
1751
|
} catch {
|
|
3536
1752
|
}
|
|
3537
1753
|
break;
|
|
@@ -3555,8 +1771,8 @@ function scanStyling(projectRoot) {
|
|
|
3555
1771
|
}
|
|
3556
1772
|
|
|
3557
1773
|
// src/analyzers/layout.ts
|
|
3558
|
-
import { existsSync as
|
|
3559
|
-
import { join as
|
|
1774
|
+
import { existsSync as existsSync15, readFileSync as readFileSync12, readdirSync as readdirSync4 } from "fs";
|
|
1775
|
+
import { join as join15 } from "path";
|
|
3560
1776
|
var SIDEBAR_PATTERNS = ["sidebar", "side-bar", "sidenav", "side-nav", "drawer", "aside"];
|
|
3561
1777
|
var NAV_PATTERNS = ["nav", "navbar", "header", "top-bar", "topbar", "app-bar", "appbar"];
|
|
3562
1778
|
var FOOTER_PATTERNS = ["footer", "bottom-bar", "bottombar"];
|
|
@@ -3567,12 +1783,12 @@ function containsPattern(text, patterns) {
|
|
|
3567
1783
|
function checkComponentDirs(projectRoot) {
|
|
3568
1784
|
const result = { sidebar: false, nav: false, footer: false };
|
|
3569
1785
|
const componentDirs = [
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
1786
|
+
join15(projectRoot, "src", "components"),
|
|
1787
|
+
join15(projectRoot, "components"),
|
|
1788
|
+
join15(projectRoot, "src", "ui")
|
|
3573
1789
|
];
|
|
3574
1790
|
for (const dir of componentDirs) {
|
|
3575
|
-
if (!
|
|
1791
|
+
if (!existsSync15(dir)) continue;
|
|
3576
1792
|
let entries;
|
|
3577
1793
|
try {
|
|
3578
1794
|
entries = readdirSync4(dir);
|
|
@@ -3591,16 +1807,16 @@ function checkComponentDirs(projectRoot) {
|
|
|
3591
1807
|
function checkLayoutFiles(projectRoot) {
|
|
3592
1808
|
const result = { sidebar: false, nav: false, footer: false };
|
|
3593
1809
|
const layoutPaths = [
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
1810
|
+
join15(projectRoot, "src", "app", "layout.tsx"),
|
|
1811
|
+
join15(projectRoot, "src", "app", "layout.jsx"),
|
|
1812
|
+
join15(projectRoot, "app", "layout.tsx"),
|
|
1813
|
+
join15(projectRoot, "app", "layout.jsx")
|
|
3598
1814
|
];
|
|
3599
1815
|
for (const layoutPath of layoutPaths) {
|
|
3600
|
-
if (!
|
|
1816
|
+
if (!existsSync15(layoutPath)) continue;
|
|
3601
1817
|
let content;
|
|
3602
1818
|
try {
|
|
3603
|
-
content =
|
|
1819
|
+
content = readFileSync12(layoutPath, "utf-8");
|
|
3604
1820
|
} catch {
|
|
3605
1821
|
continue;
|
|
3606
1822
|
}
|
|
@@ -3611,16 +1827,16 @@ function checkLayoutFiles(projectRoot) {
|
|
|
3611
1827
|
const subLayoutDirs = ["dashboard", "admin", "app"];
|
|
3612
1828
|
for (const sub of subLayoutDirs) {
|
|
3613
1829
|
const paths = [
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
1830
|
+
join15(projectRoot, "src", "app", sub, "layout.tsx"),
|
|
1831
|
+
join15(projectRoot, "src", "app", sub, "layout.jsx"),
|
|
1832
|
+
join15(projectRoot, "app", sub, "layout.tsx"),
|
|
1833
|
+
join15(projectRoot, "app", sub, "layout.jsx")
|
|
3618
1834
|
];
|
|
3619
1835
|
for (const layoutPath of paths) {
|
|
3620
|
-
if (!
|
|
1836
|
+
if (!existsSync15(layoutPath)) continue;
|
|
3621
1837
|
let content;
|
|
3622
1838
|
try {
|
|
3623
|
-
content =
|
|
1839
|
+
content = readFileSync12(layoutPath, "utf-8");
|
|
3624
1840
|
} catch {
|
|
3625
1841
|
continue;
|
|
3626
1842
|
}
|
|
@@ -3656,8 +1872,8 @@ function scanLayout(projectRoot) {
|
|
|
3656
1872
|
}
|
|
3657
1873
|
|
|
3658
1874
|
// src/analyzers/features.ts
|
|
3659
|
-
import { existsSync as
|
|
3660
|
-
import { join as
|
|
1875
|
+
import { existsSync as existsSync16, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
|
|
1876
|
+
import { join as join16 } from "path";
|
|
3661
1877
|
var FEATURE_PATTERNS = {
|
|
3662
1878
|
auth: ["login", "signin", "sign-in", "signup", "sign-up", "register", "auth", "callback", "signout", "sign-out", "logout", "forgot-password", "reset-password", "verify-email"],
|
|
3663
1879
|
dashboard: ["dashboard", "overview", "analytics", "metrics", "stats"],
|
|
@@ -3685,7 +1901,7 @@ function collectPaths(dir, baseDir, depth = 0) {
|
|
|
3685
1901
|
}
|
|
3686
1902
|
for (const entry of entries) {
|
|
3687
1903
|
if (entry.startsWith(".") || entry.startsWith("_") || entry === "node_modules" || entry === "api") continue;
|
|
3688
|
-
const fullPath =
|
|
1904
|
+
const fullPath = join16(dir, entry);
|
|
3689
1905
|
const relPath = fullPath.slice(baseDir.length + 1);
|
|
3690
1906
|
paths.push(relPath);
|
|
3691
1907
|
try {
|
|
@@ -3702,16 +1918,16 @@ function scanFeatures(projectRoot) {
|
|
|
3702
1918
|
const detected = [];
|
|
3703
1919
|
const evidence = {};
|
|
3704
1920
|
const scanDirs = [
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
1921
|
+
join16(projectRoot, "src", "app"),
|
|
1922
|
+
join16(projectRoot, "app"),
|
|
1923
|
+
join16(projectRoot, "src", "pages"),
|
|
1924
|
+
join16(projectRoot, "pages"),
|
|
1925
|
+
join16(projectRoot, "src", "components"),
|
|
1926
|
+
join16(projectRoot, "components")
|
|
3711
1927
|
];
|
|
3712
1928
|
const allPaths = [];
|
|
3713
1929
|
for (const dir of scanDirs) {
|
|
3714
|
-
if (
|
|
1930
|
+
if (existsSync16(dir)) {
|
|
3715
1931
|
allPaths.push(...collectPaths(dir, projectRoot));
|
|
3716
1932
|
}
|
|
3717
1933
|
}
|
|
@@ -3735,8 +1951,8 @@ function scanFeatures(projectRoot) {
|
|
|
3735
1951
|
}
|
|
3736
1952
|
|
|
3737
1953
|
// src/analyzers/dependencies.ts
|
|
3738
|
-
import { existsSync as
|
|
3739
|
-
import { join as
|
|
1954
|
+
import { existsSync as existsSync17, readFileSync as readFileSync13 } from "fs";
|
|
1955
|
+
import { join as join17 } from "path";
|
|
3740
1956
|
var CATEGORIES = {
|
|
3741
1957
|
ui: [
|
|
3742
1958
|
"react",
|
|
@@ -3884,11 +2100,11 @@ function scanDependencies(projectRoot) {
|
|
|
3884
2100
|
styling: [],
|
|
3885
2101
|
other: []
|
|
3886
2102
|
};
|
|
3887
|
-
const pkgPath =
|
|
3888
|
-
if (!
|
|
2103
|
+
const pkgPath = join17(projectRoot, "package.json");
|
|
2104
|
+
if (!existsSync17(pkgPath)) return result;
|
|
3889
2105
|
let pkg;
|
|
3890
2106
|
try {
|
|
3891
|
-
pkg = JSON.parse(
|
|
2107
|
+
pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
3892
2108
|
} catch {
|
|
3893
2109
|
return result;
|
|
3894
2110
|
}
|
|
@@ -3949,12 +2165,12 @@ ${BOLD3}Analyzing project...${RESET8}
|
|
|
3949
2165
|
features,
|
|
3950
2166
|
dependencies
|
|
3951
2167
|
};
|
|
3952
|
-
const decantrDir =
|
|
3953
|
-
if (!
|
|
3954
|
-
|
|
2168
|
+
const decantrDir = join18(projectRoot, ".decantr");
|
|
2169
|
+
if (!existsSync18(decantrDir)) {
|
|
2170
|
+
mkdirSync4(decantrDir, { recursive: true });
|
|
3955
2171
|
}
|
|
3956
|
-
const outputPath =
|
|
3957
|
-
|
|
2172
|
+
const outputPath = join18(decantrDir, "analysis.json");
|
|
2173
|
+
writeFileSync9(outputPath, JSON.stringify(analysis, null, 2) + "\n", "utf-8");
|
|
3958
2174
|
console.log(`
|
|
3959
2175
|
${GREEN8}Analysis complete.${RESET8}
|
|
3960
2176
|
`);
|
|
@@ -4143,25 +2359,25 @@ async function cmdGet(type, id) {
|
|
|
4143
2359
|
};
|
|
4144
2360
|
const apiType = typeMap[type];
|
|
4145
2361
|
const registryClient = new RegistryClient({
|
|
4146
|
-
cacheDir:
|
|
2362
|
+
cacheDir: join19(process.cwd(), ".decantr", "cache")
|
|
4147
2363
|
});
|
|
4148
2364
|
const result = await registryClient.fetchContentItem(apiType, id);
|
|
4149
2365
|
if (result) {
|
|
4150
2366
|
console.log(JSON.stringify(result.data, null, 2));
|
|
4151
2367
|
return;
|
|
4152
2368
|
}
|
|
4153
|
-
const currentDir =
|
|
2369
|
+
const currentDir = dirname(fileURLToPath(import.meta.url));
|
|
4154
2370
|
const bundledCandidates = [
|
|
4155
|
-
|
|
2371
|
+
join19(currentDir, "bundled", apiType, `${id}.json`),
|
|
4156
2372
|
// Running from src/
|
|
4157
|
-
|
|
2373
|
+
join19(currentDir, "..", "src", "bundled", apiType, `${id}.json`),
|
|
4158
2374
|
// Running from dist/
|
|
4159
|
-
|
|
2375
|
+
join19(currentDir, "..", "bundled", apiType, `${id}.json`)
|
|
4160
2376
|
// Alternative dist layout
|
|
4161
2377
|
];
|
|
4162
|
-
const bundledPath = bundledCandidates.find((p) =>
|
|
2378
|
+
const bundledPath = bundledCandidates.find((p) => existsSync19(p)) || null;
|
|
4163
2379
|
if (bundledPath) {
|
|
4164
|
-
const data = JSON.parse(
|
|
2380
|
+
const data = JSON.parse(readFileSync14(bundledPath, "utf-8"));
|
|
4165
2381
|
console.log(JSON.stringify(data, null, 2));
|
|
4166
2382
|
return;
|
|
4167
2383
|
}
|
|
@@ -4173,13 +2389,13 @@ function buildRegistryContext() {
|
|
|
4173
2389
|
const themeRegistry = /* @__PURE__ */ new Map();
|
|
4174
2390
|
const patternRegistry = /* @__PURE__ */ new Map();
|
|
4175
2391
|
const projectRoot = process.cwd();
|
|
4176
|
-
const cacheDir =
|
|
4177
|
-
const customDir =
|
|
4178
|
-
const cachedThemesDir =
|
|
2392
|
+
const cacheDir = join19(projectRoot, ".decantr", "cache");
|
|
2393
|
+
const customDir = join19(projectRoot, ".decantr", "custom");
|
|
2394
|
+
const cachedThemesDir = join19(cacheDir, "@official", "themes");
|
|
4179
2395
|
try {
|
|
4180
|
-
if (
|
|
2396
|
+
if (existsSync19(cachedThemesDir)) {
|
|
4181
2397
|
for (const f of readdirSync6(cachedThemesDir).filter((f2) => f2.endsWith(".json") && f2 !== "index.json")) {
|
|
4182
|
-
const data = JSON.parse(
|
|
2398
|
+
const data = JSON.parse(readFileSync14(join19(cachedThemesDir, f), "utf-8"));
|
|
4183
2399
|
if (data.id && !themeRegistry.has(data.id)) {
|
|
4184
2400
|
themeRegistry.set(data.id, { modes: data.modes || ["light", "dark"] });
|
|
4185
2401
|
}
|
|
@@ -4187,11 +2403,11 @@ function buildRegistryContext() {
|
|
|
4187
2403
|
}
|
|
4188
2404
|
} catch {
|
|
4189
2405
|
}
|
|
4190
|
-
const customThemesDir =
|
|
2406
|
+
const customThemesDir = join19(customDir, "themes");
|
|
4191
2407
|
try {
|
|
4192
|
-
if (
|
|
2408
|
+
if (existsSync19(customThemesDir)) {
|
|
4193
2409
|
for (const f of readdirSync6(customThemesDir).filter((f2) => f2.endsWith(".json"))) {
|
|
4194
|
-
const data = JSON.parse(
|
|
2410
|
+
const data = JSON.parse(readFileSync14(join19(customThemesDir, f), "utf-8"));
|
|
4195
2411
|
if (data.id) {
|
|
4196
2412
|
themeRegistry.set(`custom:${data.id}`, { modes: data.modes || ["light", "dark"] });
|
|
4197
2413
|
}
|
|
@@ -4199,11 +2415,11 @@ function buildRegistryContext() {
|
|
|
4199
2415
|
}
|
|
4200
2416
|
} catch {
|
|
4201
2417
|
}
|
|
4202
|
-
const cachedPatternsDir =
|
|
2418
|
+
const cachedPatternsDir = join19(cacheDir, "@official", "patterns");
|
|
4203
2419
|
try {
|
|
4204
|
-
if (
|
|
2420
|
+
if (existsSync19(cachedPatternsDir)) {
|
|
4205
2421
|
for (const f of readdirSync6(cachedPatternsDir).filter((f2) => f2.endsWith(".json") && f2 !== "index.json")) {
|
|
4206
|
-
const data = JSON.parse(
|
|
2422
|
+
const data = JSON.parse(readFileSync14(join19(cachedPatternsDir, f), "utf-8"));
|
|
4207
2423
|
if (data.id && !patternRegistry.has(data.id)) {
|
|
4208
2424
|
patternRegistry.set(data.id, data);
|
|
4209
2425
|
}
|
|
@@ -4214,10 +2430,10 @@ function buildRegistryContext() {
|
|
|
4214
2430
|
return { themeRegistry, patternRegistry };
|
|
4215
2431
|
}
|
|
4216
2432
|
async function cmdValidate(path) {
|
|
4217
|
-
const essencePath = path ||
|
|
2433
|
+
const essencePath = path || join19(process.cwd(), "decantr.essence.json");
|
|
4218
2434
|
let raw;
|
|
4219
2435
|
try {
|
|
4220
|
-
raw =
|
|
2436
|
+
raw = readFileSync14(essencePath, "utf-8");
|
|
4221
2437
|
} catch {
|
|
4222
2438
|
console.error(error(`Could not read ${essencePath}`));
|
|
4223
2439
|
process.exitCode = 1;
|
|
@@ -4272,7 +2488,7 @@ async function cmdList(type) {
|
|
|
4272
2488
|
return;
|
|
4273
2489
|
}
|
|
4274
2490
|
const registryClient = new RegistryClient({
|
|
4275
|
-
cacheDir:
|
|
2491
|
+
cacheDir: join19(process.cwd(), ".decantr", "cache")
|
|
4276
2492
|
});
|
|
4277
2493
|
const result = await registryClient.fetchContentList(type);
|
|
4278
2494
|
const items = result.data.items;
|
|
@@ -4319,7 +2535,7 @@ async function cmdInit(args) {
|
|
|
4319
2535
|
}
|
|
4320
2536
|
}
|
|
4321
2537
|
const registryClient = new RegistryClient({
|
|
4322
|
-
cacheDir:
|
|
2538
|
+
cacheDir: join19(projectRoot, ".decantr", "cache"),
|
|
4323
2539
|
apiUrl: args.registry,
|
|
4324
2540
|
offline: args.offline
|
|
4325
2541
|
});
|
|
@@ -4362,11 +2578,9 @@ ${YELLOW6}You're offline. Scaffolding Decantr default.${RESET9}`);
|
|
|
4362
2578
|
);
|
|
4363
2579
|
selectedBlueprint = selected || "default";
|
|
4364
2580
|
}
|
|
4365
|
-
const
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
registryClient.fetchThemes()
|
|
4369
|
-
]);
|
|
2581
|
+
const archetypesResult = await registryClient.fetchArchetypes();
|
|
2582
|
+
const blueprintsResult = await registryClient.fetchBlueprints();
|
|
2583
|
+
const themesResult = await registryClient.fetchThemes();
|
|
4370
2584
|
if (archetypesResult.source.type === "api") {
|
|
4371
2585
|
registrySource = "api";
|
|
4372
2586
|
}
|
|
@@ -4410,15 +2624,14 @@ ${YELLOW6}You're offline. Scaffolding Decantr default.${RESET9}`);
|
|
|
4410
2624
|
blueprintRecipeName = blueprint.theme?.recipe;
|
|
4411
2625
|
if (blueprint.compose && blueprint.compose.length > 0) {
|
|
4412
2626
|
const entries = blueprint.compose;
|
|
4413
|
-
const
|
|
2627
|
+
const results = [];
|
|
2628
|
+
for (const entry of entries) {
|
|
4414
2629
|
const id = typeof entry === "string" ? entry : entry.archetype;
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
});
|
|
4421
|
-
const results = await Promise.all(fetchPromises);
|
|
2630
|
+
const r = await registryClient.fetchArchetype(id);
|
|
2631
|
+
const raw = r?.data;
|
|
2632
|
+
const inner = raw?.data ?? raw;
|
|
2633
|
+
results.push([id, inner]);
|
|
2634
|
+
}
|
|
4422
2635
|
const archetypeMap = new Map(results.map(([id, data]) => [id, data || null]));
|
|
4423
2636
|
const composed = composeArchetypes(entries, archetypeMap);
|
|
4424
2637
|
const primaryId = typeof entries[0] === "string" ? entries[0] : entries[0].archetype;
|
|
@@ -4461,8 +2674,7 @@ ${YELLOW6}You're offline. Scaffolding Decantr default.${RESET9}`);
|
|
|
4461
2674
|
}
|
|
4462
2675
|
patternSpecs = {};
|
|
4463
2676
|
if (allPatternIds.size > 0) {
|
|
4464
|
-
const
|
|
4465
|
-
const fetches = [...allPatternIds].map(async (pid) => {
|
|
2677
|
+
for (const pid of allPatternIds) {
|
|
4466
2678
|
try {
|
|
4467
2679
|
const result2 = await registryClient.fetchPattern(pid);
|
|
4468
2680
|
if (result2) {
|
|
@@ -4470,17 +2682,15 @@ ${YELLOW6}You're offline. Scaffolding Decantr default.${RESET9}`);
|
|
|
4470
2682
|
const inner = raw.data ?? raw;
|
|
4471
2683
|
const defaultPreset = inner.default_preset || "standard";
|
|
4472
2684
|
const preset = inner.presets?.[defaultPreset];
|
|
4473
|
-
|
|
2685
|
+
patternSpecs[pid] = {
|
|
4474
2686
|
description: inner.description || "",
|
|
4475
2687
|
components: inner.components || [],
|
|
4476
|
-
slots: preset?.layout?.slots || {}
|
|
4477
|
-
code: preset?.code?.example || inner.code?.example || ""
|
|
2688
|
+
slots: preset?.layout?.slots || {}
|
|
4478
2689
|
};
|
|
4479
2690
|
}
|
|
4480
2691
|
} catch {
|
|
4481
2692
|
}
|
|
4482
|
-
}
|
|
4483
|
-
await Promise.all(fetches);
|
|
2693
|
+
}
|
|
4484
2694
|
}
|
|
4485
2695
|
const zoneInputs = [];
|
|
4486
2696
|
for (const entry of entries) {
|
|
@@ -4550,6 +2760,10 @@ ${YELLOW6}You're offline. Scaffolding Decantr default.${RESET9}`);
|
|
|
4550
2760
|
}
|
|
4551
2761
|
const recipeName = blueprintRecipeName || options.theme;
|
|
4552
2762
|
const recipeResult = await registryClient.fetchRecipe(recipeName);
|
|
2763
|
+
if (process.env.DECANTR_DEBUG && recipeResult) {
|
|
2764
|
+
const dbg = recipeResult.data;
|
|
2765
|
+
console.error(` [debug] recipe source: ${recipeResult.source.type}, keys: ${Object.keys(dbg).join(",")}, has .data: ${"data" in dbg}, has .decorators: ${"decorators" in dbg}, inner.decorators: ${!!dbg.data?.decorators}`);
|
|
2766
|
+
}
|
|
4553
2767
|
if (recipeResult) {
|
|
4554
2768
|
const rawRecipe = recipeResult.data;
|
|
4555
2769
|
const recipe = rawRecipe.data ?? rawRecipe;
|
|
@@ -4598,20 +2812,23 @@ ${YELLOW6}You're offline. Scaffolding Decantr default.${RESET9}`);
|
|
|
4598
2812
|
console.log(` ${cyan("decantr upgrade")} Update to latest patterns`);
|
|
4599
2813
|
console.log(` ${cyan("decantr check")} Detect drift issues`);
|
|
4600
2814
|
console.log(` ${cyan("decantr migrate")} Migrate v2 essence to v3`);
|
|
4601
|
-
const essenceContent =
|
|
2815
|
+
const essenceContent = readFileSync14(result.essencePath, "utf-8");
|
|
4602
2816
|
const essence = JSON.parse(essenceContent);
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
2817
|
+
if (essence.version !== "3.1.0") {
|
|
2818
|
+
const validation = validateEssence2(essence);
|
|
2819
|
+
if (!validation.valid) {
|
|
2820
|
+
console.log(error(`
|
|
4606
2821
|
Validation warnings: ${validation.errors.join(", ")}`));
|
|
2822
|
+
}
|
|
4607
2823
|
}
|
|
4608
2824
|
console.log("");
|
|
4609
2825
|
let promptPages;
|
|
4610
2826
|
if (isV36(essence)) {
|
|
4611
|
-
|
|
2827
|
+
const allPages = essence.blueprint.sections ? essence.blueprint.sections.flatMap((s) => s.pages.map((p) => ({ ...p, _shell: s.shell }))) : essence.blueprint.pages || [];
|
|
2828
|
+
promptPages = allPages.map((p) => ({
|
|
4612
2829
|
id: p.id,
|
|
4613
|
-
shell: p.shell_override ?? essence.blueprint.shell,
|
|
4614
|
-
layout: p.layout.map((item) => typeof item === "string" ? item : extractPatternName(item))
|
|
2830
|
+
shell: p.shell_override ?? p._shell ?? essence.blueprint.shell,
|
|
2831
|
+
layout: (p.layout || []).map((item) => typeof item === "string" ? item : extractPatternName(item))
|
|
4615
2832
|
}));
|
|
4616
2833
|
} else {
|
|
4617
2834
|
promptPages = essence.structure || [{ id: "home", shell: options.shell, layout: ["hero"] }];
|
|
@@ -4636,16 +2853,16 @@ Validation warnings: ${validation.errors.join(", ")}`));
|
|
|
4636
2853
|
}
|
|
4637
2854
|
async function cmdStatus() {
|
|
4638
2855
|
const projectRoot = process.cwd();
|
|
4639
|
-
const essencePath =
|
|
4640
|
-
const projectJsonPath =
|
|
2856
|
+
const essencePath = join19(projectRoot, "decantr.essence.json");
|
|
2857
|
+
const projectJsonPath = join19(projectRoot, ".decantr", "project.json");
|
|
4641
2858
|
console.log(heading("Decantr Project Status"));
|
|
4642
|
-
if (!
|
|
2859
|
+
if (!existsSync19(essencePath)) {
|
|
4643
2860
|
console.log(`${RED7}No decantr.essence.json found.${RESET9}`);
|
|
4644
2861
|
console.log(dim('Run "decantr init" to create one.'));
|
|
4645
2862
|
return;
|
|
4646
2863
|
}
|
|
4647
2864
|
try {
|
|
4648
|
-
const essence = JSON.parse(
|
|
2865
|
+
const essence = JSON.parse(readFileSync14(essencePath, "utf-8"));
|
|
4649
2866
|
const validation = validateEssence2(essence);
|
|
4650
2867
|
const essenceVersion = isV36(essence) ? "v3" : "v2";
|
|
4651
2868
|
console.log(`${BOLD4}Essence:${RESET9}`);
|
|
@@ -4687,9 +2904,9 @@ async function cmdStatus() {
|
|
|
4687
2904
|
}
|
|
4688
2905
|
console.log("");
|
|
4689
2906
|
console.log(`${BOLD4}Sync Status:${RESET9}`);
|
|
4690
|
-
if (
|
|
2907
|
+
if (existsSync19(projectJsonPath)) {
|
|
4691
2908
|
try {
|
|
4692
|
-
const projectJson = JSON.parse(
|
|
2909
|
+
const projectJson = JSON.parse(readFileSync14(projectJsonPath, "utf-8"));
|
|
4693
2910
|
const syncStatus = projectJson.sync?.status || "unknown";
|
|
4694
2911
|
const lastSync = projectJson.sync?.lastSync || "never";
|
|
4695
2912
|
const source = projectJson.sync?.registrySource || "unknown";
|
|
@@ -4707,7 +2924,7 @@ async function cmdStatus() {
|
|
|
4707
2924
|
}
|
|
4708
2925
|
async function cmdSync() {
|
|
4709
2926
|
const projectRoot = process.cwd();
|
|
4710
|
-
const cacheDir =
|
|
2927
|
+
const cacheDir = join19(projectRoot, ".decantr", "cache");
|
|
4711
2928
|
console.log(heading("Syncing registry content..."));
|
|
4712
2929
|
const result = await syncRegistry(cacheDir);
|
|
4713
2930
|
if (result.synced.length > 0) {
|
|
@@ -4725,15 +2942,15 @@ async function cmdSync() {
|
|
|
4725
2942
|
}
|
|
4726
2943
|
async function cmdAudit() {
|
|
4727
2944
|
const projectRoot = process.cwd();
|
|
4728
|
-
const essencePath =
|
|
2945
|
+
const essencePath = join19(projectRoot, "decantr.essence.json");
|
|
4729
2946
|
console.log(heading("Auditing project..."));
|
|
4730
|
-
if (!
|
|
2947
|
+
if (!existsSync19(essencePath)) {
|
|
4731
2948
|
console.log(`${RED7}No decantr.essence.json found.${RESET9}`);
|
|
4732
2949
|
process.exitCode = 1;
|
|
4733
2950
|
return;
|
|
4734
2951
|
}
|
|
4735
2952
|
try {
|
|
4736
|
-
const essence = JSON.parse(
|
|
2953
|
+
const essence = JSON.parse(readFileSync14(essencePath, "utf-8"));
|
|
4737
2954
|
const validation = validateEssence2(essence);
|
|
4738
2955
|
if (!validation.valid) {
|
|
4739
2956
|
console.log(`${RED7}Essence validation failed:${RESET9}`);
|
|
@@ -4833,14 +3050,14 @@ ${BOLD4}Examples:${RESET9}
|
|
|
4833
3050
|
process.exitCode = 1;
|
|
4834
3051
|
return;
|
|
4835
3052
|
}
|
|
4836
|
-
const themePath =
|
|
4837
|
-
if (!
|
|
3053
|
+
const themePath = join19(projectRoot, ".decantr", "custom", "themes", `${name}.json`);
|
|
3054
|
+
if (!existsSync19(themePath)) {
|
|
4838
3055
|
console.error(error(`Theme "${name}" not found at ${themePath}`));
|
|
4839
3056
|
process.exitCode = 1;
|
|
4840
3057
|
return;
|
|
4841
3058
|
}
|
|
4842
3059
|
try {
|
|
4843
|
-
const theme = JSON.parse(
|
|
3060
|
+
const theme = JSON.parse(readFileSync14(themePath, "utf-8"));
|
|
4844
3061
|
const result = validateCustomTheme(theme);
|
|
4845
3062
|
if (result.valid) {
|
|
4846
3063
|
console.log(success(`Custom theme "${name}" is valid`));
|
|
@@ -5025,8 +3242,9 @@ async function main() {
|
|
|
5025
3242
|
break;
|
|
5026
3243
|
}
|
|
5027
3244
|
case "upgrade": {
|
|
5028
|
-
const { cmdUpgrade } = await import("./upgrade-
|
|
5029
|
-
|
|
3245
|
+
const { cmdUpgrade } = await import("./upgrade-I2RUTNAT.js");
|
|
3246
|
+
const applyFlag = args.includes("--apply");
|
|
3247
|
+
await cmdUpgrade(process.cwd(), { apply: applyFlag });
|
|
5030
3248
|
break;
|
|
5031
3249
|
}
|
|
5032
3250
|
case "check":
|
|
@@ -5279,5 +3497,6 @@ async function main() {
|
|
|
5279
3497
|
}
|
|
5280
3498
|
main().catch((e) => {
|
|
5281
3499
|
console.error(error(e.message));
|
|
3500
|
+
if (e.stack) console.error(e.stack);
|
|
5282
3501
|
process.exitCode = 1;
|
|
5283
3502
|
});
|