@loworbitstudio/visor 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -61,6 +61,7 @@ function resolveTransitiveDeps(registry, names, onWarning) {
61
61
  function collectDependencies(items) {
62
62
  const deps = /* @__PURE__ */ new Set();
63
63
  const devDeps = /* @__PURE__ */ new Set();
64
+ const pubDeps = /* @__PURE__ */ new Map();
64
65
  for (const item of items) {
65
66
  if (item.dependencies) {
66
67
  for (const dep of item.dependencies) {
@@ -72,12 +73,36 @@ function collectDependencies(items) {
72
73
  devDeps.add(dep);
73
74
  }
74
75
  }
76
+ if (item.pubDependencies) {
77
+ for (const dep of item.pubDependencies) {
78
+ pubDeps.set(dep.pub, dep);
79
+ }
80
+ }
75
81
  }
76
82
  return {
77
83
  dependencies: Array.from(deps).sort(),
78
- devDependencies: Array.from(devDeps).sort()
84
+ devDependencies: Array.from(devDeps).sort(),
85
+ pubDependencies: Array.from(pubDeps.values()).sort(
86
+ (a, b) => a.pub.localeCompare(b.pub)
87
+ )
79
88
  };
80
89
  }
90
+ function filterItemsByTarget(items, target) {
91
+ return items.filter(
92
+ (item) => item.target === target || item.target === void 0
93
+ );
94
+ }
95
+ function slug(name) {
96
+ return name.toLowerCase().replace(/[^a-z0-9]/g, "");
97
+ }
98
+ function findItemForTarget(registry, name, target) {
99
+ const needle = slug(name);
100
+ return registry.items.find(
101
+ (item) => slug(item.name) === needle && item.target === target
102
+ ) ?? registry.items.find(
103
+ (item) => slug(item.name) === needle && item.target === void 0
104
+ );
105
+ }
81
106
 
82
107
  // src/check/catalog.ts
83
108
  var STOP_WORDS = /* @__PURE__ */ new Set([
@@ -524,8 +549,10 @@ function checkCommand() {
524
549
  }
525
550
 
526
551
  // src/commands/init.ts
527
- import { existsSync as existsSync3, writeFileSync as writeFileSync2, mkdirSync } from "fs";
552
+ import { existsSync as existsSync3, writeFileSync as writeFileSync2, mkdirSync, readFileSync as readFileSync5 } from "fs";
528
553
  import { join as join5, dirname as dirname2 } from "path";
554
+ import { fileURLToPath as fileURLToPath2 } from "url";
555
+ import * as childProcess from "child_process";
529
556
 
530
557
  // src/config/config.ts
531
558
  import { readFileSync as readFileSync3, writeFileSync, existsSync } from "fs";
@@ -536,6 +563,7 @@ var DEFAULT_CONFIG = {
536
563
  paths: {
537
564
  components: "components/ui",
538
565
  deckComponents: "components/deck",
566
+ flutterComponents: "lib/visor/components",
539
567
  blocks: "blocks",
540
568
  hooks: "hooks",
541
569
  lib: "lib"
@@ -624,11 +652,45 @@ function installPackages(packages, cwd, dev = false) {
624
652
  }
625
653
 
626
654
  // src/commands/templates/nextjs.ts
655
+ var NEXTJS_PINNED_VERSION = "15.1.6";
656
+ var CREATE_NEXT_APP_FLAGS = [
657
+ "--ts",
658
+ "--app",
659
+ "--no-tailwind",
660
+ "--no-eslint",
661
+ "--no-src-dir",
662
+ "--import-alias",
663
+ "@/*",
664
+ "--use-npm"
665
+ ];
627
666
  var NEXTJS_STARTER_YAML = `name: my-app
628
667
  version: 1
629
668
  colors:
630
669
  primary: "#2563EB"
631
670
  `;
671
+ function generateNextjsLayout() {
672
+ return `import "./globals.css";
673
+ import { FOWT_SCRIPT } from "@loworbitstudio/visor-theme-engine/fowt";
674
+ import type { Metadata } from "next";
675
+ import type { ReactNode } from "react";
676
+
677
+ export const metadata: Metadata = {
678
+ title: "My Visor App",
679
+ description: "Built with Visor \u2014 Low Orbit Studio's design system.",
680
+ };
681
+
682
+ export default function RootLayout({ children }: { children: ReactNode }) {
683
+ return (
684
+ <html lang="en">
685
+ <head>
686
+ <script>{FOWT_SCRIPT}</script>
687
+ </head>
688
+ <body>{children}</body>
689
+ </html>
690
+ );
691
+ }
692
+ `;
693
+ }
632
694
 
633
695
  // src/commands/init.ts
634
696
  import { generateThemeData } from "@loworbitstudio/visor-theme-engine";
@@ -639,18 +701,14 @@ function initCommand(cwd, options) {
639
701
  const filesSkipped = [];
640
702
  const warnings = [];
641
703
  if (options?.template && options.template !== "nextjs") {
642
- if (json) {
643
- console.log(
644
- JSON.stringify(
645
- { success: false, error: `Unknown template: ${options.template}. Available templates: nextjs` },
646
- null,
647
- 2
648
- )
649
- );
650
- } else {
651
- logger.error(`Unknown template: ${options.template}`);
652
- logger.info("Available templates: nextjs");
653
- }
704
+ emitError(json, `Unknown template: ${options.template}. Available templates: nextjs`);
705
+ process.exit(1);
706
+ }
707
+ if (options?.template === "nextjs" && existsSync3(join5(cwd, "package.json"))) {
708
+ emitError(
709
+ json,
710
+ "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"
711
+ );
654
712
  process.exit(1);
655
713
  }
656
714
  if (configExists(cwd)) {
@@ -673,51 +731,62 @@ function initCommand(cwd, options) {
673
731
  }
674
732
  }
675
733
  if (options?.template === "nextjs") {
676
- scaffoldNextjs(cwd, json, filesCreated, filesSkipped);
734
+ scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings);
677
735
  }
678
- const missingTokens = !hasVisorTokens(cwd);
679
- if (missingTokens) {
680
- const warning = "@loworbitstudio/visor-core is not installed. Components require it for styling.";
681
- warnings.push(warning);
682
- if (!json) {
683
- logger.blank();
684
- logger.warn(warning);
685
- logger.info(" For Next.js: re-run with --template nextjs to generate tokens inline.");
736
+ if (options?.template !== "nextjs") {
737
+ const missingTokens = !hasVisorTokens(cwd);
738
+ if (missingTokens) {
739
+ const warning = "@loworbitstudio/visor-core is not installed. Components require it for styling.";
740
+ warnings.push(warning);
741
+ if (!json) {
742
+ logger.blank();
743
+ logger.warn(warning);
744
+ logger.info(" For a complete one-command setup: run `npx @loworbitstudio/visor init --template nextjs` in an empty directory.");
745
+ }
686
746
  }
687
747
  }
688
748
  if (json) {
689
- const nextSteps = [];
690
- if (options?.template === "nextjs") {
691
- nextSteps.push("Customize colors in .visor.yaml");
692
- nextSteps.push("Add FOWT prevention script to your layout.tsx <head>");
693
- nextSteps.push("Run: npx visor add button \u2014 to add your first component");
694
- } else {
695
- nextSteps.push("Run: npx visor add button \u2014 to add your first component");
696
- }
697
- if (missingTokens) {
698
- nextSteps.push("Re-run with --template nextjs to generate tokens inline (no npm package needed)");
699
- }
700
- console.log(
701
- JSON.stringify(
702
- {
703
- success: true,
704
- config: DEFAULT_CONFIG,
705
- files: { created: filesCreated, skipped: filesSkipped },
706
- warnings,
707
- nextSteps
708
- },
709
- null,
710
- 2
711
- )
712
- );
749
+ const nextSteps = buildNextSteps(options, warnings);
750
+ const result = {
751
+ success: true,
752
+ config: DEFAULT_CONFIG,
753
+ files: { created: filesCreated, skipped: filesSkipped },
754
+ warnings,
755
+ nextSteps
756
+ };
757
+ console.log(JSON.stringify(result, null, 2));
713
758
  process.exit(0);
714
759
  }
715
760
  }
716
- function scaffoldNextjs(cwd, json, filesCreated, filesSkipped) {
761
+ function buildNextSteps(options, warnings) {
762
+ const steps = [];
763
+ if (options?.template === "nextjs") {
764
+ steps.push("Run: npm run dev \u2014 start the development server");
765
+ steps.push("Customize colors in .visor.yaml, then re-run `npx visor theme apply .visor.yaml --adapter nextjs`");
766
+ steps.push("Run: npx visor add button \u2014 add your first component");
767
+ } else {
768
+ steps.push("Run: npx visor add button \u2014 add your first component");
769
+ }
770
+ if (warnings.some((w) => w.includes("visor-core"))) {
771
+ steps.push("For a complete one-command setup: re-run with --template nextjs in an empty directory");
772
+ }
773
+ return steps;
774
+ }
775
+ function emitError(json, message) {
776
+ if (json) {
777
+ const result = { success: false, error: message };
778
+ console.log(JSON.stringify(result, null, 2));
779
+ } else {
780
+ logger.error(message);
781
+ }
782
+ }
783
+ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
717
784
  if (!json) {
718
785
  logger.blank();
719
- logger.info("Scaffolding NextJS theme...");
786
+ logger.info("Scaffolding a Borealis-native Next.js app...");
720
787
  }
788
+ runCreateNextApp(cwd, json);
789
+ runInstallVisorDeps(cwd, json);
721
790
  const yamlPath = join5(cwd, ".visor.yaml");
722
791
  if (existsSync3(yamlPath)) {
723
792
  filesSkipped.push(".visor.yaml");
@@ -739,32 +808,110 @@ function scaffoldNextjs(cwd, json, filesCreated, filesSkipped) {
739
808
  });
740
809
  const globalsPath = join5(cwd, "app", "globals.css");
741
810
  const globalsDir = dirname2(globalsPath);
811
+ mkdirSync(globalsDir, { recursive: true });
742
812
  if (existsSync3(globalsPath)) {
743
- filesSkipped.push("app/globals.css");
744
- if (!json) {
745
- logger.warn("app/globals.css already exists. Skipping.");
746
- }
813
+ writeFileSync2(globalsPath, css, "utf-8");
814
+ filesCreated.push("app/globals.css");
747
815
  } else {
748
- mkdirSync(globalsDir, { recursive: true });
749
816
  writeFileSync2(globalsPath, css, "utf-8");
750
817
  filesCreated.push("app/globals.css");
818
+ }
819
+ if (!json) {
820
+ logger.success("Created app/globals.css with theme tokens");
821
+ }
822
+ const layoutPath = join5(cwd, "app", "layout.tsx");
823
+ writeFileSync2(layoutPath, generateNextjsLayout(), "utf-8");
824
+ filesCreated.push("app/layout.tsx");
825
+ if (!json) {
826
+ logger.success("Wired app/layout.tsx with FOWT prevention and theme tokens");
827
+ }
828
+ const stampDir = join5(cwd, ".lo");
829
+ const stampPath = join5(stampDir, "borealis.json");
830
+ if (existsSync3(stampPath)) {
831
+ filesSkipped.push(".lo/borealis.json");
832
+ if (!json) {
833
+ logger.warn(".lo/borealis.json already exists. Skipping.");
834
+ }
835
+ } else {
836
+ mkdirSync(stampDir, { recursive: true });
837
+ const stamp = {
838
+ visorVersion: readVisorCliVersion(),
839
+ initializedAt: (/* @__PURE__ */ new Date()).toISOString()
840
+ };
841
+ writeFileSync2(stampPath, JSON.stringify(stamp, null, 2) + "\n", "utf-8");
842
+ filesCreated.push(".lo/borealis.json");
751
843
  if (!json) {
752
- logger.success("Created app/globals.css with theme tokens");
844
+ logger.success("Stamped .lo/borealis.json");
753
845
  }
754
846
  }
755
847
  if (!json) {
848
+ logger.blank();
849
+ logger.success("Your Borealis-native Next.js app is ready.");
756
850
  logger.blank();
757
851
  logger.info("Next steps:");
758
- logger.item("Customize colors in .visor.yaml");
759
- logger.item("Add FOWT prevention script to your layout.tsx <head>");
760
- logger.item("Run: npx visor add button \u2014 to add your first component");
852
+ logger.item("npm run dev # start the dev server");
853
+ logger.item("Edit .visor.yaml to customize tokens, then re-run theme apply");
854
+ logger.item("npx visor add button # add your first component");
855
+ }
856
+ void warnings;
857
+ }
858
+ function runCreateNextApp(cwd, json) {
859
+ if (!json) {
860
+ logger.info(`Running create-next-app@${NEXTJS_PINNED_VERSION}...`);
761
861
  }
862
+ const result = childProcess.spawnSync(
863
+ "npx",
864
+ [`create-next-app@${NEXTJS_PINNED_VERSION}`, ".", ...CREATE_NEXT_APP_FLAGS],
865
+ { cwd, stdio: json ? "ignore" : "inherit" }
866
+ );
867
+ assertSpawnSuccess(result, "create-next-app");
868
+ }
869
+ function runInstallVisorDeps(cwd, json) {
870
+ if (!json) {
871
+ logger.info("Installing @loworbitstudio/visor-core and visor-theme-engine...");
872
+ }
873
+ const result = childProcess.spawnSync(
874
+ "npm",
875
+ [
876
+ "install",
877
+ "@loworbitstudio/visor-core",
878
+ "@loworbitstudio/visor-theme-engine"
879
+ ],
880
+ { cwd, stdio: json ? "ignore" : "inherit" }
881
+ );
882
+ assertSpawnSuccess(result, "npm install");
883
+ }
884
+ function assertSpawnSuccess(result, label) {
885
+ if (result.error) {
886
+ throw new Error(`${label} failed to start: ${result.error.message}`);
887
+ }
888
+ if (typeof result.status === "number" && result.status !== 0) {
889
+ throw new Error(`${label} exited with code ${result.status}`);
890
+ }
891
+ }
892
+ function readVisorCliVersion() {
893
+ try {
894
+ const here = dirname2(fileURLToPath2(import.meta.url));
895
+ for (let i = 0; i < 5; i++) {
896
+ const segments = new Array(i).fill("..");
897
+ const candidate = join5(here, ...segments, "package.json");
898
+ try {
899
+ const pkg = JSON.parse(readFileSync5(candidate, "utf-8"));
900
+ if (pkg.name === "@loworbitstudio/visor" && pkg.version) {
901
+ return pkg.version;
902
+ }
903
+ } catch {
904
+ }
905
+ }
906
+ } catch {
907
+ }
908
+ return "0.0.0-dev";
762
909
  }
763
910
 
764
911
  // src/utils/fs.ts
765
912
  import {
766
913
  writeFileSync as writeFileSync3,
767
- readFileSync as readFileSync5,
914
+ readFileSync as readFileSync6,
768
915
  existsSync as existsSync4,
769
916
  mkdirSync as mkdirSync2
770
917
  } from "fs";
@@ -780,6 +927,10 @@ function resolveOutputPath(registryPath, type, config, cwd) {
780
927
  relativePath = registryPath.replace(/^components\/deck\//, "");
781
928
  return join6(cwd, config.paths.deckComponents, relativePath);
782
929
  }
930
+ if (registryPath.startsWith("components/flutter/")) {
931
+ relativePath = registryPath.replace(/^components\/flutter\//, "");
932
+ return join6(cwd, config.paths.flutterComponents, relativePath);
933
+ }
783
934
  relativePath = registryPath.replace(/^components\/ui\//, "");
784
935
  return join6(cwd, config.paths.components, relativePath);
785
936
  }
@@ -802,7 +953,7 @@ function writeFile(filePath, content) {
802
953
  }
803
954
  function readFile(filePath) {
804
955
  if (!existsSync4(filePath)) return null;
805
- return readFileSync5(filePath, "utf-8");
956
+ return readFileSync6(filePath, "utf-8");
806
957
  }
807
958
  function fileExists(filePath) {
808
959
  return existsSync4(filePath);
@@ -945,10 +1096,140 @@ function listCommand(cwd, options = {}) {
945
1096
  }
946
1097
  }
947
1098
 
1099
+ // src/utils/pubspec.ts
1100
+ import { existsSync as existsSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
1101
+ import { join as join7 } from "path";
1102
+ import { parseDocument, YAMLMap } from "yaml";
1103
+ function mergePubspec(pubspecText, deps) {
1104
+ const doc = parseDocument(pubspecText);
1105
+ const added = [];
1106
+ const skipped = [];
1107
+ let depsNode = doc.get("dependencies");
1108
+ if (depsNode == null || !(depsNode instanceof YAMLMap)) {
1109
+ depsNode = new YAMLMap();
1110
+ doc.set("dependencies", depsNode);
1111
+ }
1112
+ for (const dep of deps) {
1113
+ if (depsNode.has(dep.pub)) {
1114
+ skipped.push(dep.pub);
1115
+ continue;
1116
+ }
1117
+ depsNode.set(dep.pub, dep.version);
1118
+ added.push(dep.pub);
1119
+ }
1120
+ return { text: doc.toString(), added, skipped };
1121
+ }
1122
+ function pubspecPath(cwd) {
1123
+ return join7(cwd, "pubspec.yaml");
1124
+ }
1125
+ function pubspecExists(cwd) {
1126
+ return existsSync5(pubspecPath(cwd));
1127
+ }
1128
+ function isPubPackageInstalled(packageName, cwd) {
1129
+ if (!pubspecExists(cwd)) return false;
1130
+ const text = readFileSync7(pubspecPath(cwd), "utf-8");
1131
+ const doc = parseDocument(text);
1132
+ const depsNode = doc.get("dependencies");
1133
+ if (!(depsNode instanceof YAMLMap)) return false;
1134
+ return depsNode.has(packageName);
1135
+ }
1136
+ function getUninstalledPubDeps(deps, cwd) {
1137
+ return deps.filter((d) => !isPubPackageInstalled(d.pub, cwd));
1138
+ }
1139
+ function addPubDependencies(deps, cwd) {
1140
+ const path2 = pubspecPath(cwd);
1141
+ if (!existsSync5(path2)) {
1142
+ throw new Error(
1143
+ `No pubspec.yaml found at ${path2}. Run this command from a Flutter project root.`
1144
+ );
1145
+ }
1146
+ const text = readFileSync7(path2, "utf-8");
1147
+ const result = mergePubspec(text, deps);
1148
+ if (result.added.length > 0) {
1149
+ writeFileSync4(path2, result.text, "utf-8");
1150
+ }
1151
+ return result;
1152
+ }
1153
+
1154
+ // src/utils/flutter.ts
1155
+ import { execFileSync as execFileSync2 } from "child_process";
1156
+ import { existsSync as existsSync6, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
1157
+ import { homedir } from "os";
1158
+ import { join as join8 } from "path";
1159
+ function isExecutable(path2) {
1160
+ try {
1161
+ const s = statSync2(path2);
1162
+ return s.isFile();
1163
+ } catch {
1164
+ return false;
1165
+ }
1166
+ }
1167
+ function fromPath(env) {
1168
+ const pathVar = env.PATH ?? "";
1169
+ const sep = process.platform === "win32" ? ";" : ":";
1170
+ const bin = process.platform === "win32" ? "flutter.bat" : "flutter";
1171
+ for (const dir of pathVar.split(sep)) {
1172
+ if (!dir) continue;
1173
+ const candidate = join8(dir, bin);
1174
+ if (isExecutable(candidate)) return candidate;
1175
+ }
1176
+ return null;
1177
+ }
1178
+ function fromFvm(home) {
1179
+ const fvmDefault = join8(home, "fvm", "default", "bin", "flutter");
1180
+ if (isExecutable(fvmDefault)) return fvmDefault;
1181
+ const versionsDir = join8(home, "fvm", "versions");
1182
+ if (!existsSync6(versionsDir)) return null;
1183
+ let best = null;
1184
+ try {
1185
+ for (const name of readdirSync2(versionsDir)) {
1186
+ const candidate = join8(versionsDir, name, "bin", "flutter");
1187
+ if (!isExecutable(candidate)) continue;
1188
+ if (!best || compareSemver(name, best.version) > 0) {
1189
+ best = { version: name, path: candidate };
1190
+ }
1191
+ }
1192
+ } catch {
1193
+ return null;
1194
+ }
1195
+ return best?.path ?? null;
1196
+ }
1197
+ function compareSemver(a, b) {
1198
+ const pa = a.split(".").map((x) => parseInt(x, 10) || 0);
1199
+ const pb = b.split(".").map((x) => parseInt(x, 10) || 0);
1200
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
1201
+ const av = pa[i] ?? 0;
1202
+ const bv = pb[i] ?? 0;
1203
+ if (av !== bv) return av - bv;
1204
+ }
1205
+ return 0;
1206
+ }
1207
+ function findFlutterBin(options = {}) {
1208
+ const env = options.env ?? process.env;
1209
+ const home = options.home ?? homedir();
1210
+ const envRoot = env.FLUTTER_ROOT;
1211
+ if (envRoot) {
1212
+ const bin = join8(envRoot, "bin", "flutter");
1213
+ if (isExecutable(bin)) return bin;
1214
+ }
1215
+ const fromPathBin = fromPath(env);
1216
+ if (fromPathBin) return fromPathBin;
1217
+ return fromFvm(home);
1218
+ }
1219
+ function runFlutterPubGet(cwd, bin) {
1220
+ try {
1221
+ execFileSync2(bin, ["pub", "get"], { cwd, stdio: "inherit" });
1222
+ return true;
1223
+ } catch {
1224
+ return false;
1225
+ }
1226
+ }
1227
+
948
1228
  // src/commands/add.ts
949
1229
  function addCommand(components, cwd, options = {}) {
950
1230
  const json = options.json ?? false;
951
1231
  const dryRun = options.dryRun ?? false;
1232
+ const target = options.target ?? "react";
952
1233
  const prefix = dryRun ? "[dry-run] " : "";
953
1234
  let autoInitialized = false;
954
1235
  if (!configExists(cwd)) {
@@ -973,9 +1254,12 @@ function addCommand(components, cwd, options = {}) {
973
1254
  }
974
1255
  process.exit(1);
975
1256
  }
1257
+ const targetRegistry = {
1258
+ items: filterItemsByTarget(registry.items, target)
1259
+ };
976
1260
  if (options.block && components.length > 0) {
977
1261
  for (const name of components) {
978
- const item = registry.items.find((i) => i.name === name);
1262
+ const item = targetRegistry.items.find((i) => i.name === name);
979
1263
  if (item && item.type !== "registry:block") {
980
1264
  if (json) {
981
1265
  console.log(
@@ -1012,7 +1296,7 @@ function addCommand(components, cwd, options = {}) {
1012
1296
  }
1013
1297
  process.exit(1);
1014
1298
  }
1015
- const categoryItems = registry.items.filter(
1299
+ const categoryItems = targetRegistry.items.filter(
1016
1300
  (item) => item.category === options.category
1017
1301
  );
1018
1302
  if (categoryItems.length === 0) {
@@ -1038,7 +1322,7 @@ function addCommand(components, cwd, options = {}) {
1038
1322
  }
1039
1323
  if (itemNames.length === 0) {
1040
1324
  if (options.block) {
1041
- const blockItems = registry.items.filter(
1325
+ const blockItems = targetRegistry.items.filter(
1042
1326
  (item) => item.type === "registry:block"
1043
1327
  );
1044
1328
  if (json) {
@@ -1077,10 +1361,24 @@ function addCommand(components, cwd, options = {}) {
1077
1361
  }
1078
1362
  process.exit(1);
1079
1363
  }
1364
+ const canonicalNames = [];
1365
+ for (const name of itemNames) {
1366
+ const resolved = findItemForTarget(targetRegistry, name, target);
1367
+ if (!resolved) {
1368
+ const message = `Registry item "${name}" not found for target "${target}".`;
1369
+ if (json) {
1370
+ console.log(JSON.stringify({ success: false, error: message }, null, 2));
1371
+ } else {
1372
+ logger.error(message);
1373
+ }
1374
+ process.exit(1);
1375
+ }
1376
+ canonicalNames.push(resolved.name);
1377
+ }
1080
1378
  const circularWarnings = [];
1081
1379
  let items;
1082
1380
  try {
1083
- items = resolveTransitiveDeps(registry, itemNames, (msg) => {
1381
+ items = resolveTransitiveDeps(targetRegistry, canonicalNames, (msg) => {
1084
1382
  circularWarnings.push(msg);
1085
1383
  });
1086
1384
  } catch (error) {
@@ -1134,66 +1432,124 @@ function addCommand(components, cwd, options = {}) {
1134
1432
  `${prefix}Files: ${writtenFiles.length} written, ${skippedFiles.length} skipped`
1135
1433
  );
1136
1434
  }
1137
- const { dependencies, devDependencies } = collectDependencies(items);
1138
- const uninstalledDeps = dryRun ? dependencies : getUninstalledDeps(dependencies, cwd);
1139
- const uninstalledDevDeps = dryRun ? devDependencies : getUninstalledDeps(devDependencies, cwd);
1435
+ const { dependencies, devDependencies, pubDependencies } = collectDependencies(items);
1140
1436
  const installedDeps = [];
1141
1437
  const failedDeps = [];
1142
- if (uninstalledDeps.length > 0) {
1143
- if (dryRun) {
1144
- if (!json) {
1145
- logger.blank();
1146
- logger.info(`${prefix}Would install dependencies: ${uninstalledDeps.join(", ")}`);
1147
- }
1148
- installedDeps.push(...uninstalledDeps);
1149
- } else {
1150
- if (!json) {
1151
- logger.blank();
1152
- logger.info("Installing dependencies...");
1438
+ const warnings = [];
1439
+ if (target === "flutter") {
1440
+ const uninstalledPubDeps = dryRun ? pubDependencies : pubspecExists(cwd) ? getUninstalledPubDeps(pubDependencies, cwd) : pubDependencies;
1441
+ if (uninstalledPubDeps.length > 0) {
1442
+ if (dryRun) {
1443
+ if (!json) {
1444
+ logger.blank();
1445
+ logger.info(
1446
+ `${prefix}Would add pub dependencies: ${uninstalledPubDeps.map((d) => `${d.pub}@${d.version}`).join(", ")}`
1447
+ );
1448
+ }
1449
+ installedDeps.push(...uninstalledPubDeps.map((d) => d.pub));
1450
+ } else if (!pubspecExists(cwd)) {
1451
+ const message = "No pubspec.yaml found. Run this from a Flutter project root, or add " + uninstalledPubDeps.map((d) => `${d.pub}: ${d.version}`).join(", ") + " to pubspec.yaml manually.";
1452
+ warnings.push(message);
1453
+ if (!json) {
1454
+ logger.blank();
1455
+ logger.warn(message);
1456
+ }
1457
+ } else {
1458
+ if (!json) {
1459
+ logger.blank();
1460
+ logger.info("Updating pubspec.yaml...");
1461
+ }
1462
+ const result = addPubDependencies(uninstalledPubDeps, cwd);
1463
+ installedDeps.push(...result.added);
1464
+ const flutterBin = findFlutterBin();
1465
+ if (flutterBin) {
1466
+ if (!json) {
1467
+ logger.info("Running flutter pub get...");
1468
+ }
1469
+ if (!runFlutterPubGet(cwd, flutterBin)) {
1470
+ const warning = "flutter pub get failed. Run it manually to refresh dependencies.";
1471
+ warnings.push(warning);
1472
+ if (!json) logger.warn(warning);
1473
+ }
1474
+ } else {
1475
+ const warning = "flutter CLI not found. Run `flutter pub get` manually after setting up Flutter (or FVM).";
1476
+ warnings.push(warning);
1477
+ if (!json) logger.warn(warning);
1478
+ }
1153
1479
  }
1154
- if (installPackages(uninstalledDeps, cwd)) {
1480
+ }
1481
+ } else {
1482
+ const uninstalledDeps = dryRun ? dependencies : getUninstalledDeps(dependencies, cwd);
1483
+ const uninstalledDevDeps = dryRun ? devDependencies : getUninstalledDeps(devDependencies, cwd);
1484
+ if (uninstalledDeps.length > 0) {
1485
+ if (dryRun) {
1486
+ if (!json) {
1487
+ logger.blank();
1488
+ logger.info(
1489
+ `${prefix}Would install dependencies: ${uninstalledDeps.join(", ")}`
1490
+ );
1491
+ }
1155
1492
  installedDeps.push(...uninstalledDeps);
1156
1493
  } else {
1157
- failedDeps.push(...uninstalledDeps);
1158
1494
  if (!json) {
1159
- logger.warn("Some dependencies failed to install. Install them manually:");
1160
- logger.info(` npm install ${uninstalledDeps.join(" ")}`);
1495
+ logger.blank();
1496
+ logger.info("Installing dependencies...");
1497
+ }
1498
+ if (installPackages(uninstalledDeps, cwd)) {
1499
+ installedDeps.push(...uninstalledDeps);
1500
+ } else {
1501
+ failedDeps.push(...uninstalledDeps);
1502
+ if (!json) {
1503
+ logger.warn(
1504
+ "Some dependencies failed to install. Install them manually:"
1505
+ );
1506
+ logger.info(` npm install ${uninstalledDeps.join(" ")}`);
1507
+ }
1161
1508
  }
1162
1509
  }
1163
1510
  }
1164
- }
1165
- if (uninstalledDevDeps.length > 0) {
1166
- if (dryRun) {
1167
- if (!json) {
1168
- logger.blank();
1169
- logger.info(`${prefix}Would install dev dependencies: ${uninstalledDevDeps.join(", ")}`);
1170
- }
1171
- installedDeps.push(...uninstalledDevDeps);
1172
- } else {
1173
- if (!json) {
1174
- logger.blank();
1175
- logger.info("Installing dev dependencies...");
1176
- }
1177
- if (installPackages(uninstalledDevDeps, cwd, true)) {
1511
+ if (uninstalledDevDeps.length > 0) {
1512
+ if (dryRun) {
1513
+ if (!json) {
1514
+ logger.blank();
1515
+ logger.info(
1516
+ `${prefix}Would install dev dependencies: ${uninstalledDevDeps.join(", ")}`
1517
+ );
1518
+ }
1178
1519
  installedDeps.push(...uninstalledDevDeps);
1179
1520
  } else {
1180
- failedDeps.push(...uninstalledDevDeps);
1181
1521
  if (!json) {
1182
- logger.warn("Some dev dependencies failed to install. Install them manually:");
1183
- logger.info(` npm install --save-dev ${uninstalledDevDeps.join(" ")}`);
1522
+ logger.blank();
1523
+ logger.info("Installing dev dependencies...");
1524
+ }
1525
+ if (installPackages(uninstalledDevDeps, cwd, true)) {
1526
+ installedDeps.push(...uninstalledDevDeps);
1527
+ } else {
1528
+ failedDeps.push(...uninstalledDevDeps);
1529
+ if (!json) {
1530
+ logger.warn(
1531
+ "Some dev dependencies failed to install. Install them manually:"
1532
+ );
1533
+ logger.info(
1534
+ ` npm install --save-dev ${uninstalledDevDeps.join(" ")}`
1535
+ );
1536
+ }
1184
1537
  }
1185
1538
  }
1186
1539
  }
1187
- }
1188
- const warnings = [];
1189
- if (!hasVisorTokens(cwd)) {
1190
- const warning = "@loworbitstudio/visor-core is not installed. Components require it for styling.";
1191
- warnings.push(warning);
1192
- if (!json) {
1193
- logger.blank();
1194
- logger.warn(warning);
1195
- logger.info(" For Next.js: npx @loworbitstudio/visor init --template nextjs");
1196
- logger.info(" This generates all tokens inline \u2014 no npm package needed.");
1540
+ if (!hasVisorTokens(cwd)) {
1541
+ const warning = "@loworbitstudio/visor-core is not installed. Components require it for styling.";
1542
+ warnings.push(warning);
1543
+ if (!json) {
1544
+ logger.blank();
1545
+ logger.warn(warning);
1546
+ logger.info(
1547
+ " For Next.js: npx @loworbitstudio/visor init --template nextjs"
1548
+ );
1549
+ logger.info(
1550
+ " This generates all tokens inline \u2014 no npm package needed."
1551
+ );
1552
+ }
1197
1553
  }
1198
1554
  }
1199
1555
  if (json) {
@@ -1496,14 +1852,15 @@ function infoCommand(name, cwd, options = {}) {
1496
1852
  }
1497
1853
 
1498
1854
  // src/commands/theme-apply.ts
1499
- import { readFileSync as readFileSync6, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
1500
- import { resolve as resolve2, dirname as dirname4 } from "path";
1855
+ import { readFileSync as readFileSync8, writeFileSync as writeFileSync5, mkdirSync as mkdirSync3 } from "fs";
1856
+ import { resolve as resolve2, dirname as dirname4, join as join9 } from "path";
1501
1857
  import { generateTheme, generateThemeData as generateThemeData2 } from "@loworbitstudio/visor-theme-engine";
1502
1858
  import {
1503
1859
  nextjsAdapter as nextjsAdapter2,
1504
1860
  fumadocsAdapter,
1505
1861
  deckAdapter,
1506
- docsAdapter
1862
+ docsAdapter,
1863
+ flutterAdapter
1507
1864
  } from "@loworbitstudio/visor-theme-engine/adapters";
1508
1865
  function defaultOutputPath(adapter, themeName) {
1509
1866
  switch (adapter) {
@@ -1512,13 +1869,15 @@ function defaultOutputPath(adapter, themeName) {
1512
1869
  case "fumadocs":
1513
1870
  return "visor-fumadocs-bridge.css";
1514
1871
  case "deck": {
1515
- const slug = (themeName ?? "theme").toLowerCase().replace(/\s+/g, "-");
1516
- return `visor-deck-${slug}.css`;
1872
+ const slug2 = (themeName ?? "theme").toLowerCase().replace(/\s+/g, "-");
1873
+ return `visor-deck-${slug2}.css`;
1517
1874
  }
1518
1875
  case "docs": {
1519
- const slug = (themeName ?? "theme").toLowerCase().replace(/\s+/g, "-");
1520
- return `${slug}-theme.css`;
1876
+ const slug2 = (themeName ?? "theme").toLowerCase().replace(/\s+/g, "-");
1877
+ return `${slug2}-theme.css`;
1521
1878
  }
1879
+ case "flutter":
1880
+ return "packages/ui";
1522
1881
  default:
1523
1882
  return "visor-theme.css";
1524
1883
  }
@@ -1527,7 +1886,7 @@ function themeApplyCommand(file, cwd, options) {
1527
1886
  const filePath = resolve2(cwd, file);
1528
1887
  let yamlContent;
1529
1888
  try {
1530
- yamlContent = readFileSync6(filePath, "utf-8");
1889
+ yamlContent = readFileSync8(filePath, "utf-8");
1531
1890
  } catch {
1532
1891
  if (options.json) {
1533
1892
  console.log(
@@ -1542,7 +1901,8 @@ function themeApplyCommand(file, cwd, options) {
1542
1901
  }
1543
1902
  process.exit(2);
1544
1903
  }
1545
- let css;
1904
+ let css = null;
1905
+ let fileMap = null;
1546
1906
  let themeName;
1547
1907
  let sections;
1548
1908
  try {
@@ -1567,6 +1927,17 @@ function themeApplyCommand(file, cwd, options) {
1567
1927
  case "docs":
1568
1928
  css = docsAdapter(adapterInput);
1569
1929
  break;
1930
+ case "flutter": {
1931
+ const flutterOptions = {
1932
+ packageName: options.packageName,
1933
+ tokensOnly: options.tokensOnly,
1934
+ lightOnly: options.lightOnly,
1935
+ darkOnly: options.darkOnly,
1936
+ themeClassName: options.themeClassName
1937
+ };
1938
+ fileMap = flutterAdapter(adapterInput, flutterOptions);
1939
+ break;
1940
+ }
1570
1941
  default:
1571
1942
  throw new Error(`Unknown adapter: ${options.adapter}`);
1572
1943
  }
@@ -1591,12 +1962,56 @@ function themeApplyCommand(file, cwd, options) {
1591
1962
  }
1592
1963
  process.exit(1);
1593
1964
  }
1594
- const outputFile = options.output ?? defaultOutputPath(options.adapter, themeName);
1595
- const outputPath = resolve2(cwd, outputFile);
1965
+ const outputTarget = options.output ?? defaultOutputPath(options.adapter, themeName);
1966
+ const outputPath = resolve2(cwd, outputTarget);
1967
+ if (fileMap) {
1968
+ try {
1969
+ mkdirSync3(outputPath, { recursive: true });
1970
+ let totalBytes = 0;
1971
+ for (const [relPath, content] of Object.entries(fileMap.files)) {
1972
+ const filePath2 = join9(outputPath, relPath);
1973
+ mkdirSync3(dirname4(filePath2), { recursive: true });
1974
+ writeFileSync5(filePath2, content, "utf-8");
1975
+ totalBytes += content.length;
1976
+ }
1977
+ if (options.json) {
1978
+ console.log(
1979
+ JSON.stringify({
1980
+ success: true,
1981
+ directory: outputPath,
1982
+ adapter: options.adapter,
1983
+ files: Object.keys(fileMap.files),
1984
+ size: totalBytes
1985
+ })
1986
+ );
1987
+ } else {
1988
+ logger.success(`Flutter theme package generated: ${outputPath}`);
1989
+ logger.info(`Adapter: ${options.adapter}`);
1990
+ logger.item(`Files: ${Object.keys(fileMap.files).length}`);
1991
+ logger.item(`Size: ${formatSize(totalBytes)}`);
1992
+ }
1993
+ } catch {
1994
+ if (options.json) {
1995
+ console.log(
1996
+ JSON.stringify({
1997
+ success: false,
1998
+ error: `Could not write package to: ${outputPath}`
1999
+ })
2000
+ );
2001
+ } else {
2002
+ logger.error(`Could not write package to: ${outputPath}`);
2003
+ }
2004
+ process.exit(2);
2005
+ }
2006
+ return;
2007
+ }
2008
+ if (css === null) {
2009
+ process.exit(1);
2010
+ }
1596
2011
  const outputDir = dirname4(outputPath);
1597
2012
  try {
1598
2013
  mkdirSync3(outputDir, { recursive: true });
1599
- writeFileSync4(outputPath, css, "utf-8");
2014
+ writeFileSync5(outputPath, css, "utf-8");
1600
2015
  } catch {
1601
2016
  if (options.json) {
1602
2017
  console.log(
@@ -1637,7 +2052,7 @@ function formatSize(bytes) {
1637
2052
  }
1638
2053
 
1639
2054
  // src/commands/theme-export.ts
1640
- import { readFileSync as readFileSync7 } from "fs";
2055
+ import { readFileSync as readFileSync9 } from "fs";
1641
2056
  import { resolve as resolve3 } from "path";
1642
2057
  import {
1643
2058
  parseConfig,
@@ -1649,7 +2064,7 @@ function themeExportCommand(file, cwd, options) {
1649
2064
  const filePath = resolve3(cwd, file ?? ".visor.yaml");
1650
2065
  let yamlContent;
1651
2066
  try {
1652
- yamlContent = readFileSync7(filePath, "utf-8");
2067
+ yamlContent = readFileSync9(filePath, "utf-8");
1653
2068
  } catch {
1654
2069
  if (options.json) {
1655
2070
  console.log(
@@ -1701,7 +2116,7 @@ function themeExportCommand(file, cwd, options) {
1701
2116
  }
1702
2117
 
1703
2118
  // src/commands/theme-validate.ts
1704
- import { readFileSync as readFileSync8 } from "fs";
2119
+ import { readFileSync as readFileSync10 } from "fs";
1705
2120
  import { resolve as resolve4 } from "path";
1706
2121
  import { parse as parseYaml } from "yaml";
1707
2122
  import { validate } from "@loworbitstudio/visor-theme-engine";
@@ -1710,7 +2125,7 @@ function themeValidateCommand(file, cwd, options) {
1710
2125
  const filePath = resolve4(cwd, file);
1711
2126
  let fileContent;
1712
2127
  try {
1713
- fileContent = readFileSync8(filePath, "utf-8");
2128
+ fileContent = readFileSync10(filePath, "utf-8");
1714
2129
  } catch {
1715
2130
  if (options.json) {
1716
2131
  console.log(
@@ -1797,8 +2212,8 @@ function printIssue(issue) {
1797
2212
  }
1798
2213
 
1799
2214
  // src/commands/theme-extract.ts
1800
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, existsSync as existsSync5, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
1801
- import { resolve as resolve5, join as join7, basename, extname as extname2, relative } from "path";
2215
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, existsSync as existsSync7, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
2216
+ import { resolve as resolve5, join as join10, basename, extname as extname2, relative } from "path";
1802
2217
  import { stringify as stringifyYaml } from "yaml";
1803
2218
  import {
1804
2219
  extractFromCSS,
@@ -1828,7 +2243,7 @@ var CSS_DIRS = [
1828
2243
  ];
1829
2244
  function themeExtractCommand(cwd, options) {
1830
2245
  const targetDir = resolve5(cwd, options.from ?? ".");
1831
- if (!existsSync5(targetDir)) {
2246
+ if (!existsSync7(targetDir)) {
1832
2247
  if (options.json) {
1833
2248
  console.log(JSON.stringify({ success: false, error: `Directory not found: ${targetDir}` }));
1834
2249
  } else {
@@ -1882,16 +2297,16 @@ function collectCSSFiles(targetDir) {
1882
2297
  const files = [];
1883
2298
  const seen = /* @__PURE__ */ new Set();
1884
2299
  for (const pattern2 of CSS_FILE_PATTERNS) {
1885
- const rootPath = join7(targetDir, pattern2);
2300
+ const rootPath = join10(targetDir, pattern2);
1886
2301
  addFileIfExists(rootPath, files, seen);
1887
2302
  for (const dir of CSS_DIRS) {
1888
- const dirPath = join7(targetDir, dir, pattern2);
2303
+ const dirPath = join10(targetDir, dir, pattern2);
1889
2304
  addFileIfExists(dirPath, files, seen);
1890
2305
  }
1891
2306
  }
1892
2307
  for (const dir of CSS_DIRS) {
1893
- const dirPath = join7(targetDir, dir);
1894
- if (existsSync5(dirPath) && statSync2(dirPath).isDirectory()) {
2308
+ const dirPath = join10(targetDir, dir);
2309
+ if (existsSync7(dirPath) && statSync3(dirPath).isDirectory()) {
1895
2310
  scanDirForCSS(dirPath, files, seen, 2);
1896
2311
  }
1897
2312
  }
@@ -1901,9 +2316,9 @@ function collectCSSFiles(targetDir) {
1901
2316
  function addFileIfExists(filePath, files, seen) {
1902
2317
  const resolved = resolve5(filePath);
1903
2318
  if (seen.has(resolved)) return;
1904
- if (!existsSync5(resolved)) return;
2319
+ if (!existsSync7(resolved)) return;
1905
2320
  try {
1906
- const content = readFileSync9(resolved, "utf-8");
2321
+ const content = readFileSync11(resolved, "utf-8");
1907
2322
  if (content.includes("--")) {
1908
2323
  files.push({ path: resolved, content });
1909
2324
  seen.add(resolved);
@@ -1912,7 +2327,7 @@ function addFileIfExists(filePath, files, seen) {
1912
2327
  }
1913
2328
  }
1914
2329
  function scanDirForCSS(dir, files, seen, maxDepth) {
1915
- if (!existsSync5(dir)) return;
2330
+ if (!existsSync7(dir)) return;
1916
2331
  const SKIP_DIRS = /* @__PURE__ */ new Set([
1917
2332
  "node_modules",
1918
2333
  ".next",
@@ -1926,15 +2341,15 @@ function scanDirForCSS(dir, files, seen, maxDepth) {
1926
2341
  ".vercel"
1927
2342
  ]);
1928
2343
  try {
1929
- const entries = readdirSync2(dir, { withFileTypes: true });
2344
+ const entries = readdirSync3(dir, { withFileTypes: true });
1930
2345
  for (const entry of entries) {
1931
2346
  if (entry.isDirectory()) {
1932
2347
  if (SKIP_DIRS.has(entry.name)) continue;
1933
2348
  if (maxDepth > 0) {
1934
- scanDirForCSS(join7(dir, entry.name), files, seen, maxDepth - 1);
2349
+ scanDirForCSS(join10(dir, entry.name), files, seen, maxDepth - 1);
1935
2350
  }
1936
2351
  } else if (entry.isFile() && extname2(entry.name) === ".css") {
1937
- addFileIfExists(join7(dir, entry.name), files, seen);
2352
+ addFileIfExists(join10(dir, entry.name), files, seen);
1938
2353
  }
1939
2354
  }
1940
2355
  } catch {
@@ -2016,10 +2431,10 @@ function extractVarName(varExpr) {
2016
2431
  function parseNextFontFromLayouts(targetDir) {
2017
2432
  const fontMap = /* @__PURE__ */ new Map();
2018
2433
  for (const relPath of LAYOUT_FILE_PATHS) {
2019
- const fullPath = join7(targetDir, relPath);
2020
- if (!existsSync5(fullPath)) continue;
2434
+ const fullPath = join10(targetDir, relPath);
2435
+ if (!existsSync7(fullPath)) continue;
2021
2436
  try {
2022
- const content = readFileSync9(fullPath, "utf-8");
2437
+ const content = readFileSync11(fullPath, "utf-8");
2023
2438
  parseNextFontDeclarations(content, fontMap);
2024
2439
  } catch {
2025
2440
  }
@@ -2104,10 +2519,10 @@ var MONO_FONT_NAMES = /* @__PURE__ */ new Set([
2104
2519
  "IBM Plex Mono"
2105
2520
  ]);
2106
2521
  function extractFontHints(targetDir) {
2107
- const pkgPath = join7(targetDir, "package.json");
2108
- if (!existsSync5(pkgPath)) return void 0;
2522
+ const pkgPath = join10(targetDir, "package.json");
2523
+ if (!existsSync7(pkgPath)) return void 0;
2109
2524
  try {
2110
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
2525
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
2111
2526
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
2112
2527
  const fonts2 = [];
2113
2528
  for (const [dep, _] of Object.entries(allDeps)) {
@@ -2143,10 +2558,10 @@ function extractFontHints(targetDir) {
2143
2558
  }
2144
2559
  }
2145
2560
  function inferThemeName(targetDir) {
2146
- const pkgPath = join7(targetDir, "package.json");
2147
- if (existsSync5(pkgPath)) {
2561
+ const pkgPath = join10(targetDir, "package.json");
2562
+ if (existsSync7(pkgPath)) {
2148
2563
  try {
2149
- const pkg = JSON.parse(readFileSync9(pkgPath, "utf-8"));
2564
+ const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
2150
2565
  if (pkg.name) {
2151
2566
  const name = pkg.name.replace(/^@[\w-]+\//, "");
2152
2567
  return `${name}-theme`;
@@ -2211,7 +2626,7 @@ function outputYAML(result, outputPath, cwd, validationResult) {
2211
2626
  }
2212
2627
  logger.blank();
2213
2628
  }
2214
- writeFileSync5(outFile, yamlStr, "utf-8");
2629
+ writeFileSync6(outFile, yamlStr, "utf-8");
2215
2630
  logger.success(`Theme written to ${relative(cwd, outFile)}`);
2216
2631
  if (validationResult) {
2217
2632
  logger.blank();
@@ -2269,14 +2684,14 @@ function buildAnnotatedYAML(result) {
2269
2684
  }
2270
2685
 
2271
2686
  // src/commands/theme-register.ts
2272
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync6, mkdirSync as mkdirSync4, existsSync as existsSync7 } from "fs";
2273
- import { resolve as resolve7, join as join9 } from "path";
2687
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync4, existsSync as existsSync9 } from "fs";
2688
+ import { resolve as resolve7, join as join12 } from "path";
2274
2689
  import { generateThemeData as generateThemeData3 } from "@loworbitstudio/visor-theme-engine";
2275
2690
  import { docsAdapter as docsAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
2276
2691
 
2277
2692
  // src/utils/theme-helpers.ts
2278
- import { existsSync as existsSync6 } from "fs";
2279
- import { resolve as resolve6, dirname as dirname5, join as join8 } from "path";
2693
+ import { existsSync as existsSync8 } from "fs";
2694
+ import { resolve as resolve6, dirname as dirname5, join as join11 } from "path";
2280
2695
  function toSlug(name) {
2281
2696
  return name.toLowerCase().replace(/\s+/g, "-");
2282
2697
  }
@@ -2286,7 +2701,7 @@ function toLabel(name) {
2286
2701
  function findRepoRoot(startDir) {
2287
2702
  let current = resolve6(startDir);
2288
2703
  while (true) {
2289
- if (existsSync6(join8(current, "packages", "docs"))) {
2704
+ if (existsSync8(join11(current, "packages", "docs"))) {
2290
2705
  return current;
2291
2706
  }
2292
2707
  const parent = dirname5(current);
@@ -2297,8 +2712,8 @@ function findRepoRoot(startDir) {
2297
2712
  }
2298
2713
 
2299
2714
  // src/commands/theme-register.ts
2300
- function insertGlobalsImport(content, slug) {
2301
- const importLine = `@import './${slug}-theme.css';`;
2715
+ function insertGlobalsImport(content, slug2) {
2716
+ const importLine = `@import './${slug2}-theme.css';`;
2302
2717
  if (content.includes(importLine)) {
2303
2718
  return { updated: content, changed: false };
2304
2719
  }
@@ -2329,8 +2744,8 @@ function insertGlobalsImport(content, slug) {
2329
2744
  lines.splice(insertAt, 0, importLine);
2330
2745
  return { updated: lines.join("\n"), changed: true };
2331
2746
  }
2332
- function insertThemeConfig(content, slug, label, group) {
2333
- if (content.includes(`value: "${slug}"`)) {
2747
+ function insertThemeConfig(content, slug2, label, group) {
2748
+ if (content.includes(`value: "${slug2}"`)) {
2334
2749
  return { updated: content, changed: false };
2335
2750
  }
2336
2751
  const escapedGroup = group.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -2363,7 +2778,7 @@ function insertThemeConfig(content, slug, label, group) {
2363
2778
  }
2364
2779
  let insertPos = closingBracket;
2365
2780
  for (const e of entries) {
2366
- if (slug < e.value) {
2781
+ if (slug2 < e.value) {
2367
2782
  insertPos = e.start;
2368
2783
  break;
2369
2784
  }
@@ -2372,7 +2787,7 @@ function insertThemeConfig(content, slug, label, group) {
2372
2787
  const lineContent = content.slice(prevNewline + 1, insertPos);
2373
2788
  const indentMatch = /^(\s*)/.exec(lineContent);
2374
2789
  const indent = indentMatch ? indentMatch[1] : " ";
2375
- const newEntry = `{ value: "${slug}", label: "${label}" }`;
2790
+ const newEntry = `{ value: "${slug2}", label: "${label}" }`;
2376
2791
  const insertion = entries.length === 0 ? `
2377
2792
  ${indent}${newEntry},
2378
2793
  ` : `${indent}${newEntry},
@@ -2384,7 +2799,7 @@ function themeRegisterCommand(file, cwd, options) {
2384
2799
  const filePath = resolve7(cwd, file);
2385
2800
  let yamlContent;
2386
2801
  try {
2387
- yamlContent = readFileSync10(filePath, "utf-8");
2802
+ yamlContent = readFileSync12(filePath, "utf-8");
2388
2803
  } catch {
2389
2804
  if (options.json) {
2390
2805
  console.log(JSON.stringify({ success: false, error: `Could not read file: ${filePath}` }));
@@ -2408,7 +2823,7 @@ function themeRegisterCommand(file, cwd, options) {
2408
2823
  process.exit(1);
2409
2824
  return;
2410
2825
  }
2411
- const slug = toSlug(data.config.name);
2826
+ const slug2 = toSlug(data.config.name);
2412
2827
  const label = toLabel(data.config.name);
2413
2828
  const adapterInput = {
2414
2829
  primitives: data.primitives,
@@ -2427,11 +2842,11 @@ function themeRegisterCommand(file, cwd, options) {
2427
2842
  process.exit(1);
2428
2843
  return;
2429
2844
  }
2430
- const docsAppDir = join9(repoRoot, "packages", "docs", "app");
2431
- const cssFilePath = join9(docsAppDir, `${slug}-theme.css`);
2432
- const globalsPath = join9(docsAppDir, "globals.css");
2433
- const themeConfigPath = join9(repoRoot, "packages", "docs", "lib", "theme-config.ts");
2434
- if (!existsSync7(docsAppDir)) {
2845
+ const docsAppDir = join12(repoRoot, "packages", "docs", "app");
2846
+ const cssFilePath = join12(docsAppDir, `${slug2}-theme.css`);
2847
+ const globalsPath = join12(docsAppDir, "globals.css");
2848
+ const themeConfigPath = join12(repoRoot, "packages", "docs", "lib", "theme-config.ts");
2849
+ if (!existsSync9(docsAppDir)) {
2435
2850
  const msg = `Docs app directory not found: ${docsAppDir}`;
2436
2851
  if (options.json) {
2437
2852
  console.log(JSON.stringify({ success: false, error: msg }));
@@ -2444,8 +2859,8 @@ function themeRegisterCommand(file, cwd, options) {
2444
2859
  let globalsContent = "";
2445
2860
  let themeConfigContent = "";
2446
2861
  try {
2447
- globalsContent = readFileSync10(globalsPath, "utf-8");
2448
- themeConfigContent = readFileSync10(themeConfigPath, "utf-8");
2862
+ globalsContent = readFileSync12(globalsPath, "utf-8");
2863
+ themeConfigContent = readFileSync12(themeConfigPath, "utf-8");
2449
2864
  } catch (err) {
2450
2865
  const msg = err instanceof Error ? err.message : "Could not read docs files";
2451
2866
  if (options.json) {
@@ -2456,12 +2871,12 @@ function themeRegisterCommand(file, cwd, options) {
2456
2871
  process.exit(1);
2457
2872
  return;
2458
2873
  }
2459
- const cssExists = existsSync7(cssFilePath);
2460
- const cssChanged = !cssExists || readFileSync10(cssFilePath, "utf-8") !== css;
2461
- const { updated: newGlobals, changed: globalsChanged } = insertGlobalsImport(globalsContent, slug);
2874
+ const cssExists = existsSync9(cssFilePath);
2875
+ const cssChanged = !cssExists || readFileSync12(cssFilePath, "utf-8") !== css;
2876
+ const { updated: newGlobals, changed: globalsChanged } = insertGlobalsImport(globalsContent, slug2);
2462
2877
  const { updated: newThemeConfig, changed: themeConfigChanged, error: configError } = insertThemeConfig(
2463
2878
  themeConfigContent,
2464
- slug,
2879
+ slug2,
2465
2880
  label,
2466
2881
  options.group
2467
2882
  );
@@ -2479,7 +2894,7 @@ function themeRegisterCommand(file, cwd, options) {
2479
2894
  console.log(JSON.stringify({
2480
2895
  success: true,
2481
2896
  dryRun: true,
2482
- slug,
2897
+ slug: slug2,
2483
2898
  label,
2484
2899
  group: options.group,
2485
2900
  changes: {
@@ -2490,7 +2905,7 @@ function themeRegisterCommand(file, cwd, options) {
2490
2905
  }));
2491
2906
  } else {
2492
2907
  logger.info("Dry run \u2014 no files written");
2493
- logger.item(`Theme: ${label} (${slug})`);
2908
+ logger.item(`Theme: ${label} (${slug2})`);
2494
2909
  logger.item(`Group: ${options.group}`);
2495
2910
  logger.item(`CSS file: ${cssFilePath} \u2014 ${cssChanged ? cssExists ? "update" : "create" : "no change"}`);
2496
2911
  logger.item(`globals.css: ${globalsChanged ? "add import" : "already registered"}`);
@@ -2501,13 +2916,13 @@ function themeRegisterCommand(file, cwd, options) {
2501
2916
  try {
2502
2917
  if (cssChanged) {
2503
2918
  mkdirSync4(docsAppDir, { recursive: true });
2504
- writeFileSync6(cssFilePath, css, "utf-8");
2919
+ writeFileSync7(cssFilePath, css, "utf-8");
2505
2920
  }
2506
2921
  if (globalsChanged) {
2507
- writeFileSync6(globalsPath, newGlobals, "utf-8");
2922
+ writeFileSync7(globalsPath, newGlobals, "utf-8");
2508
2923
  }
2509
2924
  if (themeConfigChanged) {
2510
- writeFileSync6(themeConfigPath, newThemeConfig, "utf-8");
2925
+ writeFileSync7(themeConfigPath, newThemeConfig, "utf-8");
2511
2926
  }
2512
2927
  } catch (err) {
2513
2928
  const msg = err instanceof Error ? err.message : "Write failed";
@@ -2522,14 +2937,14 @@ function themeRegisterCommand(file, cwd, options) {
2522
2937
  if (options.json) {
2523
2938
  console.log(JSON.stringify({
2524
2939
  success: true,
2525
- slug,
2940
+ slug: slug2,
2526
2941
  label,
2527
2942
  group: options.group,
2528
2943
  files: { css: cssFilePath, globals: globalsPath, themeConfig: themeConfigPath },
2529
2944
  changes: { cssFile: cssChanged, globalsCSS: globalsChanged, themeConfig: themeConfigChanged }
2530
2945
  }));
2531
2946
  } else {
2532
- logger.success(`Theme registered: ${label} (${slug})`);
2947
+ logger.success(`Theme registered: ${label} (${slug2})`);
2533
2948
  logger.item(`Group: ${options.group}`);
2534
2949
  if (cssChanged) logger.item(`CSS: ${cssFilePath}`);
2535
2950
  if (globalsChanged) logger.item(`globals.css updated`);
@@ -2541,28 +2956,28 @@ function themeRegisterCommand(file, cwd, options) {
2541
2956
  }
2542
2957
 
2543
2958
  // src/commands/theme-unregister.ts
2544
- import { readFileSync as readFileSync11, writeFileSync as writeFileSync7, existsSync as existsSync8, unlinkSync } from "fs";
2545
- import { join as join10 } from "path";
2546
- function removeGlobalsImport(content, slug) {
2547
- const importLine = `@import './${slug}-theme.css';`;
2959
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync10, unlinkSync } from "fs";
2960
+ import { join as join13 } from "path";
2961
+ function removeGlobalsImport(content, slug2) {
2962
+ const importLine = `@import './${slug2}-theme.css';`;
2548
2963
  if (!content.includes(importLine)) {
2549
2964
  return { updated: content, changed: false };
2550
2965
  }
2551
2966
  const updated = content.split("\n").filter((line) => line !== importLine).join("\n");
2552
2967
  return { updated, changed: true };
2553
2968
  }
2554
- function removeThemeConfigEntry(content, slug) {
2555
- if (!content.includes(`value: "${slug}"`)) {
2969
+ function removeThemeConfigEntry(content, slug2) {
2970
+ if (!content.includes(`value: "${slug2}"`)) {
2556
2971
  return { updated: content, changed: false };
2557
2972
  }
2558
2973
  const entryPattern = new RegExp(
2559
- `\\s*\\{\\s*value:\\s*"${slug.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"[^}]*\\},?`,
2974
+ `\\s*\\{\\s*value:\\s*"${slug2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}"[^}]*\\},?`,
2560
2975
  "g"
2561
2976
  );
2562
2977
  const updated = content.replace(entryPattern, "");
2563
2978
  return { updated, changed: true };
2564
2979
  }
2565
- function themeUnregisterCommand(slug, cwd, options) {
2980
+ function themeUnregisterCommand(slug2, cwd, options) {
2566
2981
  const repoRoot = findRepoRoot(cwd);
2567
2982
  if (!repoRoot) {
2568
2983
  const msg = "Could not locate repo root (packages/docs/ not found). Run from within the visor repo.";
@@ -2574,11 +2989,11 @@ function themeUnregisterCommand(slug, cwd, options) {
2574
2989
  process.exit(1);
2575
2990
  return;
2576
2991
  }
2577
- const docsAppDir = join10(repoRoot, "packages", "docs", "app");
2578
- const cssFilePath = join10(docsAppDir, `${slug}-theme.css`);
2579
- const globalsPath = join10(docsAppDir, "globals.css");
2580
- const themeConfigPath = join10(repoRoot, "packages", "docs", "lib", "theme-config.ts");
2581
- if (!existsSync8(docsAppDir)) {
2992
+ const docsAppDir = join13(repoRoot, "packages", "docs", "app");
2993
+ const cssFilePath = join13(docsAppDir, `${slug2}-theme.css`);
2994
+ const globalsPath = join13(docsAppDir, "globals.css");
2995
+ const themeConfigPath = join13(repoRoot, "packages", "docs", "lib", "theme-config.ts");
2996
+ if (!existsSync10(docsAppDir)) {
2582
2997
  const msg = `Docs app directory not found: ${docsAppDir}`;
2583
2998
  if (options.json) {
2584
2999
  console.log(JSON.stringify({ success: false, error: msg }));
@@ -2591,8 +3006,8 @@ function themeUnregisterCommand(slug, cwd, options) {
2591
3006
  let globalsContent = "";
2592
3007
  let themeConfigContent = "";
2593
3008
  try {
2594
- globalsContent = readFileSync11(globalsPath, "utf-8");
2595
- themeConfigContent = readFileSync11(themeConfigPath, "utf-8");
3009
+ globalsContent = readFileSync13(globalsPath, "utf-8");
3010
+ themeConfigContent = readFileSync13(themeConfigPath, "utf-8");
2596
3011
  } catch (err) {
2597
3012
  const msg = err instanceof Error ? err.message : "Could not read docs files";
2598
3013
  if (options.json) {
@@ -2603,21 +3018,21 @@ function themeUnregisterCommand(slug, cwd, options) {
2603
3018
  process.exit(1);
2604
3019
  return;
2605
3020
  }
2606
- const cssExists = existsSync8(cssFilePath);
2607
- const { updated: newGlobals, changed: globalsChanged } = removeGlobalsImport(globalsContent, slug);
2608
- const { updated: newThemeConfig, changed: themeConfigChanged } = removeThemeConfigEntry(themeConfigContent, slug);
3021
+ const cssExists = existsSync10(cssFilePath);
3022
+ const { updated: newGlobals, changed: globalsChanged } = removeGlobalsImport(globalsContent, slug2);
3023
+ const { updated: newThemeConfig, changed: themeConfigChanged } = removeThemeConfigEntry(themeConfigContent, slug2);
2609
3024
  if (!cssExists && !globalsChanged && !themeConfigChanged) {
2610
3025
  if (options.json) {
2611
- console.log(JSON.stringify({ success: true, slug, changes: { cssFile: false, globalsCSS: false, themeConfig: false } }));
3026
+ console.log(JSON.stringify({ success: true, slug: slug2, changes: { cssFile: false, globalsCSS: false, themeConfig: false } }));
2612
3027
  } else {
2613
- logger.info(`Theme "${slug}" is not registered \u2014 nothing to remove.`);
3028
+ logger.info(`Theme "${slug2}" is not registered \u2014 nothing to remove.`);
2614
3029
  }
2615
3030
  return;
2616
3031
  }
2617
3032
  try {
2618
3033
  if (cssExists) unlinkSync(cssFilePath);
2619
- if (globalsChanged) writeFileSync7(globalsPath, newGlobals, "utf-8");
2620
- if (themeConfigChanged) writeFileSync7(themeConfigPath, newThemeConfig, "utf-8");
3034
+ if (globalsChanged) writeFileSync8(globalsPath, newGlobals, "utf-8");
3035
+ if (themeConfigChanged) writeFileSync8(themeConfigPath, newThemeConfig, "utf-8");
2621
3036
  } catch (err) {
2622
3037
  const msg = err instanceof Error ? err.message : "Write failed";
2623
3038
  if (options.json) {
@@ -2631,11 +3046,11 @@ function themeUnregisterCommand(slug, cwd, options) {
2631
3046
  if (options.json) {
2632
3047
  console.log(JSON.stringify({
2633
3048
  success: true,
2634
- slug,
3049
+ slug: slug2,
2635
3050
  changes: { cssFile: cssExists, globalsCSS: globalsChanged, themeConfig: themeConfigChanged }
2636
3051
  }));
2637
3052
  } else {
2638
- logger.success(`Theme unregistered: ${slug}`);
3053
+ logger.success(`Theme unregistered: ${slug2}`);
2639
3054
  if (cssExists) logger.item(`CSS file removed: ${cssFilePath}`);
2640
3055
  if (globalsChanged) logger.item(`globals.css updated`);
2641
3056
  if (themeConfigChanged) logger.item(`theme-config.ts updated`);
@@ -2644,15 +3059,15 @@ function themeUnregisterCommand(slug, cwd, options) {
2644
3059
 
2645
3060
  // src/commands/theme-sync.ts
2646
3061
  import {
2647
- readFileSync as readFileSync12,
2648
- writeFileSync as writeFileSync8,
3062
+ readFileSync as readFileSync14,
3063
+ writeFileSync as writeFileSync9,
2649
3064
  mkdirSync as mkdirSync5,
2650
- existsSync as existsSync9,
2651
- readdirSync as readdirSync3,
3065
+ existsSync as existsSync11,
3066
+ readdirSync as readdirSync4,
2652
3067
  unlinkSync as unlinkSync2,
2653
3068
  copyFileSync
2654
3069
  } from "fs";
2655
- import { join as join11, basename as basename2 } from "path";
3070
+ import { join as join14, basename as basename2 } from "path";
2656
3071
  import { parse as parseYaml2 } from "yaml";
2657
3072
  import { generateThemeData as generateThemeData4 } from "@loworbitstudio/visor-theme-engine";
2658
3073
  import { docsAdapter as docsAdapter3 } from "@loworbitstudio/visor-theme-engine/adapters";
@@ -2666,8 +3081,8 @@ var CUSTOM_OVERLAY_CSS_PATH = "packages/docs/app/custom-themes.generated.css";
2666
3081
  var CUSTOM_OVERLAY_TS_PATH = "packages/docs/lib/theme-config.custom.generated.ts";
2667
3082
  var CUSTOM_OVERLAY_IMPORT_LINE = "@import './custom-themes.generated.css';";
2668
3083
  function scanThemeDir(dir) {
2669
- if (!existsSync9(dir)) return [];
2670
- return readdirSync3(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join11(dir, f));
3084
+ if (!existsSync11(dir)) return [];
3085
+ return readdirSync4(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join14(dir, f));
2671
3086
  }
2672
3087
  function extractGroup(yamlContent) {
2673
3088
  const parsed = parseYaml2(yamlContent);
@@ -2693,7 +3108,7 @@ function sortGroups(groups) {
2693
3108
  });
2694
3109
  }
2695
3110
  function updateGlobalsImports(content, stockSlugs) {
2696
- const importLines = [...stockSlugs].sort().map((slug) => `@import './${slug}-theme.css';`).join("\n");
3111
+ const importLines = [...stockSlugs].sort().map((slug2) => `@import './${slug2}-theme.css';`).join("\n");
2697
3112
  const newBlock = `${GLOBALS_BEGIN_MARKER}
2698
3113
  ${importLines}
2699
3114
  ${GLOBALS_END_MARKER}`;
@@ -2827,7 +3242,7 @@ ${groupsTs}
2827
3242
  `;
2828
3243
  }
2829
3244
  function updateGitignoreBlock(content, customSlugs) {
2830
- const cssLines = customSlugs.sort().map((slug) => `packages/docs/app/${slug}-theme.css`).join("\n");
3245
+ const cssLines = customSlugs.sort().map((slug2) => `packages/docs/app/${slug2}-theme.css`).join("\n");
2831
3246
  const newBlock = `${GITIGNORE_BEGIN_MARKER}
2832
3247
  ${cssLines}
2833
3248
  ${GITIGNORE_END_MARKER}`;
@@ -2850,16 +3265,16 @@ function themeSyncCommand(cwd, options) {
2850
3265
  process.exit(1);
2851
3266
  return;
2852
3267
  }
2853
- const themesDir = join11(repoRoot, "themes");
2854
- const customThemesDir = join11(repoRoot, "custom-themes");
2855
- const docsAppDir = join11(repoRoot, "packages", "docs", "app");
2856
- const docsLibDir = join11(repoRoot, "packages", "docs", "lib");
2857
- const docsPublicThemesDir = join11(repoRoot, "packages", "docs", "public", "themes");
2858
- const themeConfigPath = join11(repoRoot, "packages", "docs", "lib", "theme-config.ts");
2859
- const globalsPath = join11(docsAppDir, "globals.css");
2860
- const gitignorePath = join11(repoRoot, ".gitignore");
2861
- const customOverlayCssPath = join11(repoRoot, CUSTOM_OVERLAY_CSS_PATH);
2862
- const customOverlayTsPath = join11(repoRoot, CUSTOM_OVERLAY_TS_PATH);
3268
+ const themesDir = join14(repoRoot, "themes");
3269
+ const customThemesDir = join14(repoRoot, "custom-themes");
3270
+ const docsAppDir = join14(repoRoot, "packages", "docs", "app");
3271
+ const docsLibDir = join14(repoRoot, "packages", "docs", "lib");
3272
+ const docsPublicThemesDir = join14(repoRoot, "packages", "docs", "public", "themes");
3273
+ const themeConfigPath = join14(repoRoot, "packages", "docs", "lib", "theme-config.ts");
3274
+ const globalsPath = join14(docsAppDir, "globals.css");
3275
+ const gitignorePath = join14(repoRoot, ".gitignore");
3276
+ const customOverlayCssPath = join14(repoRoot, CUSTOM_OVERLAY_CSS_PATH);
3277
+ const customOverlayTsPath = join14(repoRoot, CUSTOM_OVERLAY_TS_PATH);
2863
3278
  const stockFiles = scanThemeDir(themesDir);
2864
3279
  const customFiles = scanThemeDir(customThemesDir);
2865
3280
  if (stockFiles.length === 0 && customFiles.length === 0) {
@@ -2876,7 +3291,7 @@ function themeSyncCommand(cwd, options) {
2876
3291
  const processFile = (filePath, isCustom) => {
2877
3292
  let yamlContent;
2878
3293
  try {
2879
- yamlContent = readFileSync12(filePath, "utf-8");
3294
+ yamlContent = readFileSync14(filePath, "utf-8");
2880
3295
  } catch {
2881
3296
  errors.push(`Could not read: ${filePath}`);
2882
3297
  return;
@@ -2888,13 +3303,13 @@ function themeSyncCommand(cwd, options) {
2888
3303
  errors.push(`Failed to parse ${basename2(filePath)}: ${err instanceof Error ? err.message : "Unknown error"}`);
2889
3304
  return;
2890
3305
  }
2891
- const slug = toSlug(data.config.name);
3306
+ const slug2 = toSlug(data.config.name);
2892
3307
  const label = extractLabel(yamlContent) ?? toLabel(data.config.name);
2893
3308
  const group = extractGroup(yamlContent) ?? (isCustom ? "Custom" : "Visor");
2894
3309
  const defaultMode = extractDefaultMode(yamlContent);
2895
3310
  const css = docsAdapter3({ primitives: data.primitives, tokens: data.tokens, config: data.config });
2896
3311
  const yamlFilename = basename2(filePath).replace(/\.visor\.yaml$/, "");
2897
- manifest.push({ slug, label, group, defaultMode, css, yamlFilename, isCustom });
3312
+ manifest.push({ slug: slug2, label, group, defaultMode, css, yamlFilename, isCustom });
2898
3313
  };
2899
3314
  for (const f of stockFiles) processFile(f, false);
2900
3315
  for (const f of customFiles) processFile(f, true);
@@ -2916,9 +3331,9 @@ function themeSyncCommand(cwd, options) {
2916
3331
  let themeConfigContent;
2917
3332
  let gitignoreContent;
2918
3333
  try {
2919
- globalsContent = readFileSync12(globalsPath, "utf-8");
2920
- themeConfigContent = readFileSync12(themeConfigPath, "utf-8");
2921
- gitignoreContent = existsSync9(gitignorePath) ? readFileSync12(gitignorePath, "utf-8") : "";
3334
+ globalsContent = readFileSync14(globalsPath, "utf-8");
3335
+ themeConfigContent = readFileSync14(themeConfigPath, "utf-8");
3336
+ gitignoreContent = existsSync11(gitignorePath) ? readFileSync14(gitignorePath, "utf-8") : "";
2922
3337
  } catch (err) {
2923
3338
  const msg = err instanceof Error ? err.message : "Could not read docs files";
2924
3339
  if (options.json) {
@@ -2934,12 +3349,12 @@ function themeSyncCommand(cwd, options) {
2934
3349
  const newGitignore = customSlugs.length > 0 ? updateGitignoreBlock(gitignoreContent, customSlugs) : gitignoreContent;
2935
3350
  const newCustomOverlayCss = generateCustomOverlayCss(customManifest);
2936
3351
  const newCustomOverlayTs = generateCustomOverlayTs(customManifest);
2937
- const existingCssFiles = existsSync9(docsAppDir) ? readdirSync3(docsAppDir).filter(
3352
+ const existingCssFiles = existsSync11(docsAppDir) ? readdirSync4(docsAppDir).filter(
2938
3353
  (f) => f.endsWith("-theme.css") && f !== "custom-themes.generated.css"
2939
3354
  ) : [];
2940
3355
  const newCssSet = new Set(allSlugs.map((s) => `${s}-theme.css`));
2941
3356
  const staleCssFiles = existingCssFiles.filter((f) => !newCssSet.has(f));
2942
- const existingPublicYamls = existsSync9(docsPublicThemesDir) ? readdirSync3(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
3357
+ const existingPublicYamls = existsSync11(docsPublicThemesDir) ? readdirSync4(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
2943
3358
  const newPublicYamlSet = new Set(manifest.map((e) => `${e.yamlFilename}.visor.yaml`));
2944
3359
  const stalePublicYamls = existingPublicYamls.filter((f) => !newPublicYamlSet.has(f));
2945
3360
  if (options.dryRun) {
@@ -2971,25 +3386,25 @@ function themeSyncCommand(cwd, options) {
2971
3386
  mkdirSync5(docsLibDir, { recursive: true });
2972
3387
  mkdirSync5(docsPublicThemesDir, { recursive: true });
2973
3388
  for (const entry of manifest) {
2974
- writeFileSync8(join11(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
3389
+ writeFileSync9(join14(docsAppDir, `${entry.slug}-theme.css`), entry.css, "utf-8");
2975
3390
  }
2976
3391
  for (const stale of staleCssFiles) {
2977
- unlinkSync2(join11(docsAppDir, stale));
3392
+ unlinkSync2(join14(docsAppDir, stale));
2978
3393
  }
2979
- writeFileSync8(customOverlayCssPath, newCustomOverlayCss, "utf-8");
2980
- writeFileSync8(customOverlayTsPath, newCustomOverlayTs, "utf-8");
2981
- writeFileSync8(themeConfigPath, newThemeConfig, "utf-8");
2982
- writeFileSync8(globalsPath, newGlobals, "utf-8");
2983
- if (existsSync9(gitignorePath)) {
2984
- writeFileSync8(gitignorePath, newGitignore, "utf-8");
3394
+ writeFileSync9(customOverlayCssPath, newCustomOverlayCss, "utf-8");
3395
+ writeFileSync9(customOverlayTsPath, newCustomOverlayTs, "utf-8");
3396
+ writeFileSync9(themeConfigPath, newThemeConfig, "utf-8");
3397
+ writeFileSync9(globalsPath, newGlobals, "utf-8");
3398
+ if (existsSync11(gitignorePath)) {
3399
+ writeFileSync9(gitignorePath, newGitignore, "utf-8");
2985
3400
  }
2986
3401
  const allSourceFiles = [...stockFiles, ...customFiles];
2987
3402
  for (const srcFile of allSourceFiles) {
2988
3403
  const filename = basename2(srcFile);
2989
- copyFileSync(srcFile, join11(docsPublicThemesDir, filename));
3404
+ copyFileSync(srcFile, join14(docsPublicThemesDir, filename));
2990
3405
  }
2991
3406
  for (const stale of stalePublicYamls) {
2992
- unlinkSync2(join11(docsPublicThemesDir, stale));
3407
+ unlinkSync2(join14(docsPublicThemesDir, stale));
2993
3408
  }
2994
3409
  } catch (err) {
2995
3410
  const msg = err instanceof Error ? err.message : "Write failed";
@@ -3023,12 +3438,304 @@ function themeSyncCommand(cwd, options) {
3023
3438
  }
3024
3439
  }
3025
3440
 
3441
+ // src/commands/theme-batch-apply-flutter.ts
3442
+ import {
3443
+ readFileSync as readFileSync15,
3444
+ writeFileSync as writeFileSync10,
3445
+ mkdirSync as mkdirSync6,
3446
+ existsSync as existsSync12,
3447
+ readdirSync as readdirSync5,
3448
+ rmSync
3449
+ } from "fs";
3450
+ import { join as join15, basename as basename3, dirname as dirname6 } from "path";
3451
+ import { generateThemeData as generateThemeData5 } from "@loworbitstudio/visor-theme-engine";
3452
+ import { flutterAdapter as flutterAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
3453
+ function scanThemeDir2(dir) {
3454
+ if (!existsSync12(dir)) return [];
3455
+ return readdirSync5(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join15(dir, f)).sort();
3456
+ }
3457
+ function slugToCamel(slug2) {
3458
+ return slug2.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
3459
+ }
3460
+ function mapAdapterPath(relPath) {
3461
+ if (relPath === "pubspec.yaml") return null;
3462
+ if (relPath === "lib/ui.dart") return null;
3463
+ if (relPath.startsWith("lib/src/")) {
3464
+ return relPath.slice("lib/src/".length);
3465
+ }
3466
+ return null;
3467
+ }
3468
+ function emitVisorThemesPubspec() {
3469
+ return [
3470
+ `name: visor_themes`,
3471
+ `description: All Visor-generated Flutter ThemeData packages \u2014 4 stock + 7 custom themes. GENERATED \u2014 regenerate with \`npm run themes:apply-flutter\`.`,
3472
+ `version: 0.1.0+1`,
3473
+ `publish_to: none`,
3474
+ ``,
3475
+ `environment:`,
3476
+ ` sdk: ^3.5.0`,
3477
+ ` flutter: ^3.24.0`,
3478
+ ``,
3479
+ `dependencies:`,
3480
+ ` flutter:`,
3481
+ ` sdk: flutter`,
3482
+ ` visor_core: ^0.1.0`,
3483
+ ``,
3484
+ `dev_dependencies:`,
3485
+ ` flutter_test:`,
3486
+ ` sdk: flutter`,
3487
+ ` flutter_lints: ^5.0.0`,
3488
+ ``,
3489
+ `flutter:`,
3490
+ ` uses-material-design: true`,
3491
+ ``
3492
+ ].join("\n");
3493
+ }
3494
+ function emitVisorThemesPubspecOverrides() {
3495
+ return [
3496
+ `# Path overrides for in-monorepo development.`,
3497
+ `# visor_core is not yet published to pub.dev; this forces the local copy.`,
3498
+ `dependency_overrides:`,
3499
+ ` visor_core:`,
3500
+ ` path: ../visor-flutter`,
3501
+ ``
3502
+ ].join("\n");
3503
+ }
3504
+ function emitAnalysisOptions() {
3505
+ return [
3506
+ `include: package:flutter_lints/flutter.yaml`,
3507
+ ``,
3508
+ `linter:`,
3509
+ ` rules:`,
3510
+ ` - avoid_print`,
3511
+ ``
3512
+ ].join("\n");
3513
+ }
3514
+ function slugToDartPrefix(slug2) {
3515
+ return slug2.replace(/-/g, "_") + "_t";
3516
+ }
3517
+ function emitMetaBarrel(slugs) {
3518
+ const lines = [
3519
+ `// GENERATED BY visor \u2014 DO NOT EDIT.`,
3520
+ `// Regenerate with \`npm run themes:apply-flutter\`.`,
3521
+ `//`,
3522
+ `// Aggregates Dart ThemeData for all Visor themes (4 stock + 7 custom).`,
3523
+ `// Access themes via [VisorThemes], e.g. VisorThemes.blackout.light`,
3524
+ ``,
3525
+ `import 'package:flutter/material.dart';`,
3526
+ ``,
3527
+ `// Re-export visor_core so consumers access VisorColorsData etc. with`,
3528
+ `// a single import of this package.`,
3529
+ `export 'package:visor_core/visor_core.dart';`,
3530
+ ``
3531
+ ];
3532
+ for (const slug2 of slugs) {
3533
+ const prefix = slugToDartPrefix(slug2);
3534
+ lines.push(`import 'src/${slug2}/theme/visor_theme.dart' as ${prefix};`);
3535
+ }
3536
+ lines.push(``);
3537
+ lines.push(`/// A light/dark [ThemeData] pair for a single Visor theme.`);
3538
+ lines.push(`class VisorThemePair {`);
3539
+ lines.push(` final ThemeData light;`);
3540
+ lines.push(` final ThemeData dark;`);
3541
+ lines.push(` const VisorThemePair({required this.light, required this.dark});`);
3542
+ lines.push(`}`);
3543
+ lines.push(``);
3544
+ lines.push(`/// Static access to all Visor-generated Flutter themes.`);
3545
+ lines.push(`///`);
3546
+ lines.push(`/// Usage:`);
3547
+ lines.push(`/// \`\`\`dart`);
3548
+ lines.push(`/// MaterialApp(`);
3549
+ lines.push(`/// theme: VisorThemes.blackout.light,`);
3550
+ lines.push(`/// darkTheme: VisorThemes.blackout.dark,`);
3551
+ lines.push(`/// );`);
3552
+ lines.push(`/// \`\`\``);
3553
+ lines.push(`sealed class VisorThemes {`);
3554
+ for (const slug2 of slugs) {
3555
+ const camel = slugToCamel(slug2);
3556
+ const prefix = slugToDartPrefix(slug2);
3557
+ lines.push(` static VisorThemePair get ${camel} => VisorThemePair(`);
3558
+ lines.push(` light: ${prefix}.VisorAppTheme.light,`);
3559
+ lines.push(` dark: ${prefix}.VisorAppTheme.dark,`);
3560
+ lines.push(` );`);
3561
+ }
3562
+ lines.push(`}`);
3563
+ lines.push(``);
3564
+ return lines.join("\n");
3565
+ }
3566
+ function emitGitignore() {
3567
+ return [
3568
+ `.dart_tool/`,
3569
+ `.packages`,
3570
+ `build/`,
3571
+ `pubspec.lock`,
3572
+ `*.g.dart`,
3573
+ ``
3574
+ ].join("\n");
3575
+ }
3576
+ function themeBatchApplyFlutterCommand(cwd, options) {
3577
+ const repoRoot = findRepoRoot(cwd);
3578
+ if (!repoRoot) {
3579
+ const msg = "Could not locate repo root (packages/docs/ not found). Run from within the visor repo.";
3580
+ if (options.json) {
3581
+ console.log(JSON.stringify({ success: false, error: msg }));
3582
+ } else {
3583
+ logger.error(msg);
3584
+ }
3585
+ process.exit(1);
3586
+ return;
3587
+ }
3588
+ const themesDir = join15(repoRoot, "themes");
3589
+ const customThemesDir = join15(repoRoot, "custom-themes");
3590
+ const outputDir = join15(repoRoot, "packages", "visor_themes");
3591
+ const stockFiles = scanThemeDir2(themesDir);
3592
+ const customFiles = scanThemeDir2(customThemesDir);
3593
+ const allFiles = [...stockFiles, ...customFiles];
3594
+ if (allFiles.length === 0) {
3595
+ const msg = `No .visor.yaml files found in themes/ or custom-themes/. Nothing to generate.`;
3596
+ if (options.json) {
3597
+ console.log(JSON.stringify({ success: false, error: msg }));
3598
+ } else {
3599
+ logger.warn(msg);
3600
+ }
3601
+ return;
3602
+ }
3603
+ if (!options.json) {
3604
+ logger.info(`Found ${allFiles.length} theme YAML files (${stockFiles.length} stock, ${customFiles.length} custom)`);
3605
+ }
3606
+ const processed = [];
3607
+ const errors = [];
3608
+ for (const filePath of allFiles) {
3609
+ let yamlContent;
3610
+ try {
3611
+ yamlContent = readFileSync15(filePath, "utf-8");
3612
+ } catch {
3613
+ errors.push(`Could not read: ${filePath}`);
3614
+ continue;
3615
+ }
3616
+ let data;
3617
+ try {
3618
+ data = generateThemeData5(yamlContent);
3619
+ } catch (err) {
3620
+ errors.push(
3621
+ `Failed to parse ${basename3(filePath)}: ${err instanceof Error ? err.message : "Unknown error"}`
3622
+ );
3623
+ continue;
3624
+ }
3625
+ const slug2 = data.config.name.toLowerCase().replace(/\s+/g, "-");
3626
+ const camel = slugToCamel(slug2);
3627
+ const flutterOptions = {
3628
+ packageName: `visor_themes_${slug2.replace(/-/g, "_")}`,
3629
+ themeClassName: "VisorAppTheme"
3630
+ };
3631
+ let fileMap;
3632
+ try {
3633
+ fileMap = flutterAdapter2(
3634
+ {
3635
+ primitives: data.primitives,
3636
+ tokens: data.tokens,
3637
+ config: data.config
3638
+ },
3639
+ flutterOptions
3640
+ );
3641
+ } catch (err) {
3642
+ errors.push(
3643
+ `Failed flutter adapter for ${slug2}: ${err instanceof Error ? err.message : "Unknown error"}`
3644
+ );
3645
+ continue;
3646
+ }
3647
+ const primsAsUnknown = data.primitives;
3648
+ const primitivesMap = primsAsUnknown;
3649
+ const primaryHex = typeof primitivesMap?.primary500 === "string" ? primitivesMap.primary500 : "#000000";
3650
+ const tokenFiles = {};
3651
+ for (const [relPath, content] of Object.entries(fileMap.files)) {
3652
+ const mapped = mapAdapterPath(relPath);
3653
+ if (mapped !== null) {
3654
+ tokenFiles[mapped] = content;
3655
+ }
3656
+ }
3657
+ if (tokenFiles["theme/visor_theme.dart"]) {
3658
+ tokenFiles["theme/visor_theme.dart"] = tokenFiles["theme/visor_theme.dart"].replace(/import '\.\.\/colors\/visor_colors\.dart';/g, "import '../colors/visor_colors.dart';").replace(/import '\.\.\/typography\/visor_text_styles\.dart';/g, "import '../typography/visor_text_styles.dart';").replace(/import '\.\.\/spacing\/visor_spacing\.dart';/g, "import '../spacing/visor_spacing.dart';").replace(/import '\.\.\/radius\/visor_radius\.dart';/g, "import '../radius/visor_radius.dart';").replace(/import '\.\.\/shadows\/visor_shadows\.dart';/g, "import '../shadows/visor_shadows.dart';").replace(/import '\.\.\/strokes\/visor_stroke_widths\.dart';/g, "import '../strokes/visor_stroke_widths.dart';").replace(/import '\.\.\/opacity\/visor_opacity\.dart';/g, "import '../opacity/visor_opacity.dart';").replace(/import '\.\.\/motion\/visor_motion\.dart';/g, "import '../motion/visor_motion.dart';");
3659
+ }
3660
+ processed.push({ slug: slug2, camel, primaryHex, tokenFiles });
3661
+ }
3662
+ if (errors.length > 0) {
3663
+ if (options.json) {
3664
+ console.log(JSON.stringify({ success: false, errors }));
3665
+ } else {
3666
+ errors.forEach((e) => logger.error(e));
3667
+ }
3668
+ process.exit(1);
3669
+ return;
3670
+ }
3671
+ if (options.dryRun) {
3672
+ if (!options.json) {
3673
+ logger.info(`[dry-run] Would generate ${processed.length} theme packages in ${outputDir}`);
3674
+ for (const { slug: slug2 } of processed) {
3675
+ logger.item(` packages/visor_themes/lib/src/${slug2}/`);
3676
+ }
3677
+ } else {
3678
+ console.log(
3679
+ JSON.stringify({
3680
+ success: true,
3681
+ dryRun: true,
3682
+ themes: processed.map((p) => p.slug),
3683
+ outputDir
3684
+ })
3685
+ );
3686
+ }
3687
+ return;
3688
+ }
3689
+ const slugs = processed.map((p) => p.slug);
3690
+ const libSrcDir = join15(outputDir, "lib", "src");
3691
+ if (existsSync12(libSrcDir)) {
3692
+ rmSync(libSrcDir, { recursive: true, force: true });
3693
+ }
3694
+ const packageFiles = {
3695
+ "pubspec.yaml": emitVisorThemesPubspec(),
3696
+ "pubspec_overrides.yaml": emitVisorThemesPubspecOverrides(),
3697
+ "analysis_options.yaml": emitAnalysisOptions(),
3698
+ ".gitignore": emitGitignore(),
3699
+ "lib/visor_themes.dart": emitMetaBarrel(slugs)
3700
+ };
3701
+ let totalFiles = 0;
3702
+ for (const [relPath, content] of Object.entries(packageFiles)) {
3703
+ const absPath = join15(outputDir, relPath);
3704
+ mkdirSync6(dirname6(absPath), { recursive: true });
3705
+ writeFileSync10(absPath, content, "utf-8");
3706
+ totalFiles++;
3707
+ }
3708
+ for (const { slug: slug2, tokenFiles } of processed) {
3709
+ const themeBaseDir = join15(outputDir, "lib", "src", slug2);
3710
+ for (const [relPath, content] of Object.entries(tokenFiles)) {
3711
+ const absPath = join15(themeBaseDir, relPath);
3712
+ mkdirSync6(dirname6(absPath), { recursive: true });
3713
+ writeFileSync10(absPath, content, "utf-8");
3714
+ totalFiles++;
3715
+ }
3716
+ }
3717
+ if (options.json) {
3718
+ console.log(
3719
+ JSON.stringify({
3720
+ success: true,
3721
+ outputDir,
3722
+ themes: slugs,
3723
+ totalFiles
3724
+ })
3725
+ );
3726
+ } else {
3727
+ logger.success(`Flutter theme package generated: ${outputDir}`);
3728
+ logger.item(`Themes: ${slugs.join(", ")}`);
3729
+ logger.item(`Total files written: ${totalFiles}`);
3730
+ }
3731
+ }
3732
+
3026
3733
  // src/commands/fonts-add.ts
3027
- import { existsSync as existsSync10, statSync as statSync3, readdirSync as readdirSync4, readFileSync as readFileSync13 } from "fs";
3028
- import { resolve as resolve8, basename as basename3, extname as extname3 } from "path";
3734
+ import { existsSync as existsSync13, statSync as statSync4, readdirSync as readdirSync6, readFileSync as readFileSync16 } from "fs";
3735
+ import { resolve as resolve8, basename as basename4, extname as extname3 } from "path";
3029
3736
  import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
3030
3737
  function deriveFamilySlug(filename) {
3031
- const name = basename3(filename, extname3(filename));
3738
+ const name = basename4(filename, extname3(filename));
3032
3739
  const WEIGHT_STYLE_SUFFIXES = /* @__PURE__ */ new Set([
3033
3740
  "thin",
3034
3741
  "hairline",
@@ -3070,26 +3777,26 @@ function deriveFamilySlug(filename) {
3070
3777
  }
3071
3778
  function collectWoff2Files(inputPath) {
3072
3779
  const resolved = resolve8(inputPath);
3073
- if (!existsSync10(resolved)) {
3780
+ if (!existsSync13(resolved)) {
3074
3781
  throw new Error(`Path not found: ${resolved}`);
3075
3782
  }
3076
- const stat = statSync3(resolved);
3783
+ const stat = statSync4(resolved);
3077
3784
  if (stat.isFile()) {
3078
3785
  if (extname3(resolved).toLowerCase() !== ".woff2") {
3079
3786
  throw new Error(
3080
- `Invalid file format: ${basename3(resolved)}. Only .woff2 files are accepted.`
3787
+ `Invalid file format: ${basename4(resolved)}. Only .woff2 files are accepted.`
3081
3788
  );
3082
3789
  }
3083
3790
  return [resolved];
3084
3791
  }
3085
3792
  if (stat.isDirectory()) {
3086
- const files = readdirSync4(resolved).filter((f) => extname3(f).toLowerCase() === ".woff2").map((f) => resolve8(resolved, f));
3793
+ const files = readdirSync6(resolved).filter((f) => extname3(f).toLowerCase() === ".woff2").map((f) => resolve8(resolved, f));
3087
3794
  if (files.length === 0) {
3088
3795
  throw new Error(
3089
3796
  `No .woff2 files found in directory: ${resolved}`
3090
3797
  );
3091
3798
  }
3092
- const nonWoff2Fonts = readdirSync4(resolved).filter((f) => {
3799
+ const nonWoff2Fonts = readdirSync6(resolved).filter((f) => {
3093
3800
  const ext = extname3(f).toLowerCase();
3094
3801
  return [".ttf", ".otf", ".woff", ".eot"].includes(ext);
3095
3802
  });
@@ -3099,10 +3806,10 @@ function collectWoff2Files(inputPath) {
3099
3806
  }
3100
3807
  function getNonWoff2Fonts(inputPath) {
3101
3808
  const resolved = resolve8(inputPath);
3102
- if (!existsSync10(resolved) || !statSync3(resolved).isDirectory()) {
3809
+ if (!existsSync13(resolved) || !statSync4(resolved).isDirectory()) {
3103
3810
  return [];
3104
3811
  }
3105
- return readdirSync4(resolved).filter((f) => {
3812
+ return readdirSync6(resolved).filter((f) => {
3106
3813
  const ext = extname3(f).toLowerCase();
3107
3814
  return [".ttf", ".otf", ".woff", ".eot"].includes(ext);
3108
3815
  });
@@ -3136,7 +3843,7 @@ function createR2Client(config) {
3136
3843
  });
3137
3844
  }
3138
3845
  async function uploadFile(client, bucket, key, filePath) {
3139
- const body = readFileSync13(filePath);
3846
+ const body = readFileSync16(filePath);
3140
3847
  await client.send(
3141
3848
  new PutObjectCommand({
3142
3849
  Bucket: bucket,
@@ -3151,9 +3858,9 @@ async function fontsAddCommand(inputPath, options) {
3151
3858
  try {
3152
3859
  const r2Config = getR2Config();
3153
3860
  const files = collectWoff2Files(inputPath);
3154
- const familySlug = options.family ?? deriveFamilySlug(basename3(files[0]));
3861
+ const familySlug = options.family ?? deriveFamilySlug(basename4(files[0]));
3155
3862
  const resolved = resolve8(inputPath);
3156
- const nonWoff2 = statSync3(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
3863
+ const nonWoff2 = statSync4(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
3157
3864
  if (!json) {
3158
3865
  logger.heading("Visor Font Upload");
3159
3866
  logger.info(`Organization: ${org}`);
@@ -3171,13 +3878,13 @@ async function fontsAddCommand(inputPath, options) {
3171
3878
  const bucket = "visor-fonts";
3172
3879
  const results = [];
3173
3880
  for (const filePath of files) {
3174
- const filename = basename3(filePath);
3881
+ const filename = basename4(filePath);
3175
3882
  const key = buildS3Key(org, familySlug, filename);
3176
3883
  if (!json) {
3177
3884
  logger.info(`Uploading ${filename}...`);
3178
3885
  }
3179
3886
  await uploadFile(client, bucket, key, filePath);
3180
- const size = statSync3(filePath).size;
3887
+ const size = statSync4(filePath).size;
3181
3888
  results.push({ file: filename, key, size });
3182
3889
  if (!json) {
3183
3890
  logger.success(`Uploaded: ${key} (${formatBytes(size)})`);
@@ -3227,7 +3934,7 @@ function formatBytes(bytes) {
3227
3934
  // src/commands/doctor.ts
3228
3935
  import * as fs from "fs";
3229
3936
  import * as path from "path";
3230
- import { execFileSync as execFileSync2 } from "child_process";
3937
+ import { execFileSync as execFileSync3 } from "child_process";
3231
3938
  async function doctorCommand(cwd, options, cliVersion) {
3232
3939
  const checks = [];
3233
3940
  const visorJsonPath = path.join(cwd, "visor.json");
@@ -3362,9 +4069,9 @@ async function doctorCommand(cwd, options, cliVersion) {
3362
4069
  }
3363
4070
  if (process.platform !== "win32") {
3364
4071
  try {
3365
- const globalPath = execFileSync2("which", ["visor"], { encoding: "utf-8" }).trim();
4072
+ const globalPath = execFileSync3("which", ["visor"], { encoding: "utf-8" }).trim();
3366
4073
  if (globalPath) {
3367
- const globalVersionRaw = execFileSync2(globalPath, ["--version"], { encoding: "utf-8" }).trim();
4074
+ const globalVersionRaw = execFileSync3(globalPath, ["--version"], { encoding: "utf-8" }).trim();
3368
4075
  const globalVersion = globalVersionRaw.split(/\s+/).pop() ?? "";
3369
4076
  if (isOlder(globalVersion, cliVersion)) {
3370
4077
  checks.push({
@@ -3440,27 +4147,27 @@ function findCssFiles(dir, maxDepth = 3) {
3440
4147
  }
3441
4148
 
3442
4149
  // src/utils/patterns.ts
3443
- import { existsSync as existsSync12, readdirSync as readdirSync6, readFileSync as readFileSync15 } from "fs";
3444
- import { join as join13 } from "path";
4150
+ import { existsSync as existsSync15, readdirSync as readdirSync8, readFileSync as readFileSync18 } from "fs";
4151
+ import { join as join17 } from "path";
3445
4152
  import { parse as parseYAML } from "yaml";
3446
4153
  function loadPatternsFromYaml(repoRoot) {
3447
- const patternsDir = join13(repoRoot, "patterns");
3448
- if (!existsSync12(patternsDir)) return [];
3449
- const files = readdirSync6(patternsDir).filter(
4154
+ const patternsDir = join17(repoRoot, "patterns");
4155
+ if (!existsSync15(patternsDir)) return [];
4156
+ const files = readdirSync8(patternsDir).filter(
3450
4157
  (f) => f.endsWith(".visor-pattern.yaml")
3451
4158
  );
3452
4159
  return files.map((file) => {
3453
- const content = readFileSync15(join13(patternsDir, file), "utf-8");
4160
+ const content = readFileSync18(join17(patternsDir, file), "utf-8");
3454
4161
  return parseYAML(content);
3455
4162
  }).filter(Boolean);
3456
4163
  }
3457
4164
  function findRepoRoot2(startDir) {
3458
4165
  let current = startDir;
3459
4166
  while (true) {
3460
- if (existsSync12(join13(current, "patterns"))) {
4167
+ if (existsSync15(join17(current, "patterns"))) {
3461
4168
  return current;
3462
4169
  }
3463
- const parent = join13(current, "..");
4170
+ const parent = join17(current, "..");
3464
4171
  if (parent === current) return null;
3465
4172
  current = parent;
3466
4173
  }
@@ -3842,14 +4549,15 @@ Visor Tokens (${categoryLabel}) \u2014 ${tokens2.length} tokens
3842
4549
  var program = new Command2();
3843
4550
  program.name("visor").description("CLI for the Visor design system").version("0.3.0");
3844
4551
  program.addCommand(checkCommand());
3845
- program.command("init").description("Initialize Visor in the current project").option("--template <name>", "scaffold a themed project (nextjs)").option("--json", "output structured JSON (for AI agents)").action((options) => {
4552
+ program.command("init").description("Initialize Visor \u2014 with --template nextjs, scaffolds a complete runnable Borealis-native Next.js app in one command").option("--template <name>", "scaffold a complete runnable app (templates: nextjs)").option("--json", "output structured JSON (for AI agents)").action((options) => {
3846
4553
  initCommand(process.cwd(), options);
3847
4554
  });
3848
4555
  program.command("list").description("List all available registry items").option("--json", "output structured JSON (for AI agents)").option("--category <name>", "filter items by category").action((options) => {
3849
4556
  listCommand(process.cwd(), options);
3850
4557
  });
3851
- program.command("add").description("Add components, hooks, blocks, or utilities to your project").argument("[items...]", "names of registry items to add").option("--overwrite", "overwrite existing files", false).option("--category <name>", "install all items from a category").option("--block", "install blocks instead of components").option("--dry-run", "preview what would be added without writing files").option("--json", "output structured JSON (for AI agents)").action((items, options) => {
3852
- addCommand(items, process.cwd(), { overwrite: options.overwrite, category: options.category, block: options.block, dryRun: options.dryRun, json: options.json });
4558
+ program.command("add").description("Add components, hooks, blocks, or utilities to your project").argument("[items...]", "names of registry items to add").option("--overwrite", "overwrite existing files", false).option("--category <name>", "install all items from a category").option("--block", "install blocks instead of components").option("--target <platform>", "target platform: react (default) or flutter", "react").option("--dry-run", "preview what would be added without writing files").option("--json", "output structured JSON (for AI agents)").action((items, options) => {
4559
+ const target = options.target === "flutter" ? "flutter" : "react";
4560
+ addCommand(items, process.cwd(), { overwrite: options.overwrite, category: options.category, block: options.block, target, dryRun: options.dryRun, json: options.json });
3853
4561
  });
3854
4562
  program.command("diff").description(
3855
4563
  "Show differences between local files and the registry"
@@ -3864,8 +4572,29 @@ program.command("doctor").description("Run diagnostics on a Visor installation")
3864
4572
  });
3865
4573
  var theme = program.command("theme").description("Theme management commands");
3866
4574
  theme.command("apply").description(
3867
- "Read a .visor.yaml file and generate full CSS token overrides"
3868
- ).argument("<file>", "path to .visor.yaml file").option("-o, --output <path>", "output CSS file path").option("--json", "output structured JSON (for AI agents)").option("--adapter <name>", "target adapter: nextjs, fumadocs, deck").action(
4575
+ "Read a .visor.yaml file and generate token overrides (CSS or Flutter)"
4576
+ ).argument("<file>", "path to .visor.yaml file").option(
4577
+ "-o, --output <path>",
4578
+ "output CSS file path (or directory for --adapter flutter)"
4579
+ ).option("--json", "output structured JSON (for AI agents)").option(
4580
+ "--adapter <name>",
4581
+ "target adapter: nextjs, fumadocs, deck, docs, flutter"
4582
+ ).option(
4583
+ "--package-name <name>",
4584
+ "(flutter) Dart package name for generated pubspec.yaml (default: ui)"
4585
+ ).option(
4586
+ "--tokens-only",
4587
+ "(flutter) emit only token files \u2014 skip pubspec.yaml and theme scaffolding"
4588
+ ).option(
4589
+ "--light-only",
4590
+ "(flutter) emit only the light-brightness theme getter"
4591
+ ).option(
4592
+ "--dark-only",
4593
+ "(flutter) emit only the dark-brightness theme getter"
4594
+ ).option(
4595
+ "--theme-class-name <name>",
4596
+ "(flutter) class name for generated theme (default: VisorAppTheme)"
4597
+ ).action(
3869
4598
  (file, options) => {
3870
4599
  themeApplyCommand(file, process.cwd(), {
3871
4600
  ...options,
@@ -3909,8 +4638,8 @@ theme.command("register").description(
3909
4638
  theme.command("unregister").description(
3910
4639
  "Remove a theme from the docs site \u2014 deletes CSS file, removes globals.css import and theme-config.ts entry"
3911
4640
  ).argument("<slug>", "theme slug to unregister (e.g. entr, kaiah)").option("--json", "output structured JSON (for AI agents)").action(
3912
- (slug, options) => {
3913
- themeUnregisterCommand(slug, process.cwd(), options);
4641
+ (slug2, options) => {
4642
+ themeUnregisterCommand(slug2, process.cwd(), options);
3914
4643
  }
3915
4644
  );
3916
4645
  theme.command("sync").description(
@@ -3920,6 +4649,13 @@ theme.command("sync").description(
3920
4649
  themeSyncCommand(process.cwd(), options);
3921
4650
  }
3922
4651
  );
4652
+ theme.command("batch-apply-flutter").description(
4653
+ "Batch-generate Dart ThemeData for all .visor.yaml themes (themes/ + custom-themes/) into packages/visor_themes/"
4654
+ ).option("--dry-run", "show what would be generated without writing files").option("--json", "output structured JSON (for AI agents)").action(
4655
+ (options) => {
4656
+ themeBatchApplyFlutterCommand(process.cwd(), options);
4657
+ }
4658
+ );
3923
4659
  var fonts = program.command("fonts").description("Font library management commands");
3924
4660
  fonts.command("add").description("Upload woff2 font files to the Visor Font Library on R2").argument("<path>", "path to a .woff2 file or directory containing .woff2 files").requiredOption("--org <org>", "organization namespace (e.g. low-orbit)").option("--family <name>", "font family slug (auto-inferred from filename if omitted)").option("--json", "output structured JSON (for AI agents)").action(
3925
4661
  (path2, options) => {