@loworbitstudio/visor 1.22.0 → 1.23.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,40 @@ 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";
1219
1630
  if (useJson) {
1220
1631
  console.log(JSON.stringify({ success: true, ...result }, null, 2));
1221
- if (!options.noFail && result.summary.errorCount > 0) process.exit(1);
1222
- process.exit(0);
1632
+ if (!options.noFail && result.summary.errorCount > 0) process.exitCode = 1;
1223
1633
  return;
1224
1634
  }
1225
- const { errors, warnings, summary } = result;
1635
+ const { errors, warnings, summary, composition } = result;
1636
+ const printKitMembership = () => {
1637
+ const km = composition.kitMembership;
1638
+ if (km.asserted) {
1639
+ logger.item(`Kit membership: asserted against ${km.taxonomyPath} (${km.elementCount} element(s), via ${km.source}).`);
1640
+ } else {
1641
+ logger.item(`Kit membership: NOT asserted \u2014 ${km.reason}`);
1642
+ }
1643
+ };
1226
1644
  if (summary.errorCount === 0 && summary.warningCount === 0) {
1227
1645
  logger.success(`No design anti-patterns found \u2014 ${summary.filesScanned} file(s) scanned.`);
1646
+ printCompositionScope();
1647
+ printKitMembership();
1648
+ logger.blank();
1228
1649
  process.exit(0);
1229
1650
  return;
1230
1651
  }
@@ -1256,6 +1677,8 @@ function checkDesignCommand(pathArg, options) {
1256
1677
  } else {
1257
1678
  logger.warn(`${summary.warningCount} warning(s) (0 errors)`);
1258
1679
  }
1680
+ printCompositionScope();
1681
+ printKitMembership();
1259
1682
  logger.blank();
1260
1683
  if (!options.noFail && summary.errorCount > 0) process.exit(1);
1261
1684
  }
@@ -1307,7 +1730,7 @@ function checkCommand() {
1307
1730
  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
1731
  await checkDiffCommand(pathArg, options);
1309
1732
  });
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) => {
1733
+ 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
1734
  checkDesignCommand(pathArg, options);
1312
1735
  });
1313
1736
  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 +1740,14 @@ function checkCommand() {
1317
1740
  }
1318
1741
 
1319
1742
  // 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";
1743
+ import { existsSync as existsSync7, writeFileSync as writeFileSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync10 } from "fs";
1744
+ import { join as join9, dirname as dirname6, basename as basename2 } from "path";
1322
1745
  import { fileURLToPath as fileURLToPath2 } from "url";
1323
1746
  import * as childProcess2 from "child_process";
1324
1747
 
1325
1748
  // src/config/config.ts
1326
- import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync2 } from "fs";
1327
- import { join as join4 } from "path";
1749
+ import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync3 } from "fs";
1750
+ import { join as join5 } from "path";
1328
1751
 
1329
1752
  // src/config/defaults.ts
