@wix/himalaya-cli 0.808.0 → 0.810.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -807,7 +807,13 @@ var init_source_deny = __esm({
807
807
  /^\.env\./,
808
808
  /^\.DS_Store$/,
809
809
  /^id_rsa($|\.)/,
810
- /\.(pem|p8|p12|keystore|jks|mobileprovision)$/i
810
+ /\.(pem|p8|p12|keystore|jks|mobileprovision)$/i,
811
+ // `.git` IS NOT ALWAYS A DIRECTORY. In a linked worktree or a submodule it is a FILE holding
812
+ // `gitdir: …`, and DENY_DIRS is consulted for directories only — so at a package root it was
813
+ // denied by nothing: `himi push` archived the pointer, and `himi pull --replace` would delete
814
+ // it back out of a checkout whose release predates it (#2437). The guarantee that a restore
815
+ // cannot cost you a git history has to hold for both shapes of `.git`, not just the common one.
816
+ /^\.git$/
811
817
  ];
812
818
  }
813
819
  });
@@ -1014,7 +1020,7 @@ function sentinelizeIdentity(body, identity) {
1014
1020
  }
1015
1021
  if (identity.name) {
1016
1022
  const re = new RegExp(`\\b(${NAME_FIELDS.join("|")})(["']?\\s*[:=]\\s*)(["'])${escapeRe(identity.name)}\\3`, "g");
1017
- out = out.replace(re, (_m, key2, sep10, q) => `${key2}${sep10}${q}__HIMI_APP_NAME__${q}`);
1023
+ out = out.replace(re, (_m, key2, sep11, q) => `${key2}${sep11}${q}__HIMI_APP_NAME__${q}`);
1018
1024
  }
1019
1025
  return out;
1020
1026
  }
@@ -1954,7 +1960,7 @@ async function mintToken(args, label2, cacheKey, spawnImpl) {
1954
1960
  tokenCache.set(cacheKey, fromEnv);
1955
1961
  return fromEnv;
1956
1962
  }
