@loworbitstudio/visor 1.22.0 → 1.24.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/index.js CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync33 } from "fs";
5
- import { dirname as dirname15, join as join33 } from "path";
4
+ import { readFileSync as readFileSync34 } from "fs";
5
+ import { dirname as dirname16, join as join34 } from "path";
6
6
  import { fileURLToPath as fileURLToPath4 } from "url";
7
7
  import { Command as Command2 } from "commander";
8
8
 
9
9
  // src/commands/check.ts
10
10
  import { Command } from "commander";
11
- import { statSync as statSync3 } from "fs";
12
- import { resolve as resolve4, dirname as dirname3 } from "path";
11
+ import { statSync as statSync4 } from "fs";
12
+ import { resolve as resolve5, dirname as dirname4 } from "path";
13
13
 
14
14
  // src/registry/resolve.ts
15
15
  import { readFileSync } from "fs";
@@ -395,13 +395,160 @@ async function scanJsx(pathArg) {
395
395
  }
396
396
 
397
397
  // src/check/design.ts
398
- import { readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2, existsSync } from "fs";
399
- import { resolve as resolve2, extname as extname2, join as join3, basename, dirname as dirname2 } from "path";
398
+ import { readFileSync as readFileSync4, readdirSync as readdirSync2, statSync as statSync3, existsSync as existsSync2 } from "fs";
399
+ import { resolve as resolve3, extname as extname2, join as join4, basename, dirname as dirname3 } from "path";
400
+
401
+ // src/check/jsx-ast.ts
402
+ import { parse as babelParse } from "@babel/parser";
403
+ function parseJsx(source) {
404
+ try {
405
+ return babelParse(source, {
406
+ sourceType: "module",
407
+ plugins: ["jsx", "typescript"],
408
+ errorRecovery: true
409
+ });
410
+ } catch {
411
+ return null;
412
+ }
413
+ }
414
+
415
+ // src/check/taxonomy.ts
416
+ import { readFileSync as readFileSync3, existsSync, statSync as statSync2 } from "fs";
417
+ import { resolve as resolve2, join as join3, dirname as dirname2, isAbsolute } from "path";
418
+ var DISCOVERY_CANDIDATES = [
419
+ "taxonomy.json",
420
+ join3(".visor", "taxonomy.json"),
421
+ join3("design-prototypes", "admin-ui", "taxonomy.json")
422
+ ];
423
+ function slugToIdentifier(slug2) {
424
+ const bare = slug2.includes("/") ? slug2.slice(slug2.lastIndexOf("/") + 1) : slug2;
425
+ return bare.split(/[-_]/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
426
+ }
427
+ function refToSlug(ref) {
428
+ const bare = ref.includes("/") ? ref.slice(ref.lastIndexOf("/") + 1) : ref;
429
+ if (!/^[a-z][a-z0-9-]*$/.test(bare)) return null;
430
+ return bare;
431
+ }
432
+ function parseTaxonomy(data, path2) {
433
+ const root = data;
434
+ const tiers = Array.isArray(root?.tiers) ? root.tiers : null;
435
+ if (!tiers) {
436
+ throw new Error(`taxonomy.json at ${path2} has no "tiers" array \u2014 not a kit taxonomy.`);
437
+ }
438
+ const slugs = /* @__PURE__ */ new Set();
439
+ for (const tier of tiers) {
440
+ const families = tier.families;
441
+ if (!Array.isArray(families)) continue;
442
+ for (const family of families) {
443
+ const rows = family.rows;
444
+ if (!Array.isArray(rows)) continue;
445
+ for (const row of rows) {
446
+ const entries = row?.visor?.entries;
447
+ if (!Array.isArray(entries)) continue;
448
+ for (const entry of entries) {
449
+ if (entry?.kind !== "component" && entry?.kind !== "block") continue;
450
+ if (typeof entry.ref !== "string") continue;
451
+ const slug2 = refToSlug(entry.ref);
452
+ if (slug2) slugs.add(slug2);
453
+ }
454
+ }
455
+ }
456
+ }
457
+ if (slugs.size === 0) {
458
+ throw new Error(`taxonomy.json at ${path2} declares no component or block elements.`);
459
+ }
460
+ const identifiers = /* @__PURE__ */ new Set();
461
+ for (const slug2 of slugs) identifiers.add(slugToIdentifier(slug2));
462
+ return { path: path2, slugs, identifiers };
463
+ }
464
+ function loadFrom(path2) {
465
+ const raw = readFileSync3(path2, "utf-8");
466
+ let data;
467
+ try {
468
+ data = JSON.parse(raw);
469
+ } catch {
470
+ throw new Error(`taxonomy.json at ${path2} is not valid JSON.`);
471
+ }
472
+ return parseTaxonomy(data, path2);
473
+ }
474
+ function discover(startDir) {
475
+ let dir = startDir;
476
+ for (; ; ) {
477
+ for (const candidate of DISCOVERY_CANDIDATES) {
478
+ const full = join3(dir, candidate);
479
+ if (existsSync(full)) return full;
480
+ }
481
+ if (existsSync(join3(dir, ".git"))) return null;
482
+ const parent = dirname2(dir);
483
+ if (parent === dir) return null;
484
+ dir = parent;
485
+ }
486
+ }
487
+ function resolveTaxonomy(options) {
488
+ const { explicitPath, visorrcPath, scanPath, env = process.env } = options;
489
+ const scanAbs = resolve2(scanPath);
490
+ let scanDir = scanAbs;
491
+ try {
492
+ if (!statSync2(scanAbs).isDirectory()) scanDir = dirname2(scanAbs);
493
+ } catch {
494
+ scanDir = dirname2(scanAbs);
495
+ }
496
+ const envPath = env.VISOR_TAXONOMY;
497
+ const configured = [];
498
+ if (explicitPath) configured.push({ path: resolve2(explicitPath), source: "flag" });
499
+ else if (envPath) configured.push({ path: resolve2(envPath), source: "env" });
500
+ else if (visorrcPath) {
501
+ configured.push({
502
+ path: isAbsolute(visorrcPath) ? visorrcPath : resolve2(scanDir, visorrcPath),
503
+ source: "visorrc"
504
+ });
505
+ }
506
+ if (configured.length > 0) {
507
+ const { path: path2, source } = configured[0];
508
+ if (!existsSync(path2)) {
509
+ return {
510
+ taxonomy: null,
511
+ path: path2,
512
+ source,
513
+ error: `Kit taxonomy not found at ${path2}.`,
514
+ requested: true
515
+ };
516
+ }
517
+ try {
518
+ return { taxonomy: loadFrom(path2), path: path2, source, error: null, requested: true };
519
+ } catch (err) {
520
+ return {
521
+ taxonomy: null,
522
+ path: path2,
523
+ source,
524
+ error: err instanceof Error ? err.message : String(err),
525
+ requested: true
526
+ };
527
+ }
528
+ }
529
+ const found = discover(scanDir);
530
+ if (!found) {
531
+ return { taxonomy: null, path: null, source: null, error: null, requested: false };
532
+ }
533
+ try {
534
+ return { taxonomy: loadFrom(found), path: found, source: "discovered", error: null, requested: false };
535
+ } catch (err) {
536
+ return {
537
+ taxonomy: null,
538
+ path: found,
539
+ source: "discovered",
540
+ error: err instanceof Error ? err.message : String(err),
541
+ requested: false
542
+ };
543
+ }
544
+ }
545
+
546
+ // src/check/design.ts
400
547
  function loadVisorRc(dir) {
401
- const rcPath = join3(dir, ".visorrc.json");
402
- if (!existsSync(rcPath)) return {};
548
+ const rcPath = join4(dir, ".visorrc.json");
549
+ if (!existsSync2(rcPath)) return {};
403
550
  try {
404
- const raw = readFileSync3(rcPath, "utf-8");
551
+ const raw = readFileSync4(rcPath, "utf-8");
405
552
  return JSON.parse(raw);
406
553
  } catch {
407
554
  return {};
@@ -411,15 +558,15 @@ var CODE_EXTS = /* @__PURE__ */ new Set([".tsx", ".jsx", ".ts", ".js"]);
411
558
  var STYLE_EXTS = /* @__PURE__ */ new Set([".css", ".module.css"]);
412
559
  var ALL_EXTS = /* @__PURE__ */ new Set([...CODE_EXTS, ...STYLE_EXTS]);
413
560
  function collectFiles2(pathArg) {
414
- const abs = resolve2(pathArg);
561
+ const abs = resolve3(pathArg);
415
562
  try {
416
- const s = statSync2(abs);
563
+ const s = statSync3(abs);
417
564
  if (s.isDirectory()) {
418
565
  let recurse2 = function(dir) {
419
566
  for (const entry of readdirSync2(dir)) {
420
567
  if (entry.startsWith(".") || entry === "node_modules" || entry === "dist") continue;
421
- const full = join3(dir, entry);
422
- const es = statSync2(full);
568
+ const full = join4(dir, entry);
569
+ const es = statSync3(full);
423
570
  if (es.isDirectory()) recurse2(full);
424
571
  else if (ALL_EXTS.has(extname2(full))) files.push(full);
425
572
  }
@@ -493,6 +640,93 @@ function ruleHardcodedHex(source, filePath) {
493
640
  return found;
494
641
  }
495
642
  var PX_WHITELIST = /* @__PURE__ */ new Set(["0px", "1px", "2px", "3px"]);
643
+ var LEGACY_PX_CONTEXT_RE = /margin|padding|width|height|gap|top:|left:|right:|bottom:|font-size|line-height|min-width|max-width|min-height|max-height/;
644
+ var SIZING_PROPS = /* @__PURE__ */ new Set([
645
+ // box model
646
+ "margin",
647
+ "margintop",
648
+ "marginright",
649
+ "marginbottom",
650
+ "marginleft",
651
+ "marginblock",
652
+ "marginblockstart",
653
+ "marginblockend",
654
+ "margininline",
655
+ "margininlinestart",
656
+ "margininlineend",
657
+ "padding",
658
+ "paddingtop",
659
+ "paddingright",
660
+ "paddingbottom",
661
+ "paddingleft",
662
+ "paddingblock",
663
+ "paddingblockstart",
664
+ "paddingblockend",
665
+ "paddinginline",
666
+ "paddinginlinestart",
667
+ "paddinginlineend",
668
+ // intrinsic size
669
+ "width",
670
+ "minwidth",
671
+ "maxwidth",
672
+ "height",
673
+ "minheight",
674
+ "maxheight",
675
+ "size",
676
+ "blocksize",
677
+ "minblocksize",
678
+ "maxblocksize",
679
+ "inlinesize",
680
+ "mininlinesize",
681
+ "maxinlinesize",
682
+ // position
683
+ "top",
684
+ "right",
685
+ "bottom",
686
+ "left",
687
+ "inset",
688
+ "insetblock",
689
+ "insetinline",
690
+ // layout gaps
691
+ "gap",
692
+ "rowgap",
693
+ "columngap",
694
+ "gridgap",
695
+ "gridrowgap",
696
+ "gridcolumngap",
697
+ // typographic metrics
698
+ "fontsize",
699
+ "lineheight",
700
+ "letterspacing",
701
+ "wordspacing",
702
+ "textindent",
703
+ // corner radius
704
+ "borderradius",
705
+ "bordertopleftradius",
706
+ "bordertoprightradius",
707
+ "borderbottomleftradius",
708
+ "borderbottomrightradius",
709
+ "borderstartstartradius",
710
+ "borderstartendradius",
711
+ "borderendstartradius",
712
+ "borderendendradius",
713
+ // flex + scroll boxes
714
+ "flexbasis",
715
+ "scrollmargin",
716
+ "scrollpadding"
717
+ ]);
718
+ var PROP_NAME_RE = /(^|[^\w$])(-{0,2}[A-Za-z][\w-]*)\s*:/g;
719
+ function normalizeProp(name) {
720
+ return name.replace(/[^A-Za-z0-9]/g, "").toLowerCase();
721
+ }
722
+ function hasSizingProp(line) {
723
+ PROP_NAME_RE.lastIndex = 0;
724
+ let m;
725
+ while ((m = PROP_NAME_RE.exec(line)) !== null) {
726
+ if (SIZING_PROPS.has(normalizeProp(m[2]))) return true;
727
+ }
728
+ return false;
729
+ }
496
730
  function ruleHardcodedPx(source, filePath) {
497
731
  const found = [];
498
732
  const PX_RE = /\b(\d+(?:\.\d+)?)px\b/g;
@@ -500,24 +734,134 @@ function ruleHardcodedPx(source, filePath) {
500
734
  for (let i = 0; i < src.length; i++) {
501
735
  const l = src[i];
502
736
  if (l.trim().startsWith("//") || l.trim().startsWith("*") || l.trim().startsWith("/*")) continue;
503
- if (!/margin|padding|width|height|gap|top:|left:|right:|bottom:|font-size|line-height|min-width|max-width|min-height|max-height/.test(l)) continue;
737
+ const legacy = LEGACY_PX_CONTEXT_RE.test(l);
738
+ if (!legacy && !hasSizingProp(l)) continue;
504
739
  let m;
505
740
  PX_RE.lastIndex = 0;
506
741
  while ((m = PX_RE.exec(l)) !== null) {
507
742
  const full = m[0];
508
743
  if (PX_WHITELIST.has(full)) continue;
509
- found.push(finding(
744
+ found.push(legacy ? finding(
510
745
  filePath,
511
746
  i + 1,
512
747
  "hardcoded-px",
513
748
  "error",
514
749
  `Hardcoded pixel value "${full}" in spacing/sizing bypasses the Borealis spacing token system.`,
515
750
  "Replace with a semantic spacing token: var(--space-1), var(--space-2), var(--space-4), etc."
751
+ ) : finding(
752
+ filePath,
753
+ i + 1,
754
+ "hardcoded-px",
755
+ "warn",
756
+ `Hardcoded pixel value "${full}" in spacing/sizing bypasses the Borealis spacing token system. Reported as a warning because this property spelling was previously skipped (VI-631) \u2014 it will graduate to an error in a future major.`,
757
+ "Replace with a semantic spacing token: var(--space-1), var(--space-2), var(--space-4), etc."
758
+ ));
759
+ }
760
+ }
761
+ return found;
762
+ }
763
+ var activeKit = null;
764
+ function ruleInlineStyleObject(source, filePath) {
765
+ if (!CODE_EXTS.has(extname2(filePath))) return [];
766
+ const ast = parseJsx(source);
767
+ if (!ast) return [];
768
+ const found = [];
769
+ walk(ast, (node) => {
770
+ if (node.type !== "JSXAttribute") return;
771
+ const nameNode = node.name;
772
+ if (nameNode?.type !== "JSXIdentifier" || nameNode.name !== "style") return;
773
+ const valueNode = node.value;
774
+ if (!valueNode || valueNode.type !== "JSXExpressionContainer") return;
775
+ const expr = valueNode.expression;
776
+ if (expr?.type !== "ObjectExpression") return;
777
+ const declared = [];
778
+ for (const raw of expr.properties ?? []) {
779
+ const prop = raw;
780
+ if (prop.type !== "ObjectProperty") continue;
781
+ if (prop.computed === true) continue;
782
+ const key = prop.key;
783
+ let keyName = null;
784
+ if (key?.type === "Identifier") keyName = String(key.name ?? "");
785
+ else if (key?.type === "StringLiteral") keyName = String(key.value ?? "");
786
+ if (!keyName) continue;
787
+ if (keyName.startsWith("--")) continue;
788
+ declared.push(keyName);
789
+ }
790
+ if (declared.length === 0) return;
791
+ const loc = node.loc;
792
+ found.push(finding(
793
+ filePath,
794
+ loc?.start?.line ?? 1,
795
+ "inline-style-object",
796
+ "warn",
797
+ `Inline style object declares ${declared.map((d) => `"${d}"`).join(", ")}. Inline styles introduce styling outside the kit \u2014 untokenized, unthemeable, and invisible to the design system.`,
798
+ `Move these declarations into the component's CSS Module and reference tokens, or pass a CSS-variable bridge: style={{ "--my-token": value }}.`
799
+ ));
800
+ });
801
+ return found;
802
+ }
803
+ function ruleKitElementRedeclared(source, filePath) {
804
+ const kit = activeKit;
805
+ if (!kit) return [];
806
+ if (!CODE_EXTS.has(extname2(filePath))) return [];
807
+ if (isOwnedKitSource(filePath, kit)) return [];
808
+ const ast = parseJsx(source);
809
+ if (!ast) return [];
810
+ const program2 = ast.program;
811
+ const body = program2?.body ?? [];
812
+ const found = [];
813
+ for (const raw of body) {
814
+ for (const decl of moduleScopeDeclarations(raw)) {
815
+ if (!kit.identifiers.has(decl.name)) continue;
816
+ found.push(finding(
817
+ filePath,
818
+ decl.line,
819
+ "kit-element-redeclared",
820
+ "warn",
821
+ `Local re-declaration of the kit element "${decl.name}". This surface builds its own copy of a kit element instead of composing the kit's \u2014 the styling it introduces is outside the kit by construction.`,
822
+ `Compose the kit element instead (\`npx visor add ${identifierToSlug(decl.name)}\`), or rename this local component so it does not shadow a kit element.`
516
823
  ));
517
824
  }
518
825
  }
519
826
  return found;
520
827
  }
828
+ function moduleScopeDeclarations(raw) {
829
+ let node = raw;
830
+ if (!node) return [];
831
+ if (node.type === "ExportNamedDeclaration" || node.type === "ExportDefaultDeclaration") {
832
+ node = node.declaration;
833
+ if (!node) return [];
834
+ }
835
+ const line = node.loc?.start?.line ?? 1;
836
+ if (node.type === "FunctionDeclaration" || node.type === "ClassDeclaration") {
837
+ const id = node.id;
838
+ const name = id?.name;
839
+ return typeof name === "string" ? [{ name, line }] : [];
840
+ }
841
+ if (node.type === "VariableDeclaration") {
842
+ const out = [];
843
+ for (const d of node.declarations ?? []) {
844
+ const declarator = d;
845
+ const id = declarator.id;
846
+ if (id?.type !== "Identifier" || typeof id.name !== "string") continue;
847
+ const init = declarator.init;
848
+ const initType = init?.type;
849
+ if (initType !== "ArrowFunctionExpression" && initType !== "FunctionExpression" && initType !== "CallExpression") continue;
850
+ const dLine = declarator.loc?.start?.line ?? line;
851
+ out.push({ name: id.name, line: dLine });
852
+ }
853
+ return out;
854
+ }
855
+ return [];
856
+ }
857
+ function identifierToSlug(identifier) {
858
+ return identifier.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase();
859
+ }
860
+ function isOwnedKitSource(filePath, kit) {
861
+ const base = basename(filePath).replace(/\.(tsx|ts|jsx|js)$/, "").replace(/\.module$/, "");
862
+ if (kit.slugs.has(base)) return true;
863
+ return kit.slugs.has(basename(dirname3(filePath)));
864
+ }
521
865
  function ruleMissingDarkModeBlock(source, filePath) {
522
866
  const ext = extname2(filePath);
523
867
  if (!STYLE_EXTS.has(ext)) return [];
@@ -836,10 +1180,10 @@ function resetBaseLayerCache() {
836
1180
  baseLayerReported.clear();
837
1181
  }
838
1182
  function findProjectRoot(filePath) {
839
- let dir = dirname2(resolve2(filePath));
1183
+ let dir = dirname3(resolve3(filePath));
840
1184
  for (; ; ) {
841
- if (existsSync(join3(dir, "package.json"))) return dir;
842
- const parent = dirname2(dir);
1185
+ if (existsSync2(join4(dir, "package.json"))) return dir;
1186
+ const parent = dirname3(dir);
843
1187
  if (parent === dir) return null;
844
1188
  dir = parent;
845
1189
  }
@@ -856,10 +1200,10 @@ function collectStylesheets(root) {
856
1200
  }
857
1201
  for (const entry of entries) {
858
1202
  if (entry === "node_modules" || entry === "dist" || entry.startsWith(".")) continue;
859
- const full = join3(dir, entry);
1203
+ const full = join4(dir, entry);
860
1204
  let s;
861
1205
  try {
862
- s = statSync2(full);
1206
+ s = statSync3(full);
863
1207
  } catch {
864
1208
  continue;
865
1209
  }
@@ -878,7 +1222,7 @@ function baseLayerState(root) {
878
1222
  for (const sheet of collectStylesheets(root)) {
879
1223
  let css;
880
1224
  try {
881
- css = readFileSync3(sheet, "utf-8");
1225
+ css = readFileSync4(sheet, "utf-8");
882
1226
  } catch {
883
1227
  continue;
884
1228
  }
@@ -886,12 +1230,12 @@ function baseLayerState(root) {
886
1230
  if (PREFLIGHT_RE.test(css)) hasPreflight = true;
887
1231
  if (hasReset && hasPreflight) break;
888
1232
  }
889
- const corePath = join3(root, "node_modules", "@loworbitstudio", "visor-core");
1233
+ const corePath = join4(root, "node_modules", "@loworbitstudio", "visor-core");
890
1234
  const state = {
891
1235
  hasReset,
892
1236
  hasPreflight,
893
- visorCoreInstalled: existsSync(corePath),
894
- visorCoreExportsReset: existsSync(join3(corePath, "dist", "reset.css"))
1237
+ visorCoreInstalled: existsSync2(corePath),
1238
+ visorCoreExportsReset: existsSync2(join4(corePath, "dist", "reset.css"))
895
1239
  };
896
1240
  baseLayerStateCache.set(root, state);
897
1241
  return state;
@@ -954,12 +1298,35 @@ var RULES = [
954
1298
  { name: "line-length-over-75ch", severity: "warn", fn: ruleLineLengthOver75ch },
955
1299
  { name: "gradient-text", severity: "warn", fn: ruleGradientText },
956
1300
  { name: "excessive-card-nesting", severity: "warn", fn: ruleExcessiveCardNesting },
957
- { name: "missing-visor-base-layer", severity: "warn", fn: ruleMissingVisorBaseLayer }
1301
+ { name: "missing-visor-base-layer", severity: "warn", fn: ruleMissingVisorBaseLayer },
1302
+ // Composition (VI-631) — warning-only by default (D3) so the camelCase fix and
1303
+ // the two new assertions can be adopted without escalating an existing
1304
+ // project's exit code on first run.
1305
+ { name: "inline-style-object", severity: "warn", fn: ruleInlineStyleObject },
1306
+ { name: "kit-element-redeclared", severity: "warn", fn: ruleKitElementRedeclared }
958
1307
  ];
1308
+ var COMPOSITION_SCOPE = {
1309
+ scope: "composition-only",
1310
+ asserts: "this surface introduced no styling outside the kit",
1311
+ doesNotAssert: [
1312
+ "arrangement (right elements, wrong order)",
1313
+ "content (wrong icon, dropped hint)",
1314
+ "data / reachability"
1315
+ ],
1316
+ limit: "Green means this surface introduced no styling outside the kit. It does NOT mean the surface is on-design. Arrangement (right elements, wrong order), content (wrong icon, dropped hint), and data/reachability are not checked here and still need a human."
1317
+ };
959
1318
  function scanDesign(pathArg, options = {}) {
960
1319
  const files = collectFiles2(pathArg);
961
1320
  const { disabledRules = [], errorsOnly = false } = options;
962
1321
  resetBaseLayerCache();
1322
+ const resolution = resolveTaxonomy({
1323
+ explicitPath: options.taxonomyPath,
1324
+ visorrcPath: options.visorrcTaxonomyPath,
1325
+ scanPath: pathArg,
1326
+ env: options.env
1327
+ });
1328
+ const engaged = options.composition === true || resolution.requested || resolution.path !== null;
1329
+ activeKit = resolution.taxonomy;
963
1330
  const activeRules = RULES.filter((r) => {
964
1331
  if (disabledRules.includes(r.name)) return false;
965
1332
  if (errorsOnly && r.severity !== "error") return false;
@@ -967,20 +1334,35 @@ function scanDesign(pathArg, options = {}) {
967
1334
  });
968
1335
  const errors = [];
969
1336
  const warnings = [];
970
- for (const file of files) {
971
- let source;
972
- try {
973
- source = readFileSync3(file, "utf-8");
974
- } catch {
975
- continue;
976
- }
977
- for (const rule of activeRules) {
978
- const ruleFindings = rule.fn(source, file);
979
- for (const f of ruleFindings) {
980
- if (f.severity === "error") errors.push(f);
981
- else warnings.push(f);
1337
+ try {
1338
+ for (const file of files) {
1339
+ let source;
1340
+ try {
1341
+ source = readFileSync4(file, "utf-8");
1342
+ } catch {
1343
+ continue;
1344
+ }
1345
+ for (const rule of activeRules) {
1346
+ const ruleFindings = rule.fn(source, file);
1347
+ for (const f of ruleFindings) {
1348
+ if (errorsOnly && f.severity !== "error") continue;
1349
+ if (f.severity === "error") errors.push(f);
1350
+ else warnings.push(f);
1351
+ }
982
1352
  }
983
1353
  }
1354
+ } finally {
1355
+ activeKit = null;
1356
+ }
1357
+ if (engaged && !resolution.taxonomy && !disabledRules.includes("kit-taxonomy-missing")) {
1358
+ errors.push(finding(
1359
+ resolve3(pathArg),
1360
+ 1,
1361
+ "kit-taxonomy-missing",
1362
+ "error",
1363
+ resolution.error ?? "Kit membership could not be asserted: no taxonomy.json resolved. The composition lint fails closed rather than reporting a green it did not earn.",
1364
+ 'Point the lint at the kit definition: --taxonomy <path/to/taxonomy.json>, the VISOR_TAXONOMY environment variable, or a "taxonomy" key in .visorrc.json.'
1365
+ ));
984
1366
  }
985
1367
  return {
986
1368
  errors,
@@ -989,13 +1371,34 @@ function scanDesign(pathArg, options = {}) {
989
1371
  errorCount: errors.length,
990
1372
  warningCount: warnings.length,
991
1373
  filesScanned: files.length
1374
+ },
1375
+ composition: {
1376
+ scope: COMPOSITION_SCOPE.scope,
1377
+ asserts: COMPOSITION_SCOPE.asserts,
1378
+ doesNotAssert: [...COMPOSITION_SCOPE.doesNotAssert],
1379
+ limit: COMPOSITION_SCOPE.limit,
1380
+ kitMembership: {
1381
+ asserted: resolution.taxonomy !== null,
1382
+ taxonomyPath: resolution.path,
1383
+ source: resolution.source,
1384
+ elementCount: resolution.taxonomy?.slugs.size ?? 0,
1385
+ reason: resolution.taxonomy ? `resolved from ${resolution.source} (${resolution.taxonomy.slugs.size} kit elements)` : resolution.error ?? "no taxonomy.json resolved; pass --taxonomy <path> to assert kit membership"
1386
+ }
992
1387
  }
993
1388
  };
994
1389
  }
1390
+ function compositionScopeNotice() {
1391
+ return [
1392
+ "Composition scope \u2014 this check asserts one thing:",
1393
+ ` ${COMPOSITION_SCOPE.asserts}.`,
1394
+ "It does NOT assert the surface is on-design. Uncovered residue:",
1395
+ ...COMPOSITION_SCOPE.doesNotAssert.map((r) => ` \xB7 ${r}`)
1396
+ ];
1397
+ }
995
1398
 
996
1399
  // src/check/theme-mode.ts
997
- import { readFileSync as readFileSync4 } from "fs";
998
- import { resolve as resolve3 } from "path";
1400
+ import { readFileSync as readFileSync5 } from "fs";
1401
+ import { resolve as resolve4 } from "path";
999
1402
  import {
1000
1403
  generateThemeData,
1001
1404
  parseColor,
@@ -1044,8 +1447,8 @@ function checkThemeModeSource(yamlSource, threshold = LUMINANCE_THRESHOLD) {
1044
1447
  };
1045
1448
  }
1046
1449
  function checkThemeModeFile(filePath, threshold = LUMINANCE_THRESHOLD) {
1047
- const abs = resolve3(filePath);
1048
- const source = readFileSync4(abs, "utf-8");
1450
+ const abs = resolve4(filePath);
1451
+ const source = readFileSync5(abs, "utf-8");
1049
1452
  return checkThemeModeSource(source, threshold);
1050
1453
  }
1051
1454
 
@@ -1184,16 +1587,21 @@ function checkHasCommand(pattern2, options) {
1184
1587
  logger.success(`${result.name} [${result.type}]${cmd}`);
1185
1588
  logger.info(` ${result.description}`);
1186
1589
  }
1590
+ function printCompositionScope() {
1591
+ logger.blank();
1592
+ for (const line of compositionScopeNotice()) logger.item(line);
1593
+ logger.blank();
1594
+ }
1187
1595
  async function checkDiffCommand(pathArg, options) {
1188
1596
  const result = await scanJsx(pathArg);
1189
1597
  if (options.json) {
1190
- console.log(JSON.stringify({ success: true, ...result }, null, 2));
1191
- if (options.failOnHits && result.summary.hits > 0) process.exit(1);
1192
- process.exit(0);
1598
+ console.log(JSON.stringify({ success: true, ...result, composition: COMPOSITION_SCOPE }, null, 2));
1599
+ if (options.failOnHits && result.summary.hits > 0) process.exitCode = 1;
1193
1600
  return;
1194
1601
  }
1195
1602
  if (result.summary.hits === 0) {
1196
1603
  logger.success(`No native HTML primitives found \u2014 ${result.summary.scanned} file(s) scanned.`);
1604
+ printCompositionScope();
1197
1605
  return;
1198
1606
  }
1199
1607
  logger.heading(`Found ${result.summary.hits} native HTML usage(s) in ${result.summary.scanned} file(s):
@@ -1204,27 +1612,41 @@ async function checkDiffCommand(pathArg, options) {
1204
1612
  logger.warn(` <${f.nativeTag}> \u2192 use <${f.suggestedPrimitive}>${note}`);
1205
1613
  logger.item(` ${loc} ${f.installCmd}`);
1206
1614
  }
1207
- logger.blank();
1615
+ printCompositionScope();
1208
1616
  if (options.failOnHits) process.exit(1);
1209
1617
  }
1210
1618
  function checkDesignCommand(pathArg, options) {
1211
- const absPath = resolve4(pathArg);
1212
- const rcDir = statSync3(absPath).isDirectory() ? absPath : dirname3(absPath);
1619
+ const absPath = resolve5(pathArg);
1620
+ const rcDir = statSync4(absPath).isDirectory() ? absPath : dirname4(absPath);
1213
1621
  const rc = loadVisorRc(rcDir);
1214
1622
  const result = scanDesign(absPath, {
1215
1623
  disabledRules: rc.disabledRules ?? [],
1216
- errorsOnly: options.errorsOnly ?? false
1624
+ errorsOnly: options.errorsOnly ?? false,
1625
+ taxonomyPath: options.taxonomy,
1626
+ visorrcTaxonomyPath: rc.taxonomy,
1627
+ composition: options.composition ?? false
1217
1628
  });
1218
1629
  const useJson = options.json || options.format === "json";
1630
+ const shouldFail = options.fail !== false;
1219
1631
  if (useJson) {
1220
1632
  console.log(JSON.stringify({ success: true, ...result }, null, 2));
1221
- if (!options.noFail && result.summary.errorCount > 0) process.exit(1);
1222
- process.exit(0);
1633
+ if (shouldFail && result.summary.errorCount > 0) process.exitCode = 1;
1223
1634
  return;
1224
1635
  }
1225
- const { errors, warnings, summary } = result;
1636
+ const { errors, warnings, summary, composition } = result;
1637
+ const printKitMembership = () => {
1638
+ const km = composition.kitMembership;
1639
+ if (km.asserted) {
1640
+ logger.item(`Kit membership: asserted against ${km.taxonomyPath} (${km.elementCount} element(s), via ${km.source}).`);
1641
+ } else {
1642
+ logger.item(`Kit membership: NOT asserted \u2014 ${km.reason}`);
1643
+ }
1644
+ };
1226
1645
  if (summary.errorCount === 0 && summary.warningCount === 0) {
1227
1646
  logger.success(`No design anti-patterns found \u2014 ${summary.filesScanned} file(s) scanned.`);
1647
+ printCompositionScope();
1648
+ printKitMembership();
1649
+ logger.blank();
1228
1650
  process.exit(0);
1229
1651
  return;
1230
1652
  }
@@ -1256,8 +1678,10 @@ function checkDesignCommand(pathArg, options) {
1256
1678
  } else {
1257
1679
  logger.warn(`${summary.warningCount} warning(s) (0 errors)`);
1258
1680
  }
1681
+ printCompositionScope();
1682
+ printKitMembership();
1259
1683
  logger.blank();
1260
- if (!options.noFail && summary.errorCount > 0) process.exit(1);
1684
+ if (shouldFail && summary.errorCount > 0) process.exit(1);
1261
1685
  }
1262
1686
  function checkThemeModeCommand(pathArg, options) {
1263
1687
  let result;
@@ -1307,7 +1731,7 @@ function checkCommand() {
1307
1731
  check.command("diff").description("Scan JSX/TSX for native HTML elements that have Visor equivalents").argument("<path>", "file path, directory, or - for stdin").option("--fail-on-hits", "exit 1 if any native HTML usages are found (for CI use)").option("--json", "output structured JSON (for AI agents)").action(async (pathArg, options) => {
1308
1732
  await checkDiffCommand(pathArg, options);
1309
1733
  });
1310
- check.command("design").description("Scan frontend code for Borealis design anti-patterns (deterministic, no LLM)").argument("<path>", "file path or directory to scan").option("--format <format>", "output format: json or human (default: human when TTY, json otherwise)").option("--errors-only", "report only error-severity rules (skip warnings)").option("--no-fail", "do not exit 1 on errors (advisory mode)").option("--json", "shorthand for --format json").action((pathArg, options) => {
1734
+ check.command("design").description("Scan frontend code for Borealis design anti-patterns (deterministic, no LLM). Asserts composition \u2014 that the surface introduced no styling outside the kit \u2014 never arrangement.").argument("<path>", "file path or directory to scan").option("--format <format>", "output format: json or human (default: human when TTY, json otherwise)").option("--errors-only", "report only error-severity rules (skip warnings)").option("--no-fail", "do not exit 1 on errors (advisory mode)").option("--json", "shorthand for --format json").option("--taxonomy <path>", "path to the kit's taxonomy.json \u2014 the definition kit membership is asserted against").option("--composition", "require the kit-membership assertion: fail closed when no taxonomy.json resolves").action((pathArg, options) => {
1311
1735
  checkDesignCommand(pathArg, options);
1312
1736
  });
1313
1737
  check.command("theme-mode").description("Assert a theme's rendered app-root background matches its declared color-scheme (dark-only/light-only/adaptive)").argument("<path>", "path to a .visor.yaml theme file").option("--format <format>", "output format: json or human (default: human)").option("--no-fail", "do not exit 1 on a mode mismatch (advisory mode)").option("--json", "shorthand for --format json").action((pathArg, options) => {
@@ -1317,14 +1741,14 @@ function checkCommand() {
1317
1741
  }
1318
1742
 
1319
1743
  // src/commands/init.ts
1320
- import { existsSync as existsSync6, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync9 } from "fs";
1321
- import { join as join8, dirname as dirname5, basename as basename2 } from "path";
1744
+ import { existsSync as existsSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync10 } from "fs";
1745
+ import { join as join9, dirname as dirname6, basename as basename2 } from "path";
1322
1746
  import { fileURLToPath as fileURLToPath2 } from "url";
1323
1747
  import * as childProcess2 from "child_process";
1324
1748
 
1325
1749
  // src/config/config.ts
1326
- import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync2 } from "fs";
1327
- import { join as join4 } from "path";
1750
+ import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync3 } from "fs";
1751
+ import { join as join5 } from "path";
1328
1752
 
1329
1753
  // src/config/defaults.ts
1330
1754
  var DEFAULT_CONFIG = {
@@ -1341,19 +1765,19 @@ var CONFIG_FILE = "visor.json";
1341
1765
 
1342
1766
  // src/config/config.ts
1343
1767
  function getConfigPath(cwd) {
1344
- return join4(cwd, CONFIG_FILE);
1768
+ return join5(cwd, CONFIG_FILE);
1345
1769
  }
1346
1770
  function configExists(cwd) {
1347
- return existsSync2(getConfigPath(cwd));
1771
+ return existsSync3(getConfigPath(cwd));
1348
1772
  }
1349
1773
  function loadConfig(cwd) {
1350
1774
  const configPath = getConfigPath(cwd);
1351
- if (!existsSync2(configPath)) {
1775
+ if (!existsSync3(configPath)) {
1352
1776
  throw new Error(
1353
1777
  `No ${CONFIG_FILE} found. Run "visor init" first.`
1354
1778
  );
1355
1779
  }
1356
- const raw = readFileSync5(configPath, "utf-8");
1780
+ const raw = readFileSync6(configPath, "utf-8");
1357
1781
  const parsed = JSON.parse(raw);
1358
1782
  const knownKeys = /* @__PURE__ */ new Set(["paths"]);
1359
1783
  for (const key of Object.keys(parsed)) {
@@ -1390,12 +1814,12 @@ function writeConfig(cwd, config) {
1390
1814
 
1391
1815
  // src/utils/packages.ts
1392
1816
  import { execFileSync } from "child_process";
1393
- import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
1394
- import { join as join5 } from "path";
1817
+ import { existsSync as existsSync4, readFileSync as readFileSync7 } from "fs";
1818
+ import { join as join6 } from "path";
1395
1819
  function readPackageJson(cwd) {
1396
- const pkgPath = join5(cwd, "package.json");
1397
- if (!existsSync3(pkgPath)) return null;
1398
- return JSON.parse(readFileSync6(pkgPath, "utf-8"));
1820
+ const pkgPath = join6(cwd, "package.json");
1821
+ if (!existsSync4(pkgPath)) return null;
1822
+ return JSON.parse(readFileSync7(pkgPath, "utf-8"));
1399
1823
  }
1400
1824
  function isPackageInstalled(packageName, cwd) {
1401
1825
  const pkg2 = readPackageJson(cwd);
@@ -1505,8 +1929,8 @@ export default function RootLayout({ children }: { children: ReactNode }) {
1505
1929
  }
1506
1930
 
1507
1931
  // src/commands/init-plays/registry.ts
1508
- import { existsSync as existsSync4, mkdirSync, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
1509
- import { dirname as dirname4, join as join6 } from "path";
1932
+ import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
1933
+ import { dirname as dirname5, join as join7 } from "path";
1510
1934
 
1511
1935
  // src/commands/init-plays/pattern-build.ts
1512
1936
  var patternBuildPlay = {
@@ -1545,24 +1969,24 @@ function knownPlayIds() {
1545
1969
  return KNOWN_PLAYS.map((p) => p.id);
1546
1970
  }
1547
1971
  function playStatePaths(cwd, def, name) {
1548
- const relDir = join6(".lo", def.loSubdir, name);
1549
- const dir = join6(cwd, relDir);
1972
+ const relDir = join7(".lo", def.loSubdir, name);
1973
+ const dir = join7(cwd, relDir);
1550
1974
  return {
1551
1975
  dir,
1552
- statePath: join6(dir, "state.json"),
1553
- relStatePath: join6(relDir, "state.json")
1976
+ statePath: join7(dir, "state.json"),
1977
+ relStatePath: join7(relDir, "state.json")
1554
1978
  };
1555
1979
  }
1556
1980
  function readPlayState(statePath) {
1557
- if (!existsSync4(statePath)) return null;
1981
+ if (!existsSync5(statePath)) return null;
1558
1982
  try {
1559
- return JSON.parse(readFileSync7(statePath, "utf-8"));
1983
+ return JSON.parse(readFileSync8(statePath, "utf-8"));
1560
1984
  } catch {
1561
1985
  return null;
1562
1986
  }
1563
1987
  }
1564
1988
  function writePlayState(statePath, state) {
1565
- mkdirSync(dirname4(statePath), { recursive: true });
1989
+ mkdirSync(dirname5(statePath), { recursive: true });
1566
1990
  writeFileSync2(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
1567
1991
  }
1568
1992
 
@@ -1627,16 +2051,16 @@ function heuristicPort(name) {
1627
2051
  }
1628
2052
 
1629
2053
  // src/lib/lo-play-checklist.ts
1630
- import { existsSync as existsSync5, readFileSync as readFileSync8 } from "fs";
2054
+ import { existsSync as existsSync6, readFileSync as readFileSync9 } from "fs";
1631
2055
  import { homedir } from "os";
1632
- import { join as join7 } from "path";
2056
+ import { join as join8 } from "path";
1633
2057
  function defaultChecklistRoot() {
1634
- return join7(homedir(), ".claude", "skills", "lo-play");
2058
+ return join8(homedir(), ".claude", "skills", "lo-play");
1635
2059
  }
1636
2060
  function readEntryChecklist(playType, options = {}) {
1637
2061
  const root = options.root ?? defaultChecklistRoot();
1638
- const path2 = join7(root, playType, "entry-checklist.md");
1639
- if (!existsSync5(path2)) {
2062
+ const path2 = join8(root, playType, "entry-checklist.md");
2063
+ if (!existsSync6(path2)) {
1640
2064
  return {
1641
2065
  found: false,
1642
2066
  path: path2,
@@ -1644,7 +2068,7 @@ function readEntryChecklist(playType, options = {}) {
1644
2068
  };
1645
2069
  }
1646
2070
  try {
1647
- return { found: true, path: path2, content: readFileSync8(path2, "utf-8") };
2071
+ return { found: true, path: path2, content: readFileSync9(path2, "utf-8") };
1648
2072
  } catch {
1649
2073
  return {
1650
2074
  found: false,
@@ -1686,7 +2110,7 @@ function initCommand(cwd, options) {
1686
2110
  process.exit(1);
1687
2111
  }
1688
2112
  }
1689
- if (options?.template === "nextjs" && existsSync6(join8(cwd, "package.json"))) {
2113
+ if (options?.template === "nextjs" && existsSync7(join9(cwd, "package.json"))) {
1690
2114
  emitError(
1691
2115
  json,
1692
2116
  "package.json already exists in this directory. visor init --template nextjs only scaffolds into empty directories. For an existing app, see the retrofit flow: https://visor.loworbit.studio/docs/guides/migration"
@@ -1857,8 +2281,8 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
1857
2281
  runCreateNextApp(cwd, json);
1858
2282
  writeVisorConfig(cwd, json, filesCreated, filesSkipped);
1859
2283
  runInstallVisorDeps(cwd, json);
1860
- const yamlPath = join8(cwd, ".visor.yaml");
1861
- if (existsSync6(yamlPath)) {
2284
+ const yamlPath = join9(cwd, ".visor.yaml");
2285
+ if (existsSync7(yamlPath)) {
1862
2286
  filesSkipped.push(".visor.yaml");
1863
2287
  if (!json) {
1864
2288
  logger.warn(".visor.yaml already exists. Skipping.");
@@ -1876,10 +2300,10 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
1876
2300
  tokens: data.tokens,
1877
2301
  config: data.config
1878
2302
  });
1879
- const globalsPath = join8(cwd, "app", "globals.css");
1880
- const globalsDir = dirname5(globalsPath);
2303
+ const globalsPath = join9(cwd, "app", "globals.css");
2304
+ const globalsDir = dirname6(globalsPath);
1881
2305
  mkdirSync2(globalsDir, { recursive: true });
1882
- if (existsSync6(globalsPath)) {
2306
+ if (existsSync7(globalsPath)) {
1883
2307
  writeFileSync3(globalsPath, css, "utf-8");
1884
2308
  filesCreated.push("app/globals.css");
1885
2309
  } else {
@@ -1889,16 +2313,16 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
1889
2313
  if (!json) {
1890
2314
  logger.success("Created app/globals.css with theme tokens");
1891
2315
  }
1892
- const layoutPath = join8(cwd, "app", "layout.tsx");
2316
+ const layoutPath = join9(cwd, "app", "layout.tsx");
1893
2317
  const colorScheme = extractColorScheme(NEXTJS_STARTER_YAML);
1894
2318
  writeFileSync3(layoutPath, generateNextjsLayout(colorScheme), "utf-8");
1895
2319
  filesCreated.push("app/layout.tsx");
1896
2320
  if (!json) {
1897
2321
  logger.success("Wired app/layout.tsx with FOWT prevention and theme tokens");
1898
2322
  }
1899
- const stampDir = join8(cwd, ".lo");
1900
- const stampPath = join8(stampDir, "borealis.json");
1901
- if (existsSync6(stampPath)) {
2323
+ const stampDir = join9(cwd, ".lo");
2324
+ const stampPath = join9(stampDir, "borealis.json");
2325
+ if (existsSync7(stampPath)) {
1902
2326
  filesSkipped.push(".lo/borealis.json");
1903
2327
  if (!json) {
1904
2328
  logger.warn(".lo/borealis.json already exists. Skipping.");
@@ -1962,12 +2386,12 @@ function assertSpawnSuccess(result, label) {
1962
2386
  }
1963
2387
  function readVisorCliVersion() {
1964
2388
  try {
1965
- const here = dirname5(fileURLToPath2(import.meta.url));
2389
+ const here = dirname6(fileURLToPath2(import.meta.url));
1966
2390
  for (let i = 0; i < 5; i++) {
1967
2391
  const segments = new Array(i).fill("..");
1968
- const candidate = join8(here, ...segments, "package.json");
2392
+ const candidate = join9(here, ...segments, "package.json");
1969
2393
  try {
1970
- const pkg2 = JSON.parse(readFileSync9(candidate, "utf-8"));
2394
+ const pkg2 = JSON.parse(readFileSync10(candidate, "utf-8"));
1971
2395
  if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) {
1972
2396
  return pkg2.version;
1973
2397
  }
@@ -1982,52 +2406,52 @@ function readVisorCliVersion() {
1982
2406
  // src/utils/fs.ts
1983
2407
  import {
1984
2408
  writeFileSync as writeFileSync4,
1985
- readFileSync as readFileSync10,
1986
- existsSync as existsSync7,
2409
+ readFileSync as readFileSync11,
2410
+ existsSync as existsSync8,
1987
2411
  mkdirSync as mkdirSync3
1988
2412
  } from "fs";
1989
- import { dirname as dirname6, join as join9 } from "path";
2413
+ import { dirname as dirname7, join as join10 } from "path";
1990
2414
  function resolveOutputPath(registryPath, type, config, cwd) {
1991
2415
  let relativePath;
1992
2416
  if (type === "registry:block") {
1993
2417
  relativePath = registryPath.replace(/^blocks\//, "");
1994
- return join9(cwd, config.paths.blocks, relativePath);
2418
+ return join10(cwd, config.paths.blocks, relativePath);
1995
2419
  }
1996
2420
  if (type === "registry:ui") {
1997
2421
  if (registryPath.startsWith("components/deck/")) {
1998
2422
  relativePath = registryPath.replace(/^components\/deck\//, "");
1999
- return join9(cwd, config.paths.deckComponents, relativePath);
2423
+ return join10(cwd, config.paths.deckComponents, relativePath);
2000
2424
  }
2001
2425
  if (registryPath.startsWith("components/flutter/")) {
2002
2426
  relativePath = registryPath.replace(/^components\/flutter\//, "");
2003
- return join9(cwd, config.paths.flutterComponents, relativePath);
2427
+ return join10(cwd, config.paths.flutterComponents, relativePath);
2004
2428
  }
2005
2429
  relativePath = registryPath.replace(/^components\/ui\//, "");
2006
- return join9(cwd, config.paths.components, relativePath);
2430
+ return join10(cwd, config.paths.components, relativePath);
2007
2431
  }
2008
2432
  if (type === "registry:hook") {
2009
2433
  relativePath = registryPath.replace(/^hooks\//, "");
2010
- return join9(cwd, config.paths.hooks, relativePath);
2434
+ return join10(cwd, config.paths.hooks, relativePath);
2011
2435
  }
2012
2436
  if (type === "registry:lib") {
2013
2437
  relativePath = registryPath.replace(/^lib\//, "");
2014
- return join9(cwd, config.paths.lib, relativePath);
2438
+ return join10(cwd, config.paths.lib, relativePath);
2015
2439
  }
2016
- return join9(cwd, registryPath);
2440
+ return join10(cwd, registryPath);
2017
2441
  }
2018
2442
  function writeFile(filePath, content) {
2019
- const dir = dirname6(filePath);
2020
- if (!existsSync7(dir)) {
2443
+ const dir = dirname7(filePath);
2444
+ if (!existsSync8(dir)) {
2021
2445
  mkdirSync3(dir, { recursive: true });
2022
2446
  }
2023
2447
  writeFileSync4(filePath, content, "utf-8");
2024
2448
  }
2025
2449
  function readFile(filePath) {
2026
- if (!existsSync7(filePath)) return null;
2027
- return readFileSync10(filePath, "utf-8");
2450
+ if (!existsSync8(filePath)) return null;
2451
+ return readFileSync11(filePath, "utf-8");
2028
2452
  }
2029
2453
  function fileExists(filePath) {
2030
- return existsSync7(filePath);
2454
+ return existsSync8(filePath);
2031
2455
  }
2032
2456
 
2033
2457
  // src/commands/list.ts
@@ -2168,8 +2592,8 @@ function listCommand(cwd, options = {}) {
2168
2592
  }
2169
2593
 
2170
2594
  // src/utils/pubspec.ts
2171
- import { existsSync as existsSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
2172
- import { join as join10 } from "path";
2595
+ import { existsSync as existsSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
2596
+ import { join as join11 } from "path";
2173
2597
  import { parseDocument, YAMLMap } from "yaml";
2174
2598
  function mergePubspec(pubspecText, deps) {
2175
2599
  const doc = parseDocument(pubspecText);
@@ -2191,14 +2615,14 @@ function mergePubspec(pubspecText, deps) {
2191
2615
  return { text: doc.toString(), added, skipped };
2192
2616
  }
2193
2617
  function pubspecPath(cwd) {
2194
- return join10(cwd, "pubspec.yaml");
2618
+ return join11(cwd, "pubspec.yaml");
2195
2619
  }
2196
2620
  function pubspecExists(cwd) {
2197
- return existsSync8(pubspecPath(cwd));
2621
+ return existsSync9(pubspecPath(cwd));
2198
2622
  }
2199
2623
  function isPubPackageInstalled(packageName, cwd) {
2200
2624
  if (!pubspecExists(cwd)) return false;
2201
- const text = readFileSync11(pubspecPath(cwd), "utf-8");
2625
+ const text = readFileSync12(pubspecPath(cwd), "utf-8");
2202
2626
  const doc = parseDocument(text);
2203
2627
  const depsNode = doc.get("dependencies");
2204
2628
  if (!(depsNode instanceof YAMLMap)) return false;
@@ -2209,12 +2633,12 @@ function getUninstalledPubDeps(deps, cwd) {
2209
2633
  }
2210
2634
  function addPubDependencies(deps, cwd) {
2211
2635
  const path2 = pubspecPath(cwd);
2212
- if (!existsSync8(path2)) {
2636
+ if (!existsSync9(path2)) {
2213
2637
  throw new Error(
2214
2638
  `No pubspec.yaml found at ${path2}. Run this command from a Flutter project root.`
2215
2639
  );
2216
2640
  }
2217
- const text = readFileSync11(path2, "utf-8");
2641
+ const text = readFileSync12(path2, "utf-8");
2218
2642
  const result = mergePubspec(text, deps);
2219
2643
  if (result.added.length > 0) {
2220
2644
  writeFileSync5(path2, result.text, "utf-8");
@@ -2224,12 +2648,12 @@ function addPubDependencies(deps, cwd) {
2224
2648
 
2225
2649
  // src/utils/flutter.ts
2226
2650
  import { execFileSync as execFileSync2 } from "child_process";
2227
- import { existsSync as existsSync9, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
2651
+ import { existsSync as existsSync10, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
2228
2652
  import { homedir as homedir2 } from "os";
2229
- import { join as join11 } from "path";
2653
+ import { join as join12 } from "path";
2230
2654
  function isExecutable(path2) {
2231
2655
  try {
2232
- const s = statSync4(path2);
2656
+ const s = statSync5(path2);
2233
2657
  return s.isFile();
2234
2658
  } catch {
2235
2659
  return false;
@@ -2241,20 +2665,20 @@ function fromPath(env) {
2241
2665
  const bin = process.platform === "win32" ? "flutter.bat" : "flutter";
2242
2666
  for (const dir of pathVar.split(sep2)) {
2243
2667
  if (!dir) continue;
2244
- const candidate = join11(dir, bin);
2668
+ const candidate = join12(dir, bin);
2245
2669
  if (isExecutable(candidate)) return candidate;
2246
2670
  }
2247
2671
  return null;
2248
2672
  }
2249
2673
  function fromFvm(home) {
2250
- const fvmDefault = join11(home, "fvm", "default", "bin", "flutter");
2674
+ const fvmDefault = join12(home, "fvm", "default", "bin", "flutter");
2251
2675
  if (isExecutable(fvmDefault)) return fvmDefault;
2252
- const versionsDir = join11(home, "fvm", "versions");
2253
- if (!existsSync9(versionsDir)) return null;
2676
+ const versionsDir = join12(home, "fvm", "versions");
2677
+ if (!existsSync10(versionsDir)) return null;
2254
2678
  let best = null;
2255
2679
  try {
2256
2680
  for (const name of readdirSync3(versionsDir)) {
2257
- const candidate = join11(versionsDir, name, "bin", "flutter");
2681
+ const candidate = join12(versionsDir, name, "bin", "flutter");
2258
2682
  if (!isExecutable(candidate)) continue;
2259
2683
  if (!best || compareSemver(name, best.version) > 0) {
2260
2684
  best = { version: name, path: candidate };
@@ -2280,7 +2704,7 @@ function findFlutterBin(options = {}) {
2280
2704
  const home = options.home ?? homedir2();
2281
2705
  const envRoot = env.FLUTTER_ROOT;
2282
2706
  if (envRoot) {
2283
- const bin = join11(envRoot, "bin", "flutter");
2707
+ const bin = join12(envRoot, "bin", "flutter");
2284
2708
  if (isExecutable(bin)) return bin;
2285
2709
  }
2286
2710
  const fromPathBin = fromPath(env);
@@ -2943,8 +3367,8 @@ function infoCommand(name, cwd, options = {}) {
2943
3367
  }
2944
3368
 
2945
3369
  // src/commands/theme-apply.ts
2946
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
2947
- import { resolve as resolve7, dirname as dirname8, join as join15 } from "path";
3370
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
3371
+ import { resolve as resolve8, dirname as dirname9, join as join16 } from "path";
2948
3372
  import { generateTheme, generateThemeData as generateThemeData3 } from "@loworbitstudio/visor-theme-engine";
2949
3373
  import {
2950
3374
  nextjsAdapter as nextjsAdapter2,
@@ -2955,8 +3379,8 @@ import {
2955
3379
  } from "@loworbitstudio/visor-theme-engine/adapters";
2956
3380
 
2957
3381
  // src/lib/blessed-manifest.ts
2958
- import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
2959
- import { join as join12 } from "path";
3382
+ import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
3383
+ import { join as join13 } from "path";
2960
3384
  import { z } from "zod";
2961
3385
  var BLESSED_MANIFEST_FILENAME = "blessed-manifest.json";
2962
3386
  var themeApplyTargetSchema = z.discriminatedUnion("kind", [
@@ -2988,8 +3412,8 @@ var blessedManifestSchema = z.object({
2988
3412
  theme_apply_target: themeApplyTargetSchema.optional()
2989
3413
  }).strict();
2990
3414
  function parseBlessedManifest(buildDir) {
2991
- const manifestPath = join12(buildDir, BLESSED_MANIFEST_FILENAME);
2992
- if (!existsSync10(manifestPath)) {
3415
+ const manifestPath = join13(buildDir, BLESSED_MANIFEST_FILENAME);
3416
+ if (!existsSync11(manifestPath)) {
2993
3417
  return {
2994
3418
  ok: false,
2995
3419
  error: `this directory is not a blessed build (missing ${BLESSED_MANIFEST_FILENAME}); see docs/blessed-builds.md`
@@ -2997,7 +3421,7 @@ function parseBlessedManifest(buildDir) {
2997
3421
  }
2998
3422
  let raw;
2999
3423
  try {
3000
- raw = readFileSync12(manifestPath, "utf-8");
3424
+ raw = readFileSync13(manifestPath, "utf-8");
3001
3425
  } catch {
3002
3426
  return { ok: false, error: `could not read ${manifestPath}` };
3003
3427
  }
@@ -3023,31 +3447,31 @@ function parseBlessedManifest(buildDir) {
3023
3447
  }
3024
3448
 
3025
3449
  // src/lib/theme-apply-targets/globals-css.ts
3026
- import { existsSync as existsSync11, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
3027
- import { dirname as dirname7, isAbsolute, join as join13, resolve as resolve5 } from "path";
3450
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
3451
+ import { dirname as dirname8, isAbsolute as isAbsolute2, join as join14, resolve as resolve6 } from "path";
3028
3452
  function applyGlobalsCss(input) {
3029
3453
  const outFile = input.path !== void 0 ? resolveInsideBuild(input.buildDir, input.path) : resolveDefaultGlobalsCss(input.buildDir);
3030
- mkdirSync4(dirname7(outFile), { recursive: true });
3454
+ mkdirSync4(dirname8(outFile), { recursive: true });
3031
3455
  writeFileSync6(outFile, input.adapterCss, "utf-8");
3032
3456
  return outFile;
3033
3457
  }
3034
3458
  function resolveDefaultGlobalsCss(buildDir) {
3035
3459
  const candidates = [
3036
- join13(buildDir, "app", "globals.css"),
3037
- join13(buildDir, "src", "app", "globals.css")
3460
+ join14(buildDir, "app", "globals.css"),
3461
+ join14(buildDir, "src", "app", "globals.css")
3038
3462
  ];
3039
- return candidates.find((candidate) => existsSync11(candidate)) ?? candidates[0];
3463
+ return candidates.find((candidate) => existsSync12(candidate)) ?? candidates[0];
3040
3464
  }
3041
3465
  function resolveInsideBuild(buildDir, relativeOrAbsolute) {
3042
- return isAbsolute(relativeOrAbsolute) ? relativeOrAbsolute : resolve5(buildDir, relativeOrAbsolute);
3466
+ return isAbsolute2(relativeOrAbsolute) ? relativeOrAbsolute : resolve6(buildDir, relativeOrAbsolute);
3043
3467
  }
3044
3468
 
3045
3469
  // src/lib/theme-apply-targets/themes-css-dir.ts
3046
3470
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
3047
- import { isAbsolute as isAbsolute2, join as join14, resolve as resolve6 } from "path";
3471
+ import { isAbsolute as isAbsolute3, join as join15, resolve as resolve7 } from "path";
3048
3472
  function applyThemesCssDir(input) {
3049
- const dir = isAbsolute2(input.path) ? input.path : resolve6(input.buildDir, input.path);
3050
- const outFile = join14(dir, `${input.themeId}.css`);
3473
+ const dir = isAbsolute3(input.path) ? input.path : resolve7(input.buildDir, input.path);
3474
+ const outFile = join15(dir, `${input.themeId}.css`);
3051
3475
  mkdirSync5(dir, { recursive: true });
3052
3476
  writeFileSync7(outFile, input.adapterCss, "utf-8");
3053
3477
  return outFile;
@@ -3111,10 +3535,10 @@ function defaultOutputPath(adapter, themeName) {
3111
3535
  }
3112
3536
  }
3113
3537
  function themeApplyCommand(file, cwd, options) {
3114
- const filePath = resolve7(cwd, file);
3538
+ const filePath = resolve8(cwd, file);
3115
3539
  let yamlContent;
3116
3540
  try {
3117
- yamlContent = readFileSync13(filePath, "utf-8");
3541
+ yamlContent = readFileSync14(filePath, "utf-8");
3118
3542
  } catch {
3119
3543
  if (options.json) {
3120
3544
  console.log(
@@ -3208,7 +3632,7 @@ function themeApplyCommand(file, cwd, options) {
3208
3632
  else logger.error(message);
3209
3633
  process.exit(1);
3210
3634
  }
3211
- const buildDir = resolve7(cwd, options.targetPath);
3635
+ const buildDir = resolve8(cwd, options.targetPath);
3212
3636
  const manifestResult = parseBlessedManifest(buildDir);
3213
3637
  if (!manifestResult.ok) {
3214
3638
  if (options.json) {
@@ -3251,14 +3675,14 @@ function themeApplyCommand(file, cwd, options) {
3251
3675
  return;
3252
3676
  }
3253
3677
  const outputTarget = options.output ?? defaultOutputPath(options.adapter, themeName);
3254
- const outputPath = resolve7(cwd, outputTarget);
3678
+ const outputPath = resolve8(cwd, outputTarget);
3255
3679
  if (fileMap) {
3256
3680
  try {
3257
3681
  mkdirSync6(outputPath, { recursive: true });
3258
3682
  let totalBytes = 0;
3259
3683
  for (const [relPath, content] of Object.entries(fileMap.files)) {
3260
- const filePath2 = join15(outputPath, relPath);
3261
- mkdirSync6(dirname8(filePath2), { recursive: true });
3684
+ const filePath2 = join16(outputPath, relPath);
3685
+ mkdirSync6(dirname9(filePath2), { recursive: true });
3262
3686
  writeFileSync8(filePath2, content, "utf-8");
3263
3687
  totalBytes += content.length;
3264
3688
  }
@@ -3296,7 +3720,7 @@ function themeApplyCommand(file, cwd, options) {
3296
3720
  if (css === null) {
3297
3721
  process.exit(1);
3298
3722
  }
3299
- const outputDir = dirname8(outputPath);
3723
+ const outputDir = dirname9(outputPath);
3300
3724
  try {
3301
3725
  mkdirSync6(outputDir, { recursive: true });
3302
3726
  writeFileSync8(outputPath, css, "utf-8");
@@ -3340,8 +3764,8 @@ function formatSize(bytes) {
3340
3764
  }
3341
3765
 
3342
3766
  // src/commands/theme-export.ts
3343
- import { readFileSync as readFileSync14 } from "fs";
3344
- import { resolve as resolve8 } from "path";
3767
+ import { readFileSync as readFileSync15 } from "fs";
3768
+ import { resolve as resolve9 } from "path";
3345
3769
  import {
3346
3770
  parseConfig,
3347
3771
  resolveConfig,
@@ -3349,10 +3773,10 @@ import {
3349
3773
  exportTheme
3350
3774
  } from "@loworbitstudio/visor-theme-engine";
3351
3775
  function themeExportCommand(file, cwd, options) {
3352
- const filePath = resolve8(cwd, file ?? ".visor.yaml");
3776
+ const filePath = resolve9(cwd, file ?? ".visor.yaml");
3353
3777
  let yamlContent;
3354
3778
  try {
3355
- yamlContent = readFileSync14(filePath, "utf-8");
3779
+ yamlContent = readFileSync15(filePath, "utf-8");
3356
3780
  } catch {
3357
3781
  if (options.json) {
3358
3782
  console.log(
@@ -3404,16 +3828,16 @@ function themeExportCommand(file, cwd, options) {
3404
3828
  }
3405
3829
 
3406
3830
  // src/commands/theme-validate.ts
3407
- import { readFileSync as readFileSync15 } from "fs";
3408
- import { resolve as resolve9 } from "path";
3831
+ import { readFileSync as readFileSync16 } from "fs";
3832
+ import { resolve as resolve10 } from "path";
3409
3833
  import { parse as parseYaml2 } from "yaml";
3410
3834
  import { validate } from "@loworbitstudio/visor-theme-engine";
3411
3835
  import pc3 from "picocolors";
3412
3836
  function themeValidateCommand(file, cwd, options) {
3413
- const filePath = resolve9(cwd, file);
3837
+ const filePath = resolve10(cwd, file);
3414
3838
  let fileContent;
3415
3839
  try {
3416
- fileContent = readFileSync15(filePath, "utf-8");
3840
+ fileContent = readFileSync16(filePath, "utf-8");
3417
3841
  } catch {
3418
3842
  if (options.json) {
3419
3843
  console.log(
@@ -3502,8 +3926,8 @@ function printIssue(issue) {
3502
3926
 
3503
3927
  // src/commands/theme-verify.ts
3504
3928
  import { spawnSync as _spawnSync } from "child_process";
3505
- import { existsSync as existsSync12 } from "fs";
3506
- import { resolve as resolve10 } from "path";
3929
+ import { existsSync as existsSync13 } from "fs";
3930
+ import { resolve as resolve11 } from "path";
3507
3931
  function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
3508
3932
  const target = options.target ?? "flutter";
3509
3933
  if (target !== "flutter") {
@@ -3525,8 +3949,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
3525
3949
  }
3526
3950
  process.exit(1);
3527
3951
  }
3528
- const dirPath = resolve10(cwd, dir);
3529
- if (!existsSync12(dirPath)) {
3952
+ const dirPath = resolve11(cwd, dir);
3953
+ if (!existsSync13(dirPath)) {
3530
3954
  if (options.json) {
3531
3955
  console.log(
3532
3956
  JSON.stringify({
@@ -3613,8 +4037,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
3613
4037
  }
3614
4038
 
3615
4039
  // src/commands/theme-extract.ts
3616
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync9, existsSync as existsSync13, readdirSync as readdirSync4, statSync as statSync5 } from "fs";
3617
- import { resolve as resolve11, join as join16, basename as basename3, extname as extname3, relative } from "path";
4040
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync9, existsSync as existsSync14, readdirSync as readdirSync4, statSync as statSync6 } from "fs";
4041
+ import { resolve as resolve12, join as join17, basename as basename3, extname as extname3, relative } from "path";
3618
4042
  import { stringify as stringifyYaml } from "yaml";
3619
4043
  import {
3620
4044
  extractFromCSS,
@@ -3643,8 +4067,8 @@ var CSS_DIRS = [
3643
4067
  "packages/design-tokens"
3644
4068
  ];
3645
4069
  function themeExtractCommand(cwd, options) {
3646
- const targetDir = resolve11(cwd, options.from ?? ".");
3647
- if (!existsSync13(targetDir)) {
4070
+ const targetDir = resolve12(cwd, options.from ?? ".");
4071
+ if (!existsSync14(targetDir)) {
3648
4072
  if (options.json) {
3649
4073
  console.log(JSON.stringify({ success: false, error: `Directory not found: ${targetDir}` }));
3650
4074
  } else {
@@ -3698,16 +4122,16 @@ function collectCSSFiles(targetDir) {
3698
4122
  const files = [];
3699
4123
  const seen = /* @__PURE__ */ new Set();
3700
4124
  for (const pattern2 of CSS_FILE_PATTERNS) {
3701
- const rootPath = join16(targetDir, pattern2);
4125
+ const rootPath = join17(targetDir, pattern2);
3702
4126
  addFileIfExists(rootPath, files, seen);
3703
4127
  for (const dir of CSS_DIRS) {
3704
- const dirPath = join16(targetDir, dir, pattern2);
4128
+ const dirPath = join17(targetDir, dir, pattern2);
3705
4129
  addFileIfExists(dirPath, files, seen);
3706
4130
  }
3707
4131
  }
3708
4132
  for (const dir of CSS_DIRS) {
3709
- const dirPath = join16(targetDir, dir);
3710
- if (existsSync13(dirPath) && statSync5(dirPath).isDirectory()) {
4133
+ const dirPath = join17(targetDir, dir);
4134
+ if (existsSync14(dirPath) && statSync6(dirPath).isDirectory()) {
3711
4135
  scanDirForCSS(dirPath, files, seen, 2);
3712
4136
  }
3713
4137
  }
@@ -3715,11 +4139,11 @@ function collectCSSFiles(targetDir) {
3715
4139
  return files;
3716
4140
  }
3717
4141
  function addFileIfExists(filePath, files, seen) {
3718
- const resolved = resolve11(filePath);
4142
+ const resolved = resolve12(filePath);
3719
4143
  if (seen.has(resolved)) return;
3720
- if (!existsSync13(resolved)) return;
4144
+ if (!existsSync14(resolved)) return;
3721
4145
  try {
3722
- const content = readFileSync16(resolved, "utf-8");
4146
+ const content = readFileSync17(resolved, "utf-8");
3723
4147
  if (content.includes("--")) {
3724
4148
  files.push({ path: resolved, content });
3725
4149
  seen.add(resolved);
@@ -3728,7 +4152,7 @@ function addFileIfExists(filePath, files, seen) {
3728
4152
  }
3729
4153
  }
3730
4154
  function scanDirForCSS(dir, files, seen, maxDepth) {
3731
- if (!existsSync13(dir)) return;
4155
+ if (!existsSync14(dir)) return;
3732
4156
  const SKIP_DIRS = /* @__PURE__ */ new Set([
3733
4157
  "node_modules",
3734
4158
  ".next",
@@ -3747,10 +4171,10 @@ function scanDirForCSS(dir, files, seen, maxDepth) {
3747
4171
  if (entry.isDirectory()) {
3748
4172
  if (SKIP_DIRS.has(entry.name)) continue;
3749
4173
  if (maxDepth > 0) {
3750
- scanDirForCSS(join16(dir, entry.name), files, seen, maxDepth - 1);
4174
+ scanDirForCSS(join17(dir, entry.name), files, seen, maxDepth - 1);
3751
4175
  }
3752
4176
  } else if (entry.isFile() && extname3(entry.name) === ".css") {
3753
- addFileIfExists(join16(dir, entry.name), files, seen);
4177
+ addFileIfExists(join17(dir, entry.name), files, seen);
3754
4178
  }
3755
4179
  }
3756
4180
  } catch {
@@ -3832,10 +4256,10 @@ function extractVarName(varExpr) {
3832
4256
  function parseNextFontFromLayouts(targetDir) {
3833
4257
  const fontMap = /* @__PURE__ */ new Map();
3834
4258
  for (const relPath of LAYOUT_FILE_PATHS) {
3835
- const fullPath = join16(targetDir, relPath);
3836
- if (!existsSync13(fullPath)) continue;
4259
+ const fullPath = join17(targetDir, relPath);
4260
+ if (!existsSync14(fullPath)) continue;
3837
4261
  try {
3838
- const content = readFileSync16(fullPath, "utf-8");
4262
+ const content = readFileSync17(fullPath, "utf-8");
3839
4263
  parseNextFontDeclarations(content, fontMap);
3840
4264
  } catch {
3841
4265
  }
@@ -3920,10 +4344,10 @@ var MONO_FONT_NAMES = /* @__PURE__ */ new Set([
3920
4344
  "IBM Plex Mono"
3921
4345
  ]);
3922
4346
  function extractFontHints(targetDir) {
3923
- const pkgPath = join16(targetDir, "package.json");
3924
- if (!existsSync13(pkgPath)) return void 0;
4347
+ const pkgPath = join17(targetDir, "package.json");
4348
+ if (!existsSync14(pkgPath)) return void 0;
3925
4349
  try {
3926
- const pkg2 = JSON.parse(readFileSync16(pkgPath, "utf-8"));
4350
+ const pkg2 = JSON.parse(readFileSync17(pkgPath, "utf-8"));
3927
4351
  const allDeps = { ...pkg2.dependencies, ...pkg2.devDependencies };
3928
4352
  const fonts2 = [];
3929
4353
  for (const [dep, _] of Object.entries(allDeps)) {
@@ -3959,10 +4383,10 @@ function extractFontHints(targetDir) {
3959
4383
  }
3960
4384
  }
3961
4385
  function inferThemeName(targetDir) {
3962
- const pkgPath = join16(targetDir, "package.json");
3963
- if (existsSync13(pkgPath)) {
4386
+ const pkgPath = join17(targetDir, "package.json");
4387
+ if (existsSync14(pkgPath)) {
3964
4388
  try {
3965
- const pkg2 = JSON.parse(readFileSync16(pkgPath, "utf-8"));
4389
+ const pkg2 = JSON.parse(readFileSync17(pkgPath, "utf-8"));
3966
4390
  if (pkg2.name) {
3967
4391
  const name = pkg2.name.replace(/^@[\w-]+\//, "");
3968
4392
  return `${name}-theme`;
@@ -3999,7 +4423,7 @@ function outputJSON(result, validationResult) {
3999
4423
  }
4000
4424
  function outputYAML(result, outputPath, cwd, validationResult) {
4001
4425
  const yamlStr = buildAnnotatedYAML(result);
4002
- const outFile = resolve11(cwd, outputPath ?? ".visor.yaml");
4426
+ const outFile = resolve12(cwd, outputPath ?? ".visor.yaml");
4003
4427
  const high = result.tokens.filter((t) => t.confidence === "high").length;
4004
4428
  const med = result.tokens.filter((t) => t.confidence === "medium").length;
4005
4429
  const low = result.tokens.filter((t) => t.confidence === "low").length;
@@ -4085,14 +4509,14 @@ function buildAnnotatedYAML(result) {
4085
4509
  }
4086
4510
 
4087
4511
  // src/commands/theme-register.ts
4088
- import { readFileSync as readFileSync18, writeFileSync as writeFileSync10, mkdirSync as mkdirSync7, existsSync as existsSync15 } from "fs";
4089
- import { resolve as resolve13, join as join18 } from "path";
4512
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync10, mkdirSync as mkdirSync7, existsSync as existsSync16 } from "fs";
4513
+ import { resolve as resolve14, join as join19 } from "path";
4090
4514
  import { generateThemeData as generateThemeData4 } from "@loworbitstudio/visor-theme-engine";
4091
4515
  import { docsAdapter as docsAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
4092
4516
 
4093
4517
  // src/utils/theme-helpers.ts
4094
- import { existsSync as existsSync14, lstatSync, readdirSync as readdirSync5, readFileSync as readFileSync17, readlinkSync, realpathSync, statSync as statSync6 } from "fs";
4095
- import { resolve as resolve12, dirname as dirname9, join as join17 } from "path";
4518
+ import { existsSync as existsSync15, lstatSync, readdirSync as readdirSync5, readFileSync as readFileSync18, readlinkSync, realpathSync, statSync as statSync7 } from "fs";
4519
+ import { resolve as resolve13, dirname as dirname10, join as join18 } from "path";
4096
4520
  import { execFileSync as execFileSync3 } from "child_process";
4097
4521
  function toSlug(name) {
4098
4522
  return name.toLowerCase().replace(/\s+/g, "-");
@@ -4101,12 +4525,12 @@ function toLabel(name) {
4101
4525
  return name.split(/[\s-]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4102
4526
  }
4103
4527
  function findRepoRoot(startDir) {
4104
- let current = resolve12(startDir);
4528
+ let current = resolve13(startDir);
4105
4529
  while (true) {
4106
- if (existsSync14(join17(current, "packages", "docs"))) {
4530
+ if (existsSync15(join18(current, "packages", "docs"))) {
4107
4531
  return current;
4108
4532
  }
4109
- const parent = dirname9(current);
4533
+ const parent = dirname10(current);
4110
4534
  if (parent === current) break;
4111
4535
  current = parent;
4112
4536
  }
@@ -4120,9 +4544,9 @@ function findMainRepoRoot(startDir) {
4120
4544
  stdio: ["ignore", "pipe", "ignore"]
4121
4545
  }).trim();
4122
4546
  if (commonDir) {
4123
- const absoluteCommonDir = resolve12(startDir, commonDir);
4124
- const candidate = dirname9(absoluteCommonDir);
4125
- if (existsSync14(join17(candidate, "packages", "docs"))) {
4547
+ const absoluteCommonDir = resolve13(startDir, commonDir);
4548
+ const candidate = dirname10(absoluteCommonDir);
4549
+ if (existsSync15(join18(candidate, "packages", "docs"))) {
4126
4550
  return candidate;
4127
4551
  }
4128
4552
  }
@@ -4141,10 +4565,10 @@ var BrokenSymlinkError = class extends Error {
4141
4565
  code = "BROKEN_SYMLINK";
4142
4566
  };
4143
4567
  function assertNoBrokenSymlinks(dir) {
4144
- if (!existsSync14(dir)) return;
4568
+ if (!existsSync15(dir)) return;
4145
4569
  const entries = readdirSync5(dir, { withFileTypes: true });
4146
4570
  for (const entry of entries) {
4147
- const entryPath = join17(dir, entry.name);
4571
+ const entryPath = join18(dir, entry.name);
4148
4572
  let lst;
4149
4573
  try {
4150
4574
  lst = lstatSync(entryPath);
@@ -4154,7 +4578,7 @@ function assertNoBrokenSymlinks(dir) {
4154
4578
  if (lst.isSymbolicLink()) {
4155
4579
  const target = readlinkSync(entryPath);
4156
4580
  try {
4157
- statSync6(entryPath);
4581
+ statSync7(entryPath);
4158
4582
  } catch {
4159
4583
  throw new BrokenSymlinkError(entryPath, target);
4160
4584
  }
@@ -4162,39 +4586,39 @@ function assertNoBrokenSymlinks(dir) {
4162
4586
  }
4163
4587
  }
4164
4588
  function scanParentForPrivateThemes(parentDir) {
4165
- if (!existsSync14(parentDir)) return [];
4589
+ if (!existsSync15(parentDir)) return [];
4166
4590
  const entries = readdirSync5(parentDir, { withFileTypes: true });
4167
4591
  const matches = [];
4168
4592
  for (const entry of entries) {
4169
4593
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
4170
- const candidate = join17(parentDir, entry.name, "visor-themes-private", "themes");
4171
- if (existsSync14(candidate)) {
4594
+ const candidate = join18(parentDir, entry.name, "visor-themes-private", "themes");
4595
+ if (existsSync15(candidate)) {
4172
4596
  matches.push(candidate);
4173
4597
  }
4174
4598
  }
4175
4599
  return matches.sort();
4176
4600
  }
4177
4601
  function scanNestedThemeDir(dir) {
4178
- if (!existsSync14(dir)) return [];
4602
+ if (!existsSync15(dir)) return [];
4179
4603
  assertNoBrokenSymlinks(dir);
4180
4604
  const entries = readdirSync5(dir, { withFileTypes: true });
4181
4605
  const out = [];
4182
4606
  for (const entry of entries) {
4183
4607
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
4184
- const themeFile = join17(dir, entry.name, "theme.visor.yaml");
4185
- if (existsSync14(themeFile)) {
4608
+ const themeFile = join18(dir, entry.name, "theme.visor.yaml");
4609
+ if (existsSync15(themeFile)) {
4186
4610
  out.push({ filePath: themeFile, slug: entry.name });
4187
4611
  }
4188
4612
  }
4189
4613
  return out;
4190
4614
  }
4191
4615
  function detectVisorWorkspace(cwd) {
4192
- let current = resolve12(cwd);
4616
+ let current = resolve13(cwd);
4193
4617
  while (true) {
4194
- const pkgPath = join17(current, "package.json");
4195
- if (existsSync14(pkgPath)) {
4618
+ const pkgPath = join18(current, "package.json");
4619
+ if (existsSync15(pkgPath)) {
4196
4620
  try {
4197
- const pkg2 = JSON.parse(readFileSync17(pkgPath, "utf-8"));
4621
+ const pkg2 = JSON.parse(readFileSync18(pkgPath, "utf-8"));
4198
4622
  const workspaces = pkg2.workspaces ?? [];
4199
4623
  const hasCli = workspaces.some((w) => w.includes("packages/cli"));
4200
4624
  const hasEngine = workspaces.some((w) => w.includes("packages/theme-engine"));
@@ -4204,7 +4628,7 @@ function detectVisorWorkspace(cwd) {
4204
4628
  } catch {
4205
4629
  }
4206
4630
  }
4207
- const parent = dirname9(current);
4631
+ const parent = dirname10(current);
4208
4632
  if (parent === current) break;
4209
4633
  current = parent;
4210
4634
  }
@@ -4212,18 +4636,18 @@ function detectVisorWorkspace(cwd) {
4212
4636
  }
4213
4637
  function isLocalVisorBinary(workspaceRoot, scriptPath) {
4214
4638
  if (!scriptPath) return false;
4215
- const expectedPrefix = join17(workspaceRoot, "packages", "cli", "dist");
4639
+ const expectedPrefix = join18(workspaceRoot, "packages", "cli", "dist");
4216
4640
  let resolvedScript;
4217
4641
  let resolvedPrefix;
4218
4642
  try {
4219
4643
  resolvedScript = realpathSync(scriptPath);
4220
4644
  } catch {
4221
- resolvedScript = resolve12(scriptPath);
4645
+ resolvedScript = resolve13(scriptPath);
4222
4646
  }
4223
4647
  try {
4224
4648
  resolvedPrefix = realpathSync(expectedPrefix);
4225
4649
  } catch {
4226
- resolvedPrefix = resolve12(expectedPrefix);
4650
+ resolvedPrefix = resolve13(expectedPrefix);
4227
4651
  }
4228
4652
  return resolvedScript.startsWith(resolvedPrefix + "/") || resolvedScript === resolvedPrefix;
4229
4653
  }
@@ -4313,10 +4737,10 @@ ${indent}${newEntry},
4313
4737
  return { updated, changed: true };
4314
4738
  }
4315
4739
  function themeRegisterCommand(file, cwd, options) {
4316
- const filePath = resolve13(cwd, file);
4740
+ const filePath = resolve14(cwd, file);
4317
4741
  let yamlContent;
4318
4742
  try {
4319
- yamlContent = readFileSync18(filePath, "utf-8");
4743
+ yamlContent = readFileSync19(filePath, "utf-8");
4320
4744
  } catch {
4321
4745
  if (options.json) {
4322
4746
  console.log(JSON.stringify({ success: false, error: `Could not read file: ${filePath}` }));
@@ -4359,11 +4783,11 @@ function themeRegisterCommand(file, cwd, options) {
4359
4783
  process.exit(1);
4360
4784
  return;
4361
4785
  }
4362
- const docsAppDir = join18(repoRoot, "packages", "docs", "app");
4363
- const cssFilePath = join18(docsAppDir, `${slug2}-theme.css`);
4364
- const globalsPath = join18(docsAppDir, "globals.css");
4365
- const themeConfigPath = join18(repoRoot, "packages", "docs", "lib", "theme-config.ts");
4366
- if (!existsSync15(docsAppDir)) {
4786
+ const docsAppDir = join19(repoRoot, "packages", "docs", "app");
4787
+ const cssFilePath = join19(docsAppDir, `${slug2}-theme.css`);
4788
+ const globalsPath = join19(docsAppDir, "globals.css");
4789
+ const themeConfigPath = join19(repoRoot, "packages", "docs", "lib", "theme-config.ts");
4790
+ if (!existsSync16(docsAppDir)) {
4367
4791
  const msg = `Docs app directory not found: ${docsAppDir}`;
4368
4792
  if (options.json) {
4369
4793
  console.log(JSON.stringify({ success: false, error: msg }));
@@ -4376,8 +4800,8 @@ function themeRegisterCommand(file, cwd, options) {
4376
4800
  let globalsContent = "";
4377
4801
  let themeConfigContent = "";
4378
4802
  try {
4379
- globalsContent = readFileSync18(globalsPath, "utf-8");
4380
- themeConfigContent = readFileSync18(themeConfigPath, "utf-8");
4803
+ globalsContent = readFileSync19(globalsPath, "utf-8");
4804
+ themeConfigContent = readFileSync19(themeConfigPath, "utf-8");
4381
4805
  } catch (err) {
4382
4806
  const msg = err instanceof Error ? err.message : "Could not read docs files";
4383
4807
  if (options.json) {
@@ -4388,8 +4812,8 @@ function themeRegisterCommand(file, cwd, options) {
4388
4812
  process.exit(1);
4389
4813
  return;
4390
4814
  }
4391
- const cssExists = existsSync15(cssFilePath);
4392
- const cssChanged = !cssExists || readFileSync18(cssFilePath, "utf-8") !== css;
4815
+ const cssExists = existsSync16(cssFilePath);
4816
+ const cssChanged = !cssExists || readFileSync19(cssFilePath, "utf-8") !== css;
4393
4817
  const { updated: newGlobals, changed: globalsChanged } = insertGlobalsImport(globalsContent, slug2);
4394
4818
  const { updated: newThemeConfig, changed: themeConfigChanged, error: configError } = insertThemeConfig(
4395
4819
  themeConfigContent,
@@ -4473,8 +4897,8 @@ function themeRegisterCommand(file, cwd, options) {
4473
4897
  }
4474
4898
 
4475
4899
  // src/commands/theme-unregister.ts
4476
- import { readFileSync as readFileSync19, writeFileSync as writeFileSync11, existsSync as existsSync16, unlinkSync } from "fs";
4477
- import { join as join19 } from "path";
4900
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync11, existsSync as existsSync17, unlinkSync } from "fs";
4901
+ import { join as join20 } from "path";
4478
4902
  function removeGlobalsImport(content, slug2) {
4479
4903
  const importLine = `@import './${slug2}-theme.css';`;
4480
4904
  if (!content.includes(importLine)) {
@@ -4506,11 +4930,11 @@ function themeUnregisterCommand(slug2, cwd, options) {
4506
4930
  process.exit(1);
4507
4931
  return;
4508
4932
  }
4509
- const docsAppDir = join19(repoRoot, "packages", "docs", "app");
4510
- const cssFilePath = join19(docsAppDir, `${slug2}-theme.css`);
4511
- const globalsPath = join19(docsAppDir, "globals.css");
4512
- const themeConfigPath = join19(repoRoot, "packages", "docs", "lib", "theme-config.ts");
4513
- if (!existsSync16(docsAppDir)) {
4933
+ const docsAppDir = join20(repoRoot, "packages", "docs", "app");
4934
+ const cssFilePath = join20(docsAppDir, `${slug2}-theme.css`);
4935
+ const globalsPath = join20(docsAppDir, "globals.css");
4936
+ const themeConfigPath = join20(repoRoot, "packages", "docs", "lib", "theme-config.ts");
4937
+ if (!existsSync17(docsAppDir)) {
4514
4938
  const msg = `Docs app directory not found: ${docsAppDir}`;
4515
4939
  if (options.json) {
4516
4940
  console.log(JSON.stringify({ success: false, error: msg }));
@@ -4523,8 +4947,8 @@ function themeUnregisterCommand(slug2, cwd, options) {
4523
4947
  let globalsContent = "";
4524
4948
  let themeConfigContent = "";
4525
4949
  try {
4526
- globalsContent = readFileSync19(globalsPath, "utf-8");
4527
- themeConfigContent = readFileSync19(themeConfigPath, "utf-8");
4950
+ globalsContent = readFileSync20(globalsPath, "utf-8");
4951
+ themeConfigContent = readFileSync20(themeConfigPath, "utf-8");
4528
4952
  } catch (err) {
4529
4953
  const msg = err instanceof Error ? err.message : "Could not read docs files";
4530
4954
  if (options.json) {
@@ -4535,7 +4959,7 @@ function themeUnregisterCommand(slug2, cwd, options) {
4535
4959
  process.exit(1);
4536
4960
  return;
4537
4961
  }
4538
- const cssExists = existsSync16(cssFilePath);
4962
+ const cssExists = existsSync17(cssFilePath);
4539
4963
  const { updated: newGlobals, changed: globalsChanged } = removeGlobalsImport(globalsContent, slug2);
4540
4964
  const { updated: newThemeConfig, changed: themeConfigChanged } = removeThemeConfigEntry(themeConfigContent, slug2);
4541
4965
  if (!cssExists && !globalsChanged && !themeConfigChanged) {
@@ -4576,15 +5000,15 @@ function themeUnregisterCommand(slug2, cwd, options) {
4576
5000
 
4577
5001
  // src/commands/theme-sync.ts
4578
5002
  import {
4579
- readFileSync as readFileSync20,
5003
+ readFileSync as readFileSync21,
4580
5004
  writeFileSync as writeFileSync12,
4581
5005
  mkdirSync as mkdirSync8,
4582
- existsSync as existsSync17,
5006
+ existsSync as existsSync18,
4583
5007
  readdirSync as readdirSync6,
4584
5008
  unlinkSync as unlinkSync2,
4585
5009
  copyFileSync
4586
5010
  } from "fs";
4587
- import { join as join20, basename as basename4, resolve as resolve14, sep, relative as relative2 } from "path";
5011
+ import { join as join21, basename as basename4, resolve as resolve15, sep, relative as relative2 } from "path";
4588
5012
  import { parse as parseYaml3 } from "yaml";
4589
5013
  import { generateThemeData as generateThemeData5, validateFontCoverage, formatFontCoverageError } from "@loworbitstudio/visor-theme-engine";
4590
5014
  import { docsAdapter as docsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
@@ -4598,9 +5022,9 @@ var CUSTOM_OVERLAY_CSS_PATH = "packages/docs/app/custom-themes.generated.css";
4598
5022
  var CUSTOM_OVERLAY_TS_PATH = "packages/docs/lib/theme-config.custom.generated.ts";
4599
5023
  var CUSTOM_OVERLAY_IMPORT_LINE = "@import './custom-themes.generated.css';";
4600
5024
  function scanThemeDir(dir) {
4601
- if (!existsSync17(dir)) return [];
5025
+ if (!existsSync18(dir)) return [];
4602
5026
  assertNoBrokenSymlinks(dir);
4603
- return readdirSync6(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join20(dir, f));
5027
+ return readdirSync6(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join21(dir, f));
4604
5028
  }
4605
5029
  function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
4606
5030
  const merged = /* @__PURE__ */ new Map();
@@ -4624,20 +5048,20 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
4624
5048
  };
4625
5049
  const envPath = process.env[PRIVATE_THEMES_ENV_VAR];
4626
5050
  if (envPath && envPath.trim() !== "") {
4627
- const resolved = resolve14(envPath);
4628
- if (!existsSync17(resolved)) {
5051
+ const resolved = resolve15(envPath);
5052
+ if (!existsSync18(resolved)) {
4629
5053
  throw new Error(
4630
5054
  `${PRIVATE_THEMES_ENV_VAR} is set to "${envPath}" but the path does not exist. Expected a directory containing {slug}/theme.visor.yaml entries.`
4631
5055
  );
4632
5056
  }
4633
5057
  addNested(resolved, "env");
4634
5058
  }
4635
- const siblingPath = join20(mainRepoRoot, "..", "visor-themes-private", "themes");
4636
- const siblingExists = existsSync17(siblingPath);
5059
+ const siblingPath = join21(mainRepoRoot, "..", "visor-themes-private", "themes");
5060
+ const siblingExists = existsSync18(siblingPath);
4637
5061
  if (siblingExists) {
4638
5062
  addNested(siblingPath, "sibling");
4639
5063
  }
4640
- const parentDir = join20(mainRepoRoot, "..");
5064
+ const parentDir = join21(mainRepoRoot, "..");
4641
5065
  const parentGlobMatches = scanParentForPrivateThemes(parentDir).filter(
4642
5066
  (path2) => !path2.startsWith(mainRepoRoot + sep)
4643
5067
  );
@@ -4658,7 +5082,7 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
4658
5082
  }
4659
5083
  }
4660
5084
  }
4661
- const legacyDir = join20(repoRoot, "custom-themes");
5085
+ const legacyDir = join21(repoRoot, "custom-themes");
4662
5086
  const legacyFiles = scanThemeDir(legacyDir);
4663
5087
  for (const legacyFile of legacyFiles) {
4664
5088
  const slug2 = basename4(legacyFile).replace(/\.visor\.yaml$/, "");
@@ -4715,14 +5139,14 @@ function reportBrokenSymlink(err, options) {
4715
5139
  }
4716
5140
  }
4717
5141
  function buildEmptySourcesMessage(mainRepoRoot) {
4718
- const expectedSibling = join20(mainRepoRoot, "..", "visor-themes-private");
5142
+ const expectedSibling = join21(mainRepoRoot, "..", "visor-themes-private");
4719
5143
  return [
4720
5144
  "No theme sources discovered. Cannot proceed \u2014 refusing to wipe generated CSS.",
4721
5145
  "",
4722
5146
  "Resolution order checked:",
4723
5147
  ` 1. Env var ${PRIVATE_THEMES_ENV_VAR} (unset or empty)`,
4724
5148
  ` 2. Sibling checkout at ${expectedSibling}/themes/ (not found)`,
4725
- ` 3. One-level-deeper at ${join20(mainRepoRoot, "..")}/<parent>/visor-themes-private/themes/ (not found)`,
5149
+ ` 3. One-level-deeper at ${join21(mainRepoRoot, "..")}/<parent>/visor-themes-private/themes/ (not found)`,
4726
5150
  " 4. Legacy custom-themes/ (no .visor.yaml files)",
4727
5151
  "",
4728
5152
  "To fix, clone the private themes repo as a sibling:",
@@ -4927,14 +5351,14 @@ function themeSyncCommand(cwd, options) {
4927
5351
  return;
4928
5352
  }
4929
5353
  const mainRepoRoot = findMainRepoRoot(cwd) ?? repoRoot;
4930
- const themesDir = join20(repoRoot, "themes");
4931
- const docsAppDir = join20(repoRoot, "packages", "docs", "app");
4932
- const docsLibDir = join20(repoRoot, "packages", "docs", "lib");
4933
- const docsPublicThemesDir = join20(repoRoot, "packages", "docs", "public", "themes");
4934
- const themeConfigPath = join20(repoRoot, "packages", "docs", "lib", "theme-config.ts");
4935
- const globalsPath = join20(docsAppDir, "globals.css");
4936
- const customOverlayCssPath = join20(repoRoot, CUSTOM_OVERLAY_CSS_PATH);
4937
- const customOverlayTsPath = join20(repoRoot, CUSTOM_OVERLAY_TS_PATH);
5354
+ const themesDir = join21(repoRoot, "themes");
5355
+ const docsAppDir = join21(repoRoot, "packages", "docs", "app");
5356
+ const docsLibDir = join21(repoRoot, "packages", "docs", "lib");
5357
+ const docsPublicThemesDir = join21(repoRoot, "packages", "docs", "public", "themes");
5358
+ const themeConfigPath = join21(repoRoot, "packages", "docs", "lib", "theme-config.ts");
5359
+ const globalsPath = join21(docsAppDir, "globals.css");
5360
+ const customOverlayCssPath = join21(repoRoot, CUSTOM_OVERLAY_CSS_PATH);
5361
+ const customOverlayTsPath = join21(repoRoot, CUSTOM_OVERLAY_TS_PATH);
4938
5362
  let stockFiles;
4939
5363
  try {
4940
5364
  stockFiles = scanThemeDir(themesDir);
@@ -4991,7 +5415,7 @@ function themeSyncCommand(cwd, options) {
4991
5415
  const processFile = (filePath, isCustom, slugOverride) => {
4992
5416
  let yamlContent;
4993
5417
  try {
4994
- yamlContent = readFileSync20(filePath, "utf-8");
5418
+ yamlContent = readFileSync21(filePath, "utf-8");
4995
5419
  } catch {
4996
5420
  failures.push({ filePath, error: `Could not read: ${filePath}` });
4997
5421
  return;
@@ -5041,8 +5465,8 @@ function themeSyncCommand(cwd, options) {
5041
5465
  let globalsContent;
5042
5466
  let themeConfigContent;
5043
5467
  try {
5044
- globalsContent = readFileSync20(globalsPath, "utf-8");
5045
- themeConfigContent = readFileSync20(themeConfigPath, "utf-8");
5468
+ globalsContent = readFileSync21(globalsPath, "utf-8");
5469
+ themeConfigContent = readFileSync21(themeConfigPath, "utf-8");
5046
5470
  } catch (err) {
5047
5471
  const msg = err instanceof Error ? err.message : "Could not read docs files";
5048
5472
  if (options.json) {
@@ -5057,12 +5481,12 @@ function themeSyncCommand(cwd, options) {
5057
5481
  const newThemeConfig = updateStockThemeConfigBlock(themeConfigContent, stockManifest);
5058
5482
  const newCustomOverlayCss = generateCustomOverlayCss(customManifest);
5059
5483
  const newCustomOverlayTs = generateCustomOverlayTs(customManifest);
5060
- const existingCssFiles = existsSync17(docsAppDir) ? readdirSync6(docsAppDir).filter(
5484
+ const existingCssFiles = existsSync18(docsAppDir) ? readdirSync6(docsAppDir).filter(
5061
5485
  (f) => f.endsWith("-theme.css") && f !== "custom-themes.generated.css"
5062
5486
  ) : [];
5063
5487
  const newCssSet = new Set(allSlugs.map((s) => `${s}-theme.css`));
5064
5488
  const staleCssFiles = existingCssFiles.filter((f) => !newCssSet.has(f));
5065
- const existingPublicYamls = existsSync17(docsPublicThemesDir) ? readdirSync6(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
5489
+ const existingPublicYamls = existsSync18(docsPublicThemesDir) ? readdirSync6(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
5066
5490
  const newPublicYamlSet = new Set(manifest.map((e) => `${e.yamlFilename}.visor.yaml`));
5067
5491
  const stalePublicYamls = existingPublicYamls.filter((f) => !newPublicYamlSet.has(f));
5068
5492
  if (options.dryRun) {
@@ -5106,24 +5530,24 @@ function themeSyncCommand(cwd, options) {
5106
5530
  mkdirSync8(docsLibDir, { recursive: true });
5107
5531
  mkdirSync8(docsPublicThemesDir, { recursive: true });
5108
5532
  for (const entry of manifest) {
5109
- writeFileSync12(join20(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
5533
+ writeFileSync12(join21(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
5110
5534
  }
5111
5535
  for (const stale of staleCssFiles) {
5112
- unlinkSync2(join20(docsAppDir, stale));
5536
+ unlinkSync2(join21(docsAppDir, stale));
5113
5537
  }
5114
5538
  writeFileSync12(customOverlayCssPath, newCustomOverlayCss, "utf-8");
5115
5539
  writeFileSync12(customOverlayTsPath, newCustomOverlayTs, "utf-8");
5116
5540
  writeFileSync12(themeConfigPath, newThemeConfig, "utf-8");
5117
5541
  writeFileSync12(globalsPath, newGlobals, "utf-8");
5118
5542
  for (const srcFile of stockFiles) {
5119
- copyFileSync(srcFile, join20(docsPublicThemesDir, basename4(srcFile)));
5543
+ copyFileSync(srcFile, join21(docsPublicThemesDir, basename4(srcFile)));
5120
5544
  }
5121
5545
  for (const c of customSources) {
5122
5546
  const targetName = c.origin === "legacy" ? basename4(c.filePath) : `${c.slug}.visor.yaml`;
5123
- copyFileSync(c.filePath, join20(docsPublicThemesDir, targetName));
5547
+ copyFileSync(c.filePath, join21(docsPublicThemesDir, targetName));
5124
5548
  }
5125
5549
  for (const stale of stalePublicYamls) {
5126
- unlinkSync2(join20(docsPublicThemesDir, stale));
5550
+ unlinkSync2(join21(docsPublicThemesDir, stale));
5127
5551
  }
5128
5552
  } catch (err) {
5129
5553
  const msg = err instanceof Error ? err.message : "Write failed";
@@ -5176,19 +5600,19 @@ function themeSyncCommand(cwd, options) {
5176
5600
 
5177
5601
  // src/commands/theme-batch-apply-flutter.ts
5178
5602
  import {
5179
- readFileSync as readFileSync21,
5603
+ readFileSync as readFileSync22,
5180
5604
  writeFileSync as writeFileSync13,
5181
5605
  mkdirSync as mkdirSync9,
5182
- existsSync as existsSync18,
5606
+ existsSync as existsSync19,
5183
5607
  readdirSync as readdirSync7,
5184
5608
  rmSync
5185
5609
  } from "fs";
5186
- import { join as join21, basename as basename5, dirname as dirname10 } from "path";
5610
+ import { join as join22, basename as basename5, dirname as dirname11 } from "path";
5187
5611
  import { generateThemeData as generateThemeData6 } from "@loworbitstudio/visor-theme-engine";
5188
5612
  import { flutterAdapter as flutterAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
5189
5613
  function scanThemeDir2(dir) {
5190
- if (!existsSync18(dir)) return [];
5191
- return readdirSync7(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join21(dir, f)).sort();
5614
+ if (!existsSync19(dir)) return [];
5615
+ return readdirSync7(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join22(dir, f)).sort();
5192
5616
  }
5193
5617
  function slugToCamel(slug2) {
5194
5618
  return slug2.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
@@ -5321,9 +5745,9 @@ function themeBatchApplyFlutterCommand(cwd, options) {
5321
5745
  process.exit(1);
5322
5746
  return;
5323
5747
  }
5324
- const themesDir = join21(repoRoot, "themes");
5325
- const customThemesDir = join21(repoRoot, "custom-themes");
5326
- const outputDir = join21(repoRoot, "packages", "visor_themes");
5748
+ const themesDir = join22(repoRoot, "themes");
5749
+ const customThemesDir = join22(repoRoot, "custom-themes");
5750
+ const outputDir = join22(repoRoot, "packages", "visor_themes");
5327
5751
  const stockFiles = scanThemeDir2(themesDir);
5328
5752
  const customFiles = scanThemeDir2(customThemesDir);
5329
5753
  const allFiles = [...stockFiles, ...customFiles];
@@ -5344,7 +5768,7 @@ function themeBatchApplyFlutterCommand(cwd, options) {
5344
5768
  for (const filePath of allFiles) {
5345
5769
  let yamlContent;
5346
5770
  try {
5347
- yamlContent = readFileSync21(filePath, "utf-8");
5771
+ yamlContent = readFileSync22(filePath, "utf-8");
5348
5772
  } catch {
5349
5773
  errors.push(`Could not read: ${filePath}`);
5350
5774
  continue;
@@ -5423,8 +5847,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
5423
5847
  return;
5424
5848
  }
5425
5849
  const slugs = processed.map((p) => p.slug);
5426
- const libSrcDir = join21(outputDir, "lib", "src");
5427
- if (existsSync18(libSrcDir)) {
5850
+ const libSrcDir = join22(outputDir, "lib", "src");
5851
+ if (existsSync19(libSrcDir)) {
5428
5852
  rmSync(libSrcDir, { recursive: true, force: true });
5429
5853
  }
5430
5854
  const packageFiles = {
@@ -5436,16 +5860,16 @@ function themeBatchApplyFlutterCommand(cwd, options) {
5436
5860
  };
5437
5861
  let totalFiles = 0;
5438
5862
  for (const [relPath, content] of Object.entries(packageFiles)) {
5439
- const absPath = join21(outputDir, relPath);
5440
- mkdirSync9(dirname10(absPath), { recursive: true });
5863
+ const absPath = join22(outputDir, relPath);
5864
+ mkdirSync9(dirname11(absPath), { recursive: true });
5441
5865
  writeFileSync13(absPath, content, "utf-8");
5442
5866
  totalFiles++;
5443
5867
  }
5444
5868
  for (const { slug: slug2, tokenFiles } of processed) {
5445
- const themeBaseDir = join21(outputDir, "lib", "src", slug2);
5869
+ const themeBaseDir = join22(outputDir, "lib", "src", slug2);
5446
5870
  for (const [relPath, content] of Object.entries(tokenFiles)) {
5447
- const absPath = join21(themeBaseDir, relPath);
5448
- mkdirSync9(dirname10(absPath), { recursive: true });
5871
+ const absPath = join22(themeBaseDir, relPath);
5872
+ mkdirSync9(dirname11(absPath), { recursive: true });
5449
5873
  writeFileSync13(absPath, content, "utf-8");
5450
5874
  totalFiles++;
5451
5875
  }
@@ -5467,8 +5891,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
5467
5891
  }
5468
5892
 
5469
5893
  // src/commands/fonts-add.ts
5470
- import { existsSync as existsSync19, statSync as statSync7, readdirSync as readdirSync8, readFileSync as readFileSync22 } from "fs";
5471
- import { resolve as resolve15, basename as basename6, extname as extname4 } from "path";
5894
+ import { existsSync as existsSync20, statSync as statSync8, readdirSync as readdirSync8, readFileSync as readFileSync23 } from "fs";
5895
+ import { resolve as resolve16, basename as basename6, extname as extname4 } from "path";
5472
5896
  import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
5473
5897
  function deriveFamilySlug(filename) {
5474
5898
  const name = basename6(filename, extname4(filename));
@@ -5512,11 +5936,11 @@ function deriveFamilySlug(filename) {
5512
5936
  return parts.join("-").toLowerCase();
5513
5937
  }
5514
5938
  function collectWoff2Files(inputPath) {
5515
- const resolved = resolve15(inputPath);
5516
- if (!existsSync19(resolved)) {
5939
+ const resolved = resolve16(inputPath);
5940
+ if (!existsSync20(resolved)) {
5517
5941
  throw new Error(`Path not found: ${resolved}`);
5518
5942
  }
5519
- const stat = statSync7(resolved);
5943
+ const stat = statSync8(resolved);
5520
5944
  if (stat.isFile()) {
5521
5945
  if (extname4(resolved).toLowerCase() !== ".woff2") {
5522
5946
  throw new Error(
@@ -5526,7 +5950,7 @@ function collectWoff2Files(inputPath) {
5526
5950
  return [resolved];
5527
5951
  }
5528
5952
  if (stat.isDirectory()) {
5529
- const files = readdirSync8(resolved).filter((f) => extname4(f).toLowerCase() === ".woff2").map((f) => resolve15(resolved, f));
5953
+ const files = readdirSync8(resolved).filter((f) => extname4(f).toLowerCase() === ".woff2").map((f) => resolve16(resolved, f));
5530
5954
  if (files.length === 0) {
5531
5955
  throw new Error(
5532
5956
  `No .woff2 files found in directory: ${resolved}`
@@ -5541,8 +5965,8 @@ function collectWoff2Files(inputPath) {
5541
5965
  throw new Error(`Path is neither a file nor a directory: ${resolved}`);
5542
5966
  }
5543
5967
  function getNonWoff2Fonts(inputPath) {
5544
- const resolved = resolve15(inputPath);
5545
- if (!existsSync19(resolved) || !statSync7(resolved).isDirectory()) {
5968
+ const resolved = resolve16(inputPath);
5969
+ if (!existsSync20(resolved) || !statSync8(resolved).isDirectory()) {
5546
5970
  return [];
5547
5971
  }
5548
5972
  return readdirSync8(resolved).filter((f) => {
@@ -5579,7 +6003,7 @@ function createR2Client(config) {
5579
6003
  });
5580
6004
  }
5581
6005
  async function uploadFile(client, bucket, key, filePath) {
5582
- const body = readFileSync22(filePath);
6006
+ const body = readFileSync23(filePath);
5583
6007
  await client.send(
5584
6008
  new PutObjectCommand({
5585
6009
  Bucket: bucket,
@@ -5595,8 +6019,8 @@ async function fontsAddCommand(inputPath, options) {
5595
6019
  const r2Config = getR2Config();
5596
6020
  const files = collectWoff2Files(inputPath);
5597
6021
  const familySlug = options.family ?? deriveFamilySlug(basename6(files[0]));
5598
- const resolved = resolve15(inputPath);
5599
- const nonWoff2 = statSync7(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
6022
+ const resolved = resolve16(inputPath);
6023
+ const nonWoff2 = statSync8(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
5600
6024
  if (!json) {
5601
6025
  logger.heading("Visor Font Upload");
5602
6026
  logger.info(`Organization: ${org}`);
@@ -5620,7 +6044,7 @@ async function fontsAddCommand(inputPath, options) {
5620
6044
  logger.info(`Uploading ${filename}...`);
5621
6045
  }
5622
6046
  await uploadFile(client, bucket, key, filePath);
5623
- const size = statSync7(filePath).size;
6047
+ const size = statSync8(filePath).size;
5624
6048
  results.push({ file: filename, key, size });
5625
6049
  if (!json) {
5626
6050
  logger.success(`Uploaded: ${key} (${formatBytes(size)})`);
@@ -5883,27 +6307,27 @@ function findCssFiles(dir, maxDepth = 3) {
5883
6307
  }
5884
6308
 
5885
6309
  // src/utils/patterns.ts
5886
- import { existsSync as existsSync21, readdirSync as readdirSync10, readFileSync as readFileSync24 } from "fs";
5887
- import { join as join23 } from "path";
6310
+ import { existsSync as existsSync22, readdirSync as readdirSync10, readFileSync as readFileSync25 } from "fs";
6311
+ import { join as join24 } from "path";
5888
6312
  import { parse as parseYAML } from "yaml";
5889
6313
  function loadPatternsFromYaml(repoRoot) {
5890
- const patternsDir = join23(repoRoot, "patterns");
5891
- if (!existsSync21(patternsDir)) return [];
6314
+ const patternsDir = join24(repoRoot, "patterns");
6315
+ if (!existsSync22(patternsDir)) return [];
5892
6316
  const files = readdirSync10(patternsDir).filter(
5893
6317
  (f) => f.endsWith(".visor-pattern.yaml")
5894
6318
  );
5895
6319
  return files.map((file) => {
5896
- const content = readFileSync24(join23(patternsDir, file), "utf-8");
6320
+ const content = readFileSync25(join24(patternsDir, file), "utf-8");
5897
6321
  return parseYAML(content);
5898
6322
  }).filter(Boolean);
5899
6323
  }
5900
6324
  function findRepoRoot2(startDir) {
5901
6325
  let current = startDir;
5902
6326
  while (true) {
5903
- if (existsSync21(join23(current, "patterns"))) {
6327
+ if (existsSync22(join24(current, "patterns"))) {
5904
6328
  return current;
5905
6329
  }
5906
- const parent = join23(current, "..");
6330
+ const parent = join24(current, "..");
5907
6331
  if (parent === current) return null;
5908
6332
  current = parent;
5909
6333
  }
@@ -6282,8 +6706,8 @@ Visor Tokens (${categoryLabel}) \u2014 ${tokens2.length} tokens
6282
6706
  }
6283
6707
 
6284
6708
  // src/commands/migrate-token-substitution.ts
6285
- import { readFileSync as readFileSync25, writeFileSync as writeFileSync14, readdirSync as readdirSync11, statSync as statSync8, existsSync as existsSync22 } from "fs";
6286
- import { resolve as resolve16, join as join24, relative as relative4 } from "path";
6709
+ import { readFileSync as readFileSync26, writeFileSync as writeFileSync14, readdirSync as readdirSync11, statSync as statSync9, existsSync as existsSync23 } from "fs";
6710
+ import { resolve as resolve17, join as join25, relative as relative4 } from "path";
6287
6711
  import { parse as parseYaml4 } from "yaml";
6288
6712
  import pc4 from "picocolors";
6289
6713
  var V7_ENTR_SUBSTITUTION_MAP = {
@@ -6306,14 +6730,14 @@ var BUILT_IN_SUBSTITUTION_MAPS = {
6306
6730
  var DEFAULT_THEME_ID = "entr";
6307
6731
  function readMapFromThemeFile(themeId, cwd) {
6308
6732
  const candidates = [
6309
- join24(cwd, "themes", `${themeId}.visor.yaml`),
6310
- join24(cwd, "custom-themes", `${themeId}.visor.yaml`),
6311
- join24(cwd, "packages", "docs", "public", "themes", `${themeId}.visor.yaml`)
6733
+ join25(cwd, "themes", `${themeId}.visor.yaml`),
6734
+ join25(cwd, "custom-themes", `${themeId}.visor.yaml`),
6735
+ join25(cwd, "packages", "docs", "public", "themes", `${themeId}.visor.yaml`)
6312
6736
  ];
6313
6737
  for (const candidate of candidates) {
6314
- if (existsSync22(candidate)) {
6738
+ if (existsSync23(candidate)) {
6315
6739
  try {
6316
- const raw = readFileSync25(candidate, "utf-8");
6740
+ const raw = readFileSync26(candidate, "utf-8");
6317
6741
  const parsed = parseYaml4(raw);
6318
6742
  if (parsed?.migrate?.["token-substitution"]) {
6319
6743
  return parsed.migrate["token-substitution"];
@@ -6369,7 +6793,7 @@ function collectCssFiles(dirPath) {
6369
6793
  function walk2(current) {
6370
6794
  const entries = readdirSync11(current, { withFileTypes: true });
6371
6795
  for (const entry of entries) {
6372
- const fullPath = join24(current, entry.name);
6796
+ const fullPath = join25(current, entry.name);
6373
6797
  if (entry.isDirectory()) {
6374
6798
  if (["node_modules", ".git", ".next", "dist", "build", ".cache"].includes(entry.name)) {
6375
6799
  continue;
@@ -6388,7 +6812,7 @@ function collectCssFiles(dirPath) {
6388
6812
  }
6389
6813
  function runSubstitutionPass(targetPath, map, themeId) {
6390
6814
  let files;
6391
- const stat = statSync8(targetPath);
6815
+ const stat = statSync9(targetPath);
6392
6816
  if (stat.isFile()) {
6393
6817
  files = [targetPath];
6394
6818
  } else {
@@ -6397,7 +6821,7 @@ function runSubstitutionPass(targetPath, map, themeId) {
6397
6821
  const fileResults = [];
6398
6822
  let totalSubstitutions = 0;
6399
6823
  for (const file of files) {
6400
- const content = readFileSync25(file, "utf-8");
6824
+ const content = readFileSync26(file, "utf-8");
6401
6825
  const { newContent, substitutions } = applySubstitutionsToContent(content, map);
6402
6826
  if (substitutions.length > 0) {
6403
6827
  fileResults.push({ file, substitutions, originalContent: content, newContent });
@@ -6431,9 +6855,9 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
6431
6855
  process.exit(1);
6432
6856
  return;
6433
6857
  }
6434
- const targetPath = resolve16(cwd, targetArg ?? ".");
6858
+ const targetPath = resolve17(cwd, targetArg ?? ".");
6435
6859
  try {
6436
- statSync8(targetPath);
6860
+ statSync9(targetPath);
6437
6861
  } catch {
6438
6862
  if (options.json) {
6439
6863
  console.log(JSON.stringify({
@@ -6520,23 +6944,23 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
6520
6944
  }
6521
6945
 
6522
6946
  // src/commands/sandbox/init.ts
6523
- import { existsSync as existsSync25, mkdirSync as mkdirSync12, readFileSync as readFileSync28, rmSync as rmSync2 } from "fs";
6524
- import { isAbsolute as isAbsolute4, join as join27, resolve as resolve18, dirname as dirname12 } from "path";
6947
+ import { existsSync as existsSync26, mkdirSync as mkdirSync12, readFileSync as readFileSync29, rmSync as rmSync2 } from "fs";
6948
+ import { isAbsolute as isAbsolute5, join as join28, resolve as resolve19, dirname as dirname13 } from "path";
6525
6949
  import { fileURLToPath as fileURLToPath3 } from "url";
6526
6950
  import * as childProcess3 from "child_process";
6527
6951
 
6528
6952
  // src/commands/sandbox/parse-handoff.ts
6529
- import { readFileSync as readFileSync26, existsSync as existsSync23 } from "fs";
6530
- import { dirname as dirname11, resolve as resolve17, isAbsolute as isAbsolute3 } from "path";
6953
+ import { readFileSync as readFileSync27, existsSync as existsSync24 } from "fs";
6954
+ import { dirname as dirname12, resolve as resolve18, isAbsolute as isAbsolute4 } from "path";
6531
6955
  function parseHandoff(handoffPath) {
6532
- const text = readFileSync26(handoffPath, "utf-8");
6956
+ const text = readFileSync27(handoffPath, "utf-8");
6533
6957
  const lines2 = text.split("\n");
6534
6958
  const warnings = [];
6535
6959
  const pattern2 = extractPatternSlug(lines2, warnings);
6536
6960
  const theme2 = extractMetaField(lines2, "Theme");
6537
6961
  const recipePath = extractRecipePath(text, handoffPath, warnings);
6538
6962
  const primitives = extractPrimitives(text, warnings);
6539
- const { screens, mockShapes } = recipePath && existsSync23(recipePath) ? extractRecipeArtifacts(recipePath, warnings) : { screens: [], mockShapes: [] };
6963
+ const { screens, mockShapes } = recipePath && existsSync24(recipePath) ? extractRecipeArtifacts(recipePath, warnings) : { screens: [], mockShapes: [] };
6540
6964
  return { pattern: pattern2, theme: theme2, recipePath, primitives, screens, mockShapes, warnings };
6541
6965
  }
6542
6966
  function extractPatternSlug(lines2, warnings) {
@@ -6567,8 +6991,8 @@ function extractRecipePath(text, handoffPath, warnings) {
6567
6991
  return void 0;
6568
6992
  }
6569
6993
  const href = m[2].trim();
6570
- if (isAbsolute3(href)) return href;
6571
- return resolve17(dirname11(handoffPath), href);
6994
+ if (isAbsolute4(href)) return href;
6995
+ return resolve18(dirname12(handoffPath), href);
6572
6996
  }
6573
6997
  var STATUS_PATTERNS = [
6574
6998
  { re: /NEW\s+gap/i, status: "gap-new" },
@@ -6615,7 +7039,7 @@ function extractPrimitives(text, warnings) {
6615
7039
  return primitives;
6616
7040
  }
6617
7041
  function extractRecipeArtifacts(recipePath, warnings) {
6618
- const text = readFileSync26(recipePath, "utf-8");
7042
+ const text = readFileSync27(recipePath, "utf-8");
6619
7043
  const screens = extractScreens(text);
6620
7044
  const mockShapes = extractMockFields(text, warnings);
6621
7045
  return { screens, mockShapes };
@@ -6689,8 +7113,8 @@ function tryPort(port) {
6689
7113
  }
6690
7114
 
6691
7115
  // src/commands/sandbox/scaffold.ts
6692
- import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync15, existsSync as existsSync24, readdirSync as readdirSync12 } from "fs";
6693
- import { join as join25 } from "path";
7116
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync15, existsSync as existsSync25, readdirSync as readdirSync12 } from "fs";
7117
+ import { join as join26 } from "path";
6694
7118
 
6695
7119
  // src/commands/sandbox/templates.ts
6696
7120
  var SANDBOX_PACKAGE_VERSION = "16.2.6";
@@ -7193,7 +7617,7 @@ function writeScaffold(sandboxDir, manifest, port, options = {}) {
7193
7617
  mkdirSync10(sandboxDir, { recursive: true });
7194
7618
  const created = [];
7195
7619
  const writeIfNew = (rel, contents) => {
7196
- const full = join25(sandboxDir, rel);
7620
+ const full = join26(sandboxDir, rel);
7197
7621
  mkdirSync10(dirOf(full), { recursive: true });
7198
7622
  writeFileSync15(full, contents, "utf-8");
7199
7623
  created.push(rel);
@@ -7221,7 +7645,7 @@ function writeScaffold(sandboxDir, manifest, port, options = {}) {
7221
7645
  }
7222
7646
  }
7223
7647
  writeIfNew("playwright.capture.mjs", captureScriptTemplate());
7224
- mkdirSync10(join25(sandboxDir, "captures", "approved"), { recursive: true });
7648
+ mkdirSync10(join26(sandboxDir, "captures", "approved"), { recursive: true });
7225
7649
  return { created };
7226
7650
  }
7227
7651
  function dirOf(p) {
@@ -7255,10 +7679,10 @@ function writeSandboxConfig(sandboxDir, manifest, port, options) {
7255
7679
  stripChromeSelectors: options.prototypeImport.stripChromeSelectors
7256
7680
  } : null
7257
7681
  };
7258
- writeFileSync15(join25(sandboxDir, "sandbox.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
7682
+ writeFileSync15(join26(sandboxDir, "sandbox.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
7259
7683
  }
7260
7684
  function sandboxIsEmpty(sandboxDir) {
7261
- if (!existsSync24(sandboxDir)) return true;
7685
+ if (!existsSync25(sandboxDir)) return true;
7262
7686
  try {
7263
7687
  return readdirSync12(sandboxDir).filter((f) => !f.startsWith(".")).length === 0;
7264
7688
  } catch {
@@ -7267,8 +7691,8 @@ function sandboxIsEmpty(sandboxDir) {
7267
7691
  }
7268
7692
 
7269
7693
  // src/commands/sandbox/html-prototype.ts
7270
- import { mkdirSync as mkdirSync11, readdirSync as readdirSync13, readFileSync as readFileSync27, statSync as statSync9, writeFileSync as writeFileSync16 } from "fs";
7271
- import { join as join26 } from "path";
7694
+ import { mkdirSync as mkdirSync11, readdirSync as readdirSync13, readFileSync as readFileSync28, statSync as statSync10, writeFileSync as writeFileSync16 } from "fs";
7695
+ import { join as join27 } from "path";
7272
7696
 
7273
7697
  // src/commands/sandbox/strip-chrome.ts
7274
7698
  var DEFAULT_STRIP_SELECTORS = [
@@ -7452,7 +7876,7 @@ function stripDocumentaryChrome(html, selectors) {
7452
7876
  var SCREEN_FILE_PATTERN = /^screen-(\d+)-([^/.]+)\.html$/i;
7453
7877
  function copyHtmlPrototype(sourceDir, sandboxDir, manifest, options = {}) {
7454
7878
  const warnings = [];
7455
- const destAbs = join26(sandboxDir, "public", "prototype");
7879
+ const destAbs = join27(sandboxDir, "public", "prototype");
7456
7880
  mkdirSync11(destAbs, { recursive: true });
7457
7881
  const stripChromeSelectors = options.stripChromeSelectors ?? [];
7458
7882
  const copiedFiles = copyTreeRelative(sourceDir, destAbs, "", stripChromeSelectors);
@@ -7506,24 +7930,24 @@ function deriveStateCoverageScreen(file, existingNames) {
7506
7930
  }
7507
7931
  function copyTreeRelative(srcDir, destDir, relDir = "", stripChromeSelectors = []) {
7508
7932
  const out = [];
7509
- const entries = readdirSync13(join26(srcDir, relDir));
7933
+ const entries = readdirSync13(join27(srcDir, relDir));
7510
7934
  for (const name of entries) {
7511
7935
  if (name.startsWith(".")) continue;
7512
- const srcPath = join26(srcDir, relDir, name);
7513
- const destPath = join26(destDir, relDir, name);
7514
- const stat = statSync9(srcPath);
7936
+ const srcPath = join27(srcDir, relDir, name);
7937
+ const destPath = join27(destDir, relDir, name);
7938
+ const stat = statSync10(srcPath);
7515
7939
  if (stat.isDirectory()) {
7516
7940
  mkdirSync11(destPath, { recursive: true });
7517
- out.push(...copyTreeRelative(srcDir, destDir, join26(relDir, name), stripChromeSelectors));
7941
+ out.push(...copyTreeRelative(srcDir, destDir, join27(relDir, name), stripChromeSelectors));
7518
7942
  } else if (stat.isFile()) {
7519
7943
  if (stripChromeSelectors.length > 0 && name.toLowerCase().endsWith(".html")) {
7520
- const html = readFileSync27(srcPath, "utf-8");
7944
+ const html = readFileSync28(srcPath, "utf-8");
7521
7945
  const stripped = stripDocumentaryChrome(html, stripChromeSelectors);
7522
7946
  writeFileSync16(destPath, stripped);
7523
7947
  } else {
7524
- writeFileSync16(destPath, readFileSync27(srcPath));
7948
+ writeFileSync16(destPath, readFileSync28(srcPath));
7525
7949
  }
7526
- out.push(join26("public", "prototype", relDir, name).replace(/\\/g, "/"));
7950
+ out.push(join27("public", "prototype", relDir, name).replace(/\\/g, "/"));
7527
7951
  }
7528
7952
  }
7529
7953
  return out;
@@ -7575,8 +7999,8 @@ async function runInit(name, cwd, options) {
7575
7999
  if (!name || !/^[a-z0-9][a-z0-9-_]*$/i.test(name)) {
7576
8000
  throw new Error(`Invalid sandbox name '${name}'. Use letters, digits, '-' or '_'.`);
7577
8001
  }
7578
- const handoffPath = isAbsolute4(options.handoff) ? options.handoff : resolve18(cwd, options.handoff);
7579
- if (!existsSync25(handoffPath)) {
8002
+ const handoffPath = isAbsolute5(options.handoff) ? options.handoff : resolve19(cwd, options.handoff);
8003
+ if (!existsSync26(handoffPath)) {
7580
8004
  throw new Error(`Handoff manifest not found: ${handoffPath}`);
7581
8005
  }
7582
8006
  const manifest = parseHandoff(handoffPath);
@@ -7585,7 +8009,7 @@ async function runInit(name, cwd, options) {
7585
8009
  `Handoff has no primitives \u2014 refusing to scaffold an empty sandbox. Check the manifest at ${handoffPath}`
7586
8010
  );
7587
8011
  }
7588
- const sandboxDir = join27(cwd, ".lo", "sandbox", name);
8012
+ const sandboxDir = join28(cwd, ".lo", "sandbox", name);
7589
8013
  if (!sandboxIsEmpty(sandboxDir)) {
7590
8014
  if (!options.overwrite) {
7591
8015
  throw new Error(
@@ -7598,8 +8022,8 @@ async function runInit(name, cwd, options) {
7598
8022
  const port = await findOpenPort();
7599
8023
  let prototypeImport;
7600
8024
  if (options.fromHtmlPrototype) {
7601
- const prototypeDir = isAbsolute4(options.fromHtmlPrototype) ? options.fromHtmlPrototype : resolve18(cwd, options.fromHtmlPrototype);
7602
- if (!existsSync25(prototypeDir)) {
8025
+ const prototypeDir = isAbsolute5(options.fromHtmlPrototype) ? options.fromHtmlPrototype : resolve19(cwd, options.fromHtmlPrototype);
8026
+ if (!existsSync26(prototypeDir)) {
7603
8027
  throw new Error(`HTML prototype directory not found: ${prototypeDir}`);
7604
8028
  }
7605
8029
  const stripChromeSelectors = resolveStripSelectors(
@@ -7658,22 +8082,22 @@ async function runInit(name, cwd, options) {
7658
8082
  function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile) {
7659
8083
  const candidates = [];
7660
8084
  if (themeFile) {
7661
- candidates.push(isAbsolute4(themeFile) ? themeFile : resolve18(cwd, themeFile));
8085
+ candidates.push(isAbsolute5(themeFile) ? themeFile : resolve19(cwd, themeFile));
7662
8086
  }
7663
- candidates.push(isAbsolute4(theme2) ? theme2 : resolve18(cwd, theme2));
8087
+ candidates.push(isAbsolute5(theme2) ? theme2 : resolve19(cwd, theme2));
7664
8088
  const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
7665
8089
  if (privateRoot && privateRoot.length > 0) {
7666
- candidates.push(join27(privateRoot, "themes", theme2, "theme.visor.yaml"));
8090
+ candidates.push(join28(privateRoot, "themes", theme2, "theme.visor.yaml"));
7667
8091
  }
7668
8092
  candidates.push(
7669
- join27(cwd, "themes", `${theme2}.visor.yaml`),
7670
- join27(cwd, "custom-themes", `${theme2}.visor.yaml`)
8093
+ join28(cwd, "themes", `${theme2}.visor.yaml`),
8094
+ join28(cwd, "custom-themes", `${theme2}.visor.yaml`)
7671
8095
  );
7672
- const yamlPath = candidates.find((p) => existsSync25(p));
8096
+ const yamlPath = candidates.find((p) => existsSync26(p));
7673
8097
  if (!yamlPath) {
7674
8098
  const searched = candidates.join(", ");
7675
8099
  manifest.warnings.push(
7676
- `Theme '${theme2}' not found (searched: ${searched}) \u2014 sandbox uses placeholder globals.css. Run 'npx visor theme apply <path-to-theme.visor.yaml> --adapter nextjs -o ${join27(sandboxDir, "app", "globals.css")}' manually, or re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH to a directory containing themes/${theme2}/theme.visor.yaml.`
8100
+ `Theme '${theme2}' not found (searched: ${searched}) \u2014 sandbox uses placeholder globals.css. Run 'npx visor theme apply <path-to-theme.visor.yaml> --adapter nextjs -o ${join28(sandboxDir, "app", "globals.css")}' manually, or re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH to a directory containing themes/${theme2}/theme.visor.yaml.`
7677
8101
  );
7678
8102
  if (!json) {
7679
8103
  logger.warn(`Theme '${theme2}' not found \u2014 leaving placeholder globals.css.`);
@@ -7681,12 +8105,12 @@ function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile
7681
8105
  `Re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH=<dir-containing-themes/${theme2}/theme.visor.yaml>.`
7682
8106
  );
7683
8107
  logger.item(
7684
- `Or apply manually: npx visor theme apply <path> --adapter nextjs -o ${join27(sandboxDir, "app", "globals.css")}`
8108
+ `Or apply manually: npx visor theme apply <path> --adapter nextjs -o ${join28(sandboxDir, "app", "globals.css")}`
7685
8109
  );
7686
8110
  }
7687
8111
  return;
7688
8112
  }
7689
- const globalsOut = join27(sandboxDir, "app", "globals.css");
8113
+ const globalsOut = join28(sandboxDir, "app", "globals.css");
7690
8114
  try {
7691
8115
  themeApplyCommand(yamlPath, sandboxDir, {
7692
8116
  output: globalsOut,
@@ -7739,12 +8163,12 @@ function loadKnownPrimitives() {
7739
8163
  }
7740
8164
  function readCliVersion() {
7741
8165
  try {
7742
- const here = dirname12(fileURLToPath3(import.meta.url));
8166
+ const here = dirname13(fileURLToPath3(import.meta.url));
7743
8167
  for (let i = 0; i < 6; i++) {
7744
8168
  const segments = new Array(i).fill("..");
7745
- const candidate = join27(here, ...segments, "package.json");
8169
+ const candidate = join28(here, ...segments, "package.json");
7746
8170
  try {
7747
- const pkg2 = JSON.parse(readFileSync28(candidate, "utf-8"));
8171
+ const pkg2 = JSON.parse(readFileSync29(candidate, "utf-8"));
7748
8172
  if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) return pkg2.version;
7749
8173
  } catch {
7750
8174
  }
@@ -7755,14 +8179,14 @@ function readCliVersion() {
7755
8179
  }
7756
8180
 
7757
8181
  // src/commands/sandbox/dev.ts
7758
- import { existsSync as existsSync26, readFileSync as readFileSync29 } from "fs";
7759
- import { join as join28 } from "path";
8182
+ import { existsSync as existsSync27, readFileSync as readFileSync30 } from "fs";
8183
+ import { join as join29 } from "path";
7760
8184
  import * as childProcess4 from "child_process";
7761
8185
  function sandboxDevCommand(cwd, options) {
7762
8186
  const json = options.json ?? false;
7763
- const sandboxDir = join28(cwd, ".lo", "sandbox", options.name);
7764
- const configPath = join28(sandboxDir, "sandbox.json");
7765
- if (!existsSync26(configPath)) {
8187
+ const sandboxDir = join29(cwd, ".lo", "sandbox", options.name);
8188
+ const configPath = join29(sandboxDir, "sandbox.json");
8189
+ if (!existsSync27(configPath)) {
7766
8190
  fail(
7767
8191
  json,
7768
8192
  `Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init ${options.name} --handoff ... --theme ...' first.`
@@ -7771,7 +8195,7 @@ function sandboxDevCommand(cwd, options) {
7771
8195
  }
7772
8196
  let config;
7773
8197
  try {
7774
- config = JSON.parse(readFileSync29(configPath, "utf-8"));
8198
+ config = JSON.parse(readFileSync30(configPath, "utf-8"));
7775
8199
  } catch (err) {
7776
8200
  fail(json, `Invalid sandbox.json at ${configPath}: ${err.message}`);
7777
8201
  return;
@@ -7829,20 +8253,20 @@ function fail(json, message) {
7829
8253
  // src/commands/sandbox/approve.ts
7830
8254
  import {
7831
8255
  copyFileSync as copyFileSync2,
7832
- existsSync as existsSync27,
8256
+ existsSync as existsSync28,
7833
8257
  mkdirSync as mkdirSync13,
7834
- readFileSync as readFileSync30,
8258
+ readFileSync as readFileSync31,
7835
8259
  readdirSync as readdirSync14,
7836
8260
  rmSync as rmSync3,
7837
8261
  writeFileSync as writeFileSync17
7838
8262
  } from "fs";
7839
- import { basename as basename7, join as join29 } from "path";
8263
+ import { basename as basename7, join as join30 } from "path";
7840
8264
  import * as childProcess5 from "child_process";
7841
8265
  function sandboxApproveCommand(cwd, options) {
7842
8266
  const json = options.json ?? false;
7843
- const sandboxDir = join29(cwd, ".lo", "sandbox", options.name);
7844
- const configPath = join29(sandboxDir, "sandbox.json");
7845
- if (!existsSync27(configPath)) {
8267
+ const sandboxDir = join30(cwd, ".lo", "sandbox", options.name);
8268
+ const configPath = join30(sandboxDir, "sandbox.json");
8269
+ if (!existsSync28(configPath)) {
7846
8270
  fail2(
7847
8271
  json,
7848
8272
  `Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init' first.`
@@ -7856,8 +8280,8 @@ function sandboxApproveCommand(cwd, options) {
7856
8280
  runCapture(sandboxDir, configPath, options.name, json);
7857
8281
  }
7858
8282
  function runCapture(sandboxDir, configPath, sandboxName, json) {
7859
- const config = JSON.parse(readFileSync30(configPath, "utf-8"));
7860
- const captureScriptPath = join29(sandboxDir, "playwright.capture.mjs");
8283
+ const config = JSON.parse(readFileSync31(configPath, "utf-8"));
8284
+ const captureScriptPath = join30(sandboxDir, "playwright.capture.mjs");
7861
8285
  writeFileSync17(captureScriptPath, captureScriptTemplate(), "utf-8");
7862
8286
  ensurePlaywrightInstalled(sandboxDir, json);
7863
8287
  const result = childProcess5.spawnSync(
@@ -7879,9 +8303,9 @@ function runCapture(sandboxDir, configPath, sandboxName, json) {
7879
8303
  ${result.stderr}`);
7880
8304
  return;
7881
8305
  }
7882
- const pendingDir = join29(sandboxDir, "captures", "pending");
7883
- const diffsDir = join29(sandboxDir, "captures", "diffs");
7884
- const approvedDir = join29(sandboxDir, "captures", "approved");
8306
+ const pendingDir = join30(sandboxDir, "captures", "pending");
8307
+ const diffsDir = join30(sandboxDir, "captures", "diffs");
8308
+ const approvedDir = join30(sandboxDir, "captures", "approved");
7885
8309
  const pendingFiles = safeListPngs(pendingDir);
7886
8310
  const diffFiles = safeListPngs(diffsDir);
7887
8311
  const hasBaseline = safeListPngs(approvedDir).length > 0;
@@ -7918,9 +8342,9 @@ ${result.stderr}`);
7918
8342
  }
7919
8343
  }
7920
8344
  function runPromotion(sandboxDir, sandboxName, json) {
7921
- const pendingDir = join29(sandboxDir, "captures", "pending");
7922
- const approvedDir = join29(sandboxDir, "captures", "approved");
7923
- const diffsDir = join29(sandboxDir, "captures", "diffs");
8345
+ const pendingDir = join30(sandboxDir, "captures", "pending");
8346
+ const approvedDir = join30(sandboxDir, "captures", "approved");
8347
+ const diffsDir = join30(sandboxDir, "captures", "diffs");
7924
8348
  const pendingFiles = safeListPngs(pendingDir);
7925
8349
  if (pendingFiles.length === 0) {
7926
8350
  fail2(
@@ -7932,7 +8356,7 @@ function runPromotion(sandboxDir, sandboxName, json) {
7932
8356
  mkdirSync13(approvedDir, { recursive: true });
7933
8357
  const promoted = [];
7934
8358
  for (const src of pendingFiles) {
7935
- const dest = join29(approvedDir, basename7(src));
8359
+ const dest = join30(approvedDir, basename7(src));
7936
8360
  copyFileSync2(src, dest);
7937
8361
  promoted.push(dest);
7938
8362
  }
@@ -7952,8 +8376,8 @@ function runPromotion(sandboxDir, sandboxName, json) {
7952
8376
  }
7953
8377
  }
7954
8378
  function ensurePlaywrightInstalled(sandboxDir, json) {
7955
- const markerPath = join29(sandboxDir, ".playwright-installed");
7956
- if (existsSync27(markerPath)) return;
8379
+ const markerPath = join30(sandboxDir, ".playwright-installed");
8380
+ if (existsSync28(markerPath)) return;
7957
8381
  if (!json) logger.info("Installing Playwright Chromium (one-time)...");
7958
8382
  const result = childProcess5.spawnSync(
7959
8383
  "npx",
@@ -7972,8 +8396,8 @@ function ensurePlaywrightInstalled(sandboxDir, json) {
7972
8396
  writeFileSync17(markerPath, (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
7973
8397
  }
7974
8398
  function safeListPngs(dir) {
7975
- if (!existsSync27(dir)) return [];
7976
- return readdirSync14(dir).filter((f) => f.endsWith(".png")).map((f) => join29(dir, f));
8399
+ if (!existsSync28(dir)) return [];
8400
+ return readdirSync14(dir).filter((f) => f.endsWith(".png")).map((f) => join30(dir, f));
7977
8401
  }
7978
8402
  function tryParseJson(s) {
7979
8403
  try {
@@ -7994,13 +8418,13 @@ function fail2(json, message) {
7994
8418
  // src/commands/spawn.ts
7995
8419
  import {
7996
8420
  cpSync,
7997
- existsSync as existsSync29,
7998
- readFileSync as readFileSync31,
8421
+ existsSync as existsSync30,
8422
+ readFileSync as readFileSync32,
7999
8423
  readdirSync as readdirSync16,
8000
8424
  rmSync as rmSync4,
8001
- statSync as statSync11
8425
+ statSync as statSync12
8002
8426
  } from "fs";
8003
- import { basename as basename8, dirname as dirname13, isAbsolute as isAbsolute5, join as join31, resolve as resolve19 } from "path";
8427
+ import { basename as basename8, dirname as dirname14, isAbsolute as isAbsolute6, join as join32, resolve as resolve20 } from "path";
8004
8428
  import * as childProcess6 from "child_process";
8005
8429
  import { parse as parseYaml5 } from "yaml";
8006
8430
  import { generateThemeData as generateThemeData7 } from "@loworbitstudio/visor-theme-engine";
@@ -8008,8 +8432,8 @@ import { nextjsAdapter as nextjsAdapter3 } from "@loworbitstudio/visor-theme-eng
8008
8432
  import { validate as validate3 } from "@loworbitstudio/visor-theme-engine";
8009
8433
 
8010
8434
  // src/lib/blessed-discovery.ts
8011
- import { existsSync as existsSync28, readdirSync as readdirSync15, statSync as statSync10 } from "fs";
8012
- import { join as join30 } from "path";
8435
+ import { existsSync as existsSync29, readdirSync as readdirSync15, statSync as statSync11 } from "fs";
8436
+ import { join as join31 } from "path";
8013
8437
  var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
8014
8438
  "node_modules",
8015
8439
  ".next",
@@ -8020,13 +8444,13 @@ var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
8020
8444
  ".cache"
8021
8445
  ]);
8022
8446
  var REFERENCE_BUILD_DIRNAME = "reference-build";
8023
- var APPROVED_CAPTURES_SUBPATH = join30("captures", "approved");
8447
+ var APPROVED_CAPTURES_SUBPATH = join31("captures", "approved");
8024
8448
  var MISSING_MANIFEST_REASON = `missing ${BLESSED_MANIFEST_FILENAME}`;
8025
8449
  function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
8026
8450
  const builds = [];
8027
8451
  const incomplete = [];
8028
8452
  const errors = [];
8029
- if (!existsSync28(blessedDir)) {
8453
+ if (!existsSync29(blessedDir)) {
8030
8454
  return { builds, incomplete, errors };
8031
8455
  }
8032
8456
  walk2(blessedDir, 0);
@@ -8065,7 +8489,7 @@ function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
8065
8489
  for (const entry of entries) {
8066
8490
  if (!entry.isDirectory()) continue;
8067
8491
  if (WALK_EXCLUDE_DIRS.has(entry.name)) continue;
8068
- walk2(join30(dir, entry.name), depth + 1);
8492
+ walk2(join31(dir, entry.name), depth + 1);
8069
8493
  }
8070
8494
  }
8071
8495
  }
@@ -8074,13 +8498,13 @@ function isNearMissRoot(dir, entries) {
8074
8498
  (entry) => entry.isDirectory() && entry.name === REFERENCE_BUILD_DIRNAME
8075
8499
  );
8076
8500
  if (!hasReferenceBuild) return false;
8077
- const approved = join30(dir, APPROVED_CAPTURES_SUBPATH);
8501
+ const approved = join31(dir, APPROVED_CAPTURES_SUBPATH);
8078
8502
  try {
8079
- if (!statSync10(approved).isDirectory()) return false;
8503
+ if (!statSync11(approved).isDirectory()) return false;
8080
8504
  } catch {
8081
8505
  return false;
8082
8506
  }
8083
- return !existsSync28(join30(dir, REFERENCE_BUILD_DIRNAME, BLESSED_MANIFEST_FILENAME));
8507
+ return !existsSync29(join31(dir, REFERENCE_BUILD_DIRNAME, BLESSED_MANIFEST_FILENAME));
8084
8508
  }
8085
8509
  function resolveBlessedBuild(blessedDir, shape, pattern2) {
8086
8510
  const { builds } = discoverBlessedBuilds(blessedDir);
@@ -8094,11 +8518,11 @@ function resolveBlessedBuild(blessedDir, shape, pattern2) {
8094
8518
  var BLESSED_ROOT_DIRNAME = "design-prototypes";
8095
8519
  var NO_BLESSED_ROOT_ERROR = `No blessed-build root configured. Pass --blessed-dir <path>, set VISOR_BLESSED_DIR, or run from a checkout that contains a '${BLESSED_ROOT_DIRNAME}/' directory. See docs/blessed-builds.md.`;
8096
8520
  function discoverBlessedRoot(cwd) {
8097
- let dir = resolve19(cwd);
8521
+ let dir = resolve20(cwd);
8098
8522
  for (; ; ) {
8099
- const candidate = join31(dir, BLESSED_ROOT_DIRNAME);
8100
- if (existsSync29(candidate) && statSync11(candidate).isDirectory()) return candidate;
8101
- const parent = dirname13(dir);
8523
+ const candidate = join32(dir, BLESSED_ROOT_DIRNAME);
8524
+ if (existsSync30(candidate) && statSync12(candidate).isDirectory()) return candidate;
8525
+ const parent = dirname14(dir);
8102
8526
  if (parent === dir) return void 0;
8103
8527
  dir = parent;
8104
8528
  }
@@ -8133,25 +8557,25 @@ function parseBlessedIdentifier(from) {
8133
8557
  function resolveBlessedDir(cwd, options) {
8134
8558
  const explicit = options.blessedDir ?? (process.env.VISOR_BLESSED_DIR && process.env.VISOR_BLESSED_DIR.length > 0 ? process.env.VISOR_BLESSED_DIR : void 0);
8135
8559
  if (explicit !== void 0) {
8136
- return isAbsolute5(explicit) ? explicit : resolve19(cwd, explicit);
8560
+ return isAbsolute6(explicit) ? explicit : resolve20(cwd, explicit);
8137
8561
  }
8138
8562
  return discoverBlessedRoot(cwd);
8139
8563
  }
8140
8564
  function resolveThemeFile(theme2, cwd, themeFile) {
8141
8565
  const candidates = [];
8142
8566
  if (themeFile) {
8143
- candidates.push(isAbsolute5(themeFile) ? themeFile : resolve19(cwd, themeFile));
8567
+ candidates.push(isAbsolute6(themeFile) ? themeFile : resolve20(cwd, themeFile));
8144
8568
  }
8145
- candidates.push(isAbsolute5(theme2) ? theme2 : resolve19(cwd, theme2));
8569
+ candidates.push(isAbsolute6(theme2) ? theme2 : resolve20(cwd, theme2));
8146
8570
  const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
8147
8571
  if (privateRoot && privateRoot.length > 0) {
8148
- candidates.push(join31(privateRoot, "themes", theme2, "theme.visor.yaml"));
8572
+ candidates.push(join32(privateRoot, "themes", theme2, "theme.visor.yaml"));
8149
8573
  }
8150
8574
  candidates.push(
8151
- join31(cwd, "themes", `${theme2}.visor.yaml`),
8152
- join31(cwd, "custom-themes", `${theme2}.visor.yaml`)
8575
+ join32(cwd, "themes", `${theme2}.visor.yaml`),
8576
+ join32(cwd, "custom-themes", `${theme2}.visor.yaml`)
8153
8577
  );
8154
- const found = candidates.find((candidate) => existsSync29(candidate));
8578
+ const found = candidates.find((candidate) => existsSync30(candidate));
8155
8579
  if (!found) {
8156
8580
  throw new Error(
8157
8581
  `Theme '${theme2}' not found (searched: ${candidates.join(", ")}). Pass --theme-file <path>, use a direct path, or set VISOR_THEMES_PRIVATE_PATH.`
@@ -8160,7 +8584,7 @@ function resolveThemeFile(theme2, cwd, themeFile) {
8160
8584
  return { path: found, searched: candidates };
8161
8585
  }
8162
8586
  function applyThemeToFork(themeFile, outputDir, manifest) {
8163
- const yaml = readFileSync31(themeFile, "utf-8");
8587
+ const yaml = readFileSync32(themeFile, "utf-8");
8164
8588
  const data = generateThemeData7(yaml);
8165
8589
  const themeId = data.config.name;
8166
8590
  if (!themeId || themeId.length === 0) {
@@ -8207,8 +8631,8 @@ Available builds:
8207
8631
  ${list}`
8208
8632
  );
8209
8633
  }
8210
- const outputDir = isAbsolute5(options.output) ? options.output : resolve19(cwd, options.output);
8211
- if (existsSync29(outputDir) && readdirSync16(outputDir).length > 0) {
8634
+ const outputDir = isAbsolute6(options.output) ? options.output : resolve20(cwd, options.output);
8635
+ if (existsSync30(outputDir) && readdirSync16(outputDir).length > 0) {
8212
8636
  throw new Error(`Output directory already exists and is not empty: ${outputDir}`);
8213
8637
  }
8214
8638
  const { path: themeFile } = resolveThemeFile(options.theme, cwd, options.themeFile);
@@ -8221,7 +8645,7 @@ ${list}`
8221
8645
  try {
8222
8646
  applyThemeToFork(themeFile, outputDir, build.manifest);
8223
8647
  if (options.validate) {
8224
- const parsed = parseYaml5(readFileSync31(themeFile, "utf-8"));
8648
+ const parsed = parseYaml5(readFileSync32(themeFile, "utf-8"));
8225
8649
  const result = validate3(parsed);
8226
8650
  if (!result.valid) {
8227
8651
  const messages = result.errors.map((e) => e.message).join("; ");
@@ -8333,8 +8757,8 @@ function spawnCommand(cwd, options) {
8333
8757
  }
8334
8758
 
8335
8759
  // src/commands/render.ts
8336
- import { existsSync as existsSync30, mkdirSync as mkdirSync14, readFileSync as readFileSync32 } from "fs";
8337
- import { dirname as dirname14, isAbsolute as isAbsolute6, resolve as resolve20 } from "path";
8760
+ import { existsSync as existsSync31, mkdirSync as mkdirSync14, readFileSync as readFileSync33 } from "fs";
8761
+ import { dirname as dirname15, isAbsolute as isAbsolute7, resolve as resolve21 } from "path";
8338
8762
  var FIXTURES = {
8339
8763
  "stat-card": {
8340
8764
  default: {
@@ -8486,28 +8910,28 @@ function pascalCase(kebab) {
8486
8910
  }
8487
8911
  function firstExisting(candidates) {
8488
8912
  for (const c of candidates) {
8489
- if (existsSync30(c)) return c;
8913
+ if (existsSync31(c)) return c;
8490
8914
  }
8491
8915
  return null;
8492
8916
  }
8493
8917
  function resolveComponentFile(cwd, name) {
8494
8918
  return firstExisting([
8495
- resolve20(cwd, "components", "ui", name, `${name}.tsx`),
8496
- resolve20(cwd, "components", "devtools", name, `${name}.tsx`),
8497
- resolve20(cwd, "components", "ui", name, "index.tsx")
8919
+ resolve21(cwd, "components", "ui", name, `${name}.tsx`),
8920
+ resolve21(cwd, "components", "devtools", name, `${name}.tsx`),
8921
+ resolve21(cwd, "components", "ui", name, "index.tsx")
8498
8922
  ]);
8499
8923
  }
8500
8924
  function resolveThemeCssFile(cwd, slug2) {
8501
8925
  return firstExisting([
8502
- resolve20(cwd, "packages", "docs", "app", `${slug2}-theme.css`),
8503
- resolve20(cwd, "app", `${slug2}-theme.css`),
8504
- resolve20(cwd, `${slug2}-theme.css`)
8926
+ resolve21(cwd, "packages", "docs", "app", `${slug2}-theme.css`),
8927
+ resolve21(cwd, "app", `${slug2}-theme.css`),
8928
+ resolve21(cwd, `${slug2}-theme.css`)
8505
8929
  ]);
8506
8930
  }
8507
8931
  function resolveTokensCssFile(cwd) {
8508
8932
  return firstExisting([
8509
- resolve20(cwd, "packages", "tokens", "dist", "tokens.css"),
8510
- resolve20(cwd, "node_modules", "@loworbitstudio", "visor-core", "dist", "tokens.css")
8933
+ resolve21(cwd, "packages", "tokens", "dist", "tokens.css"),
8934
+ resolve21(cwd, "node_modules", "@loworbitstudio", "visor-core", "dist", "tokens.css")
8511
8935
  ]);
8512
8936
  }
8513
8937
  async function loadOptional(moduleName) {
@@ -8686,8 +9110,8 @@ async function renderCommand(component, cwd, options) {
8686
9110
  }
8687
9111
  const themeClass = `${options.theme}-theme`;
8688
9112
  const html = buildHtml({
8689
- tokensCss: readFileSync32(tokensCssFile, "utf-8"),
8690
- themeCss: readFileSync32(themeCssFile, "utf-8"),
9113
+ tokensCss: readFileSync33(tokensCssFile, "utf-8"),
9114
+ themeCss: readFileSync33(themeCssFile, "utf-8"),
8691
9115
  componentCss,
8692
9116
  bundleJs,
8693
9117
  themeClass,
@@ -8696,7 +9120,7 @@ async function renderCommand(component, cwd, options) {
8696
9120
  const width = Number(options.width ?? 720);
8697
9121
  const height = Number(options.height ?? 640);
8698
9122
  const outPath = resolveOutPath(cwd, options.out, component, options.theme, mode, state);
8699
- mkdirSync14(dirname14(outPath), { recursive: true });
9123
+ mkdirSync14(dirname15(outPath), { recursive: true });
8700
9124
  const browser = await playwright.chromium.launch();
8701
9125
  let probe;
8702
9126
  try {
@@ -8752,7 +9176,7 @@ async function renderCommand(component, cwd, options) {
8752
9176
  } finally {
8753
9177
  await browser.close();
8754
9178
  }
8755
- const fileSize = existsSync30(outPath) ? statBytes(outPath) : 0;
9179
+ const fileSize = existsSync31(outPath) ? statBytes(outPath) : 0;
8756
9180
  if (json) {
8757
9181
  console.log(
8758
9182
  JSON.stringify(
@@ -8788,14 +9212,14 @@ async function renderCommand(component, cwd, options) {
8788
9212
  }
8789
9213
  function resolveOutPath(cwd, out, component, theme2, mode, state) {
8790
9214
  if (out) {
8791
- return isAbsolute6(out) ? out : resolve20(cwd, out);
9215
+ return isAbsolute7(out) ? out : resolve21(cwd, out);
8792
9216
  }
8793
9217
  const suffix = state === "default" ? "" : `__${state}`;
8794
- return resolve20(cwd, ".visor", "renders", `${component}__${theme2}__${mode}${suffix}.png`);
9218
+ return resolve21(cwd, ".visor", "renders", `${component}__${theme2}__${mode}${suffix}.png`);
8795
9219
  }
8796
9220
  function statBytes(path2) {
8797
9221
  try {
8798
- return readFileSync32(path2).length;
9222
+ return readFileSync33(path2).length;
8799
9223
  } catch {
8800
9224
  return 0;
8801
9225
  }
@@ -8808,9 +9232,9 @@ function formatBytes2(bytes) {
8808
9232
  }
8809
9233
 
8810
9234
  // src/index.ts
8811
- var __dirname2 = dirname15(fileURLToPath4(import.meta.url));
9235
+ var __dirname2 = dirname16(fileURLToPath4(import.meta.url));
8812
9236
  var pkg = JSON.parse(
8813
- readFileSync33(join33(__dirname2, "..", "package.json"), "utf-8")
9237
+ readFileSync34(join34(__dirname2, "..", "package.json"), "utf-8")
8814
9238
  );
8815
9239
  var program = new Command2();
8816
9240
  program.name("visor").description("CLI for the Visor design system").version(pkg.version);