@loworbitstudio/visor 1.20.0 → 1.22.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-07-21T04:26:48.827Z",
3
+ "generated_at": "2026-09-05T02:55:23.179Z",
4
4
  "components": {
5
5
  "accessibility-specimen": {
6
6
  "changeType": "current",
package/dist/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/index.ts
4
4
  import { readFileSync as readFileSync33 } from "fs";
5
- import { dirname as dirname14, join as join33 } from "path";
5
+ import { dirname as dirname15, join as join33 } from "path";
6
6
  import { fileURLToPath as fileURLToPath4 } from "url";
7
7
  import { Command as Command2 } from "commander";
8
8
 
@@ -1693,27 +1693,10 @@ function initCommand(cwd, options) {
1693
1693
  );
1694
1694
  process.exit(1);
1695
1695
  }
1696
- if (configExists(cwd)) {
1697
- filesSkipped.push("visor.json");
1698
- if (!json) {
1699
- logger.warn("visor.json already exists. Skipping config creation.");
1700
- }
1701
- } else {
1702
- writeConfig(cwd, DEFAULT_CONFIG);
1703
- filesCreated.push("visor.json");
1704
- if (!json) {
1705
- logger.success("Created visor.json");
1706
- logger.blank();
1707
- logger.info("Default paths:");
1708
- logger.item(`components \u2192 ${DEFAULT_CONFIG.paths.components}`);
1709
- logger.item(`deck components \u2192 ${DEFAULT_CONFIG.paths.deckComponents}`);
1710
- logger.item(`blocks \u2192 ${DEFAULT_CONFIG.paths.blocks}`);
1711
- logger.item(`hooks \u2192 ${DEFAULT_CONFIG.paths.hooks}`);
1712
- logger.item(`lib \u2192 ${DEFAULT_CONFIG.paths.lib}`);
1713
- }
1714
- }
1715
1696
  if (options?.template === "nextjs") {
1716
1697
  scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings);
1698
+ } else {
1699
+ writeVisorConfig(cwd, json, filesCreated, filesSkipped);
1717
1700
  }
1718
1701
  if (options?.template !== "nextjs") {
1719
1702
  const missingTokens = !hasVisorTokens(cwd);
@@ -1845,12 +1828,34 @@ function emitError(json, message) {
1845
1828
  logger.error(message);
1846
1829
  }
1847
1830
  }