1330
1753
  var DEFAULT_CONFIG = {
@@ -1341,19 +1764,19 @@ var CONFIG_FILE = "visor.json";
1341
1764
 
1342
1765
  // src/config/config.ts
1343
1766
  function getConfigPath(cwd) {
1344
- return join4(cwd, CONFIG_FILE);
1767
+ return join5(cwd, CONFIG_FILE);
1345
1768
  }
1346
1769
  function configExists(cwd) {
1347
- return existsSync2(getConfigPath(cwd));
1770
+ return existsSync3(getConfigPath(cwd));
1348
1771
  }
1349
1772
  function loadConfig(cwd) {
1350
1773
  const configPath = getConfigPath(cwd);
1351
- if (!existsSync2(configPath)) {
1774
+ if (!existsSync3(configPath)) {
1352
1775
  throw new Error(
1353
1776
  `No ${CONFIG_FILE} found. Run "visor init" first.`
1354
1777
  );
1355
1778
  }
1356
- const raw = readFileSync5(configPath, "utf-8");
1779
+ const raw = readFileSync6(configPath, "utf-8");
1357
1780
  const parsed = JSON.parse(raw);
1358
1781
  const knownKeys = /* @__PURE__ */ new Set(["paths"]);
1359
1782
  for (const key of Object.keys(parsed)) {
@@ -1390,12 +1813,12 @@ function writeConfig(cwd, config) {
1390
1813
 
1391
1814
  // src/utils/packages.ts
1392
1815
  import { execFileSync } from "child_process";
1393
- import { existsSync as existsSync3, readFileSync as readFileSync6 } from "fs";
1394
- import { join as join5 } from "path";
1816
+ import { existsSync as existsSync4, readFileSync as readFileSync7 } from "fs";
1817
+ import { join as join6 } from "path";
1395
1818
  function readPackageJson(cwd) {
1396
- const pkgPath = join5(cwd, "package.json");
1397
- if (!existsSync3(pkgPath)) return null;
1398
- return JSON.parse(readFileSync6(pkgPath, "utf-8"));
1819
+ const pkgPath = join6(cwd, "package.json");
1820
+ if (!existsSync4(pkgPath)) return null;
1821
+ return JSON.parse(readFileSync7(pkgPath, "utf-8"));
1399
1822
  }
1400
1823
  function isPackageInstalled(packageName, cwd) {
1401
1824
  const pkg2 = readPackageJson(cwd);
@@ -1505,8 +1928,8 @@ export default function RootLayout({ children }: { children: ReactNode }) {
1505
1928
  }
1506
1929
 
1507
1930
  // 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";
1931
+ import { existsSync as existsSync5, mkdirSync, readFileSync as readFileSync8, writeFileSync as writeFileSync2 } from "fs";
1932
+ import { dirname as dirname5, join as join7 } from "path";
1510
1933
 
1511
1934
  // src/commands/init-plays/pattern-build.ts
1512
1935
  var patternBuildPlay = {
@@ -1545,24 +1968,24 @@ function knownPlayIds() {
1545
1968
  return KNOWN_PLAYS.map((p) => p.id);
1546
1969
  }
1547
1970
  function playStatePaths(cwd, def, name) {
1548
- const relDir = join6(".lo", def.loSubdir, name);
1549
- const dir = join6(cwd, relDir);
1971
+ const relDir = join7(".lo", def.loSubdir, name);
1972
+ const dir = join7(cwd, relDir);
1550
1973
  return {
1551
1974
  dir,
1552
- statePath: join6(dir, "state.json"),
1553
- relStatePath: join6(relDir, "state.json")
1975
+ statePath: join7(dir, "state.json"),
1976
+ relStatePath: join7(relDir, "state.json")
1554
1977
  };
1555
1978
  }
1556
1979
  function readPlayState(statePath) {
1557
- if (!existsSync4(statePath)) return null;
1980
+ if (!existsSync5(statePath)) return null;
1558
1981
  try {
1559
- return JSON.parse(readFileSync7(statePath, "utf-8"));
1982
+ return JSON.parse(readFileSync8(statePath, "utf-8"));
1560
1983
  } catch {
1561
1984
  return null;
1562
1985
  }
1563
1986
  }
1564
1987
  function writePlayState(statePath, state) {
1565
- mkdirSync(dirname4(statePath), { recursive: true });
1988
+ mkdirSync(dirname5(statePath), { recursive: true });
1566
1989
  writeFileSync2(statePath, JSON.stringify(state, null, 2) + "\n", "utf-8");
1567
1990
  }
1568
1991
 
@@ -1627,16 +2050,16 @@ function heuristicPort(name) {
1627
2050
  }
1628
2051
 
1629
2052
  // src/lib/lo-play-checklist.ts
1630
- import { existsSync as existsSync5, readFileSync as readFileSync8 } from "fs";
2053
+ import { existsSync as existsSync6, readFileSync as readFileSync9 } from "fs";
1631
2054
  import { homedir } from "os";
1632
- import { join as join7 } from "path";
2055
+ import { join as join8 } from "path";
1633
2056
  function defaultChecklistRoot() {
1634
- return join7(homedir(), ".claude", "skills", "lo-play");
2057
+ return join8(homedir(), ".claude", "skills", "lo-play");
1635
2058
  }
1636
2059
  function readEntryChecklist(playType, options = {}) {
1637
2060
  const root = options.root ?? defaultChecklistRoot();
1638
- const path2 = join7(root, playType, "entry-checklist.md");
1639
- if (!existsSync5(path2)) {
2061
+ const path2 = join8(root, playType, "entry-checklist.md");
2062
+ if (!existsSync6(path2)) {
1640
2063
  return {
1641
2064
  found: false,
1642
2065
  path: path2,
@@ -1644,7 +2067,7 @@ function readEntryChecklist(playType, options = {}) {
1644
2067
  };
1645
2068
  }
1646
2069
  try {
1647
- return { found: true, path: path2, content: readFileSync8(path2, "utf-8") };
2070
+ return { found: true, path: path2, content: readFileSync9(path2, "utf-8") };
1648
2071
  } catch {
1649
2072
  return {
1650
2073
  found: false,
@@ -1686,7 +2109,7 @@ function initCommand(cwd, options) {
1686
2109
  process.exit(1);
1687
2110
  }
1688
2111
  }
1689
- if (options?.template === "nextjs" && existsSync6(join8(cwd, "package.json"))) {
2112
+ if (options?.template === "nextjs" && existsSync7(join9(cwd, "package.json"))) {
1690
2113
  emitError(
1691
2114
  json,
1692
2115
  "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 +2280,8 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
1857
2280
  runCreateNextApp(cwd, json);
1858
2281
  writeVisorConfig(cwd, json, filesCreated, filesSkipped);
1859
2282
  runInstallVisorDeps(cwd, json);
1860
- const yamlPath = join8(cwd, ".visor.yaml");
1861
- if (existsSync6(yamlPath)) {
2283
+ const yamlPath = join9(cwd, ".visor.yaml");
2284
+ if (existsSync7(yamlPath)) {
1862
2285
  filesSkipped.push(".visor.yaml");
1863
2286
  if (!json) {
1864
2287
  logger.warn(".visor.yaml already exists. Skipping.");
@@ -1876,10 +2299,10 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
1876
2299
  tokens: data.tokens,
1877
2300
  config: data.config
1878
2301
  });
1879
- const globalsPath = join8(cwd, "app", "globals.css");
1880
- const globalsDir = dirname5(globalsPath);
2302
+ const globalsPath = join9(cwd, "app", "globals.css");
2303
+ const globalsDir = dirname6(globalsPath);
1881
2304
  mkdirSync2(globalsDir, { recursive: true });
1882
- if (existsSync6(globalsPath)) {
2305
+ if (existsSync7(globalsPath)) {
1883
2306
  writeFileSync3(globalsPath, css, "utf-8");
1884
2307
  filesCreated.push("app/globals.css");
1885
2308
  } else {
@@ -1889,16 +2312,16 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
1889
2312
  if (!json) {
1890
2313
  logger.success("Created app/globals.css with theme tokens");
1891
2314
  }
1892
- const layoutPath = join8(cwd, "app", "layout.tsx");
2315
+ const layoutPath = join9(cwd, "app", "layout.tsx");
1893
2316
  const colorScheme = extractColorScheme(NEXTJS_STARTER_YAML);
1894
2317
  writeFileSync3(layoutPath, generateNextjsLayout(colorScheme), "utf-8");
1895
2318
  filesCreated.push("app/layout.tsx");
1896
2319
  if (!json) {
1897
2320
  logger.success("Wired app/layout.tsx with FOWT prevention and theme tokens");
1898
2321
  }
1899
- const stampDir = join8(cwd, ".lo");
1900
- const stampPath = join8(stampDir, "borealis.json");
1901
- if (existsSync6(stampPath)) {
2322
+ const stampDir = join9(cwd, ".lo");
2323
+ const stampPath = join9(stampDir, "borealis.json");
2324
+ if (existsSync7(stampPath)) {
1902
2325
  filesSkipped.push(".lo/borealis.json");
1903
2326
  if (!json) {
1904
2327
  logger.warn(".lo/borealis.json already exists. Skipping.");
@@ -1962,12 +2385,12 @@ function assertSpawnSuccess(result, label) {
1962
2385
  }
1963
2386
  function readVisorCliVersion() {
1964
2387
  try {
1965
- const here = dirname5(fileURLToPath2(import.meta.url));
2388
+ const here = dirname6(fileURLToPath2(import.meta.url));
1966
2389
  for (let i = 0; i < 5; i++) {
1967
2390
  const segments = new Array(i).fill("..");
1968
- const candidate = join8(here, ...segments, "package.json");
2391
+ const candidate = join9(here, ...segments, "package.json");
1969
2392
  try {
1970
- const pkg2 = JSON.parse(readFileSync9(candidate, "utf-8"));
2393
+ const pkg2 = JSON.parse(readFileSync10(candidate, "utf-8"));
1971
2394
  if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) {
1972
2395
  return pkg2.version;
1973
2396
  }
@@ -1982,52 +2405,52 @@ function readVisorCliVersion() {
1982
2405
  // src/utils/fs.ts
1983
2406
  import {
1984
2407
  writeFileSync as writeFileSync4,
1985
- readFileSync as readFileSync10,
1986
- existsSync as existsSync7,
2408
+ readFileSync as readFileSync11,
2409
+ existsSync as existsSync8,
1987
2410
  mkdirSync as mkdirSync3
1988
2411
  } from "fs";
1989
- import { dirname as dirname6, join as join9 } from "path";
2412
+ import { dirname as dirname7, join as join10 } from "path";
1990
2413
  function resolveOutputPath(registryPath, type, config, cwd) {
1991
2414
  let relativePath;
1992
2415
  if (type === "registry:block") {
1993
2416
  relativePath = registryPath.replace(/^blocks\//, "");
1994
- return join9(cwd, config.paths.blocks, relativePath);
2417
+ return join10(cwd, config.paths.blocks, relativePath);
1995
2418
  }
1996
2419
  if (type === "registry:ui") {
1997
2420
  if (registryPath.startsWith("components/deck/")) {
1998
2421
  relativePath = registryPath.replace(/^components\/deck\//, "");
1999
- return join9(cwd, config.paths.deckComponents, relativePath);
2422
+ return join10(cwd, config.paths.deckComponents, relativePath);
2000
2423
  }
2001
2424
  if (registryPath.startsWith("components/flutter/")) {
2002
2425
  relativePath = registryPath.replace(/^components\/flutter\//, "");
2003
- return join9(cwd, config.paths.flutterComponents, relativePath);
2426
+ return join10(cwd, config.paths.flutterComponents, relativePath);
2004
2427
  }
2005
2428
  relativePath = registryPath.replace(/^components\/ui\//, "");
2006
- return join9(cwd, config.paths.components, relativePath);
2429
+ return join10(cwd, config.paths.components, relativePath);
2007
2430
  }
2008
2431
  if (type === "registry:hook") {
2009
2432
  relativePath = registryPath.replace(/^hooks\//, "");
2010
- return join9(cwd, config.paths.hooks, relativePath);
2433
+ return join10(cwd, config.paths.hooks, relativePath);
2011
2434
  }
2012
2435
  if (type === "registry:lib") {
2013
2436
  relativePath = registryPath.replace(/^lib\//, "");
2014
- return join9(cwd, config.paths.lib, relativePath);
2437
+ return join10(cwd, config.paths.lib, relativePath);
2015
2438
  }
2016
- return join9(cwd, registryPath);
2439
+ return join10(cwd, registryPath);
2017
2440
  }
2018
2441
  function writeFile(filePath, content) {
2019
- const dir = dirname6(filePath);
2020
- if (!existsSync7(dir)) {
2442
+ const dir = dirname7(filePath);
2443
+ if (!existsSync8(dir)) {
2021
2444
  mkdirSync3(dir, { recursive: true });
2022
2445
  }
2023
2446
  writeFileSync4(filePath, content, "utf-8");
2024
2447
  }
2025
2448
  function readFile(filePath) {
2026
- if (!existsSync7(filePath)) return null;
2027
- return readFileSync10(filePath, "utf-8");
2449
+ if (!existsSync8(filePath)) return null;
2450
+ return readFileSync11(filePath, "utf-8");
2028
2451
  }
2029
2452
  function fileExists(filePath) {
2030
- return existsSync7(filePath);
2453
+ return existsSync8(filePath);
2031
2454
  }
2032
2455
 
2033
2456
  // src/commands/list.ts
@@ -2168,8 +2591,8 @@ function listCommand(cwd, options = {}) {
2168
2591
  }
2169
2592
 
2170
2593
  // src/utils/pubspec.ts
2171
- import { existsSync as existsSync8, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
2172
- import { join as join10 } from "path";
2594
+ import { existsSync as existsSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
2595
+ import { join as join11 } from "path";
2173
2596
  import { parseDocument, YAMLMap } from "yaml";
2174
2597
  function mergePubspec(pubspecText, deps) {
2175
2598
  const doc = parseDocument(pubspecText);
@@ -2191,14 +2614,14 @@ function mergePubspec(pubspecText, deps) {
2191
2614
  return { text: doc.toString(), added, skipped };
2192
2615
  }
2193
2616
  function pubspecPath(cwd) {
2194
- return join10(cwd, "pubspec.yaml");
2617
+ return join11(cwd, "pubspec.yaml");
2195
2618
  }
2196
2619
  function pubspecExists(cwd) {
2197
- return existsSync8(pubspecPath(cwd));
2620
+ return existsSync9(pubspecPath(cwd));
2198
2621
  }
2199
2622
  function isPubPackageInstalled(packageName, cwd) {
2200
2623
  if (!pubspecExists(cwd)) return false;
2201
- const text = readFileSync11(pubspecPath(cwd), "utf-8");
2624
+ const text = readFileSync12(pubspecPath(cwd), "utf-8");
2202
2625
  const doc = parseDocument(text);
2203
2626
  const depsNode = doc.get("dependencies");
2204
2627
  if (!(depsNode instanceof YAMLMap)) return false;
@@ -2209,12 +2632,12 @@ function getUninstalledPubDeps(deps, cwd) {
2209
2632
  }
2210
2633
  function addPubDependencies(deps, cwd) {
2211
2634
  const path2 = pubspecPath(cwd);
2212
- if (!existsSync8(path2)) {
2635
+ if (!existsSync9(path2)) {
2213
2636
  throw new Error(
2214
2637
  `No pubspec.yaml found at ${path2}. Run this command from a Flutter project root.`
2215
2638
  );
2216
2639
  }
2217
- const text = readFileSync11(path2, "utf-8");
2640
+ const text = readFileSync12(path2, "utf-8");
2218
2641
  const result = mergePubspec(text, deps);
2219
2642
  if (result.added.length > 0) {
2220
2643
  writeFileSync5(path2, result.text, "utf-8");
@@ -2224,12 +2647,12 @@ function addPubDependencies(deps, cwd) {
2224
2647
 
2225
2648
  // src/utils/flutter.ts
2226
2649
  import { execFileSync as execFileSync2 } from "child_process";
2227
- import { existsSync as existsSync9, readdirSync as readdirSync3, statSync as statSync4 } from "fs";
2650
+ import { existsSync as existsSync10, readdirSync as readdirSync3, statSync as statSync5 } from "fs";
2228
2651
  import { homedir as homedir2 } from "os";
2229
- import { join as join11 } from "path";
2652
+ import { join as join12 } from "path";
2230
2653
  function isExecutable(path2) {
2231
2654
  try {
2232
- const s = statSync4(path2);
2655
+ const s = statSync5(path2);
2233
2656
  return s.isFile();
2234
2657
  } catch {
2235
2658
  return false;
@@ -2241,20 +2664,20 @@ function fromPath(env) {
2241
2664
  const bin = process.platform === "win32" ? "flutter.bat" : "flutter";
2242
2665
  for (const dir of pathVar.split(sep2)) {
2243
2666
  if (!dir) continue;
2244
- const candidate = join11(dir, bin);
2667
+ const candidate = join12(dir, bin);
2245
2668
  if (isExecutable(candidate)) return candidate;
2246
2669
  }
2247
2670
  return null;
2248
2671
  }
2249
2672
  function fromFvm(home) {
2250
- const fvmDefault = join11(home, "fvm", "default", "bin", "flutter");
2673
+ const fvmDefault = join12(home, "fvm", "default", "bin", "flutter");
2251
2674
  if (isExecutable(fvmDefault)) return fvmDefault;
2252
- const versionsDir = join11(home, "fvm", "versions");
2253
- if (!existsSync9(versionsDir)) return null;
2675
+ const versionsDir = join12(home, "fvm", "versions");
2676
+ if (!existsSync10(versionsDir)) return null;
2254
2677
  let best = null;
2255
2678
  try {
2256
2679
  for (const name of readdirSync3(versionsDir)) {
2257
- const candidate = join11(versionsDir, name, "bin", "flutter");
2680
+ const candidate = join12(versionsDir, name, "bin", "flutter");
2258
2681
  if (!isExecutable(candidate)) continue;
2259
2682
  if (!best || compareSemver(name, best.version) > 0) {
2260
2683
  best = { version: name, path: candidate };
@@ -2280,7 +2703,7 @@ function findFlutterBin(options = {}) {
2280
2703
  const home = options.home ?? homedir2();
2281
2704
  const envRoot = env.FLUTTER_ROOT;
2282
2705
  if (envRoot) {
2283
- const bin = join11(envRoot, "bin", "flutter");
2706
+ const bin = join12(envRoot, "bin", "flutter");
2284
2707
  if (isExecutable(bin)) return bin;
2285
2708
  }
2286
2709
  const fromPathBin = fromPath(env);
@@ -2943,8 +3366,8 @@ function infoCommand(name, cwd, options = {}) {
2943
3366
  }
2944
3367
 
2945
3368
  // 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";
3369
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync8, mkdirSync as mkdirSync6 } from "fs";
3370
+ import { resolve as resolve8, dirname as dirname9, join as join16 } from "path";
2948
3371
  import { generateTheme, generateThemeData as generateThemeData3 } from "@loworbitstudio/visor-theme-engine";
2949
3372
  import {
2950
3373
  nextjsAdapter as nextjsAdapter2,
@@ -2955,8 +3378,8 @@ import {
2955
3378
  } from "@loworbitstudio/visor-theme-engine/adapters";
2956
3379
 
2957
3380
  // src/lib/blessed-manifest.ts
2958
- import { existsSync as existsSync10, readFileSync as readFileSync12 } from "fs";
2959
- import { join as join12 } from "path";
3381
+ import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
3382
+ import { join as join13 } from "path";
2960
3383
  import { z } from "zod";
2961
3384
  var BLESSED_MANIFEST_FILENAME = "blessed-manifest.json";
2962
3385
  var themeApplyTargetSchema = z.discriminatedUnion("kind", [
@@ -2988,8 +3411,8 @@ var blessedManifestSchema = z.object({
2988
3411
  theme_apply_target: themeApplyTargetSchema.optional()
2989
3412
  }).strict();
2990
3413
  function parseBlessedManifest(buildDir) {
2991
- const manifestPath = join12(buildDir, BLESSED_MANIFEST_FILENAME);
2992
- if (!existsSync10(manifestPath)) {
3414
+ const manifestPath = join13(buildDir, BLESSED_MANIFEST_FILENAME);
3415
+ if (!existsSync11(manifestPath)) {
2993
3416
  return {
2994
3417
  ok: false,
2995
3418
  error: `this directory is not a blessed build (missing ${BLESSED_MANIFEST_FILENAME}); see docs/blessed-builds.md`
@@ -2997,7 +3420,7 @@ function parseBlessedManifest(buildDir) {
2997
3420
  }
2998
3421
  let raw;
2999
3422
  try {
3000
- raw = readFileSync12(manifestPath, "utf-8");
3423
+ raw = readFileSync13(manifestPath, "utf-8");
3001
3424
  } catch {
3002
3425
  return { ok: false, error: `could not read ${manifestPath}` };
3003
3426
  }
@@ -3023,31 +3446,31 @@ function parseBlessedManifest(buildDir) {
3023
3446
  }
3024
3447
 
3025
3448
  // 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";
3449
+ import { existsSync as existsSync12, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
3450
+ import { dirname as dirname8, isAbsolute as isAbsolute2, join as join14, resolve as resolve6 } from "path";
3028
3451
  function applyGlobalsCss(input) {
3029
3452
  const outFile = input.path !== void 0 ? resolveInsideBuild(input.buildDir, input.path) : resolveDefaultGlobalsCss(input.buildDir);
3030
- mkdirSync4(dirname7(outFile), { recursive: true });
3453
+ mkdirSync4(dirname8(outFile), { recursive: true });
3031
3454
  writeFileSync6(outFile, input.adapterCss, "utf-8");
3032
3455
  return outFile;
3033
3456
  }
3034
3457
  function resolveDefaultGlobalsCss(buildDir) {
3035
3458
  const candidates = [
3036
- join13(buildDir, "app", "globals.css"),
3037
- join13(buildDir, "src", "app", "globals.css")
3459
+ join14(buildDir, "app", "globals.css"),
3460
+ join14(buildDir, "src", "app", "globals.css")
3038
3461
  ];
3039
- return candidates.find((candidate) => existsSync11(candidate)) ?? candidates[0];
3462
+ return candidates.find((candidate) => existsSync12(candidate)) ?? candidates[0];
3040
3463
  }
3041
3464
  function resolveInsideBuild(buildDir, relativeOrAbsolute) {
3042
- return isAbsolute(relativeOrAbsolute) ? relativeOrAbsolute : resolve5(buildDir, relativeOrAbsolute);
3465
+ return isAbsolute2(relativeOrAbsolute) ? relativeOrAbsolute : resolve6(buildDir, relativeOrAbsolute);
3043
3466
  }
3044
3467
 
3045
3468
  // src/lib/theme-apply-targets/themes-css-dir.ts
3046
3469
  import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
3047
- import { isAbsolute as isAbsolute2, join as join14, resolve as resolve6 } from "path";
3470
+ import { isAbsolute as isAbsolute3, join as join15, resolve as resolve7 } from "path";
3048
3471
  function applyThemesCssDir(input) {
3049
- const dir = isAbsolute2(input.path) ? input.path : resolve6(input.buildDir, input.path);
3050
- const outFile = join14(dir, `${input.themeId}.css`);
3472
+ const dir = isAbsolute3(input.path) ? input.path : resolve7(input.buildDir, input.path);
3473
+ const outFile = join15(dir, `${input.themeId}.css`);
3051
3474
  mkdirSync5(dir, { recursive: true });
3052
3475
  writeFileSync7(outFile, input.adapterCss, "utf-8");
3053
3476
  return outFile;
@@ -3111,10 +3534,10 @@ function defaultOutputPath(adapter, themeName) {
3111
3534
  }
3112
3535
  }
3113
3536
  function themeApplyCommand(file, cwd, options) {
3114
- const filePath = resolve7(cwd, file);
3537
+ const filePath = resolve8(cwd, file);
3115
3538
  let yamlContent;
3116
3539
  try {
3117
- yamlContent = readFileSync13(filePath, "utf-8");
3540
+ yamlContent = readFileSync14(filePath, "utf-8");
3118
3541
  } catch {
3119
3542
  if (options.json) {
3120
3543
  console.log(
@@ -3208,7 +3631,7 @@ function themeApplyCommand(file, cwd, options) {
3208
3631
  else logger.error(message);
3209
3632
  process.exit(1);
3210
3633
  }
3211
- const buildDir = resolve7(cwd, options.targetPath);
3634
+ const buildDir = resolve8(cwd, options.targetPath);
3212
3635
  const manifestResult = parseBlessedManifest(buildDir);
3213
3636
  if (!manifestResult.ok) {
3214
3637
  if (options.json) {
@@ -3251,14 +3674,14 @@ function themeApplyCommand(file, cwd, options) {
3251
3674
  return;
3252
3675
  }
3253
3676
  const outputTarget = options.output ?? defaultOutputPath(options.adapter, themeName);
3254
- const outputPath = resolve7(cwd, outputTarget);
3677
+ const outputPath = resolve8(cwd, outputTarget);
3255
3678
  if (fileMap) {
3256
3679
  try {
3257
3680
  mkdirSync6(outputPath, { recursive: true });
3258
3681
  let totalBytes = 0;
3259
3682
  for (const [relPath, content] of Object.entries(fileMap.files)) {
3260
- const filePath2 = join15(outputPath, relPath);
3261
- mkdirSync6(dirname8(filePath2), { recursive: true });
3683
+ const filePath2 = join16(outputPath, relPath);
3684
+ mkdirSync6(dirname9(filePath2), { recursive: true });
3262
3685
  writeFileSync8(filePath2, content, "utf-8");
3263
3686
  totalBytes += content.length;
3264
3687
  }
@@ -3296,7 +3719,7 @@ function themeApplyCommand(file, cwd, options) {
3296
3719
  if (css === null) {
3297
3720
  process.exit(1);
3298
3721
  }
3299
- const outputDir = dirname8(outputPath);
3722
+ const outputDir = dirname9(outputPath);
3300
3723
  try {
3301
3724
  mkdirSync6(outputDir, { recursive: true });
3302
3725
  writeFileSync8(outputPath, css, "utf-8");
@@ -3340,8 +3763,8 @@ function formatSize(bytes) {
3340
3763
  }
3341
3764
 
3342
3765
  // src/commands/theme-export.ts
3343
- import { readFileSync as readFileSync14 } from "fs";
3344
- import { resolve as resolve8 } from "path";
3766
+ import { readFileSync as readFileSync15 } from "fs";
3767
+ import { resolve as resolve9 } from "path";
3345
3768
  import {
3346
3769
  parseConfig,
3347
3770
  resolveConfig,
@@ -3349,10 +3772,10 @@ import {
3349
3772
  exportTheme
3350
3773
  } from "@loworbitstudio/visor-theme-engine";
3351
3774
  function themeExportCommand(file, cwd, options) {
3352
- const filePath = resolve8(cwd, file ?? ".visor.yaml");
3775
+ const filePath = resolve9(cwd, file ?? ".visor.yaml");
3353
3776
  let yamlContent;
3354
3777
  try {
3355
- yamlContent = readFileSync14(filePath, "utf-8");
3778
+ yamlContent = readFileSync15(filePath, "utf-8");
3356
3779
  } catch {
3357
3780
  if (options.json) {
3358
3781
  console.log(
@@ -3404,16 +3827,16 @@ function themeExportCommand(file, cwd, options) {
3404
3827
  }
3405
3828
 
3406
3829
  // src/commands/theme-validate.ts
3407
- import { readFileSync as readFileSync15 } from "fs";
3408
- import { resolve as resolve9 } from "path";
3830
+ import { readFileSync as readFileSync16 } from "fs";
3831
+ import { resolve as resolve10 } from "path";
3409
3832
  import { parse as parseYaml2 } from "yaml";
3410
3833
  import { validate } from "@loworbitstudio/visor-theme-engine";
3411
3834
  import pc3 from "picocolors";
3412
3835
  function themeValidateCommand(file, cwd, options) {
3413
- const filePath = resolve9(cwd, file);
3836
+ const filePath = resolve10(cwd, file);
3414
3837
  let fileContent;
3415
3838
  try {
3416
- fileContent = readFileSync15(filePath, "utf-8");
3839
+ fileContent = readFileSync16(filePath, "utf-8");
3417
3840
  } catch {
3418
3841
  if (options.json) {
3419
3842
  console.log(
@@ -3502,8 +3925,8 @@ function printIssue(issue) {
3502
3925
 
3503
3926
  // src/commands/theme-verify.ts
3504
3927
  import { spawnSync as _spawnSync } from "child_process";
3505
- import { existsSync as existsSync12 } from "fs";
3506
- import { resolve as resolve10 } from "path";
3928
+ import { existsSync as existsSync13 } from "fs";
3929
+ import { resolve as resolve11 } from "path";
3507
3930
  function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
3508
3931
  const target = options.target ?? "flutter";
3509
3932
  if (target !== "flutter") {
@@ -3525,8 +3948,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
3525
3948
  }
3526
3949
  process.exit(1);
3527
3950
  }
3528
- const dirPath = resolve10(cwd, dir);
3529
- if (!existsSync12(dirPath)) {
3951
+ const dirPath = resolve11(cwd, dir);
3952
+ if (!existsSync13(dirPath)) {
3530
3953
  if (options.json) {
3531
3954
  console.log(
3532
3955
  JSON.stringify({
@@ -3613,8 +4036,8 @@ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
3613
4036
  }
3614
4037
 
3615
4038
  // 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";
4039
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync9, existsSync as existsSync14, readdirSync as readdirSync4, statSync as statSync6 } from "fs";
4040
+ import { resolve as resolve12, join as join17, basename as basename3, extname as extname3, relative } from "path";
3618
4041
  import { stringify as stringifyYaml } from "yaml";
3619
4042
  import {
3620
4043
  extractFromCSS,
@@ -3643,8 +4066,8 @@ var CSS_DIRS = [
3643
4066
  "packages/design-tokens"
3644
4067
  ];
3645
4068
  function themeExtractCommand(cwd, options) {
3646
- const targetDir = resolve11(cwd, options.from ?? ".");
3647
- if (!existsSync13(targetDir)) {
4069
+ const targetDir = resolve12(cwd, options.from ?? ".");
4070
+ if (!existsSync14(targetDir)) {
3648
4071
  if (options.json) {
3649
4072
  console.log(JSON.stringify({ success: false, error: `Directory not found: ${targetDir}` }));
3650
4073
  } else {
@@ -3698,16 +4121,16 @@ function collectCSSFiles(targetDir) {
3698
4121
  const files = [];
3699
4122
  const seen = /* @__PURE__ */ new Set();
3700
4123
  for (const pattern2 of CSS_FILE_PATTERNS) {
3701
- const rootPath = join16(targetDir, pattern2);
4124
+ const rootPath = join17(targetDir, pattern2);
3702
4125
  addFileIfExists(rootPath, files, seen);
3703
4126
  for (const dir of CSS_DIRS) {
3704
- const dirPath = join16(targetDir, dir, pattern2);
4127
+ const dirPath = join17(targetDir, dir, pattern2);
3705
4128
  addFileIfExists(dirPath, files, seen);
3706
4129
  }
3707
4130
  }
3708
4131
  for (const dir of CSS_DIRS) {
3709
- const dirPath = join16(targetDir, dir);
3710
- if (existsSync13(dirPath) && statSync5(dirPath).isDirectory()) {
4132
+ const dirPath = join17(targetDir, dir);
4133
+ if (existsSync14(dirPath) && statSync6(dirPath).isDirectory()) {
3711
4134
  scanDirForCSS(dirPath, files, seen, 2);
3712
4135
  }
3713
4136
  }
@@ -3715,11 +4138,11 @@ function collectCSSFiles(targetDir) {
3715
4138
  return files;
3716
4139
  }
3717
4140
  function addFileIfExists(filePath, files, seen) {
3718
- const resolved = resolve11(filePath);
4141
+ const resolved = resolve12(filePath);
3719
4142
  if (seen.has(resolved)) return;
3720
- if (!existsSync13(resolved)) return;
4143
+ if (!existsSync14(resolved)) return;
3721
4144
  try {
3722
- const content = readFileSync16(resolved, "utf-8");
4145
+ const content = readFileSync17(resolved, "utf-8");
3723
4146
  if (content.includes("--")) {
3724
4147
  files.push({ path: resolved, content });
3725
4148
  seen.add(resolved);
@@ -3728,7 +4151,7 @@ function addFileIfExists(filePath, files, seen) {
3728
4151
  }
3729
4152
  }
3730
4153
  function scanDirForCSS(dir, files, seen, maxDepth) {
3731
- if (!existsSync13(dir)) return;
4154
+ if (!existsSync14(dir)) return;
3732
4155
  const SKIP_DIRS = /* @__PURE__ */ new Set([
3733
4156
  "node_modules",
3734
4157
  ".next",
@@ -3747,10 +4170,10 @@ function scanDirForCSS(dir, files, seen, maxDepth) {
3747
4170
  if (entry.isDirectory()) {
3748
4171
  if (SKIP_DIRS.has(entry.name)) continue;
3749
4172
  if (maxDepth > 0) {
3750
- scanDirForCSS(join16(dir, entry.name), files, seen, maxDepth - 1);
4173
+ scanDirForCSS(join17(dir, entry.name), files, seen, maxDepth - 1);
3751
4174
  }
3752
4175
  } else if (entry.isFile() && extname3(entry.name) === ".css") {
3753
- addFileIfExists(join16(dir, entry.name), files, seen);
4176
+ addFileIfExists(join17(dir, entry.name), files, seen);
3754
4177
  }
3755
4178
  }
3756
4179
  } catch {
@@ -3832,10 +4255,10 @@ function extractVarName(varExpr) {
3832
4255
  function parseNextFontFromLayouts(targetDir) {
3833
4256
  const fontMap = /* @__PURE__ */ new Map();
3834
4257
  for (const relPath of LAYOUT_FILE_PATHS) {
3835
- const fullPath = join16(targetDir, relPath);
3836
- if (!existsSync13(fullPath)) continue;
4258
+ const fullPath = join17(targetDir, relPath);
4259
+ if (!existsSync14(fullPath)) continue;
3837
4260
  try {
3838
- const content = readFileSync16(fullPath, "utf-8");
4261
+ const content = readFileSync17(fullPath, "utf-8");
3839
4262
  parseNextFontDeclarations(content, fontMap);
3840
4263
  } catch {
3841
4264
  }
@@ -3920,10 +4343,10 @@ var MONO_FONT_NAMES = /* @__PURE__ */ new Set([
3920
4343
  "IBM Plex Mono"
3921
4344
  ]);
3922
4345
  function extractFontHints(targetDir) {
3923
- const pkgPath = join16(targetDir, "package.json");
3924
- if (!existsSync13(pkgPath)) return void 0;
4346
+ const pkgPath = join17(targetDir, "package.json");
4347
+ if (!existsSync14(pkgPath)) return void 0;
3925
4348
  try {
3926
- const pkg2 = JSON.parse(readFileSync16(pkgPath, "utf-8"));
4349
+ const pkg2 = JSON.parse(readFileSync17(pkgPath, "utf-8"));
3927
4350
  const allDeps = { ...pkg2.dependencies, ...pkg2.devDependencies };
3928
4351
  const fonts2 = [];
3929
4352
  for (const [dep, _] of Object.entries(allDeps)) {
@@ -3959,10 +4382,10 @@ function extractFontHints(targetDir) {
3959
4382
  }
3960
4383
  }
3961
4384
  function inferThemeName(targetDir) {
3962
- const pkgPath = join16(targetDir, "package.json");
3963
- if (existsSync13(pkgPath)) {
4385
+ const pkgPath = join17(targetDir, "package.json");
4386
+ if (existsSync14(pkgPath)) {
3964
4387
  try {
3965
- const pkg2 = JSON.parse(readFileSync16(pkgPath, "utf-8"));
4388
+ const pkg2 = JSON.parse(readFileSync17(pkgPath, "utf-8"));
3966
4389
  if (pkg2.name) {
3967
4390
  const name = pkg2.name.replace(/^@[\w-]+\//, "");
3968
4391
  return `${name}-theme`;
@@ -3999,7 +4422,7 @@ function outputJSON(result, validationResult) {
3999
4422
  }
4000
4423
  function outputYAML(result, outputPath, cwd, validationResult) {
4001
4424
  const yamlStr = buildAnnotatedYAML(result);
4002
- const outFile = resolve11(cwd, outputPath ?? ".visor.yaml");
4425
+ const outFile = resolve12(cwd, outputPath ?? ".visor.yaml");
4003
4426
  const high = result.tokens.filter((t) => t.confidence === "high").length;
4004
4427
  const med = result.tokens.filter((t) => t.confidence === "medium").length;
4005
4428
  const low = result.tokens.filter((t) => t.confidence === "low").length;
@@ -4085,14 +4508,14 @@ function buildAnnotatedYAML(result) {
4085
4508
  }
4086
4509
 
4087
4510
  // 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";
4511
+ import { readFileSync as readFileSync19, writeFileSync as writeFileSync10, mkdirSync as mkdirSync7, existsSync as existsSync16 } from "fs";
4512
+ import { resolve as resolve14, join as join19 } from "path";
4090
4513
  import { generateThemeData as generateThemeData4 } from "@loworbitstudio/visor-theme-engine";
4091
4514
  import { docsAdapter as docsAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
4092
4515
 
4093
4516
  // 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";
4517
+ import { existsSync as existsSync15, lstatSync, readdirSync as readdirSync5, readFileSync as readFileSync18, readlinkSync, realpathSync, statSync as statSync7 } from "fs";
4518
+ import { resolve as resolve13, dirname as dirname10, join as join18 } from "path";
4096
4519
  import { execFileSync as execFileSync3 } from "child_process";
4097
4520
  function toSlug(name) {
4098
4521
  return name.toLowerCase().replace(/\s+/g, "-");
@@ -4101,12 +4524,12 @@ function toLabel(name) {
4101
4524
  return name.split(/[\s-]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
4102
4525
  }
4103
4526
  function findRepoRoot(startDir) {
4104
- let current = resolve12(startDir);
4527
+ let current = resolve13(startDir);
4105
4528
  while (true) {
4106
- if (existsSync14(join17(current, "packages", "docs"))) {
4529
+ if (existsSync15(join18(current, "packages", "docs"))) {
4107
4530
  return current;
4108
4531
  }
4109
- const parent = dirname9(current);
4532
+ const parent = dirname10(current);
4110
4533
  if (parent === current) break;
4111
4534
  current = parent;
4112
4535
  }
@@ -4120,9 +4543,9 @@ function findMainRepoRoot(startDir) {
4120
4543
  stdio: ["ignore", "pipe", "ignore"]
4121
4544
  }).trim();
4122
4545
  if (commonDir) {
4123
- const absoluteCommonDir = resolve12(startDir, commonDir);
4124
- const candidate = dirname9(absoluteCommonDir);
4125
- if (existsSync14(join17(candidate, "packages", "docs"))) {
4546
+ const absoluteCommonDir = resolve13(startDir, commonDir);
4547
+ const candidate = dirname10(absoluteCommonDir);
4548
+ if (existsSync15(join18(candidate, "packages", "docs"))) {
4126
4549
  return candidate;
4127
4550
  }
4128
4551
  }
@@ -4141,10 +4564,10 @@ var BrokenSymlinkError = class extends Error {
4141
4564
  code = "BROKEN_SYMLINK";
4142
4565
  };
4143
4566
  function assertNoBrokenSymlinks(dir) {
4144
- if (!existsSync14(dir)) return;
4567
+ if (!existsSync15(dir)) return;
4145
4568
  const entries = readdirSync5(dir, { withFileTypes: true });
4146
4569
  for (const entry of entries) {
4147
- const entryPath = join17(dir, entry.name);
4570
+ const entryPath = join18(dir, entry.name);
4148
4571
  let lst;
4149
4572
  try {
4150
4573
  lst = lstatSync(entryPath);
@@ -4154,7 +4577,7 @@ function assertNoBrokenSymlinks(dir) {
4154
4577
  if (lst.isSymbolicLink()) {
4155
4578
  const target = readlinkSync(entryPath);
4156
4579
  try {
4157
- statSync6(entryPath);
4580
+ statSync7(entryPath);
4158
4581
  } catch {
4159
4582
  throw new BrokenSymlinkError(entryPath, target);
4160
4583
  }
@@ -4162,39 +4585,39 @@ function assertNoBrokenSymlinks(dir) {
4162
4585
  }
4163
4586
  }
4164
4587
  function scanParentForPrivateThemes(parentDir) {
4165
- if (!existsSync14(parentDir)) return [];
4588
+ if (!existsSync15(parentDir)) return [];
4166
4589
  const entries = readdirSync5(parentDir, { withFileTypes: true });
4167
4590
  const matches = [];
4168
4591
  for (const entry of entries) {
4169
4592
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
4170
- const candidate = join17(parentDir, entry.name, "visor-themes-private", "themes");
4171
- if (existsSync14(candidate)) {
4593
+ const candidate = join18(parentDir, entry.name, "visor-themes-private", "themes");
4594
+ if (existsSync15(candidate)) {
4172
4595
  matches.push(candidate);
4173
4596
  }
4174
4597
  }
4175
4598
  return matches.sort();
4176
4599
  }
4177
4600
  function scanNestedThemeDir(dir) {
4178
- if (!existsSync14(dir)) return [];
4601
+ if (!existsSync15(dir)) return [];
4179
4602
  assertNoBrokenSymlinks(dir);
4180
4603
  const entries = readdirSync5(dir, { withFileTypes: true });
4181
4604
  const out = [];
4182
4605
  for (const entry of entries) {
4183
4606
  if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
4184
- const themeFile = join17(dir, entry.name, "theme.visor.yaml");
4185
- if (existsSync14(themeFile)) {
4607
+ const themeFile = join18(dir, entry.name, "theme.visor.yaml");
4608
+ if (existsSync15(themeFile)) {
4186
4609
  out.push({ filePath: themeFile, slug: entry.name });
4187
4610
  }
4188
4611
  }
4189
4612
  return out;
4190
4613
  }
4191
4614
  function detectVisorWorkspace(cwd) {
4192
- let current = resolve12(cwd);
4615
+ let current = resolve13(cwd);
4193
4616
  while (true) {
4194
- const pkgPath = join17(current, "package.json");
4195
- if (existsSync14(pkgPath)) {
4617
+ const pkgPath = join18(current, "package.json");
4618
+ if (existsSync15(pkgPath)) {
4196
4619
  try {
4197
- const pkg2 = JSON.parse(readFileSync17(pkgPath, "utf-8"));
4620
+ const pkg2 = JSON.parse(readFileSync18(pkgPath, "utf-8"));
4198
4621
  const workspaces = pkg2.workspaces ?? [];
4199
4622
  const hasCli = workspaces.some((w) => w.includes("packages/cli"));
4200
4623
  const hasEngine = workspaces.some((w) => w.includes("packages/theme-engine"));
@@ -4204,7 +4627,7 @@ function detectVisorWorkspace(cwd) {
4204
4627
  } catch {
4205
4628
  }
4206
4629
  }
4207
- const parent = dirname9(current);
4630
+ const parent = dirname10(current);
4208
4631
  if (parent === current) break;
4209
4632
  current = parent;
4210
4633
  }
@@ -4212,18 +4635,18 @@ function detectVisorWorkspace(cwd) {
4212
4635
  }
4213
4636
  function isLocalVisorBinary(workspaceRoot, scriptPath) {
4214
4637
  if (!scriptPath) return false;
4215
- const expectedPrefix = join17(workspaceRoot, "packages", "cli", "dist");
4638
+ const expectedPrefix = join18(workspaceRoot, "packages", "cli", "dist");
4216
4639
  let resolvedScript;
4217
4640
  let resolvedPrefix;
4218
4641
  try {
4219
4642
  resolvedScript = realpathSync(scriptPath);
4220
4643
  } catch {
4221
- resolvedScript = resolve12(scriptPath);
4644
+ resolvedScript = resolve13(scriptPath);
4222
4645
  }
4223
4646
  try {
4224
4647
  resolvedPrefix = realpathSync(expectedPrefix);
4225
4648
  } catch {
4226
- resolvedPrefix = resolve12(expectedPrefix);
4649
+ resolvedPrefix = resolve13(expectedPrefix);
4227
4650
  }
4228
4651
  return resolvedScript.startsWith(resolvedPrefix + "/") || resolvedScript === resolvedPrefix;
4229
4652
  }
@@ -4313,10 +4736,10 @@ ${indent}${newEntry},
4313
4736
  return { updated, changed: true };
4314
4737
  }
4315
4738
  function themeRegisterCommand(file, cwd, options) {
4316
- const filePath = resolve13(cwd, file);
4739
+ const filePath = resolve14(cwd, file);
4317
4740
  let yamlContent;
4318
4741
  try {
4319
- yamlContent = readFileSync18(filePath, "utf-8");
4742
+ yamlContent = readFileSync19(filePath, "utf-8");
4320
4743
  } catch {
4321
4744
  if (options.json) {
4322
4745
  console.log(JSON.stringify({ success: false, error: `Could not read file: ${filePath}` }));
@@ -4359,11 +4782,11 @@ function themeRegisterCommand(file, cwd, options) {
4359
4782
  process.exit(1);
4360
4783
  return;
4361
4784
  }
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)) {
4785
+ const docsAppDir = join19(repoRoot, "packages", "docs", "app");
4786
+ const cssFilePath = join19(docsAppDir, `${slug2}-theme.css`);
4787
+ const globalsPath = join19(docsAppDir, "globals.css");
4788
+ const themeConfigPath = join19(repoRoot, "packages", "docs", "lib", "theme-config.ts");
4789
+ if (!existsSync16(docsAppDir)) {
4367
4790
  const msg = `Docs app directory not found: ${docsAppDir}`;
4368
4791
  if (options.json) {
4369
4792
  console.log(JSON.stringify({ success: false, error: msg }));
@@ -4376,8 +4799,8 @@ function themeRegisterCommand(file, cwd, options) {
4376
4799
  let globalsContent = "";
4377
4800
  let themeConfigContent = "";
4378
4801
  try {
4379
- globalsContent = readFileSync18(globalsPath, "utf-8");
4380
- themeConfigContent = readFileSync18(themeConfigPath, "utf-8");
4802
+ globalsContent = readFileSync19(globalsPath, "utf-8");
4803
+ themeConfigContent = readFileSync19(themeConfigPath, "utf-8");
4381
4804
  } catch (err) {
4382
4805
  const msg = err instanceof Error ? err.message : "Could not read docs files";
4383
4806
  if (options.json) {
@@ -4388,8 +4811,8 @@ function themeRegisterCommand(file, cwd, options) {
4388
4811
  process.exit(1);
4389
4812
  return;
4390
4813
  }
4391
- const cssExists = existsSync15(cssFilePath);
4392
- const cssChanged = !cssExists || readFileSync18(cssFilePath, "utf-8") !== css;
4814
+ const cssExists = existsSync16(cssFilePath);
4815
+ const cssChanged = !cssExists || readFileSync19(cssFilePath, "utf-8") !== css;
4393
4816
  const { updated: newGlobals, changed: globalsChanged } = insertGlobalsImport(globalsContent, slug2);
4394
4817
  const { updated: newThemeConfig, changed: themeConfigChanged, error: configError } = insertThemeConfig(
4395
4818
  themeConfigContent,
@@ -4473,8 +4896,8 @@ function themeRegisterCommand(file, cwd, options) {
4473
4896
  }
4474
4897
 
4475
4898
  // 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";
4899
+ import { readFileSync as readFileSync20, writeFileSync as writeFileSync11, existsSync as existsSync17, unlinkSync } from "fs";
4900
+ import { join as join20 } from "path";
4478
4901
  function removeGlobalsImport(content, slug2) {
4479
4902
  const importLine = `@import './${slug2}-theme.css';`;
4480
4903
  if (!content.includes(importLine)) {
@@ -4506,11 +4929,11 @@ function themeUnregisterCommand(slug2, cwd, options) {
4506
4929
  process.exit(1);
4507
4930
  return;
4508
4931
  }
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)) {
4932
+ const docsAppDir = join20(repoRoot, "packages", "docs", "app");
4933
+ const cssFilePath = join20(docsAppDir, `${slug2}-theme.css`);
4934
+ const globalsPath = join20(docsAppDir, "globals.css");
4935
+ const themeConfigPath = join20(repoRoot, "packages", "docs", "lib", "theme-config.ts");
4936
+ if (!existsSync17(docsAppDir)) {
4514
4937
  const msg = `Docs app directory not found: ${docsAppDir}`;
4515
4938
  if (options.json) {
4516
4939
  console.log(JSON.stringify({ success: false, error: msg }));
@@ -4523,8 +4946,8 @@ function themeUnregisterCommand(slug2, cwd, options) {
4523
4946
  let globalsContent = "";
4524
4947
  let themeConfigContent = "";
4525
4948
  try {
4526
- globalsContent = readFileSync19(globalsPath, "utf-8");
4527
- themeConfigContent = readFileSync19(themeConfigPath, "utf-8");
4949
+ globalsContent = readFileSync20(globalsPath, "utf-8");
4950
+ themeConfigContent = readFileSync20(themeConfigPath, "utf-8");
4528
4951
  } catch (err) {
4529
4952
  const msg = err instanceof Error ? err.message : "Could not read docs files";
4530
4953
  if (options.json) {
@@ -4535,7 +4958,7 @@ function themeUnregisterCommand(slug2, cwd, options) {
4535
4958
  process.exit(1);
4536
4959
  return;
4537
4960
  }
4538
- const cssExists = existsSync16(cssFilePath);
4961
+ const cssExists = existsSync17(cssFilePath);
4539
4962
  const { updated: newGlobals, changed: globalsChanged } = removeGlobalsImport(globalsContent, slug2);
4540
4963
  const { updated: newThemeConfig, changed: themeConfigChanged } = removeThemeConfigEntry(themeConfigContent, slug2);
4541
4964
  if (!cssExists && !globalsChanged && !themeConfigChanged) {
@@ -4576,15 +4999,15 @@ function themeUnregisterCommand(slug2, cwd, options) {
4576
4999
 
4577
5000
  // src/commands/theme-sync.ts
4578
5001
  import {
4579
- readFileSync as readFileSync20,
5002
+ readFileSync as readFileSync21,
4580
5003
  writeFileSync as writeFileSync12,
4581
5004
  mkdirSync as mkdirSync8,
4582
- existsSync as existsSync17,
5005
+ existsSync as existsSync18,
4583
5006
  readdirSync as readdirSync6,
4584
5007
  unlinkSync as unlinkSync2,
4585
5008
  copyFileSync
4586
5009
  } from "fs";
4587
- import { join as join20, basename as basename4, resolve as resolve14, sep, relative as relative2 } from "path";
5010
+ import { join as join21, basename as basename4, resolve as resolve15, sep, relative as relative2 } from "path";
4588
5011
  import { parse as parseYaml3 } from "yaml";
4589
5012
  import { generateThemeData as generateThemeData5, validateFontCoverage, formatFontCoverageError } from "@loworbitstudio/visor-theme-engine";
4590
5013
  import { docsAdapter as docsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
@@ -4598,9 +5021,9 @@ var CUSTOM_OVERLAY_CSS_PATH = "packages/docs/app/custom-themes.generated.css";
4598
5021
  var CUSTOM_OVERLAY_TS_PATH = "packages/docs/lib/theme-config.custom.generated.ts";
4599
5022
  var CUSTOM_OVERLAY_IMPORT_LINE = "@import './custom-themes.generated.css';";
4600
5023
  function scanThemeDir(dir) {
4601
- if (!existsSync17(dir)) return [];
5024
+ if (!existsSync18(dir)) return [];
4602
5025
  assertNoBrokenSymlinks(dir);
4603
- return readdirSync6(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join20(dir, f));
5026
+ return readdirSync6(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join21(dir, f));
4604
5027
  }
4605
5028
  function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
4606
5029
  const merged = /* @__PURE__ */ new Map();
@@ -4624,20 +5047,20 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
4624
5047
  };
4625
5048
  const envPath = process.env[PRIVATE_THEMES_ENV_VAR];
4626
5049
  if (envPath && envPath.trim() !== "") {
4627
- const resolved = resolve14(envPath);
4628
- if (!existsSync17(resolved)) {
5050
+ const resolved = resolve15(envPath);
5051
+ if (!existsSync18(resolved)) {
4629
5052
  throw new Error(
4630
5053
  `${PRIVATE_THEMES_ENV_VAR} is set to "${envPath}" but the path does not exist. Expected a directory containing {slug}/theme.visor.yaml entries.`
4631
5054
  );
4632
5055
  }
4633
5056
  addNested(resolved, "env");
4634
5057
  }
4635
- const siblingPath = join20(mainRepoRoot, "..", "visor-themes-private", "themes");
4636
- const siblingExists = existsSync17(siblingPath);
5058
+ const siblingPath = join21(mainRepoRoot, "..", "visor-themes-private", "themes");
5059
+ const siblingExists = existsSync18(siblingPath);
4637
5060
  if (siblingExists) {
4638
5061
  addNested(siblingPath, "sibling");
4639
5062
  }
4640
- const parentDir = join20(mainRepoRoot, "..");
5063
+ const parentDir = join21(mainRepoRoot, "..");
4641
5064
  const parentGlobMatches = scanParentForPrivateThemes(parentDir).filter(
4642
5065
  (path2) => !path2.startsWith(mainRepoRoot + sep)
4643
5066
  );
@@ -4658,7 +5081,7 @@ function resolveCustomSources(repoRoot, mainRepoRoot, warn) {
4658
5081
  }
4659
5082
  }
4660
5083
  }
4661
- const legacyDir = join20(repoRoot, "custom-themes");
5084
+ const legacyDir = join21(repoRoot, "custom-themes");
4662
5085
  const legacyFiles = scanThemeDir(legacyDir);
4663
5086
  for (const legacyFile of legacyFiles) {
4664
5087
  const slug2 = basename4(legacyFile).replace(/\.visor\.yaml$/, "");
@@ -4715,14 +5138,14 @@ function reportBrokenSymlink(err, options) {
4715
5138
  }
4716
5139
  }
4717
5140
  function buildEmptySourcesMessage(mainRepoRoot) {
4718
- const expectedSibling = join20(mainRepoRoot, "..", "visor-themes-private");
5141
+ const expectedSibling = join21(mainRepoRoot, "..", "visor-themes-private");
4719
5142
  return [
4720
5143
  "No theme sources discovered. Cannot proceed \u2014 refusing to wipe generated CSS.",
4721
5144
  "",
4722
5145
  "Resolution order checked:",
4723
5146
  ` 1. Env var ${PRIVATE_THEMES_ENV_VAR} (unset or empty)`,
4724
5147
  ` 2. Sibling checkout at ${expectedSibling}/themes/ (not found)`,
4725
- ` 3. One-level-deeper at ${join20(mainRepoRoot, "..")}/<parent>/visor-themes-private/themes/ (not found)`,
5148
+ ` 3. One-level-deeper at ${join21(mainRepoRoot, "..")}/<parent>/visor-themes-private/themes/ (not found)`,
4726
5149
  " 4. Legacy custom-themes/ (no .visor.yaml files)",
4727
5150
  "",
4728
5151
  "To fix, clone the private themes repo as a sibling:",
@@ -4927,14 +5350,14 @@ function themeSyncCommand(cwd, options) {
4927
5350
  return;
4928
5351
  }
4929
5352
  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);
5353
+ const themesDir = join21(repoRoot, "themes");
5354
+ const docsAppDir = join21(repoRoot, "packages", "docs", "app");
5355
+ const docsLibDir = join21(repoRoot, "packages", "docs", "lib");
5356
+ const docsPublicThemesDir = join21(repoRoot, "packages", "docs", "public", "themes");
5357
+ const themeConfigPath = join21(repoRoot, "packages", "docs", "lib", "theme-config.ts");
5358
+ const globalsPath = join21(docsAppDir, "globals.css");
5359
+ const customOverlayCssPath = join21(repoRoot, CUSTOM_OVERLAY_CSS_PATH);
5360
+ const customOverlayTsPath = join21(repoRoot, CUSTOM_OVERLAY_TS_PATH);
4938
5361
  let stockFiles;
4939
5362
  try {
4940
5363
  stockFiles = scanThemeDir(themesDir);
@@ -4991,7 +5414,7 @@ function themeSyncCommand(cwd, options) {
4991
5414
  const processFile = (filePath, isCustom, slugOverride) => {
4992
5415
  let yamlContent;
4993
5416
  try {
4994
- yamlContent = readFileSync20(filePath, "utf-8");
5417
+ yamlContent = readFileSync21(filePath, "utf-8");
4995
5418
  } catch {
4996
5419
  failures.push({ filePath, error: `Could not read: ${filePath}` });
4997
5420
  return;
@@ -5041,8 +5464,8 @@ function themeSyncCommand(cwd, options) {
5041
5464
  let globalsContent;
5042
5465
  let themeConfigContent;
5043
5466
  try {
5044
- globalsContent = readFileSync20(globalsPath, "utf-8");
5045
- themeConfigContent = readFileSync20(themeConfigPath, "utf-8");
5467
+ globalsContent = readFileSync21(globalsPath, "utf-8");
5468
+ themeConfigContent = readFileSync21(themeConfigPath, "utf-8");
5046
5469
  } catch (err) {
5047
5470
  const msg = err instanceof Error ? err.message : "Could not read docs files";
5048
5471
  if (options.json) {
@@ -5057,12 +5480,12 @@ function themeSyncCommand(cwd, options) {
5057
5480
  const newThemeConfig = updateStockThemeConfigBlock(themeConfigContent, stockManifest);
5058
5481
  const newCustomOverlayCss = generateCustomOverlayCss(customManifest);
5059
5482
  const newCustomOverlayTs = generateCustomOverlayTs(customManifest);
5060
- const existingCssFiles = existsSync17(docsAppDir) ? readdirSync6(docsAppDir).filter(
5483
+ const existingCssFiles = existsSync18(docsAppDir) ? readdirSync6(docsAppDir).filter(
5061
5484
  (f) => f.endsWith("-theme.css") && f !== "custom-themes.generated.css"
5062
5485
  ) : [];
5063
5486
  const newCssSet = new Set(allSlugs.map((s) => `${s}-theme.css`));
5064
5487
  const staleCssFiles = existingCssFiles.filter((f) => !newCssSet.has(f));
5065
- const existingPublicYamls = existsSync17(docsPublicThemesDir) ? readdirSync6(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
5488
+ const existingPublicYamls = existsSync18(docsPublicThemesDir) ? readdirSync6(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
5066
5489
  const newPublicYamlSet = new Set(manifest.map((e) => `${e.yamlFilename}.visor.yaml`));
5067
5490
  const stalePublicYamls = existingPublicYamls.filter((f) => !newPublicYamlSet.has(f));
5068
5491
  if (options.dryRun) {
@@ -5106,24 +5529,24 @@ function themeSyncCommand(cwd, options) {
5106
5529
  mkdirSync8(docsLibDir, { recursive: true });
5107
5530
  mkdirSync8(docsPublicThemesDir, { recursive: true });
5108
5531
  for (const entry of manifest) {
5109
- writeFileSync12(join20(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
5532
+ writeFileSync12(join21(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
5110
5533
  }
5111
5534
  for (const stale of staleCssFiles) {
5112
- unlinkSync2(join20(docsAppDir, stale));
5535
+ unlinkSync2(join21(docsAppDir, stale));
5113
5536
  }
5114
5537
  writeFileSync12(customOverlayCssPath, newCustomOverlayCss, "utf-8");
5115
5538
  writeFileSync12(customOverlayTsPath, newCustomOverlayTs, "utf-8");
5116
5539
  writeFileSync12(themeConfigPath, newThemeConfig, "utf-8");
5117
5540
  writeFileSync12(globalsPath, newGlobals, "utf-8");
5118
5541
  for (const srcFile of stockFiles) {
5119
- copyFileSync(srcFile, join20(docsPublicThemesDir, basename4(srcFile)));
5542
+ copyFileSync(srcFile, join21(docsPublicThemesDir, basename4(srcFile)));
5120
5543
  }
5121
5544
  for (const c of customSources) {
5122
5545
  const targetName = c.origin === "legacy" ? basename4(c.filePath) : `${c.slug}.visor.yaml`;
5123
- copyFileSync(c.filePath, join20(docsPublicThemesDir, targetName));
5546
+ copyFileSync(c.filePath, join21(docsPublicThemesDir, targetName));
5124
5547
  }
5125
5548
  for (const stale of stalePublicYamls) {
5126
- unlinkSync2(join20(docsPublicThemesDir, stale));
5549
+ unlinkSync2(join21(docsPublicThemesDir, stale));
5127
5550
  }
5128
5551
  } catch (err) {
5129
5552
  const msg = err instanceof Error ? err.message : "Write failed";
@@ -5176,19 +5599,19 @@ function themeSyncCommand(cwd, options) {
5176
5599
 
5177
5600
  // src/commands/theme-batch-apply-flutter.ts
5178
5601
  import {
5179
- readFileSync as readFileSync21,
5602
+ readFileSync as readFileSync22,
5180
5603
  writeFileSync as writeFileSync13,
5181
5604
  mkdirSync as mkdirSync9,
5182
- existsSync as existsSync18,
5605
+ existsSync as existsSync19,
5183
5606
  readdirSync as readdirSync7,
5184
5607
  rmSync
5185
5608
  } from "fs";
5186
- import { join as join21, basename as basename5, dirname as dirname10 } from "path";
5609
+ import { join as join22, basename as basename5, dirname as dirname11 } from "path";
5187
5610
  import { generateThemeData as generateThemeData6 } from "@loworbitstudio/visor-theme-engine";
5188
5611
  import { flutterAdapter as flutterAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
5189
5612
  function scanThemeDir2(dir) {
5190
- if (!existsSync18(dir)) return [];
5191
- return readdirSync7(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join21(dir, f)).sort();
5613
+ if (!existsSync19(dir)) return [];
5614
+ return readdirSync7(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join22(dir, f)).sort();
5192
5615
  }
5193
5616
  function slugToCamel(slug2) {
5194
5617
  return slug2.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
@@ -5321,9 +5744,9 @@ function themeBatchApplyFlutterCommand(cwd, options) {
5321
5744
  process.exit(1);
5322
5745
  return;
5323
5746
  }
5324
- const themesDir = join21(repoRoot, "themes");
5325
- const customThemesDir = join21(repoRoot, "custom-themes");
5326
- const outputDir = join21(repoRoot, "packages", "visor_themes");
5747
+ const themesDir = join22(repoRoot, "themes");
5748
+ const customThemesDir = join22(repoRoot, "custom-themes");
5749
+ const outputDir = join22(repoRoot, "packages", "visor_themes");
5327
5750
  const stockFiles = scanThemeDir2(themesDir);
5328
5751
  const customFiles = scanThemeDir2(customThemesDir);
5329
5752
  const allFiles = [...stockFiles, ...customFiles];
@@ -5344,7 +5767,7 @@ function themeBatchApplyFlutterCommand(cwd, options) {
5344
5767
  for (const filePath of allFiles) {
5345
5768
  let yamlContent;
5346
5769
  try {
5347
- yamlContent = readFileSync21(filePath, "utf-8");
5770
+ yamlContent = readFileSync22(filePath, "utf-8");
5348
5771
  } catch {
5349
5772
  errors.push(`Could not read: ${filePath}`);
5350
5773
  continue;
@@ -5423,8 +5846,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
5423
5846
  return;
5424
5847
  }
5425
5848
  const slugs = processed.map((p) => p.slug);
5426
- const libSrcDir = join21(outputDir, "lib", "src");
5427
- if (existsSync18(libSrcDir)) {
5849
+ const libSrcDir = join22(outputDir, "lib", "src");
5850
+ if (existsSync19(libSrcDir)) {
5428
5851
  rmSync(libSrcDir, { recursive: true, force: true });
5429
5852
  }
5430
5853
  const packageFiles = {
@@ -5436,16 +5859,16 @@ function themeBatchApplyFlutterCommand(cwd, options) {
5436
5859
  };
5437
5860
  let totalFiles = 0;
5438
5861
  for (const [relPath, content] of Object.entries(packageFiles)) {
5439
- const absPath = join21(outputDir, relPath);
5440
- mkdirSync9(dirname10(absPath), { recursive: true });
5862
+ const absPath = join22(outputDir, relPath);
5863
+ mkdirSync9(dirname11(absPath), { recursive: true });
5441
5864
  writeFileSync13(absPath, content, "utf-8");
5442
5865
  totalFiles++;
5443
5866
  }
5444
5867
  for (const { slug: slug2, tokenFiles } of processed) {
5445
- const themeBaseDir = join21(outputDir, "lib", "src", slug2);
5868
+ const themeBaseDir = join22(outputDir, "lib", "src", slug2);
5446
5869
  for (const [relPath, content] of Object.entries(tokenFiles)) {
5447
- const absPath = join21(themeBaseDir, relPath);
5448
- mkdirSync9(dirname10(absPath), { recursive: true });
5870
+ const absPath = join22(themeBaseDir, relPath);
5871
+ mkdirSync9(dirname11(absPath), { recursive: true });
5449
5872
  writeFileSync13(absPath, content, "utf-8");
5450
5873
  totalFiles++;
5451
5874
  }
@@ -5467,8 +5890,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
5467
5890
  }
5468
5891
 
5469
5892
  // 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";
5893
+ import { existsSync as existsSync20, statSync as statSync8, readdirSync as readdirSync8, readFileSync as readFileSync23 } from "fs";
5894
+ import { resolve as resolve16, basename as basename6, extname as extname4 } from "path";
5472
5895
  import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
5473
5896
  function deriveFamilySlug(filename) {
5474
5897
  const name = basename6(filename, extname4(filename));
@@ -5512,11 +5935,11 @@ function deriveFamilySlug(filename) {
5512
5935
  return parts.join("-").toLowerCase();
5513
5936
  }
5514
5937
  function collectWoff2Files(inputPath) {
5515
- const resolved = resolve15(inputPath);
5516
- if (!existsSync19(resolved)) {
5938
+ const resolved = resolve16(inputPath);
5939
+ if (!existsSync20(resolved)) {
5517
5940
  throw new Error(`Path not found: ${resolved}`);
5518
5941
  }
5519
- const stat = statSync7(resolved);
5942
+ const stat = statSync8(resolved);
5520
5943
  if (stat.isFile()) {
5521
5944
  if (extname4(resolved).toLowerCase() !== ".woff2") {
5522
5945
  throw new Error(
@@ -5526,7 +5949,7 @@ function collectWoff2Files(inputPath) {
5526
5949
  return [resolved];
5527
5950
  }
5528
5951
  if (stat.isDirectory()) {
5529
- const files = readdirSync8(resolved).filter((f) => extname4(f).toLowerCase() === ".woff2").map((f) => resolve15(resolved, f));
5952
+ const files = readdirSync8(resolved).filter((f) => extname4(f).toLowerCase() === ".woff2").map((f) => resolve16(resolved, f));
5530
5953
  if (files.length === 0) {
5531
5954
  throw new Error(
5532
5955
  `No .woff2 files found in directory: ${resolved}`
@@ -5541,8 +5964,8 @@ function collectWoff2Files(inputPath) {
5541
5964
  throw new Error(`Path is neither a file nor a directory: ${resolved}`);
5542
5965
  }
5543
5966
  function getNonWoff2Fonts(inputPath) {
5544
- const resolved = resolve15(inputPath);
5545
- if (!existsSync19(resolved) || !statSync7(resolved).isDirectory()) {
5967
+ const resolved = resolve16(inputPath);
5968
+ if (!existsSync20(resolved) || !statSync8(resolved).isDirectory()) {
5546
5969
  return [];
5547
5970
  }
5548
5971
  return readdirSync8(resolved).filter((f) => {
@@ -5579,7 +6002,7 @@ function createR2Client(config) {
5579
6002
  });
5580
6003
  }
5581
6004
  async function uploadFile(client, bucket, key, filePath) {
5582
- const body = readFileSync22(filePath);
6005
+ const body = readFileSync23(filePath);
5583
6006
  await client.send(
5584
6007
  new PutObjectCommand({
5585
6008
  Bucket: bucket,
@@ -5595,8 +6018,8 @@ async function fontsAddCommand(inputPath, options) {
5595
6018
  const r2Config = getR2Config();
5596
6019
  const files = collectWoff2Files(inputPath);
5597
6020
  const familySlug = options.family ?? deriveFamilySlug(basename6(files[0]));
5598
- const resolved = resolve15(inputPath);
5599
- const nonWoff2 = statSync7(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
6021
+ const resolved = resolve16(inputPath);
6022
+ const nonWoff2 = statSync8(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
5600
6023
  if (!json) {
5601
6024
  logger.heading("Visor Font Upload");
5602
6025
  logger.info(`Organization: ${org}`);
@@ -5620,7 +6043,7 @@ async function fontsAddCommand(inputPath, options) {
5620
6043
  logger.info(`Uploading ${filename}...`);
5621
6044
  }
5622
6045
  await uploadFile(client, bucket, key, filePath);
5623
- const size = statSync7(filePath).size;
6046
+ const size = statSync8(filePath).size;
5624
6047
  results.push({ file: filename, key, size });
5625
6048
  if (!json) {
5626
6049
  logger.success(`Uploaded: ${key} (${formatBytes(size)})`);
@@ -5883,27 +6306,27 @@ function findCssFiles(dir, maxDepth = 3) {
5883
6306
  }
5884
6307
 
5885
6308
  // src/utils/patterns.ts
5886
- import { existsSync as existsSync21, readdirSync as readdirSync10, readFileSync as readFileSync24 } from "fs";
5887
- import { join as join23 } from "path";
6309
+ import { existsSync as existsSync22, readdirSync as readdirSync10, readFileSync as readFileSync25 } from "fs";
6310
+ import { join as join24 } from "path";
5888
6311
  import { parse as parseYAML } from "yaml";
5889
6312
  function loadPatternsFromYaml(repoRoot) {
5890
- const patternsDir = join23(repoRoot, "patterns");
5891
- if (!existsSync21(patternsDir)) return [];
6313
+ const patternsDir = join24(repoRoot, "patterns");
6314
+ if (!existsSync22(patternsDir)) return [];
5892
6315
  const files = readdirSync10(patternsDir).filter(
5893
6316
  (f) => f.endsWith(".visor-pattern.yaml")
5894
6317
  );
5895
6318
  return files.map((file) => {
5896
- const content = readFileSync24(join23(patternsDir, file), "utf-8");
6319
+ const content = readFileSync25(join24(patternsDir, file), "utf-8");
5897
6320
  return parseYAML(content);
5898
6321
  }).filter(Boolean);
5899
6322
  }
5900
6323
  function findRepoRoot2(startDir) {
5901
6324
  let current = startDir;
5902
6325
  while (true) {
5903
- if (existsSync21(join23(current, "patterns"))) {
6326
+ if (existsSync22(join24(current, "patterns"))) {
5904
6327
  return current;
5905
6328
  }
5906
- const parent = join23(current, "..");
6329
+ const parent = join24(current, "..");
5907
6330
  if (parent === current) return null;
5908
6331
  current = parent;
5909
6332
  }
@@ -6282,8 +6705,8 @@ Visor Tokens (${categoryLabel}) \u2014 ${tokens2.length} tokens
6282
6705
  }
6283
6706
 
6284
6707
  // 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";
6708
+ import { readFileSync as readFileSync26, writeFileSync as writeFileSync14, readdirSync as readdirSync11, statSync as statSync9, existsSync as existsSync23 } from "fs";
6709
+ import { resolve as resolve17, join as join25, relative as relative4 } from "path";
6287
6710
  import { parse as parseYaml4 } from "yaml";
6288
6711
  import pc4 from "picocolors";
6289
6712
  var V7_ENTR_SUBSTITUTION_MAP = {
@@ -6306,14 +6729,14 @@ var BUILT_IN_SUBSTITUTION_MAPS = {
6306
6729
  var DEFAULT_THEME_ID = "entr";
6307
6730
  function readMapFromThemeFile(themeId, cwd) {
6308
6731
  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`)
6732
+ join25(cwd, "themes", `${themeId}.visor.yaml`),
6733
+ join25(cwd, "custom-themes", `${themeId}.visor.yaml`),
6734
+ join25(cwd, "packages", "docs", "public", "themes", `${themeId}.visor.yaml`)
6312
6735
  ];
6313
6736
  for (const candidate of candidates) {
6314
- if (existsSync22(candidate)) {
6737
+ if (existsSync23(candidate)) {
6315
6738
  try {
6316
- const raw = readFileSync25(candidate, "utf-8");
6739
+ const raw = readFileSync26(candidate, "utf-8");
6317
6740
  const parsed = parseYaml4(raw);
6318
6741
  if (parsed?.migrate?.["token-substitution"]) {
6319
6742
  return parsed.migrate["token-substitution"];
@@ -6369,7 +6792,7 @@ function collectCssFiles(dirPath) {
6369
6792
  function walk2(current) {
6370
6793
  const entries = readdirSync11(current, { withFileTypes: true });
6371
6794
  for (const entry of entries) {
6372
- const fullPath = join24(current, entry.name);
6795
+ const fullPath = join25(current, entry.name);
6373
6796
  if (entry.isDirectory()) {
6374
6797
  if (["node_modules", ".git", ".next", "dist", "build", ".cache"].includes(entry.name)) {
6375
6798
  continue;
@@ -6388,7 +6811,7 @@ function collectCssFiles(dirPath) {
6388
6811
  }
6389
6812
  function runSubstitutionPass(targetPath, map, themeId) {
6390
6813
  let files;
6391
- const stat = statSync8(targetPath);
6814
+ const stat = statSync9(targetPath);
6392
6815
  if (stat.isFile()) {
6393
6816
  files = [targetPath];
6394
6817
  } else {
@@ -6397,7 +6820,7 @@ function runSubstitutionPass(targetPath, map, themeId) {
6397
6820
  const fileResults = [];
6398
6821
  let totalSubstitutions = 0;
6399
6822
  for (const file of files) {
6400
- const content = readFileSync25(file, "utf-8");
6823
+ const content = readFileSync26(file, "utf-8");
6401
6824
  const { newContent, substitutions } = applySubstitutionsToContent(content, map);
6402
6825
  if (substitutions.length > 0) {
6403
6826
  fileResults.push({ file, substitutions, originalContent: content, newContent });
@@ -6431,9 +6854,9 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
6431
6854
  process.exit(1);
6432
6855
  return;
6433
6856
  }
6434
- const targetPath = resolve16(cwd, targetArg ?? ".");
6857
+ const targetPath = resolve17(cwd, targetArg ?? ".");
6435
6858
  try {
6436
- statSync8(targetPath);
6859
+ statSync9(targetPath);
6437
6860
  } catch {
6438
6861
  if (options.json) {
6439
6862
  console.log(JSON.stringify({
@@ -6520,23 +6943,23 @@ function migrateTokenSubstitutionCommand(targetArg, cwd, options) {
6520
6943
  }
6521
6944
 
6522
6945
  // 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";
6946
+ import { existsSync as existsSync26, mkdirSync as mkdirSync12, readFileSync as readFileSync29, rmSync as rmSync2 } from "fs";
6947
+ import { isAbsolute as isAbsolute5, join as join28, resolve as resolve19, dirname as dirname13 } from "path";
6525
6948
  import { fileURLToPath as fileURLToPath3 } from "url";
6526
6949
  import * as childProcess3 from "child_process";
6527
6950
 
6528
6951
  // 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";
6952
+ import { readFileSync as readFileSync27, existsSync as existsSync24 } from "fs";
6953
+ import { dirname as dirname12, resolve as resolve18, isAbsolute as isAbsolute4 } from "path";
6531
6954
  function parseHandoff(handoffPath) {
6532
- const text = readFileSync26(handoffPath, "utf-8");
6955
+ const text = readFileSync27(handoffPath, "utf-8");
6533
6956
  const lines2 = text.split("\n");
6534
6957
  const warnings = [];
6535
6958
  const pattern2 = extractPatternSlug(lines2, warnings);
6536
6959
  const theme2 = extractMetaField(lines2, "Theme");
6537
6960
  const recipePath = extractRecipePath(text, handoffPath, warnings);
6538
6961
  const primitives = extractPrimitives(text, warnings);
6539
- const { screens, mockShapes } = recipePath && existsSync23(recipePath) ? extractRecipeArtifacts(recipePath, warnings) : { screens: [], mockShapes: [] };
6962
+ const { screens, mockShapes } = recipePath && existsSync24(recipePath) ? extractRecipeArtifacts(recipePath, warnings) : { screens: [], mockShapes: [] };
6540
6963
  return { pattern: pattern2, theme: theme2, recipePath, primitives, screens, mockShapes, warnings };
6541
6964
  }
6542
6965
  function extractPatternSlug(lines2, warnings) {
@@ -6567,8 +6990,8 @@ function extractRecipePath(text, handoffPath, warnings) {
6567
6990
  return void 0;
6568
6991
  }
6569
6992
  const href = m[2].trim();
6570
- if (isAbsolute3(href)) return href;
6571
- return resolve17(dirname11(handoffPath), href);
6993
+ if (isAbsolute4(href)) return href;
6994
+ return resolve18(dirname12(handoffPath), href);
6572
6995
  }
6573
6996
  var STATUS_PATTERNS = [
6574
6997
  { re: /NEW\s+gap/i, status: "gap-new" },
@@ -6615,7 +7038,7 @@ function extractPrimitives(text, warnings) {
6615
7038
  return primitives;
6616
7039
  }
6617
7040
  function extractRecipeArtifacts(recipePath, warnings) {
6618
- const text = readFileSync26(recipePath, "utf-8");
7041
+ const text = readFileSync27(recipePath, "utf-8");
6619
7042
  const screens = extractScreens(text);
6620
7043
  const mockShapes = extractMockFields(text, warnings);
6621
7044
  return { screens, mockShapes };
@@ -6689,8 +7112,8 @@ function tryPort(port) {
6689
7112
  }
6690
7113
 
6691
7114
  // 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";
7115
+ import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync15, existsSync as existsSync25, readdirSync as readdirSync12 } from "fs";
7116
+ import { join as join26 } from "path";
6694
7117
 
6695
7118
  // src/commands/sandbox/templates.ts
6696
7119
  var SANDBOX_PACKAGE_VERSION = "16.2.6";
@@ -7193,7 +7616,7 @@ function writeScaffold(sandboxDir, manifest, port, options = {}) {
7193
7616
  mkdirSync10(sandboxDir, { recursive: true });
7194
7617
  const created = [];
7195
7618
  const writeIfNew = (rel, contents) => {
7196
- const full = join25(sandboxDir, rel);
7619
+ const full = join26(sandboxDir, rel);
7197
7620
  mkdirSync10(dirOf(full), { recursive: true });
7198
7621
  writeFileSync15(full, contents, "utf-8");
7199
7622
  created.push(rel);
@@ -7221,7 +7644,7 @@ function writeScaffold(sandboxDir, manifest, port, options = {}) {
7221
7644
  }
7222
7645
  }
7223
7646
  writeIfNew("playwright.capture.mjs", captureScriptTemplate());
7224
- mkdirSync10(join25(sandboxDir, "captures", "approved"), { recursive: true });
7647
+ mkdirSync10(join26(sandboxDir, "captures", "approved"), { recursive: true });
7225
7648
  return { created };
7226
7649
  }
7227
7650
  function dirOf(p) {
@@ -7255,10 +7678,10 @@ function writeSandboxConfig(sandboxDir, manifest, port, options) {
7255
7678
  stripChromeSelectors: options.prototypeImport.stripChromeSelectors
7256
7679
  } : null
7257
7680
  };
7258
- writeFileSync15(join25(sandboxDir, "sandbox.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
7681
+ writeFileSync15(join26(sandboxDir, "sandbox.json"), JSON.stringify(config, null, 2) + "\n", "utf-8");
7259
7682
  }
7260
7683
  function sandboxIsEmpty(sandboxDir) {
7261
- if (!existsSync24(sandboxDir)) return true;
7684
+ if (!existsSync25(sandboxDir)) return true;
7262
7685
  try {
7263
7686
  return readdirSync12(sandboxDir).filter((f) => !f.startsWith(".")).length === 0;
7264
7687
  } catch {
@@ -7267,8 +7690,8 @@ function sandboxIsEmpty(sandboxDir) {
7267
7690
  }
7268
7691
 
7269
7692
  // 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";
7693
+ import { mkdirSync as mkdirSync11, readdirSync as readdirSync13, readFileSync as readFileSync28, statSync as statSync10, writeFileSync as writeFileSync16 } from "fs";
7694
+ import { join as join27 } from "path";
7272
7695
 
7273
7696
  // src/commands/sandbox/strip-chrome.ts
7274
7697
  var DEFAULT_STRIP_SELECTORS = [
@@ -7452,7 +7875,7 @@ function stripDocumentaryChrome(html, selectors) {
7452
7875
  var SCREEN_FILE_PATTERN = /^screen-(\d+)-([^/.]+)\.html$/i;
7453
7876
  function copyHtmlPrototype(sourceDir, sandboxDir, manifest, options = {}) {
7454
7877
  const warnings = [];
7455
- const destAbs = join26(sandboxDir, "public", "prototype");
7878
+ const destAbs = join27(sandboxDir, "public", "prototype");
7456
7879
  mkdirSync11(destAbs, { recursive: true });
7457
7880
  const stripChromeSelectors = options.stripChromeSelectors ?? [];
7458
7881
  const copiedFiles = copyTreeRelative(sourceDir, destAbs, "", stripChromeSelectors);
@@ -7506,24 +7929,24 @@ function deriveStateCoverageScreen(file, existingNames) {
7506
7929
  }
7507
7930
  function copyTreeRelative(srcDir, destDir, relDir = "", stripChromeSelectors = []) {
7508
7931
  const out = [];
7509
- const entries = readdirSync13(join26(srcDir, relDir));
7932
+ const entries = readdirSync13(join27(srcDir, relDir));
7510
7933
  for (const name of entries) {
7511
7934
  if (name.startsWith(".")) continue;
7512
- const srcPath = join26(srcDir, relDir, name);
7513
- const destPath = join26(destDir, relDir, name);
7514
- const stat = statSync9(srcPath);
7935
+ const srcPath = join27(srcDir, relDir, name);
7936
+ const destPath = join27(destDir, relDir, name);
7937
+ const stat = statSync10(srcPath);
7515
7938
  if (stat.isDirectory()) {
7516
7939
  mkdirSync11(destPath, { recursive: true });
7517
- out.push(...copyTreeRelative(srcDir, destDir, join26(relDir, name), stripChromeSelectors));
7940
+ out.push(...copyTreeRelative(srcDir, destDir, join27(relDir, name), stripChromeSelectors));
7518
7941
  } else if (stat.isFile()) {
7519
7942
  if (stripChromeSelectors.length > 0 && name.toLowerCase().endsWith(".html")) {
7520
- const html = readFileSync27(srcPath, "utf-8");
7943
+ const html = readFileSync28(srcPath, "utf-8");
7521
7944
  const stripped = stripDocumentaryChrome(html, stripChromeSelectors);
7522
7945
  writeFileSync16(destPath, stripped);
7523
7946
  } else {
7524
- writeFileSync16(destPath, readFileSync27(srcPath));
7947
+ writeFileSync16(destPath, readFileSync28(srcPath));
7525
7948
  }
7526
- out.push(join26("public", "prototype", relDir, name).replace(/\\/g, "/"));
7949
+ out.push(join27("public", "prototype", relDir, name).replace(/\\/g, "/"));
7527
7950
  }
7528
7951
  }
7529
7952
  return out;
@@ -7575,8 +7998,8 @@ async function runInit(name, cwd, options) {
7575
7998
  if (!name || !/^[a-z0-9][a-z0-9-_]*$/i.test(name)) {
7576
7999
  throw new Error(`Invalid sandbox name '${name}'. Use letters, digits, '-' or '_'.`);
7577
8000
  }
7578
- const handoffPath = isAbsolute4(options.handoff) ? options.handoff : resolve18(cwd, options.handoff);
7579
- if (!existsSync25(handoffPath)) {
8001
+ const handoffPath = isAbsolute5(options.handoff) ? options.handoff : resolve19(cwd, options.handoff);
8002
+ if (!existsSync26(handoffPath)) {
7580
8003
  throw new Error(`Handoff manifest not found: ${handoffPath}`);
7581
8004
  }
7582
8005
  const manifest = parseHandoff(handoffPath);
@@ -7585,7 +8008,7 @@ async function runInit(name, cwd, options) {
7585
8008
  `Handoff has no primitives \u2014 refusing to scaffold an empty sandbox. Check the manifest at ${handoffPath}`
7586
8009
  );
7587
8010
  }
7588
- const sandboxDir = join27(cwd, ".lo", "sandbox", name);
8011
+ const sandboxDir = join28(cwd, ".lo", "sandbox", name);
7589
8012
  if (!sandboxIsEmpty(sandboxDir)) {
7590
8013
  if (!options.overwrite) {
7591
8014
  throw new Error(
@@ -7598,8 +8021,8 @@ async function runInit(name, cwd, options) {
7598
8021
  const port = await findOpenPort();
7599
8022
  let prototypeImport;
7600
8023
  if (options.fromHtmlPrototype) {
7601
- const prototypeDir = isAbsolute4(options.fromHtmlPrototype) ? options.fromHtmlPrototype : resolve18(cwd, options.fromHtmlPrototype);
7602
- if (!existsSync25(prototypeDir)) {
8024
+ const prototypeDir = isAbsolute5(options.fromHtmlPrototype) ? options.fromHtmlPrototype : resolve19(cwd, options.fromHtmlPrototype);
8025
+ if (!existsSync26(prototypeDir)) {
7603
8026
  throw new Error(`HTML prototype directory not found: ${prototypeDir}`);
7604
8027
  }
7605
8028
  const stripChromeSelectors = resolveStripSelectors(
@@ -7658,22 +8081,22 @@ async function runInit(name, cwd, options) {
7658
8081
  function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile) {
7659
8082
  const candidates = [];
7660
8083
  if (themeFile) {
7661
- candidates.push(isAbsolute4(themeFile) ? themeFile : resolve18(cwd, themeFile));
8084
+ candidates.push(isAbsolute5(themeFile) ? themeFile : resolve19(cwd, themeFile));
7662
8085
  }
7663
- candidates.push(isAbsolute4(theme2) ? theme2 : resolve18(cwd, theme2));
8086
+ candidates.push(isAbsolute5(theme2) ? theme2 : resolve19(cwd, theme2));
7664
8087
  const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
7665
8088
  if (privateRoot && privateRoot.length > 0) {
7666
- candidates.push(join27(privateRoot, "themes", theme2, "theme.visor.yaml"));
8089
+ candidates.push(join28(privateRoot, "themes", theme2, "theme.visor.yaml"));
7667
8090
  }
7668
8091
  candidates.push(
7669
- join27(cwd, "themes", `${theme2}.visor.yaml`),
7670
- join27(cwd, "custom-themes", `${theme2}.visor.yaml`)
8092
+ join28(cwd, "themes", `${theme2}.visor.yaml`),
8093
+ join28(cwd, "custom-themes", `${theme2}.visor.yaml`)
7671
8094
  );
7672
- const yamlPath = candidates.find((p) => existsSync25(p));
8095
+ const yamlPath = candidates.find((p) => existsSync26(p));
7673
8096
  if (!yamlPath) {
7674
8097
  const searched = candidates.join(", ");
7675
8098
  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.`
8099
+ `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
8100
  );
7678
8101
  if (!json) {
7679
8102
  logger.warn(`Theme '${theme2}' not found \u2014 leaving placeholder globals.css.`);
@@ -7681,12 +8104,12 @@ function applyThemeIfPossible(sandboxDir, manifest, theme2, cwd, json, themeFile
7681
8104
  `Re-run with --theme-file <path>, or set VISOR_THEMES_PRIVATE_PATH=<dir-containing-themes/${theme2}/theme.visor.yaml>.`
7682
8105
  );
7683
8106
  logger.item(
7684
- `Or apply manually: npx visor theme apply <path> --adapter nextjs -o ${join27(sandboxDir, "app", "globals.css")}`
8107
+ `Or apply manually: npx visor theme apply <path> --adapter nextjs -o ${join28(sandboxDir, "app", "globals.css")}`
7685
8108
  );
7686
8109
  }
7687
8110
  return;
7688
8111
  }
7689
- const globalsOut = join27(sandboxDir, "app", "globals.css");
8112
+ const globalsOut = join28(sandboxDir, "app", "globals.css");
7690
8113
  try {
7691
8114
  themeApplyCommand(yamlPath, sandboxDir, {
7692
8115
  output: globalsOut,
@@ -7739,12 +8162,12 @@ function loadKnownPrimitives() {
7739
8162
  }
7740
8163
  function readCliVersion() {
7741
8164
  try {
7742
- const here = dirname12(fileURLToPath3(import.meta.url));
8165
+ const here = dirname13(fileURLToPath3(import.meta.url));
7743
8166
  for (let i = 0; i < 6; i++) {
7744
8167
  const segments = new Array(i).fill("..");
7745
- const candidate = join27(here, ...segments, "package.json");
8168
+ const candidate = join28(here, ...segments, "package.json");
7746
8169
  try {
7747
- const pkg2 = JSON.parse(readFileSync28(candidate, "utf-8"));
8170
+ const pkg2 = JSON.parse(readFileSync29(candidate, "utf-8"));
7748
8171
  if (pkg2.name === "@loworbitstudio/visor" && pkg2.version) return pkg2.version;
7749
8172
  } catch {
7750
8173
  }
@@ -7755,14 +8178,14 @@ function readCliVersion() {
7755
8178
  }
7756
8179
 
7757
8180
  // src/commands/sandbox/dev.ts
7758
- import { existsSync as existsSync26, readFileSync as readFileSync29 } from "fs";
7759
- import { join as join28 } from "path";
8181
+ import { existsSync as existsSync27, readFileSync as readFileSync30 } from "fs";
8182
+ import { join as join29 } from "path";
7760
8183
  import * as childProcess4 from "child_process";
7761
8184
  function sandboxDevCommand(cwd, options) {
7762
8185
  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)) {
8186
+ const sandboxDir = join29(cwd, ".lo", "sandbox", options.name);
8187
+ const configPath = join29(sandboxDir, "sandbox.json");
8188
+ if (!existsSync27(configPath)) {
7766
8189
  fail(
7767
8190
  json,
7768
8191
  `Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init ${options.name} --handoff ... --theme ...' first.`
@@ -7771,7 +8194,7 @@ function sandboxDevCommand(cwd, options) {
7771
8194
  }
7772
8195
  let config;
7773
8196
  try {
7774
- config = JSON.parse(readFileSync29(configPath, "utf-8"));
8197
+ config = JSON.parse(readFileSync30(configPath, "utf-8"));
7775
8198
  } catch (err) {
7776
8199
  fail(json, `Invalid sandbox.json at ${configPath}: ${err.message}`);
7777
8200
  return;
@@ -7829,20 +8252,20 @@ function fail(json, message) {
7829
8252
  // src/commands/sandbox/approve.ts
7830
8253
  import {
7831
8254
  copyFileSync as copyFileSync2,
7832
- existsSync as existsSync27,
8255
+ existsSync as existsSync28,
7833
8256
  mkdirSync as mkdirSync13,
7834
- readFileSync as readFileSync30,
8257
+ readFileSync as readFileSync31,
7835
8258
  readdirSync as readdirSync14,
7836
8259
  rmSync as rmSync3,
7837
8260
  writeFileSync as writeFileSync17
7838
8261
  } from "fs";
7839
- import { basename as basename7, join as join29 } from "path";
8262
+ import { basename as basename7, join as join30 } from "path";
7840
8263
  import * as childProcess5 from "child_process";
7841
8264
  function sandboxApproveCommand(cwd, options) {
7842
8265
  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)) {
8266
+ const sandboxDir = join30(cwd, ".lo", "sandbox", options.name);
8267
+ const configPath = join30(sandboxDir, "sandbox.json");
8268
+ if (!existsSync28(configPath)) {
7846
8269
  fail2(
7847
8270
  json,
7848
8271
  `Sandbox '${options.name}' not found at ${sandboxDir}. Run 'visor sandbox init' first.`
@@ -7856,8 +8279,8 @@ function sandboxApproveCommand(cwd, options) {
7856
8279
  runCapture(sandboxDir, configPath, options.name, json);
7857
8280
  }
7858
8281
  function runCapture(sandboxDir, configPath, sandboxName, json) {
7859
- const config = JSON.parse(readFileSync30(configPath, "utf-8"));
7860
- const captureScriptPath = join29(sandboxDir, "playwright.capture.mjs");
8282
+ const config = JSON.parse(readFileSync31(configPath, "utf-8"));
8283
+ const captureScriptPath = join30(sandboxDir, "playwright.capture.mjs");
7861
8284
  writeFileSync17(captureScriptPath, captureScriptTemplate(), "utf-8");
7862
8285
  ensurePlaywrightInstalled(sandboxDir, json);
7863
8286
  const result = childProcess5.spawnSync(
@@ -7879,9 +8302,9 @@ function runCapture(sandboxDir, configPath, sandboxName, json) {
7879
8302
  ${result.stderr}`);
7880
8303
  return;
7881
8304
  }
7882
- const pendingDir = join29(sandboxDir, "captures", "pending");
7883
- const diffsDir = join29(sandboxDir, "captures", "diffs");
7884
- const approvedDir = join29(sandboxDir, "captures", "approved");
8305
+ const pendingDir = join30(sandboxDir, "captures", "pending");
8306
+ const diffsDir = join30(sandboxDir, "captures", "diffs");
8307
+ const approvedDir = join30(sandboxDir, "captures", "approved");
7885
8308
  const pendingFiles = safeListPngs(pendingDir);
7886
8309
  const diffFiles = safeListPngs(diffsDir);
7887
8310
  const hasBaseline = safeListPngs(approvedDir).length > 0;
@@ -7918,9 +8341,9 @@ ${result.stderr}`);
7918
8341
  }
7919
8342
  }
7920
8343
  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");
8344
+ const pendingDir = join30(sandboxDir, "captures", "pending");
8345
+ const approvedDir = join30(sandboxDir, "captures", "approved");
8346
+ const diffsDir = join30(sandboxDir, "captures", "diffs");
7924
8347
  const pendingFiles = safeListPngs(pendingDir);
7925
8348
  if (pendingFiles.length === 0) {
7926
8349
  fail2(
@@ -7932,7 +8355,7 @@ function runPromotion(sandboxDir, sandboxName, json) {
7932
8355
  mkdirSync13(approvedDir, { recursive: true });
7933
8356
  const promoted = [];
7934
8357
  for (const src of pendingFiles) {
7935
- const dest = join29(approvedDir, basename7(src));
8358
+ const dest = join30(approvedDir, basename7(src));
7936
8359
  copyFileSync2(src, dest);
7937
8360
  promoted.push(dest);
7938
8361
  }
@@ -7952,8 +8375,8 @@ function runPromotion(sandboxDir, sandboxName, json) {
7952
8375
  }
7953
8376
  }
7954
8377
  function ensurePlaywrightInstalled(sandboxDir, json) {
7955
- const markerPath = join29(sandboxDir, ".playwright-installed");
7956
- if (existsSync27(markerPath)) return;
8378
+ const markerPath = join30(sandboxDir, ".playwright-installed");
8379
+ if (existsSync28(markerPath)) return;
7957
8380
  if (!json) logger.info("Installing Playwright Chromium (one-time)...");
7958
8381
  const result = childProcess5.spawnSync(
7959
8382
  "npx",
@@ -7972,8 +8395,8 @@ function ensurePlaywrightInstalled(sandboxDir, json) {
7972
8395
  writeFileSync17(markerPath, (/* @__PURE__ */ new Date()).toISOString(), "utf-8");
7973
8396
  }
7974
8397
  function safeListPngs(dir) {
7975
- if (!existsSync27(dir)) return [];
7976
- return readdirSync14(dir).filter((f) => f.endsWith(".png")).map((f) => join29(dir, f));
8398
+ if (!existsSync28(dir)) return [];
8399
+ return readdirSync14(dir).filter((f) => f.endsWith(".png")).map((f) => join30(dir, f));
7977
8400
  }
7978
8401
  function tryParseJson(s) {
7979
8402
  try {
@@ -7994,13 +8417,13 @@ function fail2(json, message) {
7994
8417
  // src/commands/spawn.ts
7995
8418
  import {
7996
8419
  cpSync,
7997
- existsSync as existsSync29,
7998
- readFileSync as readFileSync31,
8420
+ existsSync as existsSync30,
8421
+ readFileSync as readFileSync32,
7999
8422
  readdirSync as readdirSync16,
8000
8423
  rmSync as rmSync4,
8001
- statSync as statSync11
8424
+ statSync as statSync12
8002
8425
  } from "fs";
8003
- import { basename as basename8, dirname as dirname13, isAbsolute as isAbsolute5, join as join31, resolve as resolve19 } from "path";
8426
+ import { basename as basename8, dirname as dirname14, isAbsolute as isAbsolute6, join as join32, resolve as resolve20 } from "path";
8004
8427
  import * as childProcess6 from "child_process";
8005
8428
  import { parse as parseYaml5 } from "yaml";
8006
8429
  import { generateThemeData as generateThemeData7 } from "@loworbitstudio/visor-theme-engine";
@@ -8008,8 +8431,8 @@ import { nextjsAdapter as nextjsAdapter3 } from "@loworbitstudio/visor-theme-eng
8008
8431
  import { validate as validate3 } from "@loworbitstudio/visor-theme-engine";
8009
8432
 
8010
8433
  // 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";
8434
+ import { existsSync as existsSync29, readdirSync as readdirSync15, statSync as statSync11 } from "fs";
8435
+ import { join as join31 } from "path";
8013
8436
  var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
8014
8437
  "node_modules",
8015
8438
  ".next",
@@ -8020,13 +8443,13 @@ var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
8020
8443
  ".cache"
8021
8444
  ]);
8022
8445
  var REFERENCE_BUILD_DIRNAME = "reference-build";
8023
- var APPROVED_CAPTURES_SUBPATH = join30("captures", "approved");
8446
+ var APPROVED_CAPTURES_SUBPATH = join31("captures", "approved");
8024
8447
  var MISSING_MANIFEST_REASON = `missing ${BLESSED_MANIFEST_FILENAME}`;
8025
8448
  function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
8026
8449
  const builds = [];
8027
8450
  const incomplete = [];
8028
8451
  const errors = [];
8029
- if (!existsSync28(blessedDir)) {
8452
+ if (!existsSync29(blessedDir)) {
8030
8453
  return { builds, incomplete, errors };
8031
8454
  }
8032
8455
  walk2(blessedDir, 0);
@@ -8065,7 +8488,7 @@ function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
8065
8488
  for (const entry of entries) {
8066
8489
  if (!entry.isDirectory()) continue;
8067
8490
  if (WALK_EXCLUDE_DIRS.has(entry.name)) continue;
8068
- walk2(join30(dir, entry.name), depth + 1);
8491
+ walk2(join31(dir, entry.name), depth + 1);
8069
8492
  }
8070
8493
  }
8071
8494
  }
@@ -8074,13 +8497,13 @@ function isNearMissRoot(dir, entries) {
8074
8497
  (entry) => entry.isDirectory() && entry.name === REFERENCE_BUILD_DIRNAME
8075
8498
  );
8076
8499
  if (!hasReferenceBuild) return false;
8077
- const approved = join30(dir, APPROVED_CAPTURES_SUBPATH);
8500
+ const approved = join31(dir, APPROVED_CAPTURES_SUBPATH);
8078
8501
  try {
8079
- if (!statSync10(approved).isDirectory()) return false;
8502
+ if (!statSync11(approved).isDirectory()) return false;
8080
8503
  } catch {
8081
8504
  return false;
8082
8505
  }
8083
- return !existsSync28(join30(dir, REFERENCE_BUILD_DIRNAME, BLESSED_MANIFEST_FILENAME));
8506
+ return !existsSync29(join31(dir, REFERENCE_BUILD_DIRNAME, BLESSED_MANIFEST_FILENAME));
8084
8507
  }
8085
8508
  function resolveBlessedBuild(blessedDir, shape, pattern2) {
8086
8509
  const { builds } = discoverBlessedBuilds(blessedDir);
@@ -8094,11 +8517,11 @@ function resolveBlessedBuild(blessedDir, shape, pattern2) {
8094
8517
  var BLESSED_ROOT_DIRNAME = "design-prototypes";
8095
8518
  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
8519
  function discoverBlessedRoot(cwd) {
8097
- let dir = resolve19(cwd);
8520
+ let dir = resolve20(cwd);
8098
8521
  for (; ; ) {
8099
- const candidate = join31(dir, BLESSED_ROOT_DIRNAME);
8100
- if (existsSync29(candidate) && statSync11(candidate).isDirectory()) return candidate;
8101
- const parent = dirname13(dir);
8522
+ const candidate = join32(dir, BLESSED_ROOT_DIRNAME);
8523
+ if (existsSync30(candidate) && statSync12(candidate).isDirectory()) return candidate;
8524
+ const parent = dirname14(dir);
8102
8525
  if (parent === dir) return void 0;
8103
8526
  dir = parent;
8104
8527
  }
@@ -8133,25 +8556,25 @@ function parseBlessedIdentifier(from) {
8133
8556
  function resolveBlessedDir(cwd, options) {
8134
8557
  const explicit = options.blessedDir ?? (process.env.VISOR_BLESSED_DIR && process.env.VISOR_BLESSED_DIR.length > 0 ? process.env.VISOR_BLESSED_DIR : void 0);
8135
8558
  if (explicit !== void 0) {
8136
- return isAbsolute5(explicit) ? explicit : resolve19(cwd, explicit);
8559
+ return isAbsolute6(explicit) ? explicit : resolve20(cwd, explicit);
8137
8560
  }
8138
8561
  return discoverBlessedRoot(cwd);
8139
8562
  }
8140
8563
  function resolveThemeFile(theme2, cwd, themeFile) {
8141
8564
  const candidates = [];
8142
8565
  if (themeFile) {
8143
- candidates.push(isAbsolute5(themeFile) ? themeFile : resolve19(cwd, themeFile));
8566
+ candidates.push(isAbsolute6(themeFile) ? themeFile : resolve20(cwd, themeFile));
8144
8567
  }
8145
- candidates.push(isAbsolute5(theme2) ? theme2 : resolve19(cwd, theme2));
8568
+ candidates.push(isAbsolute6(theme2) ? theme2 : resolve20(cwd, theme2));
8146
8569
  const privateRoot = process.env.VISOR_THEMES_PRIVATE_PATH;
8147
8570
  if (privateRoot && privateRoot.length > 0) {
8148
- candidates.push(join31(privateRoot, "themes", theme2, "theme.visor.yaml"));
8571
+ candidates.push(join32(privateRoot, "themes", theme2, "theme.visor.yaml"));
8149
8572
  }
8150
8573
  candidates.push(
8151
- join31(cwd, "themes", `${theme2}.visor.yaml`),
8152
- join31(cwd, "custom-themes", `${theme2}.visor.yaml`)
8574
+ join32(cwd, "themes", `${theme2}.visor.yaml`),
8575
+ join32(cwd, "custom-themes", `${theme2}.visor.yaml`)
8153
8576
  );
8154
- const found = candidates.find((candidate) => existsSync29(candidate));
8577
+ const found = candidates.find((candidate) => existsSync30(candidate));
8155
8578
  if (!found) {
8156
8579
  throw new Error(
8157
8580
  `Theme '${theme2}' not found (searched: ${candidates.join(", ")}). Pass --theme-file <path>, use a direct path, or set VISOR_THEMES_PRIVATE_PATH.`
@@ -8160,7 +8583,7 @@ function resolveThemeFile(theme2, cwd, themeFile) {
8160
8583
  return { path: found, searched: candidates };
8161
8584
  }
8162
8585
  function applyThemeToFork(themeFile, outputDir, manifest) {
8163
- const yaml = readFileSync31(themeFile, "utf-8");
8586
+ const yaml = readFileSync32(themeFile, "utf-8");
8164
8587
  const data = generateThemeData7(yaml);
8165
8588
  const themeId = data.config.name;
8166
8589
  if (!themeId || themeId.length === 0) {
@@ -8207,8 +8630,8 @@ Available builds:
8207
8630
  ${list}`
8208
8631
  );
8209
8632
  }
8210
- const outputDir = isAbsolute5(options.output) ? options.output : resolve19(cwd, options.output);
8211
- if (existsSync29(outputDir) && readdirSync16(outputDir).length > 0) {
8633
+ const outputDir = isAbsolute6(options.output) ? options.output : resolve20(cwd, options.output);
8634
+ if (existsSync30(outputDir) && readdirSync16(outputDir).length > 0) {
8212
8635
  throw new Error(`Output directory already exists and is not empty: ${outputDir}`);
8213
8636
  }
8214
8637
  const { path: themeFile } = resolveThemeFile(options.theme, cwd, options.themeFile);
@@ -8221,7 +8644,7 @@ ${list}`
8221
8644
  try {
8222
8645
  applyThemeToFork(themeFile, outputDir, build.manifest);
8223
8646
  if (options.validate) {
8224
- const parsed = parseYaml5(readFileSync31(themeFile, "utf-8"));
8647
+ const parsed = parseYaml5(readFileSync32(themeFile, "utf-8"));
8225
8648
  const result = validate3(parsed);
8226
8649
  if (!result.valid) {
8227
8650
  const messages = result.errors.map((e) => e.message).join("; ");
@@ -8333,8 +8756,8 @@ function spawnCommand(cwd, options) {
8333
8756
  }
8334
8757
 
8335
8758
  // 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";
8759
+ import { existsSync as existsSync31, mkdirSync as mkdirSync14, readFileSync as readFileSync33 } from "fs";
8760
+ import { dirname as dirname15, isAbsolute as isAbsolute7, resolve as resolve21 } from "path";
8338
8761
  var FIXTURES = {
8339
8762
  "stat-card": {
8340
8763
  default: {
@@ -8486,28 +8909,28 @@ function pascalCase(kebab) {
8486
8909
  }
8487
8910
  function firstExisting(candidates) {
8488
8911
  for (const c of candidates) {
8489
- if (existsSync30(c)) return c;
8912
+ if (existsSync31(c)) return c;
8490
8913
  }
8491
8914
  return null;
8492
8915
  }
8493
8916
  function resolveComponentFile(cwd, name) {
8494
8917
  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")
8918
+ resolve21(cwd, "components", "ui", name, `${name}.tsx`),
8919
+ resolve21(cwd, "components", "devtools", name, `${name}.tsx`),
8920
+ resolve21(cwd, "components", "ui", name, "index.tsx")
8498
8921
  ]);
8499
8922
  }
8500
8923
  function resolveThemeCssFile(cwd, slug2) {
8501
8924
  return firstExisting([
8502
- resolve20(cwd, "packages", "docs", "app", `${slug2}-theme.css`),
8503
- resolve20(cwd, "app", `${slug2}-theme.css`),
8504
- resolve20(cwd, `${slug2}-theme.css`)
8925
+ resolve21(cwd, "packages", "docs", "app", `${slug2}-theme.css`),
8926
+ resolve21(cwd, "app", `${slug2}-theme.css`),
8927
+ resolve21(cwd, `${slug2}-theme.css`)
8505
8928
  ]);
8506
8929
  }
8507
8930
  function resolveTokensCssFile(cwd) {
8508
8931
  return firstExisting([
8509
- resolve20(cwd, "packages", "tokens", "dist", "tokens.css"),
8510
- resolve20(cwd, "node_modules", "@loworbitstudio", "visor-core", "dist", "tokens.css")
8932
+ resolve21(cwd, "packages", "tokens", "dist", "tokens.css"),
8933
+ resolve21(cwd, "node_modules", "@loworbitstudio", "visor-core", "dist", "tokens.css")
8511
8934
  ]);
8512
8935
  }
8513
8936
  async function loadOptional(moduleName) {
@@ -8686,8 +9109,8 @@ async function renderCommand(component, cwd, options) {
8686
9109
  }
8687
9110
  const themeClass = `${options.theme}-theme`;
8688
9111
  const html = buildHtml({
8689
- tokensCss: readFileSync32(tokensCssFile, "utf-8"),
8690
- themeCss: readFileSync32(themeCssFile, "utf-8"),
9112
+ tokensCss: readFileSync33(tokensCssFile, "utf-8"),
9113
+ themeCss: readFileSync33(themeCssFile, "utf-8"),
8691
9114
  componentCss,
8692
9115
  bundleJs,
8693
9116
  themeClass,
@@ -8696,7 +9119,7 @@ async function renderCommand(component, cwd, options) {
8696
9119
  const width = Number(options.width ?? 720);
8697
9120
  const height = Number(options.height ?? 640);
8698
9121
  const outPath = resolveOutPath(cwd, options.out, component, options.theme, mode, state);
8699
- mkdirSync14(dirname14(outPath), { recursive: true });
9122
+ mkdirSync14(dirname15(outPath), { recursive: true });
8700
9123
  const browser = await playwright.chromium.launch();
8701
9124
  let probe;
8702
9125
  try {
@@ -8752,7 +9175,7 @@ async function renderCommand(component, cwd, options) {
8752
9175
  } finally {
8753
9176
  await browser.close();
8754
9177
  }
8755
- const fileSize = existsSync30(outPath) ? statBytes(outPath) : 0;
9178
+ const fileSize = existsSync31(outPath) ? statBytes(outPath) : 0;
8756
9179
  if (json) {
8757
9180
  console.log(
8758
9181
  JSON.stringify(
@@ -8788,14 +9211,14 @@ async function renderCommand(component, cwd, options) {
8788
9211
  }
8789
9212
  function resolveOutPath(cwd, out, component, theme2, mode, state) {
8790
9213
  if (out) {
8791
- return isAbsolute6(out) ? out : resolve20(cwd, out);
9214
+ return isAbsolute7(out) ? out : resolve21(cwd, out);
8792
9215
  }
8793
9216
  const suffix = state === "default" ? "" : `__${state}`;
8794
- return resolve20(cwd, ".visor", "renders", `${component}__${theme2}__${mode}${suffix}.png`);
9217
+ return resolve21(cwd, ".visor", "renders", `${component}__${theme2}__${mode}${suffix}.png`);
8795
9218
  }
8796
9219
  function statBytes(path2) {
8797
9220
  try {
8798
- return readFileSync32(path2).length;
9221
+ return readFileSync33(path2).length;
8799
9222
  } catch {
8800
9223
  return 0;
8801
9224
  }
@@ -8808,9 +9231,9 @@ function formatBytes2(bytes) {
8808
9231
  }
8809
9232
 
8810
9233
  // src/index.ts
8811
- var __dirname2 = dirname15(fileURLToPath4(import.meta.url));
9234
+ var __dirname2 = dirname16(fileURLToPath4(import.meta.url));
8812
9235
  var pkg = JSON.parse(
8813
- readFileSync33(join33(__dirname2, "..", "package.json"), "utf-8")
9236
+ readFileSync34(join34(__dirname2, "..", "package.json"), "utf-8")
8814
9237
  );
8815
9238
  var program = new Command2();
8816
9239
  program.name("visor").description("CLI for the Visor design system").version(pkg.version);