@loworbitstudio/visor 0.6.0 → 0.7.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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "version": "0.4.0",
3
- "generated_at": "2026-04-29T19:23:27.559Z",
3
+ "generated_at": "2026-05-01T00:42:09.768Z",
4
4
  "components": {
5
5
  "accessibility-specimen": {
6
6
  "changeType": "current",
package/dist/index.js CHANGED
@@ -2211,9 +2211,121 @@ function printIssue(issue) {
2211
2211
  console.log(`${prefix} ${code} ${issue.message}${path2}`);
2212
2212
  }
2213
2213
 
2214
+ // src/commands/theme-verify.ts
2215
+ import { spawnSync as _spawnSync } from "child_process";
2216
+ import { existsSync as existsSync7 } from "fs";
2217
+ import { resolve as resolve5 } from "path";
2218
+ function themeVerifyCommand(dir, cwd, options, _spawnFn = _spawnSync) {
2219
+ const target = options.target ?? "flutter";
2220
+ if (target !== "flutter") {
2221
+ if (options.json) {
2222
+ console.log(
2223
+ JSON.stringify({
2224
+ valid: false,
2225
+ target,
2226
+ errors: [
2227
+ {
2228
+ code: "UNSUPPORTED_TARGET",
2229
+ message: `Unsupported target: "${target}". Only "flutter" is supported.`
2230
+ }
2231
+ ]
2232
+ })
2233
+ );
2234
+ } else {
2235
+ logger.error(`Unsupported target: "${target}". Only "flutter" is supported.`);
2236
+ }
2237
+ process.exit(1);
2238
+ }
2239
+ const dirPath = resolve5(cwd, dir);
2240
+ if (!existsSync7(dirPath)) {
2241
+ if (options.json) {
2242
+ console.log(
2243
+ JSON.stringify({
2244
+ valid: false,
2245
+ target,
2246
+ dir: dirPath,
2247
+ errors: [
2248
+ {
2249
+ code: "DIR_NOT_FOUND",
2250
+ message: `Directory not found: ${dirPath}`
2251
+ }
2252
+ ]
2253
+ })
2254
+ );
2255
+ } else {
2256
+ logger.error(`Directory not found: ${dirPath}`);
2257
+ logger.info("Make sure the path exists and is readable.");
2258
+ }
2259
+ process.exit(1);
2260
+ }
2261
+ const flutterBin = findFlutterBin();
2262
+ if (!flutterBin) {
2263
+ if (options.json) {
2264
+ console.log(
2265
+ JSON.stringify({
2266
+ valid: false,
2267
+ target,
2268
+ dir: dirPath,
2269
+ errors: [
2270
+ {
2271
+ code: "FLUTTER_NOT_FOUND",
2272
+ message: "Flutter binary not found. Set FLUTTER_ROOT, add flutter to PATH, or install via FVM."
2273
+ }
2274
+ ]
2275
+ })
2276
+ );
2277
+ } else {
2278
+ logger.error("Flutter binary not found.");
2279
+ logger.info(
2280
+ "Set FLUTTER_ROOT, add flutter to PATH, or install via FVM."
2281
+ );
2282
+ }
2283
+ process.exit(1);
2284
+ }
2285
+ if (!options.json) {
2286
+ logger.info(`Verifying Flutter output at: ${dirPath}`);
2287
+ logger.item(`Using Flutter binary: ${flutterBin}`);
2288
+ }
2289
+ const result = _spawnFn(flutterBin, ["analyze", "--no-pub"], {
2290
+ cwd: dirPath,
2291
+ stdio: options.json ? "pipe" : "inherit",
2292
+ encoding: "utf-8"
2293
+ });
2294
+ if (options.json) {
2295
+ const stdout = (result.stdout ?? "").toString();
2296
+ const stderr = (result.stderr ?? "").toString();
2297
+ const success = result.status === 0;
2298
+ console.log(
2299
+ JSON.stringify({
2300
+ valid: success,
2301
+ target,
2302
+ dir: dirPath,
2303
+ exitCode: result.status,
2304
+ stdout: stdout.trim() || void 0,
2305
+ stderr: stderr.trim() || void 0,
2306
+ errors: success ? [] : [
2307
+ {
2308
+ code: "DART_ANALYZE_FAILED",
2309
+ message: stderr.trim() || stdout.trim() || "dart analyze reported errors"
2310
+ }
2311
+ ]
2312
+ })
2313
+ );
2314
+ process.exit(success ? 0 : 1);
2315
+ }
2316
+ if (result.status === 0) {
2317
+ logger.success("Flutter output is clean \u2014 dart analyze passed.");
2318
+ process.exit(0);
2319
+ } else {
2320
+ logger.error("dart analyze reported errors in the generated output.");
2321
+ logger.info("Fix the errors above, then re-run: visor theme apply --target flutter");
2322
+ process.exit(1);
2323
+ }
2324
+ }
2325
+
2214
2326
  // src/commands/theme-extract.ts
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";
2327
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync6, existsSync as existsSync8, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
2328
+ import { resolve as resolve6, join as join10, basename, extname as extname2, relative } from "path";
2217
2329
  import { stringify as stringifyYaml } from "yaml";
2218
2330
  import {
2219
2331
  extractFromCSS,
@@ -2242,8 +2354,8 @@ var CSS_DIRS = [
2242
2354
  "packages/design-tokens"
2243
2355
  ];
2244
2356
  function themeExtractCommand(cwd, options) {
2245
- const targetDir = resolve5(cwd, options.from ?? ".");
2246
- if (!existsSync7(targetDir)) {
2357
+ const targetDir = resolve6(cwd, options.from ?? ".");
2358
+ if (!existsSync8(targetDir)) {
2247
2359
  if (options.json) {
2248
2360
  console.log(JSON.stringify({ success: false, error: `Directory not found: ${targetDir}` }));
2249
2361
  } else {
@@ -2306,7 +2418,7 @@ function collectCSSFiles(targetDir) {
2306
2418
  }
2307
2419
  for (const dir of CSS_DIRS) {
2308
2420
  const dirPath = join10(targetDir, dir);
2309
- if (existsSync7(dirPath) && statSync3(dirPath).isDirectory()) {
2421
+ if (existsSync8(dirPath) && statSync3(dirPath).isDirectory()) {
2310
2422
  scanDirForCSS(dirPath, files, seen, 2);
2311
2423
  }
2312
2424
  }
@@ -2314,9 +2426,9 @@ function collectCSSFiles(targetDir) {
2314
2426
  return files;
2315
2427
  }
2316
2428
  function addFileIfExists(filePath, files, seen) {
2317
- const resolved = resolve5(filePath);
2429
+ const resolved = resolve6(filePath);
2318
2430
  if (seen.has(resolved)) return;
2319
- if (!existsSync7(resolved)) return;
2431
+ if (!existsSync8(resolved)) return;
2320
2432
  try {
2321
2433
  const content = readFileSync11(resolved, "utf-8");
2322
2434
  if (content.includes("--")) {
@@ -2327,7 +2439,7 @@ function addFileIfExists(filePath, files, seen) {
2327
2439
  }
2328
2440
  }
2329
2441
  function scanDirForCSS(dir, files, seen, maxDepth) {
2330
- if (!existsSync7(dir)) return;
2442
+ if (!existsSync8(dir)) return;
2331
2443
  const SKIP_DIRS = /* @__PURE__ */ new Set([
2332
2444
  "node_modules",
2333
2445
  ".next",
@@ -2432,7 +2544,7 @@ function parseNextFontFromLayouts(targetDir) {
2432
2544
  const fontMap = /* @__PURE__ */ new Map();
2433
2545
  for (const relPath of LAYOUT_FILE_PATHS) {
2434
2546
  const fullPath = join10(targetDir, relPath);
2435
- if (!existsSync7(fullPath)) continue;
2547
+ if (!existsSync8(fullPath)) continue;
2436
2548
  try {
2437
2549
  const content = readFileSync11(fullPath, "utf-8");
2438
2550
  parseNextFontDeclarations(content, fontMap);
@@ -2520,7 +2632,7 @@ var MONO_FONT_NAMES = /* @__PURE__ */ new Set([
2520
2632
  ]);
2521
2633
  function extractFontHints(targetDir) {
2522
2634
  const pkgPath = join10(targetDir, "package.json");
2523
- if (!existsSync7(pkgPath)) return void 0;
2635
+ if (!existsSync8(pkgPath)) return void 0;
2524
2636
  try {
2525
2637
  const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
2526
2638
  const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
@@ -2559,7 +2671,7 @@ function extractFontHints(targetDir) {
2559
2671
  }
2560
2672
  function inferThemeName(targetDir) {
2561
2673
  const pkgPath = join10(targetDir, "package.json");
2562
- if (existsSync7(pkgPath)) {
2674
+ if (existsSync8(pkgPath)) {
2563
2675
  try {
2564
2676
  const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
2565
2677
  if (pkg.name) {
@@ -2598,7 +2710,7 @@ function outputJSON(result, validationResult) {
2598
2710
  }
2599
2711
  function outputYAML(result, outputPath, cwd, validationResult) {
2600
2712
  const yamlStr = buildAnnotatedYAML(result);
2601
- const outFile = resolve5(cwd, outputPath ?? ".visor.yaml");
2713
+ const outFile = resolve6(cwd, outputPath ?? ".visor.yaml");
2602
2714
  const high = result.tokens.filter((t) => t.confidence === "high").length;
2603
2715
  const med = result.tokens.filter((t) => t.confidence === "medium").length;
2604
2716
  const low = result.tokens.filter((t) => t.confidence === "low").length;
@@ -2684,14 +2796,14 @@ function buildAnnotatedYAML(result) {
2684
2796
  }
2685
2797
 
2686
2798
  // src/commands/theme-register.ts
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";
2799
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, mkdirSync as mkdirSync4, existsSync as existsSync10 } from "fs";
2800
+ import { resolve as resolve8, join as join12 } from "path";
2689
2801
  import { generateThemeData as generateThemeData3 } from "@loworbitstudio/visor-theme-engine";
2690
2802
  import { docsAdapter as docsAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
2691
2803
 
2692
2804
  // src/utils/theme-helpers.ts
2693
- import { existsSync as existsSync8 } from "fs";
2694
- import { resolve as resolve6, dirname as dirname5, join as join11 } from "path";
2805
+ import { existsSync as existsSync9 } from "fs";
2806
+ import { resolve as resolve7, dirname as dirname5, join as join11 } from "path";
2695
2807
  function toSlug(name) {
2696
2808
  return name.toLowerCase().replace(/\s+/g, "-");
2697
2809
  }
@@ -2699,9 +2811,9 @@ function toLabel(name) {
2699
2811
  return name.split(/[\s-]+/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
2700
2812
  }
2701
2813
  function findRepoRoot(startDir) {
2702
- let current = resolve6(startDir);
2814
+ let current = resolve7(startDir);
2703
2815
  while (true) {
2704
- if (existsSync8(join11(current, "packages", "docs"))) {
2816
+ if (existsSync9(join11(current, "packages", "docs"))) {
2705
2817
  return current;
2706
2818
  }
2707
2819
  const parent = dirname5(current);
@@ -2796,7 +2908,7 @@ ${indent}${newEntry},
2796
2908
  return { updated, changed: true };
2797
2909
  }
2798
2910
  function themeRegisterCommand(file, cwd, options) {
2799
- const filePath = resolve7(cwd, file);
2911
+ const filePath = resolve8(cwd, file);
2800
2912
  let yamlContent;
2801
2913
  try {
2802
2914
  yamlContent = readFileSync12(filePath, "utf-8");
@@ -2846,7 +2958,7 @@ function themeRegisterCommand(file, cwd, options) {
2846
2958
  const cssFilePath = join12(docsAppDir, `${slug2}-theme.css`);
2847
2959
  const globalsPath = join12(docsAppDir, "globals.css");
2848
2960
  const themeConfigPath = join12(repoRoot, "packages", "docs", "lib", "theme-config.ts");
2849
- if (!existsSync9(docsAppDir)) {
2961
+ if (!existsSync10(docsAppDir)) {
2850
2962
  const msg = `Docs app directory not found: ${docsAppDir}`;
2851
2963
  if (options.json) {
2852
2964
  console.log(JSON.stringify({ success: false, error: msg }));
@@ -2871,7 +2983,7 @@ function themeRegisterCommand(file, cwd, options) {
2871
2983
  process.exit(1);
2872
2984
  return;
2873
2985
  }
2874
- const cssExists = existsSync9(cssFilePath);
2986
+ const cssExists = existsSync10(cssFilePath);
2875
2987
  const cssChanged = !cssExists || readFileSync12(cssFilePath, "utf-8") !== css;
2876
2988
  const { updated: newGlobals, changed: globalsChanged } = insertGlobalsImport(globalsContent, slug2);
2877
2989
  const { updated: newThemeConfig, changed: themeConfigChanged, error: configError } = insertThemeConfig(
@@ -2956,7 +3068,7 @@ function themeRegisterCommand(file, cwd, options) {
2956
3068
  }
2957
3069
 
2958
3070
  // src/commands/theme-unregister.ts
2959
- import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync10, unlinkSync } from "fs";
3071
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8, existsSync as existsSync11, unlinkSync } from "fs";
2960
3072
  import { join as join13 } from "path";
2961
3073
  function removeGlobalsImport(content, slug2) {
2962
3074
  const importLine = `@import './${slug2}-theme.css';`;
@@ -2993,7 +3105,7 @@ function themeUnregisterCommand(slug2, cwd, options) {
2993
3105
  const cssFilePath = join13(docsAppDir, `${slug2}-theme.css`);
2994
3106
  const globalsPath = join13(docsAppDir, "globals.css");
2995
3107
  const themeConfigPath = join13(repoRoot, "packages", "docs", "lib", "theme-config.ts");
2996
- if (!existsSync10(docsAppDir)) {
3108
+ if (!existsSync11(docsAppDir)) {
2997
3109
  const msg = `Docs app directory not found: ${docsAppDir}`;
2998
3110
  if (options.json) {
2999
3111
  console.log(JSON.stringify({ success: false, error: msg }));
@@ -3018,7 +3130,7 @@ function themeUnregisterCommand(slug2, cwd, options) {
3018
3130
  process.exit(1);
3019
3131
  return;
3020
3132
  }
3021
- const cssExists = existsSync10(cssFilePath);
3133
+ const cssExists = existsSync11(cssFilePath);
3022
3134
  const { updated: newGlobals, changed: globalsChanged } = removeGlobalsImport(globalsContent, slug2);
3023
3135
  const { updated: newThemeConfig, changed: themeConfigChanged } = removeThemeConfigEntry(themeConfigContent, slug2);
3024
3136
  if (!cssExists && !globalsChanged && !themeConfigChanged) {
@@ -3062,7 +3174,7 @@ import {
3062
3174
  readFileSync as readFileSync14,
3063
3175
  writeFileSync as writeFileSync9,
3064
3176
  mkdirSync as mkdirSync5,
3065
- existsSync as existsSync11,
3177
+ existsSync as existsSync12,
3066
3178
  readdirSync as readdirSync4,
3067
3179
  unlinkSync as unlinkSync2,
3068
3180
  copyFileSync
@@ -3081,7 +3193,7 @@ var CUSTOM_OVERLAY_CSS_PATH = "packages/docs/app/custom-themes.generated.css";
3081
3193
  var CUSTOM_OVERLAY_TS_PATH = "packages/docs/lib/theme-config.custom.generated.ts";
3082
3194
  var CUSTOM_OVERLAY_IMPORT_LINE = "@import './custom-themes.generated.css';";
3083
3195
  function scanThemeDir(dir) {
3084
- if (!existsSync11(dir)) return [];
3196
+ if (!existsSync12(dir)) return [];
3085
3197
  return readdirSync4(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join14(dir, f));
3086
3198
  }
3087
3199
  function extractGroup(yamlContent) {
@@ -3333,7 +3445,7 @@ function themeSyncCommand(cwd, options) {
3333
3445
  try {
3334
3446
  globalsContent = readFileSync14(globalsPath, "utf-8");
3335
3447
  themeConfigContent = readFileSync14(themeConfigPath, "utf-8");
3336
- gitignoreContent = existsSync11(gitignorePath) ? readFileSync14(gitignorePath, "utf-8") : "";
3448
+ gitignoreContent = existsSync12(gitignorePath) ? readFileSync14(gitignorePath, "utf-8") : "";
3337
3449
  } catch (err) {
3338
3450
  const msg = err instanceof Error ? err.message : "Could not read docs files";
3339
3451
  if (options.json) {
@@ -3349,12 +3461,12 @@ function themeSyncCommand(cwd, options) {
3349
3461
  const newGitignore = customSlugs.length > 0 ? updateGitignoreBlock(gitignoreContent, customSlugs) : gitignoreContent;
3350
3462
  const newCustomOverlayCss = generateCustomOverlayCss(customManifest);
3351
3463
  const newCustomOverlayTs = generateCustomOverlayTs(customManifest);
3352
- const existingCssFiles = existsSync11(docsAppDir) ? readdirSync4(docsAppDir).filter(
3464
+ const existingCssFiles = existsSync12(docsAppDir) ? readdirSync4(docsAppDir).filter(
3353
3465
  (f) => f.endsWith("-theme.css") && f !== "custom-themes.generated.css"
3354
3466
  ) : [];
3355
3467
  const newCssSet = new Set(allSlugs.map((s) => `${s}-theme.css`));
3356
3468
  const staleCssFiles = existingCssFiles.filter((f) => !newCssSet.has(f));
3357
- const existingPublicYamls = existsSync11(docsPublicThemesDir) ? readdirSync4(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
3469
+ const existingPublicYamls = existsSync12(docsPublicThemesDir) ? readdirSync4(docsPublicThemesDir).filter((f) => f.endsWith(".visor.yaml")) : [];
3358
3470
  const newPublicYamlSet = new Set(manifest.map((e) => `${e.yamlFilename}.visor.yaml`));
3359
3471
  const stalePublicYamls = existingPublicYamls.filter((f) => !newPublicYamlSet.has(f));
3360
3472
  if (options.dryRun) {
@@ -3395,7 +3507,7 @@ function themeSyncCommand(cwd, options) {
3395
3507
  writeFileSync9(customOverlayTsPath, newCustomOverlayTs, "utf-8");
3396
3508
  writeFileSync9(themeConfigPath, newThemeConfig, "utf-8");
3397
3509
  writeFileSync9(globalsPath, newGlobals, "utf-8");
3398
- if (existsSync11(gitignorePath)) {
3510
+ if (existsSync12(gitignorePath)) {
3399
3511
  writeFileSync9(gitignorePath, newGitignore, "utf-8");
3400
3512
  }
3401
3513
  const allSourceFiles = [...stockFiles, ...customFiles];
@@ -3443,7 +3555,7 @@ import {
3443
3555
  readFileSync as readFileSync15,
3444
3556
  writeFileSync as writeFileSync10,
3445
3557
  mkdirSync as mkdirSync6,
3446
- existsSync as existsSync12,
3558
+ existsSync as existsSync13,
3447
3559
  readdirSync as readdirSync5,
3448
3560
  rmSync
3449
3561
  } from "fs";
@@ -3451,7 +3563,7 @@ import { join as join15, basename as basename3, dirname as dirname6 } from "path
3451
3563
  import { generateThemeData as generateThemeData5 } from "@loworbitstudio/visor-theme-engine";
3452
3564
  import { flutterAdapter as flutterAdapter2 } from "@loworbitstudio/visor-theme-engine/adapters";
3453
3565
  function scanThemeDir2(dir) {
3454
- if (!existsSync12(dir)) return [];
3566
+ if (!existsSync13(dir)) return [];
3455
3567
  return readdirSync5(dir).filter((f) => f.endsWith(".visor.yaml")).map((f) => join15(dir, f)).sort();
3456
3568
  }
3457
3569
  function slugToCamel(slug2) {
@@ -3688,7 +3800,7 @@ function themeBatchApplyFlutterCommand(cwd, options) {
3688
3800
  }
3689
3801
  const slugs = processed.map((p) => p.slug);
3690
3802
  const libSrcDir = join15(outputDir, "lib", "src");
3691
- if (existsSync12(libSrcDir)) {
3803
+ if (existsSync13(libSrcDir)) {
3692
3804
  rmSync(libSrcDir, { recursive: true, force: true });
3693
3805
  }
3694
3806
  const packageFiles = {
@@ -3731,8 +3843,8 @@ function themeBatchApplyFlutterCommand(cwd, options) {
3731
3843
  }
3732
3844
 
3733
3845
  // src/commands/fonts-add.ts
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";
3846
+ import { existsSync as existsSync14, statSync as statSync4, readdirSync as readdirSync6, readFileSync as readFileSync16 } from "fs";
3847
+ import { resolve as resolve9, basename as basename4, extname as extname3 } from "path";
3736
3848
  import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
3737
3849
  function deriveFamilySlug(filename) {
3738
3850
  const name = basename4(filename, extname3(filename));
@@ -3776,8 +3888,8 @@ function deriveFamilySlug(filename) {
3776
3888
  return parts.join("-").toLowerCase();
3777
3889
  }
3778
3890
  function collectWoff2Files(inputPath) {
3779
- const resolved = resolve8(inputPath);
3780
- if (!existsSync13(resolved)) {
3891
+ const resolved = resolve9(inputPath);
3892
+ if (!existsSync14(resolved)) {
3781
3893
  throw new Error(`Path not found: ${resolved}`);
3782
3894
  }
3783
3895
  const stat = statSync4(resolved);
@@ -3790,7 +3902,7 @@ function collectWoff2Files(inputPath) {
3790
3902
  return [resolved];
3791
3903
  }
3792
3904
  if (stat.isDirectory()) {
3793
- const files = readdirSync6(resolved).filter((f) => extname3(f).toLowerCase() === ".woff2").map((f) => resolve8(resolved, f));
3905
+ const files = readdirSync6(resolved).filter((f) => extname3(f).toLowerCase() === ".woff2").map((f) => resolve9(resolved, f));
3794
3906
  if (files.length === 0) {
3795
3907
  throw new Error(
3796
3908
  `No .woff2 files found in directory: ${resolved}`
@@ -3805,8 +3917,8 @@ function collectWoff2Files(inputPath) {
3805
3917
  throw new Error(`Path is neither a file nor a directory: ${resolved}`);
3806
3918
  }
3807
3919
  function getNonWoff2Fonts(inputPath) {
3808
- const resolved = resolve8(inputPath);
3809
- if (!existsSync13(resolved) || !statSync4(resolved).isDirectory()) {
3920
+ const resolved = resolve9(inputPath);
3921
+ if (!existsSync14(resolved) || !statSync4(resolved).isDirectory()) {
3810
3922
  return [];
3811
3923
  }
3812
3924
  return readdirSync6(resolved).filter((f) => {
@@ -3859,7 +3971,7 @@ async function fontsAddCommand(inputPath, options) {
3859
3971
  const r2Config = getR2Config();
3860
3972
  const files = collectWoff2Files(inputPath);
3861
3973
  const familySlug = options.family ?? deriveFamilySlug(basename4(files[0]));
3862
- const resolved = resolve8(inputPath);
3974
+ const resolved = resolve9(inputPath);
3863
3975
  const nonWoff2 = statSync4(resolved).isDirectory() ? getNonWoff2Fonts(resolved) : [];
3864
3976
  if (!json) {
3865
3977
  logger.heading("Visor Font Upload");
@@ -4147,12 +4259,12 @@ function findCssFiles(dir, maxDepth = 3) {
4147
4259
  }
4148
4260
 
4149
4261
  // src/utils/patterns.ts
4150
- import { existsSync as existsSync15, readdirSync as readdirSync8, readFileSync as readFileSync18 } from "fs";
4262
+ import { existsSync as existsSync16, readdirSync as readdirSync8, readFileSync as readFileSync18 } from "fs";
4151
4263
  import { join as join17 } from "path";
4152
4264
  import { parse as parseYAML } from "yaml";
4153
4265
  function loadPatternsFromYaml(repoRoot) {
4154
4266
  const patternsDir = join17(repoRoot, "patterns");
4155
- if (!existsSync15(patternsDir)) return [];
4267
+ if (!existsSync16(patternsDir)) return [];
4156
4268
  const files = readdirSync8(patternsDir).filter(
4157
4269
  (f) => f.endsWith(".visor-pattern.yaml")
4158
4270
  );
@@ -4164,7 +4276,7 @@ function loadPatternsFromYaml(repoRoot) {
4164
4276
  function findRepoRoot2(startDir) {
4165
4277
  let current = startDir;
4166
4278
  while (true) {
4167
- if (existsSync15(join17(current, "patterns"))) {
4279
+ if (existsSync16(join17(current, "patterns"))) {
4168
4280
  return current;
4169
4281
  }
4170
4282
  const parent = join17(current, "..");
@@ -4618,6 +4730,11 @@ theme.command("validate").description("Run full validation ruleset on a .visor.y
4618
4730
  themeValidateCommand(file, process.cwd(), options);
4619
4731
  }
4620
4732
  );
4733
+ theme.command("verify").description("Verify generated theme output for a target platform").argument("<dir>", "path to generated output directory").option("--target <platform>", "target platform (flutter)", "flutter").option("--json", "output structured JSON (for AI agents)").action(
4734
+ (dir, options) => {
4735
+ themeVerifyCommand(dir, process.cwd(), options);
4736
+ }
4737
+ );
4621
4738
  theme.command("extract").description(
4622
4739
  "Scan an existing project's CSS and produce a best-effort .visor.yaml theme file"
4623
4740
  ).option("--from <path>", "path to project directory to scan").option("--json", "output structured JSON (for AI agents)").option("-o, --output <path>", "output file path (default: .visor.yaml)").option("--validate", "run validator on the extracted theme").action(