1831
+ function writeVisorConfig(cwd, json, filesCreated, filesSkipped) {
1832
+ if (configExists(cwd)) {
1833
+ filesSkipped.push("visor.json");
1834
+ if (!json) {
1835
+ logger.warn("visor.json already exists. Skipping config creation.");
1836
+ }
1837
+ return;
1838
+ }
1839
+ writeConfig(cwd, DEFAULT_CONFIG);
1840
+ filesCreated.push("visor.json");
1841
+ if (!json) {
1842
+ logger.success("Created visor.json");
1843
+ logger.blank();
1844
+ logger.info("Default paths:");
1845
+ logger.item(`components \u2192 ${DEFAULT_CONFIG.paths.components}`);
1846
+ logger.item(`deck components \u2192 ${DEFAULT_CONFIG.paths.deckComponents}`);
1847
+ logger.item(`blocks \u2192 ${DEFAULT_CONFIG.paths.blocks}`);
1848
+ logger.item(`hooks \u2192 ${DEFAULT_CONFIG.paths.hooks}`);
1849
+ logger.item(`lib \u2192 ${DEFAULT_CONFIG.paths.lib}`);
1850
+ }
1851
+ }
1848
1852
  function scaffoldNextjs(cwd, json, filesCreated, filesSkipped, warnings) {
1849
1853
  if (!json) {
1850
1854
  logger.blank();
1851
1855
  logger.info("Scaffolding a Borealis-native Next.js app...");
1852
1856
  }
1853
1857
  runCreateNextApp(cwd, json);
1858
+ writeVisorConfig(cwd, json, filesCreated, filesSkipped);
1854
1859
  runInstallVisorDeps(cwd, json);
1855
1860
  const yamlPath = join8(cwd, ".visor.yaml");
1856
1861
  if (existsSync6(yamlPath)) {
@@ -7992,10 +7997,10 @@ import {
7992
7997
  existsSync as existsSync29,
7993
7998
  readFileSync as readFileSync31,
7994
7999
  readdirSync as readdirSync16,
7995
- rmSync as rmSync4
8000
+ rmSync as rmSync4,
8001
+ statSync as statSync11
7996
8002
  } from "fs";
7997
- import { basename as basename8, isAbsolute as isAbsolute5, join as join31, resolve as resolve19 } from "path";
7998
- import { homedir as homedir3 } from "os";
8003
+ import { basename as basename8, dirname as dirname13, isAbsolute as isAbsolute5, join as join31, resolve as resolve19 } from "path";
7999
8004
  import * as childProcess6 from "child_process";
8000
8005
  import { parse as parseYaml5 } from "yaml";
8001
8006
  import { generateThemeData as generateThemeData7 } from "@loworbitstudio/visor-theme-engine";
@@ -8003,7 +8008,7 @@ import { nextjsAdapter as nextjsAdapter3 } from "@loworbitstudio/visor-theme-eng
8003
8008
  import { validate as validate3 } from "@loworbitstudio/visor-theme-engine";
8004
8009
 
8005
8010
  // src/lib/blessed-discovery.ts
8006
- import { existsSync as existsSync28, readdirSync as readdirSync15 } from "fs";
8011
+ import { existsSync as existsSync28, readdirSync as readdirSync15, statSync as statSync10 } from "fs";
8007
8012
  import { join as join30 } from "path";
8008
8013
  var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
8009
8014
  "node_modules",
@@ -8014,11 +8019,15 @@ var WALK_EXCLUDE_DIRS = /* @__PURE__ */ new Set([
8014
8019
  "coverage",
8015
8020
  ".cache"
8016
8021
  ]);
8022
+ var REFERENCE_BUILD_DIRNAME = "reference-build";
8023
+ var APPROVED_CAPTURES_SUBPATH = join30("captures", "approved");
8024
+ var MISSING_MANIFEST_REASON = `missing ${BLESSED_MANIFEST_FILENAME}`;
8017
8025
  function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
8018
8026
  const builds = [];
8027
+ const incomplete = [];
8019
8028
  const errors = [];
8020
8029
  if (!existsSync28(blessedDir)) {
8021
- return { builds, errors };
8030
+ return { builds, incomplete, errors };
8022
8031
  }
8023
8032
  walk2(blessedDir, 0);
8024
8033
  builds.sort((a, b) => {
@@ -8027,7 +8036,8 @@ function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
8027
8036
  }
8028
8037
  return a.manifest.pattern.localeCompare(b.manifest.pattern);
8029
8038
  });
8030
- return { builds, errors };
8039
+ incomplete.sort((a, b) => a.dir.localeCompare(b.dir));
8040
+ return { builds, incomplete, errors };
8031
8041
  function walk2(dir, depth) {
8032
8042
  if (depth > maxDepth) return;
8033
8043
  let entries;
@@ -8048,6 +8058,10 @@ function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
8048
8058
  }
8049
8059
  return;
8050
8060
  }
8061
+ if (isNearMissRoot(dir, entries)) {
8062
+ incomplete.push({ dir, reason: MISSING_MANIFEST_REASON });
8063
+ return;
8064
+ }
8051
8065
  for (const entry of entries) {
8052
8066
  if (!entry.isDirectory()) continue;
8053
8067
  if (WALK_EXCLUDE_DIRS.has(entry.name)) continue;
@@ -8055,6 +8069,19 @@ function discoverBlessedBuilds(blessedDir, maxDepth = 8) {
8055
8069
  }
8056
8070
  }
8057
8071
  }