1957
- const token = await new Promise((resolve40, reject) => {
1963
+ const token = await new Promise((resolve41, reject) => {
1958
1964
  let child;
1959
1965
  try {
1960
1966
  child = spawnImpl("wix", [...args]);
@@ -1983,12 +1989,12 @@ async function mintToken(args, label2, cacheKey, spawnImpl) {
1983
1989
  child.stdout?.on("data", (d) => {
1984
1990
  out += String(d);
1985
1991
  const m = out.match(TOKEN_RE);
1986
- if (m) done(() => resolve40(m[0]));
1992
+ if (m) done(() => resolve41(m[0]));
1987
1993
  });
1988
1994
  child.stderr?.on("data", (d) => {
1989
1995
  out += String(d);
1990
1996
  const m = out.match(TOKEN_RE);
1991
- if (m) done(() => resolve40(m[0]));
1997
+ if (m) done(() => resolve41(m[0]));
1992
1998
  });
1993
1999
  child.on("error", (e) => done(() => reject(new Error(mintHelp(label2, e.message)))));
1994
2000
  child.on("exit", (code) => done(() => reject(new Error(
@@ -2496,8 +2502,8 @@ var init_splash = __esm({
2496
2502
 
2497
2503
  // ../../core/dev-server/tier5-bundles-src/build-lib.mjs
2498
2504
  import { fileURLToPath as fileURLToPath5, pathToFileURL as pathToFileURL2 } from "node:url";
2499
- import { dirname as dirname8, resolve as resolve8 } from "node:path";
2500
- import { readdirSync as readdirSync6, existsSync as existsSync8, readFileSync as readFileSync10 } from "node:fs";
2505
+ import { dirname as dirname8, join as join12, resolve as resolve8, sep as sep2 } from "node:path";
2506
+ import { readdirSync as readdirSync6, existsSync as existsSync8, readFileSync as readFileSync10, realpathSync as realpathSync2, statSync as statSync2 } from "node:fs";
2501
2507
  async function loadCompiler() {
2502
2508
  try {
2503
2509
  const nativeCompiler = ["es", "build"].join("");
@@ -2530,6 +2536,259 @@ function isOffRepo() {
2530
2536
  if (process.env.HIMI_OFF_REPO === "1") return true;
2531
2537
  return !existsSync8(alias["@himalaya/state"]);
2532
2538
  }
2539
+ function buildConfinementRequired() {
2540
+ return _confinementRequired;
2541
+ }
2542
+ function realpathOrSelf(p) {
2543
+ try {
2544
+ return realpathSync2(resolve8(p));
2545
+ } catch {
2546
+ return resolve8(p);
2547
+ }
2548
+ }
2549
+ function isUnder(p, root) {
2550
+ return p === root || p.startsWith(root + sep2);
2551
+ }
2552
+ function confinementRoots(contentRoot) {
2553
+ const roots = /* @__PURE__ */ new Set();
2554
+ const add = (p) => {
2555
+ if (!p) return;
2556
+ roots.add(realpathOrSelf(p));
2557
+ roots.add(resolve8(p));
2558
+ };
2559
+ const real = realpathOrSelf(contentRoot);
2560
+ add(real);
2561
+ for (let d = real, up; ; d = up) {
2562
+ add(join12(d, "node_modules"));
2563
+ up = dirname8(d);
2564
+ if (up === d) break;
2565
+ }
2566
+ add(process.env.HIMI_AUTHORING_RUNTIME_DIR);
2567
+ add(process.env.HIMI_CLI_RUNTIME_DIR);
2568
+ if (!isOffRepo()) add(REPO_ROOT4);
2569
+ return [...roots];
2570
+ }
2571
+ function confinementMatcher(contentRoot, extraRoots = []) {
2572
+ const roots = [...confinementRoots(contentRoot), ...extraRoots.filter(Boolean).flatMap((r) => [realpathOrSelf(r), resolve8(r)])];
2573
+ let linked;
2574
+ const linkedRoots = () => {
2575
+ if (linked) return linked;
2576
+ linked = [];
2577
+ const scanned = /* @__PURE__ */ new Set();
2578
+ const queue = [...roots];
2579
+ const CAP3 = 4096;
2580
+ const take = (abs) => {
2581
+ const target = realpathOrSelf(abs);
2582
+ if (scanned.has(target) || linked.length >= CAP3) return;
2583
+ linked.push(target);
2584
+ queue.push(target);
2585
+ };
2586
+ while (queue.length && linked.length < CAP3) {
2587
+ const r = queue.shift();
2588
+ for (const nm of [r, join12(r, "node_modules")]) {
2589
+ if (scanned.has(nm)) continue;
2590
+ scanned.add(nm);
2591
+ let entries;
2592
+ try {
2593
+ entries = readdirSync6(nm, { withFileTypes: true });
2594
+ } catch {
2595
+ continue;
2596
+ }
2597
+ for (const e of entries) {
2598
+ const abs = join12(nm, e.name);
2599
+ if (e.isSymbolicLink()) take(abs);
2600
+ else if (e.isDirectory() && e.name.startsWith("@")) {
2601
+ let scoped;
2602
+ try {
2603
+ scoped = readdirSync6(abs, { withFileTypes: true });
2604
+ } catch {
2605
+ continue;
2606
+ }
2607
+ for (const sc of scoped) if (sc.isSymbolicLink()) take(join12(abs, sc.name));
2608
+ }
2609
+ }
2610
+ }
2611
+ }
2612
+ return linked;
2613
+ };
2614
+ return (p) => {
2615
+ const direct = resolve8(p);
2616
+ if (roots.some((r) => isUnder(direct, r))) return true;
2617
+ const real = realpathOrSelf(direct);
2618
+ if (real !== direct && roots.some((r) => isUnder(real, r))) return true;
2619
+ return linkedRoots().some((r) => isUnder(real, r) || isUnder(direct, r));
2620
+ };
2621
+ }
2622
+ function confinementPlugin(contentRoot) {
2623
+ const allows = confinementMatcher(contentRoot);
2624
+ return {
2625
+ name: "himalaya-confine-import-graph",
2626
+ setup(b) {
2627
+ b.onResolve({ filter: /^[./]/ }, (args) => {
2628
+ const base = args.path.startsWith("/") ? args.path : args.resolveDir ? resolve8(args.resolveDir, args.path) : void 0;
2629
+ if (!base || allows(base)) return void 0;
2630
+ return {
2631
+ errors: [{
2632
+ text: `refused to resolve ${args.path}: it is outside this content package. A worker or app config may import files from its own package, the Himalaya SDK, and its installed dependencies \u2014 nothing else.`
2633
+ }]
2634
+ };
2635
+ });
2636
+ b.onLoad({ filter: /.*/ }, (args) => {
2637
+ if (args.namespace && args.namespace !== "file") return void 0;
2638
+ if (allows(args.path)) return void 0;
2639
+ return {
2640
+ errors: [{
2641
+ text: `refused to read ${args.path}: it is outside this content package. A worker or app config may import files from its own package, the Himalaya SDK, and its installed dependencies \u2014 nothing else.`
2642
+ }]
2643
+ };
2644
+ });
2645
+ }
2646
+ };
2647
+ }
2648
+ function assertConfinedTsconfig(entry, contentRoot, allows) {
2649
+ const seen = /* @__PURE__ */ new Set();
2650
+ const refuse = (p, why) => {
2651
+ throw new Error(
2652
+ `refused to read ${p}: ${why}. A tsconfig may extend a file from its own package or an installed dependency \u2014 nothing else.`
2653
+ );
2654
+ };
2655
+ const stripJsonc = (text2) => {
2656
+ if (text2.charCodeAt(0) === 65279) text2 = text2.slice(1);
2657
+ let out = "";
2658
+ let inStr = false;
2659
+ let esc = false;
2660
+ for (let i = 0; i < text2.length; i++) {
2661
+ const c = text2[i];
2662
+ if (inStr) {
2663
+ out += c;
2664
+ if (esc) esc = false;
2665
+ else if (c === "\\") esc = true;
2666
+ else if (c === '"') inStr = false;
2667
+ continue;
2668
+ }
2669
+ if (c === '"') {
2670
+ inStr = true;
2671
+ out += c;
2672
+ continue;
2673
+ }
2674
+ if (c === "/" && text2[i + 1] === "/") {
2675
+ while (i < text2.length && text2[i] !== "\n") i++;
2676
+ continue;
2677
+ }
2678
+ if (c === "/" && text2[i + 1] === "*") {
2679
+ i += 2;
2680
+ while (i < text2.length && !(text2[i] === "*" && text2[i + 1] === "/")) i++;
2681
+ i++;
2682
+ continue;
2683
+ }
2684
+ if (c === "}" || c === "]") {
2685
+ const trimmed = out.replace(/\s+$/, "");
2686
+ if (trimmed.endsWith(",")) out = trimmed.slice(0, -1);
2687
+ }
2688
+ out += c;
2689
+ }
2690
+ return out;
2691
+ };
2692
+ const isFile = (p) => {
2693
+ try {
2694
+ return statSync2(p).isFile();
2695
+ } catch {
2696
+ return false;
2697
+ }
2698
+ };
2699
+ const asConfigFile = (t) => {
2700
+ if (isFile(t)) return t;
2701
+ const inDir = join12(t, "tsconfig.json");
2702
+ if (isFile(inDir)) return inDir;
2703
+ if (isFile(`${t}.json`)) return `${t}.json`;
2704
+ return t;
2705
+ };
2706
+ const resolveBare = (spec, fromDir) => {
2707
+ for (let d = fromDir, up; ; d = up) {
2708
+ const base = join12(d, "node_modules", spec);
2709
+ for (const cand of [base, `${base}.json`]) if (isFile(cand)) return cand;
2710
+ const pj = join12(base, "package.json");
2711
+ if (isFile(pj)) {
2712
+ let manifest;
2713
+ try {
2714
+ manifest = JSON.parse(stripJsonc(readFileSync10(pj, "utf8")));
2715
+ } catch {
2716
+ refuse(pj, "its package.json could not be parsed, so its `tsconfig` field cannot be checked");
2717
+ }
2718
+ const field = manifest?.tsconfig;
2719
+ if (typeof field === "string" && field) {
2720
+ return asConfigFile(field.startsWith("/") ? field : resolve8(base, field));
2721
+ }
2722
+ }
2723
+ if (isFile(join12(base, "tsconfig.json"))) return join12(base, "tsconfig.json");
2724
+ up = dirname8(d);
2725
+ if (up === d) return void 0;
2726
+ }
2727
+ };
2728
+ const follow = (file) => {
2729
+ const real = realpathOrSelf(file);
2730
+ if (seen.has(real)) return;
2731
+ seen.add(real);
2732
+ if (!allows(real)) refuse(file, "it is outside this content package");
2733
+ let raw;
2734
+ try {
2735
+ raw = readFileSync10(real, "utf8");
2736
+ } catch {
2737
+ return;
2738
+ }
2739
+ let parsed;
2740
+ try {
2741
+ parsed = JSON.parse(stripJsonc(raw));
2742
+ } catch {
2743
+ refuse(file, "its tsconfig could not be parsed, so its `extends` cannot be checked");
2744
+ }
2745
+ const ext = parsed?.extends;
2746
+ for (const one of Array.isArray(ext) ? ext : ext ? [ext] : []) {
2747
+ if (typeof one !== "string" || !one) continue;
2748
+ if (one.startsWith(".") || one.startsWith("/")) {
2749
+ const target = asConfigFile(one.startsWith("/") ? one : resolve8(dirname8(real), one));
2750
+ if (target) follow(target);
2751
+ } else {
2752
+ const target = resolveBare(one, dirname8(real));
2753
+ if (target) follow(target);
2754
+ }
2755
+ }
2756
+ };
2757
+ const scan = (dir) => {
2758
+ let entries;
2759
+ try {
2760
+ entries = readdirSync6(dir, { withFileTypes: true });
2761
+ } catch {
2762
+ return;
2763
+ }
2764
+ for (const e of entries) {
2765
+ const abs = join12(dir, e.name);
2766
+ if (e.isDirectory()) scan(abs);
2767
+ else if (e.name === "tsconfig.json" && e.isFile()) follow(abs);
2768
+ }
2769
+ };
2770
+ scan(realpathOrSelf(contentRoot));
2771
+ for (let d = dirname8(realpathOrSelf(entry)), up; ; d = up) {
2772
+ const cfg = join12(d, "tsconfig.json");
2773
+ if (existsSync8(cfg)) {
2774
+ follow(cfg);
2775
+ return;
2776
+ }
2777
+ up = dirname8(d);
2778
+ if (up === d) return;
2779
+ }
2780
+ }
2781
+ function confinementFor(contentRoot, where, entry) {
2782
+ if (!_confinementRequired) return void 0;
2783
+ if (!contentRoot) {
2784
+ throw new Error(
2785
+ `${where}: this process compiles untrusted uploads (requireBuildConfinement) but no content root was given, so the import graph would be unconfined. Pass \`confineTo: <content dir>\`.`
2786
+ );
2787
+ }
2788
+ const allows = confinementMatcher(contentRoot);
2789
+ if (entry) assertConfinedTsconfig(entry, contentRoot, allows);
2790
+ return confinementPlugin(contentRoot);
2791
+ }
2533
2792
  function umbrellaRewritePlugin({ external = false } = {}) {
2534
2793
  return {
2535
2794
  name: "himalaya-umbrella-rewrite",
@@ -2597,7 +2856,8 @@ function importMetaUrlPlugin() {
2597
2856
  };
2598
2857
  }
2599
2858
  async function buildBundleFromEntry(entry, outfile, opts = {}) {
2600
- const { resolution = "source", name = "bundle", absWorkingDir = REPO_ROOT4 } = opts;
2859
+ const { resolution = "source", name = "bundle", absWorkingDir = REPO_ROOT4, confineTo } = opts;
2860
+ const confine = confinementFor(confineTo, "buildBundleFromEntry", entry);
2601
2861
  if (!existsSync8(entry)) throw new Error(`no bundle entry: ${entry}`);
2602
2862
  const buildOpts = {
2603
2863
  entryPoints: [entry],
@@ -2620,11 +2880,13 @@ async function buildBundleFromEntry(entry, outfile, opts = {}) {
2620
2880
  else if (isOffRepo()) {
2621
2881
  buildOpts.plugins = process.env.HIMI_CLI_RUNTIME_DIR ? [cliRuntimeUmbrellaPlugin()] : [umbrellaRewritePlugin({ external: false })];
2622
2882
  }
2883
+ if (confine) buildOpts.plugins = [confine, ...buildOpts.plugins ?? []];
2623
2884
  await build(buildOpts);
2624
2885
  return outfile;
2625
2886
  }
2626
2887
  async function buildAppEntry(entry, outfile, opts = {}) {
2627
- const { umbrella = false, resolveTsExtensions = false, absWorkingDir = REPO_ROOT4 } = opts;
2888
+ const { umbrella = false, resolveTsExtensions = false, absWorkingDir = REPO_ROOT4, confineTo } = opts;
2889
+ const confine = confinementFor(confineTo, "buildAppEntry", entry);
2628
2890
  if (!existsSync8(entry)) throw new Error(`unknown app entry: ${entry}`);
2629
2891
  await build({
2630
2892
  entryPoints: [entry],
@@ -2648,6 +2910,9 @@ async function buildAppEntry(entry, outfile, opts = {}) {
2648
2910
  // -- a single global `define` here pinned the whole graph to the entry and broke
2649
2911
  // every app whose config.ts reads a file relative to itself.
2650
2912
  plugins: [
2913
+ // FIRST, and here it is load-bearing: `importMetaUrlPlugin` registers an onLoad and CLAIMS
2914
+ // every file containing `import.meta.url`, so a confinement behind it would never see them.
2915
+ ...confine ? [confine] : [],
2651
2916
  importMetaUrlPlugin(),
2652
2917
  ...resolveTsExtensions ? [tsExtResolvePlugin()] : [],
2653
2918
  ...process.env.HIMI_CLI_RUNTIME_DIR ? [cliRuntimeUmbrellaPlugin()] : [],
@@ -2656,7 +2921,7 @@ async function buildAppEntry(entry, outfile, opts = {}) {
2656
2921
  });
2657
2922
  return outfile;
2658
2923
  }
2659
- var _compilerP, __dirname, REPO_ROOT4, SDK, alias, sourceAliases, OUT_DIR, HERMES_SAFE_SUPPORTED, HERMES_UNSAFE_BUNDLES, APP_ENTRY_TARGET;
2924
+ var _compilerP, __dirname, REPO_ROOT4, SDK, alias, sourceAliases, OUT_DIR, HERMES_SAFE_SUPPORTED, HERMES_UNSAFE_BUNDLES, _confinementRequired, APP_ENTRY_TARGET;
2660
2925
  var init_build_lib = __esm({
2661
2926
  "../../core/dev-server/tier5-bundles-src/build-lib.mjs"() {
2662
2927
  "use strict";
@@ -2805,6 +3070,7 @@ var init_build_lib = __esm({
2805
3070
  // worker-safe wix-auth showcase pulls ~11 classes from stdlib/auth-providers.
2806
3071
  "demo-app-wix-platform"
2807
3072
  ]);
3073
+ _confinementRequired = false;
2808
3074
  APP_ENTRY_TARGET = ["node18"];
2809
3075
  }
2810
3076
  });
@@ -2829,7 +3095,7 @@ var init_tier5_dir = __esm({
2829
3095
 
2830
3096
  // src/tier5-typecheck.ts
2831
3097
  import { existsSync as existsSync9, readFileSync as readFileSync11 } from "node:fs";
2832
- import { relative as relative2, resolve as resolve10, sep as sep2 } from "node:path";
3098
+ import { relative as relative2, resolve as resolve10, sep as sep3 } from "node:path";
2833
3099
  import { pathToFileURL as pathToFileURL3 } from "node:url";
2834
3100
  async function loadTypeScript() {
2835
3101
  const runtime = process.env.HIMI_AUTHORING_RUNTIME_DIR;
@@ -2868,7 +3134,7 @@ function pathsFor(options) {
2868
3134
  return declarationPaths(options.umbrellaDir);
2869
3135
  }
2870
3136
  function relativeFile(contentDir2, fileName) {
2871
- return relative2(contentDir2, fileName).split(sep2).join("/");
3137
+ return relative2(contentDir2, fileName).split(sep3).join("/");
2872
3138
  }
2873
3139
  function belongsToContentWorkers(contentDir2, diagnostic) {
2874
3140
  if (!diagnostic.file) return true;
@@ -2890,6 +3156,19 @@ function toIssue(ts, contentDir2, diagnostic) {
2890
3156
  message: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")
2891
3157
  };
2892
3158
  }
3159
+ function confinedHost(ts, contentDir2, options) {
3160
+ if (!buildConfinementRequired()) return void 0;
3161
+ const host = ts.createCompilerHost(options);
3162
+ const libDir = resolve10(ts.getDefaultLibFilePath(options), "..");
3163
+ const allows = confinementMatcher(contentDir2, [libDir]);
3164
+ const getSourceFile = host.getSourceFile.bind(host);
3165
+ const readFile3 = host.readFile.bind(host);
3166
+ const fileExists = host.fileExists.bind(host);
3167
+ host.getSourceFile = (fileName, ...rest) => allows(fileName) ? getSourceFile(fileName, ...rest) : void 0;
3168
+ host.readFile = (fileName) => allows(fileName) ? readFile3(fileName) : void 0;
3169
+ host.fileExists = (fileName) => allows(fileName) && fileExists(fileName);
3170
+ return host;
3171
+ }
2893
3172
  async function typecheckContentWorkers(contentDir2, options) {
2894
3173
  if (!existsSync9(resolve10(contentDir2, "tier5-src"))) return [];
2895
3174
  const ts = await compiler2();
@@ -2907,7 +3186,11 @@ async function typecheckContentWorkers(contentDir2, options) {
2907
3186
  };
2908
3187
  const parsed = ts.parseJsonConfigFileContent(config, ts.sys, contentDir2);
2909
3188
  if (parsed.fileNames.length === 0) return [];
2910
- const program = ts.createProgram({ rootNames: parsed.fileNames, options: parsed.options });
3189
+ const program = ts.createProgram({
3190
+ rootNames: parsed.fileNames,
3191
+ options: parsed.options,
3192
+ host: confinedHost(ts, contentDir2, parsed.options)
3193
+ });
2911
3194
  return [...parsed.errors, ...ts.getPreEmitDiagnostics(program)].filter((diagnostic) => diagnostic.category === ts.DiagnosticCategory.Error && belongsToContentWorkers(contentDir2, diagnostic)).map((diagnostic) => toIssue(ts, contentDir2, diagnostic));
2912
3195
  }
2913
3196
  var compilerPromise;
@@ -2936,31 +3219,31 @@ __export(build_exports, {
2936
3219
  umbrellaVersion: () => umbrellaVersion,
2937
3220
  umbrellaVersionIssue: () => umbrellaVersionIssue
2938
3221
  });
2939
- import { existsSync as existsSync10, readdirSync as readdirSync8, mkdirSync as mkdirSync6, statSync as statSync2, symlinkSync, readFileSync as readFileSync12 } from "node:fs";
2940
- import { resolve as resolve11, join as join12, dirname as dirname9 } from "node:path";
3222
+ import { existsSync as existsSync10, readdirSync as readdirSync8, mkdirSync as mkdirSync6, statSync as statSync3, symlinkSync, readFileSync as readFileSync12 } from "node:fs";
3223
+ import { resolve as resolve11, join as join13, dirname as dirname9 } from "node:path";
2941
3224
  import { fileURLToPath as fileURLToPath6 } from "node:url";
2942
3225
  import { execFileSync as execFileSync3 } from "node:child_process";
2943
3226
  function bundledRuntimeDir() {
2944
3227
  const configured = process.env.HIMI_CLI_RUNTIME_DIR;
2945
- if (configured && existsSync10(join12(configured, "node_modules", "@wix", "himalaya", "package.json"))) {
3228
+ if (configured && existsSync10(join13(configured, "node_modules", "@wix", "himalaya", "package.json"))) {
2946
3229
  return configured;
2947
3230
  }
2948
3231
  const candidate = resolve11(dirname9(fileURLToPath6(import.meta.url)), "authoring-runtime");
2949
- if (!existsSync10(join12(candidate, "node_modules", "@wix", "himalaya", "package.json"))) return null;
3232
+ if (!existsSync10(join13(candidate, "node_modules", "@wix", "himalaya", "package.json"))) return null;
2950
3233
  process.env.HIMI_CLI_RUNTIME_DIR = candidate;
2951
3234
  return candidate;
2952
3235
  }
2953
3236
  function bundledUmbrella() {
2954
3237
  const runtime = bundledRuntimeDir();
2955
- return runtime ? join12(runtime, "node_modules", "@wix", "himalaya") : null;
3238
+ return runtime ? join13(runtime, "node_modules", "@wix", "himalaya") : null;
2956
3239
  }
2957
3240
  function umbrellaDir(fromDir) {
2958
3241
  const bundled = bundledUmbrella();
2959
3242
  if (bundled) return bundled;
2960
3243
  let d = resolve11(fromDir);
2961
3244
  for (; ; ) {
2962
- const pkg = join12(d, "node_modules", "@wix", "himalaya");
2963
- if (existsSync10(join12(pkg, "package.json"))) return pkg;
3245
+ const pkg = join13(d, "node_modules", "@wix", "himalaya");
3246
+ if (existsSync10(join13(pkg, "package.json"))) return pkg;
2964
3247
  const parent = dirname9(d);
2965
3248
  if (parent === d) return null;
2966
3249
  d = parent;
@@ -2973,8 +3256,8 @@ function globalUmbrella() {
2973
3256
  try {
2974
3257
  const root = execFileSync3("npm", ["root", "-g"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
2975
3258
  if (!root) return null;
2976
- const pkg = join12(root, "@wix", "himalaya");
2977
- return existsSync10(join12(pkg, "package.json")) ? pkg : null;
3259
+ const pkg = join13(root, "@wix", "himalaya");
3260
+ return existsSync10(join13(pkg, "package.json")) ? pkg : null;
2978
3261
  } catch {
2979
3262
  return null;
2980
3263
  }
@@ -2983,11 +3266,11 @@ function ensureUmbrella(contentDir2, globalPath = globalUmbrella()) {
2983
3266
  if (umbrellaInstalled(contentDir2)) return true;
2984
3267
  if (!globalPath) return false;
2985
3268
  try {
2986
- const scope = join12(contentDir2, "node_modules", "@wix");
3269
+ const scope = join13(contentDir2, "node_modules", "@wix");
2987
3270
  mkdirSync6(scope, { recursive: true });
2988
- const link = join12(scope, "himalaya");
3271
+ const link = join13(scope, "himalaya");
2989
3272
  if (!existsSync10(link)) symlinkSync(globalPath, link, "dir");
2990
- return existsSync10(join12(link, "package.json"));
3273
+ return existsSync10(join13(link, "package.json"));
2991
3274
  } catch {
2992
3275
  return false;
2993
3276
  }
@@ -3005,7 +3288,7 @@ function umbrellaVersion(fromDir) {
3005
3288
  const dir = umbrellaDir(fromDir);
3006
3289
  if (!dir) return null;
3007
3290
  try {
3008
- const v = JSON.parse(readFileSync12(join12(dir, "package.json"), "utf8")).version;
3291
+ const v = JSON.parse(readFileSync12(join13(dir, "package.json"), "utf8")).version;
3009
3292
  return typeof v === "string" && v ? v : null;
3010
3293
  } catch {
3011
3294
  return null;
@@ -3026,7 +3309,7 @@ function umbrellaDistIssue(contentDir2) {
3026
3309
  if (!dir) return null;
3027
3310
  let exportsMap;
3028
3311
  try {
3029
- exportsMap = JSON.parse(readFileSync12(join12(dir, "package.json"), "utf8")).exports;
3312
+ exportsMap = JSON.parse(readFileSync12(join13(dir, "package.json"), "utf8")).exports;
3030
3313
  } catch {
3031
3314
  return null;
3032
3315
  }
@@ -3080,14 +3363,19 @@ async function buildContentBundles(contentDir2, opts = {}) {
3080
3363
  if (!existsSync10(srcDir)) return { built, outDir, compileErrors, ...sdkVersionIssue ? { sdkVersionIssue } : {} };
3081
3364
  mkdirSync6(outDir, { recursive: true });
3082
3365
  for (const name of readdirSync8(srcDir).sort()) {
3083
- const entry = join12(srcDir, name, "index.ts");
3084
- if (!statSync2(join12(srcDir, name)).isDirectory() || !existsSync10(entry)) continue;
3366
+ const entry = join13(srcDir, name, "index.ts");
3367
+ if (!statSync3(join13(srcDir, name)).isDirectory() || !existsSync10(entry)) continue;
3085
3368
  try {
3086
- await buildBundleFromEntry(entry, join12(outDir, `${name}.bundle.js`), {
3369
+ await buildBundleFromEntry(entry, join13(outDir, `${name}.bundle.js`), {
3087
3370
  resolution,
3088
3371
  name,
3089
3372
  // Off-repo, resolve the umbrella from the content package's own node_modules.
3090
- ...offRepo ? { absWorkingDir: contentDir2 } : {}
3373
+ ...offRepo ? { absWorkingDir: contentDir2 } : {},
3374
+ // Bounds the import graph in a process that compiles untrusted uploads
3375
+ // (core/serve-authoring); inert everywhere else. Always passed, so the authoring guest
3376
+ // cannot reach this build without a root — build-lib throws rather than compile
3377
+ // unconfined.
3378
+ confineTo: contentDir2
3091
3379
  });
3092
3380
  built.push(name);
3093
3381
  } catch (err) {
@@ -3125,11 +3413,11 @@ __export(app_config_loader_exports, {
3125
3413
  loadAppConfigModule: () => loadAppConfigModule
3126
3414
  });
3127
3415
  import { existsSync as existsSync11, mkdirSync as mkdirSync7 } from "node:fs";
3128
- import { dirname as dirname10, join as join13, resolve as resolve12 } from "node:path";
3416
+ import { dirname as dirname10, join as join14, resolve as resolve12 } from "node:path";
3129
3417
  import { pathToFileURL as pathToFileURL4 } from "node:url";
3130
3418
  function configEntry(dir) {
3131
3419
  for (const f of ["config.ts", "config.js", "config.mjs"]) {
3132
- const p = join13(dir, f);
3420
+ const p = join14(dir, f);
3133
3421
  if (existsSync11(p)) return p;
3134
3422
  }
3135
3423
  return null;
@@ -3141,7 +3429,7 @@ async function buildConfigModuleUrl(entry, dir) {
3141
3429
  bundledRuntimeDir2();
3142
3430
  const out = resolve12(dir, "dist/.himi/config.mjs");
3143
3431
  mkdirSync7(dirname10(out), { recursive: true });
3144
- await buildAppEntry(entry, out, { umbrella: true, resolveTsExtensions: true, absWorkingDir: dir });
3432
+ await buildAppEntry(entry, out, { umbrella: true, resolveTsExtensions: true, absWorkingDir: dir, confineTo: dir });
3145
3433
  return pathToFileURL4(out).href;
3146
3434
  }
3147
3435
  function rememberBuild(key2, pending) {
@@ -3889,10 +4177,10 @@ var init_app_icon_rules = __esm({
3889
4177
 
3890
4178
  // ../../core/dev-server/src/app-icon.ts
3891
4179
  import { existsSync as existsSync14, readFileSync as readFileSync17, readdirSync as readdirSync9 } from "node:fs";
3892
- import { dirname as dirname13, join as join17, resolve as resolve13 } from "node:path";
4180
+ import { dirname as dirname13, join as join18, resolve as resolve13 } from "node:path";
3893
4181
  import { fileURLToPath as fileURLToPath7 } from "node:url";
3894
4182
  function findAppIconPath(appDir2) {
3895
- const iosDir = join17(appDir2, "ios");
4183
+ const iosDir = join18(appDir2, "ios");
3896
4184
  let children;
3897
4185
  try {
3898
4186
  children = readdirSync9(iosDir);
@@ -3900,7 +4188,7 @@ function findAppIconPath(appDir2) {
3900
4188
  return null;
3901
4189
  }
3902
4190
  for (const child of children) {
3903
- const p = join17(iosDir, child, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
4191
+ const p = join18(iosDir, child, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
3904
4192
  if (existsSync14(p)) return p;
3905
4193
  }
3906
4194
  return null;
@@ -3908,7 +4196,7 @@ function findAppIconPath(appDir2) {
3908
4196
  function findContentIconPath(appDir2) {
3909
4197
  let rel;
3910
4198
  try {
3911
- rel = JSON.parse(readFileSync17(join17(appDir2, "himalaya.content.json"), "utf8")).icon;
4199
+ rel = JSON.parse(readFileSync17(join18(appDir2, "himalaya.content.json"), "utf8")).icon;
3912
4200
  } catch {
3913
4201
  return null;
3914
4202
  }
@@ -3933,7 +4221,7 @@ function readIconFromDir(appDir2) {
3933
4221
  function resolveAppDir(appId2) {
3934
4222
  if (!/^[a-z0-9][a-z0-9._-]*$/i.test(appId2)) return null;
3935
4223
  for (const base of APP_ROOTS) {
3936
- const dir = join17(ROOT, base, appId2);
4224
+ const dir = join18(ROOT, base, appId2);
3937
4225
  if (existsSync14(dir)) return dir;
3938
4226
  }
3939
4227
  return null;
@@ -4226,11 +4514,11 @@ __export(icon_exports, {
4226
4514
  summarize: () => summarize
4227
4515
  });
4228
4516
  import { createHash as createHash5 } from "node:crypto";
4229
- import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync18, statSync as statSync3, writeFileSync as writeFileSync8 } from "node:fs";
4230
- import { basename as basename4, dirname as dirname14, join as join18, resolve as resolve14, sep as sep3 } from "node:path";
4517
+ import { existsSync as existsSync15, mkdirSync as mkdirSync9, readFileSync as readFileSync18, statSync as statSync4, writeFileSync as writeFileSync8 } from "node:fs";
4518
+ import { basename as basename4, dirname as dirname14, join as join19, resolve as resolve14, sep as sep4 } from "node:path";
4231
4519
  function readManifest(dir) {
4232
4520
  try {
4233
- return JSON.parse(readFileSync18(join18(dir, MANIFEST), "utf8"));
4521
+ return JSON.parse(readFileSync18(join19(dir, MANIFEST), "utf8"));
4234
4522
  } catch {
4235
4523
  return null;
4236
4524
  }
@@ -4244,7 +4532,7 @@ function declaredIconName(dir) {
4244
4532
  function iconDest(dir, name) {
4245
4533
  const base = resolve14(dir);
4246
4534
  const abs = resolve14(base, name);
4247
- if (!abs.startsWith(base + sep3)) {
4535
+ if (!abs.startsWith(base + sep4)) {
4248
4536
  throw new Error(
4249
4537
  `himi icon: himalaya.content.json declares "icon": "${name}", which resolves outside the package (${abs}). Declare a path inside it, e.g. "${DEFAULT_CONTENT_ICON}".`
4250
4538
  );
@@ -4254,9 +4542,9 @@ function iconDest(dir, name) {
4254
4542
  }
4255
4543
  function templateIconMatch(png) {
4256
4544
  for (const template of availableTemplates()) {
4257
- const candidate = join18(templateSourceDir(template), DEFAULT_CONTENT_ICON);
4545
+ const candidate = join19(templateSourceDir(template), DEFAULT_CONTENT_ICON);
4258
4546
  try {
4259
- if (statSync3(candidate).size !== png.length) continue;
4547
+ if (statSync4(candidate).size !== png.length) continue;
4260
4548
  if (readFileSync18(candidate).equals(png)) return template;
4261
4549
  } catch {
4262
4550
  }
@@ -4264,10 +4552,10 @@ function templateIconMatch(png) {
4264
4552
  return null;
4265
4553
  }
4266
4554
  function playState(dir) {
4267
- const p = join18(resolve14(dir), PLAY_LISTING_ICON);
4555
+ const p = join19(resolve14(dir), PLAY_LISTING_ICON);
4268
4556
  if (!existsSync15(p)) return null;
4269
4557
  try {
4270
- if (!statSync3(p).isFile()) return { path: p, issues: ["is a directory, not a PNG file"] };
4558
+ if (!statSync4(p).isFile()) return { path: p, issues: ["is a directory, not a PNG file"] };
4271
4559
  return { path: p, issues: playListingIconIssues(readFileSync18(p)) };
4272
4560
  } catch (err) {
4273
4561
  return { path: p, issues: [`could not be read (${err.message.split("\n")[0]})`] };
@@ -4294,7 +4582,7 @@ async function iconStatus(dir) {
4294
4582
  if (!path || !existsSync15(path)) return base;
4295
4583
  let png;
4296
4584
  try {
4297
- if (!statSync3(path).isFile()) {
4585
+ if (!statSync4(path).isFile()) {
4298
4586
  return {
4299
4587
  ...base,
4300
4588
  byteIssues: [`at ${path} is a directory, not a PNG file`],
@@ -4371,7 +4659,7 @@ function summarize(status) {
4371
4659
  }
4372
4660
  function backdropFromTokens(dir, fallback = "#1F2933") {
4373
4661
  try {
4374
- const tokens = JSON.parse(readFileSync18(join18(dir, "tokens.json"), "utf8"));
4662
+ const tokens = JSON.parse(readFileSync18(join19(dir, "tokens.json"), "utf8"));
4375
4663
  const accent = tokens.colors?.accentPrimary;
4376
4664
  const hex2 = typeof accent === "string" ? accent : accent?.light;
4377
4665
  return typeof hex2 === "string" && HEX_COLOR2.test(hex2) ? hex2 : fallback;
@@ -4430,15 +4718,15 @@ function declareIcon(dir, name) {
4430
4718
  if (k === "name") next.icon = name;
4431
4719
  }
4432
4720
  next.icon = name;
4433
- writeFileSync8(join18(dir, MANIFEST), JSON.stringify(next, null, 2) + "\n");
4434
- return [join18(dir, MANIFEST)];
4721
+ writeFileSync8(join19(dir, MANIFEST), JSON.stringify(next, null, 2) + "\n");
4722
+ return [join19(dir, MANIFEST)];
4435
4723
  }
4436
4724
  function siteBookIcon(bookDir) {
4437
- const p = join18(resolve14(bookDir), SITE_BOOK_ICON_REL);
4725
+ const p = join19(resolve14(bookDir), SITE_BOOK_ICON_REL);
4438
4726
  return existsSync15(p) ? p : null;
4439
4727
  }
4440
4728
  function siteLogoAbsence(bookDir) {
4441
- const bookJson = join18(resolve14(bookDir), "book.json");
4729
+ const bookJson = join19(resolve14(bookDir), "book.json");
4442
4730
  if (!existsSync15(bookJson)) {
4443
4731
  return { reason: "no-book", hint: `no business book at ${bookDir} \u2014 run \`himi site analyze --out <dir>\` first, or pass --book <dir>` };
4444
4732
  }
@@ -4520,7 +4808,7 @@ var init_icon = __esm({
4520
4808
  // ../fonts/catalog.ts
4521
4809
  import { existsSync as existsSync17, readFileSync as readFileSync19 } from "node:fs";
4522
4810
  import { fileURLToPath as fileURLToPath8 } from "node:url";
4523
- import { join as join20 } from "node:path";
4811
+ import { join as join21 } from "node:path";
4524
4812
  function loadCatalog() {
4525
4813
  if (cached) return cached;
4526
4814
  const path = CATALOG_CANDIDATES.find((p) => existsSync17(p));
@@ -4589,9 +4877,9 @@ var init_catalog = __esm({
4589
4877
  "../fonts/catalog.ts"() {
4590
4878
  CATALOG_CANDIDATES = [
4591
4879
  // in-repo: tools/fonts/catalog.ts → <repo>/stdlib/fonts/catalog.json
4592
- join20(fileURLToPath8(new URL("../..", import.meta.url)), "stdlib", "fonts", "catalog.json"),
4880
+ join21(fileURLToPath8(new URL("../..", import.meta.url)), "stdlib", "fonts", "catalog.json"),
4593
4881
  // published CLI: <pkg>/dist/cli.mjs → <pkg>/stdlib/fonts/catalog.json
4594
- join20(fileURLToPath8(new URL("..", import.meta.url)), "stdlib", "fonts", "catalog.json")
4882
+ join21(fileURLToPath8(new URL("..", import.meta.url)), "stdlib", "fonts", "catalog.json")
4595
4883
  ];
4596
4884
  cached = null;
4597
4885
  normalizeFamily = (name) => name.toLowerCase().replace(/[^a-z0-9]/g, "");
@@ -4696,17 +4984,17 @@ var init_plan_fonts = __esm({
4696
4984
  import { createHash as createHash6 } from "node:crypto";
4697
4985
  import { existsSync as existsSync18, readFileSync as readFileSync20 } from "node:fs";
4698
4986
  import { homedir as homedir4 } from "node:os";
4699
- import { basename as basename5, join as join21 } from "node:path";
4987
+ import { basename as basename5, join as join22 } from "node:path";
4700
4988
  import { fileURLToPath as fileURLToPath9 } from "node:url";
4701
4989
  function hasVendoredFloor() {
4702
4990
  return existsSync18(VENDORED_DIR);
4703
4991
  }
4704
4992
  function faceSource(file, sha2565) {
4705
- const vendored = join21(VENDORED_DIR, basename5(file));
4993
+ const vendored = join22(VENDORED_DIR, basename5(file));
4706
4994
  if (existsSync18(vendored) && createHash6("sha256").update(readFileSync20(vendored)).digest("hex") === sha2565) {
4707
4995
  return vendored;
4708
4996
  }
4709
- const cached3 = join21(CACHE_DIR, sha2565);
4997
+ const cached3 = join22(CACHE_DIR, sha2565);
4710
4998
  if (existsSync18(cached3)) return cached3;
4711
4999
  return null;
4712
5000
  }
@@ -4714,8 +5002,8 @@ var REPO_ROOT5, VENDORED_DIR, CACHE_DIR;
4714
5002
  var init_face_source = __esm({
4715
5003
  "../font-bake/face-source.ts"() {
4716
5004
  REPO_ROOT5 = fileURLToPath9(new URL("../..", import.meta.url));
4717
- VENDORED_DIR = process.env.HIMI_FONTS_VENDORED_DIR || join21(REPO_ROOT5, "stdlib", "fonts", "vendored", "wix-madefor");
4718
- CACHE_DIR = join21(process.env.XDG_CACHE_HOME || join21(homedir4(), ".cache"), "himi", "fonts");
5005
+ VENDORED_DIR = process.env.HIMI_FONTS_VENDORED_DIR || join22(REPO_ROOT5, "stdlib", "fonts", "vendored", "wix-madefor");
5006
+ CACHE_DIR = join22(process.env.XDG_CACHE_HOME || join22(homedir4(), ".cache"), "himi", "fonts");
4719
5007
  }
4720
5008
  });
4721
5009
 
@@ -4729,7 +5017,7 @@ __export(fonts_exports, {
4729
5017
  resolveFamilyName: () => resolveFamilyName
4730
5018
  });
4731
5019
  import { existsSync as existsSync19, readFileSync as readFileSync21 } from "node:fs";
4732
- import { join as join22 } from "node:path";
5020
+ import { join as join23 } from "node:path";
4733
5021
  function readJson(path, fallback) {
4734
5022
  try {
4735
5023
  return JSON.parse(readFileSync21(path, "utf8"));
@@ -4761,11 +5049,11 @@ function listReport(opts = {}) {
4761
5049
  return { ok: true, count: families.length, totalBytes, families };
4762
5050
  }
4763
5051
  function fontFindings(appDir2) {
4764
- const tokensPath = join22(appDir2, "tokens.json");
5052
+ const tokensPath = join23(appDir2, "tokens.json");
4765
5053
  if (!existsSync19(tokensPath)) return null;
4766
5054
  const catalog = loadCatalog();
4767
5055
  const tokens = readJson(tokensPath, {});
4768
- const policy = readJson(join22(appDir2, "fonts.json"), {});
5056
+ const policy = readJson(join23(appDir2, "fonts.json"), {});
4769
5057
  const plan = planFonts(tokens, policy, catalog);
4770
5058
  const findings = [];
4771
5059
  for (const family of plan.unresolved) {
@@ -4851,7 +5139,7 @@ function addReport(appDir2, family) {
4851
5139
  totalBytes: bytes2,
4852
5140
  problems: [],
4853
5141
  families: [{ family: resolved, license: fam?.license.id ?? "system", copyright: fam?.license.copyright, kb: Math.round(bytes2 / 1024) }],
4854
- hint: `add "${resolved}" to ${join22(appDir2, "fonts.json")} under "families" (or name it in tokens.json typography), then run gen.sh. Licence: ${fam?.license.id ?? "system"}${fam?.license.copyright ? ` \u2014 ${fam.license.copyright}` : ""}`
5142
+ hint: `add "${resolved}" to ${join23(appDir2, "fonts.json")} under "families" (or name it in tokens.json typography), then run gen.sh. Licence: ${fam?.license.id ?? "system"}${fam?.license.copyright ? ` \u2014 ${fam.license.copyright}` : ""}`
4855
5143
  };
4856
5144
  }
4857
5145
  var DEFAULT_ASSET_BUDGET_BYTES;
@@ -5773,7 +6061,7 @@ var init_src = __esm({
5773
6061
  // ../app-icons/plan.ts
5774
6062
  import { existsSync as existsSync23, readFileSync as readFileSync25, readdirSync as readdirSync12 } from "node:fs";
5775
6063
  import { createHash as createHash7 } from "node:crypto";
5776
- import { dirname as dirname16, join as join26, resolve as resolve17 } from "node:path";
6064
+ import { dirname as dirname16, join as join27, resolve as resolve17 } from "node:path";
5777
6065
  function stripComments(src) {
5778
6066
  let out = "";
5779
6067
  let i = 0;
@@ -5903,10 +6191,10 @@ function rotateHue(hex2, deg) {
5903
6191
  function resolveTokensPath(app, configTs) {
5904
6192
  const literal = /^ {2}designTokensPath\s*:\s*["']([^"']+)["']/m.exec(stripComments(configTs))?.[1];
5905
6193
  if (literal) return { path: resolve17(appDir(app), literal), source: "declared" };
5906
- return { path: join26(appDir(app), "tokens.json"), source: "assumed" };
6194
+ return { path: join27(appDir(app), "tokens.json"), source: "assumed" };
5907
6195
  }
5908
6196
  function resolveToken(app, token, tokensPathOverride) {
5909
- const tokensPath = tokensPathOverride ?? join26(appDir(app), "tokens.json");
6197
+ const tokensPath = tokensPathOverride ?? join27(appDir(app), "tokens.json");
5910
6198
  if (!existsSync23(tokensPath)) {
5911
6199
  throw new Error(`app-icons: ${app} has no tokens.json at ${tokensPath} (needed to resolve "${token}").`);
5912
6200
  }
@@ -5989,17 +6277,17 @@ function iconSpec(app, configTs, id, tokensPathOverride) {
5989
6277
  };
5990
6278
  }
5991
6279
  function iosAssetCatalog2(app) {
5992
- return assetCatalogIn(join26(appDir(app), "ios"));
6280
+ return assetCatalogIn(join27(appDir(app), "ios"));
5993
6281
  }
5994
6282
  function assetCatalogIn(platformDir) {
5995
6283
  if (!existsSync23(platformDir)) return void 0;
5996
6284
  const children = readdirSync12(platformDir).filter((c) => !c.startsWith("build")).sort();
5997
- const catalogs = children.map((c) => join26(platformDir, c, "Assets.xcassets")).filter(existsSync23);
6285
+ const catalogs = children.map((c) => join27(platformDir, c, "Assets.xcassets")).filter(existsSync23);
5998
6286
  if (catalogs.length === 1) return catalogs[0];
5999
6287
  if (catalogs.length > 1) return void 0;
6000
6288
  const targets = [];
6001
6289
  for (const child of children) {
6002
- const dir = join26(platformDir, child);
6290
+ const dir = join27(platformDir, child);
6003
6291
  let entries;
6004
6292
  try {
6005
6293
  entries = readdirSync12(dir);
@@ -6007,20 +6295,20 @@ function assetCatalogIn(platformDir) {
6007
6295
  continue;
6008
6296
  }
6009
6297
  if (entries.includes("Info.plist") || entries.some((e) => e.endsWith(".swift"))) {
6010
- targets.push(join26(dir, "Assets.xcassets"));
6298
+ targets.push(join27(dir, "Assets.xcassets"));
6011
6299
  }
6012
6300
  }
6013
6301
  return targets.length === 1 ? targets[0] : void 0;
6014
6302
  }
6015
6303
  function androidResDir2(manifestPath) {
6016
- return join26(dirname16(manifestPath), "res");
6304
+ return join27(dirname16(manifestPath), "res");
6017
6305
  }
6018
6306
  function androidManifestPath(app) {
6019
- const androidDir = join26(appDir(app), "android");
6307
+ const androidDir = join27(appDir(app), "android");
6020
6308
  if (!existsSync23(androidDir)) return void 0;
6021
- const preferred = join26(androidDir, `${app}-app`, "src", "main", "AndroidManifest.xml");
6309
+ const preferred = join27(androidDir, `${app}-app`, "src", "main", "AndroidManifest.xml");
6022
6310
  if (existsSync23(preferred)) return preferred;
6023
- const candidates = readdirSync12(androidDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.endsWith("-app")).map((d) => join26(androidDir, d.name, "src", "main", "AndroidManifest.xml")).filter(existsSync23);
6311
+ const candidates = readdirSync12(androidDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.endsWith("-app")).map((d) => join27(androidDir, d.name, "src", "main", "AndroidManifest.xml")).filter(existsSync23);
6024
6312
  return candidates.length === 1 ? candidates[0] : void 0;
6025
6313
  }
6026
6314
  function androidOptedIn2(manifestPath) {
@@ -6029,7 +6317,7 @@ function androidOptedIn2(manifestPath) {
6029
6317
  }
6030
6318
  function templateIconSha256() {
6031
6319
  const cat = iosAssetCatalog2("_template");
6032
- return cat ? fileSha256(join26(cat, "AppIcon.appiconset", "icon_1024.png")) : null;
6320
+ return cat ? fileSha256(join27(cat, "AppIcon.appiconset", "icon_1024.png")) : null;
6033
6321
  }
6034
6322
  function placeholderHashes() {
6035
6323
  const current = templateIconSha256();
@@ -6048,7 +6336,7 @@ function generationAllowed(app, configTs, opts = {}) {
6048
6336
  return { allowed: true, reason: "declares an icon block" };
6049
6337
  }
6050
6338
  const iosCat = iosAssetCatalog2(app);
6051
- const iosIcon = iosCat ? join26(iosCat, "AppIcon.appiconset", "icon_1024.png") : void 0;
6339
+ const iosIcon = iosCat ? join27(iosCat, "AppIcon.appiconset", "icon_1024.png") : void 0;
6052
6340
  if (!iosIcon || !existsSync23(iosIcon)) return { allowed: true, reason: "no icon yet" };
6053
6341
  const hash = fileSha256(iosIcon);
6054
6342
  const placeholders = opts.placeholderHashes ?? placeholderHashes();
@@ -6074,7 +6362,7 @@ var init_plan2 = __esm({
6074
6362
 
6075
6363
  // ../native-config/icon.ts
6076
6364
  import { existsSync as existsSync24, readFileSync as readFileSync26 } from "node:fs";
6077
- import { join as join27 } from "node:path";
6365
+ import { join as join28 } from "node:path";
6078
6366
  function androidAdaptiveIconXml() {
6079
6367
  return [
6080
6368
  '<?xml version="1.0" encoding="utf-8"?>',
@@ -6101,7 +6389,7 @@ function androidIconArtifacts(app, configTs) {
6101
6389
  if (!androidOptedIn2(manifest)) return [];
6102
6390
  if (!generationAllowed(app, configTs).allowed) return [];
6103
6391
  const res = androidResDir2(manifest);
6104
- const foreground = join27(res, "mipmap-mdpi", "ic_launcher_foreground.png");
6392
+ const foreground = join28(res, "mipmap-mdpi", "ic_launcher_foreground.png");
6105
6393
  if (!existsSync24(foreground)) {
6106
6394
  throw new Error(
6107
6395
  `native-config icon: ${app} opts into @mipmap/ic_launcher but has no mipmap-*/ic_launcher_foreground.png. Run \`npm run app-icons -- ${app}\` first \u2014 the XML emitted here points at those rasters.`
@@ -6111,11 +6399,11 @@ function androidIconArtifacts(app, configTs) {
6111
6399
  const spec = iconSpec(app, configTs, id, resolveTokensPath(app, configTs).path);
6112
6400
  return [
6113
6401
  ...ANDROID_ADAPTIVE_FILES.map((name) => ({
6114
- path: join27(res, "mipmap-anydpi-v26", name),
6402
+ path: join28(res, "mipmap-anydpi-v26", name),
6115
6403
  content: androidAdaptiveIconXml()
6116
6404
  })),
6117
6405
  {
6118
- path: join27(res, "values", ANDROID_ICON_BACKGROUND_FILE),
6406
+ path: join28(res, "values", ANDROID_ICON_BACKGROUND_FILE),
6119
6407
  content: androidIconBackgroundXml(spec.background)
6120
6408
  }
6121
6409
  ];
@@ -6350,7 +6638,7 @@ __export(gen_native_config_exports, {
6350
6638
  watchInfoPlan: () => watchInfoPlan
6351
6639
  });
6352
6640
  import { existsSync as existsSync25, mkdirSync as mkdirSync12, readFileSync as readFileSync27, readdirSync as readdirSync13, rmSync as rmSync3, writeFileSync as writeFileSync11 } from "node:fs";
6353
- import { dirname as dirname17, join as join28 } from "node:path";
6641
+ import { dirname as dirname17, join as join29 } from "node:path";
6354
6642
  function parseStringArray(configTs, field) {
6355
6643
  const m = new RegExp(`${field}:\\s*\\[([^\\]]*)\\]`).exec(configTs);
6356
6644
  if (!m) return [];
@@ -6422,7 +6710,7 @@ function buildSet(capabilities, envelope = []) {
6422
6710
  return [...new Set([...capabilities, ...envelope].map(catalogKey))].sort();
6423
6711
  }
6424
6712
  function loadOverrides(app) {
6425
- const p = join28(appDir(app), "native-config.overrides.json");
6713
+ const p = join29(appDir(app), "native-config.overrides.json");
6426
6714
  if (!existsSync25(p)) return {};
6427
6715
  return JSON.parse(readFileSync27(p, "utf8"));
6428
6716
  }
@@ -6479,14 +6767,14 @@ function endText(kind, label2, indent) {
6479
6767
  return kind === "yaml" ? `${indent}# <<< ${label2} <<<` : `${indent}<!-- <<< ${label2} <<< -->`;
6480
6768
  }
6481
6769
  function hostAppliesDeferredWidgetActions(app) {
6482
- const root = join28(appDir(app), "ios");
6770
+ const root = join29(appDir(app), "ios");
6483
6771
  if (!existsSync25(root)) return false;
6484
6772
  const stack = [root];
6485
6773
  while (stack.length) {
6486
6774
  const dir = stack.pop();
6487
6775
  for (const entry of readdirSync13(dir, { withFileTypes: true })) {
6488
6776
  if (entry.name.startsWith(".") || entry.name === "GeneratedLiveActivity") continue;
6489
- const path = join28(dir, entry.name);
6777
+ const path = join29(dir, entry.name);
6490
6778
  if (entry.isDirectory()) {
6491
6779
  if (entry.name.endsWith(".xcodeproj") || entry.name === "build") continue;
6492
6780
  stack.push(path);
@@ -6513,14 +6801,14 @@ function injectBlock(text2, kind, label2, render) {
6513
6801
  return [...lines.slice(0, beginIdx), ...block, ...lines.slice(endIdx + 1)].join("\n");
6514
6802
  }
6515
6803
  function androidManifestPath2(app) {
6516
- const androidDir = join28(appDir(app), "android");
6517
- const preferred = join28(androidDir, `${app}-app`, "src", "main", "AndroidManifest.xml");
6804
+ const androidDir = join29(appDir(app), "android");
6805
+ const preferred = join29(androidDir, `${app}-app`, "src", "main", "AndroidManifest.xml");
6518
6806
  if (existsSync25(preferred)) return preferred;
6519
- const candidates = existsSync25(androidDir) ? readdirSync13(androidDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.endsWith("-app")).map((d) => join28(androidDir, d.name, "src", "main", "AndroidManifest.xml")).filter(existsSync25) : [];
6807
+ const candidates = existsSync25(androidDir) ? readdirSync13(androidDir, { withFileTypes: true }).filter((d) => d.isDirectory() && d.name.endsWith("-app")).map((d) => join29(androidDir, d.name, "src", "main", "AndroidManifest.xml")).filter(existsSync25) : [];
6520
6808
  return candidates.length === 1 ? candidates[0] : preferred;
6521
6809
  }
6522
6810
  function automotiveAppDescPath(app) {
6523
- return join28(dirname17(androidManifestPath2(app)), "res", "xml", "automotive_app_desc.xml");
6811
+ return join29(dirname17(androidManifestPath2(app)), "res", "xml", "automotive_app_desc.xml");
6524
6812
  }
6525
6813
  function applyToFile(path, kind, label2, render, check) {
6526
6814
  if (!existsSync25(path)) return { path, outcome: "skipped" };
@@ -6619,9 +6907,9 @@ function genNativeConfig(app, opts = {}) {
6619
6907
  const plan = live2;
6620
6908
  files.push(applyToFile(iosYml, "yaml", LABEL_LIVE_TARGET, (i) => plan.target.map((line) => i + line), check));
6621
6909
  files.push(applyToFile(iosYml, "yaml", LABEL_LIVE_EMBED, (i) => plan.embed.map((line) => i + line), check));
6622
- const source = join28(dirname17(iosYml), LIVE_SOURCE_PATH);
6910
+ const source = join29(dirname17(iosYml), LIVE_SOURCE_PATH);
6623
6911
  files.push(plan.source ? applyWholeFile(source, plan.source, check) : removeWholeFile(source, check));
6624
- const entitlements = join28(dirname17(iosYml), LIVE_ENTITLEMENTS_PATH);
6912
+ const entitlements = join29(dirname17(iosYml), LIVE_ENTITLEMENTS_PATH);
6625
6913
  files.push(plan.entitlements ? applyWholeFile(entitlements, plan.entitlements, check) : removeWholeFile(entitlements, check));
6626
6914
  }
6627
6915
  const iosSplashManaged = existsSync25(iosYml) && hasMarkers(readFileSync27(iosYml, "utf8"), "yaml", LABEL_IOS_SPLASH);
@@ -6759,7 +7047,8 @@ async function devModuleUrl(entry, dir) {
6759
7047
  // intentionally point at `.ts` sources, which are only loadable under tsx.
6760
7048
  umbrella: true,
6761
7049
  resolveTsExtensions: true,
6762
- absWorkingDir: dir
7050
+ absWorkingDir: dir,
7051
+ confineTo: dir
6763
7052
  });
6764
7053
  return pathToFileURL5(out).href;
6765
7054
  }
@@ -6881,8 +7170,8 @@ var init_build_sha = __esm({
6881
7170
 
6882
7171
  // ../../core/dev-server/src/app-assets.ts
6883
7172
  import { createHash as createHash8 } from "node:crypto";
6884
- import { existsSync as existsSync27, lstatSync as lstatSync3, readFileSync as readFileSync29, readdirSync as readdirSync15, realpathSync as realpathSync2, statSync as statSync5 } from "node:fs";
6885
- import { join as join29, resolve as resolve19, sep as sep4 } from "node:path";
7173
+ import { existsSync as existsSync27, lstatSync as lstatSync3, readFileSync as readFileSync29, readdirSync as readdirSync15, realpathSync as realpathSync3, statSync as statSync6 } from "node:fs";
7174
+ import { join as join30, resolve as resolve19, sep as sep5 } from "node:path";
6886
7175
  function contentTypeFor(name) {
6887
7176
  const ext = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1).toLowerCase() : "";
6888
7177
  return CONTENT_TYPES[ext] ?? "application/octet-stream";
@@ -6896,19 +7185,19 @@ function assetSha256(bytes2) {
6896
7185
  function findAssetsDir(appDir2) {
6897
7186
  let declared;
6898
7187
  try {
6899
- declared = JSON.parse(readFileSync29(join29(appDir2, "himalaya.content.json"), "utf8")).assets;
7188
+ declared = JSON.parse(readFileSync29(join30(appDir2, "himalaya.content.json"), "utf8")).assets;
6900
7189
  } catch {
6901
7190
  declared = void 0;
6902
7191
  }
6903
7192
  const rel = typeof declared === "string" && declared ? declared : "assets";
6904
7193
  const abs = resolve19(appDir2, rel);
6905
7194
  const base = resolve19(appDir2);
6906
- if (abs !== base && !abs.startsWith(base + sep4)) return null;
6907
- return existsSync27(abs) && statSync5(abs).isDirectory() ? abs : null;
7195
+ if (abs !== base && !abs.startsWith(base + sep5)) return null;
7196
+ return existsSync27(abs) && statSync6(abs).isDirectory() ? abs : null;
6908
7197
  }
6909
7198
  function readOtaOnlyPatterns(appDir2) {
6910
7199
  try {
6911
- const raw = JSON.parse(readFileSync29(join29(appDir2, "assets.json"), "utf8"));
7200
+ const raw = JSON.parse(readFileSync29(join30(appDir2, "assets.json"), "utf8"));
6912
7201
  return Array.isArray(raw.otaOnly) ? raw.otaOnly.filter((p) => typeof p === "string") : [];
6913
7202
  } catch {
6914
7203
  return [];
@@ -6926,7 +7215,7 @@ function collectAssets(appDir2) {
6926
7215
  const walk2 = (abs, prefix) => {
6927
7216
  for (const entry of readdirSync15(abs, { withFileTypes: true })) {
6928
7217
  if (entry.name.startsWith(".")) continue;
6929
- const child = join29(abs, entry.name);
7218
+ const child = join30(abs, entry.name);
6930
7219
  const name = prefix ? `${prefix}/${entry.name}` : entry.name;
6931
7220
  if (entry.isDirectory()) {
6932
7221
  walk2(child, name);
@@ -6935,7 +7224,7 @@ function collectAssets(appDir2) {
6935
7224
  if (!entry.isFile()) continue;
6936
7225
  const ext = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1).toLowerCase() : "";
6937
7226
  const baked = !OTA_ONLY_BY_DEFAULT.has(ext) && !otaOnly.some((p) => matchesPattern(name, p));
6938
- out.push({ name, path: child, bytes: statSync5(child).size, contentType: contentTypeFor(name), baked });
7227
+ out.push({ name, path: child, bytes: statSync6(child).size, contentType: contentTypeFor(name), baked });
6939
7228
  }
6940
7229
  };
6941
7230
  walk2(dir, "");
@@ -6944,7 +7233,7 @@ function collectAssets(appDir2) {
6944
7233
  function collectBrandFonts(appDir2) {
6945
7234
  let faces;
6946
7235
  try {
6947
- faces = JSON.parse(readFileSync29(join29(appDir2, "media/fonts/faces.json"), "utf8"));
7236
+ faces = JSON.parse(readFileSync29(join30(appDir2, "media/fonts/faces.json"), "utf8"));
6948
7237
  } catch {
6949
7238
  return [];
6950
7239
  }
@@ -6957,11 +7246,11 @@ function collectBrandFonts(appDir2) {
6957
7246
  const rel = typeof raw?.file === "string" ? raw.file : "";
6958
7247
  if (!family || !postscriptName || !rel) continue;
6959
7248
  if (rel.startsWith("/") || rel.split("/").includes("..")) continue;
6960
- const path = join29(appDir2, rel);
7249
+ const path = join30(appDir2, rel);
6961
7250
  try {
6962
7251
  if (!lstatSync3(path).isFile()) continue;
6963
- const root = realpathSync2(appDir2);
6964
- if (!realpathSync2(path).startsWith(root + sep4)) continue;
7252
+ const root = realpathSync3(appDir2);
7253
+ if (!realpathSync3(path).startsWith(root + sep5)) continue;
6965
7254
  } catch {
6966
7255
  continue;
6967
7256
  }
@@ -7067,7 +7356,7 @@ async function injectRequest(listener, request) {
7067
7356
  req.headers.host ??= url.host;
7068
7357
  if (body && body.length > 0) req.push(body);
7069
7358
  req.push(null);
7070
- return await new Promise((resolve40, reject) => {
7359
+ return await new Promise((resolve41, reject) => {
7071
7360
  const streamed = () => {
7072
7361
  queueMicrotask(() => {
7073
7362
  req.emit("close");
@@ -7083,7 +7372,7 @@ async function injectRequest(listener, request) {
7083
7372
  else headers2.set(name, value);
7084
7373
  }
7085
7374
  const bodyless = out.status === 204 || out.status === 304 || request.method === "HEAD";
7086
- resolve40(new Response(bodyless ? null : out.body, { status: out.status, headers: headers2 }));
7375
+ resolve41(new Response(bodyless ? null : out.body, { status: out.status, headers: headers2 }));
7087
7376
  }, streamed);
7088
7377
  res.on("inject-error", reject);
7089
7378
  try {
@@ -8782,7 +9071,7 @@ var init_stable_ids = __esm({
8782
9071
  // ../../core/dev-server/src/preview.ts
8783
9072
  import { spawn as spawn2 } from "node:child_process";
8784
9073
  import { createHash as createHash11 } from "node:crypto";
8785
- import { existsSync as existsSync30, createReadStream, statSync as statSync6, readFileSync as readFileSync33, renameSync, unlinkSync as unlinkSync2 } from "node:fs";
9074
+ import { existsSync as existsSync30, createReadStream, statSync as statSync7, readFileSync as readFileSync33, renameSync, unlinkSync as unlinkSync2 } from "node:fs";
8786
9075
  import { homedir as homedir5, tmpdir as tmpdir2 } from "node:os";
8787
9076
  import { resolve as resolvePath, dirname as dirname20 } from "node:path";
8788
9077
  import { fileURLToPath as fileURLToPath14 } from "node:url";
@@ -9455,12 +9744,12 @@ function finalizeRecording(key2, rec) {
9455
9744
  if (rec.finalize) return rec.finalize;
9456
9745
  rec.finalize = (async () => {
9457
9746
  clearTimeout(rec.autoStop);
9458
- await new Promise((resolve40) => {
9747
+ await new Promise((resolve41) => {
9459
9748
  let done = false;
9460
9749
  const finish2 = () => {
9461
9750
  if (!done) {
9462
9751
  done = true;
9463
- resolve40();
9752
+ resolve41();
9464
9753
  }
9465
9754
  };
9466
9755
  rec.child.once("close", finish2);
@@ -9480,7 +9769,7 @@ function finalizeRecording(key2, rec) {
9480
9769
  } catch {
9481
9770
  }
9482
9771
  }
9483
- const sizeBytes = existsSync30(rec.localPath) ? statSync6(rec.localPath).size : 0;
9772
+ const sizeBytes = existsSync30(rec.localPath) ? statSync7(rec.localPath).size : 0;
9484
9773
  clips.set(rec.id, { path: rec.localPath, mime: rec.mime });
9485
9774
  recordings.delete(key2);
9486
9775
  return { sizeBytes };
@@ -9557,7 +9846,7 @@ async function handlePreviewClip(res, url) {
9557
9846
  res.setHeader("content-type", "application/json");
9558
9847
  return void res.end(JSON.stringify({ error: "clip_not_found", id }));
9559
9848
  }
9560
- const size = statSync6(clip.path).size;
9849
+ const size = statSync7(clip.path).size;
9561
9850
  res.statusCode = 200;
9562
9851
  res.setHeader("content-type", clip.mime);
9563
9852
  res.setHeader("content-length", String(size));
@@ -9571,7 +9860,7 @@ var init_preview = __esm({
9571
9860
  DEVICE_LIST_TIMEOUT_MS = 5e3;
9572
9861
  defaultRunner2 = {
9573
9862
  capture(cmd, args, timeoutMs = DEFAULT_CAPTURE_TIMEOUT_MS) {
9574
- return new Promise((resolve40, reject) => {
9863
+ return new Promise((resolve41, reject) => {
9575
9864
  const child = spawn2(cmd, args, { stdio: ["ignore", "pipe", "pipe"] });
9576
9865
  const out = [];
9577
9866
  const err = [];
@@ -9593,7 +9882,7 @@ var init_preview = __esm({
9593
9882
  child.stderr.on("data", (b) => err.push(b));
9594
9883
  child.on("error", (e) => settle(reject, e));
9595
9884
  child.on("close", (code) => {
9596
- if (code === 0) settle(resolve40, Buffer.concat(out));
9885
+ if (code === 0) settle(resolve41, Buffer.concat(out));
9597
9886
  else settle(reject, new Error(`${cmd} exited ${code}: ${Buffer.concat(err).toString("utf8")}`));
9598
9887
  });
9599
9888
  });
@@ -11170,7 +11459,6 @@ function shouldRevealError(args) {
11170
11459
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
11171
11460
  var init_validation = __esm({
11172
11461
  "../../core/ts/src/validation.ts"() {
11173
- "use strict";
11174
11462
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
11175
11463
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
11176
11464
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -14516,7 +14804,6 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
14516
14804
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
14517
14805
  var init_worker_subtree = __esm({
14518
14806
  "../../core/ts/src/worker-subtree.ts"() {
14519
- "use strict";
14520
14807
  init_worker_subtree_safe_types_generated();
14521
14808
  WORKER_SUBTREE_CEILINGS = Object.freeze({
14522
14809
  maxEncodedBytes: 65536,
@@ -14736,8 +15023,8 @@ function ensureCountUpRuntime() {
14736
15023
  const neg = fixed.startsWith("-");
14737
15024
  const body = neg ? fixed.slice(1) : fixed;
14738
15025
  const [int, frac] = body.split(".");
14739
- const sep10 = int.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
14740
- return (neg ? "-" : "") + sep10 + (frac ? "." + frac : "");
15026
+ const sep11 = int.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
15027
+ return (neg ? "-" : "") + sep11 + (frac ? "." + frac : "");
14741
15028
  };
14742
15029
  const animateEl = (el) => {
14743
15030
  const node = el;
@@ -19902,7 +20189,7 @@ async function stabilizePngPage(page, scheme) {
19902
20189
  const browser = globalThis;
19903
20190
  browser.document.documentElement.style.colorScheme = selected;
19904
20191
  await browser.document.fonts?.ready;
19905
- await new Promise((resolve40) => browser.requestAnimationFrame(() => browser.requestAnimationFrame(() => resolve40())));
20192
+ await new Promise((resolve41) => browser.requestAnimationFrame(() => browser.requestAnimationFrame(() => resolve41())));
19906
20193
  }, scheme);
19907
20194
  }
19908
20195
  async function renderPngBatch(items) {
@@ -21914,18 +22201,18 @@ var init_motion_flags = __esm({
21914
22201
 
21915
22202
  // ../../core/dev-server/src/kits.ts
21916
22203
  import { readdirSync as readdirSync16, readFileSync as readFileSync36 } from "node:fs";
21917
- import { resolve as resolve25, join as join30 } from "node:path";
22204
+ import { resolve as resolve25, join as join31 } from "node:path";
21918
22205
  import { fileURLToPath as fileURLToPath15 } from "node:url";
21919
22206
  function listKits() {
21920
22207
  return readdirSync16(CATALOG).filter((f) => f.endsWith(".json")).map((f) => {
21921
- const { tokens, ...meta } = JSON.parse(readFileSync36(join30(CATALOG, f), "utf8"));
22208
+ const { tokens, ...meta } = JSON.parse(readFileSync36(join31(CATALOG, f), "utf8"));
21922
22209
  return meta;
21923
22210
  });
21924
22211
  }
21925
22212
  function readKit(id) {
21926
22213
  if (!/^[a-z0-9-]+$/.test(id)) return null;
21927
22214
  try {
21928
- return JSON.parse(readFileSync36(join30(CATALOG, `${id}.json`), "utf8"));
22215
+ return JSON.parse(readFileSync36(join31(CATALOG, `${id}.json`), "utf8"));
21929
22216
  } catch {
21930
22217
  return null;
21931
22218
  }
@@ -21934,7 +22221,7 @@ var ROOT5, CATALOG;
21934
22221
  var init_kits = __esm({
21935
22222
  "../../core/dev-server/src/kits.ts"() {
21936
22223
  ROOT5 = resolve25(fileURLToPath15(new URL("../../..", import.meta.url)));
21937
- CATALOG = join30(ROOT5, "stdlib/design-kits");
22224
+ CATALOG = join31(ROOT5, "stdlib/design-kits");
21938
22225
  }
21939
22226
  });
21940
22227
 
@@ -24137,7 +24424,7 @@ __export(config_exports, {
24137
24424
  validateOverlayPatch: () => validateOverlayPatch
24138
24425
  });
24139
24426
  import { mkdirSync as mkdirSync15, readFileSync as readFileSync38, writeFileSync as writeFileSync12 } from "node:fs";
24140
- import { dirname as dirname21, join as join31, resolve as resolve27 } from "node:path";
24427
+ import { dirname as dirname21, join as join32, resolve as resolve27 } from "node:path";
24141
24428
  import { fileURLToPath as fileURLToPath16 } from "node:url";
24142
24429
  function strip(line) {
24143
24430
  const h = line.indexOf("#");
@@ -24202,14 +24489,14 @@ function parseRevocationsYaml(text2) {
24202
24489
  function appConfigPath(app, file, root = REPO_ROOT6) {
24203
24490
  assertAppId(app, "appConfigPath");
24204
24491
  for (const base of ["apps", "test-apps"]) {
24205
- const p = join31(root, base, app, file);
24492
+ const p = join32(root, base, app, file);
24206
24493
  try {
24207
24494
  readFileSync38(p);
24208
24495
  return p;
24209
24496
  } catch {
24210
24497
  }
24211
24498
  }
24212
- return join31(root, "apps", app, file);
24499
+ return join32(root, "apps", app, file);
24213
24500
  }
24214
24501
  function serializeRolloutYaml(cfg) {
24215
24502
  const lines = [`app: ${cfg.app}`, "rings:"];
@@ -24353,7 +24640,7 @@ var init_config2 = __esm({
24353
24640
  import { createServer as createServer2 } from "node:http";
24354
24641
  import { readFile as readFile2, stat } from "node:fs/promises";
24355
24642
  import { readFileSync as readFileSync39, existsSync as existsSync33, watch, mkdirSync as mkdirSync16, rmSync as rmSync4 } from "node:fs";
24356
- import { extname, resolve as resolve28, normalize as normalize3, sep as sep5, join as join32 } from "node:path";
24643
+ import { extname, resolve as resolve28, normalize as normalize3, sep as sep6, join as join33 } from "node:path";
24357
24644
  import { createHash as createHash15, randomUUID } from "node:crypto";
24358
24645
  import { fileURLToPath as fileURLToPath17, pathToFileURL as pathToFileURL6 } from "node:url";
24359
24646
  function deepMerge2(base, patch) {
@@ -24528,7 +24815,7 @@ async function runScreenDiagnostics(state, screenId, descriptor) {
24528
24815
  }
24529
24816
  function servedFontFaces(appDir2, tokens) {
24530
24817
  try {
24531
- const policyPath = join32(appDir2, "fonts.json");
24818
+ const policyPath = join33(appDir2, "fonts.json");
24532
24819
  const policy = existsSync33(policyPath) ? JSON.parse(readFileSync39(policyPath, "utf8")) : {};
24533
24820
  const plan = planFonts(tokens, policy);
24534
24821
  if (plan.families.length === 0) return void 0;
@@ -25171,7 +25458,7 @@ function startBundleSourceWatcher(state) {
25171
25458
  try {
25172
25459
  const watcher = watch(TIER5_SRC_ROOT, { persistent: false, recursive: true }, (_event, filename) => {
25173
25460
  if (!filename || !/\.(ts|tsx)$/.test(filename)) return;
25174
- const name = filename.split(sep5)[0];
25461
+ const name = filename.split(sep6)[0];
25175
25462
  if (!name || name.startsWith(".")) return;
25176
25463
  pending.add(name);
25177
25464
  if (timer) clearTimeout(timer);
@@ -25201,7 +25488,7 @@ async function reloadAppModule(state, opts) {
25201
25488
  const outfile = resolve28(HMR_TMP_DIR, `${current.name}-${++appReloadSeq}.mjs`);
25202
25489
  try {
25203
25490
  const lib = await import(pathToFileURL6(resolve28(TIER5_SRC_ROOT, "build-lib.mjs")).href);
25204
- await lib.buildAppEntry(entry, outfile);
25491
+ await lib.buildAppEntry(entry, outfile, { confineTo: current.dir });
25205
25492
  const mod = await import(pathToFileURL6(outfile).href);
25206
25493
  const next = mod.default;
25207
25494
  if (!next || !next.config || typeof next.homeScreenId !== "string" || !next.screens || typeof next.screens.exact !== "object" || Object.keys(next.screens.exact).length === 0 || !Array.isArray(next.actions) || !next.seed) {
@@ -25286,7 +25573,7 @@ function startAppSourceWatcher(state) {
25286
25573
  try {
25287
25574
  const watcher = watch(appDir2, { persistent: false, recursive: true }, (_event, filename) => {
25288
25575
  if (!filename) return;
25289
- const rel = String(filename).split(sep5).join("/");
25576
+ const rel = String(filename).split(sep6).join("/");
25290
25577
  if (!classifyAppPath(rel)) return;
25291
25578
  pending.add(rel);
25292
25579
  if (timer) clearTimeout(timer);
@@ -25468,7 +25755,7 @@ async function handleWebWix(req, res, state) {
25468
25755
  const match = state.wix.mode === "mock" ? matchRuleEx(state.wix.rules, method, target.pathname, { host: target.host, query, body }) : void 0;
25469
25756
  if (match && (match.rule.kind ?? "mock") === "mock") {
25470
25757
  const mocked = match.rule.response;
25471
- if (mocked.delayMs) await new Promise((resolve40) => setTimeout(resolve40, mocked.delayMs));
25758
+ if (mocked.delayMs) await new Promise((resolve41) => setTimeout(resolve41, mocked.delayMs));
25472
25759
  const responseBody = mocked.template ? renderTemplate(mocked.body, match.captures) : mocked.body;
25473
25760
  recordWixTraffic(state.wix, state.appName, state.adminUrl, {
25474
25761
  ts: started,
@@ -27448,7 +27735,7 @@ __export(build_exports2, {
27448
27735
  });
27449
27736
  import { createHash as createHash18 } from "node:crypto";
27450
27737
  import { readFileSync as readFileSync40, readdirSync as readdirSync18, existsSync as existsSync34 } from "node:fs";
27451
- import { dirname as dirname22, join as join33, resolve as resolve29 } from "node:path";
27738
+ import { dirname as dirname22, join as join34, resolve as resolve29 } from "node:path";
27452
27739
  import { fileURLToPath as fileURLToPath18 } from "node:url";
27453
27740
  import { createRequire as createRequire2 } from "node:module";
27454
27741
  import { execFileSync as execFileSync4 } from "node:child_process";
@@ -27486,7 +27773,7 @@ function l10nLintOptions(locale, appDir2) {
27486
27773
  const tags = (locale.translations ?? []).filter((t) => t !== locale.default);
27487
27774
  const catalog = /* @__PURE__ */ new Set();
27488
27775
  for (const tag of tags) {
27489
- const path = join33(appDir2, "l10n", `${tag}.json`);
27776
+ const path = join34(appDir2, "l10n", `${tag}.json`);
27490
27777
  if (!existsSync34(path)) continue;
27491
27778
  try {
27492
27779
  const parsed = JSON.parse(readFileSync40(path, "utf8"));
@@ -27583,7 +27870,7 @@ function shellSha(root = REPO_ROOT7) {
27583
27870
  function isMonorepoCheckout(dir) {
27584
27871
  try {
27585
27872
  const top = execFileSync4("git", ["rev-parse", "--show-toplevel"], { cwd: dir, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
27586
- return top.length > 0 && existsSync34(join33(top, "core", "schema", "core.proto"));
27873
+ return top.length > 0 && existsSync34(join34(top, "core", "schema", "core.proto"));
27587
27874
  } catch {
27588
27875
  return false;
27589
27876
  }
@@ -27813,7 +28100,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
27813
28100
  if (addrCache.has(name)) return addrCache.get(name);
27814
28101
  let bytes2;
27815
28102
  try {
27816
- bytes2 = readFileSync40(join33(tier5Dir, `${name}.bundle.js`));
28103
+ bytes2 = readFileSync40(join34(tier5Dir, `${name}.bundle.js`));
27817
28104
  } catch {
27818
28105
  return null;
27819
28106
  }
@@ -27834,7 +28121,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
27834
28121
  const MAX_AUTHORING_DOC_BYTES = 128 * 1024;
27835
28122
  if (store.putDoc) {
27836
28123
  for (const [name, file] of [["spec", "SPEC.md"], ["mobile-ux", "MOBILE-UX.md"]]) {
27837
- const path = join33(preloaded.dir, file);
28124
+ const path = join34(preloaded.dir, file);
27838
28125
  if (!existsSync34(path)) continue;
27839
28126
  const bytes2 = readFileSync40(path);
27840
28127
  if (bytes2.length === 0 || bytes2.length > MAX_AUTHORING_DOC_BYTES) {
@@ -27894,7 +28181,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
27894
28181
  const canEmitFonts = hasVendoredFloor();
27895
28182
  try {
27896
28183
  const appTokens = readTokensForLint(preloaded) ?? {};
27897
- const policy = existsSync34(join33(preloaded.dir, "fonts.json")) ? JSON.parse(readFileSync40(join33(preloaded.dir, "fonts.json"), "utf8")) : {};
28184
+ const policy = existsSync34(join34(preloaded.dir, "fonts.json")) ? JSON.parse(readFileSync40(join34(preloaded.dir, "fonts.json"), "utf8")) : {};
27898
28185
  fontPlan = planFonts(appTokens, policy);
27899
28186
  } catch (err) {
27900
28187
  throw new Error(
@@ -28359,7 +28646,7 @@ async function buildFunctionsOnlyRelease(preloaded, dest) {
28359
28646
  await store.reset(app);
28360
28647
  const sources = {};
28361
28648
  for (const definition of config.functions.functions) {
28362
- const candidates = [".ts", ".js", ".mts", ".mjs"].map((extension) => join33(preloaded.dir, "functions", `${definition.name}${extension}`));
28649
+ const candidates = [".ts", ".js", ".mts", ".mjs"].map((extension) => join34(preloaded.dir, "functions", `${definition.name}${extension}`));
28363
28650
  const source = candidates.find((candidate) => existsSync34(candidate));
28364
28651
  if (!source) throw new Error(`missing source for function ${definition.name}; expected functions/${definition.name}.ts`);
28365
28652
  sources[definition.name] = readFileSync40(source, "utf8");
@@ -28390,7 +28677,7 @@ async function buildFunctionsOnlyRelease(preloaded, dest) {
28390
28677
  return { manifest, failedToBuild: [], missingBundles: [], auth: {} };
28391
28678
  }
28392
28679
  function listBuildableApps() {
28393
- const appsDir = join33(REPO_ROOT7, "apps");
28680
+ const appsDir = join34(REPO_ROOT7, "apps");
28394
28681
  return readdirSync18(appsDir, { withFileTypes: true }).filter((d) => d.isDirectory() && !d.name.startsWith("_")).map((d) => d.name);
28395
28682
  }
28396
28683
  var BAKE_OFFLINE_BASE, KNOWN_ICON_NAMES, KNOWN_ICONS, REPO_ROOT7, TIER5_DIR, requireFromHere, SOURCEMAP_ON_DEMAND_PATH, BAKED_SERVER_BASE_URL;
@@ -28421,9 +28708,9 @@ var init_build3 = __esm({
28421
28708
  KNOWN_ICON_NAMES = [...ICON_NAMES].sort();
28422
28709
  KNOWN_ICONS = new Set(KNOWN_ICON_NAMES);
28423
28710
  REPO_ROOT7 = resolve29(dirname22(fileURLToPath18(import.meta.url)), "../..");
28424
- TIER5_DIR = join33(REPO_ROOT7, "core/dev-server/tier5-bundles");
28711
+ TIER5_DIR = join34(REPO_ROOT7, "core/dev-server/tier5-bundles");
28425
28712
  requireFromHere = createRequire2(import.meta.url);
28426
- SOURCEMAP_ON_DEMAND_PATH = join33(REPO_ROOT7, "core/dev-server/tier5-bundles-src/sourcemap-on-demand.mjs");
28713
+ SOURCEMAP_ON_DEMAND_PATH = join34(REPO_ROOT7, "core/dev-server/tier5-bundles-src/sourcemap-on-demand.mjs");
28427
28714
  BAKED_SERVER_BASE_URL = "http://baked.himalaya.invalid";
28428
28715
  }
28429
28716
  });
@@ -28470,6 +28757,133 @@ var init_net_scenario = __esm({
28470
28757
  }
28471
28758
  });
28472
28759
 
28760
+ // ../../core/dev-server/src/crawl-relations.ts
28761
+ function inversePartner(action, declared) {
28762
+ if (/^toggle/i.test(action)) return null;
28763
+ const candidates = [];
28764
+ if (/^un[A-Z_a-z]/.test(action)) {
28765
+ candidates.push(lowerFirst(action.slice(2)), action.slice(2));
28766
+ } else {
28767
+ candidates.push("un" + action, "un" + upperFirst(action));
28768
+ }
28769
+ for (const c of candidates) if (c !== action && declared.includes(c)) return c;
28770
+ for (const [a, b] of PREFIX_PAIRS) {
28771
+ for (const [from, to] of [
28772
+ [a, b],
28773
+ [b, a]
28774
+ ]) {
28775
+ if (!action.toLowerCase().startsWith(from)) continue;
28776
+ const rest = action.slice(from.length);
28777
+ for (const c of [to + rest, to + upperFirst(rest), to]) {
28778
+ if (c !== action && declared.includes(c)) return c;
28779
+ }
28780
+ }
28781
+ }
28782
+ return null;
28783
+ }
28784
+ function isUndoAction(action) {
28785
+ if (/^toggle/i.test(action)) return false;
28786
+ if (/^un[A-Z]/.test(action) || /^un[a-z]/.test(action)) return true;
28787
+ return PREFIX_PAIRS.some(([, undo]) => action.toLowerCase().startsWith(undo));
28788
+ }
28789
+ function isOptionSelection(params) {
28790
+ return Object.keys(params).some((k) => /^(selected|tab|segment|mode|scope)/i.test(k) || /Id$/.test(k) || k === "id");
28791
+ }
28792
+ function stripVolatile(value, depth = 0) {
28793
+ if (depth > 8 || value === null || typeof value !== "object") return value;
28794
+ if (Array.isArray(value)) return value.map((v) => stripVolatile(v, depth + 1));
28795
+ const out = {};
28796
+ for (const k of Object.keys(value).sort()) {
28797
+ if (VOLATILE_KEY.test(k)) continue;
28798
+ out[k] = stripVolatile(value[k], depth + 1);
28799
+ }
28800
+ return out;
28801
+ }
28802
+ function relationalSignature(state) {
28803
+ return JSON.stringify(stripVolatile(state));
28804
+ }
28805
+ function listLengths(state, depth = 4) {
28806
+ const out = {};
28807
+ const walk2 = (node, path, left) => {
28808
+ if (left < 0 || node === null || typeof node !== "object") return;
28809
+ if (Array.isArray(node)) {
28810
+ out[path] = node.length;
28811
+ return;
28812
+ }
28813
+ for (const [k, v] of Object.entries(node)) {
28814
+ walk2(v, path ? `${path}.${k}` : k, left - 1);
28815
+ }
28816
+ };
28817
+ walk2(state, "", depth);
28818
+ return out;
28819
+ }
28820
+ function summarizeDiff(aSig, bSig, limit = 3) {
28821
+ let a;
28822
+ let b;
28823
+ try {
28824
+ a = JSON.parse(aSig);
28825
+ b = JSON.parse(bSig);
28826
+ } catch {
28827
+ return aSig === bSig ? "" : "states differ (unparseable signature)";
28828
+ }
28829
+ const diffs = [];
28830
+ const brief = (v) => {
28831
+ const t = JSON.stringify(v);
28832
+ if (t === void 0) return "undefined";
28833
+ return t.length > 80 ? `${t.slice(0, 77)}\u2026` : t;
28834
+ };
28835
+ const walk2 = (x, y, path) => {
28836
+ if (diffs.length >= limit) return;
28837
+ const bothObjects = x !== null && y !== null && typeof x === "object" && typeof y === "object" && !Array.isArray(x) && !Array.isArray(y);
28838
+ if (bothObjects) {
28839
+ for (const k of /* @__PURE__ */ new Set([...Object.keys(x), ...Object.keys(y)])) {
28840
+ walk2(x[k], y[k], path ? `${path}.${k}` : k);
28841
+ }
28842
+ return;
28843
+ }
28844
+ if (Array.isArray(x) && Array.isArray(y)) {
28845
+ if (x.length !== y.length) {
28846
+ diffs.push(`${path || "state"}: ${x.length} \u2192 ${y.length} items`);
28847
+ return;
28848
+ }
28849
+ for (let i = 0; i < x.length && diffs.length < limit; i++) walk2(x[i], y[i], `${path}[${i}]`);
28850
+ return;
28851
+ }
28852
+ const xs = brief(x);
28853
+ const ys = brief(y);
28854
+ if (xs !== ys) diffs.push(`${path || "state"}: ${xs} \u2192 ${ys}`);
28855
+ };
28856
+ walk2(a, b, "");
28857
+ return diffs.slice(0, limit).join("; ");
28858
+ }
28859
+ var PREFIX_PAIRS, upperFirst, lowerFirst, REFRESH_ACTION, LOAD_MORE_ACTION, FILTER_ACTION, VOLATILE_KEY;
28860
+ var init_crawl_relations = __esm({
28861
+ "../../core/dev-server/src/crawl-relations.ts"() {
28862
+ PREFIX_PAIRS = [
28863
+ ["add", "remove"],
28864
+ ["add", "delete"],
28865
+ // NOT ["start", "stop"]. Measured 2026-09-16: it produced 2 warnings on unmutated, correct code
28866
+ // (voice-journal, `start → stop` and `startBackground → stop`, both leaving
28867
+ // `statusLabel: "Ready" → "Ready — recording stopped"`). start/stop is a LIFECYCLE pair, not an
28868
+ // algebraic inverse: stopping does not unmake the recording that was started, and an app is right
28869
+ // to say so. Only pairs where the undo genuinely restores the prior value belong here.
28870
+ ["open", "close"],
28871
+ ["show", "hide"],
28872
+ ["expand", "collapse"],
28873
+ ["select", "deselect"],
28874
+ ["increment", "decrement"],
28875
+ ["follow", "unfollow"],
28876
+ ["save", "unsave"]
28877
+ ];
28878
+ upperFirst = (s) => s ? s[0].toUpperCase() + s.slice(1) : s;
28879
+ lowerFirst = (s) => s ? s[0].toLowerCase() + s.slice(1) : s;
28880
+ REFRESH_ACTION = /^(refresh|reload|retry|sync|pullToRefresh)/i;
28881
+ LOAD_MORE_ACTION = /^(loadMore|nextPage|fetchMore)|^more$|More$|NextPage$/;
28882
+ FILTER_ACTION = /^(filter|search|query|sort)|(Filter|Search|Query|Sort)/;
28883
+ VOLATILE_KEY = /^_|^(id|uuid|guid|nonce|seed|token|timestamp|now|revision|etag)$|(Id|ID|Uuid|Guid|Nonce|Seed|Token|At|Timestamp|Ms|Revision|ETag)$/;
28884
+ }
28885
+ });
28886
+
28473
28887
  // ../../core/dev-server/src/screen-crawl.ts
28474
28888
  var screen_crawl_exports = {};
28475
28889
  __export(screen_crawl_exports, {
@@ -28482,6 +28896,7 @@ __export(screen_crawl_exports, {
28482
28896
  crawlScreenDeep: () => crawlScreenDeep,
28483
28897
  crawlScreenHostile: () => crawlScreenHostile,
28484
28898
  crawlScreenJourney: () => crawlScreenJourney,
28899
+ crawlScreenRelations: () => crawlScreenRelations,
28485
28900
  crawlSignature: () => crawlSignature,
28486
28901
  deepStateSignature: () => deepStateSignature,
28487
28902
  emptiedArrays: () => emptiedArrays,
@@ -28492,6 +28907,7 @@ __export(screen_crawl_exports, {
28492
28907
  parseWorkerAction: () => parseWorkerAction,
28493
28908
  recordScreenTraffic: () => recordScreenTraffic,
28494
28909
  rootOnAppear: () => rootOnAppear,
28910
+ snapshot: () => snapshot,
28495
28911
  structuralSignature: () => structuralSignature,
28496
28912
  unverifiableNodes: () => unverifiableNodes,
28497
28913
  workerArgs: () => workerArgs
@@ -29727,7 +30143,209 @@ async function crawlScreenDeep(opts, deep) {
29727
30143
  return { findings: dedupe(findings), nodesVisited: visited.size, dispatches, capped };
29728
30144
  });
29729
30145
  }
29730
- var ACTION_KEYS, UNTRACKED_ITEMS, NESTED_ITEMS_SEP, REJECT_ALL_MESSAGE, VLQ, WIX_DATA_QUERY, WIX_DATA_ANY, CAPABILITY_ACTION, BLANK_WHEN_BOUND;
30146
+ function effectiveSiteForRow(site, row) {
30147
+ const merged = { ...site.params ?? {}, ...site.args ?? {} };
30148
+ const substituted = {};
30149
+ for (const [k, v] of Object.entries(merged)) {
30150
+ substituted[k] = typeof v === "string" && v.startsWith("$item.") ? pathValue(row, v.slice("$item.".length)) : v === "$item" ? row : v;
30151
+ }
30152
+ return { ...site, params: substituted, args: void 0 };
30153
+ }
30154
+ function isRowBound(site) {
30155
+ const payload = { ...site.params ?? {}, ...site.args ?? {} };
30156
+ return Object.values(payload).some((v) => typeof v === "string" && (v === "$item" || v.startsWith("$item.")));
30157
+ }
30158
+ function collectItemsPaths(node, out = /* @__PURE__ */ new Set()) {
30159
+ if (!node || typeof node !== "object") return out;
30160
+ if (Array.isArray(node)) {
30161
+ for (const n of node) collectItemsPaths(n, out);
30162
+ return out;
30163
+ }
30164
+ const rec = node;
30165
+ const body = rec.body;
30166
+ if (body && typeof body === "object" && typeof body.type === "string" && body.props && typeof body.props === "object") {
30167
+ const itemsPath = body.props.itemsPath;
30168
+ if (typeof itemsPath === "string") out.add(itemsPath);
30169
+ }
30170
+ for (const v of Object.values(rec)) collectItemsPaths(v, out);
30171
+ return out;
30172
+ }
30173
+ function newlyAddedRow(before, after, itemsPath) {
30174
+ if (!itemsPath || itemsPath === UNTRACKED_ITEMS) return null;
30175
+ const rowsOf = (s) => {
30176
+ const provider = new FakeServiceProvider(s);
30177
+ const v = resolveSiteRows(itemsPath, (p) => provider.value(p));
30178
+ return Array.isArray(v) ? v.filter((r) => r && typeof r === "object") : [];
30179
+ };
30180
+ const seen = new Set(rowsOf(before).map((r) => relationalSignature(r)));
30181
+ const fresh = rowsOf(after).filter((r) => !seen.has(relationalSignature(r)));
30182
+ return fresh.length === 1 ? fresh[0] : null;
30183
+ }
30184
+ async function crawlScreenRelations(opts) {
30185
+ return withSwallowedRejections(async () => {
30186
+ const { screenId, descriptor, bundleJs } = opts;
30187
+ const findings = [];
30188
+ const byKind = { inverse: 0, refresh: 0, loadMore: 0, filter: 0 };
30189
+ let checked = 0;
30190
+ if (!bundleJs) return { findings, checked, byKind };
30191
+ const layered = layeredTransport({ rules: opts.rules, cmsSeed: opts.cmsSeed, canned: opts.canned });
30192
+ const wixCall = opts.transportWrap ? opts.transportWrap(layered.call) : layered.call;
30193
+ const sites = actionSitesForDescriptor(descriptor).filter(
30194
+ (s) => parseWorkerAction(s.actionId) && !opts.skipAction?.(s.actionId, s.componentId)
30195
+ );
30196
+ const nameOf = (s) => parseWorkerAction(s.actionId).action;
30197
+ const serviceOf = (s) => parseWorkerAction(s.actionId).service;
30198
+ const boot = () => bootScreenWorker({
30199
+ bundleJs,
30200
+ descriptor,
30201
+ wixCall,
30202
+ screenId,
30203
+ inputOverride: opts.inputOverride,
30204
+ budgetMs: opts.budgetMs,
30205
+ clock: opts.clock,
30206
+ seed: opts.seed,
30207
+ localDbSchema: opts.localDbSchema,
30208
+ ...opts.evaluator ? { evaluator: opts.evaluator } : {}
30209
+ });
30210
+ const replay = async (path, rowFor = () => null) => {
30211
+ const w = await boot();
30212
+ try {
30213
+ if (w.errors.length) return null;
30214
+ let prev = snapshot(w).state;
30215
+ const bootState = prev;
30216
+ for (const [i, site] of path.entries()) {
30217
+ const parsed = parseWorkerAction(site.actionId);
30218
+ const h = w.handles[parsed.service];
30219
+ if (!h || !h.actionNames().includes(parsed.action)) return null;
30220
+ const pinned = rowFor(i, bootState, prev);
30221
+ if (pinned === UNBINDABLE_ROW) return null;
30222
+ const eff = pinned ? effectiveSiteForRow(site, pinned) : effectiveSiteFor(site, prev);
30223
+ if (!eff) return null;
30224
+ try {
30225
+ await h.dispatch(parsed.action, ...workerArgs(eff));
30226
+ } catch {
30227
+ return null;
30228
+ }
30229
+ for (let k = 0; k < 20; k++) await yieldMacrotask();
30230
+ if (w.errors.length) return null;
30231
+ prev = snapshot(w).state;
30232
+ }
30233
+ return prev;
30234
+ } finally {
30235
+ w.dispose();
30236
+ }
30237
+ };
30238
+ const base = await replay([]);
30239
+ if (!base || Object.values(base).some(hasOwnError)) return { findings, checked, byKind };
30240
+ const baseSig = relationalSignature(base);
30241
+ const baseLens = listLengths(base);
30242
+ const listPathOf = (itemsPath) => {
30243
+ if (!itemsPath || itemsPath === UNTRACKED_ITEMS || itemsPath.includes("[]")) return null;
30244
+ const m = /^\$service:(.+)$/.exec(itemsPath);
30245
+ return m && baseLens[m[1]] !== void 0 ? m[1] : null;
30246
+ };
30247
+ const screenListPaths = [
30248
+ ...new Set(
30249
+ [...collectItemsPaths(descriptor)].map(listPathOf).filter((x) => x !== null)
30250
+ )
30251
+ ];
30252
+ const trig = (s) => ({ tap: s.componentId, key: s.key, action: s.actionId });
30253
+ for (const s of sites.filter((s2) => REFRESH_ACTION.test(nameOf(s2)) || s2.key === "onPullToRefresh")) {
30254
+ const after = await replay([s]);
30255
+ if (!after) continue;
30256
+ checked++;
30257
+ byKind.refresh++;
30258
+ const sig = relationalSignature(after);
30259
+ if (sig !== baseSig) {
30260
+ findings.push({
30261
+ screen: screenId,
30262
+ trigger: trig(s),
30263
+ invariant: "refresh-not-idempotent",
30264
+ severity: "warning",
30265
+ axis: "relation",
30266
+ message: `${nameOf(s)} from a fresh boot settles on a different state than the boot itself, under identical data: ${summarizeDiff(baseSig, sig)}`,
30267
+ hint: "a refresh that appends instead of replacing, or clears a flag the loader set \u2014 REPLACE collections on refresh; ids, *At and _counters are already ignored by this comparison"
30268
+ });
30269
+ }
30270
+ }
30271
+ for (const s of sites.filter((s2) => LOAD_MORE_ACTION.test(nameOf(s2)) || s2.key === "onLoadMore")) {
30272
+ const target = listPathOf(s.itemsPath);
30273
+ const after = target ? await replay([s]) : null;
30274
+ if (!after) continue;
30275
+ checked++;
30276
+ byKind.loadMore++;
30277
+ for (const [p, n] of Object.entries(listLengths(after))) {
30278
+ if (p === target && baseLens[p] !== void 0 && n < baseLens[p]) {
30279
+ findings.push({
30280
+ screen: screenId,
30281
+ trigger: trig(s),
30282
+ invariant: "list-shrank-on-load-more",
30283
+ severity: "warning",
30284
+ axis: "relation",
30285
+ at: p,
30286
+ actual: `${baseLens[p]} \u2192 ${n}`,
30287
+ message: `${nameOf(s)} shrank ${p} from ${baseLens[p]} to ${n} rows \u2014 a "load more" that replaces the page instead of appending it`,
30288
+ hint: "append the new page to the existing rows (and de-duplicate by id); the user scrolled to see MORE"
30289
+ });
30290
+ }
30291
+ }
30292
+ }
30293
+ for (const s of sites.filter((s2) => FILTER_ACTION.test(nameOf(s2)) && !isOptionSelection({ ...s2.params, ...s2.args }))) {
30294
+ if (screenListPaths.length !== 1) continue;
30295
+ const after = await replay([s]);
30296
+ if (!after) continue;
30297
+ checked++;
30298
+ byKind.filter++;
30299
+ for (const [p, n] of Object.entries(listLengths(after))) {
30300
+ if (screenListPaths.includes(p) && baseLens[p] !== void 0 && n > baseLens[p]) {
30301
+ findings.push({
30302
+ screen: screenId,
30303
+ trigger: trig(s),
30304
+ invariant: "list-grew-on-filter",
30305
+ severity: "warning",
30306
+ axis: "relation",
30307
+ at: p,
30308
+ actual: `${baseLens[p]} \u2192 ${n}`,
30309
+ message: `${nameOf(s)} grew ${p} from ${baseLens[p]} to ${n} rows \u2014 a filter/search/sort can narrow or reorder a list, never enlarge it`,
30310
+ hint: "filter from the FULL set you keep aside, and assign the narrowed result \u2014 not a concatenation"
30311
+ });
30312
+ }
30313
+ }
30314
+ }
30315
+ const declaredNames = sites.map(nameOf);
30316
+ for (const a of sites) {
30317
+ const partner = inversePartner(nameOf(a), declaredNames);
30318
+ if (!partner) continue;
30319
+ if (isUndoAction(nameOf(a))) continue;
30320
+ const b = sites.find((s) => nameOf(s) === partner && serviceOf(s) === serviceOf(a) && s !== a);
30321
+ if (!b) continue;
30322
+ const undoIsRowBound = isRowBound(b);
30323
+ const after = await replay([a, b], (i, bootState, prev) => {
30324
+ if (i !== 1) return null;
30325
+ const row = newlyAddedRow(bootState, prev, b.itemsPath);
30326
+ if (row) return row;
30327
+ return undoIsRowBound ? UNBINDABLE_ROW : null;
30328
+ });
30329
+ if (!after) continue;
30330
+ checked++;
30331
+ byKind.inverse++;
30332
+ const sig = relationalSignature(after);
30333
+ if (sig !== baseSig) {
30334
+ findings.push({
30335
+ screen: screenId,
30336
+ trigger: trig(b),
30337
+ invariant: "inverse-pair-diverges",
30338
+ severity: "warning",
30339
+ axis: "relation",
30340
+ message: `${nameOf(a)} \u2192 ${nameOf(b)} does not return to the boot state: ${summarizeDiff(baseSig, sig)}`,
30341
+ hint: `${nameOf(b)} should undo exactly what ${nameOf(a)} did \u2014 check which row it removes (by the id it was given) and every field ${nameOf(a)} touched; ids, *At and _counters are already ignored`
30342
+ });
30343
+ }
30344
+ }
30345
+ return { findings: dedupe(findings), checked, byKind };
30346
+ });
30347
+ }
30348
+ var ACTION_KEYS, UNTRACKED_ITEMS, NESTED_ITEMS_SEP, REJECT_ALL_MESSAGE, VLQ, WIX_DATA_QUERY, WIX_DATA_ANY, CAPABILITY_ACTION, BLANK_WHEN_BOUND, UNBINDABLE_ROW;
29731
30349
  var init_screen_crawl = __esm({
29732
30350
  "../../core/dev-server/src/screen-crawl.ts"() {
29733
30351
  init_validate_descriptor_core();
@@ -29737,6 +30355,7 @@ var init_screen_crawl = __esm({
29737
30355
  init_FakeServiceProvider();
29738
30356
  init_actionParams();
29739
30357
  init_implications();
30358
+ init_crawl_relations();
29740
30359
  ACTION_KEYS = [
29741
30360
  "action",
29742
30361
  "onTap",
@@ -29760,6 +30379,7 @@ var init_screen_crawl = __esm({
29760
30379
  Marquee: ["items", "itemsPath", "text"],
29761
30380
  MediaPlayer: ["items", "itemsPath"]
29762
30381
  };
30382
+ UNBINDABLE_ROW = Symbol("unbindable-row");
29763
30383
  }
29764
30384
  });
29765
30385
 
@@ -30315,8 +30935,8 @@ __export(pack_exports, {
30315
30935
  });
30316
30936
  import { createHash as createHash19 } from "node:crypto";
30317
30937
  import { deflateRawSync, inflateRawSync } from "node:zlib";
30318
- import { existsSync as existsSync35, mkdirSync as mkdirSync17, readFileSync as readFileSync42, readdirSync as readdirSync19, rmSync as rmSync5, statSync as statSync7, writeFileSync as writeFileSync13 } from "node:fs";
30319
- import { dirname as dirname23, join as join34, resolve as resolve31 } from "node:path";
30938
+ import { existsSync as existsSync35, mkdirSync as mkdirSync17, readFileSync as readFileSync42, readdirSync as readdirSync19, rmSync as rmSync5, statSync as statSync8, writeFileSync as writeFileSync13 } from "node:fs";
30939
+ import { dirname as dirname23, join as join35, resolve as resolve31 } from "node:path";
30320
30940
  function writeZip(entries) {
30321
30941
  const sorted = entries.slice().sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
30322
30942
  const total = sorted.reduce((n, e) => n + e.bytes.length, 0);
@@ -30410,13 +31030,13 @@ function inflateEntry(buf, entry) {
30410
31030
  return bytes2;
30411
31031
  }
30412
31032
  function readIf(path) {
30413
- return existsSync35(path) && statSync7(path).isFile() ? readFileSync42(path) : null;
31033
+ return existsSync35(path) && statSync8(path).isFile() ? readFileSync42(path) : null;
30414
31034
  }
30415
31035
  function listDir(dir) {
30416
- return existsSync35(dir) ? readdirSync19(dir).filter((f) => statSync7(join34(dir, f)).isFile()) : [];
31036
+ return existsSync35(dir) ? readdirSync19(dir).filter((f) => statSync8(join35(dir, f)).isFile()) : [];
30417
31037
  }
30418
31038
  function gatherReleaseEntries(appDir2) {
30419
- const manifestPath = join34(appDir2, HIMI_PACKAGE_MANIFEST_PATH);
31039
+ const manifestPath = join35(appDir2, HIMI_PACKAGE_MANIFEST_PATH);
30420
31040
  const manifestBytes = readIf(manifestPath);
30421
31041
  if (!manifestBytes) throw new Error(`no ${HIMI_PACKAGE_MANIFEST_PATH} in ${appDir2} \u2014 build a release there first`);
30422
31042
  const manifest = JSON.parse(manifestBytes.toString("utf8"));
@@ -30440,7 +31060,7 @@ function gatherReleaseEntries(appDir2) {
30440
31060
  }
30441
31061
  const missing = [];
30442
31062
  for (const rel of wanted) {
30443
- const bytes2 = readIf(join34(appDir2, rel));
31063
+ const bytes2 = readIf(join35(appDir2, rel));
30444
31064
  if (bytes2) entries.push({ path: rel, bytes: bytes2 });
30445
31065
  else missing.push(rel);
30446
31066
  }
@@ -30451,12 +31071,12 @@ function gatherReleaseEntries(appDir2) {
30451
31071
  );
30452
31072
  }
30453
31073
  for (const name of SINGLETONS) {
30454
- const bytes2 = readIf(join34(appDir2, name));
31074
+ const bytes2 = readIf(join35(appDir2, name));
30455
31075
  if (bytes2) entries.push({ path: name, bytes: bytes2 });
30456
31076
  }
30457
31077
  const skipped = [];
30458
31078
  for (const sub of ["screens", "bundles", "assets", "widgets"]) {
30459
- for (const f of listDir(join34(appDir2, sub))) {
31079
+ for (const f of listDir(join35(appDir2, sub))) {
30460
31080
  if (!wanted.has(`${sub}/${f}`)) skipped.push(`${sub}/${f}`);
30461
31081
  }
30462
31082
  }
@@ -30487,18 +31107,18 @@ function buildEntries(appDir2, app, opts) {
30487
31107
  return { all, header, manifest, skipped };
30488
31108
  }
30489
31109
  function packRelease(releaseRoot, app, out, opts = {}) {
30490
- const appDir2 = join34(resolve31(releaseRoot), app);
31110
+ const appDir2 = join35(resolve31(releaseRoot), app);
30491
31111
  const { all, header, skipped } = buildEntries(appDir2, app, opts);
30492
31112
  const buf = writeZip(all);
30493
- const isDir = existsSync35(out) && statSync7(out).isDirectory();
30494
- const outFile = isDir ? join34(resolve31(out), packageFileName(app, header.releaseId)) : resolve31(out);
31113
+ const isDir = existsSync35(out) && statSync8(out).isDirectory();
31114
+ const outFile = isDir ? join35(resolve31(out), packageFileName(app, header.releaseId)) : resolve31(out);
30495
31115
  mkdirSync17(dirname23(outFile), { recursive: true });
30496
31116
  writeFileSync13(outFile, buf);
30497
31117
  return { app, releaseId: header.releaseId, header, out: outFile, bytes: buf.length, entryCount: all.length, skipped };
30498
31118
  }
30499
31119
  function resolvePackageDirDest(outDir, app, releaseId) {
30500
31120
  const packagedApp = (dir) => {
30501
- const bytes2 = readIf(join34(dir, HIMI_PACKAGE_HEADER_PATH));
31121
+ const bytes2 = readIf(join35(dir, HIMI_PACKAGE_HEADER_PATH));
30502
31122
  if (!bytes2) return void 0;
30503
31123
  try {
30504
31124
  return JSON.parse(bytes2.toString("utf8")).app;
@@ -30508,14 +31128,14 @@ function resolvePackageDirDest(outDir, app, releaseId) {
30508
31128
  };
30509
31129
  const replaceable = (dir) => {
30510
31130
  if (!existsSync35(dir)) return true;
30511
- if (!statSync7(dir).isDirectory()) return false;
31131
+ if (!statSync8(dir).isDirectory()) return false;
30512
31132
  return readdirSync19(dir).length === 0 || packagedApp(dir) === app;
30513
31133
  };
30514
- if (existsSync35(outDir) && !statSync7(outDir).isDirectory()) {
31134
+ if (existsSync35(outDir) && !statSync8(outDir).isDirectory()) {
30515
31135
  throw new Error(`refusing to write a package tree over ${outDir} \u2014 it is a file, not a directory`);
30516
31136
  }
30517
31137
  if (replaceable(outDir)) return outDir;
30518
- const child = join34(outDir, packageDirName(app, releaseId));
31138
+ const child = join35(outDir, packageDirName(app, releaseId));
30519
31139
  if (!replaceable(child)) {
30520
31140
  throw new Error(
30521
31141
  `refusing to replace ${child}: it is not empty and is not a ${app} package (no ${HIMI_PACKAGE_HEADER_PATH}). Pass --out to a new or empty directory.`
@@ -30524,13 +31144,13 @@ function resolvePackageDirDest(outDir, app, releaseId) {
30524
31144
  return child;
30525
31145
  }
30526
31146
  function packReleaseToDir(releaseRoot, app, outDir, opts = {}) {
30527
- const appDir2 = join34(resolve31(releaseRoot), app);
31147
+ const appDir2 = join35(resolve31(releaseRoot), app);
30528
31148
  const { all, header, skipped } = buildEntries(appDir2, app, opts);
30529
31149
  const dest = resolvePackageDirDest(resolve31(outDir), app, header.releaseId);
30530
31150
  rmSync5(dest, { recursive: true, force: true });
30531
31151
  let bytes2 = 0;
30532
31152
  for (const entry of all) {
30533
- const target = join34(dest, entry.path);
31153
+ const target = join35(dest, entry.path);
30534
31154
  mkdirSync17(dirname23(target), { recursive: true });
30535
31155
  writeFileSync13(target, entry.bytes);
30536
31156
  bytes2 += entry.bytes.length;
@@ -30559,11 +31179,11 @@ function openPackageDir(dir) {
30559
31179
  const root = resolve31(dir);
30560
31180
  const entries = /* @__PURE__ */ new Map();
30561
31181
  const walk2 = (rel) => {
30562
- const abs = rel ? join34(root, rel) : root;
31182
+ const abs = rel ? join35(root, rel) : root;
30563
31183
  for (const name of readdirSync19(abs)) {
30564
31184
  const childRel = rel ? `${rel}/${name}` : name;
30565
- const childAbs = join34(abs, name);
30566
- if (statSync7(childAbs).isDirectory()) walk2(childRel);
31185
+ const childAbs = join35(abs, name);
31186
+ if (statSync8(childAbs).isDirectory()) walk2(childRel);
30567
31187
  else if (isSafeEntryName(childRel)) entries.set(childRel, readFileSync42(childAbs));
30568
31188
  }
30569
31189
  };
@@ -30601,10 +31221,10 @@ function unpackTo(buf, dest, appFallback) {
30601
31221
  if (!app) throw new Error("package declares no app id (no himi-package.json and no manifest.app)");
30602
31222
  if (!isSafeAppId(app)) throw new Error(`package declares an unsafe app id ${JSON.stringify(app)} \u2014 refusing to unpack it`);
30603
31223
  const root = resolve31(dest);
30604
- const appDir2 = join34(root, app);
31224
+ const appDir2 = join35(root, app);
30605
31225
  rmSync5(appDir2, { recursive: true, force: true });
30606
31226
  for (const [rel, bytes2] of entries) {
30607
- const target = join34(appDir2, rel);
31227
+ const target = join35(appDir2, rel);
30608
31228
  mkdirSync17(dirname23(target), { recursive: true });
30609
31229
  writeFileSync13(target, bytes2);
30610
31230
  }
@@ -30629,38 +31249,49 @@ var app_source_exports = {};
30629
31249
  __export(app_source_exports, {
30630
31250
  MAX_APP_SOURCE_BYTES: () => MAX_APP_SOURCE_BYTES,
30631
31251
  collectAppSource: () => collectAppSource,
31252
+ deniedArchivePaths: () => deniedArchivePaths,
30632
31253
  isDeniedSourcePath: () => isDeniedSourcePath,
30633
31254
  readAppSource: () => readAppSource,
31255
+ removeStaleSource: () => removeStaleSource,
30634
31256
  secretFindings: () => secretFindings,
31257
+ staleSourcePaths: () => staleSourcePaths,
30635
31258
  writeAppSource: () => writeAppSource,
31259
+ writeConflicts: () => writeConflicts,
30636
31260
  zipAppSource: () => zipAppSource
30637
31261
  });
30638
- import { readdirSync as readdirSync20, lstatSync as lstatSync4, readFileSync as readFileSync43, mkdirSync as mkdirSync18, writeFileSync as writeFileSync14 } from "node:fs";
30639
- import { join as join35, dirname as dirname24, sep as sep6 } from "node:path";
30640
- function collectAppSource(dir) {
30641
- const entries = [];
30642
- const skipped = [];
31262
+ import { readdirSync as readdirSync20, lstatSync as lstatSync4, readFileSync as readFileSync43, mkdirSync as mkdirSync18, writeFileSync as writeFileSync14, existsSync as existsSync36, rmSync as rmSync6, rmdirSync, unlinkSync as unlinkSync3 } from "node:fs";
31263
+ import { join as join36, resolve as resolve32, sep as sep7 } from "node:path";
31264
+ function walkSourceTree(dir, file, skip) {
30643
31265
  const walk2 = (relDir) => {
30644
- for (const name of readdirSync20(join35(dir, relDir))) {
30645
- const rel = relDir ? join35(relDir, name) : name;
30646
- const st = lstatSync4(join35(dir, rel));
31266
+ for (const name of readdirSync20(join36(dir, relDir))) {
31267
+ const rel = relDir ? join36(relDir, name) : name;
31268
+ const st = lstatSync4(join36(dir, rel));
30647
31269
  if (st.isSymbolicLink()) {
30648
- skipped.push(rel);
31270
+ skip(rel);
30649
31271
  continue;
30650
31272
  }
30651
31273
  if (st.isDirectory()) {
30652
- if (isDeniedSourceDir(name)) skipped.push(rel);
31274
+ if (isDeniedSourceDir(name)) skip(rel);
30653
31275
  else walk2(rel);
30654
31276
  continue;
30655
31277
  }
30656
31278
  if (isDeniedSourcePath(rel)) {
30657
- skipped.push(rel);
31279
+ skip(rel);
30658
31280
  continue;
30659
31281
  }
30660
- entries.push({ path: rel.split(sep6).join("/"), bytes: readFileSync43(join35(dir, rel)) });
31282
+ file(rel.split(sep7).join("/"), rel);
30661
31283
  }
30662
31284
  };
30663
31285
  walk2("");
31286
+ }
31287
+ function collectAppSource(dir) {
31288
+ const entries = [];
31289
+ const skipped = [];
31290
+ walkSourceTree(
31291
+ dir,
31292
+ (archivePath, hostRel) => entries.push({ path: archivePath, bytes: readFileSync43(join36(dir, hostRel)) }),
31293
+ (hostRel) => skipped.push(hostRel)
31294
+ );
30664
31295
  entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
30665
31296
  skipped.sort();
30666
31297
  return { entries, skipped };
@@ -30680,16 +31311,115 @@ function zipAppSource(entries) {
30680
31311
  function readAppSource(zip) {
30681
31312
  return [...readZip(zip)].map(([path, bytes2]) => ({ path, bytes: bytes2 }));
30682
31313
  }
31314
+ function pathKind(p) {
31315
+ try {
31316
+ return lstatSync4(p);
31317
+ } catch {
31318
+ return null;
31319
+ }
31320
+ }
30683
31321
  function writeAppSource(entries, destDir) {
31322
+ const root = resolve32(destDir);
31323
+ mkdirSync18(root, { recursive: true });
30684
31324
  const written = [];
30685
31325
  for (const entry of entries) {
30686
- const target = join35(destDir, ...entry.path.split("/"));
30687
- mkdirSync18(dirname24(target), { recursive: true });
31326
+ if (isDeniedSourcePath(entry.path)) continue;
31327
+ const segments = entry.path.split("/");
31328
+ let dir = root;
31329
+ for (const segment of segments.slice(0, -1)) {
31330
+ dir = join36(dir, segment);
31331
+ const kind = pathKind(dir);
31332
+ if (kind?.isSymbolicLink()) unlinkSync3(dir);
31333
+ else if (kind) continue;
31334
+ mkdirSync18(dir);
31335
+ }
31336
+ const target = join36(root, ...segments);
31337
+ if (pathKind(target)?.isSymbolicLink()) unlinkSync3(target);
30688
31338
  writeFileSync14(target, entry.bytes);
30689
31339
  written.push(entry.path);
30690
31340
  }
30691
31341
  return written;
30692
31342
  }
31343
+ function staleSourcePaths(entries, destDir) {
31344
+ if (!existsSync36(destDir)) return [];
31345
+ const archived = new Set(entries.map((e) => e.path));
31346
+ const stale = [];
31347
+ walkSourceTree(destDir, (archivePath) => {
31348
+ if (!archived.has(archivePath)) stale.push(archivePath);
31349
+ }, () => {
31350
+ });
31351
+ return stale.sort();
31352
+ }
31353
+ function deniedArchivePaths(entries) {
31354
+ return entries.filter((e) => isDeniedSourcePath(e.path)).map((e) => e.path).sort();
31355
+ }
31356
+ function writeConflicts(entries, destDir) {
31357
+ const root = resolve32(destDir);
31358
+ const conflicts = [];
31359
+ const seen = /* @__PURE__ */ new Set();
31360
+ const say = (rel, what) => {
31361
+ if (seen.has(rel)) return;
31362
+ seen.add(rel);
31363
+ conflicts.push(`${rel} ${what}`);
31364
+ };
31365
+ for (const entry of entries) {
31366
+ if (isDeniedSourcePath(entry.path)) continue;
31367
+ const segments = entry.path.split("/");
31368
+ let dir = root;
31369
+ let stop = false;
31370
+ for (const [i, segment] of segments.slice(0, -1).entries()) {
31371
+ dir = join36(dir, segment);
31372
+ const kind2 = pathKind(dir);
31373
+ if (!kind2 || kind2.isSymbolicLink()) {
31374
+ stop = true;
31375
+ break;
31376
+ }
31377
+ if (kind2.isDirectory()) continue;
31378
+ say(segments.slice(0, i + 1).join("/"), "is a file here, but the release has a directory at that path");
31379
+ stop = true;
31380
+ break;
31381
+ }
31382
+ if (stop) continue;
31383
+ const kind = pathKind(join36(root, ...segments));
31384
+ if (kind && !kind.isSymbolicLink() && kind.isDirectory()) {
31385
+ say(entry.path, "is a directory here, but the release has a file at that path");
31386
+ }
31387
+ }
31388
+ return conflicts;
31389
+ }
31390
+ function removeStaleSource(entries, destDir) {
31391
+ const root = resolve32(destDir);
31392
+ const removed = staleSourcePaths(entries, root);
31393
+ for (const rel of removed) rmSync6(join36(root, ...rel.split("/")), { force: true });
31394
+ const needed = /* @__PURE__ */ new Set();
31395
+ for (const entry of entries) {
31396
+ if (isDeniedSourcePath(entry.path)) continue;
31397
+ const segments = entry.path.split("/");
31398
+ for (let i = 1; i < segments.length; i++) needed.add(segments.slice(0, i).join("/"));
31399
+ }
31400
+ if (existsSync36(root)) pruneEmptyDirs(root, "", needed, removed);
31401
+ return removed.sort();
31402
+ }
31403
+ function pruneEmptyDirs(root, rel, needed, removed) {
31404
+ let empty = true;
31405
+ for (const name of readdirSync20(rel ? join36(root, rel) : root)) {
31406
+ const childRel = rel ? join36(rel, name) : name;
31407
+ const posix3 = childRel.split(sep7).join("/");
31408
+ const kind = lstatSync4(join36(root, childRel));
31409
+ if (kind.isSymbolicLink() || !kind.isDirectory() || isDeniedSourceDir(name)) {
31410
+ empty = false;
31411
+ continue;
31412
+ }
31413
+ const childEmpty = pruneEmptyDirs(root, childRel, needed, removed);
31414
+ if (!childEmpty || needed.has(posix3)) {
31415
+ empty = false;
31416
+ continue;
31417
+ }
31418
+ rmdirSync(join36(root, childRel));
31419
+ removed.push(`${posix3}/`);
31420
+ }
31421
+ return empty;
31422
+ }
30693
31423
  var MAX_APP_SOURCE_BYTES, SECRET_PATTERNS;
30694
31424
  var init_app_source = __esm({
30695
31425
  "src/app-source.ts"() {
@@ -30713,13 +31443,13 @@ __export(eject_exports, {
30713
31443
  EJECTABLE: () => EJECTABLE,
30714
31444
  ejectModule: () => ejectModule
30715
31445
  });
30716
- import { existsSync as existsSync36, mkdirSync as mkdirSync19, readFileSync as readFileSync44, writeFileSync as writeFileSync15 } from "node:fs";
30717
- import { dirname as dirname25, join as join36, relative as relative4, resolve as resolve32, sep as sep7 } from "node:path";
31446
+ import { existsSync as existsSync37, mkdirSync as mkdirSync19, readFileSync as readFileSync44, writeFileSync as writeFileSync15 } from "node:fs";
31447
+ import { dirname as dirname24, join as join37, relative as relative4, resolve as resolve33, sep as sep8 } from "node:path";
30718
31448
  function locate(repoRootOrCwd, mod) {
30719
- const inRepo = resolve32(repoRootOrCwd, `stdlib/flows/${mod}/src/index.ts`);
30720
- if (existsSync36(inRepo)) return { path: inRepo, form: "source" };
30721
- const dist = resolve32(repoRootOrCwd, `node_modules/@wix/himalaya/dist/${mod}.mjs`);
30722
- if (existsSync36(dist)) return { path: dist, form: "bundle" };
31449
+ const inRepo = resolve33(repoRootOrCwd, `stdlib/flows/${mod}/src/index.ts`);
31450
+ if (existsSync37(inRepo)) return { path: inRepo, form: "source" };
31451
+ const dist = resolve33(repoRootOrCwd, `node_modules/@wix/himalaya/dist/${mod}.mjs`);
31452
+ if (existsSync37(dist)) return { path: dist, form: "bundle" };
30723
31453
  return null;
30724
31454
  }
30725
31455
  function specifiersFor(mod) {
@@ -30743,8 +31473,8 @@ function ejectModule(opts) {
30743
31473
  };
30744
31474
  }
30745
31475
  const ext = found.form === "source" ? "ts" : "mjs";
30746
- const target = join36(contentDir2, "tier5-src", "_ejected", `${mod}.${ext}`);
30747
- mkdirSync19(dirname25(target), { recursive: true });
31476
+ const target = join37(contentDir2, "tier5-src", "_ejected", `${mod}.${ext}`);
31477
+ mkdirSync19(dirname24(target), { recursive: true });
30748
31478
  const banner = `// EJECTED from @wix/himalaya/${mod}. This app owns this file now.
30749
31479
  //
30750
31480
  // Central fixes to @wix/himalaya/${mod} NO LONGER REACH THIS APP \u2014 including any
@@ -30764,10 +31494,10 @@ function ejectModule(opts) {
30764
31494
  writeFileSync15(target, banner + normalized);
30765
31495
  const rewired = [];
30766
31496
  for (const f of files) {
30767
- if (!existsSync36(f)) continue;
31497
+ if (!existsSync37(f)) continue;
30768
31498
  const before = readFileSync44(f, "utf8");
30769
31499
  let after = before;
30770
- let rel = relative4(dirname25(f), target).split(sep7).join("/").replace(/\.tsx?$/, ".js");
31500
+ let rel = relative4(dirname24(f), target).split(sep8).join("/").replace(/\.tsx?$/, ".js");
30771
31501
  if (!rel.startsWith(".")) rel = `./${rel}`;
30772
31502
  for (const spec of specifiersFor(mod)) {
30773
31503
  const q = spec.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -30813,11 +31543,11 @@ __export(test_exports, {
30813
31543
  scopeDescriptors: () => scopeDescriptors,
30814
31544
  writeRecordedMocks: () => writeRecordedMocks
30815
31545
  });
30816
- import { existsSync as existsSync37, mkdirSync as mkdirSync20, readFileSync as readFileSync45, writeFileSync as writeFileSync16 } from "node:fs";
30817
- import { join as join37, resolve as resolve33 } from "node:path";
31546
+ import { existsSync as existsSync38, mkdirSync as mkdirSync20, readFileSync as readFileSync45, writeFileSync as writeFileSync16 } from "node:fs";
31547
+ import { join as join38, resolve as resolve34 } from "node:path";
30818
31548
  function readTestConfig(dir) {
30819
- const p = resolve33(dir, TEST_CONFIG_FILE);
30820
- if (!existsSync37(p)) return {};
31549
+ const p = resolve34(dir, TEST_CONFIG_FILE);
31550
+ if (!existsSync38(p)) return {};
30821
31551
  let parsed;
30822
31552
  try {
30823
31553
  parsed = JSON.parse(readFileSync45(p, "utf8"));
@@ -30833,8 +31563,8 @@ function readTestConfig(dir) {
30833
31563
  if (typeof s.reason !== "string" || !s.reason.trim()) {
30834
31564
  throw new Error(`${TEST_CONFIG_FILE}: skip[${i}] needs a non-empty "reason" \u2014 suppressions have to stay auditable`);
30835
31565
  }
30836
- if (!s.screen && !s.action && !s.component) {
30837
- throw new Error(`${TEST_CONFIG_FILE}: skip[${i}] matches everything \u2014 give it a "screen", "action", or "component"`);
31566
+ if (!s.screen && !s.action && !s.component && !s.invariant) {
31567
+ throw new Error(`${TEST_CONFIG_FILE}: skip[${i}] matches everything \u2014 give it a "screen", "action", "component", or "invariant"`);
30838
31568
  }
30839
31569
  });
30840
31570
  }
@@ -30883,8 +31613,8 @@ function readNetRules(path) {
30883
31613
  }
30884
31614
  function defaultNetRules(dir) {
30885
31615
  for (const rel of ["dev/net-mocks.json", "tests/net-mocks.json"]) {
30886
- const p = resolve33(dir, rel);
30887
- if (existsSync37(p)) {
31616
+ const p = resolve34(dir, rel);
31617
+ if (existsSync38(p)) {
30888
31618
  const rules = readNetRules(p);
30889
31619
  if (rules.length) return { rules, from: rel };
30890
31620
  }
@@ -30992,8 +31722,8 @@ function writeRecordedMocks(opts) {
30992
31722
  mkdir: (p) => mkdirSync20(p, { recursive: true }),
30993
31723
  write: (p, s) => writeFileSync16(p, s)
30994
31724
  };
30995
- fs.mkdir(join37(opts.dir, "dev"));
30996
- fs.write(join37(opts.dir, "dev", "net-mocks.json"), JSON.stringify(opts.mocks, null, 2) + "\n");
31725
+ fs.mkdir(join38(opts.dir, "dev"));
31726
+ fs.write(join38(opts.dir, "dev", "net-mocks.json"), JSON.stringify(opts.mocks, null, 2) + "\n");
30997
31727
  return { ok: true, rules, to: "dev/net-mocks.json" };
30998
31728
  }
30999
31729
  function scopeDescriptors(descriptors, requestedIds, invalidScreens) {
@@ -31056,15 +31786,18 @@ async function runCrawl(opts) {
31056
31786
  if (list.length > 1) collapsed.push({ representative: rep.screenId, instances: list.length });
31057
31787
  }
31058
31788
  }
31059
- const net = opts.offline ? null : opts.netPath ? { rules: readNetRules(resolve33(opts.netPath)), from: opts.netPath } : defaultNetRules(opts.dir);
31789
+ const net = opts.offline ? null : opts.netPath ? { rules: readNetRules(resolve34(opts.netPath)), from: opts.netPath } : defaultNetRules(opts.dir);
31060
31790
  const suppressions = opts.config?.skip ?? [];
31061
31791
  const suppressed = [];
31062
- const matchSuppression = (screen, actionId, componentId) => suppressions.find((s) => (s.screen === void 0 || s.screen === screen) && (s.action === void 0 || s.action === actionId) && (s.component === void 0 || s.component === componentId));
31792
+ const matchSuppression = (screen, actionId, componentId, invariant) => suppressions.find((s) => (s.screen === void 0 || s.screen === screen) && (s.action === void 0 || s.action === actionId) && (s.component === void 0 || s.component === componentId) && (s.invariant === void 0 || s.invariant === invariant));
31063
31793
  const results = [];
31064
31794
  const uncoveredByScreen = /* @__PURE__ */ new Map();
31065
31795
  const coverageFns = /* @__PURE__ */ new Map();
31066
31796
  const bundleOf = new Map(descriptors.map((d) => [d.screenId, d]));
31067
31797
  const hostileFindings = [];
31798
+ const relationFindings = [];
31799
+ let relationsChecked = 0;
31800
+ const relationsByKind = { inverse: 0, refresh: 0, loadMore: 0, filter: 0 };
31068
31801
  const deepFindings = [];
31069
31802
  const journeyFindings = [];
31070
31803
  const journeyResults = [];
@@ -31134,14 +31867,22 @@ async function runCrawl(opts) {
31134
31867
  hostileFindings.push(...hs.filter((f) => !already.has(keyOf(f))));
31135
31868
  }
31136
31869
  }
31870
+ if (deps.crawlScreenRelations && baked.bundleJs) {
31871
+ const rr = await deps.crawlScreenRelations(args);
31872
+ relationsChecked += rr.checked;
31873
+ for (const [k, n] of Object.entries(rr.byKind ?? {})) relationsByKind[k] = (relationsByKind[k] ?? 0) + n;
31874
+ const keyOf = deps.findingKey ?? ((f) => `${f.invariant}|${f.at ?? ""}|${f.screen}|${f.message}`);
31875
+ const already = new Set(r.findings.map(keyOf));
31876
+ for (const f of rr.findings) if (!already.has(keyOf(f))) relationFindings.push(f);
31877
+ }
31137
31878
  await finishScreenCoverage();
31138
31879
  }
31139
31880
  const keep = (f) => {
31140
31881
  const actionId = typeof f.trigger === "object" ? f.trigger.action : "boot";
31141
31882
  const componentId = typeof f.trigger === "object" ? f.trigger.tap : null;
31142
- const s = matchSuppression(f.screen, actionId, componentId);
31143
- if (s && !suppressed.some((x) => x.screen === f.screen && x.action === actionId && x.component === (componentId ?? void 0))) {
31144
- suppressed.push({ screen: f.screen, action: actionId, ...componentId ? { component: componentId } : {}, reason: s.reason });
31883
+ const s = matchSuppression(f.screen, actionId, componentId, f.invariant);
31884
+ if (s && !suppressed.some((x) => x.screen === f.screen && x.action === actionId && x.component === (componentId ?? void 0) && x.invariant === s.invariant)) {
31885
+ suppressed.push({ screen: f.screen, action: actionId, ...componentId ? { component: componentId } : {}, ...s.invariant ? { invariant: s.invariant } : {}, reason: s.reason });
31145
31886
  }
31146
31887
  return !s;
31147
31888
  };
@@ -31222,7 +31963,7 @@ async function runCrawl(opts) {
31222
31963
  if (uncovered.length) uncoveredByScreen.set(screen, uncovered);
31223
31964
  }
31224
31965
  }
31225
- const all = [...results.flatMap((r) => r.findings), ...hostileFindings, ...deepFindings, ...journeyFindings, ...edgeFindings].filter((f) => {
31966
+ const all = [...results.flatMap((r) => r.findings), ...hostileFindings, ...deepFindings, ...journeyFindings, ...edgeFindings, ...relationFindings].filter((f) => {
31226
31967
  if (f.invariant === "no-request-issued" && !f.axis && fetchProven.has(f.screen)) {
31227
31968
  const by = fetchProven.get(f.screen);
31228
31969
  if (!retired.some((x) => x.screen === f.screen)) retired.push({ screen: f.screen, from: by.from, params: by.params });
@@ -31242,7 +31983,15 @@ async function runCrawl(opts) {
31242
31983
  ...blockedReasons.length ? { blockedReasons } : {},
31243
31984
  app: opts.app,
31244
31985
  releaseId: opts.releaseId,
31245
- transport: describeTransport(net, opts),
31986
+ // Whether the axis RAN, not whether it was injected. The engine is gated on `baked.bundleJs`,
31987
+ // so a run over static or bundle-less screens injects the dependency and replays nothing —
31988
+ // and the label then advertised "+ relations" beside `relations.checked: 0`. This is the
31989
+ // second time this line has overstated itself: it first claimed the axis unconditionally.
31990
+ transport: describeTransport(net, opts, {
31991
+ relations: relationsChecked > 0,
31992
+ hostile: opts.hostile !== false && Boolean(deps.crawlScreenHostile),
31993
+ deep: Boolean(opts.deep && opts.deep >= 2 && deps.crawlScreenDeep)
31994
+ }),
31246
31995
  screens: { total: targets.length, clean: targets.length - failedScreens.size, failed: failedScreens.size },
31247
31996
  actions: { exercised: results.reduce((n, r) => n + r.actionsExercised, 0) },
31248
31997
  counts: { errors: failures.length, warnings: warnings.length },
@@ -31265,22 +32014,22 @@ async function runCrawl(opts) {
31265
32014
  ...edgesCapped.length ? { edgesCapped } : {},
31266
32015
  ...retired.length ? { retired } : {},
31267
32016
  ...opts.deep && opts.deep >= 2 ? { deep: { depth: opts.deep, ...deepTotals } } : {},
32017
+ ...deps.crawlScreenRelations ? { relations: { checked: relationsChecked, byKind: relationsByKind } } : {},
31268
32018
  ...journeyResults.length ? { journeys: journeyResults } : {},
31269
32019
  ...results.some((r) => r.observations) ? { observations: Object.fromEntries(results.filter((r) => r.observations).map((r) => [r.screen, r.observations])) } : {}
31270
32020
  };
31271
32021
  }
31272
- function describeTransport(net, opts) {
32022
+ function describeTransport(net, opts, ran) {
31273
32023
  const layers = [];
31274
32024
  if (net) layers.push(`mocked (${net.from})`);
31275
32025
  const cmsCount = Object.keys(opts.cmsSeed ?? {}).length;
31276
32026
  if (cmsCount) layers.push(`cms (${cmsCount} seeded collection${cmsCount === 1 ? "" : "s"})`);
31277
32027
  if (opts.canned?.count) layers.push(`canned (${opts.canned.count} frozen endpoints, ${opts.canned.from} \u2014 exercises code paths, not API-contract truth)`);
31278
- if (!layers.length) {
31279
- return "offline (no net fixture \u2014 screens are crawled against a rejecting transport, which exercises their failure UI)";
31280
- }
31281
- const hostile = opts.hostile !== false ? " + hostile boot passes (empty-lists, all-500)" : "";
31282
- const deep = opts.deep && opts.deep >= 2 ? ` + deep chains (depth ${opts.deep})` : "";
31283
- return layers.join(" + ") + hostile + deep;
32028
+ const base = layers.length ? layers.join(" + ") : "offline (no net fixture \u2014 screens are crawled against a rejecting transport, which exercises their failure UI)";
32029
+ const hostile = ran.hostile ? " + hostile boot passes (empty-lists, all-500)" : "";
32030
+ const relations = ran.relations ? " + relations" : "";
32031
+ const deep = ran.deep ? ` + deep chains (depth ${opts.deep})` : "";
32032
+ return base + hostile + relations + deep;
31284
32033
  }
31285
32034
  function formatReport(r) {
31286
32035
  const out = [];
@@ -31414,11 +32163,11 @@ var mobile_ux_lint_exports = {};
31414
32163
  __export(mobile_ux_lint_exports, {
31415
32164
  mobileUxIssues: () => mobileUxIssues
31416
32165
  });
31417
- import { existsSync as existsSync38, readFileSync as readFileSync46 } from "node:fs";
31418
- import { join as join38 } from "node:path";
32166
+ import { existsSync as existsSync39, readFileSync as readFileSync46 } from "node:fs";
32167
+ import { join as join39 } from "node:path";
31419
32168
  function mobileUxIssues(dir, strict) {
31420
- const file = join38(dir, "MOBILE-UX.md");
31421
- if (!existsSync38(file)) return [];
32169
+ const file = join39(dir, "MOBILE-UX.md");
32170
+ if (!existsSync39(file)) return [];
31422
32171
  let text2;
31423
32172
  try {
31424
32173
  text2 = readFileSync46(file, "utf8");
@@ -31700,7 +32449,7 @@ async function pinnedFetch(url, init, addresses, maxResponseBytes) {
31700
32449
  const headers2 = Object.fromEntries(new Headers(init.headers).entries());
31701
32450
  const request = url.protocol === "https:" ? httpsRequest : httpRequest;
31702
32451
  const hostname = url.hostname.replace(/^\[|\]$/g, "");
31703
- return await new Promise((resolve40, reject) => {
32452
+ return await new Promise((resolve41, reject) => {
31704
32453
  let settled = false;
31705
32454
  const finish2 = (fn) => {
31706
32455
  if (settled) return;
@@ -31737,7 +32486,7 @@ async function pinnedFetch(url, init, addresses, maxResponseBytes) {
31737
32486
  }
31738
32487
  const responseHeaders = new Headers();
31739
32488
  for (const [key2, value] of Object.entries(response.headers)) if (value !== void 0) responseHeaders.set(key2, Array.isArray(value) ? value.join(", ") : value);
31740
- resolve40(new Response(bytes2, { status: response.statusCode, statusText: response.statusMessage, headers: responseHeaders }));
32489
+ resolve41(new Response(bytes2, { status: response.statusCode, statusText: response.statusMessage, headers: responseHeaders }));
31741
32490
  }));
31742
32491
  });
31743
32492
  req.once("error", (error) => finish2(() => reject(error)));
@@ -32037,7 +32786,7 @@ function createLocalTestSandbox() {
32037
32786
  }
32038
32787
  })();
32039
32788
  `;
32040
- return await new Promise((resolve40, reject) => {
32789
+ return await new Promise((resolve41, reject) => {
32041
32790
  const { port1, port2 } = new MessageChannel();
32042
32791
  const serializableContext = {
32043
32792
  auth: context.auth,
@@ -32113,7 +32862,7 @@ function createLocalTestSandbox() {
32113
32862
  timer = setTimeout(() => finish2(() => reject(new SandboxError("timeout", `execution exceeded ${limits.timeoutMs}ms`))), Math.max(1, limits.timeoutMs));
32114
32863
  return;
32115
32864
  }
32116
- if (message.ok) finish2(() => resolve40({ value: message.value, durationMs: Date.now() - started }));
32865
+ if (message.ok) finish2(() => resolve41({ value: message.value, durationMs: Date.now() - started }));
32117
32866
  else finish2(() => reject(new SandboxError("execution_failed", message.message ?? "handler failed")));
32118
32867
  });
32119
32868
  worker.once("error", (error) => finish2(() => reject(new SandboxError("execution_failed", error.message))));
@@ -33080,8 +33829,8 @@ __export(functions_exports, {
33080
33829
  resolveFunctionsTarget: () => resolveFunctionsTarget,
33081
33830
  runRemoteFunctionOperation: () => runRemoteFunctionOperation
33082
33831
  });
33083
- import { readFileSync as readFileSync47, existsSync as existsSync39 } from "node:fs";
33084
- import { join as join39, resolve as resolve34 } from "node:path";
33832
+ import { readFileSync as readFileSync47, existsSync as existsSync40 } from "node:fs";
33833
+ import { join as join40, resolve as resolve35 } from "node:path";
33085
33834
  function resolveFunctionsTarget(input) {
33086
33835
  const explicitUrl = input.functionsUrl?.trim();
33087
33836
  if (explicitUrl) return { mode: "remote", baseUrl: explicitUrl };
@@ -33093,14 +33842,14 @@ function resolveFunctionsTarget(input) {
33093
33842
  return { mode: "local" };
33094
33843
  }
33095
33844
  async function loadFunctionsApp(dir) {
33096
- const loaded = await loadContent(resolve34(dir));
33845
+ const loaded = await loadContent(resolve35(dir));
33097
33846
  const config = loaded.module.config;
33098
33847
  if (config.kind !== "functions" || !Array.isArray(config.functions?.functions)) {
33099
33848
  throw new Error(`content package "${dir}" is not a functions-only app`);
33100
33849
  }
33101
33850
  const sources = {};
33102
33851
  for (const definition of config.functions.functions) {
33103
- const source = [".ts", ".js", ".mts", ".mjs"].map((extension) => join39(loaded.dir, "functions", `${definition.name}${extension}`)).find(existsSync39);
33852
+ const source = [".ts", ".js", ".mts", ".mjs"].map((extension) => join40(loaded.dir, "functions", `${definition.name}${extension}`)).find(existsSync40);
33104
33853
  if (!source) throw new Error(`missing source for function ${definition.name}; expected functions/${definition.name}.ts`);
33105
33854
  sources[definition.name] = readFileSync47(source, "utf8");
33106
33855
  }
@@ -33256,9 +34005,9 @@ __export(native_decode_exports, {
33256
34005
  runNativeDecode: () => runNativeDecode
33257
34006
  });
33258
34007
  import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
33259
- import { existsSync as existsSync40, mkdtempSync as mkdtempSync2, readFileSync as readFileSync48, rmSync as rmSync6, writeFileSync as writeFileSync17 } from "node:fs";
34008
+ import { existsSync as existsSync41, mkdtempSync as mkdtempSync2, readFileSync as readFileSync48, rmSync as rmSync7, writeFileSync as writeFileSync17 } from "node:fs";
33260
34009
  import { tmpdir as tmpdir3 } from "node:os";
33261
- import { join as join40 } from "node:path";
34010
+ import { join as join41 } from "node:path";
33262
34011
  import { fileURLToPath as fileURLToPath19 } from "node:url";
33263
34012
  function nativeDecodePackageDir() {
33264
34013
  return fileURLToPath19(new URL("../../../core/runtime/ios/HimalayaCore/", import.meta.url));
@@ -33274,7 +34023,7 @@ function detectNativeDecode(options = {}) {
33274
34023
  return { platform: "ios", available: false, reason: `the iOS decoder oracle requires macOS (darwin); this host is ${hostPlatform}` };
33275
34024
  }
33276
34025
  if (!swiftAvailable) return { platform: "ios", available: false, reason: "swift is not on PATH" };
33277
- const packageAvailable = options.packageAvailable ?? existsSync40(join40(packageDir, "Package.swift"));
34026
+ const packageAvailable = options.packageAvailable ?? existsSync41(join41(packageDir, "Package.swift"));
33278
34027
  if (!packageAvailable) {
33279
34028
  return {
33280
34029
  platform: "ios",
@@ -33286,7 +34035,7 @@ function detectNativeDecode(options = {}) {
33286
34035
  }
33287
34036
  function localPropertiesSdk(harnessDir) {
33288
34037
  try {
33289
- const match = readFileSync48(join40(harnessDir, "local.properties"), "utf8").match(/^sdk\.dir\s*=\s*(.+)$/m);
34038
+ const match = readFileSync48(join41(harnessDir, "local.properties"), "utf8").match(/^sdk\.dir\s*=\s*(.+)$/m);
33290
34039
  return match?.[1]?.trim().replace(/\\([ :\\])/g, "$1") ?? null;
33291
34040
  } catch {
33292
34041
  return null;
@@ -33294,8 +34043,8 @@ function localPropertiesSdk(harnessDir) {
33294
34043
  }
33295
34044
  function detectAndroidNativeDecode(options = {}) {
33296
34045
  const harnessDir = options.harnessDir ?? nativeDecodeAndroidHarnessDir();
33297
- const gradlew = join40(harnessDir, "gradlew");
33298
- const gradlewAvailable = options.gradlewAvailable ?? existsSync40(gradlew);
34046
+ const gradlew = join41(harnessDir, "gradlew");
34047
+ const gradlewAvailable = options.gradlewAvailable ?? existsSync41(gradlew);
33299
34048
  if (!gradlewAvailable) {
33300
34049
  return {
33301
34050
  platform: "android",
@@ -33305,7 +34054,7 @@ function detectAndroidNativeDecode(options = {}) {
33305
34054
  }
33306
34055
  const androidHome = options.androidHome ?? process.env.ANDROID_HOME ?? "";
33307
34056
  const sdk = androidHome.trim() || (options.localPropertiesSdk !== void 0 ? options.localPropertiesSdk : localPropertiesSdk(harnessDir));
33308
- const sdkAvailable = options.sdkAvailable ?? (typeof sdk === "string" && sdk.length > 0 && existsSync40(sdk));
34057
+ const sdkAvailable = options.sdkAvailable ?? (typeof sdk === "string" && sdk.length > 0 && existsSync41(sdk));
33309
34058
  if (!sdkAvailable) {
33310
34059
  return {
33311
34060
  platform: "android",
@@ -33377,14 +34126,14 @@ function parseNativeDecodeOutput(stdout, expectedScreenIds, platform = "ios") {
33377
34126
  return verdicts;
33378
34127
  }
33379
34128
  function runNativeDecode(options) {
33380
- const descriptorDir = mkdtempSync2(join40(tmpdir3(), "himi-native-decode-"));
34129
+ const descriptorDir = mkdtempSync2(join41(tmpdir3(), "himi-native-decode-"));
33381
34130
  const startedAt = Date.now();
33382
34131
  try {
33383
34132
  for (const descriptor of options.descriptors) {
33384
34133
  if (typeof descriptor.descriptorJson !== "string") {
33385
34134
  throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
33386
34135
  }
33387
- writeFileSync17(join40(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
34136
+ writeFileSync17(join41(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
33388
34137
  }
33389
34138
  options.progress?.("building cached Swift oracle");
33390
34139
  execFileSync5("swift", ["build", "--package-path", options.packageDir, "--product", "himi-decode-oracle"], {
@@ -33396,7 +34145,7 @@ function runNativeDecode(options) {
33396
34145
  encoding: "utf8"
33397
34146
  }).trim();
33398
34147
  options.progress?.(`running real Swift decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
33399
- const stdout = execFileSync5(join40(binPath, "himi-decode-oracle"), ["--dir", descriptorDir], {
34148
+ const stdout = execFileSync5(join41(binPath, "himi-decode-oracle"), ["--dir", descriptorDir], {
33400
34149
  stdio: ["ignore", "pipe", "pipe"],
33401
34150
  encoding: "utf8"
33402
34151
  });
@@ -33410,19 +34159,19 @@ function runNativeDecode(options) {
33410
34159
  const stderr = failure.stderr ? ` \u2014 ${String(failure.stderr).trim().slice(0, 2e3)}` : "";
33411
34160
  throw new Error(`iOS decoder oracle failed: ${failure.message}${stderr}`);
33412
34161
  } finally {
33413
- rmSync6(descriptorDir, { recursive: true, force: true });
34162
+ rmSync7(descriptorDir, { recursive: true, force: true });
33414
34163
  }
33415
34164
  }
33416
34165
  function runAndroidNativeDecode(options) {
33417
- const descriptorDir = mkdtempSync2(join40(tmpdir3(), "himi-native-decode-android-"));
33418
- const outputPath = join40(descriptorDir, "verdicts.ndjson");
34166
+ const descriptorDir = mkdtempSync2(join41(tmpdir3(), "himi-native-decode-android-"));
34167
+ const outputPath = join41(descriptorDir, "verdicts.ndjson");
33419
34168
  const startedAt = Date.now();
33420
34169
  try {
33421
34170
  for (const descriptor of options.descriptors) {
33422
34171
  if (typeof descriptor.descriptorJson !== "string") {
33423
34172
  throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
33424
34173
  }
33425
- writeFileSync17(join40(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
34174
+ writeFileSync17(join41(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
33426
34175
  }
33427
34176
  options.progress?.(`running real Kotlin decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
33428
34177
  execFileSync5(options.gradlew, [
@@ -33448,7 +34197,7 @@ function runAndroidNativeDecode(options) {
33448
34197
  const stderr = failure.stderr ? ` \u2014 ${String(failure.stderr).trim().slice(0, 2e3)}` : "";
33449
34198
  throw new Error(`Android decoder oracle failed: ${failure.message}${stderr}`);
33450
34199
  } finally {
33451
- rmSync6(descriptorDir, { recursive: true, force: true });
34200
+ rmSync7(descriptorDir, { recursive: true, force: true });
33452
34201
  }
33453
34202
  }
33454
34203
  function fieldAt(path) {
@@ -33508,30 +34257,30 @@ __export(browser_authoring_exports, {
33508
34257
  import { createServer as createServer3 } from "node:http";
33509
34258
  import { createHash as createHash22, randomBytes as randomBytes5 } from "node:crypto";
33510
34259
  import { execFile as execFile2 } from "node:child_process";
33511
- import { existsSync as existsSync41, readdirSync as readdirSync21, readFileSync as readFileSync49, realpathSync as realpathSync3, statSync as statSync8 } from "node:fs";
33512
- import { join as join41, posix, relative as relative5, sep as sep8 } from "node:path";
34260
+ import { existsSync as existsSync42, readdirSync as readdirSync21, readFileSync as readFileSync49, realpathSync as realpathSync4, statSync as statSync9 } from "node:fs";
34261
+ import { join as join42, posix, relative as relative5, sep as sep9 } from "node:path";
33513
34262
  function humanMs(ms) {
33514
34263
  return ms < 1e3 ? `${ms}ms` : `${Math.round(ms / 1e3)}s`;
33515
34264
  }
33516
34265
  function collectWorkerSources(tier5SrcDir, bundleName) {
33517
- const root = join41(tier5SrcDir, bundleName);
34266
+ const root = join42(tier5SrcDir, bundleName);
33518
34267
  const files = {};
33519
34268
  const visitedDirs = /* @__PURE__ */ new Set();
33520
34269
  const walk2 = (abs, rel) => {
33521
- const real = realpathSync3(abs);
34270
+ const real = realpathSync4(abs);
33522
34271
  if (visitedDirs.has(real)) return;
33523
34272
  visitedDirs.add(real);
33524
34273
  for (const name of readdirSync21(abs).sort()) {
33525
- const childAbs = join41(abs, name);
34274
+ const childAbs = join42(abs, name);
33526
34275
  const childRel = posix.join(rel, name);
33527
- if (statSync8(childAbs).isDirectory()) {
34276
+ if (statSync9(childAbs).isDirectory()) {
33528
34277
  walk2(childAbs, childRel);
33529
34278
  } else if (/\.(ts|tsx|js|mjs|json)$/.test(name)) {
33530
34279
  files[childRel] = readFileSync49(childAbs, "utf8");
33531
34280
  }
33532
34281
  }
33533
34282
  };
33534
- if (!existsSync41(root)) return null;
34283
+ if (!existsSync42(root)) return null;
33535
34284
  walk2(root, `/tier5-src/${bundleName}`);
33536
34285
  const entry = [`/tier5-src/${bundleName}/index.ts`, `/tier5-src/${bundleName}/index.js`].find((p) => p in files);
33537
34286
  if (!entry) return null;
@@ -33548,18 +34297,18 @@ function collectRelativeSiblings(files, tier5SrcDir) {
33548
34297
  const spec = m[1];
33549
34298
  const virtual = posix.normalize(posix.join(dir, spec));
33550
34299
  if (!virtual.startsWith("/tier5-src/")) continue;
33551
- const onDisk = join41(tier5SrcDir, virtual.slice("/tier5-src/".length));
34300
+ const onDisk = join42(tier5SrcDir, virtual.slice("/tier5-src/".length));
33552
34301
  const candidates = [
33553
34302
  onDisk,
33554
34303
  `${onDisk}.ts`,
33555
34304
  `${onDisk}.js`,
33556
34305
  onDisk.replace(/\.js$/, ".ts"),
33557
- join41(onDisk, "index.ts"),
33558
- join41(onDisk, "index.js")
34306
+ join42(onDisk, "index.ts"),
34307
+ join42(onDisk, "index.js")
33559
34308
  ];
33560
34309
  for (const abs of candidates) {
33561
- if (!existsSync41(abs) || statSync8(abs).isDirectory()) continue;
33562
- const key2 = `/tier5-src/${relative5(tier5SrcDir, abs).split(sep8).join(posix.sep)}`;
34310
+ if (!existsSync42(abs) || statSync9(abs).isDirectory()) continue;
34311
+ const key2 = `/tier5-src/${relative5(tier5SrcDir, abs).split(sep9).join(posix.sep)}`;
33563
34312
  if (key2 in files) break;
33564
34313
  files[key2] = readFileSync49(abs, "utf8");
33565
34314
  queue.push(key2);
@@ -33593,10 +34342,10 @@ function collectSdkModules(files, resolveSubpath) {
33593
34342
  for (const source of Object.values(files)) visit(source);
33594
34343
  return { modules, unsupported };
33595
34344
  }
33596
- function sdkSubpathReader(resolve40) {
34345
+ function sdkSubpathReader(resolve41) {
33597
34346
  return (sub) => {
33598
34347
  try {
33599
- return readFileSync49(resolve40(`@wix/himalaya/${sub}`), "utf8");
34348
+ return readFileSync49(resolve41(`@wix/himalaya/${sub}`), "utf8");
33600
34349
  } catch {
33601
34350
  return null;
33602
34351
  }
@@ -33677,7 +34426,7 @@ async function runBrowserAuthoring(opts) {
33677
34426
  const token = mintToken2(payload);
33678
34427
  const unusable = await probeAuthoringPage(opts.authoringBase, opts.fetchImpl, opts.probeTimeoutMs);
33679
34428
  if (unusable) throw new Error(unusable);
33680
- return await new Promise((resolve40, reject) => {
34429
+ return await new Promise((resolve41, reject) => {
33681
34430
  let settled = false;
33682
34431
  let url = "";
33683
34432
  const server = createServer3();
@@ -33729,7 +34478,7 @@ async function runBrowserAuthoring(opts) {
33729
34478
  }
33730
34479
  res.writeHead(200, { "content-type": "text/plain" }).end("ok");
33731
34480
  const report = body.result;
33732
- finish2(() => resolve40({ report, url }));
34481
+ finish2(() => resolve41({ report, url }));
33733
34482
  })();
33734
34483
  });
33735
34484
  server.on("error", (e) => finish2(() => reject(e)));
@@ -33769,8 +34518,8 @@ __export(coverage_exports, {
33769
34518
  formatLedger: () => formatLedger,
33770
34519
  trendAndStore: () => trendAndStore
33771
34520
  });
33772
- import { existsSync as existsSync42, mkdirSync as mkdirSync21, readFileSync as readFileSync50, writeFileSync as writeFileSync18 } from "node:fs";
33773
- import { dirname as dirname26, join as join42 } from "node:path";
34521
+ import { existsSync as existsSync43, mkdirSync as mkdirSync21, readFileSync as readFileSync50, writeFileSync as writeFileSync18 } from "node:fs";
34522
+ import { dirname as dirname25, join as join43 } from "node:path";
33774
34523
  function assembleLedger(r) {
33775
34524
  const out = [];
33776
34525
  for (const c of r.crawled ?? []) {
@@ -33936,10 +34685,10 @@ function assembleLedger(r) {
33936
34685
  return out;
33937
34686
  }
33938
34687
  function trendAndStore(dir, entries) {
33939
- const p = join42(dir, LEDGER_FILE);
34688
+ const p = join43(dir, LEDGER_FILE);
33940
34689
  let previous = null;
33941
34690
  try {
33942
- if (existsSync42(p)) {
34691
+ if (existsSync43(p)) {
33943
34692
  const parsed = JSON.parse(readFileSync50(p, "utf8"));
33944
34693
  if (Array.isArray(parsed.keys)) previous = parsed.keys.filter((k) => typeof k === "string");
33945
34694
  }
@@ -33947,7 +34696,7 @@ function trendAndStore(dir, entries) {
33947
34696
  }
33948
34697
  const current = entries.map((e) => e.key);
33949
34698
  try {
33950
- mkdirSync21(dirname26(p), { recursive: true });
34699
+ mkdirSync21(dirname25(p), { recursive: true });
33951
34700
  writeFileSync18(p, JSON.stringify({ keys: current, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
33952
34701
  } catch {
33953
34702
  return null;
@@ -33977,10 +34726,10 @@ function formatLedger(entries, trend) {
33977
34726
  return out.join("\n");
33978
34727
  }
33979
34728
  function diffAndStoreObservations(dir, current) {
33980
- const p = join42(dir, OBS_FILE);
34729
+ const p = join43(dir, OBS_FILE);
33981
34730
  let previous = null;
33982
34731
  try {
33983
- if (existsSync42(p)) {
34732
+ if (existsSync43(p)) {
33984
34733
  const parsed = JSON.parse(readFileSync50(p, "utf8"));
33985
34734
  if (parsed.screens && typeof parsed.screens === "object" && !Array.isArray(parsed.screens) && Object.values(parsed.screens).every(
33986
34735
  (v) => Array.isArray(v) && v.every((l) => typeof l === "string")
@@ -33991,7 +34740,7 @@ function diffAndStoreObservations(dir, current) {
33991
34740
  } catch {
33992
34741
  }
33993
34742
  try {
33994
- mkdirSync21(dirname26(p), { recursive: true });
34743
+ mkdirSync21(dirname25(p), { recursive: true });
33995
34744
  writeFileSync18(p, JSON.stringify({ screens: current, ts: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
33996
34745
  } catch {
33997
34746
  return null;
@@ -34029,8 +34778,8 @@ function formatDelta(delta) {
34029
34778
  var LEDGER_FILE, OBS_FILE;
34030
34779
  var init_coverage = __esm({
34031
34780
  "src/coverage.ts"() {
34032
- LEDGER_FILE = join42(".himi", "test-ledger.json");
34033
- OBS_FILE = join42(".himi", "test-observations.json");
34781
+ LEDGER_FILE = join43(".himi", "test-ledger.json");
34782
+ OBS_FILE = join43(".himi", "test-observations.json");
34034
34783
  }
34035
34784
  });
34036
34785
 
@@ -35234,9 +35983,9 @@ __export(run_exports, {
35234
35983
  runPackage: () => runPackage,
35235
35984
  withoutProdServe: () => withoutProdServe
35236
35985
  });
35237
- import { cpSync, existsSync as existsSync43, mkdirSync as mkdirSync22, mkdtempSync as mkdtempSync3, readFileSync as readFileSync52, rmSync as rmSync7, statSync as statSync9 } from "node:fs";
35986
+ import { cpSync, existsSync as existsSync44, mkdirSync as mkdirSync22, mkdtempSync as mkdtempSync3, readFileSync as readFileSync52, rmSync as rmSync8, statSync as statSync10 } from "node:fs";
35238
35987
  import { tmpdir as tmpdir4 } from "node:os";
35239
- import { join as join43 } from "node:path";
35988
+ import { join as join44 } from "node:path";
35240
35989
  import { spawn as spawn3 } from "node:child_process";
35241
35990
  function makePackageRunHandler(opts) {
35242
35991
  return makeRunHandler({
@@ -35254,9 +36003,9 @@ async function fetchBytes(url) {
35254
36003
  }
35255
36004
  async function resolvePackage(source, workDir) {
35256
36005
  const isUrl2 = /^https?:\/\//i.test(source);
35257
- if (!isUrl2 && existsSync43(source) && statSync9(source).isDirectory()) {
35258
- const appDirManifest = join43(source, "release-manifest.json");
35259
- if (existsSync43(appDirManifest)) {
36006
+ if (!isUrl2 && existsSync44(source) && statSync10(source).isDirectory()) {
36007
+ const appDirManifest = join44(source, "release-manifest.json");
36008
+ if (existsSync44(appDirManifest)) {
35260
36009
  const opened2 = openPackageDir(source);
35261
36010
  if (!opened2.validation.ok) {
35262
36011
  throw new Error(`package is not valid:
@@ -35265,11 +36014,11 @@ async function resolvePackage(source, workDir) {
35265
36014
  const app2 = opened2.header?.app ?? opened2.manifest?.app;
35266
36015
  if (!app2) throw new Error(`${source} has no app id (no himi-package.json and no manifest.app)`);
35267
36016
  if (!isSafeAppId(app2)) throw new Error(`${source} declares an unsafe app id ${JSON.stringify(app2)} \u2014 refusing to open it`);
35268
- const root2 = workDir ?? mkdtempSync3(join43(tmpdir4(), "himi-run-"));
35269
- const target = join43(root2, app2);
35270
- if (join43(source) !== target) {
36017
+ const root2 = workDir ?? mkdtempSync3(join44(tmpdir4(), "himi-run-"));
36018
+ const target = join44(root2, app2);
36019
+ if (join44(source) !== target) {
35271
36020
  mkdirSync22(root2, { recursive: true });
35272
- rmSync7(target, { recursive: true, force: true });
36021
+ rmSync8(target, { recursive: true, force: true });
35273
36022
  cpSync(source, target, { recursive: true });
35274
36023
  }
35275
36024
  return {
@@ -35285,11 +36034,11 @@ async function resolvePackage(source, workDir) {
35285
36034
  throw new Error(`${source} is a directory but has no release-manifest.json \u2014 is it a package?`);
35286
36035
  }
35287
36036
  const bytes2 = isUrl2 ? await fetchBytes(source) : readFileSync52(source);
35288
- const root = workDir ?? mkdtempSync3(join43(tmpdir4(), "himi-run-"));
36037
+ const root = workDir ?? mkdtempSync3(join44(tmpdir4(), "himi-run-"));
35289
36038
  const { app, appDir: appDir2 } = unpackTo(bytes2, root);
35290
36039
  const opened = openPackageDir(appDir2);
35291
36040
  if (!opened.validation.ok) {
35292
- rmSync7(workDir ? appDir2 : root, { recursive: true, force: true });
36041
+ rmSync8(workDir ? appDir2 : root, { recursive: true, force: true });
35293
36042
  throw new Error(`package is not valid:
35294
36043
  ${opened.validation.errors.join("\n ")}`);
35295
36044
  }
@@ -35328,7 +36077,7 @@ async function runPackage(source, opts = {}) {
35328
36077
  const resolved = await resolvePackage(source, opts.workDir);
35329
36078
  const assets = opts.assets ?? (opts.spa ? /* @__PURE__ */ new Map() : await loadPreviewAssets());
35330
36079
  if (!opts.spa && assets.size === 0) {
35331
- if (resolved.temp) rmSync7(resolved.temp, { recursive: true, force: true });
36080
+ if (resolved.temp) rmSync8(resolved.temp, { recursive: true, force: true });
35332
36081
  throw new Error(NO_SPA_MESSAGE);
35333
36082
  }
35334
36083
  const handler = makePackageRunHandler({
@@ -35358,7 +36107,7 @@ async function runPackage(source, opts = {}) {
35358
36107
  warnings: resolved.warnings,
35359
36108
  close: () => new Promise((done) => {
35360
36109
  server.close(() => {
35361
- if (resolved.temp) rmSync7(resolved.temp, { recursive: true, force: true });
36110
+ if (resolved.temp) rmSync8(resolved.temp, { recursive: true, force: true });
35362
36111
  done();
35363
36112
  });
35364
36113
  })
@@ -35385,19 +36134,19 @@ __export(run_browser_exports, {
35385
36134
  runInBrowser: () => runInBrowser
35386
36135
  });
35387
36136
  import { createServer as createServer5 } from "node:http";
35388
- import { existsSync as existsSync44, readFileSync as readFileSync53 } from "node:fs";
36137
+ import { existsSync as existsSync45, readFileSync as readFileSync53 } from "node:fs";
35389
36138
  import { spawnSync as spawnSync3, execFile as execFile3 } from "node:child_process";
35390
- import { dirname as dirname27, resolve as resolve35 } from "node:path";
36139
+ import { dirname as dirname26, resolve as resolve36 } from "node:path";
35391
36140
  import { fileURLToPath as fileURLToPath20 } from "node:url";
35392
36141
  function stagedDir() {
35393
- return process.env.HIMI_BROWSER_PLANE_OUT ? resolve35(process.env.HIMI_BROWSER_PLANE_OUT) : resolve35(REPO, "packages", "serve", "dist", "browser-plane");
36142
+ return process.env.HIMI_BROWSER_PLANE_OUT ? resolve36(process.env.HIMI_BROWSER_PLANE_OUT) : resolve36(REPO, "packages", "serve", "dist", "browser-plane");
35394
36143
  }
35395
36144
  function missingStaged() {
35396
- return [...SERVED].filter((f) => !existsSync44(resolve35(stagedDir(), f))).sort();
36145
+ return [...SERVED].filter((f) => !existsSync45(resolve36(stagedDir(), f))).sort();
35397
36146
  }
35398
36147
  function ensureBrowserPlaneStaged() {
35399
36148
  if (missingStaged().length === 0) return { ok: true, detail: "already staged" };
35400
- const r = spawnSync3(process.execPath, [resolve35(REPO, "core/serve/scripts/stage-browser-plane.mjs")], {
36149
+ const r = spawnSync3(process.execPath, [resolve36(REPO, "core/serve/scripts/stage-browser-plane.mjs")], {
35401
36150
  cwd: REPO,
35402
36151
  encoding: "utf8"
35403
36152
  });
@@ -35483,7 +36232,7 @@ async function runInBrowser(releaseRoot, app, header, opts = {}) {
35483
36232
  }
35484
36233
  const name = path === "/" || path === "/index.html" ? "host.html" : path.slice(1);
35485
36234
  if (SERVED.has(name)) {
35486
- if (!existsSync44(resolve35(stagedDir(), name))) {
36235
+ if (!existsSync45(resolve36(stagedDir(), name))) {
35487
36236
  const restaged = ensureBrowserPlaneStaged();
35488
36237
  if (!restaged.ok) {
35489
36238
  res.writeHead(503, { "content-type": "application/json" });
@@ -35493,7 +36242,7 @@ async function runInBrowser(releaseRoot, app, header, opts = {}) {
35493
36242
  }
35494
36243
  let bytes2;
35495
36244
  try {
35496
- bytes2 = readFileSync53(resolve35(stagedDir(), name));
36245
+ bytes2 = readFileSync53(resolve36(stagedDir(), name));
35497
36246
  } catch (e) {
35498
36247
  res.writeHead(503, { "content-type": "application/json" });
35499
36248
  res.end(JSON.stringify({ error: "browser_plane_unstaged", file: name, detail: String(e) }));
@@ -35536,8 +36285,8 @@ var HERE, REPO, SERVED;
35536
36285
  var init_run_browser = __esm({
35537
36286
  "src/run-browser.ts"() {
35538
36287
  init_file_store();
35539
- HERE = dirname27(fileURLToPath20(import.meta.url));
35540
- REPO = resolve35(HERE, "..", "..", "..");
36288
+ HERE = dirname26(fileURLToPath20(import.meta.url));
36289
+ REPO = resolve36(HERE, "..", "..", "..");
35541
36290
  SERVED = /* @__PURE__ */ new Set(["host.html", "sw.js", "package-reader.js"]);
35542
36291
  }
35543
36292
  });
@@ -35911,10 +36660,10 @@ __export(preflight_exports, {
35911
36660
  parseStoreConfig: () => parseStoreConfig,
35912
36661
  requireEnv: () => requireEnv
35913
36662
  });
35914
- import { existsSync as existsSync45, readFileSync as readFileSync54 } from "node:fs";
35915
- import { isAbsolute as isAbsolute2, relative as relative6, resolve as resolve36 } from "node:path";
36663
+ import { existsSync as existsSync46, readFileSync as readFileSync54 } from "node:fs";
36664
+ import { isAbsolute as isAbsolute2, relative as relative6, resolve as resolve37 } from "node:path";
35916
36665
  function isInsideRepo(p) {
35917
- const rel = relative6(REPO_ROOT3, resolve36(p));
36666
+ const rel = relative6(REPO_ROOT3, resolve37(p));
35918
36667
  return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
35919
36668
  }
35920
36669
  function assertCredentialOutsideRepo(label2, p) {
@@ -35968,7 +36717,7 @@ function parseStoreConfig(text2) {
35968
36717
  }
35969
36718
  function loadStoreConfig(app) {
35970
36719
  const p = storeConfigPath(app);
35971
- if (!existsSync45(p)) {
36720
+ if (!existsSync46(p)) {
35972
36721
  throw new Error(`No store config at ${p}. Create apps/${app}/store/store.config.yaml first (see /setup-store-signing).`);
35973
36722
  }
35974
36723
  return parseStoreConfig(readFileSync54(p, "utf8"));
@@ -35994,9 +36743,9 @@ __export(preflight_app_exports, {
35994
36743
  pngSize: () => pngSize,
35995
36744
  preflightApp: () => preflightApp
35996
36745
  });
35997
- import { readFileSync as readFileSync55, existsSync as existsSync46, readdirSync as readdirSync22, statSync as statSync10 } from "node:fs";
36746
+ import { readFileSync as readFileSync55, existsSync as existsSync47, readdirSync as readdirSync22, statSync as statSync11 } from "node:fs";
35998
36747
  import { createHash as createHash23 } from "node:crypto";
35999
- import { join as join44 } from "node:path";
36748
+ import { join as join45 } from "node:path";
36000
36749
  function analyze(projectYml, storeConfig, icon) {
36001
36750
  const f = [];
36002
36751
  const has = (re, s) => re.test(s);
@@ -36044,10 +36793,10 @@ function analyze(projectYml, storeConfig, icon) {
36044
36793
  return f;
36045
36794
  }
36046
36795
  async function preflightApp(app) {
36047
- const projectYml = existsSync46(iosProjectYml(app)) ? readFileSync55(iosProjectYml(app), "utf8") : "";
36048
- const storeConfig = existsSync46(storeConfigPath(app)) ? readFileSync55(storeConfigPath(app), "utf8") : "";
36796
+ const projectYml = existsSync47(iosProjectYml(app)) ? readFileSync55(iosProjectYml(app), "utf8") : "";
36797
+ const storeConfig = existsSync47(storeConfigPath(app)) ? readFileSync55(storeConfigPath(app), "utf8") : "";
36049
36798
  if (!storeConfig) return [{ level: "error", message: `no apps/${app}/store/store.config.yaml \u2014 run /setup-store-signing first.` }];
36050
- const findings = analyze(projectYml, storeConfig, await appIconState(join44(appDir(app), "ios")));
36799
+ const findings = analyze(projectYml, storeConfig, await appIconState(join45(appDir(app), "ios")));
36051
36800
  findings.push(...analyzeListing(metadataFilesPresent(app), listScreenshots(app)));
36052
36801
  findings.push(
36053
36802
  ...analyzePlayListing(playListingAssets(app)).map((finding) => ({ ...finding, level: "warn" }))
@@ -36074,17 +36823,17 @@ function analyzeSubmissionRecord(appLevel, review, opts) {
36074
36823
  return f;
36075
36824
  }
36076
36825
  function appLevelMetadataFiles(app, locale = "en-US") {
36077
- const root = join44(storeDir(app), "metadata");
36826
+ const root = join45(storeDir(app), "metadata");
36078
36827
  const out = /* @__PURE__ */ new Set();
36079
- if (existsSync46(root)) {
36828
+ if (existsSync47(root)) {
36080
36829
  for (const n of readdirSync22(root)) if (n.endsWith(".txt")) out.add(n);
36081
36830
  }
36082
- if (existsSync46(join44(root, locale, "privacy_url.txt"))) out.add("privacy_url.txt");
36831
+ if (existsSync47(join45(root, locale, "privacy_url.txt"))) out.add("privacy_url.txt");
36083
36832
  return out;
36084
36833
  }
36085
36834
  function reviewInfoFiles(app) {
36086
- const dir = join44(storeDir(app), "review_information");
36087
- if (!existsSync46(dir)) return /* @__PURE__ */ new Set();
36835
+ const dir = join45(storeDir(app), "review_information");
36836
+ if (!existsSync47(dir)) return /* @__PURE__ */ new Set();
36088
36837
  return new Set(readdirSync22(dir).filter((n) => n.endsWith(".txt")));
36089
36838
  }
36090
36839
  function isValidAppStoreSize(w, h) {
@@ -36194,12 +36943,12 @@ function analyzePlayListing(assets) {
36194
36943
  return f;
36195
36944
  }
36196
36945
  function playListingAssets(app, locale = "en-US") {
36197
- const dir = join44(storeDir(app), "play", locale, "images");
36946
+ const dir = join45(storeDir(app), "play", locale, "images");
36198
36947
  const read = (names) => {
36199
36948
  for (const name of names) {
36200
- const p = join44(dir, name);
36949
+ const p = join45(dir, name);
36201
36950
  try {
36202
- if (statSync10(p).isFile()) return { name, bytes: readFileSync55(p) };
36951
+ if (statSync11(p).isFile()) return { name, bytes: readFileSync55(p) };
36203
36952
  } catch {
36204
36953
  }
36205
36954
  }
@@ -36230,26 +36979,26 @@ function analyzeListing(metaFiles, shots) {
36230
36979
  return f;
36231
36980
  }
36232
36981
  function metadataFilesPresent(app, locale = "en-US") {
36233
- const dir = join44(storeDir(app), "metadata", locale);
36234
- if (!existsSync46(dir)) return /* @__PURE__ */ new Set();
36982
+ const dir = join45(storeDir(app), "metadata", locale);
36983
+ if (!existsSync47(dir)) return /* @__PURE__ */ new Set();
36235
36984
  return new Set(readdirSync22(dir).filter((n) => n.endsWith(".txt")));
36236
36985
  }
36237
36986
  function listScreenshots(app, locale = "en-US") {
36238
36987
  const out = [];
36239
- const flat = join44(storeDir(app), "screenshots", locale);
36240
- if (existsSync46(flat)) {
36988
+ const flat = join45(storeDir(app), "screenshots", locale);
36989
+ if (existsSync47(flat)) {
36241
36990
  for (const png of readdirSync22(flat).filter((n) => n.endsWith(".png"))) {
36242
- const sz = pngSize(readFileSync55(join44(flat, png)));
36991
+ const sz = pngSize(readFileSync55(join45(flat, png)));
36243
36992
  if (sz) out.push({ name: png, w: sz.w, h: sz.h });
36244
36993
  }
36245
36994
  }
36246
- const legacy = join44(storeDir(app), "metadata", locale, "screenshots");
36247
- if (existsSync46(legacy)) {
36995
+ const legacy = join45(storeDir(app), "metadata", locale, "screenshots");
36996
+ if (existsSync47(legacy)) {
36248
36997
  for (const deviceDir of readdirSync22(legacy)) {
36249
- const d = join44(legacy, deviceDir);
36250
- if (!statSync10(d).isDirectory()) continue;
36998
+ const d = join45(legacy, deviceDir);
36999
+ if (!statSync11(d).isDirectory()) continue;
36251
37000
  for (const png of readdirSync22(d).filter((n) => n.endsWith(".png"))) {
36252
- const sz = pngSize(readFileSync55(join44(d, png)));
37001
+ const sz = pngSize(readFileSync55(join45(d, png)));
36253
37002
  if (sz) out.push({ name: `${deviceDir}/${png}`, w: sz.w, h: sz.h });
36254
37003
  }
36255
37004
  }
@@ -36257,12 +37006,12 @@ function listScreenshots(app, locale = "en-US") {
36257
37006
  return out;
36258
37007
  }
36259
37008
  function placeholderHashes2() {
36260
- const templateIos = join44(appDir("_template"), "ios");
36261
- if (!existsSync46(templateIos)) return [LEGACY_PLACEHOLDER_SHA2562];
37009
+ const templateIos = join45(appDir("_template"), "ios");
37010
+ if (!existsSync47(templateIos)) return [LEGACY_PLACEHOLDER_SHA2562];
36262
37011
  for (const target of readdirSync22(templateIos)) {
36263
- const p = join44(templateIos, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
37012
+ const p = join45(templateIos, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
36264
37013
  try {
36265
- if (!statSync10(p).isFile()) continue;
37014
+ if (!statSync11(p).isFile()) continue;
36266
37015
  return [LEGACY_PLACEHOLDER_SHA2562, createHash23("sha256").update(readFileSync55(p)).digest("hex")];
36267
37016
  } catch {
36268
37017
  }
@@ -36294,11 +37043,11 @@ async function pixelIssues(bytes2) {
36294
37043
  }
36295
37044
  }
36296
37045
  async function appIconState(iosDir) {
36297
- if (!existsSync46(iosDir)) return { exists: false, isPlaceholder: false, storeIssues: [] };
37046
+ if (!existsSync47(iosDir)) return { exists: false, isPlaceholder: false, storeIssues: [] };
36298
37047
  for (const target of readdirSync22(iosDir)) {
36299
- const p = join44(iosDir, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
37048
+ const p = join45(iosDir, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
36300
37049
  try {
36301
- if (!statSync10(p).isFile()) continue;
37050
+ if (!statSync11(p).isFile()) continue;
36302
37051
  const bytes2 = readFileSync55(p);
36303
37052
  const hash = createHash23("sha256").update(bytes2).digest("hex");
36304
37053
  const pixels = await pixelIssues(bytes2);
@@ -36369,14 +37118,14 @@ __export(credential_profile_exports, {
36369
37118
  profilePath: () => profilePath,
36370
37119
  resolveStoreCredentials: () => resolveStoreCredentials
36371
37120
  });
36372
- import { existsSync as existsSync47, readFileSync as readFileSync56 } from "node:fs";
37121
+ import { existsSync as existsSync48, readFileSync as readFileSync56 } from "node:fs";
36373
37122
  import { homedir as homedir6 } from "node:os";
36374
- import { join as join45 } from "node:path";
37123
+ import { join as join46 } from "node:path";
36375
37124
  function credentialsDir(home = homedir6()) {
36376
- return join45(home, ".himalaya", "store-credentials");
37125
+ return join46(home, ".himalaya", "store-credentials");
36377
37126
  }
36378
37127
  function profilePath(name, home = homedir6()) {
36379
- return join45(credentialsDir(home), `${name}.json`);
37128
+ return join46(credentialsDir(home), `${name}.json`);
36380
37129
  }
36381
37130
  function parseProfile(text2, label2) {
36382
37131
  let doc;
@@ -36404,7 +37153,7 @@ function resolveStoreCredentials(opts) {
36404
37153
  const env = opts.env ?? process.env;
36405
37154
  const home = opts.home ?? homedir6();
36406
37155
  const read = opts.readProfile ?? ((p) => readFileSync56(p, "utf8"));
36407
- const exists = opts.profileExists ?? existsSync47;
37156
+ const exists = opts.profileExists ?? existsSync48;
36408
37157
  const ios = cfg.ios ?? {};
36409
37158
  const flagName = opts.profileFlag?.trim();
36410
37159
  const named = flagName || ios.credentialProfile?.trim();
@@ -39696,10 +40445,10 @@ function isFetchableFontUrl(raw) {
39696
40445
  return true;
39697
40446
  }
39698
40447
  function resolveAll(hostname) {
39699
- return new Promise((resolve40, reject) => {
40448
+ return new Promise((resolve41, reject) => {
39700
40449
  dnsLookup(hostname, { all: true, verbatim: true }, (err, addresses) => {
39701
40450
  if (err) reject(err);
39702
- else resolve40(addresses);
40451
+ else resolve41(addresses);
39703
40452
  });
39704
40453
  });
39705
40454
  }
@@ -39707,13 +40456,13 @@ function withAbort(start, signal, what) {
39707
40456
  if (signal?.aborted) return Promise.reject(new Error(`${what} aborted`));
39708
40457
  const work = start();
39709
40458
  if (!signal) return work;
39710
- return new Promise((resolve40, reject) => {
40459
+ return new Promise((resolve41, reject) => {
39711
40460
  const onAbort = () => reject(new Error(`${what} aborted`));
39712
40461
  signal.addEventListener("abort", onAbort, { once: true });
39713
- work.then(resolve40, reject).finally(() => signal.removeEventListener("abort", onAbort));
40462
+ work.then(resolve41, reject).finally(() => signal.removeEventListener("abort", onAbort));
39714
40463
  });
39715
40464
  }
39716
- async function pinAddress(url, resolve40 = resolveAll, signal) {
40465
+ async function pinAddress(url, resolve41 = resolveAll, signal) {
39717
40466
  const host = url.hostname.replace(/^\[|\]$/g, "");
39718
40467
  if (ipv4Octets(host)) {
39719
40468
  if (isBlockedAddress(host)) throw new Error(`${host} is a blocked address`);
@@ -39723,7 +40472,7 @@ async function pinAddress(url, resolve40 = resolveAll, signal) {
39723
40472
  if (isBlockedAddress(host)) throw new Error(`${host} is a blocked address`);
39724
40473
  return { address: host, family: 6 };
39725
40474
  }
39726
- const addresses = await withAbort(() => resolve40(host), signal, `${host} lookup`);
40475
+ const addresses = await withAbort(() => resolve41(host), signal, `${host} lookup`);
39727
40476
  if (!addresses.length) throw new Error(`${host} resolved to no address`);
39728
40477
  const blocked = addresses.filter((a) => isBlockedAddress(a.address)).map((a) => a.address);
39729
40478
  if (blocked.length) throw new Error(`${host} resolves to a blocked address (${blocked.join(", ")})`);
@@ -39746,7 +40495,7 @@ function decodedBody(res, hasBody) {
39746
40495
  return out;
39747
40496
  }
39748
40497
  function requestOnce(url, pinned, init) {
39749
- return new Promise((resolve40, reject) => {
40498
+ return new Promise((resolve41, reject) => {
39750
40499
  const headers2 = { host: url.host, "accept-encoding": "identity" };
39751
40500
  new Headers(init.headers ?? {}).forEach((value, key2) => {
39752
40501
  if (key2.toLowerCase() !== "host") headers2[key2] = value;
@@ -39780,7 +40529,7 @@ function requestOnce(url, pinned, init) {
39780
40529
  reject(err);
39781
40530
  return;
39782
40531
  }
39783
- resolve40(new Response(body, { status, statusText: res.statusMessage ?? "", headers: out }));
40532
+ resolve41(new Response(body, { status, statusText: res.statusMessage ?? "", headers: out }));
39784
40533
  }
39785
40534
  );
39786
40535
  req.on("error", reject);
@@ -39889,7 +40638,7 @@ var init_brand_font = __esm({
39889
40638
 
39890
40639
  // src/site-analyze/write-artifacts.ts
39891
40640
  import { mkdirSync as mkdirSync23, writeFileSync as writeFileSync20 } from "node:fs";
39892
- import { join as join46 } from "node:path";
40641
+ import { join as join47 } from "node:path";
39893
40642
  function attachVeloSources(book, sources, put) {
39894
40643
  const byPath = new Map((sources ?? []).map((s) => [s.path, s.text]));
39895
40644
  for (const f of book.velo.files) {
@@ -40044,12 +40793,12 @@ async function downloadMedia(book, fetchImpl, put) {
40044
40793
  }
40045
40794
  async function writeBusinessBook(result, opts) {
40046
40795
  const out = opts.outDir;
40047
- mkdirSync23(join46(out, "evidence/pages"), { recursive: true });
40048
- mkdirSync23(join46(out, "media/catalog"), { recursive: true });
40796
+ mkdirSync23(join47(out, "evidence/pages"), { recursive: true });
40797
+ mkdirSync23(join47(out, "media/catalog"), { recursive: true });
40049
40798
  const files = [];
40050
40799
  const put = (rel, body) => {
40051
- const p = join46(out, rel);
40052
- mkdirSync23(join46(p, ".."), { recursive: true });
40800
+ const p = join47(out, rel);
40801
+ mkdirSync23(join47(p, ".."), { recursive: true });
40053
40802
  writeFileSync20(p, body);
40054
40803
  files.push(rel);
40055
40804
  };
@@ -40380,15 +41129,15 @@ var init_home_descriptor = __esm({
40380
41129
  });
40381
41130
 
40382
41131
  // src/site-analyze/apply.ts
40383
- import { existsSync as existsSync48, readFileSync as readFileSync60, writeFileSync as writeFileSync21, copyFileSync as copyFileSync3, mkdirSync as mkdirSync24, realpathSync as realpathSync4, statSync as statSync11 } from "node:fs";
40384
- import { dirname as dirname28, isAbsolute as isAbsolute3, join as join47, resolve as resolve37, sep as sep9 } from "node:path";
41132
+ import { existsSync as existsSync49, readFileSync as readFileSync60, writeFileSync as writeFileSync21, copyFileSync as copyFileSync3, mkdirSync as mkdirSync24, realpathSync as realpathSync5, statSync as statSync12 } from "node:fs";
41133
+ import { dirname as dirname27, isAbsolute as isAbsolute3, join as join48, resolve as resolve38, sep as sep10 } from "node:path";
40385
41134
  import { spawnSync as spawnSync4 } from "node:child_process";
40386
41135
  function unpairedRedirectNote(overlay) {
40387
41136
  if (!overlay.wix?.clientId || overlay.wix.redirectUri) return void 0;
40388
41137
  return `no OAuth redirect paired with client ${overlay.wix.clientId} \u2014 the app (and prepare:customer) will refuse to start until one is set. Register it with \`himi site client ensure --site ${overlay.siteId?.trim() || "<msid>"} --redirect-uri <uri> --yes\`, then re-run analyze with \`--redirect-uri <uri>\` (or export HIMI_BRANDED_LITE_WIX_REDIRECT_URI)`;
40389
41138
  }
40390
41139
  function posix2(p) {
40391
- return resolve37(p).replace(/\\/g, "/");
41140
+ return resolve38(p).replace(/\\/g, "/");
40392
41141
  }
40393
41142
  function committedShellProvisioning(path) {
40394
41143
  return committedShellFor(posix2(path));
@@ -40396,8 +41145,8 @@ function committedShellProvisioning(path) {
40396
41145
  function findRepoRoot(cwd) {
40397
41146
  let dir = cwd;
40398
41147
  for (let i = 0; i < 8; i++) {
40399
- if (SHELL_NAMES2.some((name) => existsSync48(join47(dir, shellAppDir(name), "package.json")))) return dir;
40400
- const next = dirname28(dir);
41148
+ if (SHELL_NAMES2.some((name) => existsSync49(join48(dir, shellAppDir(name), "package.json")))) return dir;
41149
+ const next = dirname27(dir);
40401
41150
  if (next === dir) break;
40402
41151
  dir = next;
40403
41152
  }
@@ -40422,7 +41171,7 @@ function assertNotCommitted(path, forbidden) {
40422
41171
  `refusing to overwrite committed ${committedProvisioningPath(committed)} \u2014 use prepare:customer --contract`
40423
41172
  );
40424
41173
  }
40425
- if (forbidden && resolve37(path) === resolve37(forbidden)) {
41174
+ if (forbidden && resolve38(path) === resolve38(forbidden)) {
40426
41175
  const named = committedShellProvisioning(forbidden);
40427
41176
  throw new Error(
40428
41177
  `refusing to overwrite committed ${named ? committedProvisioningPath(named) : forbidden} \u2014 pass a content-package provisioning.json or use prepare:customer --contract`
@@ -40464,8 +41213,8 @@ function mergeCustomerOwned(committed, overlay) {
40464
41213
  }
40465
41214
  function buildMergedContract(options) {
40466
41215
  const { appDir: appDir2, packageDir, overlay } = options;
40467
- const committedPath = join47(appDir2, "provisioning.json");
40468
- if (!existsSync48(committedPath)) {
41216
+ const committedPath = join48(appDir2, "provisioning.json");
41217
+ if (!existsSync49(committedPath)) {
40469
41218
  throw new Error(`shell has no committed provisioning.json at ${committedPath} to build a contract from`);
40470
41219
  }
40471
41220
  const committed = JSON.parse(readFileSync60(committedPath, "utf8"));
@@ -40475,25 +41224,25 @@ function buildMergedContract(options) {
40475
41224
  const staged = [];
40476
41225
  const contained = (root, rel) => {
40477
41226
  if (isAbsolute3(rel)) return void 0;
40478
- const full = resolve37(root, rel);
40479
- return full.startsWith(resolve37(root) + sep9) ? full : void 0;
41227
+ const full = resolve38(root, rel);
41228
+ return full.startsWith(resolve38(root) + sep10) ? full : void 0;
40480
41229
  };
40481
41230
  const stage = (fromRel, toRel) => {
40482
41231
  const from = contained(packageDir, fromRel);
40483
- const to = contained(join47(appDir2, STAGED), toRel.slice(STAGED.length + 1));
40484
- if (!from || !to || !existsSync48(from)) return void 0;
41232
+ const to = contained(join48(appDir2, STAGED), toRel.slice(STAGED.length + 1));
41233
+ if (!from || !to || !existsSync49(from)) return void 0;
40485
41234
  let real;
40486
41235
  let realRoot;
40487
41236
  try {
40488
- real = realpathSync4(from);
40489
- realRoot = realpathSync4(packageDir);
41237
+ real = realpathSync5(from);
41238
+ realRoot = realpathSync5(packageDir);
40490
41239
  } catch {
40491
41240
  return void 0;
40492
41241
  }
40493
- if (!real.startsWith(realRoot + sep9)) return void 0;
41242
+ if (!real.startsWith(realRoot + sep10)) return void 0;
40494
41243
  try {
40495
- if (!statSync11(real).isFile()) return void 0;
40496
- mkdirSync24(dirname28(to), { recursive: true });
41244
+ if (!statSync12(real).isFile()) return void 0;
41245
+ mkdirSync24(dirname27(to), { recursive: true });
40497
41246
  copyFileSync3(real, to);
40498
41247
  } catch {
40499
41248
  return void 0;
@@ -40503,8 +41252,8 @@ function buildMergedContract(options) {
40503
41252
  };
40504
41253
  const icon = stage("media/icon.png", `${STAGED}/icon.png`);
40505
41254
  if (icon) branding.appIconPath = icon;
40506
- const facesPath = join47(packageDir, "media/fonts/faces.json");
40507
- if (existsSync48(facesPath)) {
41255
+ const facesPath = join48(packageDir, "media/fonts/faces.json");
41256
+ if (existsSync49(facesPath)) {
40508
41257
  let faces = [];
40509
41258
  try {
40510
41259
  const parsed = JSON.parse(readFileSync60(facesPath, "utf8"));
@@ -40528,16 +41277,16 @@ function buildMergedContract(options) {
40528
41277
  merged.branding = branding;
40529
41278
  const body = `${JSON.stringify(merged, null, 2)}
40530
41279
  `;
40531
- const path = join47(packageDir, "provisioning.contract.json");
41280
+ const path = join48(packageDir, "provisioning.contract.json");
40532
41281
  writeFileSync21(path, body);
40533
- const localPath = join47(appDir2, LOCAL_CONTRACT_REL);
40534
- mkdirSync24(dirname28(localPath), { recursive: true });
41282
+ const localPath = join48(appDir2, LOCAL_CONTRACT_REL);
41283
+ mkdirSync24(dirname27(localPath), { recursive: true });
40535
41284
  writeFileSync21(localPath, body);
40536
41285
  staged.push(localPath);
40537
41286
  return { path, staged };
40538
41287
  }
40539
41288
  async function applyOverlays(input) {
40540
- const cwd = resolve37(input.cwd);
41289
+ const cwd = resolve38(input.cwd);
40541
41290
  const shell = input.shell ?? DEFAULT_BRANDABLE_SHELL;
40542
41291
  assertKnownShell(shell);
40543
41292
  const overlay = JSON.parse(readFileSync60(input.overlayPath, "utf8"));
@@ -40551,8 +41300,8 @@ async function applyOverlays(input) {
40551
41300
  `shell "${shell}" cannot bake a customer brand \u2014 it ships no prepare:customer script`
40552
41301
  );
40553
41302
  }
40554
- const appDir2 = join47(repoRoot2, shellAppDir(shell));
40555
- const packageDir = dirname28(resolve37(input.overlayPath));
41303
+ const appDir2 = join48(repoRoot2, shellAppDir(shell));
41304
+ const packageDir = dirname27(resolve38(input.overlayPath));
40556
41305
  const { path: contract, staged } = buildMergedContract({ appDir: appDir2, packageDir, overlay });
40557
41306
  wrote.push(...staged);
40558
41307
  const r = spawnSync4("npm", ["run", "prepare:customer", "--", "--contract", contract], {
@@ -40573,9 +41322,9 @@ async function applyOverlays(input) {
40573
41322
  note: `prepare:customer --contract with a merged contract (committed provisioning.json untouched)${unpairedInRepo ? ` \xB7 ${unpairedInRepo}` : ""}`
40574
41323
  };
40575
41324
  }
40576
- const pkgProv = join47(cwd, "provisioning.json");
41325
+ const pkgProv = join48(cwd, "provisioning.json");
40577
41326
  assertNotCommitted(pkgProv, input.forbiddenProvisioning);
40578
- if (existsSync48(pkgProv)) {
41327
+ if (existsSync49(pkgProv)) {
40579
41328
  const cur = JSON.parse(readFileSync60(pkgProv, "utf8"));
40580
41329
  const merged = deepMerge3(cur, overlay);
40581
41330
  writeFileSync21(pkgProv, JSON.stringify(merged, null, 2) + "\n");
@@ -40584,16 +41333,16 @@ async function applyOverlays(input) {
40584
41333
  writeFileSync21(pkgProv, JSON.stringify(overlay, null, 2) + "\n");
40585
41334
  wrote.push(pkgProv);
40586
41335
  }
40587
- const pkgTokens = join47(cwd, "tokens.json");
40588
- if (existsSync48(pkgTokens) && (tokens.colors || tokens.typography)) {
41336
+ const pkgTokens = join48(cwd, "tokens.json");
41337
+ if (existsSync49(pkgTokens) && (tokens.colors || tokens.typography)) {
40589
41338
  const cur = JSON.parse(readFileSync60(pkgTokens, "utf8"));
40590
41339
  const merged = deepMerge3(cur, tokens);
40591
41340
  writeFileSync21(pkgTokens, JSON.stringify(merged, null, 2) + "\n");
40592
41341
  wrote.push(pkgTokens);
40593
41342
  }
40594
41343
  const homeAction = homeRefreshActionId(shell);
40595
- const homeSrc = join47(cwd, "dev/index.ts");
40596
- if (homeAction && existsSync48(homeSrc)) {
41344
+ const homeSrc = join48(cwd, "dev/index.ts");
41345
+ if (homeAction && existsSync49(homeSrc)) {
40597
41346
  const prev = readFileSync60(homeSrc, "utf8");
40598
41347
  const patched = patchBrandParams(prev, overlay, homeAction);
40599
41348
  let next = patched.src;
@@ -40614,13 +41363,13 @@ async function applyOverlays(input) {
40614
41363
  notes.push(`${patched.unparsed} onAppear params block(s) in ${homeSrc} could not be read \u2014 any brand values inside them were not applied`);
40615
41364
  }
40616
41365
  }
40617
- if (!input.iconPath || !existsSync48(input.iconPath)) {
40618
- const book = input.iconPath ? dirname28(dirname28(resolve37(input.iconPath))) : void 0;
41366
+ if (!input.iconPath || !existsSync49(input.iconPath)) {
41367
+ const book = input.iconPath ? dirname27(dirname27(resolve38(input.iconPath))) : void 0;
40619
41368
  notes.push(
40620
41369
  book ? `no icon installed \u2014 ${siteLogoAbsence(book).hint}` : "no icon installed and no business book to say why \u2014 `himi icon generate` draws one from the accent, or `himi icon set <file.png>` installs the customer's own mark"
40621
41370
  );
40622
41371
  }
40623
- if (input.iconPath && existsSync48(input.iconPath)) {
41372
+ if (input.iconPath && existsSync49(input.iconPath)) {
40624
41373
  try {
40625
41374
  const installed = await installIcon(cwd, input.iconPath, { derivePlay: true });
40626
41375
  wrote.push(...installed.wrote);
@@ -41755,12 +42504,12 @@ __export(cli_exports, {
41755
42504
  runSiteFunctionCli: () => runSiteFunctionCli
41756
42505
  });
41757
42506
  import { readFileSync as readFileSync61 } from "node:fs";
41758
- import { resolve as resolve38 } from "node:path";
42507
+ import { resolve as resolve39 } from "node:path";
41759
42508
  async function runSiteAnalyze(opts) {
41760
42509
  const fetchImpl = opts.fetchImpl ?? fetch;
41761
42510
  const assetFetch = opts.fetchImpl ?? pinnedFetch2;
41762
42511
  const cwd = opts.cwd ?? process.cwd();
41763
- const outDir = resolve38(cwd, opts.outDir ?? ".himi/business-book");
42512
+ const outDir = resolve39(cwd, opts.outDir ?? ".himi/business-book");
41764
42513
  let ownerAuth;
41765
42514
  let msid;
41766
42515
  try {
@@ -41797,9 +42546,9 @@ async function runSiteAnalyze(opts) {
41797
42546
  if (opts.apply) {
41798
42547
  applied = await applyOverlays({
41799
42548
  cwd,
41800
- overlayPath: resolve38(outDir, "provisioning.overlay.json"),
41801
- tokensPath: resolve38(outDir, "tokens.overlay.json"),
41802
- iconPath: resolve38(outDir, "media/icon.png"),
42549
+ overlayPath: resolve39(outDir, "provisioning.overlay.json"),
42550
+ tokensPath: resolve39(outDir, "tokens.overlay.json"),
42551
+ iconPath: resolve39(outDir, "media/icon.png"),
41803
42552
  shell: opts.shell
41804
42553
  });
41805
42554
  const note = noteWithWarnings(applied.note, warnings);
@@ -41835,7 +42584,7 @@ async function runSiteFunctionCli(opts) {
41835
42584
  const token = opts.token ?? await siteToken(msid);
41836
42585
  let source;
41837
42586
  if (opts.sourcePath) {
41838
- const p = resolve38(opts.cwd ?? process.cwd(), opts.sourcePath);
42587
+ const p = resolve39(opts.cwd ?? process.cwd(), opts.sourcePath);
41839
42588
  source = readFileSync61(p, "utf8");
41840
42589
  }
41841
42590
  let body;
@@ -41887,7 +42636,7 @@ async function accountToken2(spawnImpl = spawn4) {
41887
42636
  cached2 = fromEnv;
41888
42637
  return fromEnv;
41889
42638
  }
41890
- const token = await new Promise((resolve40, reject) => {
42639
+ const token = await new Promise((resolve41, reject) => {
41891
42640
  let child;
41892
42641
  try {
41893
42642
  child = spawnImpl("wix", ["token"]);
@@ -41916,7 +42665,7 @@ async function accountToken2(spawnImpl = spawn4) {
41916
42665
  const scan = (d) => {
41917
42666
  out += String(d);
41918
42667
  const m = out.match(TOKEN_RE2);
41919
- if (m) done(() => resolve40(m[0]));
42668
+ if (m) done(() => resolve41(m[0]));
41920
42669
  };
41921
42670
  child.stdout?.on("data", scan);
41922
42671
  child.stderr?.on("data", scan);
@@ -42093,7 +42842,7 @@ __export(site_create_exports, {
42093
42842
  runSiteList: () => runSiteList,
42094
42843
  writeProvisioningBinding: () => writeProvisioningBinding
42095
42844
  });
42096
- import { existsSync as existsSync49, readFileSync as readFileSync62, writeFileSync as writeFileSync22 } from "node:fs";
42845
+ import { existsSync as existsSync50, readFileSync as readFileSync62, writeFileSync as writeFileSync22 } from "node:fs";
42097
42846
  import { resolve as resolvePath2 } from "node:path";
42098
42847
  function oauthClientName(projectName) {
42099
42848
  return `${projectName} client`;
@@ -42242,7 +42991,7 @@ async function runSiteCreate(opts) {
42242
42991
  if (!wantOauthApp) return { ...base, nextSteps: nextSteps(metaSiteId) };
42243
42992
  return { ...base, clientId, redirectUri, nextSteps: nextSteps(metaSiteId, clientId) };
42244
42993
  }
42245
- function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync49, readFileSync: readFileSync62, writeFileSync: writeFileSync22 }) {
42994
+ function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync50, readFileSync: readFileSync62, writeFileSync: writeFileSync22 }) {
42246
42995
  const msid = binding2.siteId;
42247
42996
  const path = resolvePath2(target);
42248
42997
  const committed = committedShellProvisioning(path);
@@ -42576,11 +43325,11 @@ var init_site_oauth_client = __esm({
42576
43325
  });
42577
43326
 
42578
43327
  // src/cli.ts
42579
- import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync63, mkdirSync as mkdirSync25, writeFileSync as writeFileSync23, existsSync as existsSync50, rmSync as rmSync8, readdirSync as readdirSync23, statSync as statSync12 } from "node:fs";
43328
+ import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync63, mkdirSync as mkdirSync25, writeFileSync as writeFileSync23, existsSync as existsSync51, rmSync as rmSync9, readdirSync as readdirSync23, statSync as statSync13 } from "node:fs";
42580
43329
  import { execFileSync as execFileSync6 } from "node:child_process";
42581
43330
  import { createHash as createHash24 } from "node:crypto";
42582
43331
  import { tmpdir as tmpdir5 } from "node:os";
42583
- import { join as join48, resolve as resolve39, basename as basename8, dirname as dirname29 } from "node:path";
43332
+ import { join as join49, resolve as resolve40, basename as basename8, dirname as dirname28 } from "node:path";
42584
43333
  import { fileURLToPath as fileURLToPath21 } from "node:url";
42585
43334
 
42586
43335
  // ../serve-cli/src/client.ts
@@ -44089,7 +44838,7 @@ async function browserLogin(opts) {
44089
44838
  const log = opts.log ?? (() => {
44090
44839
  });
44091
44840
  const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
44092
- return await new Promise((resolve40, reject) => {
44841
+ return await new Promise((resolve41, reject) => {
44093
44842
  let settled = false;
44094
44843
  let loginUrl = "";
44095
44844
  const server = createServer();
@@ -44153,7 +44902,7 @@ async function browserLogin(opts) {
44153
44902
  return;
44154
44903
  }
44155
44904
  res.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-store", connection: "close" }).end(CLOSE_PAGE);
44156
- finish2(() => resolve40({ token, loginUrl }));
44905
+ finish2(() => resolve41({ token, loginUrl }));
44157
44906
  });
44158
44907
  });
44159
44908
  server.on("error", (err) => finish2(() => reject(err)));
@@ -45088,10 +45837,10 @@ async function runRemoteSecretsCommand(operation, flags, io, options) {
45088
45837
  // src/notify.ts
45089
45838
  import { chmodSync as chmodSync2, mkdirSync as mkdirSync8, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "node:fs";
45090
45839
  import { homedir as homedir3 } from "node:os";
45091
- import { dirname as dirname12, join as join14 } from "node:path";
45840
+ import { dirname as dirname12, join as join15 } from "node:path";
45092
45841
  var DEFAULT_ADMIN_URL = "http://127.0.0.1:8888";
45093
45842
  function defaultSessionsPath() {
45094
- return process.env.HIMI_ADMIN_SESSIONS_FILE ?? join14(homedir3(), ".himalaya", "admin-sessions.json");
45843
+ return process.env.HIMI_ADMIN_SESSIONS_FILE ?? join15(homedir3(), ".himalaya", "admin-sessions.json");
45095
45844
  }
45096
45845
  function loadSessionsFile(path = defaultSessionsPath()) {
45097
45846
  try {
@@ -45330,7 +46079,7 @@ NOTE: ${simulated} of those went to the booted iOS Simulator, NOT to the device
45330
46079
  init_car();
45331
46080
  init_preview_surfaces();
45332
46081
  import { existsSync as existsSync12, readFileSync as readFileSync15 } from "node:fs";
45333
- import { join as join15 } from "node:path";
46082
+ import { join as join16 } from "node:path";
45334
46083
  var VALID_PLATFORM_KEYS = /* @__PURE__ */ new Set([
45335
46084
  ...PREVIEW_SURFACES.map((s) => s.platformKey).filter((k) => k !== null),
45336
46085
  ...PREVIEW_EXEMPT_PLATFORM_KEYS
@@ -45341,7 +46090,7 @@ function surfaceIssues(dir, surfaceNav, strict) {
45341
46090
  const error = (message) => issues.push({ screen: null, pass: "surfaces", severity: "error", message });
45342
46091
  let platforms = {};
45343
46092
  let envelopeUnreadable = false;
45344
- const p = join15(dir, "platforms.json");
46093
+ const p = join16(dir, "platforms.json");
45345
46094
  if (existsSync12(p)) {
45346
46095
  let raw;
45347
46096
  try {
@@ -45374,7 +46123,7 @@ function surfaceIssues(dir, surfaceNav, strict) {
45374
46123
  });
45375
46124
  }
45376
46125
  if (envelopeUnreadable) return issues;
45377
- const configTs = join15(dir, "config.ts");
46126
+ const configTs = join16(dir, "config.ts");
45378
46127
  if (existsSync12(configTs)) {
45379
46128
  const declared = parseCarConfig(readFileSync15(configTs, "utf8")).category;
45380
46129
  const inEnvelope = platforms.androidauto === true;
@@ -45400,7 +46149,7 @@ function surfaceIssues(dir, surfaceNav, strict) {
45400
46149
  }
45401
46150
  function readLocalPlatforms(dir) {
45402
46151
  try {
45403
- const raw = JSON.parse(readFileSync15(join15(dir, "platforms.json"), "utf8"));
46152
+ const raw = JSON.parse(readFileSync15(join16(dir, "platforms.json"), "utf8"));
45404
46153
  return Object.fromEntries(
45405
46154
  Object.entries(raw).filter(([k, v]) => VALID_PLATFORM_KEYS.has(k) && typeof v === "boolean")
45406
46155
  );
@@ -45412,10 +46161,10 @@ function readLocalPlatforms(dir) {
45412
46161
  // src/token-lint.ts
45413
46162
  init_token_colors();
45414
46163
  import { existsSync as existsSync13, readFileSync as readFileSync16 } from "node:fs";
45415
- import { join as join16 } from "node:path";
46164
+ import { join as join17 } from "node:path";
45416
46165
  function configuredTokensPath(dir) {
45417
46166
  for (const name of ["config.ts", "config.js", "himi.config.ts"]) {
45418
- const f = join16(dir, name);
46167
+ const f = join17(dir, name);
45419
46168
  if (!existsSync13(f)) continue;
45420
46169
  const src = readFileSync16(f, "utf8");
45421
46170
  const m = /designTokensPath\s*:\s*["'`]([^"'`]+)["'`]/.exec(src);
@@ -45435,7 +46184,7 @@ function tokenColorIssues(dir, tokensPathOverride) {
45435
46184
  );
45436
46185
  }
45437
46186
  const tokensPath = configured.path ?? "tokens.json";
45438
- const p = join16(dir, tokensPath);
46187
+ const p = join17(dir, tokensPath);
45439
46188
  if (!existsSync13(p)) {
45440
46189
  if (tokensPath !== "tokens.json") {
45441
46190
  warn(`config designTokensPath points at ${tokensPath}, which does not exist -- colour tokens were not checked`);
@@ -45483,9 +46232,9 @@ function tokenColorIssues(dir, tokensPathOverride) {
45483
46232
  // src/icon-lint.ts
45484
46233
  init_icon();
45485
46234
  import { existsSync as existsSync16 } from "node:fs";
45486
- import { join as join19 } from "node:path";
46235
+ import { join as join20 } from "node:path";
45487
46236
  async function iconIssues(dir, strict, opts = {}) {
45488
- if (!existsSync16(join19(dir, "himalaya.content.json"))) return [];
46237
+ if (!existsSync16(join20(dir, "himalaya.content.json"))) return [];
45489
46238
  const status = await iconStatus(dir);
45490
46239
  const issue2 = (severity, message) => ({
45491
46240
  screen: null,
@@ -45553,13 +46302,13 @@ function fontIssues(dir, strict) {
45553
46302
 
45554
46303
  // src/stores-api-lint.ts
45555
46304
  import { existsSync as existsSync20, readdirSync as readdirSync10, readFileSync as readFileSync22 } from "node:fs";
45556
- import { join as join23 } from "node:path";
46305
+ import { join as join24 } from "node:path";
45557
46306
  function storesV1Issues(contentDir2, strict) {
45558
- const roots = [join23(contentDir2, "tier5-src"), join23(contentDir2, "dev")].filter(existsSync20);
46307
+ const roots = [join24(contentDir2, "tier5-src"), join24(contentDir2, "dev")].filter(existsSync20);
45559
46308
  const files = [];
45560
46309
  const visit = (dir) => {
45561
46310
  for (const entry of readdirSync10(dir, { withFileTypes: true })) {
45562
- const path = join23(dir, entry.name);
46311
+ const path = join24(dir, entry.name);
45563
46312
  if (entry.isDirectory()) visit(path);
45564
46313
  else if (/\.(?:[cm]?[jt]sx?)$/i.test(entry.name)) files.push(path);
45565
46314
  }
@@ -45580,7 +46329,7 @@ init_crawl_layers();
45580
46329
  init_wix_gateway_rules();
45581
46330
  init_mobile_ux_contract();
45582
46331
  import { existsSync as existsSync21, mkdirSync as mkdirSync10, readFileSync as readFileSync23, readdirSync as readdirSync11, writeFileSync as writeFileSync9 } from "node:fs";
45583
- import { join as join24, relative as relative3, resolve as resolve15 } from "node:path";
46332
+ import { join as join25, relative as relative3, resolve as resolve15 } from "node:path";
45584
46333
  var EVIDENCE_RELATIVE_PATH = ".himi/ux-audit.json";
45585
46334
  var SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".json"]);
45586
46335
  var IGNORED_DIRECTORIES = /* @__PURE__ */ new Set([".git", "node_modules", "dist", ".himi", "coverage"]);
@@ -45598,12 +46347,12 @@ function walkSource(dir, root = dir) {
45598
46347
  const found = [];
45599
46348
  for (const entry of readdirSync11(dir, { withFileTypes: true })) {
45600
46349
  if (entry.isDirectory()) {
45601
- if (!IGNORED_DIRECTORIES.has(entry.name)) found.push(...walkSource(join24(dir, entry.name), root));
46350
+ if (!IGNORED_DIRECTORIES.has(entry.name)) found.push(...walkSource(join25(dir, entry.name), root));
45602
46351
  continue;
45603
46352
  }
45604
46353
  const ext = entry.name.slice(entry.name.lastIndexOf("."));
45605
46354
  if (!SOURCE_EXTENSIONS.has(ext)) continue;
45606
- const absolute = join24(dir, entry.name);
46355
+ const absolute = join25(dir, entry.name);
45607
46356
  const text2 = readText(absolute);
45608
46357
  if (text2 !== null) found.push({ path: relative3(root, absolute), text: text2 });
45609
46358
  }
@@ -45633,9 +46382,9 @@ function usesAppNetwork(source) {
45633
46382
  function auditUx(content, strict = false) {
45634
46383
  const dir = resolve15(content);
45635
46384
  const issues = [];
45636
- const evidencePath = join24(dir, EVIDENCE_RELATIVE_PATH);
45637
- const spec = readText(join24(dir, "SPEC.md"));
45638
- const mobile = readText(join24(dir, "MOBILE-UX.md"));
46385
+ const evidencePath = join25(dir, EVIDENCE_RELATIVE_PATH);
46386
+ const spec = readText(join25(dir, "SPEC.md"));
46387
+ const mobile = readText(join25(dir, "MOBILE-UX.md"));
45639
46388
  if (!spec) issue(issues, strict, "missing-spec", "SPEC.md is missing; define the customer task before calling this UX-ready.", "SPEC.md");
45640
46389
  if (!mobile) {
45641
46390
  issue(issues, strict, "missing-mobile-ux", "MOBILE-UX.md is missing; add the mobile state and platform contract.", "MOBILE-UX.md");
@@ -45674,7 +46423,7 @@ function auditUx(content, strict = false) {
45674
46423
  const preview = matches(previewControl);
45675
46424
  if (preview) issue(issues, strict, "possible-preview-control", "Possible customer-visible test/demo state control found. Keep scenario controls out of shipping UI, or document a capability-limited fallback.", preview.path);
45676
46425
  if (usesAppNetwork(source)) {
45677
- const mocksPath = join24(dir, "dev", "net-mocks.json");
46426
+ const mocksPath = join25(dir, "dev", "net-mocks.json");
45678
46427
  if (!existsSync21(mocksPath)) {
45679
46428
  issue(issues, strict, "missing-app-mocks", "This package uses a Wix/network client but has no app-specific dev/net-mocks.json. Add fixtures for dynamic, entitlement, or recovery flows.", "dev/net-mocks.json");
45680
46429
  } else {
@@ -45696,12 +46445,12 @@ function recordNativeAudit(content, platform, status, checks, reason) {
45696
46445
  if (!normalizedChecks.length) throw new Error("--checks needs at least one comma-separated check (for example: back,keyboard,voiceover)");
45697
46446
  if (status === "unavailable" && !reason?.trim()) throw new Error("--reason is required when --status unavailable");
45698
46447
  const dir = resolve15(content);
45699
- const path = join24(dir, EVIDENCE_RELATIVE_PATH);
46448
+ const path = join25(dir, EVIDENCE_RELATIVE_PATH);
45700
46449
  const parsed = parseEvidence(path);
45701
46450
  if (parsed.error) throw new Error(`${EVIDENCE_RELATIVE_PATH} ${parsed.error}`);
45702
46451
  const evidence = parsed.evidence ?? { version: 1, platforms: {} };
45703
46452
  evidence.platforms[platform] = { status, checks: normalizedChecks, ...reason?.trim() ? { reason: reason.trim() } : {}, recordedAt: (/* @__PURE__ */ new Date()).toISOString() };
45704
- mkdirSync10(join24(dir, ".himi"), { recursive: true });
46453
+ mkdirSync10(join25(dir, ".himi"), { recursive: true });
45705
46454
  writeFileSync9(path, JSON.stringify(evidence, null, 2) + "\n");
45706
46455
  return evidence;
45707
46456
  }
@@ -45710,26 +46459,26 @@ function recordNativeAudit(content, platform, status, checks, reason) {
45710
46459
  init_mobile_ux_contract();
45711
46460
  import { readFileSync as readFileSync24, existsSync as existsSync22, mkdirSync as mkdirSync11, writeFileSync as writeFileSync10, copyFileSync as copyFileSync2 } from "node:fs";
45712
46461
  import { fileURLToPath as fileURLToPath10 } from "node:url";
45713
- import { dirname as dirname15, join as join25, resolve as resolve16 } from "node:path";
46462
+ import { dirname as dirname15, join as join26, resolve as resolve16 } from "node:path";
45714
46463
  var GUIDES_DIR = resolve16(dirname15(fileURLToPath10(import.meta.url)), "../guides");
45715
46464
  function guidesAvailable() {
45716
- return existsSync22(join25(GUIDES_DIR, "index.json"));
46465
+ return existsSync22(join26(GUIDES_DIR, "index.json"));
45717
46466
  }
45718
46467
  function loadIndex() {
45719
- return JSON.parse(readFileSync24(join25(GUIDES_DIR, "index.json"), "utf8"));
46468
+ return JSON.parse(readFileSync24(join26(GUIDES_DIR, "index.json"), "utf8"));
45720
46469
  }
45721
46470
  function readGuide(id) {
45722
46471
  const meta = loadIndex().guides.find((g) => g.id === id);
45723
46472
  if (!meta) return null;
45724
- return readFileSync24(join25(GUIDES_DIR, meta.file), "utf8");
46473
+ return readFileSync24(join26(GUIDES_DIR, meta.file), "utf8");
45725
46474
  }
45726
46475
  function loadComponentReference() {
45727
46476
  const idx = loadIndex();
45728
- return JSON.parse(readFileSync24(join25(GUIDES_DIR, idx.componentReferenceFile), "utf8"));
46477
+ return JSON.parse(readFileSync24(join26(GUIDES_DIR, idx.componentReferenceFile), "utf8"));
45729
46478
  }
45730
46479
  function loadSdkReference() {
45731
46480
  const idx = loadIndex();
45732
- return JSON.parse(readFileSync24(join25(GUIDES_DIR, idx.sdkReferenceFile), "utf8"));
46481
+ return JSON.parse(readFileSync24(join26(GUIDES_DIR, idx.sdkReferenceFile), "utf8"));
45733
46482
  }
45734
46483
  function buildAgentPrimer() {
45735
46484
  const idx = loadIndex();
@@ -45837,19 +46586,19 @@ function writeAgentContext(targetDir) {
45837
46586
  const idx = loadIndex();
45838
46587
  const files = [];
45839
46588
  const write2 = (rel, body) => {
45840
- const p = join25(targetDir, rel);
46589
+ const p = join26(targetDir, rel);
45841
46590
  mkdirSync11(dirname15(p), { recursive: true });
45842
46591
  writeFileSync10(p, body);
45843
46592
  files.push(rel);
45844
46593
  };
45845
46594
  const copy = (srcRel, destRel) => {
45846
- const p = join25(targetDir, destRel);
46595
+ const p = join26(targetDir, destRel);
45847
46596
  mkdirSync11(dirname15(p), { recursive: true });
45848
- copyFileSync2(join25(GUIDES_DIR, srcRel), p);
46597
+ copyFileSync2(join26(GUIDES_DIR, srcRel), p);
45849
46598
  files.push(destRel);
45850
46599
  };
45851
46600
  const writeIfMissing = (rel, body) => {
45852
- if (existsSync22(join25(targetDir, rel))) return;
46601
+ if (existsSync22(join26(targetDir, rel))) return;
45853
46602
  write2(rel, body);
45854
46603
  };
45855
46604
  write2("HIMALAYA.md", buildAgentPrimer());
@@ -45877,7 +46626,7 @@ function writeAgentContext(targetDir) {
45877
46626
  // src/cli.ts
45878
46627
  init_wix_placeholders();
45879
46628
  var defaultIO = { log: (l) => console.log(l), error: (l) => console.error(l), isTTY: process.stdout.isTTY === true };
45880
- var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["browser", "no-open", "yes", "json", "deep", "quiet", "force", "watch", "no-claim", "new"]);
46629
+ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["browser", "no-open", "yes", "json", "deep", "quiet", "force", "replace", "watch", "no-claim", "new"]);
45881
46630
  var SHELL_ENUM = SHELL_NAMES2.join("|");
45882
46631
  function parseFlags(argv) {
45883
46632
  const _ = [];
@@ -45910,7 +46659,7 @@ var str5 = (v) => typeof v === "string" ? v : void 0;
45910
46659
  var num = (v) => typeof v === "string" && v !== "" && Number.isFinite(Number(v)) ? Number(v) : void 0;
45911
46660
  function embeddedTemplateCatalog() {
45912
46661
  return availableTemplates().map((name) => {
45913
- const path = join48(templateSourceDir(name), TEMPLATE_META_FILE);
46662
+ const path = join49(templateSourceDir(name), TEMPLATE_META_FILE);
45914
46663
  const meta = JSON.parse(readFileSync63(path, "utf8"));
45915
46664
  if (!meta || typeof meta.title !== "string" || typeof meta.description !== "string" || !Array.isArray(meta.tags)) {
45916
46665
  throw new Error(`${path} must be { title: string, description: string, tags: string[], category?: string }`);
@@ -46066,7 +46815,7 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
46066
46815
  Never archives .himi/, .env*, node_modules/, dist/ or key
46067
46816
  material, and REFUSES the push if a file it would archive
46068
46817
  looks like it holds a credential.
46069
- himi pull <app> [--release <id>] [--out <dir>] [--name <new>] [--force] [--target <t>]
46818
+ himi pull <app> [--release <id>] [--out <dir>] [--name <new>] [--force|--replace] [--target <t>]
46070
46819
  restore an app's SOURCE from the serve plane into an editable
46071
46820
  content package \u2014 the other half of push, which archives it.
46072
46821
  Owner or editor only: a viewer may download the running app,
@@ -46074,6 +46823,14 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
46074
46823
  retention window prune() keeps (current + newest ~20).
46075
46824
  --name re-substitutes the app identity, so the restored tree is
46076
46825
  a NEW app rather than a second copy of the same one.
46826
+ A non-empty destination is refused unless you say which you mean:
46827
+ --force MERGES \u2014 it keeps files the release does not have and
46828
+ NAMES them (left: [...]), so a hybrid tree is never silent.
46829
+ --replace restores exactly what was archived: it removes those
46830
+ files first (removed: [...]) and implies --force. It refuses
46831
+ a directory with no himalaya.content.json, and can only remove
46832
+ what a push would have archived \u2014 .git/, node_modules/, dist/,
46833
+ .env* and key material are never touched.
46077
46834
  himi pack [--content <dir>] [--out <file|dir>] [--format zip|dir] [--name <title>] [--snapshot]
46078
46835
  write the app as a PORTABLE .himi package \u2014 one self-contained
46079
46836
  file (screens, T5 bundles, tokens, assets, app-config) you can
@@ -46321,7 +47078,7 @@ var COMMANDS = [
46321
47078
  { name: "doctor", summary: 'off-VPN preflight in one shot: probe the PUBLIC data plane (www.wixapis.com \u2014 reachable off-VPN), the INTERNAL serve/control plane for the selected target (VPN-gated), and report auth state (dpx session on disk + stored control token). --dpx additionally runs a live, EADDRINUSE-safe `dpx whoami`. Machine-readable with --json (an unreachable internal plane carries a stable code:"needs_vpn" so an agent branches on the code, not the prose); human-readable otherwise. Never loads the authoring runtime \u2014 works off-repo.', required: "[--target <t>] [--dpx] [--json] [--data-url <url>] [--timeout <ms>]" },
46322
47079
  { name: "push", summary: "lint + build + publish a release (default --draft: servable via ?release= but NOT live). REFUSES to publish on any error unless --force; --strict promotes warnings. Prints preview + agent-context links. The published slug is NAMESPACED per owner \u2014 `<ownerPrefix>.<name>`, composed offline from the control token's ownerPrefix claim, so the CLI and the server derive the identical string and himalaya.content.json is NOT rewritten (a legacy token with no ownerPrefix publishes the declared name unchanged). An app claimed BEFORE namespacing is stored under its FLAT slug, so the claim route answers `alreadyYours` naming that slug and the push adopts it \u2014 reported as `adoptedSlug`, since it is the one case where the published `app` differs from what the declared name composes to. `--no-claim` skips the route that knows this, so a pre-namespacing app pushed with it still fails 403. --visibility public|unlisted decides whether /v1/apps + the Himi Player picker LIST the app (also declarable as `visibility` in himalaya.content.json; omitted, a brand-new app defaults to unlisted \u2014 share by link \u2014 and an existing setting is kept). --account-id names which account owns the app when your Wix session resolves to several (the auto-claim of a brand-new slug cannot guess \u2014 without it a multi-account caller is told to name one).", required: "[--content <dir>] [--draft] [--force] [--strict] [--label] [--notes] [--visibility public|unlisted] [--no-claim] [--account-id <id>] (--control-token)" },
46323
47080
  { name: "pack", summary: "write the app as a PORTABLE .himi package \u2014 one self-contained file holding every screen, Tier-5 bundle, token, asset and the app-config. Packages are live by default: they boot bundled content offline, then Himi Player follows its baked-in production ring. `--snapshot` makes a frozen reproducible artifact. Package-supplied origins are never trusted. --format dir writes the same tree unpacked, for static hosting. --release-dir packs a release that is already built on disk (needs --app) instead of rebuilding from source.", required: "[--content <dir>] [--out <file|dir>] [--format zip|dir] [--name <title>] [--snapshot] [--strict] [--force] | --release-dir <dir> --app <a>" },
46324
- { name: "pull", summary: "restore an app's SOURCE from the serve plane into an editable content package \u2014 the other half of `himi push`, which archives the content package on every publish. Owner or editor only: a viewer may download the RUNNING app (`/v1/owner/package`), not the codebase it was built from. A PAST release is restorable, not only the live one \u2014 the archive is content-addressed per release, so it has none of the live-single-slot problem that makes `himi pack` current-release-only. Two limits: pruning keeps the current release plus the newest ~20 ledger entries, and a release id identifies OTA content rather than source, so a source-only change republishes under the same id and replaces that id's archive. A release published before source archiving shipped, or pushed with `--no-source`, answers `app_source_not_found` and says to push again. `--name` re-substitutes the app identity through the same machinery `himi init --template` uses, so the restored tree is a NEW app rather than a second copy of the same one, and a name that also appears inside a screen id (`feed` in `feed_home`) is left alone.", required: "<app> [--release <id>] [--out <dir>] [--name <new>] [--force] [--target <t>]" },
47081
+ { name: "pull", summary: "restore an app's SOURCE from the serve plane into an editable content package \u2014 the other half of `himi push`, which archives the content package on every publish. Owner or editor only: a viewer may download the RUNNING app (`/v1/owner/package`), not the codebase it was built from. A PAST release is restorable, not only the live one \u2014 the archive is content-addressed per release, so it has none of the live-single-slot problem that makes `himi pack` current-release-only. Two limits: pruning keeps the current release plus the newest ~20 ledger entries, and a release id identifies OTA content rather than source, so a source-only change republishes under the same id and replaces that id's archive. A release published before source archiving shipped, or pushed with `--no-source`, answers `app_source_not_found` and says to push again. `--name` re-substitutes the app identity through the same machinery `himi init --template` uses, so the restored tree is a NEW app rather than a second copy of the same one, and a name that also appears inside a screen id (`feed` in `feed_home`) is left alone. A NON-EMPTY destination is refused unless you say which you mean: `--force` merges and reports \u2014 it writes the archive over what is there, keeps files the release does not carry, and NAMES every one of them as `left: [...]` so a tree that matches no release is never silent (#2437); `--replace` restores exactly what was archived, removing those files first (`removed: [...]`) and implying `--force`. `--replace` refuses a directory holding no `himalaya.content.json`, so a mistyped `--out` cannot clear an unrelated tree, and the only paths it can remove are the ones a push would have archived \u2014 `.git/`, `node_modules/`, `dist/`, `.env*` and key material are out of its reach by construction.", required: "<app> [--release <id>] [--out <dir>] [--name <new>] [--force|--replace] [--target <t>]" },
46325
47082
  { name: "share", summary: "publish or revoke a content-addressed `.himi` share link. The package must be a validated archive for an already-published release; the resulting https URL is safe to QR, copy, or open in Himi Player.", required: "--file <package.himi> [--expires-at <ISO>] | revoke --app <app> <shareId>" },
46326
47083
  { name: "run", summary: "serve a .himi package on localhost and open it in a browser \u2014 no serve plane, no dev-server, no setup. Given no argument it builds the current content package first, so `himi run` is the inner loop: edit, run, look at it. The SPA and /v1 are served from ONE origin because Tier-5 bundle fetches on web are same-origin-only. --chrome preview frames the app in a device chassis; --chrome app serves it as a plain web app. Blocks until Ctrl-C, like any dev server. --browser runs the whole plane INSIDE the tab (service worker + in-memory store) so no app code executes in this process or on any pod.", required: "[<package.himi|dir|url>] [--browser] [--content <dir>] [--port <n>] [--chrome preview|app] [--screen <id>] [--surface <id>] [--no-open] [--spa <url>]" },
46327
47084
  { name: "preview", summary: "print the shareable web URL + himi:// deep link + agent /v1/preview JSON URLs for an app+release; --surface frames the web preview as any catalog surface (?surface=)", required: "--app --release [--screen] [--surface]" },
@@ -46375,11 +47132,11 @@ function packageVersion(path) {
46375
47132
  }
46376
47133
  }
46377
47134
  function resolvedSdkVersion(fromDir = process.cwd(), bundledManifest = new URL("./authoring-runtime/node_modules/@wix/himalaya/package.json", import.meta.url)) {
46378
- let dir = resolve39(fromDir);
47135
+ let dir = resolve40(fromDir);
46379
47136
  for (; ; ) {
46380
- const version = packageVersion(join48(dir, "node_modules", "@wix", "himalaya", "package.json"));
47137
+ const version = packageVersion(join49(dir, "node_modules", "@wix", "himalaya", "package.json"));
46381
47138
  if (version) return { version, source: "local" };
46382
- const parent = dirname29(dir);
47139
+ const parent = dirname28(dir);
46383
47140
  if (parent === dir) break;
46384
47141
  dir = parent;
46385
47142
  }
@@ -46387,7 +47144,7 @@ function resolvedSdkVersion(fromDir = process.cwd(), bundledManifest = new URL("
46387
47144
  if (bundled) return { version: bundled, source: "bundled" };
46388
47145
  try {
46389
47146
  const root = execFileSync6("npm", ["root", "-g"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
46390
- const version = root ? packageVersion(join48(root, "@wix", "himalaya", "package.json")) : void 0;
47147
+ const version = root ? packageVersion(join49(root, "@wix", "himalaya", "package.json")) : void 0;
46391
47148
  return version ? { version, source: "global" } : {};
46392
47149
  } catch {
46393
47150
  return {};
@@ -46748,7 +47505,7 @@ async function nativeConfigAdvisories(app, configSource) {
46748
47505
  const { iosProjectYml: iosProjectYml2 } = await Promise.resolve().then(() => (init_paths(), paths_exports));
46749
47506
  const { androidOptedIn: androidOptedIn3, LABEL_IOS_SPLASH: LABEL_IOS_SPLASH2 } = await Promise.resolve().then(() => (init_splash(), splash_exports));
46750
47507
  const iosYmlPath = iosProjectYml2(app);
46751
- const iosYml = existsSync50(iosYmlPath) ? readFileSync63(iosYmlPath, "utf8") : "";
47508
+ const iosYml = existsSync51(iosYmlPath) ? readFileSync63(iosYmlPath, "utf8") : "";
46752
47509
  let optedIn = iosYml.includes(LABEL_IOS_SPLASH2);
46753
47510
  if (!optedIn) {
46754
47511
  const { androidManifestPath: androidManifestPath3 } = await Promise.resolve().then(() => (init_gen_native_config(), gen_native_config_exports));
@@ -46762,19 +47519,19 @@ async function nativeConfigAdvisories(app, configSource) {
46762
47519
  }
46763
47520
  function readConfigSource(dir) {
46764
47521
  try {
46765
- return readFileSync63(join48(dir, "config.ts"), "utf8");
47522
+ return readFileSync63(join49(dir, "config.ts"), "utf8");
46766
47523
  } catch {
46767
47524
  return null;
46768
47525
  }
46769
47526
  }
46770
47527
  function writePngFile(path, base64) {
46771
- mkdirSync25(dirname29(path), { recursive: true });
47528
+ mkdirSync25(dirname28(path), { recursive: true });
46772
47529
  writeFileSync23(path, Buffer.from(base64, "base64"));
46773
47530
  return path;
46774
47531
  }
46775
47532
  async function initFromRemoteTemplate(name, parentDir, template, c) {
46776
- const dir = resolve39(parentDir, name);
46777
- if (existsSync50(join48(dir, "himalaya.content.json"))) {
47533
+ const dir = resolve40(parentDir, name);
47534
+ if (existsSync51(join49(dir, "himalaya.content.json"))) {
46778
47535
  throw new Error(`content package already exists at ${dir}`);
46779
47536
  }
46780
47537
  const r = await c.fetchTemplateSource(TEMPLATE_APP_PREFIX + template);
@@ -46786,13 +47543,13 @@ async function initFromRemoteTemplate(name, parentDir, template, c) {
46786
47543
  return finishScaffold(materializeSource(archive.files, name, dir), template);
46787
47544
  }
46788
47545
  function contentDir(flags) {
46789
- return resolve39(str5(flags.content) ?? process.cwd());
47546
+ return resolve40(str5(flags.content) ?? process.cwd());
46790
47547
  }
46791
47548
  function isHimalayaMonorepoCheckout(start) {
46792
- let dir = resolve39(start);
47549
+ let dir = resolve40(start);
46793
47550
  for (; ; ) {
46794
- if (existsSync50(join48(dir, "tools", "himi-cli", "src", "cli.ts")) && existsSync50(join48(dir, "core", "serve", "src", "server.ts"))) return true;
46795
- const parent = dirname29(dir);
47551
+ if (existsSync51(join49(dir, "tools", "himi-cli", "src", "cli.ts")) && existsSync51(join49(dir, "core", "serve", "src", "server.ts"))) return true;
47552
+ const parent = dirname28(dir);
46796
47553
  if (parent === dir) return false;
46797
47554
  dir = parent;
46798
47555
  }
@@ -46821,9 +47578,9 @@ function materializeForPreview(appDir2, slug, displayName) {
46821
47578
  const forBuild = Object.fromEntries(
46822
47579
  Object.entries(files).map(([rel, body]) => [rel, rel === "himalaya.content.json" ? substituteIdentity(body, slug) : body])
46823
47580
  );
46824
- const repoRoot2 = resolve39(fileURLToPath21(new URL("../../..", import.meta.url)));
46825
- const dest = join48(repoRoot2, "node_modules", ".cache", "himi-template-preview", slug);
46826
- rmSync8(dest, { recursive: true, force: true });
47581
+ const repoRoot2 = resolve40(fileURLToPath21(new URL("../../..", import.meta.url)));
47582
+ const dest = join49(repoRoot2, "node_modules", ".cache", "himi-template-preview", slug);
47583
+ rmSync9(dest, { recursive: true, force: true });
46827
47584
  mkdirSync25(dest, { recursive: true });
46828
47585
  materializeSource(forBuild, displayName, dest, slug);
46829
47586
  return dest;
@@ -46831,7 +47588,7 @@ function materializeForPreview(appDir2, slug, displayName) {
46831
47588
  async function appId(dir) {
46832
47589
  try {
46833
47590
  const { readFileSync: readFileSync64 } = await import("node:fs");
46834
- const m = JSON.parse(readFileSync64(join48(dir, "himalaya.content.json"), "utf8"));
47591
+ const m = JSON.parse(readFileSync64(join49(dir, "himalaya.content.json"), "utf8"));
46835
47592
  if (m.name) return m.name;
46836
47593
  } catch {
46837
47594
  }
@@ -46840,7 +47597,7 @@ async function appId(dir) {
46840
47597
  async function contentVisibility(dir) {
46841
47598
  try {
46842
47599
  const { readFileSync: readFileSync64 } = await import("node:fs");
46843
- const m = JSON.parse(readFileSync64(join48(dir, "himalaya.content.json"), "utf8"));
47600
+ const m = JSON.parse(readFileSync64(join49(dir, "himalaya.content.json"), "utf8"));
46844
47601
  if (m.visibility === "public" || m.visibility === "unlisted") return m.visibility;
46845
47602
  } catch {
46846
47603
  }
@@ -47041,7 +47798,7 @@ Seed your coding agent with \`himi agent-init\`. Portal: ${idx.portalUrl}`);
47041
47798
  return 0;
47042
47799
  }
47043
47800
  case "agent-init": {
47044
- const dir = resolve39(str5(flags.dir) ?? process.cwd());
47801
+ const dir = resolve40(str5(flags.dir) ?? process.cwd());
47045
47802
  const res = writeAgentContext(dir);
47046
47803
  print({ ok: true, dir: res.dir, files: res.files, portalUrl: idx.portalUrl });
47047
47804
  return 0;
@@ -47079,6 +47836,7 @@ async function loadAuthoring() {
47079
47836
  crawlScreen: crawl.crawlScreen,
47080
47837
  crawlScreenJourney: crawl.crawlScreenJourney,
47081
47838
  crawlScreenHostile: crawl.crawlScreenHostile,
47839
+ crawlScreenRelations: crawl.crawlScreenRelations,
47082
47840
  findingKey: crawl.findingKey,
47083
47841
  startWorkerCoverage: cov.startWorkerCoverage,
47084
47842
  uncoveredFunctions: cov.uncoveredFunctions,
@@ -47146,7 +47904,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
47146
47904
  return 2;
47147
47905
  }
47148
47906
  const template = str5(flags.template);
47149
- const parentDir = resolve39(str5(flags.dir) ?? process.cwd());
47907
+ const parentDir = resolve40(str5(flags.dir) ?? process.cwd());
47150
47908
  if (template && flags.remote === true) {
47151
47909
  try {
47152
47910
  const res2 = await initFromRemoteTemplate(name, parentDir, template, client3());
@@ -47237,11 +47995,11 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
47237
47995
  return 0;
47238
47996
  }
47239
47997
  if (sub === "publish") {
47240
- const appDir2 = resolve39(str5(flags.dir) ?? process.cwd());
47241
- const sourceDir = resolve39(str5(flags["source-dir"]) ?? appDir2);
47998
+ const appDir2 = resolve40(str5(flags.dir) ?? process.cwd());
47999
+ const sourceDir = resolve40(str5(flags["source-dir"]) ?? appDir2);
47242
48000
  let meta;
47243
48001
  try {
47244
- meta = JSON.parse(readFileSync63(join48(sourceDir, TEMPLATE_META_FILE), "utf8"));
48002
+ meta = JSON.parse(readFileSync63(join49(sourceDir, TEMPLATE_META_FILE), "utf8"));
47245
48003
  } catch {
47246
48004
  io.error(JSON.stringify({ error: `no ${TEMPLATE_META_FILE} in ${sourceDir} \u2014 a template must declare { title, description, tags } to be published`, hint: `add ${TEMPLATE_META_FILE} next to himalaya.content.json (or pass --source-dir <dir>)` }));
47247
48005
  return 2;
@@ -47251,7 +48009,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
47251
48009
  return 2;
47252
48010
  }
47253
48011
  try {
47254
- const appearance = extractTemplateAppearance(JSON.parse(readFileSync63(join48(sourceDir, "tokens.json"), "utf8")));
48012
+ const appearance = extractTemplateAppearance(JSON.parse(readFileSync63(join49(sourceDir, "tokens.json"), "utf8")));
47255
48013
  meta = { ...appearance, ...meta };
47256
48014
  } catch {
47257
48015
  }
@@ -47263,7 +48021,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
47263
48021
  const force = flags.force === true;
47264
48022
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
47265
48023
  const bres = await buildContentForValidation2(buildDir, { resolution: resolutionFlag(flags) });
47266
- const out = mkdtempSync4(join48(tmpdir5(), "himi-template-"));
48024
+ const out = mkdtempSync4(join49(tmpdir5(), "himi-template-"));
47267
48025
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
47268
48026
  const outcome = await buildReleaseFromContent2(buildDir, out, {
47269
48027
  ...hasWorkers ? { tier5Dir: bundleOutDir2(buildDir) } : {},
@@ -47292,7 +48050,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
47292
48050
  }));
47293
48051
  return 1;
47294
48052
  }
47295
- const iconPath = str5(flags.icon) ? resolve39(str5(flags.icon)) : templateIconPath(name);
48053
+ const iconPath = str5(flags.icon) ? resolve40(str5(flags.icon)) : templateIconPath(name);
47296
48054
  let iconHash;
47297
48055
  if (iconPath) {
47298
48056
  try {
@@ -47422,7 +48180,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
47422
48180
  const { mobileUxIssues: mobileUxIssues2 } = await Promise.resolve().then(() => (init_mobile_ux_lint(), mobile_ux_lint_exports));
47423
48181
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
47424
48182
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
47425
- const out = mkdtempSync4(join48(tmpdir5(), "himi-validate-"));
48183
+ const out = mkdtempSync4(join49(tmpdir5(), "himi-validate-"));
47426
48184
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
47427
48185
  const outcome = await buildReleaseFromContent2(dir, out, {
47428
48186
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -47610,7 +48368,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
47610
48368
  case "test": {
47611
48369
  const dir = contentDir(flags);
47612
48370
  const structuredOutput = flags.json === true || io.isTTY !== true;
47613
- if (existsSync50(join48(dir, "functions"))) {
48371
+ if (existsSync51(join49(dir, "functions"))) {
47614
48372
  const { invokeLocalFunction: invokeLocalFunction2, loadFunctionsApp: loadFunctionsApp2 } = await Promise.resolve().then(() => (init_functions(), functions_exports));
47615
48373
  const loaded = await loadFunctionsApp2(dir);
47616
48374
  const results = [];
@@ -47624,7 +48382,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
47624
48382
  return results.every((result2) => result2.ok) ? 0 : 1;
47625
48383
  }
47626
48384
  const app = await appId(dir);
47627
- const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2, crawlScreen: crawlScreen2, crawlScreenJourney: crawlScreenJourney2, crawlScreenHostile: crawlScreenHostile2, findingKey: findingKey2, startWorkerCoverage: startWorkerCoverage2, uncoveredFunctions: uncoveredFunctions2, crawlScreenDeep: crawlScreenDeep2, crawlSignature: crawlSignature2, recordScreenTraffic: recordScreenTraffic2, mintVisitor: mintVisitor2, webWixCall: webWixCall2, freezeTrafficToMocks: freezeTrafficToMocks2, bootAndSnapshotState: bootAndSnapshotState2, renderDescriptorWeb: renderDescriptorWeb2, renderPngBatch: renderPngBatch2, pngAvailable: pngAvailable2, crawlBlockingPasses } = await loadAuthoring();
48385
+ const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2, crawlScreen: crawlScreen2, crawlScreenJourney: crawlScreenJourney2, crawlScreenHostile: crawlScreenHostile2, crawlScreenRelations: crawlScreenRelations2, findingKey: findingKey2, startWorkerCoverage: startWorkerCoverage2, uncoveredFunctions: uncoveredFunctions2, crawlScreenDeep: crawlScreenDeep2, crawlSignature: crawlSignature2, recordScreenTraffic: recordScreenTraffic2, mintVisitor: mintVisitor2, webWixCall: webWixCall2, freezeTrafficToMocks: freezeTrafficToMocks2, bootAndSnapshotState: bootAndSnapshotState2, renderDescriptorWeb: renderDescriptorWeb2, renderPngBatch: renderPngBatch2, pngAvailable: pngAvailable2, crawlBlockingPasses } = await loadAuthoring();
47628
48386
  const { readTestConfig: readTestConfig2, runCrawl: runCrawl2, formatReport: formatReport2, readCannedResponses: readCannedResponses2, makeRecordingWixCall: makeRecordingWixCall2, runRecordPass: runRecordPass2, runShotsPass: runShotsPass2, defaultNetRules: defaultNetRules2, readNetRules: readNetRules2, scopeDescriptors: scopeDescriptors2, writeRecordedMocks: writeRecordedMocks2 } = await Promise.resolve().then(() => (init_test(), test_exports));
47629
48387
  let config;
47630
48388
  try {
@@ -47697,7 +48455,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
47697
48455
  }
47698
48456
  }
47699
48457
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
47700
- const out = mkdtempSync4(join48(tmpdir5(), "himi-test-"));
48458
+ const out = mkdtempSync4(join49(tmpdir5(), "himi-test-"));
47701
48459
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
47702
48460
  const outcome = await buildReleaseFromContent2(dir, out, {
47703
48461
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -47742,7 +48500,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
47742
48500
  io.error(JSON.stringify({ error: `screen "${screen.screenId}" has no Tier-5 worker, so there is nothing for the browser loop to build` }));
47743
48501
  return 2;
47744
48502
  }
47745
- const sources = collectWorkerSources2(join48(dir, "tier5-src"), screen.bundleName);
48503
+ const sources = collectWorkerSources2(join49(dir, "tier5-src"), screen.bundleName);
47746
48504
  if (!sources) {
47747
48505
  io.error(JSON.stringify({ error: `no worker source found at tier5-src/${screen.bundleName} \u2014 the browser loop compiles SOURCE, not the built bundle` }));
47748
48506
  return 2;
@@ -47862,7 +48620,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
47862
48620
  ...deep ? { deep } : {},
47863
48621
  ...offline ? { offline: true } : {},
47864
48622
  ...offline || flags["no-hostile"] === true ? { hostile: false } : {},
47865
- deps: { crawlScreen: crawlScreen2, crawlScreenJourney: crawlScreenJourney2, crawlScreenHostile: crawlScreenHostile2, findingKey: findingKey2, startWorkerCoverage: startWorkerCoverage2, uncoveredFunctions: uncoveredFunctions2, crawlScreenDeep: crawlScreenDeep2, crawlSignature: crawlSignature2 }
48623
+ deps: { crawlScreen: crawlScreen2, crawlScreenJourney: crawlScreenJourney2, crawlScreenHostile: crawlScreenHostile2, crawlScreenRelations: crawlScreenRelations2, findingKey: findingKey2, startWorkerCoverage: startWorkerCoverage2, uncoveredFunctions: uncoveredFunctions2, crawlScreenDeep: crawlScreenDeep2, crawlSignature: crawlSignature2 }
47866
48624
  });
47867
48625
  } catch (e) {
47868
48626
  io.error(JSON.stringify({ error: e.message }));
@@ -47899,7 +48657,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
47899
48657
  const sp = await runShotsPass2({
47900
48658
  descriptors: scopeDescriptors2(outcome.descriptors ?? [], pos.slice(1), invalidScreens),
47901
48659
  tokens: outcome.tokens,
47902
- outDir: join48(dir, ".himi", "shots"),
48660
+ outDir: join49(dir, ".himi", "shots"),
47903
48661
  width: 390,
47904
48662
  layers: { rules: shotRules, ...cmsSeed ? { cmsSeed } : {}, ...canned ? { canned: canned.byKey } : {} },
47905
48663
  ...budgetMs ? { budgetMs } : {},
@@ -47909,7 +48667,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
47909
48667
  renderDescriptorWeb: renderDescriptorWeb2,
47910
48668
  renderPngBatch: renderPngBatch2,
47911
48669
  writePng: (path, base64) => writePngFile(path, base64),
47912
- clearDir: (d) => rmSync8(d, { recursive: true, force: true })
48670
+ clearDir: (d) => rmSync9(d, { recursive: true, force: true })
47913
48671
  }
47914
48672
  });
47915
48673
  writtenShots = sp.written;
@@ -47935,7 +48693,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
47935
48693
  releaseId: result.releaseId,
47936
48694
  releasePayload: release,
47937
48695
  descriptors: attestedDescriptors,
47938
- screenshots: Object.fromEntries(writtenShots.filter((name) => crawledScreens.has(name.replace(/\.png$/, ""))).map((name) => [name.replace(/\.png$/, ""), join48(dir, ".himi", "shots", name)])),
48696
+ screenshots: Object.fromEntries(writtenShots.filter((name) => crawledScreens.has(name.replace(/\.png$/, ""))).map((name) => [name.replace(/\.png$/, ""), join49(dir, ".himi", "shots", name)])),
47939
48697
  coverage: {
47940
48698
  screens: crawledScreens.size,
47941
48699
  actionsDispatched: result.actions.exercised,
@@ -48077,7 +48835,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
48077
48835
  }
48078
48836
  const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
48079
48837
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
48080
- const out = mkdtempSync4(join48(tmpdir5(), "himi-render-"));
48838
+ const out = mkdtempSync4(join49(tmpdir5(), "himi-render-"));
48081
48839
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
48082
48840
  const outcome = await buildReleaseFromContent2(dir, out, {
48083
48841
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -48093,7 +48851,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
48093
48851
  let attestationPngFile;
48094
48852
  if (typeof flags.png === "string" && r.pngBase64) {
48095
48853
  pngFile = writePngFile(str5(flags.png), r.pngBase64);
48096
- attestationPngFile = writePngFile(join48(dir, ".himi", "shots", `${screenId}.png`), r.pngBase64);
48854
+ attestationPngFile = writePngFile(join49(dir, ".himi", "shots", `${screenId}.png`), r.pngBase64);
48097
48855
  }
48098
48856
  const legacyRender = {
48099
48857
  ok: r.renderable,
@@ -48398,17 +49156,27 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
48398
49156
  return 2;
48399
49157
  }
48400
49158
  const renamed = str5(flags.name);
48401
- const dest = resolve39(str5(flags.out) ?? process.cwd(), renamed ?? wanted);
48402
- if (existsSync50(dest)) {
48403
- if (!statSync12(dest).isDirectory()) {
49159
+ const dest = resolve40(str5(flags.out) ?? process.cwd(), renamed ?? wanted);
49160
+ const replace = flags.replace === true;
49161
+ const force = replace || flags.force === true;
49162
+ if (existsSync51(dest)) {
49163
+ if (!statSync13(dest).isDirectory()) {
48404
49164
  io.error(JSON.stringify({
48405
49165
  error: `${dest} exists and is not a directory`,
48406
49166
  hint: "pass --out/--name to land somewhere else, or remove the file"
48407
49167
  }));
48408
49168
  return 1;
48409
49169
  }
48410
- if (readdirSync23(dest).length > 0 && flags.force !== true) {
48411
- io.error(JSON.stringify({ error: `${dest} already exists and is not empty`, hint: "pass --force to write into it anyway, or --out/--name to land somewhere else" }));
49170
+ const occupied = readdirSync23(dest).length > 0;
49171
+ if (occupied && !force) {
49172
+ io.error(JSON.stringify({ error: `${dest} already exists and is not empty`, hint: "pass --force to write into it anyway (merging, and naming what it keeps), --replace to restore exactly what was archived, or --out/--name to land somewhere else" }));
49173
+ return 1;
49174
+ }
49175
+ if (replace && occupied && !existsSync51(join49(dest, "himalaya.content.json"))) {
49176
+ io.error(JSON.stringify({
49177
+ error: `${dest} is not a content package \u2014 refusing to --replace it`,
49178
+ hint: "--replace only clears a directory that holds himalaya.content.json. Check --out/--name, or pass --force to merge into it instead."
49179
+ }));
48412
49180
  return 1;
48413
49181
  }
48414
49182
  }
@@ -48417,7 +49185,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
48417
49185
  io.error(JSON.stringify({ ok: false, app: wanted, status: pulled.status, ...pulled.json ?? {} }));
48418
49186
  return 1;
48419
49187
  }
48420
- const { readAppSource: readAppSource2, writeAppSource: writeAppSource2 } = await Promise.resolve().then(() => (init_app_source(), app_source_exports));
49188
+ const { readAppSource: readAppSource2, writeAppSource: writeAppSource2, staleSourcePaths: staleSourcePaths2, removeStaleSource: removeStaleSource2, deniedArchivePaths: deniedArchivePaths2, writeConflicts: writeConflicts2 } = await Promise.resolve().then(() => (init_app_source(), app_source_exports));
48421
49189
  let restored;
48422
49190
  try {
48423
49191
  restored = readAppSource2(pulled.bytes);
@@ -48444,6 +49212,31 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
48444
49212
  if (body !== void 0) entry.bytes = Buffer.from(substituteIdentity(body, renamed), "utf8");
48445
49213
  }
48446
49214
  }
49215
+ if (restored.length === 0) {
49216
+ io.error(JSON.stringify({
49217
+ ok: false,
49218
+ app: wanted,
49219
+ error: "app_source_empty",
49220
+ hint: "the plane returned an archive with no entries \u2014 nothing was changed. Re-push the app, then pull again"
49221
+ }));
49222
+ return 1;
49223
+ }
49224
+ const conflicts = writeConflicts2(restored, dest);
49225
+ if (conflicts.length) {
49226
+ io.error(JSON.stringify({
49227
+ ok: false,
49228
+ app: wanted,
49229
+ error: "destination_shape_conflicts",
49230
+ conflicts,
49231
+ hint: "nothing was changed \u2014 remove or rename those paths, or pass --out/--name to land somewhere else"
49232
+ }));
49233
+ return 1;
49234
+ }
49235
+ let left = [];
49236
+ let removed = [];
49237
+ if (replace) removed = removeStaleSource2(restored, dest);
49238
+ else left = staleSourcePaths2(restored, dest);
49239
+ const refused = deniedArchivePaths2(restored);
48447
49240
  const written = writeAppSource2(restored, dest);
48448
49241
  print({
48449
49242
  ok: true,
@@ -48451,6 +49244,18 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
48451
49244
  dir: dest,
48452
49245
  files: written.length,
48453
49246
  ...renamed ? { renamedTo: renamed } : {},
49247
+ ...removed.length ? { removed } : {},
49248
+ ...refused.length ? {
49249
+ refused,
49250
+ refusedNote: "this archive carries paths a restore will not write (the deny-list has grown since it was published) \u2014 they were skipped, not written over"
49251
+ } : {},
49252
+ // EVERY path, not a count and not a sample: the whole failure was that a hybrid tree
49253
+ // looked like a restore, and a list you have to go and generate yourself is the same
49254
+ // silence with extra steps.
49255
+ ...left.length ? {
49256
+ left,
49257
+ warning: `${left.length} file(s) already in ${dest} are not in this release and were KEPT \u2014 this tree is a merge of both, and matches no release. Remove them, or re-run with --replace.`
49258
+ } : {},
48454
49259
  next: `cd ${dest} && himi validate`
48455
49260
  });
48456
49261
  return 0;
@@ -48473,7 +49278,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
48473
49278
  const force = flags.force === true;
48474
49279
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
48475
49280
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
48476
- const out = mkdtempSync4(join48(tmpdir5(), "himi-push-"));
49281
+ const out = mkdtempSync4(join49(tmpdir5(), "himi-push-"));
48477
49282
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
48478
49283
  const outcome = await buildReleaseFromContent2(dir, out, {
48479
49284
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -48740,7 +49545,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
48740
49545
  return 2;
48741
49546
  }
48742
49547
  try {
48743
- const res = writePack(resolve39(releaseDir), only, resolve39(str5(flags.out) ?? process.cwd()));
49548
+ const res = writePack(resolve40(releaseDir), only, resolve40(str5(flags.out) ?? process.cwd()));
48744
49549
  print({
48745
49550
  ok: true,
48746
49551
  app: res.app,
@@ -48765,7 +49570,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
48765
49570
  const force = flags.force === true;
48766
49571
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
48767
49572
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
48768
- const built = mkdtempSync4(join48(tmpdir5(), "himi-pack-"));
49573
+ const built = mkdtempSync4(join49(tmpdir5(), "himi-pack-"));
48769
49574
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
48770
49575
  const outcome = await buildReleaseFromContent2(dir, built, {
48771
49576
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -48778,12 +49583,12 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
48778
49583
  ...fontIssues(dir, strict)
48779
49584
  ]);
48780
49585
  if (hasErrors && !force) {
48781
- rmSync8(built, { recursive: true, force: true });
49586
+ rmSync9(built, { recursive: true, force: true });
48782
49587
  print({ ...report, packed: false, hint: "lint found errors \u2014 fix them, or re-run `himi pack --force` to pack anyway" });
48783
49588
  return 1;
48784
49589
  }
48785
49590
  try {
48786
- const res = writePack(built, app, resolve39(str5(flags.out) ?? dir));
49591
+ const res = writePack(built, app, resolve40(str5(flags.out) ?? dir));
48787
49592
  print({
48788
49593
  ok: true,
48789
49594
  app: res.app,
@@ -48808,7 +49613,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
48808
49613
  io.error(JSON.stringify({ error: e.message }));
48809
49614
  return 1;
48810
49615
  } finally {
48811
- rmSync8(built, { recursive: true, force: true });
49616
+ rmSync9(built, { recursive: true, force: true });
48812
49617
  }
48813
49618
  }
48814
49619
  // ---- himi share — publish/revoke a content-addressed package link ----
@@ -48836,12 +49641,12 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
48836
49641
  }
48837
49642
  try {
48838
49643
  const { openPackageFile: openPackageFile2 } = await Promise.resolve().then(() => (init_pack(), pack_exports));
48839
- const opened = openPackageFile2(resolve39(file));
49644
+ const opened = openPackageFile2(resolve40(file));
48840
49645
  if (!opened.validation.ok || !opened.header) {
48841
49646
  io.error(JSON.stringify({ error: "refusing to share an invalid package", issues: opened.validation.errors }));
48842
49647
  return 1;
48843
49648
  }
48844
- const archive = readFileSync63(resolve39(file));
49649
+ const archive = readFileSync63(resolve40(file));
48845
49650
  const result = await client3().createPackageShare({
48846
49651
  app: opened.header.app,
48847
49652
  releaseId: opened.header.releaseId,
@@ -48878,10 +49683,10 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
48878
49683
  const app = await appId(dir);
48879
49684
  const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
48880
49685
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
48881
- temp = mkdtempSync4(join48(tmpdir5(), "himi-run-build-"));
49686
+ temp = mkdtempSync4(join49(tmpdir5(), "himi-run-build-"));
48882
49687
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
48883
49688
  await buildReleaseFromContent2(dir, temp, { ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {} });
48884
- source = join48(temp, app);
49689
+ source = join49(temp, app);
48885
49690
  }
48886
49691
  const chromeArg = str5(flags.chrome) ?? "preview";
48887
49692
  if (chromeArg !== "preview" && chromeArg !== "app") {
@@ -48906,9 +49711,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
48906
49711
  open: flags["no-open"] !== true
48907
49712
  });
48908
49713
  print({ ok: true, app: run2.app, url: run2.url, port: run2.port, plane: "browser" });
48909
- await new Promise((resolve40) => {
49714
+ await new Promise((resolve41) => {
48910
49715
  const stop = () => {
48911
- void run2.close().finally(() => resolve40());
49716
+ void run2.close().finally(() => resolve41());
48912
49717
  };
48913
49718
  process.once("SIGINT", stop);
48914
49719
  process.once("SIGTERM", stop);
@@ -48944,7 +49749,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
48944
49749
  io.error(JSON.stringify({ error: e.message }));
48945
49750
  return 1;
48946
49751
  } finally {
48947
- if (temp) rmSync8(temp, { recursive: true, force: true });
49752
+ if (temp) rmSync9(temp, { recursive: true, force: true });
48948
49753
  }
48949
49754
  }
48950
49755
  case "preview": {
@@ -49679,7 +50484,7 @@ ${label2}`);
49679
50484
  const platform = str5(flags.platform)?.split(",").map((p) => p.trim()).filter(Boolean);
49680
50485
  {
49681
50486
  const distDir = contentDir(flags);
49682
- if (existsSync50(join48(distDir, "himalaya.content.json"))) {
50487
+ if (existsSync51(join49(distDir, "himalaya.content.json"))) {
49683
50488
  const issues = await iconIssues(distDir, flags.strict === true, { requireDeclared: true, requireVerified: true });
49684
50489
  const errors = issues.filter((i) => i.severity === "error");
49685
50490
  for (const i of issues.filter((i2) => i2.severity === "warning")) {