youiyoux 0.1.4 → 0.1.6

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.
@@ -17,7 +17,7 @@ import {
17
17
  sameColor,
18
18
  splitTopLevel,
19
19
  tailwindTheme
20
- } from "./chunk-YJ7WMBTT.js";
20
+ } from "./chunk-VOSWAWKC.js";
21
21
  import {
22
22
  AST_GREP_LANGUAGES,
23
23
  BUILT_IN_CHECKS,
@@ -28,6 +28,7 @@ import {
28
28
  RuleSpec,
29
29
  SHAPE_DIMENSIONS,
30
30
  canMove,
31
+ expandGitSource,
31
32
  findFiles,
32
33
  getSystem,
33
34
  isFamilyId,
@@ -49,7 +50,7 @@ import {
49
50
  toPosix,
50
51
  unifiedDiff,
51
52
  writeFileAtomic
52
- } from "./chunk-NYDJYMID.js";
53
+ } from "./chunk-DP54SKL4.js";
53
54
 
54
55
  // ../scanner/src/parse.ts
55
56
  import { Lang, parseAsync } from "@ast-grep/napi";
@@ -552,9 +553,9 @@ var siteKey = (f) => `${f.file}:${f.line}:${f.column}:${f.text}`;
552
553
  function compareWithPublished(project, scan, id, sites) {
553
554
  const published = project.system.rules.find((r) => r.id === id);
554
555
  if (!published) return null;
555
- const run2 = scan.report.checks.find((c) => c.rule === id);
556
+ const run3 = scan.report.checks.find((c) => c.rule === id);
556
557
  const execution = ruleCapability(published).state;
557
- if (run2?.state !== "ran") return { execution, findings: null, added: null, removed: null };
558
+ if (run3?.state !== "ran") return { execution, findings: null, added: null, removed: null };
558
559
  const before2 = new Set(scan.report.violations.filter((v) => v.rule === id).map(siteKey));
559
560
  const after = new Set(sites.map(siteKey));
560
561
  return { execution, findings: before2.size, added: [...after].filter((k) => !before2.has(k)).length, removed: [...before2].filter((k) => !after.has(k)).length };
@@ -575,8 +576,8 @@ function evaluateRule(project, scan, draft) {
575
576
  const runnable = ruleCapability(asOn);
576
577
  if (runnable.state !== "enforced") return { ok: true, rule: rule.id, execution: runnable, findings: null, snapshot };
577
578
  const result = runChecks(checkInputFor(project, scan, [asOn]));
578
- const run2 = result.checks[0];
579
- if (run2?.state !== "ran") return { ok: true, rule: rule.id, execution: { state: "unsupported", reason: run2?.reason ?? "the check did not run" }, findings: null, snapshot };
579
+ const run3 = result.checks[0];
580
+ if (run3?.state !== "ran") return { ok: true, rule: rule.id, execution: { state: "unsupported", reason: run3?.reason ?? "the check did not run" }, findings: null, snapshot };
580
581
  const sites = result.violations.map(({ rule: _rule, severity: _severity, ...finding }) => finding).sort((a, b) => siteKey(a).localeCompare(siteKey(b)));
581
582
  return {
582
583
  ok: true,
@@ -594,24 +595,177 @@ function evaluateRule(project, scan, draft) {
594
595
  }
595
596
 
596
597
  // ../scanner/src/project.ts
597
- import { stat } from "node:fs/promises";
598
- import { dirname as dirname2, join as join2, posix, resolve } from "node:path";
598
+ import { stat as stat2 } from "node:fs/promises";
599
+ import { dirname as dirname3, join as join3, posix, resolve as resolve2 } from "node:path";
600
+
601
+ // ../scanner/src/design-source.ts
602
+ import { execFile as execFile2 } from "node:child_process";
603
+ import { createHash } from "node:crypto";
604
+ import { mkdir, mkdtemp, readdir, readFile, readlink, realpath, rename, rm, stat } from "node:fs/promises";
605
+ import { homedir } from "node:os";
606
+ import { dirname as dirname2, join as join2, relative, resolve } from "node:path";
607
+ import { promisify as promisify2 } from "node:util";
608
+ var run2 = promisify2(execFile2);
609
+ var LOCK_FILE = "youi.lock.json";
610
+ var COMMIT = /^[0-9a-f]{40}$/;
611
+ var systemsCacheDir = () => process.env.YOUI_SYSTEMS_CACHE || join2(process.env.XDG_CACHE_HOME || join2(homedir(), ".cache"), "youiyoux/systems");
612
+ async function designRootOf(source, configFile, productRoot, options = {}) {
613
+ return (await locateDesign(source, configFile, productRoot, options)).root;
614
+ }
615
+ async function locateDesign(source, configFile, productRoot, options = {}) {
616
+ if ("path" in source) return { root: resolve(dirname2(configFile), source.path), commit: null };
617
+ if ("package" in source) {
618
+ for (let dir = resolve(productRoot); ; dir = dirname2(dir)) {
619
+ const candidate = join2(dir, "node_modules", ...source.package.split("/"));
620
+ if (await isDirectory(candidate)) return { root: await realpath(candidate), commit: null };
621
+ if (dirname2(dir) === dir) break;
622
+ }
623
+ throw new Error(`the design system package ${source.package} is not installed for ${productRoot} (config ${configFile}): add it as a dependency and install, or set source.path to its checkout`);
624
+ }
625
+ return gitCheckout(source.git, source.ref, { ...options, locked: await lockedCommit(productRoot) });
626
+ }
627
+ async function lockedCommit(productRoot) {
628
+ try {
629
+ const commit = JSON.parse(await readTextOrNull(join2(productRoot, LOCK_FILE)) ?? "{}").sourceCommit;
630
+ return typeof commit === "string" && COMMIT.test(commit) ? commit : null;
631
+ } catch {
632
+ return null;
633
+ }
634
+ }
635
+ async function gitCheckout(url, ref, options = {}) {
636
+ const remote = expandGitSource(url);
637
+ const base = join2(options.cacheDir ?? systemsCacheDir(), createHash("sha256").update(remote).digest("hex").slice(0, 16));
638
+ const bare = join2(base, "repo.git");
639
+ const refs = join2(base, "refs.json");
640
+ const recorded = options.refresh ? null : COMMIT.test(ref) ? ref : options.locked ?? (await readRefs(refs))[ref] ?? null;
641
+ if (recorded !== null && await isDirectory(join2(base, recorded))) return { root: await contained(join2(base, recorded), recorded), commit: recorded };
642
+ if (recorded === null || !await hasCommit(bare, recorded)) await fetchAll(bare, remote);
643
+ const commit = recorded ?? await revParse(bare, ref);
644
+ if (commit === null) throw new Error(`${withoutCredentials(url)} has no branch, tag or commit "${ref}"`);
645
+ if (recorded !== null && !await hasCommit(bare, commit)) throw new Error(`${withoutCredentials(url)} has no commit ${commit}, which youi.lock.json records: run youiyoux sync to resolve "${ref}" again`);
646
+ const tree = join2(base, commit);
647
+ if (!await isDirectory(tree)) await extract(bare, commit, tree);
648
+ await contained(tree, commit);
649
+ if (recorded === null) await writeFileAtomic(refs, `${JSON.stringify({ ...await readRefs(refs), [ref]: commit }, null, 2)}
650
+ `);
651
+ return { root: tree, commit };
652
+ }
653
+ var GIT_ENV = { ...process.env, GIT_ALLOW_PROTOCOL: "https:ssh:file", GIT_TERMINAL_PROMPT: "0" };
654
+ var git = (args) => run2("git", args, { env: GIT_ENV, maxBuffer: 16 * 1024 * 1024 });
655
+ async function fetchAll(bare, remote) {
656
+ if (!await isDirectory(bare)) {
657
+ await mkdir(dirname2(bare), { recursive: true });
658
+ await git(["init", "--quiet", "--bare", bare]);
659
+ }
660
+ try {
661
+ await git(["--git-dir", bare, "fetch", "--quiet", "--force", "--prune", "--tags", "--", remote, "+refs/heads/*:refs/heads/*"]);
662
+ } catch (error) {
663
+ const detail = error.stderr?.trim().split("\n").pop() ?? (error instanceof Error ? error.message : String(error));
664
+ throw new Error(withoutCredentials(`could not fetch the design system from ${remote}: ${detail}`));
665
+ }
666
+ }
667
+ async function revParse(bare, ref) {
668
+ for (const name of [`refs/tags/${ref}`, `refs/heads/${ref}`, ref]) {
669
+ const commit = await git(["--git-dir", bare, "rev-parse", "--verify", "--quiet", `${name}^{commit}`]).then((r) => r.stdout.trim(), () => "");
670
+ if (COMMIT.test(commit)) return commit;
671
+ }
672
+ return null;
673
+ }
674
+ var hasCommit = (bare, commit) => git(["--git-dir", bare, "cat-file", "-e", `${commit}^{commit}`]).then(() => true, () => false);
675
+ async function extract(bare, commit, tree) {
676
+ const temp = await mkdtemp(`${tree}.`);
677
+ try {
678
+ await git(["--git-dir", bare, "archive", "--format=tar", "-o", join2(temp, ".tree.tar"), commit]);
679
+ await run2("tar", ["-xf", join2(temp, ".tree.tar"), "-C", temp]);
680
+ await rm(join2(temp, ".tree.tar"));
681
+ await contained(temp, commit);
682
+ await rename(temp, tree).catch(async (error) => {
683
+ if (!await isDirectory(tree)) throw error;
684
+ });
685
+ } finally {
686
+ await rm(temp, { recursive: true, force: true });
687
+ }
688
+ }
689
+ var withoutCredentials = (text) => text.replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi, "$1***@");
690
+ async function contained(tree, commit) {
691
+ const escaping = await linksLeaving(tree);
692
+ if (escaping.length > 0) throw new Error(`the design system at ${commit} has links that lead outside it (${escaping.slice(0, 3).join(", ")}): it was not used, since sync would copy what they point at`);
693
+ return tree;
694
+ }
695
+ async function linksLeaving(root) {
696
+ const leaving = [];
697
+ const pending = [root];
698
+ while (pending.length > 0) {
699
+ const dir = pending.pop();
700
+ for (const entry of await readdir(dir, { withFileTypes: true })) {
701
+ const path = join2(dir, entry.name);
702
+ if (entry.isDirectory()) pending.push(path);
703
+ else if (entry.isSymbolicLink() && !within(root, resolve(dir, await readlink(path)))) leaving.push(relative(root, path));
704
+ }
705
+ }
706
+ return leaving.sort();
707
+ }
708
+ async function readRefs(file) {
709
+ try {
710
+ const refs = JSON.parse(await readFile(file, "utf8"));
711
+ return Object.fromEntries(Object.entries(refs).filter((e) => typeof e[1] === "string" && COMMIT.test(e[1])));
712
+ } catch {
713
+ return {};
714
+ }
715
+ }
716
+ var linksFile = () => process.env.YOUI_LINKS || join2(process.env.XDG_CONFIG_HOME || join2(homedir(), ".config"), "youiyoux/links.json");
717
+ async function readLinks() {
718
+ try {
719
+ const links = JSON.parse(await readFile(linksFile(), "utf8"));
720
+ return Object.fromEntries(Object.entries(links).filter((e) => typeof e[1] === "string"));
721
+ } catch {
722
+ return {};
723
+ }
724
+ }
725
+ var productKey = async (productRoot) => realpath(productRoot).catch(() => resolve(productRoot));
726
+ async function linkedDesign(productRoot) {
727
+ return (await readLinks())[await productKey(productRoot)] ?? null;
728
+ }
729
+ async function setLink(productRoot, design) {
730
+ const links = await readLinks();
731
+ const key = await productKey(productRoot);
732
+ if (design === null) delete links[key];
733
+ else links[key] = resolve(design);
734
+ await writeFileAtomic(linksFile(), `${JSON.stringify(links, null, 2)}
735
+ `);
736
+ }
737
+ async function isDirectory(path) {
738
+ return (await stat(path).catch(() => null))?.isDirectory() ?? false;
739
+ }
740
+
741
+ // ../scanner/src/project.ts
599
742
  var PROJECT_FILE = "youi.project.json";
600
743
  var ProjectRootError = class extends Error {
601
744
  };
602
745
  var ROOT_HINT = 'set "root" in the config (relative to it, or absolute) or pass --root <dir>';
603
746
  async function loadProject(root, options = {}) {
604
- const configFile = resolve(options.configPath ?? join2(resolve(root), PROJECT_FILE));
747
+ const configFile = resolve2(options.configPath ?? join3(resolve2(root), PROJECT_FILE));
605
748
  const config = normalizeProjectConfig(await readDefinition(configFile, ProjectConfig));
606
- const fromConfig = (path) => resolve(dirname2(configFile), path);
607
- const productRoot = options.productRoot !== void 0 ? resolve(options.productRoot || ".") : config.root ? fromConfig(config.root) : resolve(root);
608
- if (!await isDirectory(productRoot)) throw new ProjectRootError(`product root ${productRoot} is not a directory (config ${configFile}); ${ROOT_HINT}`);
609
- const designRoot = options.designRoot ? resolve(options.designRoot) : fromConfig(config.source.path);
610
- const repo = await loadDesignRepo(designRoot);
611
- return { root: productRoot, config, repo, system: getSystem(repo, config.system), configFile, designOverride: Boolean(options.designRoot), rootOverride: options.productRoot !== void 0 };
749
+ const fromConfig = (path) => resolve2(dirname3(configFile), path);
750
+ const productRoot = options.productRoot !== void 0 ? resolve2(options.productRoot || ".") : config.root ? fromConfig(config.root) : resolve2(root);
751
+ if (!await isDirectory2(productRoot)) throw new ProjectRootError(`product root ${productRoot} is not a directory (config ${configFile}); ${ROOT_HINT}`);
752
+ const linked = options.designRoot ? null : await linkedDesign(productRoot);
753
+ const located = options.designRoot || linked ? { root: resolve2(options.designRoot || linked), commit: null } : await locateDesign(config.source, configFile, productRoot, { refresh: options.refreshDesign });
754
+ const repo = await loadDesignRepo(located.root);
755
+ return {
756
+ root: productRoot,
757
+ config,
758
+ repo,
759
+ system: getSystem(repo, config.system),
760
+ configFile,
761
+ designOverride: Boolean(options.designRoot),
762
+ rootOverride: options.productRoot !== void 0,
763
+ ...linked ? { linked } : {},
764
+ ...located.commit ? { designCommit: located.commit } : {}
765
+ };
612
766
  }
613
- async function isDirectory(path) {
614
- return (await stat(path).catch(() => null))?.isDirectory() ?? false;
767
+ async function isDirectory2(path) {
768
+ return (await stat2(path).catch(() => null))?.isDirectory() ?? false;
615
769
  }
616
770
  function importSpecifier(config, fromFile) {
617
771
  if (config.importFrom) return config.importFrom;
@@ -621,7 +775,7 @@ function importSpecifier(config, fromFile) {
621
775
  }
622
776
  async function emptyRootProblem(project, matched) {
623
777
  if (matched > 0) return null;
624
- const tsconfig = await stat(join2(project.root, project.config.tsconfig)).catch(() => null) ? "" : ` and no ${project.config.tsconfig}`;
778
+ const tsconfig = await stat2(join3(project.root, project.config.tsconfig)).catch(() => null) ? "" : ` and no ${project.config.tsconfig}`;
625
779
  return `product root ${project.root} has 0 files matching ${project.config.include.join(", ")}${tsconfig} (config ${project.configFile}); if the product lives elsewhere, ${ROOT_HINT}`;
626
780
  }
627
781
 
@@ -949,8 +1103,8 @@ function clusterShapes(shapes, linkage = "complete", tolerance = DEFAULT_TOLERAN
949
1103
  }
950
1104
 
951
1105
  // ../scanner/src/inventory/theme.ts
952
- import { readFile } from "node:fs/promises";
953
- import { join as join3 } from "node:path";
1106
+ import { readFile as readFile2 } from "node:fs/promises";
1107
+ import { join as join4 } from "node:path";
954
1108
  import { Lang as Lang3, parseAsync as parseAsync2 } from "@ast-grep/napi";
955
1109
  var ROOT_FONT_SIZE = 16;
956
1110
  var PILL = 9999;
@@ -1114,7 +1268,7 @@ async function readTailwindConfig(root) {
1114
1268
  const vars = /* @__PURE__ */ new Map();
1115
1269
  const warnings = [];
1116
1270
  for (const name of CONFIG_FILES) {
1117
- const source = await readFile(join3(root, name), "utf8").catch(() => null);
1271
+ const source = await readFile2(join4(root, name), "utf8").catch(() => null);
1118
1272
  if (source === null) continue;
1119
1273
  const top = (await parseAsync2(Lang3.TypeScript, source)).root();
1120
1274
  const theme = top.findAll({ rule: { kind: "pair" } }).find((p) => keyText(p.field("key")) === "theme" && p.field("value")?.kind() === "object");
@@ -1780,8 +1934,8 @@ function buildElementIndex(project) {
1780
1934
  }
1781
1935
 
1782
1936
  // ../scanner/src/graph.ts
1783
- import { readFile as readFile2 } from "node:fs/promises";
1784
- import { join as join4 } from "node:path";
1937
+ import { readFile as readFile3 } from "node:fs/promises";
1938
+ import { join as join5 } from "node:path";
1785
1939
  var ModuleGraph = class {
1786
1940
  root;
1787
1941
  resolver;
@@ -1805,7 +1959,7 @@ var ModuleGraph = class {
1805
1959
  if (!loading) {
1806
1960
  loading = (async () => {
1807
1961
  try {
1808
- const source = this.overrides.get(file) ?? await readFile2(join4(this.root, file), "utf8");
1962
+ const source = this.overrides.get(file) ?? await readFile3(join5(this.root, file), "utf8");
1809
1963
  const info = await this.parse(file, source);
1810
1964
  this.modules.set(file, info);
1811
1965
  return info;
@@ -1820,7 +1974,7 @@ var ModuleGraph = class {
1820
1974
  }
1821
1975
  /** Resolve an import specifier written in `fromFile`; files come back project-relative. */
1822
1976
  resolveSpecifier(specifier, fromFile) {
1823
- const result = this.resolver.resolve(specifier, join4(this.root, fromFile));
1977
+ const result = this.resolver.resolve(specifier, join5(this.root, fromFile));
1824
1978
  if (result.kind === "file") return { kind: "file", file: relPath(this.root, result.file) };
1825
1979
  return result.kind === "external" ? result : { kind: "unresolved" };
1826
1980
  }
@@ -2048,8 +2202,8 @@ var RouteIndex = class _RouteIndex {
2048
2202
  };
2049
2203
 
2050
2204
  // ../scanner/src/scan.ts
2051
- import { readFile as readFile3 } from "node:fs/promises";
2052
- import { join as join5 } from "node:path";
2205
+ import { readFile as readFile4 } from "node:fs/promises";
2206
+ import { join as join6 } from "node:path";
2053
2207
 
2054
2208
  // ../scanner/src/usage.ts
2055
2209
  var MAX_DYNAMIC = 120;
@@ -2235,7 +2389,7 @@ async function parseAll(project, files, overrides, parse3) {
2235
2389
  await Promise.all(
2236
2390
  files.map(async (file) => {
2237
2391
  try {
2238
- const source = overrides[file] ?? await readFile3(join5(project.root, file), "utf8");
2392
+ const source = overrides[file] ?? await readFile4(join6(project.root, file), "utf8");
2239
2393
  sources.set(file, source);
2240
2394
  const module = await parse3(file, source);
2241
2395
  modules.set(file, module);
@@ -2266,7 +2420,7 @@ async function scanProject(project, options = {}) {
2266
2420
  gitState(project.root),
2267
2421
  gitState(project.repo.root),
2268
2422
  changedFiles(project.root),
2269
- cache ? readTextOrNull(join5(project.root, project.config.tsconfig)) : Promise.resolve(null)
2423
+ cache ? readTextOrNull(join6(project.root, project.config.tsconfig)) : Promise.resolve(null)
2270
2424
  ]);
2271
2425
  const definitions = definitionsDigest(project, build.tokens);
2272
2426
  cache?.useDefinitions(definitions);
@@ -2320,7 +2474,7 @@ async function scanProject(project, options = {}) {
2320
2474
  }
2321
2475
 
2322
2476
  // ../scanner/src/codemod.ts
2323
- import { isAbsolute, join as join6, relative, resolve as resolve2 } from "node:path";
2477
+ import { isAbsolute, join as join7, relative as relative2, resolve as resolve3 } from "node:path";
2324
2478
 
2325
2479
  // ../scanner/src/export-origin.ts
2326
2480
  var ExportOrigins = class {
@@ -2388,8 +2542,8 @@ var ExportOrigins = class {
2388
2542
  const found = binding ? await follow(binding.specifier, binding.imported) : null;
2389
2543
  if (!found) return null;
2390
2544
  const change = this.changes.get(found.old);
2391
- const rename3 = change.renameBarrels || this.elements.isOwned(file) || this.elements.implementationFiles.has(file);
2392
- return { ...found, next: found.next === null ? null : rename3 && name === found.old ? change.to : name };
2545
+ const rename4 = change.renameBarrels || this.elements.isOwned(file) || this.elements.implementationFiles.has(file);
2546
+ return { ...found, next: found.next === null ? null : rename4 && name === found.old ? change.to : name };
2393
2547
  }
2394
2548
  for (const specifier of module.starExports) {
2395
2549
  const found = await follow(specifier, name);
@@ -2811,7 +2965,7 @@ async function verifyImports(migration, before2, after, target, outputs, residua
2811
2965
  const resolved = after.graph.resolveSpecifier(specifier, file);
2812
2966
  return resolved.kind === "file" ? { file: resolved.file, name } : resolved.kind === "external" ? { external: true } : null;
2813
2967
  };
2814
- const resolve9 = async (ref) => {
2968
+ const resolve10 = async (ref) => {
2815
2969
  if (!ref) return false;
2816
2970
  if ("external" in ref || ref.name === "*") return true;
2817
2971
  return (await after.graph.resolveExport(ref.file, ref.name)).kind !== "unknown";
@@ -2862,7 +3016,7 @@ async function verifyImports(migration, before2, after, target, outputs, residua
2862
3016
  return base && !("external" in base) ? { file: base.file, name } : base;
2863
3017
  };
2864
3018
  const checkResolution = async (node2, ref) => {
2865
- if (rewritten && changedNode(node2) && !await resolve9(ref)) unresolved.add(at(node2));
3019
+ if (rewritten && changedNode(node2) && !await resolve10(ref)) unresolved.add(at(node2));
2866
3020
  };
2867
3021
  for (const node2 of nodes) {
2868
3022
  if (ts.isImportSpecifier(node2) || ts.isImportClause(node2) && node2.name || ts.isNamespaceImport(node2)) {
@@ -3144,19 +3298,19 @@ async function runMigration(project, migration, options = {}) {
3144
3298
  return { ...base, status: !passed ? "rejected" : options.write ? "applied" : "dry-run", sitesAfter, gates };
3145
3299
  }
3146
3300
  async function writeChanges(project, changes) {
3147
- const outside = changes.filter((c) => isAbsolute(c.file) || relative(project.root, resolve2(project.root, c.file)).startsWith(".."));
3301
+ const outside = changes.filter((c) => isAbsolute(c.file) || relative2(project.root, resolve3(project.root, c.file)).startsWith(".."));
3148
3302
  if (outside.length > 0) return { name: "sources-unchanged", ok: false, detail: `refusing to write outside the project: ${outside.map((c) => c.file).join(", ")}` };
3149
3303
  const moved = [];
3150
- for (const change of changes) if (await readTextOrNull(join6(project.root, change.file)) !== change.before) moved.push(change.file);
3304
+ for (const change of changes) if (await readTextOrNull(join7(project.root, change.file)) !== change.before) moved.push(change.file);
3151
3305
  if (moved.length > 0) return { name: "sources-unchanged", ok: false, detail: `changed since the scan, nothing written: ${moved.join(", ")}` };
3152
3306
  const written = [];
3153
3307
  try {
3154
3308
  for (const change of changes) {
3155
- await writeFileAtomic(join6(project.root, change.file), change.after);
3309
+ await writeFileAtomic(join7(project.root, change.file), change.after);
3156
3310
  written.push(change);
3157
3311
  }
3158
3312
  } catch (error) {
3159
- for (const change of written) await writeFileAtomic(join6(project.root, change.file), change.before);
3313
+ for (const change of written) await writeFileAtomic(join7(project.root, change.file), change.before);
3160
3314
  return { name: "sources-unchanged", ok: false, detail: `write failed, ${written.length} file(s) restored: ${error instanceof Error ? error.message : String(error)}` };
3161
3315
  }
3162
3316
  return { name: "sources-unchanged", ok: true, detail: `${changes.length} file(s) written, each verified unchanged since the scan` };
@@ -3761,7 +3915,7 @@ function ruleRows(project) {
3761
3915
  // ../scanner/src/watch.ts
3762
3916
  import { existsSync, mkdtempSync, rmSync, watch, writeFileSync } from "node:fs";
3763
3917
  import { tmpdir } from "node:os";
3764
- import { dirname as dirname3, join as join7, relative as relative2, resolve as resolve3, sep } from "node:path";
3918
+ import { dirname as dirname4, join as join8, relative as relative3, resolve as resolve4, sep } from "node:path";
3765
3919
  var DEFAULT_DEBOUNCE_MS = 120;
3766
3920
  var LIVE_TIMEOUT_MS = 1e4;
3767
3921
  var PROBE_INTERVAL_MS = 50;
@@ -3793,7 +3947,7 @@ var ProjectWatcher = class _ProjectWatcher {
3793
3947
  closed = false;
3794
3948
  constructor(options) {
3795
3949
  this.options = options;
3796
- this.configFile = resolve3(options.load?.configPath ?? join7(options.root, PROJECT_FILE));
3950
+ this.configFile = resolve4(options.load?.configPath ?? join8(options.root, PROJECT_FILE));
3797
3951
  }
3798
3952
  /** Run the job once, then again on every change until `close()`. */
3799
3953
  static async start(options) {
@@ -3848,7 +4002,7 @@ var ProjectWatcher = class _ProjectWatcher {
3848
4002
  await this.rewatch(this.project);
3849
4003
  }
3850
4004
  const project = this.project;
3851
- this.cache.forget(changed.filter((file) => !existsSync(join7(project.root, file))));
4005
+ this.cache.forget(changed.filter((file) => !existsSync(join8(project.root, file))));
3852
4006
  const result = await this.options.job(project, this.cache, changed);
3853
4007
  if (!this.closed) this.options.onUpdate({ ...base(), status: "ok", result });
3854
4008
  } catch (error) {
@@ -3863,13 +4017,13 @@ var ProjectWatcher = class _ProjectWatcher {
3863
4017
  const watch2 = this.options.watch ?? defaultWatch;
3864
4018
  for (const w of this.watchers) w.close();
3865
4019
  const { root, config, repo, system } = project;
3866
- const sourceDirs = covering(config.include.map((g) => resolve3(root, staticBase(g))).filter((d) => existsSync(d)));
3867
- const definitionDirs = covering([dirname3(system.config.tokens), system.config.components, system.config.rules, ...system.config.design ? [dirname3(system.config.design)] : []].map((p) => resolve3(repo.root, p)).filter((d) => existsSync(d) && d !== repo.root));
3868
- const topLevel = [.../* @__PURE__ */ new Set([repo.root, dirname3(this.configFile), root])];
4020
+ const sourceDirs = covering(config.include.map((g) => resolve4(root, staticBase(g))).filter((d) => existsSync(d)));
4021
+ const definitionDirs = covering([dirname4(system.config.tokens), system.config.components, system.config.rules, ...system.config.design ? [dirname4(system.config.design)] : []].map((p) => resolve4(repo.root, p)).filter((d) => existsSync(d) && d !== repo.root));
4022
+ const topLevel = [.../* @__PURE__ */ new Set([repo.root, dirname4(this.configFile), root])];
3869
4023
  this.watchers = [
3870
- ...sourceDirs.map((dir) => watch2(dir, { recursive: true }, (_event, name) => name && this.sourceChanged(project, join7(dir, name.toString())))),
4024
+ ...sourceDirs.map((dir) => watch2(dir, { recursive: true }, (_event, name) => name && this.sourceChanged(project, join8(dir, name.toString())))),
3871
4025
  ...definitionDirs.map((dir) => watch2(dir, { recursive: true }, () => this.definitionsChanged())),
3872
- ...topLevel.map((dir) => watch2(dir, (_event, name) => name && this.topLevelChanged(project, join7(dir, name.toString()))))
4026
+ ...topLevel.map((dir) => watch2(dir, (_event, name) => name && this.topLevelChanged(project, join8(dir, name.toString()))))
3873
4027
  ];
3874
4028
  for (const w of this.watchers) w.on("error", (error) => this.watcherFailed(error));
3875
4029
  await this.whenLive();
@@ -3877,7 +4031,7 @@ var ProjectWatcher = class _ProjectWatcher {
3877
4031
  watchConfigOnly() {
3878
4032
  const watch2 = this.options.watch ?? defaultWatch;
3879
4033
  for (const w of this.watchers) w.close();
3880
- this.watchers = [watch2(dirname3(this.configFile), { recursive: true }, () => this.definitionsChanged())];
4034
+ this.watchers = [watch2(dirname4(this.configFile), { recursive: true }, () => this.definitionsChanged())];
3881
4035
  for (const w of this.watchers) w.on("error", (error) => this.watcherFailed(error));
3882
4036
  void this.whenLive();
3883
4037
  }
@@ -3895,8 +4049,8 @@ var ProjectWatcher = class _ProjectWatcher {
3895
4049
  const watch2 = this.options.watch ?? defaultWatch;
3896
4050
  this.stopProbing?.();
3897
4051
  if (this.closed) return Promise.resolve();
3898
- this.probeDir ??= mkdtempSync(join7(tmpdir(), "youi-watch-probe-"));
3899
- const probe = join7(this.probeDir, "probe");
4052
+ this.probeDir ??= mkdtempSync(join8(tmpdir(), "youi-watch-probe-"));
4053
+ const probe = join8(this.probeDir, "probe");
3900
4054
  return new Promise((done) => {
3901
4055
  let writes = 0;
3902
4056
  const stop = () => {
@@ -3924,7 +4078,7 @@ var ProjectWatcher = class _ProjectWatcher {
3924
4078
  if (!this.closed) this.options.onUpdate({ generation: this.generation, status: "failed", error: `file watcher: ${error.message}; changes may be missed until restart`, changed: [], reloaded: false, durationMs: 0 });
3925
4079
  }
3926
4080
  sourceChanged(project, absolute) {
3927
- const file = toPosix(relative2(project.root, absolute));
4081
+ const file = toPosix(relative3(project.root, absolute));
3928
4082
  const { include, exclude, outputs } = project.config;
3929
4083
  if (file.startsWith("..") || file.startsWith(`${outputs.dir}/`) || matchesAny(file, [...IGNORED, ...exclude]) || !matchesAny(file, include)) return;
3930
4084
  this.pending.add(file);
@@ -3932,9 +4086,9 @@ var ProjectWatcher = class _ProjectWatcher {
3932
4086
  }
3933
4087
  topLevelChanged(project, absolute) {
3934
4088
  const { design } = project.system.config;
3935
- const definitions = [this.configFile, resolve3(project.repo.root, "youi.config.json"), ...design ? [resolve3(project.repo.root, design)] : []];
4089
+ const definitions = [this.configFile, resolve4(project.repo.root, "youi.config.json"), ...design ? [resolve4(project.repo.root, design)] : []];
3936
4090
  if (definitions.includes(absolute)) this.definitionsChanged();
3937
- else if (absolute === resolve3(project.root, project.config.tsconfig)) {
4091
+ else if (absolute === resolve4(project.root, project.config.tsconfig)) {
3938
4092
  this.pending.add(project.config.tsconfig);
3939
4093
  this.schedule();
3940
4094
  }
@@ -3946,11 +4100,11 @@ var ProjectWatcher = class _ProjectWatcher {
3946
4100
  };
3947
4101
 
3948
4102
  // ../scanner/src/inventory/inventory.ts
3949
- import { relative as relative3 } from "node:path";
4103
+ import { relative as relative4 } from "node:path";
3950
4104
 
3951
4105
  // ../scanner/src/inventory/collect.ts
3952
- import { readFile as readFile5 } from "node:fs/promises";
3953
- import { join as join9, posix as posix3 } from "node:path";
4106
+ import { readFile as readFile6 } from "node:fs/promises";
4107
+ import { join as join10, posix as posix3 } from "node:path";
3954
4108
 
3955
4109
  // ../scanner/src/inventory/astro.ts
3956
4110
  var RAW_TEXT = /* @__PURE__ */ new Set(["script", "style"]);
@@ -4301,8 +4455,8 @@ function roleOfComponent(name, props, firstChild) {
4301
4455
  }
4302
4456
 
4303
4457
  // ../scanner/src/inventory/stories.ts
4304
- import { readFile as readFile4 } from "node:fs/promises";
4305
- import { join as join8, posix as posix2 } from "node:path";
4458
+ import { readFile as readFile5 } from "node:fs/promises";
4459
+ import { join as join9, posix as posix2 } from "node:path";
4306
4460
  var STORY_FILE = /\.stories\.(?:tsx|ts|jsx|js|mjs)$/;
4307
4461
  var CONFIG_DIR = ".storybook";
4308
4462
  var MAIN = ["main.ts", "main.mts", "main.cts", "main.js", "main.mjs", "main.cjs"];
@@ -4310,9 +4464,9 @@ var PREVIEW = ["preview.tsx", "preview.ts", "preview.jsx", "preview.js", "previe
4310
4464
  var DEFAULT_SPECIFIER = { glob: "**/*.stories.{tsx,ts,jsx,js,mjs}", base: "", titlePrefix: "" };
4311
4465
  var GLOB = /[*?{}[\]()!]/;
4312
4466
  function specifierOf(glob, titlePrefix) {
4313
- const relative5 = posix2.normalize(posix2.join(CONFIG_DIR, glob.replace(/^\.\//, "")));
4314
- if (relative5.startsWith("../") || posix2.isAbsolute(relative5)) return null;
4315
- const normal = relative5.replace(/@\(([^()]*)\)/g, (_, alts) => `{${alts.split("|").join(",")}}`);
4467
+ const relative6 = posix2.normalize(posix2.join(CONFIG_DIR, glob.replace(/^\.\//, "")));
4468
+ if (relative6.startsWith("../") || posix2.isAbsolute(relative6)) return null;
4469
+ const normal = relative6.replace(/@\(([^()]*)\)/g, (_, alts) => `{${alts.split("|").join(",")}}`);
4316
4470
  const segments = normal.split("/");
4317
4471
  const firstGlob = segments.findIndex((s) => GLOB.test(s));
4318
4472
  if (firstGlob === -1) return { glob: `${normal}/**/*.stories.{tsx,ts,jsx,js,mjs}`, base: normal, titlePrefix };
@@ -4348,7 +4502,7 @@ function autoTitle(file, specifier) {
4348
4502
  async function firstFile(root, names) {
4349
4503
  for (const name of names) {
4350
4504
  const file = posix2.join(CONFIG_DIR, name);
4351
- const source = await readFile4(join8(root, file), "utf8").catch(() => null);
4505
+ const source = await readFile5(join9(root, file), "utf8").catch(() => null);
4352
4506
  if (source !== null) return { file, source };
4353
4507
  }
4354
4508
  return null;
@@ -4569,7 +4723,7 @@ var InventoryGraph = class extends ModuleGraph {
4569
4723
  }
4570
4724
  };
4571
4725
  async function pathAliases(root, tsconfig) {
4572
- const text = await readFile5(join9(root, tsconfig), "utf8").catch(() => null);
4726
+ const text = await readFile6(join10(root, tsconfig), "utf8").catch(() => null);
4573
4727
  if (!text) return [];
4574
4728
  try {
4575
4729
  const json = JSON.parse(text.replace(/("(?:[^"\\\n]|\\.)*")|\/\*[\s\S]*?\*\/|\/\/.*$/gm, (_, string) => string ?? "").replace(/,(\s*[}\]])/g, "$1"));
@@ -4683,7 +4837,7 @@ var ProjectPass = class {
4683
4837
  await Promise.all(
4684
4838
  files.map(async (file) => {
4685
4839
  try {
4686
- const source = await readFile5(join9(root, file), "utf8");
4840
+ const source = await readFile6(join10(root, file), "utf8");
4687
4841
  if (file.endsWith(".css")) this.sheets.push(await readStylesheet(file, source));
4688
4842
  const module = await this.parse(file, source);
4689
4843
  this.modules.set(file, module);
@@ -4699,7 +4853,7 @@ var ProjectPass = class {
4699
4853
  if (files.some((f) => /\.(tsx|jsx|ts)$/.test(f))) for (const problem of resolver.problems) this.failed.push({ file: tsconfig, reason: problem });
4700
4854
  this.graph = new InventoryGraph(root, resolver, this.modules, this.parse, await pathAliases(root, tsconfig), new Set(files));
4701
4855
  this.routes = RouteIndex.build(this.graph, this.modules, this.target.routes);
4702
- const pkg = JSON.parse(await readFile5(join9(root, "package.json"), "utf8").catch(() => "{}") || "{}");
4856
+ const pkg = JSON.parse(await readFile6(join10(root, "package.json"), "utf8").catch(() => "{}") || "{}");
4703
4857
  const tailwindPackage = Boolean(pkg.dependencies?.tailwindcss ?? pkg.devDependencies?.tailwindcss);
4704
4858
  this.theme = await buildTheme(root, this.sheets, tailwindPackage);
4705
4859
  for (const sheet of this.sheets) this.globalRules.addSheet(sheet);
@@ -5165,7 +5319,7 @@ var ProjectPass = class {
5165
5319
  const stories = { files: files.length, read: 0, drawn: 0, skipped: [], notes: [...config.notes] };
5166
5320
  for (const { file, specifier } of files) {
5167
5321
  try {
5168
- const module = await parseModule(file, await readFile5(join9(root, file), "utf8"));
5322
+ const module = await parseModule(file, await readFile6(join10(root, file), "utf8"));
5169
5323
  if (!module) continue;
5170
5324
  if (module.parseErrors > 0) {
5171
5325
  const error = module.root.root().find({ rule: { kind: "ERROR" } });
@@ -5251,16 +5405,16 @@ async function readProject(target, options = {}) {
5251
5405
  }
5252
5406
  async function collectProject(target, options = {}) {
5253
5407
  const pass = await readProject(target, options);
5254
- const git2 = await gitState(target.root);
5408
+ const git3 = await gitState(target.root);
5255
5409
  const definitions = [...pass.definitions.values()].map((d) => d.entry).sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.name.localeCompare(b.name));
5256
5410
  const primitives = [...pass.primitives].map(([pkg, p]) => ({ package: pkg, project: target.id, exports: [...p.exports].sort(), count: p.count, sites: p.sites.sort((a, b) => a.file.localeCompare(b.file) || a.line - b.line || a.column - b.column) })).sort((a, b) => a.package.localeCompare(b.package));
5257
5411
  const looks = pass.looks.sort(byPosition);
5258
- return { id: target.id, target, commit: git2.commit, failed: pass.failed.sort((a, b) => a.file.localeCompare(b.file)), warnings: pass.theme.warnings, definitions, kits: pass.kits.sort((a, b) => a.id.localeCompare(b.id)), primitives, looks, kitImports: pass.kitImports, stories: pass.stories };
5412
+ return { id: target.id, target, commit: git3.commit, failed: pass.failed.sort((a, b) => a.file.localeCompare(b.file)), warnings: pass.theme.warnings, definitions, kits: pass.kits.sort((a, b) => a.id.localeCompare(b.id)), primitives, looks, kitImports: pass.kitImports, stories: pass.stories };
5259
5413
  }
5260
5414
 
5261
5415
  // ../scanner/src/inventory/target.ts
5262
- import { readFile as readFile6, stat as stat2 } from "node:fs/promises";
5263
- import { basename as basename2, join as join10, resolve as resolve4 } from "node:path";
5416
+ import { readFile as readFile7, stat as stat3 } from "node:fs/promises";
5417
+ import { basename as basename2, join as join11, resolve as resolve5 } from "node:path";
5264
5418
  var DEFAULT_INCLUDE = ["**/*.{tsx,jsx,ts,js,mjs,astro,css}"];
5265
5419
  var DEFAULT_EXCLUDE = ["**/*.d.ts", "**/*.test.*", "**/*.spec.*", "**/*.stories.*", "**/.storybook/**", "**/.next/**", "**/.astro/**", "**/build/**", "**/out/**", "**/coverage/**", "**/*.config.*"];
5266
5420
  var STYLES = ["**/*.css"];
@@ -5271,7 +5425,7 @@ function idOf(name) {
5271
5425
  }
5272
5426
  async function readJson(file) {
5273
5427
  try {
5274
- return JSON.parse(await readFile6(file, "utf8"));
5428
+ return JSON.parse(await readFile7(file, "utf8"));
5275
5429
  } catch {
5276
5430
  return null;
5277
5431
  }
@@ -5305,10 +5459,10 @@ function detectRoutes(files) {
5305
5459
  return { config: RouteConfig.parse({}), source: "none detected" };
5306
5460
  }
5307
5461
  async function loadInventoryTarget(dir, globs = {}) {
5308
- const root = resolve4(dir);
5309
- if (!(await stat2(root).catch(() => null))?.isDirectory()) throw new Error(`${dir} is not a directory`);
5310
- const config = await readJson(join10(root, PROJECT_FILE));
5311
- const pkg = await readJson(join10(root, "package.json"));
5462
+ const root = resolve5(dir);
5463
+ if (!(await stat3(root).catch(() => null))?.isDirectory()) throw new Error(`${dir} is not a directory`);
5464
+ const config = await readJson(join11(root, PROJECT_FILE));
5465
+ const pkg = await readJson(join11(root, "package.json"));
5312
5466
  const include = globs.include?.length ? [...globs.include, ...STYLES] : Array.isArray(config?.include) ? [...config.include, ...STYLES] : DEFAULT_INCLUDE;
5313
5467
  const exclude = [...Array.isArray(config?.exclude) ? config.exclude : DEFAULT_EXCLUDE, ...globs.exclude ?? []];
5314
5468
  const tsconfig = typeof config?.tsconfig === "string" ? normalizeRelPath(config.tsconfig) : "tsconfig.json";
@@ -5445,7 +5599,7 @@ async function buildInventory(dirs, options = {}) {
5445
5599
  let id = target.id;
5446
5600
  for (let n = 2; ids.has(id); n++) id = `${target.id}-${n}`;
5447
5601
  ids.add(id);
5448
- targets.push({ ...target, id, label: relative3(cwd, target.root) || "." });
5602
+ targets.push({ ...target, id, label: relative4(cwd, target.root) || "." });
5449
5603
  }
5450
5604
  const projects = await Promise.all(targets.map((target) => collectProject(target, { definitionRoots: options.definitionRoots === true })));
5451
5605
  const looks = projects.flatMap((p) => p.looks);
@@ -5488,8 +5642,8 @@ async function buildInventory(dirs, options = {}) {
5488
5642
  }
5489
5643
 
5490
5644
  // ../scanner/src/inventory/components.ts
5491
- import { readFile as readFile7 } from "node:fs/promises";
5492
- import { join as join11, posix as posix4 } from "node:path";
5645
+ import { readFile as readFile8 } from "node:fs/promises";
5646
+ import { join as join12, posix as posix4 } from "node:path";
5493
5647
  var SNIPPET_LINES = 6;
5494
5648
  var named = (node2) => node2.children().filter((c) => c.isNamed() && c.kind() !== "comment");
5495
5649
  function functionOf(declaration) {
@@ -5620,7 +5774,7 @@ async function classSites(pass, block) {
5620
5774
  const routes = pass.routes.of(module.file).routes;
5621
5775
  const astro = pass.astro.get(module.file);
5622
5776
  if (astro) {
5623
- const source = await readFile7(join11(pass.target.root, module.file), "utf8");
5777
+ const source = await readFile8(join12(pass.target.root, module.file), "utf8");
5624
5778
  for (const tag of astro.tags) {
5625
5779
  const classes = [];
5626
5780
  for (const a of tag.attributes.filter((a2) => ["class", "className", "class:list"].includes(a2.name))) {
@@ -5730,9 +5884,9 @@ async function collectComponents(dir, globs = {}) {
5730
5884
  });
5731
5885
  }
5732
5886
  components.sort((a, b) => a.definition.file.localeCompare(b.definition.file) || a.definition.line - b.definition.line);
5733
- const pkg = await readJson(join11(target.root, "package.json"));
5887
+ const pkg = await readJson(join12(target.root, "package.json"));
5734
5888
  const dependencies = [...new Set(["dependencies", "devDependencies"].flatMap((field) => Object.keys(pkg?.[field] ?? {})))].sort();
5735
- const ui = (await readJson(join11(target.root, "components.json")))?.aliases?.ui;
5889
+ const ui = (await readJson(join12(target.root, "components.json")))?.aliases?.ui;
5736
5890
  const shadcnUi = typeof ui === "string" ? specifierToDir(ui, aliases) : null;
5737
5891
  const dirs = /* @__PURE__ */ new Map();
5738
5892
  for (const c of components) if (c.definition.kind !== "class" && !c.definition.file.startsWith(`${target.routes.strategy === "expo-router" ? target.routes.appDir : "app"}/`)) dirs.set(posix4.dirname(c.definition.file), (dirs.get(posix4.dirname(c.definition.file)) ?? 0) + 1);
@@ -5752,15 +5906,15 @@ function specifierToDir(specifier, aliases) {
5752
5906
  import { rmSync as rmSync2 } from "node:fs";
5753
5907
 
5754
5908
  // ../scanner/src/specimens/cache.ts
5755
- import { createHash } from "node:crypto";
5909
+ import { createHash as createHash2 } from "node:crypto";
5756
5910
  import { existsSync as existsSync2, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync as writeFileSync2 } from "node:fs";
5757
- import { homedir } from "node:os";
5758
- import { dirname as dirname4, join as join12 } from "node:path";
5911
+ import { homedir as homedir2 } from "node:os";
5912
+ import { dirname as dirname5, join as join13 } from "node:path";
5759
5913
  var SPECIMEN_FORMAT = "youiyoux.specimens/5";
5760
5914
  var newStats = () => ({ tailwindCompiles: 0, bundles: 0, hits: 0, misses: 0 });
5761
5915
  function defaultCacheDir(env = process.env) {
5762
5916
  if (env.YOUI_SPECIMEN_CACHE) return env.YOUI_SPECIMEN_CACHE;
5763
- return join12(env.XDG_CACHE_HOME || join12(homedir(), ".cache"), "youiyoux", "specimens");
5917
+ return join13(env.XDG_CACHE_HOME || join13(homedir2(), ".cache"), "youiyoux", "specimens");
5764
5918
  }
5765
5919
  function versions(root) {
5766
5920
  return Object.fromEntries(
@@ -5768,7 +5922,7 @@ function versions(root) {
5768
5922
  const dir = packageUnderRoot(root, name);
5769
5923
  try {
5770
5924
  if (!dir) return [name, null];
5771
- return [name, JSON.parse(readFileSync(join12(dir, "package.json"), "utf8")).version];
5925
+ return [name, JSON.parse(readFileSync(join13(dir, "package.json"), "utf8")).version];
5772
5926
  } catch {
5773
5927
  return [name, null];
5774
5928
  }
@@ -5778,17 +5932,17 @@ function versions(root) {
5778
5932
  var EXTRA_INPUTS = ["package.json", "tsconfig.json", "pnpm-lock.yaml", "package-lock.json", "yarn.lock", "bun.lockb"];
5779
5933
  var FONT_DIRS = ["public/fonts", "static/fonts"];
5780
5934
  function specimenKey(root, commit, files, recipes) {
5781
- const hash = createHash("sha256");
5935
+ const hash = createHash2("sha256");
5782
5936
  hash.update(`${SPECIMEN_FORMAT}\0${commit ?? "no-commit"}\0${JSON.stringify(versions(root))}\0${JSON.stringify(recipes)}\0`);
5783
5937
  const fonts = FONT_DIRS.flatMap((dir) => {
5784
5938
  try {
5785
- return readdirSync(join12(root, dir)).map((f) => `${dir}/${f}`);
5939
+ return readdirSync(join13(root, dir)).map((f) => `${dir}/${f}`);
5786
5940
  } catch {
5787
5941
  return [];
5788
5942
  }
5789
5943
  });
5790
5944
  for (const file of [.../* @__PURE__ */ new Set([...files, ...EXTRA_INPUTS, ...fonts])].sort()) {
5791
- const path = join12(root, file);
5945
+ const path = join13(root, file);
5792
5946
  if (!existsSync2(path) || !statSync(path).isFile() || !within(root, path)) continue;
5793
5947
  hash.update(`${file}\0`);
5794
5948
  hash.update(readFileSync(path));
@@ -5797,7 +5951,7 @@ function specimenKey(root, commit, files, recipes) {
5797
5951
  return hash.digest("hex").slice(0, 24);
5798
5952
  }
5799
5953
  function writeJsonAtomic(file, value) {
5800
- mkdirSync(dirname4(file), { recursive: true });
5954
+ mkdirSync(dirname5(file), { recursive: true });
5801
5955
  writeFileSync2(`${file}.${process.pid}.tmp`, JSON.stringify(value));
5802
5956
  renameSync(`${file}.${process.pid}.tmp`, file);
5803
5957
  }
@@ -5807,22 +5961,22 @@ var SpecimenCache = class {
5807
5961
  this.dir = dir;
5808
5962
  }
5809
5963
  entryDir(project, key) {
5810
- return join12(this.dir, `${project.replace(/[^a-z0-9-]+/gi, "-")}-${key}`);
5964
+ return join13(this.dir, `${project.replace(/[^a-z0-9-]+/gi, "-")}-${key}`);
5811
5965
  }
5812
5966
  read(project, key) {
5813
5967
  try {
5814
- return JSON.parse(readFileSync(join12(this.entryDir(project, key), "entry.json"), "utf8"));
5968
+ return JSON.parse(readFileSync(join13(this.entryDir(project, key), "entry.json"), "utf8"));
5815
5969
  } catch {
5816
5970
  return null;
5817
5971
  }
5818
5972
  }
5819
5973
  /** Written to a temporary name and renamed, so a reader never sees half an entry. */
5820
5974
  write(project, key, value) {
5821
- writeJsonAtomic(join12(this.entryDir(project, key), "entry.json"), value);
5975
+ writeJsonAtomic(join13(this.entryDir(project, key), "entry.json"), value);
5822
5976
  }
5823
5977
  /** A scratch directory for one build (the bundle esbuild makes is evaluated from here). */
5824
5978
  scratch(project, key) {
5825
- const dir = join12(this.entryDir(project, key), "build");
5979
+ const dir = join13(this.entryDir(project, key), "build");
5826
5980
  mkdirSync(dir, { recursive: true });
5827
5981
  return dir;
5828
5982
  }
@@ -5831,7 +5985,7 @@ var SpecimenCache = class {
5831
5985
  // ../scanner/src/specimens/css.ts
5832
5986
  import { createRequire as createRequire2 } from "node:module";
5833
5987
  import { existsSync as existsSync3, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
5834
- import { basename as basename3, dirname as dirname5, extname, join as join13, posix as posix5, resolve as resolve5 } from "node:path";
5988
+ import { basename as basename3, dirname as dirname6, extname, join as join14, posix as posix5, resolve as resolve6 } from "node:path";
5835
5989
  import { Worker } from "node:worker_threads";
5836
5990
  var SCHEMES = ["light", "dark"];
5837
5991
  var TAILWIND_TIMEOUT_MS = 3e4;
@@ -5851,7 +6005,7 @@ function readInside(root, rel) {
5851
6005
  const inside = relativeInside(root, rel);
5852
6006
  if (!("ok" in inside)) return null;
5853
6007
  try {
5854
- return readFileSync2(join13(root, inside.ok), "utf8");
6008
+ return readFileSync2(join14(root, inside.ok), "utf8");
5855
6009
  } catch {
5856
6010
  return null;
5857
6011
  }
@@ -5866,7 +6020,7 @@ var isFile = (path) => {
5866
6020
  function resolveSheet(root, from, specifier, aliases) {
5867
6021
  let rel = null;
5868
6022
  if (specifier.startsWith(".") && !within(root, from)) {
5869
- const target = resolve5(dirname5(from), specifier);
6023
+ const target = resolve6(dirname6(from), specifier);
5870
6024
  if (!inPackage(target)) return { refused: `"${specifier}" leaves the package ${displayPath(root, from)}` };
5871
6025
  return existsSync3(target) ? { path: target } : null;
5872
6026
  }
@@ -5878,11 +6032,11 @@ function resolveSheet(root, from, specifier, aliases) {
5878
6032
  if (rel !== null) {
5879
6033
  const inside = relativeInside(root, rel);
5880
6034
  if ("refused" in inside) return { refused: `"${specifier}" is outside the project root` };
5881
- return existsSync3(join13(root, inside.ok)) ? { path: join13(root, inside.ok) } : null;
6035
+ return existsSync3(join14(root, inside.ok)) ? { path: join14(root, inside.ok) } : null;
5882
6036
  }
5883
6037
  if (!packageUnderRoot(root, specifier)) return packageName(specifier) ? { refused: `"${specifier}" is not a package in the project's node_modules` } : null;
5884
6038
  try {
5885
- return { path: createRequire2(join13(root, "package.json")).resolve(specifier) };
6039
+ return { path: createRequire2(join14(root, "package.json")).resolve(specifier) };
5886
6040
  } catch {
5887
6041
  return null;
5888
6042
  }
@@ -5915,7 +6069,7 @@ function importedSheets(root, files, tsconfig, warnings) {
5915
6069
  if (source === null) continue;
5916
6070
  if (file.endsWith(".astro")) source = /^\s*---\r?\n([\s\S]*?)\r?\n---/.exec(source)?.[1] ?? "";
5917
6071
  for (const match of source.matchAll(CSS_IMPORT)) {
5918
- const sheet = resolveSheet(root, join13(root, file), match[1], aliases);
6072
+ const sheet = resolveSheet(root, join14(root, file), match[1], aliases);
5919
6073
  if (sheet && "refused" in sheet) warnings.push(`${file}: refused import ${sheet.refused}`);
5920
6074
  else if (sheet && !found.some((f) => f.path === sheet.path)) found.push({ path: sheet.path, sheet: displayPath(root, sheet.path), via: `import in ${file}` });
5921
6075
  }
@@ -5959,13 +6113,13 @@ function sourceReadSheets(root, files, warnings) {
5959
6113
  for (const candidate of candidates) {
5960
6114
  const inside = relativeInside(root, candidate);
5961
6115
  if ("refused" in inside) refused ??= inside.refused;
5962
- else if (isFile(join13(root, inside.ok))) {
6116
+ else if (isFile(join14(root, inside.ok))) {
5963
6117
  sheet = inside.ok;
5964
6118
  break;
5965
6119
  }
5966
6120
  }
5967
6121
  if (sheet !== null) {
5968
- if (!found.some((f) => realOf(f.path) === realOf(join13(root, sheet)))) found.push({ path: join13(root, sheet), sheet, via });
6122
+ if (!found.some((f) => realOf(f.path) === realOf(join14(root, sheet)))) found.push({ path: join14(root, sheet), sheet, via });
5969
6123
  } else if (refused !== null) warnings.push(`${file}: refused ${call ?? (urlPath ? "new URL" : `?${query} import`)} of "${read.path}": ${refused}`);
5970
6124
  }
5971
6125
  }
@@ -5978,9 +6132,9 @@ function tailwindCliBuilds(root) {
5978
6132
  const scripts = readPackage(root).scripts ?? {};
5979
6133
  const builds = [];
5980
6134
  for (const [script, command] of Object.entries(scripts)) {
5981
- for (const run2 of command.split(/&&|\|\||[;&|]/)) {
5982
- if (!/(?:^|[\s/])(?:@tailwindcss\/cli|tailwindcss)(?:@[\w.^~-]+)?(?:\s|$)/.test(run2)) continue;
5983
- const arg = (short2, long) => new RegExp(`(?:\\s-${short2}|\\s--${long})(?:=|\\s+)["']?([^\\s"']+)`).exec(` ${run2}`)?.[1] ?? null;
6135
+ for (const run3 of command.split(/&&|\|\||[;&|]/)) {
6136
+ if (!/(?:^|[\s/])(?:@tailwindcss\/cli|tailwindcss)(?:@[\w.^~-]+)?(?:\s|$)/.test(run3)) continue;
6137
+ const arg = (short2, long) => new RegExp(`(?:\\s-${short2}|\\s--${long})(?:=|\\s+)["']?([^\\s"']+)`).exec(` ${run3}`)?.[1] ?? null;
5984
6138
  const input = arg("i", "input");
5985
6139
  const output = arg("o", "output");
5986
6140
  if (input && output) builds.push({ script, input: posix5.normalize(input), output: posix5.normalize(output) });
@@ -6011,14 +6165,14 @@ function linkTarget(root, file, href, builds, entries) {
6011
6165
  if (built) {
6012
6166
  const input = relativeInside(root, built.input);
6013
6167
  if ("refused" in input) return { refused: `package.json "${built.script}" builds it from ${built.input}, outside the project root` };
6014
- if (isFile(join13(root, input.ok))) return { path: join13(root, input.ok), sheet: input.ok, via: `${where2}, built by package.json "${built.script}"` };
6168
+ if (isFile(join14(root, input.ok))) return { path: join14(root, input.ok), sheet: input.ok, via: `${where2}, built by package.json "${built.script}"` };
6015
6169
  }
6016
- if (path.endsWith(".css") && isFile(join13(root, path))) return { path: join13(root, path), sheet: path, via: where2 };
6170
+ if (path.endsWith(".css") && isFile(join14(root, path))) return { path: join14(root, path), sheet: path, via: where2 };
6017
6171
  const entry = entries.find((e) => basename3(e) === basename3(path));
6018
- if (entry) return { path: join13(root, entry), sheet: entry, via: `${where2}, the Tailwind entry of that name` };
6172
+ if (entry) return { path: join14(root, entry), sheet: entry, via: `${where2}, the Tailwind entry of that name` };
6019
6173
  for (const dir of PUBLIC_DIRS) {
6020
6174
  const publicFile = relativeInside(root, `${dir}/${path}`);
6021
- if ("ok" in publicFile && isFile(join13(root, publicFile.ok))) return { path: join13(root, publicFile.ok), sheet: publicFile.ok, via: where2 };
6175
+ if ("ok" in publicFile && isFile(join14(root, publicFile.ok))) return { path: join14(root, publicFile.ok), sheet: publicFile.ok, via: where2 };
6022
6176
  }
6023
6177
  return null;
6024
6178
  }
@@ -6042,15 +6196,15 @@ function findStylesheets(root, files, options = {}) {
6042
6196
  for (const entry of options.override) {
6043
6197
  const inside = relativeInside(root, entry);
6044
6198
  if ("refused" in inside) warnings.push(`${from} specimens.css: refused ${inside.refused}`);
6045
- else if (!isFile(join13(root, inside.ok))) warnings.push(`${from} specimens.css: ${entry} does not exist`);
6046
- else add({ path: join13(root, inside.ok), sheet: inside.ok, via: `specimens.css in ${from}` });
6199
+ else if (!isFile(join14(root, inside.ok))) warnings.push(`${from} specimens.css: ${entry} does not exist`);
6200
+ else add({ path: join14(root, inside.ok), sheet: inside.ok, via: `specimens.css in ${from}` });
6047
6201
  }
6048
6202
  return { found, remote, warnings, skipped };
6049
6203
  }
6050
6204
  for (const sheet of sourceReadSheets(root, files, warnings)) add(sheet);
6051
6205
  const builds = tailwindCliBuilds(root);
6052
6206
  const sheetsInProject = files.filter((f) => f.endsWith(".css"));
6053
- const entries = sheetsInProject.filter((f) => "ok" in relativeInside(root, f) && isTailwindEntry(join13(root, f)));
6207
+ const entries = sheetsInProject.filter((f) => "ok" in relativeInside(root, f) && isTailwindEntry(join14(root, f)));
6054
6208
  const templates = [...options.templates ?? []].sort((a, b) => Number(b === "index.html") - Number(a === "index.html") || a.split("/").length - b.split("/").length || a.localeCompare(b));
6055
6209
  const modules = files.filter((f) => MODULE.test(f)).sort();
6056
6210
  for (const file of [...templates, ...modules]) {
@@ -6074,14 +6228,14 @@ function findStylesheets(root, files, options = {}) {
6074
6228
  if (target && "ok" in target) imported.add(target.ok);
6075
6229
  }
6076
6230
  }
6077
- const unreferenced = entries.filter((entry) => !imported.has(entry) && !found.some((f) => realOf(f.path) === realOf(join13(root, entry))));
6231
+ const unreferenced = entries.filter((entry) => !imported.has(entry) && !found.some((f) => realOf(f.path) === realOf(join14(root, entry))));
6078
6232
  if (found.some((f) => isTailwindEntry(f.path))) skipped.push(...unreferenced);
6079
- else for (const entry of unreferenced) add({ path: join13(root, entry), sheet: entry, via: "a Tailwind entry no module imports" });
6233
+ else for (const entry of unreferenced) add({ path: join14(root, entry), sheet: entry, via: "a Tailwind entry no module imports" });
6080
6234
  return { found, remote, warnings, skipped };
6081
6235
  }
6082
6236
  function readPackage(root) {
6083
6237
  try {
6084
- return JSON.parse(readFileSync2(join13(root, "package.json"), "utf8"));
6238
+ return JSON.parse(readFileSync2(join14(root, "package.json"), "utf8"));
6085
6239
  } catch {
6086
6240
  return {};
6087
6241
  }
@@ -6091,10 +6245,10 @@ var majorOf = (range) => {
6091
6245
  return match && !/^(?:\*|latest|workspace:|link:|file:)/.test(range ?? "") ? Number(match[1]) : null;
6092
6246
  };
6093
6247
  function resolveFromProject(root, id) {
6094
- const bases = [join13(root, "package.json")];
6248
+ const bases = [join14(root, "package.json")];
6095
6249
  for (const integration of ["@tailwindcss/vite", "@tailwindcss/postcss", "@tailwindcss/cli"]) {
6096
6250
  const dir = packageUnderRoot(root, integration);
6097
- if (dir) bases.push(join13(dir, "package.json"));
6251
+ if (dir) bases.push(join14(dir, "package.json"));
6098
6252
  }
6099
6253
  for (const [i, base] of bases.entries()) {
6100
6254
  if (i === 0 && !packageUnderRoot(root, id)) continue;
@@ -6112,7 +6266,7 @@ function projectTailwind(root) {
6112
6266
  const dir = packageUnderRoot(root, "tailwindcss");
6113
6267
  try {
6114
6268
  if (!dir) throw new Error("not installed");
6115
- version = JSON.parse(readFileSync2(join13(dir, "package.json"), "utf8")).version;
6269
+ version = JSON.parse(readFileSync2(join14(dir, "package.json"), "utf8")).version;
6116
6270
  } catch {
6117
6271
  return { version: null, node: null, oxide: null, note: declared ? `tailwindcss ${declared} is declared but not installed in the project's node_modules: run the project's install` : "no Tailwind" };
6118
6272
  }
@@ -6160,7 +6314,7 @@ function inlineFonts(root, css, warnings) {
6160
6314
  }
6161
6315
  const served = path.replace(/^\/+/, "");
6162
6316
  let refused = false;
6163
- const file = PUBLIC_DIRS.map((dir) => [join13(root, dir), join13(root, dir, served)]).find(([dir, candidate]) => {
6317
+ const file = PUBLIC_DIRS.map((dir) => [join14(root, dir), join14(root, dir, served)]).find(([dir, candidate]) => {
6164
6318
  if (!existsSync3(candidate)) return false;
6165
6319
  if (within(root, candidate) && within(dir, candidate)) return true;
6166
6320
  refused = true;
@@ -6492,7 +6646,7 @@ ${STATES_MARKER}
6492
6646
 
6493
6647
  // ../scanner/src/specimens/render.ts
6494
6648
  import { existsSync as existsSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "node:fs";
6495
- import { isAbsolute as isAbsolute2, join as join14, posix as posix6, resolve as resolve6 } from "node:path";
6649
+ import { isAbsolute as isAbsolute2, join as join15, posix as posix6, resolve as resolve7 } from "node:path";
6496
6650
  import { Worker as Worker2 } from "node:worker_threads";
6497
6651
  function withNamePlaceholders(job) {
6498
6652
  const names = /* @__PURE__ */ new Set(["id", "title", "aria-label", "alt", "children"]);
@@ -6634,7 +6788,7 @@ function checkedTsconfig(root, tsconfig) {
6634
6788
  seen.add(current);
6635
6789
  const inside = relativeInside(root, current);
6636
6790
  if ("refused" in inside) return { refused: `tsconfig ${current}: ${inside.refused}` };
6637
- const file = join14(root, inside.ok);
6791
+ const file = join15(root, inside.ok);
6638
6792
  if (!existsSync4(file)) return first === null ? { path: null } : { refused: `tsconfig ${current} does not exist` };
6639
6793
  first ??= file;
6640
6794
  let extend;
@@ -6651,7 +6805,17 @@ function checkedTsconfig(root, tsconfig) {
6651
6805
  }
6652
6806
  return { path: first };
6653
6807
  }
6808
+ var SERVICE_GONE = /^The service (is no longer running|was stopped)/;
6654
6809
  async function bundleWithin(options, timeoutMs, signal) {
6810
+ try {
6811
+ return await bundleOnce(options, timeoutMs, signal);
6812
+ } catch (error) {
6813
+ if (!(error instanceof Error && SERVICE_GONE.test(error.message))) throw error;
6814
+ await (await import("esbuild")).stop();
6815
+ return await bundleOnce(options, timeoutMs, signal);
6816
+ }
6817
+ }
6818
+ async function bundleOnce(options, timeoutMs, signal) {
6655
6819
  signal?.throwIfAborted();
6656
6820
  const esbuild = await import("esbuild");
6657
6821
  const context = await esbuild.context({ ...options, write: false });
@@ -6770,7 +6934,7 @@ async function renderComponents(root, tsconfig, jobs, scratch, stats, options =
6770
6934
  stdin: { contents: entry, resolveDir: root, loader: "tsx", sourcefile: "youi-specimens.tsx" },
6771
6935
  absWorkingDir: scratch,
6772
6936
  bundle: true,
6773
- outfile: join14(scratch, "bundle.cjs"),
6937
+ outfile: join15(scratch, "bundle.cjs"),
6774
6938
  platform: "node",
6775
6939
  format: "cjs",
6776
6940
  jsx: "automatic",
@@ -6785,7 +6949,7 @@ async function renderComponents(root, tsconfig, jobs, scratch, stats, options =
6785
6949
  );
6786
6950
  for (const input of read) {
6787
6951
  if (/^[a-z-]+:/.test(input)) continue;
6788
- const inside = relativeInside(root, resolve6(scratch, input));
6952
+ const inside = relativeInside(root, resolve7(scratch, input));
6789
6953
  if ("ok" in inside && !inside.ok.split("/").includes("node_modules")) inputs.add(inside.ok);
6790
6954
  }
6791
6955
  return text;
@@ -6826,7 +6990,7 @@ async function renderComponents(root, tsconfig, jobs, scratch, stats, options =
6826
6990
  else renderable.push(job);
6827
6991
  }
6828
6992
  if (code !== null && renderable.length > 0) {
6829
- const bundle = join14(scratch, "bundle.cjs");
6993
+ const bundle = join15(scratch, "bundle.cjs");
6830
6994
  writeFileSync3(bundle, code);
6831
6995
  const results = await renderInWorkers(bundle, renderable, options.renderTimeoutMs ?? RENDER_TIMEOUT_MS, TOTAL_TIMEOUT_MS);
6832
6996
  for (const job of renderable) {
@@ -6842,7 +7006,7 @@ async function renderComponents(root, tsconfig, jobs, scratch, stats, options =
6842
7006
 
6843
7007
  // ../scanner/src/specimens/client.ts
6844
7008
  import { readFileSync as readFileSync4 } from "node:fs";
6845
- import { resolve as resolve7 } from "node:path";
7009
+ import { resolve as resolve8 } from "node:path";
6846
7010
  var ENTRY = "youi-try.js";
6847
7011
  var MAX_SIBLINGS = 6;
6848
7012
  var digestOf = (path) => {
@@ -6919,14 +7083,14 @@ async function bundleForBrowser(root, tsconfig, job, siblings = [], options = {}
6919
7083
  if (!packageUnderRoot(root, "react") || !packageUnderRoot(root, "react-dom")) return { error: "react and react-dom are not installed in the project's node_modules" };
6920
7084
  const looks = siblings.length > 0 ? siblings.slice(0, MAX_SIBLINGS) : [job];
6921
7085
  const wrappers = job.recipe.wrappers;
6922
- const relative5 = /* @__PURE__ */ new Map();
7086
+ const relative6 = /* @__PURE__ */ new Map();
6923
7087
  for (const file of [...looks.map((l) => l.recipe.file), ...wrappers.map((w) => w.file)]) {
6924
7088
  const checked = relativeInside(root, file);
6925
7089
  if ("refused" in checked) return { error: `refused: ${checked.refused}` };
6926
- relative5.set(file, checked.ok);
7090
+ relative6.set(file, checked.ok);
6927
7091
  }
6928
- const inside = (file) => relative5.get(file);
6929
- const files = [...new Set(relative5.values())];
7092
+ const inside = (file) => relative6.get(file);
7093
+ const files = [...new Set(relative6.values())];
6930
7094
  const config = checkedTsconfig(root, tsconfig);
6931
7095
  const refusals = /* @__PURE__ */ new Set();
6932
7096
  const entry = [
@@ -6972,7 +7136,7 @@ async function bundleForBrowser(root, tsconfig, job, siblings = [], options = {}
6972
7136
  const base = realOf(root);
6973
7137
  for (const input of built.inputs) {
6974
7138
  if (input === "<stdin>" || input === ENTRY || /^[\w-]+:/.test(input)) continue;
6975
- const path = resolve7(base, input);
7139
+ const path = resolve8(base, input);
6976
7140
  inputs.push({ path, digest: digestOf(path) ?? "missing" });
6977
7141
  }
6978
7142
  return { hash: sha256(built.text).slice(0, 16), code: built.text, inputs };
@@ -7060,8 +7224,8 @@ var BrowserBundleCache = class {
7060
7224
  };
7061
7225
 
7062
7226
  // ../scanner/src/specimens/theme-fit.ts
7063
- import { readFile as readFile8 } from "node:fs/promises";
7064
- import { join as join15 } from "node:path";
7227
+ import { readFile as readFile9 } from "node:fs/promises";
7228
+ import { join as join16 } from "node:path";
7065
7229
  var SIZES = "xs|sm|base|lg|xl|[2-9]xl";
7066
7230
  var RADII = "none|xs|sm|md|lg|xl|2xl|3xl|4xl|full";
7067
7231
  var RADIUS_CLASS = new RegExp(`^rounded(?:-(?:t|r|b|l|s|e|tl|tr|br|bl|ss|se|es|ee))?(?:-(${RADII}|\\[[^\\]]+\\]))?$`);
@@ -7173,16 +7337,16 @@ function themeFit({ theme, item, project, cssVars }) {
7173
7337
  async function projectTheme(root) {
7174
7338
  const target = await loadInventoryTarget(root);
7175
7339
  const files = target.files.filter((f) => !("refused" in relativeInside(root, f)));
7176
- const sheets = await Promise.all(files.filter((f) => f.endsWith(".css")).map(async (f) => readStylesheet(f, await readFile8(join15(root, f), "utf8").catch(() => ""))));
7177
- const pkg = JSON.parse(await readFile8(join15(root, "package.json"), "utf8").catch(() => "{}"));
7340
+ const sheets = await Promise.all(files.filter((f) => f.endsWith(".css")).map(async (f) => readStylesheet(f, await readFile9(join16(root, f), "utf8").catch(() => ""))));
7341
+ const pkg = JSON.parse(await readFile9(join16(root, "package.json"), "utf8").catch(() => "{}"));
7178
7342
  const theme = await buildTheme(root, sheets, Boolean(pkg.dependencies?.tailwindcss ?? pkg.devDependencies?.tailwindcss));
7179
- const sources = await Promise.all(files.filter((f) => /\.(?:tsx|jsx)$/.test(f)).slice(0, 400).map((f) => readFile8(join15(root, f), "utf8").catch(() => "")));
7343
+ const sources = await Promise.all(files.filter((f) => /\.(?:tsx|jsx)$/.test(f)).slice(0, 400).map((f) => readFile9(join16(root, f), "utf8").catch(() => "")));
7180
7344
  return { theme, sources };
7181
7345
  }
7182
7346
 
7183
7347
  // ../scanner/src/specimens/preview-bundle.ts
7184
7348
  import { existsSync as existsSync5, readFileSync as readFileSync5 } from "node:fs";
7185
- import { basename as basename4, dirname as dirname6, isAbsolute as isAbsolute3, join as join16, resolve as resolve8, sep as sep2 } from "node:path";
7349
+ import { basename as basename4, dirname as dirname7, isAbsolute as isAbsolute3, join as join17, resolve as resolve9, sep as sep2 } from "node:path";
7186
7350
  var NAMESPACE2 = "youi-preview";
7187
7351
  var GUARDED2 = "youiPreviewGuarded";
7188
7352
  var ENTRY2 = "youi-preview.js";
@@ -7341,7 +7505,7 @@ function projectCn(root, tsconfig) {
7341
7505
  for (const [prefix, target] of tsconfigAliases(root, tsconfig)) {
7342
7506
  if (prefix !== "@/" && prefix !== "~/") continue;
7343
7507
  for (const ext of [".ts", ".tsx", ".js"]) {
7344
- const file = join16(root, target, `lib/utils${ext}`);
7508
+ const file = join17(root, target, `lib/utils${ext}`);
7345
7509
  if (existsSync5(file) && within(root, file) && /\bexport\s+(?:function|const|let)\s+cn\b|\bexport\s*\{[^}]*\bcn\b/.test(readFileSync5(file, "utf8"))) return file;
7346
7510
  }
7347
7511
  }
@@ -7351,7 +7515,7 @@ async function bundlePreview(job, options = {}) {
7351
7515
  const { root, dir, fallback = null } = job;
7352
7516
  try {
7353
7517
  if (!packageUnderRoot(root, "react") || !packageUnderRoot(root, "react-dom")) return { error: "react and react-dom are not installed in your project's node_modules, so nothing can be drawn" };
7354
- if (!existsSync5(join16(dir, job.main))) return { error: "the preview's files are missing; open it again" };
7518
+ if (!existsSync5(join17(dir, job.main))) return { error: "the preview's files are missing; open it again" };
7355
7519
  const config = checkedTsconfig(root, job.tsconfig);
7356
7520
  const aliases = tsconfigAliases(root, job.tsconfig);
7357
7521
  const notes = /* @__PURE__ */ new Set();
@@ -7360,7 +7524,7 @@ async function bundlePreview(job, options = {}) {
7360
7524
  const radix = /* @__PURE__ */ new Map();
7361
7525
  const cnFile = projectCn(root, job.tsconfig);
7362
7526
  const partFiles = new Set((job.projectParts ?? []).filter((file) => within(root, file)).map(realOf));
7363
- const codeRoots = [dir, ...aliases.map(([, target]) => join16(root, target)), join16(root, "node_modules"), ...fallback ? [join16(fallback, "node_modules")] : []];
7527
+ const codeRoots = [dir, ...aliases.map(([, target]) => join17(root, target)), join17(root, "node_modules"), ...fallback ? [join17(fallback, "node_modules")] : []];
7364
7528
  const previewRefusal = (spec, path) => {
7365
7529
  if (!PREVIEW_CODE.test(path)) return `a preview may import code only (.ts, .tsx, .js, .jsx, .mjs, .cjs, .css), not ${basename4(path)}`;
7366
7530
  const name = packageName(spec);
@@ -7418,7 +7582,7 @@ async function bundlePreview(job, options = {}) {
7418
7582
  const at = args.importer.lastIndexOf(`${sep2}node_modules${sep2}`) + `${sep2}node_modules${sep2}`.length;
7419
7583
  const rest = args.importer.slice(at).split(sep2);
7420
7584
  const base = args.importer.slice(0, at) + rest.slice(0, rest[0].startsWith("@") ? 2 : 1).join(sep2);
7421
- const target = resolve8(dirname6(args.importer), args.path);
7585
+ const target = resolve9(dirname7(args.importer), args.path);
7422
7586
  if (target.startsWith(base + sep2)) return void 0;
7423
7587
  }
7424
7588
  const again = (path, resolveDir) => build.resolve(path, { importer: args.importer, resolveDir, kind: args.kind, pluginData: { [GUARDED2]: true }, with: args.with });
@@ -7428,7 +7592,7 @@ async function bundlePreview(job, options = {}) {
7428
7592
  const name = packageName(spec);
7429
7593
  const alias = aliases.find(([prefix]) => spec.startsWith(prefix) && prefix.endsWith("/"));
7430
7594
  if (alias) {
7431
- result = await again(join16(realOf(root), alias[1], spec.slice(alias[0].length)), realOf(root));
7595
+ result = await again(join17(realOf(root), alias[1], spec.slice(alias[0].length)), realOf(root));
7432
7596
  if (result.errors.length > 0) {
7433
7597
  refusals.add(`it needs ${spec}, which your project does not have`);
7434
7598
  return { errors: [{ text: `missing ${spec}` }] };
@@ -7506,7 +7670,7 @@ async function bundlePreview(job, options = {}) {
7506
7670
  const inputs = [];
7507
7671
  for (const input of built.inputs) {
7508
7672
  if (input === "<stdin>" || input === ENTRY2 || /^[\w-]+:/.test(input)) continue;
7509
- const path = resolve8(realOf(dir), input);
7673
+ const path = resolve9(realOf(dir), input);
7510
7674
  inputs.push({ path, digest: digestOf2(path) ?? "missing" });
7511
7675
  }
7512
7676
  return { hash: sha256(built.text).slice(0, 16), code: built.text, inputs, notes: [...notes] };
@@ -7591,12 +7755,12 @@ async function projectSpecimens(project, options = {}) {
7591
7755
  }
7592
7756
 
7593
7757
  // ../scanner/src/specimens/shots.ts
7594
- import { execFile as execFile2 } from "node:child_process";
7595
- import { createHash as createHash2 } from "node:crypto";
7758
+ import { execFile as execFile3 } from "node:child_process";
7759
+ import { createHash as createHash3 } from "node:crypto";
7596
7760
  import { existsSync as existsSync6, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync6, rmSync as rmSync3, statSync as statSync3, writeFileSync as writeFileSync4 } from "node:fs";
7597
7761
  import { createRequire as createRequire3 } from "node:module";
7598
- import { dirname as dirname7, extname as extname2, isAbsolute as isAbsolute4, join as join17, relative as relative4 } from "node:path";
7599
- import { promisify as promisify2 } from "node:util";
7762
+ import { dirname as dirname8, extname as extname2, isAbsolute as isAbsolute4, join as join18, relative as relative5 } from "node:path";
7763
+ import { promisify as promisify3 } from "node:util";
7600
7764
  var SHOTS_FORMAT = "youiyoux.shots/4";
7601
7765
  var SHOT_WIDTHS = [1280, 390];
7602
7766
  var MAX_LOOKS = 8;
@@ -7607,15 +7771,15 @@ var newShotStats = () => ({ ...newStats(), renders: 0, baseRenders: 0, browsers:
7607
7771
  var RenderUnavailableError = class extends Error {
7608
7772
  name = "RenderUnavailableError";
7609
7773
  };
7610
- var exec = promisify2(execFile2);
7774
+ var exec = promisify3(execFile3);
7611
7775
  var slug = (text) => `${text.replace(/[^a-z0-9]+/gi, "-").replace(/^-+|-+$/g, "").slice(-48) || "component"}-${sha256(text).slice(0, 8)}`;
7612
7776
  var SOURCE = /\.(?:tsx|ts|jsx|js|mjs|cjs|mts|cts)$/;
7613
7777
  var PAGE_BACKGROUND = "html{background-color:Canvas;color:CanvasText}";
7614
7778
  var PAGE_CSS = `body{margin:0}.youi-shots{display:flex;flex-direction:column;align-items:flex-start;gap:20px;padding:24px;box-sizing:border-box;max-width:100%}.youi-look{max-width:100%}.youi-caption{margin:0 0 6px;font:12px/16px ui-monospace,SFMono-Regular,Menlo,monospace;color:#888}*,*::before,*::after{animation:none!important;transition:none!important;caret-color:transparent!important}`;
7615
- async function git(root, args) {
7779
+ async function git2(root, args) {
7616
7780
  return (await exec("git", [...args], { cwd: root, maxBuffer: 64 * 1024 * 1024, encoding: "utf8" })).stdout;
7617
7781
  }
7618
- var gitOr = (root, args) => git(root, args).catch(() => null);
7782
+ var gitOr = (root, args) => git2(root, args).catch(() => null);
7619
7783
  async function resolveBase(root, asked) {
7620
7784
  if ((await gitOr(root, ["rev-parse", "--is-inside-work-tree"]))?.trim() !== "true") return { none: "no base: not a git checkout" };
7621
7785
  if (asked !== void 0) {
@@ -7630,8 +7794,8 @@ async function resolveBase(root, asked) {
7630
7794
  return head ? { ref: "HEAD", commit: head } : { none: "no base: the repository has no commits yet" };
7631
7795
  }
7632
7796
  async function changedSince(root, commit) {
7633
- const tracked = await git(root, ["diff", "--name-only", "-z", "--relative", commit, "--"]);
7634
- const untracked = await git(root, ["ls-files", "--others", "--exclude-standard", "-z", "--", "."]);
7797
+ const tracked = await git2(root, ["diff", "--name-only", "-z", "--relative", commit, "--"]);
7798
+ const untracked = await git2(root, ["ls-files", "--others", "--exclude-standard", "-z", "--", "."]);
7635
7799
  return new Set([...tracked.split("\0"), ...untracked.split("\0")].filter(Boolean));
7636
7800
  }
7637
7801
  var atBase = (root, commit, file) => gitOr(root, ["show", `${commit}:./${file}`]);
@@ -7694,7 +7858,7 @@ function groupOf(root, rawLooks, target, file) {
7694
7858
  if (sited.length > 0) return { name: target, file, looks: distinct(sited.map((l) => ({ recipe: l.recipe, role: l.role }))), notes: [`no site renders a component defined in ${file}: drawn are the looks its own markup renders`] };
7695
7859
  let source = "";
7696
7860
  try {
7697
- source = SOURCE.test(file) ? readFileSync6(join17(root, file), "utf8") : "";
7861
+ source = SOURCE.test(file) ? readFileSync6(join18(root, file), "utf8") : "";
7698
7862
  } catch {
7699
7863
  }
7700
7864
  const names = exportedComponents(source);
@@ -7754,7 +7918,7 @@ var Eyes = class {
7754
7918
  }
7755
7919
  async open() {
7756
7920
  if (this.browser) return this.browser;
7757
- const { launchBrowser } = await import("./src-VL2ZYO6X.js");
7921
+ const { launchBrowser } = await import("./src-YYI52B5R.js");
7758
7922
  try {
7759
7923
  this.browser = await launchBrowser(this.options.executablePath, { purpose: { what: "youiyoux render", command: "youiyoux render" }, ...this.options.download === false ? { download: false } : {}, ...this.options.log ? { log: this.options.log } : {} });
7760
7924
  } catch (error) {
@@ -7781,7 +7945,7 @@ var Eyes = class {
7781
7945
  if (audit) await page.addScriptTag({ content: this.axeSource() });
7782
7946
  for (const width of SHOT_WIDTHS) {
7783
7947
  await page.setViewportSize({ width, height: VIEWPORT_HEIGHT });
7784
- const path = join17(dir, `${scheme}-${width}.png`);
7948
+ const path = join18(dir, `${scheme}-${width}.png`);
7785
7949
  await page.screenshot({ path, fullPage: true, animations: "disabled", caret: "hide" });
7786
7950
  shots.push({ scheme, width, path });
7787
7951
  if (audit) {
@@ -7817,11 +7981,11 @@ var Eyes = class {
7817
7981
  };
7818
7982
  function issuesOf(runs, looks) {
7819
7983
  const entries = /* @__PURE__ */ new Map();
7820
- for (const run2 of runs) for (const v of run2.found) for (const node2 of v.nodes) {
7984
+ for (const run3 of runs) for (const v of run3.found) for (const node2 of v.nodes) {
7821
7985
  const key = `${v.id}\0${node2.look}\0${node2.path}`;
7822
7986
  const entry = entries.get(key) ?? { v, node: node2, schemes: /* @__PURE__ */ new Set(), widths: /* @__PURE__ */ new Set() };
7823
- entry.schemes.add(run2.scheme);
7824
- entry.widths.add(run2.width);
7987
+ entry.schemes.add(run3.scheme);
7988
+ entry.widths.add(run3.width);
7825
7989
  entries.set(key, entry);
7826
7990
  }
7827
7991
  const order = ["critical", "serious", "moderate", "minor"];
@@ -7857,10 +8021,10 @@ function compareA11yIssues(after, before2) {
7857
8021
  return [...result, ...remaining.map((issue) => ({ ...issue, status: "fixed" }))];
7858
8022
  }
7859
8023
  function contentHash(root, files) {
7860
- const hash = createHash2("sha256");
8024
+ const hash = createHash3("sha256");
7861
8025
  for (const file of [...new Set(files)].sort()) {
7862
8026
  hash.update(`${file}\0`);
7863
- const path = join17(root, file);
8027
+ const path = join18(root, file);
7864
8028
  try {
7865
8029
  if (statSync3(path).isFile()) hash.update(readFileSync6(path));
7866
8030
  } catch {
@@ -7879,7 +8043,7 @@ function readManifest(file) {
7879
8043
  }
7880
8044
  }
7881
8045
  function prune(dir, prefix, keep) {
7882
- for (const name of existsSync6(dir) ? readdirSync2(dir) : []) if (name.startsWith(prefix) && name !== keep) rmSync3(join17(dir, name), { recursive: true, force: true });
8046
+ for (const name of existsSync6(dir) ? readdirSync2(dir) : []) if (name.startsWith(prefix) && name !== keep) rmSync3(join18(dir, name), { recursive: true, force: true });
7883
8047
  }
7884
8048
  var allExist = (paths) => paths.every((p) => existsSync6(p));
7885
8049
  async function renderShots(rootDir, targets, options = {}) {
@@ -7890,7 +8054,7 @@ async function renderShots(rootDir, targets, options = {}) {
7890
8054
  const target = await loadInventoryTarget(root);
7891
8055
  const report = await buildInventory([root], { cwd: root });
7892
8056
  const project = report.projects[0]?.id ?? target.id;
7893
- const dir = join17(cache.dir, "shots", `${slug(project)}-${sha256(root).slice(0, 8)}`);
8057
+ const dir = join18(cache.dir, "shots", `${slug(project)}-${sha256(root).slice(0, 8)}`);
7894
8058
  const notes = [];
7895
8059
  const base = await resolveBase(root, options.base);
7896
8060
  const baseNote = "none" in base ? base.none : null;
@@ -7900,21 +8064,21 @@ async function renderShots(rootDir, targets, options = {}) {
7900
8064
  const unchanged = [];
7901
8065
  if (targets.length > 0) {
7902
8066
  for (const asked of targets) {
7903
- const path = isAbsolute4(asked) ? asked : join17(root, asked);
8067
+ const path = isAbsolute4(asked) ? asked : join18(root, asked);
7904
8068
  const fileLike = /[\\/]/.test(asked) || extname2(asked) !== "" || existsSync6(path);
7905
8069
  if (!fileLike) {
7906
8070
  groups.push(groupOf(root, sited, asked, null));
7907
8071
  continue;
7908
8072
  }
7909
- const inside = relativeInside(root, relative4(root, realOf(path)));
8073
+ const inside = relativeInside(root, relative5(root, realOf(path)));
7910
8074
  if ("refused" in inside) groups.push({ name: asked, file: null, looks: [], notes: [`${asked}: ${inside.refused}; pass a file inside the project`] });
7911
- else if (!existsSync6(join17(root, inside.ok))) groups.push({ name: asked, file: null, looks: [], notes: [`${inside.ok} does not exist in the project`] });
8075
+ else if (!existsSync6(join18(root, inside.ok))) groups.push({ name: asked, file: null, looks: [], notes: [`${inside.ok} does not exist in the project`] });
7912
8076
  else groups.push(groupOf(root, sited, inside.ok, inside.ok));
7913
8077
  }
7914
8078
  } else {
7915
8079
  if (baseNote) notes.push(`${baseNote}: with no target, nothing is known to have changed; pass a file or component name`);
7916
8080
  const files = [...new Set(sited.flatMap((l) => l.recipe?.kind === "component" ? [l.recipe.file] : []))].sort();
7917
- const saved = existsSync6(dir) ? readdirSync2(dir).map((name) => readManifest(join17(dir, name, "manifest.json"))).filter((m) => m !== null) : [];
8081
+ const saved = existsSync6(dir) ? readdirSync2(dir).map((name) => readManifest(join18(dir, name, "manifest.json"))).filter((m) => m !== null) : [];
7918
8082
  const touched = (inputs) => inputs.some((f) => changed.has(f));
7919
8083
  for (const file of files) {
7920
8084
  const wrappersOf = sited.flatMap((l) => l.recipe?.kind === "component" && l.recipe.file === file ? l.recipe.wrappers.map((w) => w.file) : []);
@@ -7930,12 +8094,12 @@ async function renderShots(rootDir, targets, options = {}) {
7930
8094
  const components = [];
7931
8095
  const pending = [];
7932
8096
  for (const group of groups) {
7933
- const groupDir = join17(dir, slug(group.file ?? group.name));
8097
+ const groupDir = join18(dir, slug(group.file ?? group.name));
7934
8098
  if (group.looks.length === 0) {
7935
8099
  components.push({ component: group.name, file: group.file, looks: [], shots: [], a11y: [], diff: { none: "nothing drawn" }, cached: false, notes: group.notes });
7936
8100
  continue;
7937
8101
  }
7938
- const manifest = readManifest(join17(groupDir, "manifest.json"));
8102
+ const manifest = readManifest(join18(groupDir, "manifest.json"));
7939
8103
  if (manifest && manifest.key === keyOf2(group, manifest.inputs) && allExist([...manifest.result.shots.map((s) => s.path), ..."shots" in manifest.result.diff ? manifest.result.diff.shots.flatMap((d) => [d.before, d.after, d.diff]) : []])) {
7940
8104
  stats.hits++;
7941
8105
  components.push({ ...manifest.result, component: group.name, cached: true });
@@ -7953,13 +8117,13 @@ async function renderShots(rootDir, targets, options = {}) {
7953
8117
  if ("commit" in base) {
7954
8118
  const sources = [...changed].filter((f) => SOURCE.test(f)).sort();
7955
8119
  if (sources.length > MAX_OVERLAY) notes.push(`${sources.length} source files differ from ${base.ref}: the first ${MAX_OVERLAY} are drawn as they were there, the rest as they are now`);
7956
- baseSources = join17(dir, `base-${base.commit.slice(0, 12)}`);
8120
+ baseSources = join18(dir, `base-${base.commit.slice(0, 12)}`);
7957
8121
  for (const file of sources.slice(0, MAX_OVERLAY)) {
7958
8122
  const contents = await atBase(root, base.commit, file);
7959
- for (const key of [join17(root, file), join17(rootDir, file)]) overlay.set(key, contents);
8123
+ for (const key of [join18(root, file), join18(rootDir, file)]) overlay.set(key, contents);
7960
8124
  if (contents !== null) {
7961
- const copy = join17(baseSources, file);
7962
- mkdirSync2(dirname7(copy), { recursive: true });
8125
+ const copy = join18(baseSources, file);
8126
+ mkdirSync2(dirname8(copy), { recursive: true });
7963
8127
  writeFileSync4(copy, contents);
7964
8128
  }
7965
8129
  }
@@ -7977,7 +8141,7 @@ async function renderShots(rootDir, targets, options = {}) {
7977
8141
  }
7978
8142
  const namedJobs = jobs.map(withNamePlaceholders);
7979
8143
  const draw = async (side) => {
7980
- const scratch = join17(groupDir, `build-${side}`);
8144
+ const scratch = join18(groupDir, `build-${side}`);
7981
8145
  mkdirSync2(scratch, { recursive: true });
7982
8146
  try {
7983
8147
  const rendered = await renderComponents(root, target.tsconfig, namedJobs, scratch, stats, side === "before" ? { overlay } : {});
@@ -7997,7 +8161,7 @@ async function renderShots(rootDir, targets, options = {}) {
7997
8161
  const inputs = [.../* @__PURE__ */ new Set([...after.inputs, ...group.file ? [group.file] : []])].sort();
7998
8162
  const key = keyOf2(group, inputs);
7999
8163
  const pages = (looks) => ({ light: pageHtml(group.name, sheetOf("light"), "light", looks), dark: pageHtml(group.name, sheetOf("dark"), "dark", looks) });
8000
- const afterDir = join17(groupDir, `after-${key}`);
8164
+ const afterDir = join18(groupDir, `after-${key}`);
8001
8165
  const shot = await eyes.shoot(afterDir, pages(after.looks), true);
8002
8166
  stats.renders++;
8003
8167
  prune(groupDir, "after-", `after-${key}`);
@@ -8014,9 +8178,9 @@ async function renderShots(rootDir, targets, options = {}) {
8014
8178
  } else {
8015
8179
  const identity = group.looks.map((l) => l.recipe.kind === "component" ? [l.key, l.dynamic, l.namedAtEverySite, l.recipe.file, l.recipe.export, l.recipe.props, l.recipe.label, l.recipe.wrappers, l.role] : [l.recipe, l.role]);
8016
8180
  const beforeKey = sha256(`${common}\0${JSON.stringify(identity)}\0${JSON.stringify(inputs)}`).slice(0, 24);
8017
- const beforeDir = join17(groupDir, `before-${beforeKey}`);
8018
- let beforeShots = SCHEMES.flatMap((scheme) => SHOT_WIDTHS.map((width) => ({ scheme, width, path: join17(beforeDir, `${scheme}-${width}.png`) })));
8019
- const issuesFile = join17(beforeDir, "issues.json");
8181
+ const beforeDir = join18(groupDir, `before-${beforeKey}`);
8182
+ let beforeShots = SCHEMES.flatMap((scheme) => SHOT_WIDTHS.map((width) => ({ scheme, width, path: join18(beforeDir, `${scheme}-${width}.png`) })));
8183
+ const issuesFile = join18(beforeDir, "issues.json");
8020
8184
  if (!allExist([...beforeShots.map((s) => s.path), issuesFile])) {
8021
8185
  const before2 = await draw("before");
8022
8186
  for (const look of before2.looks) if (look.failed && look.recipe.kind === "component") groupNotes.push(`before (${base.ref}): ${look.recipe.file}#${look.recipe.export} did not render (${look.failed})`);
@@ -8030,7 +8194,7 @@ async function renderShots(rootDir, targets, options = {}) {
8030
8194
  const shots = [];
8031
8195
  for (const s of shot.shots) {
8032
8196
  const b = beforeShots.find((x) => x.scheme === s.scheme && x.width === s.width);
8033
- const out = join17(afterDir, `diff-${s.scheme}-${s.width}.png`);
8197
+ const out = join18(afterDir, `diff-${s.scheme}-${s.width}.png`);
8034
8198
  shots.push({ scheme: s.scheme, width: s.width, before: b.path, after: s.path, diff: out, changed: await eyes.diff(b.path, s.path, out) });
8035
8199
  }
8036
8200
  diff = { base: `${base.ref} (${base.commit.slice(0, 7)})`, shots };
@@ -8039,7 +8203,7 @@ async function renderShots(rootDir, targets, options = {}) {
8039
8203
  }
8040
8204
  if (changedSheets.length > 0 && "shots" in diff) groupNotes.push(`stylesheets changed since ${"commit" in base ? base.ref : "the base"} (${changedSheets.join(", ")}): both sides use today's CSS, so the diff shows component changes only`);
8041
8205
  const result = { component: group.name, file: group.file, looks: after.looks.map((l) => l.caption), shots: shot.shots, a11y, diff, cached: false, notes: groupNotes };
8042
- writeJsonAtomic(join17(groupDir, "manifest.json"), { format: SHOTS_FORMAT, name: group.name, file: group.file, key, inputs, result });
8206
+ writeJsonAtomic(join18(groupDir, "manifest.json"), { format: SHOTS_FORMAT, name: group.name, file: group.file, key, inputs, result });
8043
8207
  components[index] = result;
8044
8208
  }
8045
8209
  } finally {
@@ -8083,7 +8247,7 @@ function shotsText(report, again = (t) => `run youiyoux render${t ? ` ${t}` : ""
8083
8247
  const moved = c.diff.shots.filter((d) => d.changed > 0);
8084
8248
  lines.push(moved.length === 0 ? ` before/after against ${c.diff.base}: no visible change` : ` before/after against ${c.diff.base}: pixels changed ${moved.map((d) => `${percent(d.changed)} in ${d.scheme} ${d.width}`).join(", ")}`);
8085
8249
  for (const d of moved) lines.push(` diff ${d.scheme} ${d.width}: ${d.diff}`);
8086
- if (c.diff.shots[0]) lines.push(` before images: ${dirname7(c.diff.shots[0].before)}`);
8250
+ if (c.diff.shots[0]) lines.push(` before images: ${dirname8(c.diff.shots[0].before)}`);
8087
8251
  } else lines.push(` before/after: ${c.diff.none}`);
8088
8252
  if (c.a11y.length === 0) lines.push(" accessibility: no issues axe can find (WCAG 2.2 A and AA, both schemes and widths)");
8089
8253
  else {
@@ -8107,8 +8271,8 @@ function shotsText(report, again = (t) => `run youiyoux render${t ? ` ${t}` : ""
8107
8271
 
8108
8272
  // ../scanner/src/screens/capture.ts
8109
8273
  import { readFileSync as readFileSync7 } from "node:fs";
8110
- import { rename, writeFile } from "node:fs/promises";
8111
- import { join as join18 } from "node:path";
8274
+ import { rename as rename2, writeFile } from "node:fs/promises";
8275
+ import { join as join19 } from "node:path";
8112
8276
 
8113
8277
  // ../scanner/src/screens/locate.ts
8114
8278
  import { realpathSync } from "node:fs";
@@ -8285,8 +8449,8 @@ function projectPath(source, moduleUrl, root) {
8285
8449
  if (path.startsWith(prefix)) return posix7.normalize(path.slice(prefix.length));
8286
8450
  }
8287
8451
  if (/^\/(?:Users|home|private|var|tmp|opt|mnt|[A-Za-z]:)\//.test(path)) return null;
8288
- const relative5 = posix7.normalize(path.replace(/^\/+/, ""));
8289
- return relative5.startsWith("..") || relative5 === "." ? null : relative5;
8452
+ const relative6 = posix7.normalize(path.replace(/^\/+/, ""));
8453
+ return relative6.startsWith("..") || relative6 === "." ? null : relative6;
8290
8454
  }
8291
8455
  function realOrSelf(path) {
8292
8456
  try {
@@ -8349,7 +8513,7 @@ function screenSlug(route) {
8349
8513
  return route.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "root";
8350
8514
  }
8351
8515
  function readScreensSpec(dir) {
8352
- const file = join18(dir, "screens.json");
8516
+ const file = join19(dir, "screens.json");
8353
8517
  let value;
8354
8518
  try {
8355
8519
  value = JSON.parse(readFileSync7(file, "utf8"));
@@ -8371,7 +8535,7 @@ function readScreensSpec(dir) {
8371
8535
  async function writeBytes(file, bytes) {
8372
8536
  const temporary = `${file}.${process.pid}.tmp`;
8373
8537
  await writeFile(temporary, bytes);
8374
- await rename(temporary, file);
8538
+ await rename2(temporary, file);
8375
8539
  }
8376
8540
  async function encode(page, png, scale) {
8377
8541
  const url = await page.evaluate(
@@ -8396,7 +8560,7 @@ var sentence = (error) => (error instanceof Error ? error.message : String(error
8396
8560
  async function captureScreens(dir, options) {
8397
8561
  const spec = readScreensSpec(dir);
8398
8562
  const unknown = (options.routes ?? []).filter((route) => !spec.screens.some((s) => s.route === route));
8399
- if (unknown.length) throw new Error(`${join18(dir, "screens.json")} has no route ${unknown.join(", ")}: it lists ${spec.screens.map((s) => s.route).join(", ")}`);
8563
+ if (unknown.length) throw new Error(`${join19(dir, "screens.json")} has no route ${unknown.join(", ")}: it lists ${spec.screens.map((s) => s.route).join(", ")}`);
8400
8564
  const chosen = options.routes?.length ? spec.screens.filter((s) => options.routes.includes(s.route)) : spec.screens;
8401
8565
  const log = options.log ?? (() => void 0);
8402
8566
  const { width, height } = spec.viewport;
@@ -8427,8 +8591,8 @@ async function captureScreens(dir, options) {
8427
8591
  const pageHeight = located?.size.height ?? await page.evaluate(() => document.documentElement.scrollHeight);
8428
8592
  const clip = { x: 0, y: 0, width, height: Math.min(Math.max(pageHeight, height), height * MAX_VIEWPORTS) };
8429
8593
  const png = await page.screenshot({ fullPage: true, clip });
8430
- await writeBytes(join18(dir, `${slug2}.${scheme}.webp`), await encode(encoder, png, 1));
8431
- await writeBytes(join18(dir, `${slug2}.${scheme}.thumb.webp`), await encode(encoder, png, THUMB_SCALE / PICTURE_SCALE));
8594
+ await writeBytes(join19(dir, `${slug2}.${scheme}.webp`), await encode(encoder, png, 1));
8595
+ await writeBytes(join19(dir, `${slug2}.${scheme}.thumb.webp`), await encode(encoder, png, THUMB_SCALE / PICTURE_SCALE));
8432
8596
  if (located) {
8433
8597
  react ||= located.react;
8434
8598
  dev ||= located.dev;
@@ -8437,7 +8601,7 @@ async function captureScreens(dir, options) {
8437
8601
  return response?.ok ? response.text() : null;
8438
8602
  }, options.root ?? null, notes);
8439
8603
  const file = { schema: PLACES_SCHEMA, route: screen.route, path: screen.path, viewport: spec.viewport, size: { width, height: clip.height }, dev: located.dev, capped: located.capped, places: places.filter((p) => p.box.y < clip.height) };
8440
- await writeFileAtomic(join18(dir, `${slug2}.places.json`), `${JSON.stringify(file)}
8604
+ await writeFileAtomic(join19(dir, `${slug2}.places.json`), `${JSON.stringify(file)}
8441
8605
  `);
8442
8606
  results.set(screen.route, { placed: file.places.length, lined: file.places.filter((p) => p.line !== null).length, error: null });
8443
8607
  }
@@ -8452,9 +8616,9 @@ async function captureScreens(dir, options) {
8452
8616
  }
8453
8617
  }
8454
8618
  const now = (options.now ?? (() => /* @__PURE__ */ new Date()))().toISOString();
8455
- if (chosen === spec.screens) await writeFileAtomic(join18(dir, "screens.json"), `${JSON.stringify({ ...spec, capturedAt: now, base: options.base }, null, 2)}
8619
+ if (chosen === spec.screens) await writeFileAtomic(join19(dir, "screens.json"), `${JSON.stringify({ ...spec, capturedAt: now, base: options.base }, null, 2)}
8456
8620
  `);
8457
- else if (spec.base !== options.base) await writeFileAtomic(join18(dir, "screens.json"), `${JSON.stringify({ ...spec, base: options.base }, null, 2)}
8621
+ else if (spec.base !== options.base) await writeFileAtomic(join19(dir, "screens.json"), `${JSON.stringify({ ...spec, base: options.base }, null, 2)}
8458
8622
  `);
8459
8623
  const screens = chosen.map((s) => ({ route: s.route, path: s.path, ...results.get(s.route) ?? { placed: 0, lined: 0, error: "not captured" } }));
8460
8624
  const placed = screens.reduce((n, s) => n + s.placed, 0);
@@ -8476,9 +8640,9 @@ function captureText(report) {
8476
8640
  // ../scanner/src/screens/session.ts
8477
8641
  import { randomBytes } from "node:crypto";
8478
8642
  import { existsSync as existsSync7, realpathSync as realpathSync2 } from "node:fs";
8479
- import { chmod, mkdir, rename as rename2, rm, writeFile as writeFile2 } from "node:fs/promises";
8480
- import { homedir as homedir2 } from "node:os";
8481
- import { basename as basename5, dirname as dirname8, join as join19 } from "node:path";
8643
+ import { chmod, mkdir as mkdir2, rename as rename3, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
8644
+ import { homedir as homedir3 } from "node:os";
8645
+ import { basename as basename5, dirname as dirname9, join as join20 } from "node:path";
8482
8646
  import { chromium } from "playwright-core";
8483
8647
  function sessionFile(root, env = process.env) {
8484
8648
  let real = root;
@@ -8488,7 +8652,7 @@ function sessionFile(root, env = process.env) {
8488
8652
  }
8489
8653
  const name = basename5(real).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40) || "product";
8490
8654
  const hash = sha256(real).slice(0, 12);
8491
- return join19(env.XDG_CACHE_HOME || join19(homedir2(), ".cache"), "youiyoux", "sessions", `${name}-${hash}.json`);
8655
+ return join20(env.XDG_CACHE_HOME || join20(homedir3(), ".cache"), "youiyoux", "sessions", `${name}-${hash}.json`);
8492
8656
  }
8493
8657
  function chosenSession(root, named2, env = process.env) {
8494
8658
  if (named2) return { file: named2, saved: false };
@@ -8496,14 +8660,14 @@ function chosenSession(root, named2, env = process.env) {
8496
8660
  return existsSync7(file) ? { file, saved: true } : null;
8497
8661
  }
8498
8662
  async function writePrivate(file, content) {
8499
- await mkdir(dirname8(file), { recursive: true, mode: 448 });
8500
- const temp = join19(dirname8(file), `.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
8663
+ await mkdir2(dirname9(file), { recursive: true, mode: 448 });
8664
+ const temp = join20(dirname9(file), `.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
8501
8665
  try {
8502
8666
  await writeFile2(temp, content, { encoding: "utf8", mode: 384 });
8503
8667
  await chmod(temp, 384);
8504
- await rename2(temp, file);
8668
+ await rename3(temp, file);
8505
8669
  } finally {
8506
- await rm(temp, { force: true });
8670
+ await rm2(temp, { force: true });
8507
8671
  }
8508
8672
  }
8509
8673
  function openLoginBrowser(executablePath) {
@@ -8553,6 +8717,14 @@ export {
8553
8717
  checkCapabilities,
8554
8718
  checkInputFor,
8555
8719
  evaluateRule,
8720
+ systemsCacheDir,
8721
+ designRootOf,
8722
+ locateDesign,
8723
+ gitCheckout,
8724
+ withoutCredentials,
8725
+ linksFile,
8726
+ linkedDesign,
8727
+ setLink,
8556
8728
  PROJECT_FILE,
8557
8729
  ProjectRootError,
8558
8730
  ROOT_HINT,