8072
+ function isNearMissRoot(dir, entries) {
8073
+ const hasReferenceBuild = entries.some(
8074
+ (entry) => entry.isDirectory() && entry.name === REFERENCE_BUILD_DIRNAME
8075
+ );
8076
+ if (!hasReferenceBuild) return false;
8077
+ const approved = join30(dir, APPROVED_CAPTURES_SUBPATH);
8078
+ try {
8079
+ if (!statSync10(approved).isDirectory()) return false;
8080
+ } catch {
8081
+ return false;
8082
+ }
8083
+ return !existsSync28(join30(dir, REFERENCE_BUILD_DIRNAME, BLESSED_MANIFEST_FILENAME));
8084
+ }
8058
8085
  function resolveBlessedBuild(blessedDir, shape, pattern2) {
8059
8086
  const { builds } = discoverBlessedBuilds(blessedDir);
8060
8087
  const build = builds.find(
@@ -8064,13 +8091,18 @@ function resolveBlessedBuild(blessedDir, shape, pattern2) {
8064
8091
  }
8065
8092
 
8066
8093
  // src/commands/spawn.ts
8067
- var DEFAULT_BLESSED_DIR = join31(
8068
- homedir3(),
8069
- "Code",
8070
- "low-orbit",
8071
- "low-orbit-playbook",
8072
- "design-prototypes"
8073
- );
8094
+ var BLESSED_ROOT_DIRNAME = "design-prototypes";
8095
+ var NO_BLESSED_ROOT_ERROR = `No blessed-build root configured. Pass --blessed-dir <path>, set VISOR_BLESSED_DIR, or run from a checkout that contains a '${BLESSED_ROOT_DIRNAME}/' directory. See docs/blessed-builds.md.`;
8096
+ function discoverBlessedRoot(cwd) {
8097
+ let dir = resolve19(cwd);
8098
+ for (; ; ) {
8099
+ const candidate = join31(dir, BLESSED_ROOT_DIRNAME);
8100
+ if (existsSync29(candidate) && statSync11(candidate).isDirectory()) return candidate;
8101
+ const parent = dirname13(dir);
8102
+ if (parent === dir) return void 0;
8103
+ dir = parent;
8104
+ }
8105
+ }
8074
8106
  var FORK_EXCLUDE = /* @__PURE__ */ new Set([
8075
8107
  "node_modules",
8076
8108
  ".next",
@@ -8099,8 +8131,11 @@ function parseBlessedIdentifier(from) {
8099
8131
  return { shape: parts[0], pattern: parts[1] };
8100
8132
  }
8101
8133
  function resolveBlessedDir(cwd, options) {
8102
- const raw = options.blessedDir ?? (process.env.VISOR_BLESSED_DIR && process.env.VISOR_BLESSED_DIR.length > 0 ? process.env.VISOR_BLESSED_DIR : void 0) ?? DEFAULT_BLESSED_DIR;
8103
- return isAbsolute5(raw) ? raw : resolve19(cwd, raw);
8134
+ const explicit = options.blessedDir ?? (process.env.VISOR_BLESSED_DIR && process.env.VISOR_BLESSED_DIR.length > 0 ? process.env.VISOR_BLESSED_DIR : void 0);
8135
+ if (explicit !== void 0) {
8136
+ return isAbsolute5(explicit) ? explicit : resolve19(cwd, explicit);
8137
+ }
8138
+ return discoverBlessedRoot(cwd);
8104
8139
  }
8105
8140
  function resolveThemeFile(theme2, cwd, themeFile) {
8106
8141
  const candidates = [];
@@ -8162,6 +8197,7 @@ function runSpawn(cwd, options) {
8162
8197
  if (!options.output) throw new Error("Missing required --output <path>.");
8163
8198
  const { shape, pattern: pattern2 } = parseBlessedIdentifier(options.from);
8164
8199
  const blessedDir = resolveBlessedDir(cwd, options);
8200
+ if (blessedDir === void 0) throw new Error(NO_BLESSED_ROOT_ERROR);
8165
8201
  const { build, available } = resolveBlessedBuild(blessedDir, shape, pattern2);
8166
8202
  if (!build) {
8167
8203
  const list = available.length > 0 ? available.map((b) => ` - blessed:${b.manifest.shape}:${b.manifest.pattern}`).join("\n") : " (none found)";
@@ -8218,7 +8254,8 @@ ${list}`
8218
8254
  }
8219
8255
  function listBlessed(cwd, options) {
8220
8256
  const blessedDir = resolveBlessedDir(cwd, options);
8221
- const { builds, errors } = discoverBlessedBuilds(blessedDir);
8257
+ if (blessedDir === void 0) throw new Error(NO_BLESSED_ROOT_ERROR);
8258
+ const { builds, incomplete, errors } = discoverBlessedBuilds(blessedDir);
8222
8259
  if (options.json) {
8223
8260
  console.log(
8224
8261
  JSON.stringify({
@@ -8233,6 +8270,7 @@ function listBlessed(cwd, options) {
8233
8270
  three_gates_status: b.manifest.three_gates_status,
8234
8271
  dir: b.dir
8235
8272
  })),
8273
+ incomplete,
8236
8274
  errors
8237
8275
  })
8238
8276
  );
@@ -8246,6 +8284,14 @@ function listBlessed(cwd, options) {
8246
8284
  logger.success(`blessed:${b.manifest.shape}:${b.manifest.pattern}`);
8247
8285
  logger.item(`base theme: ${b.manifest.base_theme} \xB7 requires visor ${b.manifest.requires_visor} \xB7 gates: ${b.manifest.three_gates_status}`);
8248
8286
  }
8287
+ if (incomplete.length > 0) {
8288
+ logger.blank();
8289
+ logger.heading(`Near-miss builds (not spawnable) \u2014 ${incomplete.length}`);
8290
+ for (const i of incomplete) {
8291
+ logger.warn(i.dir);
8292
+ logger.item(i.reason);
8293
+ }
8294
+ }
8249
8295
  for (const e of errors) {
8250
8296
  logger.warn(`Skipped ${e.dir}: ${e.error}`);
8251
8297
  }
@@ -8288,7 +8334,7 @@ function spawnCommand(cwd, options) {
8288
8334
 
8289
8335
  // src/commands/render.ts
8290
8336
  import { existsSync as existsSync30, mkdirSync as mkdirSync14, readFileSync as readFileSync32 } from "fs";
8291
- import { dirname as dirname13, isAbsolute as isAbsolute6, resolve as resolve20 } from "path";
8337
+ import { dirname as dirname14, isAbsolute as isAbsolute6, resolve as resolve20 } from "path";
8292
8338
  var FIXTURES = {
8293
8339
  "stat-card": {
8294
8340
  default: {
@@ -8650,7 +8696,7 @@ async function renderCommand(component, cwd, options) {
8650
8696
  const width = Number(options.width ?? 720);
8651
8697
  const height = Number(options.height ?? 640);
8652
8698
  const outPath = resolveOutPath(cwd, options.out, component, options.theme, mode, state);
8653
- mkdirSync14(dirname13(outPath), { recursive: true });
8699
+ mkdirSync14(dirname14(outPath), { recursive: true });
8654
8700
  const browser = await playwright.chromium.launch();
8655
8701
  let probe;
8656
8702
  try {
@@ -8762,7 +8808,7 @@ function formatBytes2(bytes) {
8762
8808
  }
8763
8809
 
8764
8810
  // src/index.ts
8765
- var __dirname2 = dirname14(fileURLToPath4(import.meta.url));
8811
+ var __dirname2 = dirname15(fileURLToPath4(import.meta.url));
8766
8812
  var pkg = JSON.parse(
8767
8813
  readFileSync33(join33(__dirname2, "..", "package.json"), "utf-8")
8768
8814
  );
@@ -8959,7 +9005,7 @@ sandbox.command("approve").description(
8959
9005
  );
8960
9006
  program.command("spawn").description(
8961
9007
  "Fork a Playbook blessed reference build with atomic theme re-skinning \u2014 one command replaces cp -R + npm install + theme apply"
8962
- ).option("--from <identifier>", "blessed build to spawn: blessed:{shape}:{pattern}").option("--theme <id>", "theme id (or path to a .visor.yaml) to re-skin the fork with").option("--theme-file <path>", "explicit path to a theme.visor.yaml \u2014 bypasses --theme name resolution").option("--output <path>", "destination directory for the forked project").option("--blessed-dir <path>", "override the blessed-build root (default: VISOR_BLESSED_DIR or ~/Code/low-orbit/low-orbit-playbook/design-prototypes)").option("--install", "run npm install in the forked project (default: skip)").option("--validate", "validate the applied theme after forking (default: skip)").option("--list-blessed", "list all discoverable blessed builds and exit").option("--json", "output structured JSON (for AI agents)").action((options) => {
9008
+ ).option("--from <identifier>", "blessed build to spawn: blessed:{shape}:{pattern}").option("--theme <id>", "theme id (or path to a .visor.yaml) to re-skin the fork with").option("--theme-file <path>", "explicit path to a theme.visor.yaml \u2014 bypasses --theme name resolution").option("--output <path>", "destination directory for the forked project").option("--blessed-dir <path>", "override the blessed-build root (default: VISOR_BLESSED_DIR, else the nearest design-prototypes/ directory found by walking up from the cwd)").option("--install", "run npm install in the forked project (default: skip)").option("--validate", "validate the applied theme after forking (default: skip)").option("--list-blessed", "list all discoverable blessed builds and exit").option("--json", "output structured JSON (for AI agents)").action((options) => {
8963
9009
  spawnCommand(process.cwd(), options);
8964
9010
  });
8965
9011
  program.command("render").description(