@wix/himalaya-cli 0.1068.0 → 0.1070.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
@@ -484,6 +484,36 @@ var init_ownership = __esm({
484
484
  }
485
485
  });
486
486
 
487
+ // ../../core/serve-storage/src/wip-pointer.ts
488
+ function wipSupported(store) {
489
+ if (!store.getWipRecord || !store.updateWipRecord) return false;
490
+ return store.supportsWip ? store.supportsWip() : true;
491
+ }
492
+ function normalizeWipRecord(value) {
493
+ if (!value || typeof value !== "object") return { wip: null, promoted: [], evictedBefore: null };
494
+ const v = value;
495
+ const wip = v.wip && typeof v.wip.releaseId === "string" ? v.wip : null;
496
+ const promoted = (Array.isArray(v.promoted) ? v.promoted : []).flatMap((entry) => {
497
+ if (typeof entry === "string") return [{ id: entry, at: null }];
498
+ const e = entry;
499
+ return e && typeof e.id === "string" ? [{ id: e.id, at: storedTime(e.at) }] : [];
500
+ });
501
+ return { wip, promoted, evictedBefore: storedTime(v.evictedBefore) };
502
+ }
503
+ async function readWip(store, app) {
504
+ if (!wipSupported(store)) return "unsupported";
505
+ const { wip, promoted } = normalizeWipRecord(await store.getWipRecord(app));
506
+ if (!wip || promoted.some((p) => p.id === wip.releaseId)) return null;
507
+ const live2 = await store.getManifest(app);
508
+ return live2?.releaseId === wip.releaseId ? null : wip;
509
+ }
510
+ var storedTime;
511
+ var init_wip_pointer = __esm({
512
+ "../../core/serve-storage/src/wip-pointer.ts"() {
513
+ storedTime = (value) => typeof value === "string" && !Number.isNaN(Date.parse(value)) ? value : null;
514
+ }
515
+ });
516
+
487
517
  // ../../core/serve-storage/src/store.ts
488
518
  function validateFunctionManifest(manifest) {
489
519
  const dataErrors = validateDataResources(manifest.functionDataResources, manifest.app).filter((issue2) => issue2.severity === "error");
@@ -526,6 +556,14 @@ async function retainedReleaseRefs(store, app, keepRecent = 20, keepReleaseIds =
526
556
  }
527
557
  }
528
558
  }
559
+ const offered = await readWip(store, app);
560
+ if (offered && offered !== "unsupported" && !loaded.has(offered.releaseId) && store.getManifestById) {
561
+ const m = await store.getManifestById(app, offered.releaseId);
562
+ if (m) {
563
+ manifests.push(m);
564
+ loaded.add(m.releaseId);
565
+ }
566
+ }
529
567
  for (const releaseId of new Set(keepReleaseIds)) {
530
568
  if (loaded.has(releaseId)) continue;
531
569
  if (!store.getManifestById) throw new Error(`cannot retain function job release ${releaseId}: versioned manifest lookup is unsupported`);
@@ -554,6 +592,7 @@ var TEMPLATE_APP_PREFIX;
554
592
  var init_store = __esm({
555
593
  "../../core/serve-storage/src/store.ts"() {
556
594
  init_ownership();
595
+ init_wip_pointer();
557
596
  TEMPLATE_APP_PREFIX = "himi-template-";
558
597
  }
559
598
  });
@@ -764,34 +803,28 @@ var init_file_store = __esm({
764
803
  else ledger.push(entry);
765
804
  writeFileSync2(this.ledgerPath(app), JSON.stringify(ledger, null, 2));
766
805
  }
767
- // ---- RFC 0061 §3.1 owner-follow `wip` pointer ----
768
- // Inside the app directory, so `remove` and `reset` take it with the app. `clearWipIf` is a CAS
769
- // for the way this backend is deployed — ONE writer process (serve-control; serve only reads):
770
- // the read, compare and delete below are synchronous with no await between them, so no other
806
+ // ---- RFC 0061 §3.1 owner-follow `wip` record ----
807
+ // Inside the app directory, so `remove` and `reset` take it with the app. `updateWipRecord` is a
808
+ // CAS for the way this backend is deployed — ONE writer process (serve-control; serve only
809
+ // reads): the read, `fn` and write below are synchronous with no await between them, so no other
771
810
  // request in that process can land in the middle. It is not a cross-process lock.
772
- wipPath(app) {
773
- return join2(this.appDir(app), "_wip.json");
774
- }
775
811
  readWipSync(app) {
776
812
  const b = this.read(app, "_wip.json");
777
813
  if (!b) return null;
778
814
  try {
779
- const wip = JSON.parse(b.toString("utf8"));
780
- return wip && typeof wip.releaseId === "string" ? wip : null;
815
+ return JSON.parse(b.toString("utf8"));
781
816
  } catch {
782
817
  return null;
783
818
  }
784
819
  }
785
- async putWip(app, wip) {
786
- mkdirSync2(this.appDir(app), { recursive: true });
787
- writeFileSync2(this.wipPath(app), JSON.stringify(wip, null, 2));
788
- }
789
- async getWip(app) {
820
+ async getWipRecord(app) {
790
821
  return this.readWipSync(app);
791
822
  }
792
- async clearWipIf(app, releaseId) {
793
- if (this.readWipSync(app)?.releaseId !== releaseId) return false;
794
- rmSync(this.wipPath(app), { force: true });
823
+ async updateWipRecord(app, fn) {
824
+ const next = fn(this.readWipSync(app));
825
+ if (next === void 0) return false;
826
+ mkdirSync2(this.appDir(app), { recursive: true });
827
+ writeFileSync2(join2(this.appDir(app), "_wip.json"), JSON.stringify(next, null, 2));
795
828
  return true;
796
829
  }
797
830
  bundleUrl(_app, sha2565) {
@@ -3924,8 +3957,8 @@ function committedProvisioningPath(name) {
3924
3957
  function committedShellFor(path2) {
3925
3958
  const normalized = path2.replace(/\\/g, "/");
3926
3959
  return SHELL_NAMES2.find((name) => {
3927
- const relative12 = committedProvisioningPath(name);
3928
- return normalized === relative12 || normalized.endsWith(`/${relative12}`);
3960
+ const relative13 = committedProvisioningPath(name);
3961
+ return normalized === relative13 || normalized.endsWith(`/${relative13}`);
3929
3962
  });
3930
3963
  }
3931
3964
  function isBrandable(repoRoot2, name) {
@@ -17387,15 +17420,20 @@ var init_gen_native_config = __esm({
17387
17420
  // src/progress.ts
17388
17421
  var progress_exports = {};
17389
17422
  __export(progress_exports, {
17423
+ AGENT_WINS_WITHIN_MS: () => AGENT_WINS_WITHIN_MS,
17390
17424
  APPROVALS_FILE: () => APPROVALS_FILE,
17391
17425
  APPROVAL_GATES: () => APPROVAL_GATES,
17426
+ AUTO_MILESTONES: () => AUTO_MILESTONES,
17427
+ CLI_AUTHOR: () => CLI_AUTHOR,
17392
17428
  HISTORY_DIR: () => HISTORY_DIR,
17393
17429
  JOURNEY_STEPS: () => JOURNEY_STEPS,
17394
17430
  MAX_IMAGE_BYTES: () => MAX_IMAGE_BYTES,
17395
17431
  MAX_MESSAGE_CHARS: () => MAX_MESSAGE_CHARS,
17432
+ MILESTONE_STEP: () => MILESTONE_STEP,
17396
17433
  PROGRESS_FILE: () => PROGRESS_FILE,
17397
17434
  ProgressError: () => ProgressError,
17398
17435
  UnsafeHimiDirError: () => UnsafeHimiDirError,
17436
+ agentCovers: () => agentCovers,
17399
17437
  appLabel: () => appLabel,
17400
17438
  appendApproval: () => appendApproval,
17401
17439
  appendProgress: () => appendProgress,
@@ -17405,7 +17443,10 @@ __export(progress_exports, {
17405
17443
  carryPlanningJournal: () => carryPlanningJournal,
17406
17444
  formatBrief: () => formatBrief,
17407
17445
  imageExtension: () => imageExtension,
17446
+ isCliEntry: () => isCliEntry,
17447
+ journalMilestone: () => journalMilestone,
17408
17448
  journalPath: () => journalPath,
17449
+ looksSecret: () => looksSecret,
17409
17450
  notifyDesktop: () => notifyDesktop,
17410
17451
  openApprovals: () => openApprovals,
17411
17452
  parseApprovalRef: () => parseApprovalRef,
@@ -17415,13 +17456,26 @@ __export(progress_exports, {
17415
17456
  readProgress: () => readProgress,
17416
17457
  realDirIn: () => realDirIn,
17417
17458
  summarizeProgress: () => summarizeProgress,
17459
+ visibleEntries: () => visibleEntries,
17418
17460
  writeFileAtomic: () => writeFileAtomic
17419
17461
  });
17420
17462
  import { spawn as spawn3 } from "node:child_process";
17421
17463
  import { createHash as createHash8 } from "node:crypto";
17422
17464
  import { randomUUID } from "node:crypto";
17423
17465
  import { appendFileSync, closeSync, existsSync as existsSync27, lstatSync as lstatSync5, mkdirSync as mkdirSync13, openSync, readdirSync as readdirSync15, readFileSync as readFileSync27, readSync, renameSync as renameSync3, rmSync as rmSync5, statSync as statSync5, writeFileSync as writeFileSync12 } from "node:fs";
17424
- import { join as join30, relative as relative5, resolve as resolve19, sep as sep6 } from "node:path";
17466
+ import { isAbsolute as isAbsolute3, join as join30, relative as relative5, resolve as resolve19, sep as sep6 } from "node:path";
17467
+ function agentCovers(agent, cli) {
17468
+ if (isCliEntry(agent)) return false;
17469
+ const agentShas = shasOf(agent.images);
17470
+ if (!agentShas.size) return false;
17471
+ const gap = Math.abs(Date.parse(agent.ts) - Date.parse(cli.ts));
17472
+ if (!(gap <= AGENT_WINS_WITHIN_MS)) return false;
17473
+ if (cli.step && agent.step === cli.step) return true;
17474
+ return [...shasOf(cli.images)].some((sha) => agentShas.has(sha));
17475
+ }
17476
+ function visibleEntries(entries) {
17477
+ return entries.filter((e) => !isCliEntry(e) || !entries.some((a) => agentCovers(a, e)));
17478
+ }
17425
17479
  function parseStep(raw) {
17426
17480
  const v = raw.trim().toLowerCase();
17427
17481
  if (/^[1-8]$/.test(v)) return JOURNEY_STEPS[Number(v) - 1];
@@ -17510,12 +17564,13 @@ function appendProgress(dir, input) {
17510
17564
  `a postcard is a line or two (${message.length} chars; the cap is ${MAX_MESSAGE_CHARS}) \u2014 put the detail in your reply and journal the headline`
17511
17565
  );
17512
17566
  }
17513
- const images = (input.images ?? []).map((src) => archiveImage(dir, src));
17567
+ const images = (input.images ?? []).map((src) => archiveImage(dir, src)).map((img) => input.auto ? { ...img, source: packageRelative(dir, img.source) } : img);
17514
17568
  const entry = {
17515
17569
  ts: (input.now ?? /* @__PURE__ */ new Date()).toISOString(),
17516
17570
  message,
17517
17571
  ...input.step ? { step: input.step, stepNumber: JOURNEY_STEPS.indexOf(input.step) + 1 } : {},
17518
- ...images.length ? { images } : {}
17572
+ ...images.length ? { images } : {},
17573
+ ...input.auto ? { by: CLI_AUTHOR, milestone: input.auto.milestone, ...input.auto.ref ? { ref: input.auto.ref } : {} } : {}
17519
17574
  };
17520
17575
  if (!realDirIn(dir, ".himi")) throw new ProgressError("unsafe_himi_dir", new UnsafeHimiDirError(".himi").message);
17521
17576
  const file = join30(dir, PROGRESS_FILE);
@@ -17524,6 +17579,10 @@ function appendProgress(dir, input) {
17524
17579
  `);
17525
17580
  return entry;
17526
17581
  }
17582
+ function packageRelative(dir, p) {
17583
+ const rel = relative5(dir, resolve19(p));
17584
+ return rel && !rel.startsWith("..") && !isAbsolute3(rel) ? toPosix(rel) : p;
17585
+ }
17527
17586
  function endsWithNewline(file) {
17528
17587
  if (!existsSync27(file)) return true;
17529
17588
  const size = statSync5(file).size;
@@ -17542,7 +17601,7 @@ function isEntry(v) {
17542
17601
  const e = v;
17543
17602
  return typeof e.ts === "string" && typeof e.message === "string";
17544
17603
  }
17545
- function readProgress(dir) {
17604
+ function readProgress(dir, opts = {}) {
17546
17605
  const file = join30(dir, PROGRESS_FILE);
17547
17606
  if (!existsSync27(file)) return { entries: [], skipped: 0 };
17548
17607
  const entries = [];
@@ -17557,7 +17616,34 @@ function readProgress(dir) {
17557
17616
  skipped++;
17558
17617
  }
17559
17618
  }
17560
- return { entries, skipped };
17619
+ return { entries: opts.all ? entries : visibleEntries(entries), skipped };
17620
+ }
17621
+ function journalMilestone(dir, input) {
17622
+ try {
17623
+ if (looksSecret(input.message) || input.ref !== void 0 && (!REF_SHAPE.test(input.ref) || looksSecret(input.ref))) {
17624
+ return { journaled: false, reason: "unsafe" };
17625
+ }
17626
+ const { entries } = readProgress(dir, { all: true });
17627
+ const ref = input.ref ?? null;
17628
+ if (entries.some((e) => isCliEntry(e) && e.milestone === input.milestone && (e.ref ?? null) === ref)) {
17629
+ return { journaled: false, reason: "already-journaled" };
17630
+ }
17631
+ const now = input.now ?? /* @__PURE__ */ new Date();
17632
+ const step = MILESTONE_STEP[input.milestone];
17633
+ const abs = input.image ? locateImage(dir, input.image) : null;
17634
+ const images = abs ? [{ sha256: createHash8("sha256").update(readFileSync27(abs)).digest("hex") }] : [];
17635
+ if (entries.some((e) => agentCovers(e, { ts: now.toISOString(), step, images }))) return { journaled: false, reason: "agent-journaled" };
17636
+ const entry = appendProgress(dir, {
17637
+ message: input.message,
17638
+ step,
17639
+ ...abs ? { images: [abs] } : {},
17640
+ now,
17641
+ auto: { milestone: input.milestone, ...input.ref ? { ref: input.ref } : {} }
17642
+ });
17643
+ return { journaled: true, entry };
17644
+ } catch {
17645
+ return { journaled: false, reason: "failed" };
17646
+ }
17561
17647
  }
17562
17648
  function parseGate(raw) {
17563
17649
  const v = raw.trim().toLowerCase();
@@ -17688,7 +17774,7 @@ function formatBrief(input) {
17688
17774
  if (summary.last) lines.push(`Last postcard (${minute(summary.last.ts)}): ${summary.last.message}`);
17689
17775
  const pastPlan = (summary.current?.stepNumber ?? 0) > JOURNEY_STEPS.indexOf("plan") + 1;
17690
17776
  lines.push(
17691
- !summary.current ? "Resume from the journal: `himi progress` prints every postcard." : pastPlan ? `Resume at step ${summary.current.stepNumber}. Do not restart discovery: the approved plan is SPEC.md + MOBILE-UX.md.` : `Resume at step ${summary.current.stepNumber}. Implementation has not started: treat SPEC.md + MOBILE-UX.md as a draft unless the journal records the customer approving them.`
17777
+ !summary.current ? "No journey step recorded yet: a new app starts with discovery (step 1); one whose SPEC.md the customer approved continues from its current state. `himi progress` prints every postcard." : pastPlan ? `Resume at step ${summary.current.stepNumber}. Do not restart discovery: the approved plan is SPEC.md + MOBILE-UX.md.` : `Resume at step ${summary.current.stepNumber}. Implementation has not started: treat SPEC.md + MOBILE-UX.md as a draft unless the journal records the customer approving them.`
17692
17778
  );
17693
17779
  }
17694
17780
  if (open.length) {
@@ -17702,7 +17788,7 @@ function formatBrief(input) {
17702
17788
  }
17703
17789
  function summarizeProgress(entries) {
17704
17790
  if (!entries.length) return { count: 0 };
17705
- const stepped = [...entries].reverse().find((e) => e.step && JOURNEY_STEPS.includes(e.step));
17791
+ const stepped = [...entries].reverse().find((e) => !isCliEntry(e) && e.step && JOURNEY_STEPS.includes(e.step));
17706
17792
  const first = entries[0];
17707
17793
  const last = entries[entries.length - 1];
17708
17794
  const elapsed = Date.parse(last.ts) - Date.parse(first.ts);
@@ -17763,10 +17849,16 @@ function journalPath(dir) {
17763
17849
  const rel = relative5(process.cwd(), abs);
17764
17850
  return rel && !rel.startsWith("..") ? rel : abs;
17765
17851
  }
17766
- var JOURNEY_STEPS, PROGRESS_FILE, HISTORY_DIR, APPROVALS_FILE, APPROVAL_GATES, MAX_MESSAGE_CHARS, MAX_IMAGE_BYTES, ProgressError, toPosix, UnsafeHimiDirError, gateLabel, minute, NOTIFY_TIMEOUT_MS;
17852
+ var JOURNEY_STEPS, CLI_AUTHOR, AUTO_MILESTONES, MILESTONE_STEP, AGENT_WINS_WITHIN_MS, isCliEntry, shasOf, PROGRESS_FILE, HISTORY_DIR, APPROVALS_FILE, APPROVAL_GATES, MAX_MESSAGE_CHARS, MAX_IMAGE_BYTES, ProgressError, toPosix, UnsafeHimiDirError, SECRET_SHAPES, looksSecret, REF_SHAPE, gateLabel, minute, NOTIFY_TIMEOUT_MS;
17767
17853
  var init_progress = __esm({
17768
17854
  "src/progress.ts"() {
17769
17855
  JOURNEY_STEPS = ["discover", "classify", "plan", "implement", "brand", "validate", "review", "download"];
17856
+ CLI_AUTHOR = "himi";
17857
+ AUTO_MILESTONES = ["first-look", "test", "draft"];
17858
+ MILESTONE_STEP = { "first-look": "plan", test: "validate", draft: "review" };
17859
+ AGENT_WINS_WITHIN_MS = 5 * 60 * 1e3;
17860
+ isCliEntry = (e) => e.by === CLI_AUTHOR;
17861
+ shasOf = (images) => new Set(Array.isArray(images) ? images.flatMap((i) => i && typeof i === "object" && typeof i.sha256 === "string" ? [i.sha256] : []) : []);
17770
17862
  PROGRESS_FILE = join30(".himi", "progress.jsonl");
17771
17863
  HISTORY_DIR = join30(".himi", "history");
17772
17864
  APPROVALS_FILE = join30(".himi", "approvals.jsonl");
@@ -17793,6 +17885,17 @@ var init_progress = __esm({
17793
17885
  super(`${rel} is not a real directory inside the content package (a symlink?) \u2014 refusing to write through it`);
17794
17886
  }
17795
17887
  };
17888
+ SECRET_SHAPES = [
17889
+ /:\/\//,
17890
+ // any URL: a preview link carries its bearer in the query
17891
+ /[?&#;](?:pvt|h|token|code|handover|key|secret|access_token)=/i,
17892
+ // `?h=` is the handover code
17893
+ /\bpvt[_=-]/i,
17894
+ /\beyJ[A-Za-z0-9_-]{6,}/
17895
+ // a JWT (a control token), whose header always starts `{"`
17896
+ ];
17897
+ looksSecret = (s) => SECRET_SHAPES.some((re) => re.test(s));
17898
+ REF_SHAPE = /^[A-Za-z0-9._:-]{1,128}$/;
17796
17899
  gateLabel = (gate) => APPROVAL_GATES.find((g) => g.id === gate)?.label ?? gate;
17797
17900
  minute = (iso) => `${iso.slice(0, 16).replace("T", " ")} UTC`;
17798
17901
  NOTIFY_TIMEOUT_MS = 3e3;
@@ -17861,9 +17964,9 @@ ${after}`;
17861
17964
  function blockVersion(text5) {
17862
17965
  return STAMP_RE.exec(text5)?.[1] ?? null;
17863
17966
  }
17864
- function agentsMdBody(appName) {
17967
+ function agentsMdBody(appName2) {
17865
17968
  const gates = APPROVAL_GATES.map((g) => `- \`${g.id}\` \u2014 ${g.label}`).join("\n");
17866
- return `# ${appName ? `${appName} \u2014 a Himalaya app` : "A Himalaya app"}
17969
+ return `# ${appName2 ? `${appName2} \u2014 a Himalaya app` : "A Himalaya app"}
17867
17970
 
17868
17971
  This folder is a Himalaya content package: a native iOS / Android / web app whose screens are data
17869
17972
  (JSON descriptors) plus small JavaScript workers, built and shipped with the \`himi\` CLI
@@ -18924,9 +19027,9 @@ var init_store2 = __esm({
18924
19027
  Store = class {
18925
19028
  _db;
18926
19029
  dbPath;
18927
- constructor(appName) {
19030
+ constructor(appName2) {
18928
19031
  const dataRoot = process.env.HIMALAYA_DEV_STORE_DIR ? resolve23(process.env.HIMALAYA_DEV_STORE_DIR) : resolve23(ROOT3, "core/dev-server/data");
18929
- this.dbPath = resolve23(dataRoot, `${appName}.sqlite`);
19032
+ this.dbPath = resolve23(dataRoot, `${appName2}.sqlite`);
18930
19033
  }
18931
19034
  /** The node:sqlite handle — opened, bootstrapped, and seeded on first access. */
18932
19035
  get db() {
@@ -21651,8 +21754,8 @@ var init_net_mocks_rollover = __esm({
21651
21754
  });
21652
21755
 
21653
21756
  // ../../core/dev-server/src/player-proxy.ts
21654
- function isPlayerHost(appName) {
21655
- return appName === PLAYER_APP_ID || process.env.HIMALAYA_DEV_MULTI_APP === "1";
21757
+ function isPlayerHost(appName2) {
21758
+ return appName2 === PLAYER_APP_ID || process.env.HIMALAYA_DEV_MULTI_APP === "1";
21656
21759
  }
21657
21760
  function resolveServeBase() {
21658
21761
  const override = process.env.HIMALAYA_PLAYER_SERVE_URL;
@@ -22900,6 +23003,7 @@ function shouldRevealError(args) {
22900
23003
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
22901
23004
  var init_validation = __esm({
22902
23005
  "../../core/ts/src/validation.ts"() {
23006
+ "use strict";
22903
23007
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
22904
23008
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
22905
23009
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -26500,6 +26604,7 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
26500
26604
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
26501
26605
  var init_worker_subtree = __esm({
26502
26606
  "../../core/ts/src/worker-subtree.ts"() {
26607
+ "use strict";
26503
26608
  init_worker_subtree_safe_types_generated();
26504
26609
  WORKER_SUBTREE_CEILINGS = Object.freeze({
26505
26610
  maxEncodedBytes: 65536,
@@ -33161,8 +33266,8 @@ function clampRule(rule, denyPrefixes, maxTTL) {
33161
33266
  ttl: Math.min(rule.ttl, maxTTL)
33162
33267
  };
33163
33268
  }
33164
- function resolveWixCachePolicy(appName) {
33165
- const appRules = CACHE_OVERRIDES[appName] ?? [];
33269
+ function resolveWixCachePolicy(appName2) {
33270
+ const appRules = CACHE_OVERRIDES[appName2] ?? [];
33166
33271
  const rules = appRules.map((r) => clampRule(r, PLATFORM_DENY_PREFIXES, PLATFORM_MAX_TTL));
33167
33272
  return { rules, defaultCacheable: false };
33168
33273
  }
@@ -33280,7 +33385,7 @@ function expandNavBundle(ref, ctx) {
33280
33385
  };
33281
33386
  }
33282
33387
  function deriveNavigationConfig(app, bundleCtx) {
33283
- const appName = app.name;
33388
+ const appName2 = app.name;
33284
33389
  const concrete = app.screenIds.map((screenId) => ({
33285
33390
  screenId,
33286
33391
  isPublic: true,
@@ -33293,7 +33398,7 @@ function deriveNavigationConfig(app, bundleCtx) {
33293
33398
  const keys = (pat.match(/\{([^}]+)\}/g) ?? []).map((m) => m.slice(1, -1));
33294
33399
  return { screenId: base, isPublic: true, paramKeys: keys, parentScreenId: "", tabId: "" };
33295
33400
  });
33296
- const ov = NAV_OVERRIDES[appName];
33401
+ const ov = NAV_OVERRIDES[appName2];
33297
33402
  if (ov?.parents) {
33298
33403
  for (const s of [...concrete, ...templated]) {
33299
33404
  if (ov.parents[s.screenId]) s.parentScreenId = ov.parents[s.screenId];
@@ -33304,7 +33409,7 @@ function deriveNavigationConfig(app, bundleCtx) {
33304
33409
  scheme: app.urlScheme,
33305
33410
  deepLinkPatterns: app.deepLinkPatterns ?? ov?.deepLinkPatterns ?? [],
33306
33411
  linkableScreens,
33307
- wixCachePolicy: resolveWixCachePolicy(appName),
33412
+ wixCachePolicy: resolveWixCachePolicy(appName2),
33308
33413
  // Fail-open baseline; tightened OTA via the admin app-config override. The
33309
33414
  // DESTINATION(s) are derived generically from the app's config (no app-name
33310
33415
  // special-casing) so the app chooses where telemetry goes via OTA:
@@ -33382,12 +33487,12 @@ function buildAppConfigDocument(req, meta, override, bundleCtx) {
33382
33487
  return { config, body, etag, locale };
33383
33488
  }
33384
33489
  function handleAppConfigRequest(req, res, meta, override, bundleCtx) {
33385
- const appName = decodeURIComponent(
33490
+ const appName2 = decodeURIComponent(
33386
33491
  (req.url ?? "").replace(/^\/v1\/app-config\//, "").split("?")[0]
33387
33492
  );
33388
- if (appName !== meta.name) {
33493
+ if (appName2 !== meta.name) {
33389
33494
  res.writeHead(404, { "Content-Type": "application/json" });
33390
- res.end(JSON.stringify({ error: "unknown_app", appName }));
33495
+ res.end(JSON.stringify({ error: "unknown_app", appName: appName2 }));
33391
33496
  return;
33392
33497
  }
33393
33498
  const { body, etag, locale } = buildAppConfigDocument(req, meta, override, bundleCtx);
@@ -36344,7 +36449,7 @@ async function handleToggleEval(req, res, state) {
36344
36449
  for (const id of requested) out[id] = state.toggles[id] ?? false;
36345
36450
  sendJson(res, 200, out);
36346
36451
  }
36347
- function evalContextFromReq(req, appName) {
36452
+ function evalContextFromReq(req, appName2) {
36348
36453
  const h = (k) => {
36349
36454
  const v = req.headers[k];
36350
36455
  return typeof v === "string" ? v : void 0;
@@ -36352,7 +36457,7 @@ function evalContextFromReq(req, appName) {
36352
36457
  return {
36353
36458
  sessionId: h("x-himalaya-session") ?? "dev-session",
36354
36459
  userId: h("x-himalaya-user"),
36355
- appId: appName,
36460
+ appId: appName2,
36356
36461
  platform: h("x-himalaya-platform"),
36357
36462
  appVersion: h("x-himalaya-app-version"),
36358
36463
  locale: typeof req.headers["accept-language"] === "string" ? req.headers["accept-language"].split(",")[0] : void 0
@@ -37500,8 +37605,8 @@ async function handleActionHandlerBundleGet(res, app) {
37500
37605
  }
37501
37606
  async function serveStatic(req, res, root, prefix) {
37502
37607
  const requested = decodeURIComponent((req.url ?? "").split("?")[0]);
37503
- const relative12 = requested.replace(prefix, "").replace(/^\//, "");
37504
- const safe = normalize3(relative12).replace(/^(\.\.[\/\\])+/, "");
37608
+ const relative13 = requested.replace(prefix, "").replace(/^\//, "");
37609
+ const safe = normalize3(relative13).replace(/^(\.\.[\/\\])+/, "");
37505
37610
  const fullPath = resolve31(root, safe);
37506
37611
  if (!fullPath.startsWith(root)) {
37507
37612
  return send(res, 403, "forbidden");
@@ -37665,19 +37770,19 @@ function handleAssetLinks(res, state) {
37665
37770
  });
37666
37771
  }
37667
37772
  function createDevServer(opts = {}) {
37668
- const appName = opts.app ?? "sample";
37773
+ const appName2 = opts.app ?? "sample";
37669
37774
  if (!opts.preloaded) {
37670
- throw new Error(`createDevServer requires a preloaded module for "${appName}". Preload via loadApp("${appName}").`);
37775
+ throw new Error(`createDevServer requires a preloaded module for "${appName2}". Preload via loadApp("${appName2}").`);
37671
37776
  }
37672
37777
  checkRegistry(opts.preloaded.module.actions);
37673
37778
  const port = opts.port ?? Number(process.env.HIMALAYA_DEV_PORT ?? 8080);
37674
37779
  const hotReloadEnabled = opts.enableHotReload ?? process.env.HIMALAYA_DEV_HOT_RELOAD === "1";
37675
37780
  const state = {
37676
- appName,
37781
+ appName: appName2,
37677
37782
  // Store construction is cheap now; its node:sqlite DB is opened LAZILY on first query
37678
37783
  // (store.ts), so the serve authoring plane can boot createDevServer on a Whirlwind guest
37679
37784
  // that has no node:sqlite — the stateless build/render path never queries the demo store.
37680
- store: new Store(appName),
37785
+ store: new Store(appName2),
37681
37786
  toggles: {
37682
37787
  pinned_notes_v2: false,
37683
37788
  weekly_review_v1: true,
@@ -37698,7 +37803,7 @@ function createDevServer(opts = {}) {
37698
37803
  localOverrides: /* @__PURE__ */ new Map(),
37699
37804
  localOverrideHistory: /* @__PURE__ */ new Map(),
37700
37805
  app: opts.preloaded,
37701
- moduleStore: opts.preloaded ? opts.preloaded.module.createStore(appName) : void 0,
37806
+ moduleStore: opts.preloaded ? opts.preloaded.module.createStore(appName2) : void 0,
37702
37807
  telemetryEvents: [],
37703
37808
  adminUrl: process.env.HIMALAYA_ADMIN_URL,
37704
37809
  wix: createWixGatewayState(),
@@ -38224,12 +38329,6 @@ var init_src6 = __esm({
38224
38329
  }
38225
38330
  });
38226
38331
 
38227
- // ../../core/serve-storage/src/wip-pointer.ts
38228
- var init_wip_pointer = __esm({
38229
- "../../core/serve-storage/src/wip-pointer.ts"() {
38230
- }
38231
- });
38232
-
38233
38332
  // ../../core/serve-storage/src/release-patch.ts
38234
38333
  import { createHash as createHash17 } from "node:crypto";
38235
38334
  function sha256hex(b) {
@@ -42271,7 +42370,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
42271
42370
  ...collect2?.lint ? { lint, workers } : {},
42272
42371
  ...collect2?.render ? { renders } : {},
42273
42372
  ...collect2?.descriptors ? { descriptors: baked, linkableScreenIds: [...linkable] } : {},
42274
- ...collect2?.descriptors && typeof preloaded.meta.homeScreenId === "string" && preloaded.meta.homeScreenId ? { homeScreenId: preloaded.meta.homeScreenId } : {},
42373
+ ...typeof preloaded.meta.homeScreenId === "string" && preloaded.meta.homeScreenId ? { homeScreenId: preloaded.meta.homeScreenId } : {},
42275
42374
  ...renderTokens !== void 0 ? { tokens: renderTokens } : {},
42276
42375
  ...preloaded.meta.surfaces ? { surfaceNav: preloaded.meta.surfaces } : {},
42277
42376
  auth: (() => {
@@ -42568,7 +42667,7 @@ h2{font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;le
42568
42667
  .sw{display:flex;gap:6px}.sw span{width:22px;height:22px;border-radius:50%;border:1px solid var(--line)}
42569
42668
  .nav{display:flex;flex-wrap:wrap;gap:6px}.nav span{font-size:12px;padding:3px 8px;border-radius:6px;background:var(--bg);border:1px solid var(--line)}
42570
42669
  .log{list-style:none;margin:0;padding:0}.log li{display:flex;gap:10px;padding:10px 0;border-top:1px solid var(--line)}.log li:first-child{border-top:0}
42571
- .log .t{color:var(--faint);font-size:12px;min-width:64px;font-variant-numeric:tabular-nums}.log .s{font-size:11px;color:var(--muted);text-transform:capitalize}.log .imgs{display:flex;gap:6px;margin-top:6px}.log .imgs img{height:72px;border-radius:8px;border:1px solid var(--line);cursor:zoom-in}
42670
+ .log .t{color:var(--faint);font-size:12px;min-width:64px;font-variant-numeric:tabular-nums}.log .s{font-size:11px;color:var(--muted);text-transform:capitalize}.log .by{font-size:11px;color:var(--faint)}.log .imgs{display:flex;gap:6px;margin-top:6px}.log .imgs img{height:72px;border-radius:8px;border:1px solid var(--line);cursor:zoom-in}
42572
42671
  .empty{color:var(--faint);font-size:13px;padding:8px 0}
42573
42672
  #lb{position:fixed;inset:0;background:rgba(0,0,0,.72);display:none;align-items:center;justify-content:center;z-index:9;cursor:zoom-out}#lb img{max-height:92vh;max-width:92vw;border-radius:14px;background:#fff}
42574
42673
  .enter{animation:enter .7s cubic-bezier(.2,.8,.2,1) both}@keyframes enter{from{opacity:0;transform:translateY(10px) scale(.94);filter:grayscale(1)}to{opacity:1;transform:none;filter:none}}
@@ -42697,7 +42796,7 @@ function render(s){
42697
42796
  s.steps.forEach(function(name,i){var d=el("div","step"+(i+1<n?" done":i+1===n?" cur":""));d.appendChild(el("div","bar"));d.appendChild(el("div","lbl",(i+1)+". "+name));steps.appendChild(d)});
42698
42797
  var last=s.journal[0];
42699
42798
  $("nowmsg").textContent=last?last.message:"Waiting for the first postcard.";
42700
- $("nowmeta").textContent=last?("Now \xB7 "+(last.step?("step "+last.stepNumber+" of 8, "+last.step+" \xB7 "):"")+ago(last.ts)):"Now";
42799
+ $("nowmeta").textContent=last?("Now \xB7 "+(last.step?("step "+last.stepNumber+" of 8, "+last.step+" \xB7 "):"")+(last.by?"noted by "+last.by+" \xB7 ":"")+ago(last.ts)):"Now";
42701
42800
  var film=$("film");clear(film);
42702
42801
  if(!rewinding){var n0=s.timelineFrames||0;$("scrub").hidden=n0<2;$("slider").max=String(Math.max(1,n0-1));$("slider").value=$("slider").max}
42703
42802
  if(rewinding){paintStill(s)}else{$("still").hidden=true;$("focus").hidden=false;var focus=$("focus");
@@ -42729,7 +42828,7 @@ function render(s){
42729
42828
  if(s.app.palette.length){var sw=el("div","sw");s.app.palette.forEach(function(c){var d=el("span");d.style.background=c.hex;d.title=c.name+" "+c.hex;sw.appendChild(d)});brand.appendChild(sw)}
42730
42829
  var nav=$("nav");clear(nav);$("navcard").style.display=s.nav.length&&!s.map?"":"none";drawMap(s.map);s.nav.forEach(function(e){nav.appendChild(el("span",null,e.from+" \u2192 "+e.to))});
42731
42830
  var log=$("log");clear(log);if(!s.journal.length)log.appendChild(el("li","empty","Postcards the agent journals appear here."));
42732
- s.journal.forEach(function(j){var li=el("li");li.appendChild(el("div","t",hhmm(j.ts)));var body=el("div");body.appendChild(el("div",null,j.message));if(j.step)body.appendChild(el("div","s","step "+j.stepNumber+" \xB7 "+j.step));
42831
+ s.journal.forEach(function(j){var li=el("li");li.appendChild(el("div","t",hhmm(j.ts)));var body=el("div");body.appendChild(el("div",null,j.message));if(j.step)body.appendChild(el("div","s","step "+j.stepNumber+" \xB7 "+j.step));if(j.by)body.appendChild(el("div","by","noted by "+j.by));
42733
42832
  if(j.images.length){var row=el("div","imgs");j.images.forEach(function(u){row.appendChild(img(u))});body.appendChild(row)}li.appendChild(body);log.appendChild(li)});
42734
42833
  paintPop(s);
42735
42834
  }
@@ -43502,9 +43601,10 @@ function buildView(dir, memory, now) {
43502
43601
  journal: entries.slice(-MAX_JOURNAL).reverse().map((e) => ({
43503
43602
  ts: e.ts,
43504
43603
  message: e.message,
43505
- step: e.step ?? null,
43506
- stepNumber: e.stepNumber ?? null,
43507
- images: (e.images ?? []).map((i) => historyUrl(basename10(i.file)))
43604
+ step: isCliEntry(e) ? null : e.step ?? null,
43605
+ stepNumber: isCliEntry(e) ? null : e.stepNumber ?? null,
43606
+ images: (e.images ?? []).map((i) => historyUrl(basename10(i.file))),
43607
+ by: isCliEntry(e) ? CLI_AUTHOR : null
43508
43608
  })),
43509
43609
  screens,
43510
43610
  focus,
@@ -44752,6 +44852,109 @@ var init_notify_setup = __esm({
44752
44852
  }
44753
44853
  });
44754
44854
 
44855
+ // src/milestones.ts
44856
+ var milestones_exports = {};
44857
+ __export(milestones_exports, {
44858
+ afterDraftPush: () => afterDraftPush,
44859
+ afterRenderPng: () => afterRenderPng,
44860
+ afterTestRun: () => afterTestRun,
44861
+ displayPath: () => displayPath,
44862
+ homeScreenIdOf: () => homeScreenIdOf,
44863
+ openBoardForSession: () => openBoardForSession,
44864
+ progressCommand: () => progressCommand,
44865
+ sessionSteps: () => sessionSteps,
44866
+ testPostcardMessage: () => testPostcardMessage
44867
+ });
44868
+ import { existsSync as existsSync44, readFileSync as readFileSync48 } from "node:fs";
44869
+ import { basename as basename12, isAbsolute as isAbsolute4, join as join42, relative as relative8 } from "node:path";
44870
+ function homeScreenIdOf(dir) {
44871
+ let src;
44872
+ try {
44873
+ src = readFileSync48(join42(dir, "dev", "index.ts"), "utf8");
44874
+ } catch {
44875
+ return null;
44876
+ }
44877
+ const m = /^[ \t]*homeScreenId[ \t]*:[ \t]*(?:"((?:[^"\\\n]|\\.)*)"|'((?:[^'\\\n]|\\.)*)')/m.exec(src);
44878
+ const id = m ? m[1] ?? m[2] : void 0;
44879
+ return id && /^[A-Za-z0-9_.\-/{}]+$/.test(id) ? id : null;
44880
+ }
44881
+ function displayPath(p, cwd = process.cwd()) {
44882
+ const rel = relative8(cwd, p);
44883
+ return rel && !rel.startsWith("..") && !isAbsolute4(rel) ? rel : p;
44884
+ }
44885
+ function progressCommand(opts) {
44886
+ return `himi progress "<${opts.what}>"${opts.step ? ` --step ${opts.step}` : ""}${opts.image ? ` --image ${shellQuote(opts.image)}` : ""}${contentArg(opts.content)}`;
44887
+ }
44888
+ function sessionSteps(opts) {
44889
+ const content = displayPath(opts.dir, opts.cwd);
44890
+ const home = opts.home ?? "<homeScreenId>";
44891
+ const shot = `.himi/shots/${home}.png`;
44892
+ return [
44893
+ `himi run --watch${contentArg(content)} & \u2014 now, in this session: keep it running in the background. The live preview repaints on every save and serves the build board at <its url>/__himi/studio/`,
44894
+ `himi render ${shellQuote(home)} --png --device iphone${contentArg(content)} \u2014 the first picture, written to ${shot}${opts.home ? "" : " (the id is the dev module's homeScreenId)"}; the CLI journals it as the first look`,
44895
+ `${progressCommand({ what: "where the build starts", step: "plan", image: shot, content })} \u2014 your postcard for the owner, with that picture`
44896
+ ];
44897
+ }
44898
+ async function openBoardForSession(dir, cwd = process.cwd()) {
44899
+ try {
44900
+ const { openStudio: openStudio2 } = await Promise.resolve().then(() => (init_studio(), studio_exports));
44901
+ const { page, url } = openStudio2(dir);
44902
+ const rel = relative8(cwd, page);
44903
+ return { page: rel && !rel.startsWith("..") && !isAbsolute4(rel) ? rel : page, url, opened: false };
44904
+ } catch (e) {
44905
+ return { opened: false, error: `the build board was not created: ${e.message} \u2014 \`himi studio --no-open\` in the app retries it` };
44906
+ }
44907
+ }
44908
+ function journal(dir, milestone, message, extra) {
44909
+ const r = journalMilestone(dir, { milestone, message, ...extra });
44910
+ return r.journaled ? { journaled: true, milestone, message } : void 0;
44911
+ }
44912
+ function afterRenderPng(dir, input) {
44913
+ const postcard = input.renderable && input.homeScreenId !== null && input.screenId === input.homeScreenId ? journal(dir, "first-look", `First look at ${appName(dir)}: the home screen, where the build starts.`, { image: input.pngFile, ...input.now ? { now: input.now } : {} }) : void 0;
44914
+ return {
44915
+ ...postcard ? { postcard } : {},
44916
+ next: input.renderable ? [progressCommand({ what: "what changed", image: displayPath(input.pngFile), content: input.content })] : []
44917
+ };
44918
+ }
44919
+ function testPostcardMessage(s) {
44920
+ const links = s.broken === void 0 ? "" : `, ${plural(s.broken, "broken link")}`;
44921
+ const unreached = s.unreached ? `, ${plural(s.unreached, "screen")} nothing links to` : "";
44922
+ return `Test crawl: ${plural(s.screens, "screen")} and ${plural(s.actions, "action")} exercised${links}${unreached} \u2014 ${s.errors ? `${plural(s.errors, "error")} to fix` : "no errors"}.`;
44923
+ }
44924
+ function afterTestRun(dir, input) {
44925
+ const postcard = journal(dir, "test", testPostcardMessage(input), {
44926
+ ref: input.runId,
44927
+ ...input.homeShot && existsSync44(input.homeShot) ? { image: input.homeShot } : {},
44928
+ ...input.now ? { now: input.now } : {}
44929
+ });
44930
+ return {
44931
+ ...postcard ? { postcard } : {},
44932
+ next: [
44933
+ input.boardOpen ? "the build board's App map now shows every screen this crawl reached, how they link, and any broken link \u2014 .himi/studio/index.html, or /__himi/studio/ under `himi run --watch`" : "`himi studio` opens the build board, whose App map draws every screen this crawl reached, how they link, and any broken link"
44934
+ ]
44935
+ };
44936
+ }
44937
+ function afterDraftPush(dir, input) {
44938
+ const postcard = journal(dir, "draft", `Draft pushed: release ${input.releaseId.slice(0, 12)} of ${input.app} \u2014 owner-private, not live.`, {
44939
+ ref: input.releaseId,
44940
+ ...input.now ? { now: input.now } : {}
44941
+ });
44942
+ return {
44943
+ ...postcard ? { postcard } : {},
44944
+ next: `${progressCommand({ what: "what the owner can review in this draft", step: "review", content: input.content })} \u2014 your postcard for the draft; share the preview link in chat, never in the journal`
44945
+ };
44946
+ }
44947
+ var contentArg, plural, appName;
44948
+ var init_milestones = __esm({
44949
+ "src/milestones.ts"() {
44950
+ init_progress();
44951
+ init_shell_quote();
44952
+ contentArg = (content) => content ? ` --content ${shellQuote(content)}` : "";
44953
+ plural = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
44954
+ appName = (dir) => appLabel(dir) ?? basename12(dir);
44955
+ }
44956
+ });
44957
+
44755
44958
  // src/site-analyze/types.ts
44756
44959
  function isRedirectUri(value) {
44757
44960
  return /^[a-z][a-z0-9+.-]*:\/\/\S*$/i.test(value);
@@ -45189,8 +45392,8 @@ __export(template_trim_exports, {
45189
45392
  routeMatcher: () => routeMatcher,
45190
45393
  runTemplateTrim: () => runTemplateTrim
45191
45394
  });
45192
- import { cpSync, existsSync as existsSync44, mkdirSync as mkdirSync20, readdirSync as readdirSync24, readFileSync as readFileSync48, renameSync as renameSync5, rmSync as rmSync8, statSync as statSync10, writeFileSync as writeFileSync16 } from "node:fs";
45193
- import { dirname as dirname25, join as join42 } from "node:path";
45395
+ import { cpSync, existsSync as existsSync45, mkdirSync as mkdirSync20, readdirSync as readdirSync24, readFileSync as readFileSync49, renameSync as renameSync5, rmSync as rmSync8, statSync as statSync10, writeFileSync as writeFileSync16 } from "node:fs";
45396
+ import { dirname as dirname25, join as join43 } from "node:path";
45194
45397
  function routeMatcher(pattern) {
45195
45398
  const detail = /^\^([A-Za-z0-9_]+)\(\?:\[_\/\]\(\.\+\)\)\?\$$/.exec(pattern);
45196
45399
  if (detail) {
@@ -45884,20 +46087,20 @@ function homeModulesPass(app, kept, edited) {
45884
46087
  return { text: patched.src, dropped: patched.droppedModules };
45885
46088
  }
45886
46089
  function listWorkers(dir) {
45887
- const root = join42(dir, "tier5-src");
45888
- if (!existsSync44(root)) return { dirs: [], files: {} };
45889
- const dirs = readdirSync24(root).filter((n) => statSync10(join42(root, n)).isDirectory()).sort();
46090
+ const root = join43(dir, "tier5-src");
46091
+ if (!existsSync45(root)) return { dirs: [], files: {} };
46092
+ const dirs = readdirSync24(root).filter((n) => statSync10(join43(root, n)).isDirectory()).sort();
45890
46093
  const files = {};
45891
46094
  const walk2 = (abs, rel) => {
45892
46095
  for (const name of readdirSync24(abs)) {
45893
46096
  if (name === "node_modules") continue;
45894
- const p = join42(abs, name);
46097
+ const p = join43(abs, name);
45895
46098
  const r = `${rel}/${name}`;
45896
46099
  if (statSync10(p).isDirectory()) walk2(p, r);
45897
- else if (/\.(?:[cm]?[jt]sx?|json)$/.test(name)) files[r] = readFileSync48(p, "utf8");
46100
+ else if (/\.(?:[cm]?[jt]sx?|json)$/.test(name)) files[r] = readFileSync49(p, "utf8");
45898
46101
  }
45899
46102
  };
45900
- for (const d of dirs) walk2(join42(root, d), `tier5-src/${d}`);
46103
+ for (const d of dirs) walk2(join43(root, d), `tier5-src/${d}`);
45901
46104
  return { dirs, files };
45902
46105
  }
45903
46106
  function csv(v) {
@@ -45920,16 +46123,16 @@ async function runTemplateTrim(req, deps) {
45920
46123
  links = req.links;
45921
46124
  }
45922
46125
  const dir = req.dir;
45923
- const devPath = join42(dir, "dev/index.ts");
45924
- const configPath = join42(dir, "config.ts");
45925
- if (!existsSync44(devPath) || !existsSync44(configPath)) {
46126
+ const devPath = join43(dir, "dev/index.ts");
46127
+ const configPath = join43(dir, "config.ts");
46128
+ if (!existsSync45(devPath) || !existsSync45(configPath)) {
45926
46129
  return refuse3(new TrimRefusal(`${dir} has no dev/index.ts + config.ts \u2014 point --content at an app scaffolded with \`himi init --template\``));
45927
46130
  }
45928
46131
  if (isTemplateSourceTree(dir)) {
45929
46132
  return refuse3(new TrimRefusal(`${dir} is a template source tree (its identity is still __HIMI_APP_*__ sentinels) \u2014 trim the app \`himi init --template\` scaffolds from it, not the template itself`));
45930
46133
  }
45931
- const dev = readFileSync48(devPath, "utf8");
45932
- const config = readFileSync48(configPath, "utf8");
46134
+ const dev = readFileSync49(devPath, "utf8");
46135
+ const config = readFileSync49(configPath, "utf8");
45933
46136
  const workers = listWorkers(dir);
45934
46137
  let plan;
45935
46138
  try {
@@ -45939,23 +46142,23 @@ async function runTemplateTrim(req, deps) {
45939
46142
  throw e;
45940
46143
  }
45941
46144
  if (req.dryRun) return { code: 0, result: { ok: true, dryRun: true, ...plan.report, validate: null } };
45942
- const hadHimi = existsSync44(join42(dir, ".himi"));
45943
- const backupRel = join42(".himi", "trim-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
45944
- const backup = join42(dir, backupRel);
46145
+ const hadHimi = existsSync45(join43(dir, ".himi"));
46146
+ const backupRel = join43(".himi", "trim-backup", (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"));
46147
+ const backup = join43(dir, backupRel);
45945
46148
  const originals = [[devPath, "dev/index.ts"], [configPath, "config.ts"]];
45946
46149
  const moved = [];
45947
46150
  const restore = () => {
45948
46151
  for (const [path2, rel] of originals) {
45949
- const original = readFileSync48(join42(backup, rel));
45950
- if (!existsSync44(path2) || !readFileSync48(path2).equals(original)) writeFileSync16(path2, original);
46152
+ const original = readFileSync49(join43(backup, rel));
46153
+ if (!existsSync45(path2) || !readFileSync49(path2).equals(original)) writeFileSync16(path2, original);
45951
46154
  }
45952
- for (const name of [...moved].reverse()) renameSync5(join42(backup, "tier5-src", name), join42(dir, "tier5-src", name));
46155
+ for (const name of [...moved].reverse()) renameSync5(join43(backup, "tier5-src", name), join43(dir, "tier5-src", name));
45953
46156
  moved.length = 0;
45954
46157
  };
45955
46158
  const apply = () => {
45956
46159
  const moveDir = deps.moveDir ?? renameSync5;
45957
46160
  for (const name of plan.removedWorkers) {
45958
- moveDir(join42(dir, "tier5-src", name), join42(backup, "tier5-src", name));
46161
+ moveDir(join43(dir, "tier5-src", name), join43(backup, "tier5-src", name));
45959
46162
  moved.push(name);
45960
46163
  }
45961
46164
  if (plan.dev !== dev) writeFileSync16(devPath, plan.dev);
@@ -45963,8 +46166,8 @@ async function runTemplateTrim(req, deps) {
45963
46166
  };
45964
46167
  const dropBackup = () => {
45965
46168
  rmSync8(backup, { recursive: true, force: true });
45966
- for (const d of [dirname25(backup), join42(dir, ".himi")]) {
45967
- if (d === join42(dir, ".himi") && hadHimi) break;
46169
+ for (const d of [dirname25(backup), join43(dir, ".himi")]) {
46170
+ if (d === join43(dir, ".himi") && hadHimi) break;
45968
46171
  try {
45969
46172
  if (!readdirSync24(d).length) rmSync8(d, { recursive: true });
45970
46173
  } catch {
@@ -45990,9 +46193,9 @@ async function runTemplateTrim(req, deps) {
45990
46193
  return { code: 1, result: { ok: false, error: `${error} (${message}); every file was restored`, rolledBack: true, ...base(validate4) } };
45991
46194
  };
45992
46195
  try {
45993
- mkdirSync20(join42(backup, "dev"), { recursive: true });
45994
- for (const [path2, rel] of originals) cpSync(path2, join42(backup, rel));
45995
- if (plan.removedWorkers.length) mkdirSync20(join42(backup, "tier5-src"), { recursive: true });
46196
+ mkdirSync20(join43(backup, "dev"), { recursive: true });
46197
+ for (const [path2, rel] of originals) cpSync(path2, join43(backup, rel));
46198
+ if (plan.removedWorkers.length) mkdirSync20(join43(backup, "tier5-src"), { recursive: true });
45996
46199
  } catch (e) {
45997
46200
  dropBackup();
45998
46201
  return { code: 1, result: { ok: false, error: `could not back up the app before trimming it (${e.message}); nothing was changed`, rolledBack: true, ...base() } };
@@ -46005,7 +46208,7 @@ async function runTemplateTrim(req, deps) {
46005
46208
  let failure;
46006
46209
  let validate3;
46007
46210
  try {
46008
- const written = parseApp(readFileSync48(devPath, "utf8"), readFileSync48(configPath, "utf8"));
46211
+ const written = parseApp(readFileSync49(devPath, "utf8"), readFileSync49(configPath, "utf8"));
46009
46212
  const leftovers = leftoverNavRefs(written.nav.value, plan.removedIds);
46010
46213
  if (leftovers.length) failure = { error: "navigation still names screens this trim removed", leftovers };
46011
46214
  else {
@@ -46042,7 +46245,7 @@ async function runTemplateTrim(req, deps) {
46042
46245
  else if (compared) {
46043
46246
  failure = {
46044
46247
  ...failure,
46045
- error: `himi validate --strict failed on the trimmed app \u2014 the trim introduced ${plural(compared.introduced.length, "error")} the untrimmed app does not have: ${summarize3(compared.introduced)}`,
46248
+ error: `himi validate --strict failed on the trimmed app \u2014 the trim introduced ${plural2(compared.introduced.length, "error")} the untrimmed app does not have: ${summarize3(compared.introduced)}`,
46046
46249
  introducedErrors: compared.introduced,
46047
46250
  preExistingErrors: compared.preExisting
46048
46251
  };
@@ -46058,7 +46261,7 @@ async function runTemplateTrim(req, deps) {
46058
46261
  ...base(validate3),
46059
46262
  ...preExisting.length ? {
46060
46263
  preExistingErrors: preExisting,
46061
- note: `trimmed; ${plural(preExisting.length, "error")} ${preExisting.length === 1 ? "was" : "were"} already present before trim: ${summarize3(preExisting)}`
46264
+ note: `trimmed; ${plural2(preExisting.length, "error")} ${preExisting.length === 1 ? "was" : "were"} already present before trim: ${summarize3(preExisting)}`
46062
46265
  } : {}
46063
46266
  }
46064
46267
  };
@@ -46109,7 +46312,7 @@ function summarize3(errors) {
46109
46312
  const shown2 = errors.slice(0, 5).map(one).join("; ");
46110
46313
  return errors.length > 5 ? `${shown2}; and ${errors.length - 5} more` : shown2;
46111
46314
  }
46112
- var TrimRefusal, LIT, ATOM, QUANT, CAP, SAFE_ROUTE_PATTERN, lineOf2, isStatic, entryLabel, isNode3, asItems, NAV_SCREEN_KEYS, sorted, refuse3, plural;
46315
+ var TrimRefusal, LIT, ATOM, QUANT, CAP, SAFE_ROUTE_PATTERN, lineOf2, isStatic, entryLabel, isNode3, asItems, NAV_SCREEN_KEYS, sorted, refuse3, plural2;
46113
46316
  var init_template_trim = __esm({
46114
46317
  "src/template-trim.ts"() {
46115
46318
  init_validate_descriptor();
@@ -46143,7 +46346,7 @@ var init_template_trim = __esm({
46143
46346
  NAV_SCREEN_KEYS = /* @__PURE__ */ new Set(["screenId", "parentScreenId", "contentScreenId", "badgeScreenId"]);
46144
46347
  sorted = (xs) => [...new Set(xs)].sort();
46145
46348
  refuse3 = (e) => ({ code: 2, result: { ok: false, error: e.message, ...e.detail } });
46146
- plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
46349
+ plural2 = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
46147
46350
  }
46148
46351
  });
46149
46352
 
@@ -46773,8 +46976,8 @@ __export(pack_exports, {
46773
46976
  });
46774
46977
  import { createHash as createHash22 } from "node:crypto";
46775
46978
  import { deflateRawSync, inflateRawSync } from "node:zlib";
46776
- import { existsSync as existsSync45, mkdirSync as mkdirSync21, readFileSync as readFileSync49, readdirSync as readdirSync25, rmSync as rmSync9, statSync as statSync11, writeFileSync as writeFileSync17 } from "node:fs";
46777
- import { dirname as dirname26, join as join43, resolve as resolve37 } from "node:path";
46979
+ import { existsSync as existsSync46, mkdirSync as mkdirSync21, readFileSync as readFileSync50, readdirSync as readdirSync25, rmSync as rmSync9, statSync as statSync11, writeFileSync as writeFileSync17 } from "node:fs";
46980
+ import { dirname as dirname26, join as join44, resolve as resolve37 } from "node:path";
46778
46981
  function writeZip(entries) {
46779
46982
  const sorted2 = entries.slice().sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
46780
46983
  const total = sorted2.reduce((n, e) => n + e.bytes.length, 0);
@@ -46868,13 +47071,13 @@ function inflateEntry(buf, entry) {
46868
47071
  return bytes2;
46869
47072
  }
46870
47073
  function readIf(path2) {
46871
- return existsSync45(path2) && statSync11(path2).isFile() ? readFileSync49(path2) : null;
47074
+ return existsSync46(path2) && statSync11(path2).isFile() ? readFileSync50(path2) : null;
46872
47075
  }
46873
47076
  function listDir(dir) {
46874
- return existsSync45(dir) ? readdirSync25(dir).filter((f) => statSync11(join43(dir, f)).isFile()) : [];
47077
+ return existsSync46(dir) ? readdirSync25(dir).filter((f) => statSync11(join44(dir, f)).isFile()) : [];
46875
47078
  }
46876
47079
  function gatherReleaseEntries(appDir2) {
46877
- const manifestPath = join43(appDir2, HIMI_PACKAGE_MANIFEST_PATH);
47080
+ const manifestPath = join44(appDir2, HIMI_PACKAGE_MANIFEST_PATH);
46878
47081
  const manifestBytes = readIf(manifestPath);
46879
47082
  if (!manifestBytes) throw new Error(`no ${HIMI_PACKAGE_MANIFEST_PATH} in ${appDir2} \u2014 build a release there first`);
46880
47083
  const manifest = JSON.parse(manifestBytes.toString("utf8"));
@@ -46898,7 +47101,7 @@ function gatherReleaseEntries(appDir2) {
46898
47101
  }
46899
47102
  const missing = [];
46900
47103
  for (const rel of wanted) {
46901
- const bytes2 = readIf(join43(appDir2, rel));
47104
+ const bytes2 = readIf(join44(appDir2, rel));
46902
47105
  if (bytes2) entries.push({ path: rel, bytes: bytes2 });
46903
47106
  else missing.push(rel);
46904
47107
  }
@@ -46909,12 +47112,12 @@ function gatherReleaseEntries(appDir2) {
46909
47112
  );
46910
47113
  }
46911
47114
  for (const name of SINGLETONS) {
46912
- const bytes2 = readIf(join43(appDir2, name));
47115
+ const bytes2 = readIf(join44(appDir2, name));
46913
47116
  if (bytes2) entries.push({ path: name, bytes: bytes2 });
46914
47117
  }
46915
47118
  const skipped = [];
46916
47119
  for (const sub of ["screens", "bundles", "assets", "widgets"]) {
46917
- for (const f of listDir(join43(appDir2, sub))) {
47120
+ for (const f of listDir(join44(appDir2, sub))) {
46918
47121
  if (!wanted.has(`${sub}/${f}`)) skipped.push(`${sub}/${f}`);
46919
47122
  }
46920
47123
  }
@@ -46947,18 +47150,18 @@ function buildEntries(appDir2, app, opts) {
46947
47150
  return { all, header, manifest, skipped };
46948
47151
  }
46949
47152
  function packRelease(releaseRoot, app, out, opts = {}) {
46950
- const appDir2 = join43(resolve37(releaseRoot), app);
47153
+ const appDir2 = join44(resolve37(releaseRoot), app);
46951
47154
  const { all, header, skipped } = buildEntries(appDir2, app, opts);
46952
47155
  const buf = writeZip(all);
46953
- const isDir2 = existsSync45(out) && statSync11(out).isDirectory();
46954
- const outFile = isDir2 ? join43(resolve37(out), packageFileName(app, header.releaseId)) : resolve37(out);
47156
+ const isDir2 = existsSync46(out) && statSync11(out).isDirectory();
47157
+ const outFile = isDir2 ? join44(resolve37(out), packageFileName(app, header.releaseId)) : resolve37(out);
46955
47158
  mkdirSync21(dirname26(outFile), { recursive: true });
46956
47159
  writeFileSync17(outFile, buf);
46957
47160
  return { app, releaseId: header.releaseId, header, out: outFile, bytes: buf.length, entryCount: all.length, skipped };
46958
47161
  }
46959
47162
  function resolvePackageDirDest(outDir, app, releaseId) {
46960
47163
  const packagedApp = (dir) => {
46961
- const bytes2 = readIf(join43(dir, HIMI_PACKAGE_HEADER_PATH));
47164
+ const bytes2 = readIf(join44(dir, HIMI_PACKAGE_HEADER_PATH));
46962
47165
  if (!bytes2) return void 0;
46963
47166
  try {
46964
47167
  return JSON.parse(bytes2.toString("utf8")).app;
@@ -46967,15 +47170,15 @@ function resolvePackageDirDest(outDir, app, releaseId) {
46967
47170
  }
46968
47171
  };
46969
47172
  const replaceable = (dir) => {
46970
- if (!existsSync45(dir)) return true;
47173
+ if (!existsSync46(dir)) return true;
46971
47174
  if (!statSync11(dir).isDirectory()) return false;
46972
47175
  return readdirSync25(dir).length === 0 || packagedApp(dir) === app;
46973
47176
  };
46974
- if (existsSync45(outDir) && !statSync11(outDir).isDirectory()) {
47177
+ if (existsSync46(outDir) && !statSync11(outDir).isDirectory()) {
46975
47178
  throw new Error(`refusing to write a package tree over ${outDir} \u2014 it is a file, not a directory`);
46976
47179
  }
46977
47180
  if (replaceable(outDir)) return outDir;
46978
- const child = join43(outDir, packageDirName(app, releaseId));
47181
+ const child = join44(outDir, packageDirName(app, releaseId));
46979
47182
  if (!replaceable(child)) {
46980
47183
  throw new Error(
46981
47184
  `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.`
@@ -46989,13 +47192,13 @@ function packReleaseToDir(releaseRoot, app, outDir, opts = {}) {
46989
47192
  "a package carrying the app's source has no --format dir form: readers that predate the source entry drop it before hashing a directory and refuse the package as tampered. Pack it as a .himi archive."
46990
47193
  );
46991
47194
  }
46992
- const appDir2 = join43(resolve37(releaseRoot), app);
47195
+ const appDir2 = join44(resolve37(releaseRoot), app);
46993
47196
  const { all, header, skipped } = buildEntries(appDir2, app, opts);
46994
47197
  const dest = resolvePackageDirDest(resolve37(outDir), app, header.releaseId);
46995
47198
  rmSync9(dest, { recursive: true, force: true });
46996
47199
  let bytes2 = 0;
46997
47200
  for (const entry of all) {
46998
- const target = join43(dest, entry.path);
47201
+ const target = join44(dest, entry.path);
46999
47202
  mkdirSync21(dirname26(target), { recursive: true });
47000
47203
  writeFileSync17(target, entry.bytes);
47001
47204
  bytes2 += entry.bytes.length;
@@ -47018,21 +47221,21 @@ function openPackageBuffer(buf) {
47018
47221
  return openFromEntries(readZip(buf), { requireHeader: true });
47019
47222
  }
47020
47223
  function openPackageFile(file) {
47021
- return openPackageBuffer(readFileSync49(file));
47224
+ return openPackageBuffer(readFileSync50(file));
47022
47225
  }
47023
47226
  function openPackageDir(dir) {
47024
47227
  const root = resolve37(dir);
47025
47228
  const entries = /* @__PURE__ */ new Map();
47026
47229
  const walk2 = (rel) => {
47027
- const abs = rel ? join43(root, rel) : root;
47230
+ const abs = rel ? join44(root, rel) : root;
47028
47231
  for (const name of readdirSync25(abs)) {
47029
47232
  const childRel = rel ? `${rel}/${name}` : name;
47030
- const childAbs = join43(abs, name);
47233
+ const childAbs = join44(abs, name);
47031
47234
  if (statSync11(childAbs).isDirectory()) walk2(childRel);
47032
- else if (isSafeEntryName(childRel)) entries.set(childRel, readFileSync49(childAbs));
47235
+ else if (isSafeEntryName(childRel)) entries.set(childRel, readFileSync50(childAbs));
47033
47236
  }
47034
47237
  };
47035
- if (!existsSync45(root)) throw new Error(`no such package directory: ${root}`);
47238
+ if (!existsSync46(root)) throw new Error(`no such package directory: ${root}`);
47036
47239
  walk2("");
47037
47240
  const manifestBytes = entries.get(HIMI_PACKAGE_MANIFEST_PATH);
47038
47241
  if (manifestBytes) {
@@ -47074,10 +47277,10 @@ function unpackTo(buf, dest, appFallback) {
47074
47277
  if (!app) throw new Error("package declares no app id (no himi-package.json and no manifest.app)");
47075
47278
  if (!isSafeAppId(app)) throw new Error(`package declares an unsafe app id ${JSON.stringify(app)} \u2014 refusing to unpack it`);
47076
47279
  const root = resolve37(dest);
47077
- const appDir2 = join43(root, app);
47280
+ const appDir2 = join44(root, app);
47078
47281
  rmSync9(appDir2, { recursive: true, force: true });
47079
47282
  for (const [rel, bytes2] of entries) {
47080
- const target = join43(appDir2, rel);
47283
+ const target = join44(appDir2, rel);
47081
47284
  mkdirSync21(dirname26(target), { recursive: true });
47082
47285
  writeFileSync17(target, bytes2);
47083
47286
  }
@@ -47113,20 +47316,20 @@ __export(app_source_exports, {
47113
47316
  writeConflicts: () => writeConflicts,
47114
47317
  zipAppSource: () => zipAppSource
47115
47318
  });
47116
- import { readdirSync as readdirSync26, lstatSync as lstatSync8, readFileSync as readFileSync50, mkdirSync as mkdirSync22, writeFileSync as writeFileSync18, existsSync as existsSync46, rmSync as rmSync10, rmdirSync as rmdirSync2, unlinkSync as unlinkSync3 } from "node:fs";
47117
- import { join as join44, resolve as resolve38, sep as sep10 } from "node:path";
47319
+ import { readdirSync as readdirSync26, lstatSync as lstatSync8, readFileSync as readFileSync51, mkdirSync as mkdirSync22, writeFileSync as writeFileSync18, existsSync as existsSync47, rmSync as rmSync10, rmdirSync as rmdirSync2, unlinkSync as unlinkSync3 } from "node:fs";
47320
+ import { join as join45, resolve as resolve38, sep as sep10 } from "node:path";
47118
47321
  import { deflateRawSync as deflateRawSync2 } from "node:zlib";
47119
47322
  function walkSourceTree(dir, file, skip) {
47120
47323
  const walk2 = (relDir) => {
47121
- for (const name of readdirSync26(join44(dir, relDir))) {
47122
- const rel = relDir ? join44(relDir, name) : name;
47123
- const st = lstatSync8(join44(dir, rel));
47324
+ for (const name of readdirSync26(join45(dir, relDir))) {
47325
+ const rel = relDir ? join45(relDir, name) : name;
47326
+ const st = lstatSync8(join45(dir, rel));
47124
47327
  if (st.isSymbolicLink()) {
47125
47328
  skip(rel);
47126
47329
  continue;
47127
47330
  }
47128
47331
  if (st.isDirectory()) {
47129
- if (isDeniedSourceDir(name, relDir) || existsSync46(join44(dir, rel, PACKAGE_DIR_MARKER))) skip(rel);
47332
+ if (isDeniedSourceDir(name, relDir) || existsSync47(join45(dir, rel, PACKAGE_DIR_MARKER))) skip(rel);
47130
47333
  else walk2(rel);
47131
47334
  continue;
47132
47335
  }
@@ -47144,7 +47347,7 @@ function collectAppSource(dir) {
47144
47347
  const skipped = [];
47145
47348
  walkSourceTree(
47146
47349
  dir,
47147
- (archivePath, hostRel) => entries.push({ path: archivePath, bytes: readFileSync50(join44(dir, hostRel)) }),
47350
+ (archivePath, hostRel) => entries.push({ path: archivePath, bytes: readFileSync51(join45(dir, hostRel)) }),
47148
47351
  (hostRel) => skipped.push(hostRel)
47149
47352
  );
47150
47353
  entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
@@ -47198,13 +47401,13 @@ function writeAppSource(entries, destDir) {
47198
47401
  const segments = entry.path.split("/");
47199
47402
  let dir = root;
47200
47403
  for (const segment of segments.slice(0, -1)) {
47201
- dir = join44(dir, segment);
47404
+ dir = join45(dir, segment);
47202
47405
  const kind = pathKind(dir);
47203
47406
  if (kind?.isSymbolicLink()) unlinkSync3(dir);
47204
47407
  else if (kind) continue;
47205
47408
  mkdirSync22(dir);
47206
47409
  }
47207
- const target = join44(root, ...segments);
47410
+ const target = join45(root, ...segments);
47208
47411
  if (pathKind(target)?.isSymbolicLink()) unlinkSync3(target);
47209
47412
  writeFileSync18(target, entry.bytes);
47210
47413
  written.push(entry.path);
@@ -47212,7 +47415,7 @@ function writeAppSource(entries, destDir) {
47212
47415
  return written;
47213
47416
  }
47214
47417
  function staleSourcePaths(entries, destDir) {
47215
- if (!existsSync46(destDir)) return [];
47418
+ if (!existsSync47(destDir)) return [];
47216
47419
  const archived = new Set(entries.map((e) => e.path));
47217
47420
  const stale = [];
47218
47421
  walkSourceTree(destDir, (archivePath) => {
@@ -47239,7 +47442,7 @@ function writeConflicts(entries, destDir) {
47239
47442
  let dir = root;
47240
47443
  let stop = false;
47241
47444
  for (const [i, segment] of segments.slice(0, -1).entries()) {
47242
- dir = join44(dir, segment);
47445
+ dir = join45(dir, segment);
47243
47446
  const kind2 = pathKind(dir);
47244
47447
  if (!kind2 || kind2.isSymbolicLink()) {
47245
47448
  stop = true;
@@ -47251,7 +47454,7 @@ function writeConflicts(entries, destDir) {
47251
47454
  break;
47252
47455
  }
47253
47456
  if (stop) continue;
47254
- const kind = pathKind(join44(root, ...segments));
47457
+ const kind = pathKind(join45(root, ...segments));
47255
47458
  if (kind && !kind.isSymbolicLink() && kind.isDirectory()) {
47256
47459
  say(entry.path, "is a directory here, but the release has a file at that path");
47257
47460
  }
@@ -47261,22 +47464,22 @@ function writeConflicts(entries, destDir) {
47261
47464
  function removeStaleSource(entries, destDir) {
47262
47465
  const root = resolve38(destDir);
47263
47466
  const removed = staleSourcePaths(entries, root);
47264
- for (const rel of removed) rmSync10(join44(root, ...rel.split("/")), { force: true });
47467
+ for (const rel of removed) rmSync10(join45(root, ...rel.split("/")), { force: true });
47265
47468
  const needed = /* @__PURE__ */ new Set();
47266
47469
  for (const entry of entries) {
47267
47470
  if (isDeniedSourcePath(entry.path)) continue;
47268
47471
  const segments = entry.path.split("/");
47269
47472
  for (let i = 1; i < segments.length; i++) needed.add(segments.slice(0, i).join("/"));
47270
47473
  }
47271
- if (existsSync46(root)) pruneEmptyDirs(root, "", needed, removed);
47474
+ if (existsSync47(root)) pruneEmptyDirs(root, "", needed, removed);
47272
47475
  return removed.sort();
47273
47476
  }
47274
47477
  function pruneEmptyDirs(root, rel, needed, removed) {
47275
47478
  let empty = true;
47276
- for (const name of readdirSync26(rel ? join44(root, rel) : root)) {
47277
- const childRel = rel ? join44(rel, name) : name;
47479
+ for (const name of readdirSync26(rel ? join45(root, rel) : root)) {
47480
+ const childRel = rel ? join45(rel, name) : name;
47278
47481
  const posix4 = childRel.split(sep10).join("/");
47279
- const kind = lstatSync8(join44(root, childRel));
47482
+ const kind = lstatSync8(join45(root, childRel));
47280
47483
  if (kind.isSymbolicLink() || !kind.isDirectory() || isDeniedSourceDir(name, rel)) {
47281
47484
  empty = false;
47282
47485
  continue;
@@ -47286,7 +47489,7 @@ function pruneEmptyDirs(root, rel, needed, removed) {
47286
47489
  empty = false;
47287
47490
  continue;
47288
47491
  }
47289
- rmdirSync2(join44(root, childRel));
47492
+ rmdirSync2(join45(root, childRel));
47290
47493
  removed.push(`${posix4}/`);
47291
47494
  }
47292
47495
  return empty;
@@ -47341,13 +47544,13 @@ __export(eject_exports, {
47341
47544
  EJECTABLE: () => EJECTABLE,
47342
47545
  ejectModule: () => ejectModule
47343
47546
  });
47344
- import { existsSync as existsSync47, mkdirSync as mkdirSync23, readFileSync as readFileSync51, writeFileSync as writeFileSync19 } from "node:fs";
47345
- import { dirname as dirname27, join as join45, relative as relative8, resolve as resolve39, sep as sep11 } from "node:path";
47547
+ import { existsSync as existsSync48, mkdirSync as mkdirSync23, readFileSync as readFileSync52, writeFileSync as writeFileSync19 } from "node:fs";
47548
+ import { dirname as dirname27, join as join46, relative as relative9, resolve as resolve39, sep as sep11 } from "node:path";
47346
47549
  function locate(repoRootOrCwd, mod) {
47347
47550
  const inRepo = resolve39(repoRootOrCwd, `stdlib/flows/${mod}/src/index.ts`);
47348
- if (existsSync47(inRepo)) return { path: inRepo, form: "source" };
47551
+ if (existsSync48(inRepo)) return { path: inRepo, form: "source" };
47349
47552
  const dist = resolve39(repoRootOrCwd, `node_modules/@wix/himalaya/dist/${mod}.mjs`);
47350
- if (existsSync47(dist)) return { path: dist, form: "bundle" };
47553
+ if (existsSync48(dist)) return { path: dist, form: "bundle" };
47351
47554
  return null;
47352
47555
  }
47353
47556
  function specifiersFor(mod) {
@@ -47371,7 +47574,7 @@ function ejectModule(opts) {
47371
47574
  };
47372
47575
  }
47373
47576
  const ext = found.form === "source" ? "ts" : "mjs";
47374
- const target = join45(contentDir2, "tier5-src", "_ejected", `${mod}.${ext}`);
47577
+ const target = join46(contentDir2, "tier5-src", "_ejected", `${mod}.${ext}`);
47375
47578
  mkdirSync23(dirname27(target), { recursive: true });
47376
47579
  const banner = `// EJECTED from @wix/himalaya/${mod}. This app owns this file now.
47377
47580
  //
@@ -47385,17 +47588,17 @@ function ejectModule(opts) {
47385
47588
  // gate still applies. Ejecting buys layout and flow, never the floor.
47386
47589
 
47387
47590
  `;
47388
- const normalized = readFileSync51(found.path, "utf8").replace(
47591
+ const normalized = readFileSync52(found.path, "utf8").replace(
47389
47592
  /(["'])@himalaya\/([a-z0-9-]+(?:\/[a-z0-9-]+)?)\1/g,
47390
47593
  (_m, q, sub) => `${q}@wix/himalaya/${sub}${q}`
47391
47594
  );
47392
47595
  writeFileSync19(target, banner + normalized);
47393
47596
  const rewired = [];
47394
47597
  for (const f of files) {
47395
- if (!existsSync47(f)) continue;
47396
- const before = readFileSync51(f, "utf8");
47598
+ if (!existsSync48(f)) continue;
47599
+ const before = readFileSync52(f, "utf8");
47397
47600
  let after = before;
47398
- let rel = relative8(dirname27(f), target).split(sep11).join("/").replace(/\.tsx?$/, ".js");
47601
+ let rel = relative9(dirname27(f), target).split(sep11).join("/").replace(/\.tsx?$/, ".js");
47399
47602
  if (!rel.startsWith(".")) rel = `./${rel}`;
47400
47603
  for (const spec of specifiersFor(mod)) {
47401
47604
  const q = spec.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
@@ -47513,8 +47716,8 @@ var init_app_map = __esm({
47513
47716
  });
47514
47717
 
47515
47718
  // src/wix-writes.ts
47516
- import { existsSync as existsSync48, readFileSync as readFileSync52 } from "node:fs";
47517
- import { join as join46 } from "node:path";
47719
+ import { existsSync as existsSync49, readFileSync as readFileSync53 } from "node:fs";
47720
+ import { join as join47 } from "node:path";
47518
47721
  function isProvenRead(method, path2) {
47519
47722
  if (/[#\x00-\x1F\x7F]/.test(path2)) return false;
47520
47723
  const verb = method.toUpperCase();
@@ -47595,11 +47798,11 @@ function parseDataAccess(text5, from) {
47595
47798
  }
47596
47799
  function readDataAccess(dir) {
47597
47800
  for (const name of ["SPEC.md", "MOBILE-UX.md"]) {
47598
- const p = join46(dir, name);
47599
- if (!existsSync48(p)) continue;
47801
+ const p = join47(dir, name);
47802
+ if (!existsSync49(p)) continue;
47600
47803
  let text5;
47601
47804
  try {
47602
- text5 = readFileSync52(p, "utf8");
47805
+ text5 = readFileSync53(p, "utf8");
47603
47806
  } catch {
47604
47807
  continue;
47605
47808
  }
@@ -47706,14 +47909,14 @@ __export(test_exports, {
47706
47909
  scopeDescriptors: () => scopeDescriptors,
47707
47910
  writeRecordedMocks: () => writeRecordedMocks
47708
47911
  });
47709
- import { existsSync as existsSync49, mkdirSync as mkdirSync24, readFileSync as readFileSync53, writeFileSync as writeFileSync20 } from "node:fs";
47710
- import { join as join47, resolve as resolve40 } from "node:path";
47912
+ import { existsSync as existsSync50, mkdirSync as mkdirSync24, readFileSync as readFileSync54, writeFileSync as writeFileSync20 } from "node:fs";
47913
+ import { join as join48, resolve as resolve40 } from "node:path";
47711
47914
  function readTestConfig(dir) {
47712
47915
  const p = resolve40(dir, TEST_CONFIG_FILE);
47713
- if (!existsSync49(p)) return {};
47916
+ if (!existsSync50(p)) return {};
47714
47917
  let parsed;
47715
47918
  try {
47716
- parsed = JSON.parse(readFileSync53(p, "utf8"));
47919
+ parsed = JSON.parse(readFileSync54(p, "utf8"));
47717
47920
  } catch (e) {
47718
47921
  throw new Error(`${TEST_CONFIG_FILE} is not valid JSON \u2014 ${e.message}`);
47719
47922
  }
@@ -47760,7 +47963,7 @@ function readTestConfig(dir) {
47760
47963
  function readNetRules(path2) {
47761
47964
  let parsed;
47762
47965
  try {
47763
- parsed = JSON.parse(readFileSync53(path2, "utf8"));
47966
+ parsed = JSON.parse(readFileSync54(path2, "utf8"));
47764
47967
  } catch (e) {
47765
47968
  throw new Error(`--net ${path2} could not be read as JSON \u2014 ${e.message}`);
47766
47969
  }
@@ -47781,7 +47984,7 @@ function readNetRules(path2) {
47781
47984
  function defaultNetRules(dir) {
47782
47985
  for (const rel of ["dev/net-mocks.json", "tests/net-mocks.json"]) {
47783
47986
  const p = resolve40(dir, rel);
47784
- if (existsSync49(p)) {
47987
+ if (existsSync50(p)) {
47785
47988
  const rules = readNetRules(p);
47786
47989
  if (rules.length) return { rules, from: rel };
47787
47990
  }
@@ -47797,7 +48000,7 @@ function readCannedResponses() {
47797
48000
  ];
47798
48001
  for (const url of candidates) {
47799
48002
  try {
47800
- const parsed = JSON.parse(readFileSync53(url, "utf8"));
48003
+ const parsed = JSON.parse(readFileSync54(url, "utf8"));
47801
48004
  const byKey = {};
47802
48005
  for (const [k, v] of Object.entries(parsed)) {
47803
48006
  if (!k.startsWith("//")) byKey[k] = v;
@@ -47887,8 +48090,8 @@ function writeRecordedMocks(opts) {
47887
48090
  mkdir: (p) => mkdirSync24(p, { recursive: true }),
47888
48091
  write: (p, s) => writeFileSync20(p, s)
47889
48092
  };
47890
- fs.mkdir(join47(opts.dir, "dev"));
47891
- fs.write(join47(opts.dir, "dev", "net-mocks.json"), JSON.stringify(opts.mocks, null, 2) + "\n");
48093
+ fs.mkdir(join48(opts.dir, "dev"));
48094
+ fs.write(join48(opts.dir, "dev", "net-mocks.json"), JSON.stringify(opts.mocks, null, 2) + "\n");
47892
48095
  return { ok: true, rules, to: "dev/net-mocks.json" };
47893
48096
  }
47894
48097
  function scopeDescriptors(descriptors, requestedIds, invalidScreens) {
@@ -48391,14 +48594,14 @@ var mobile_ux_lint_exports = {};
48391
48594
  __export(mobile_ux_lint_exports, {
48392
48595
  mobileUxIssues: () => mobileUxIssues
48393
48596
  });
48394
- import { existsSync as existsSync50, readFileSync as readFileSync54 } from "node:fs";
48395
- import { join as join48 } from "node:path";
48597
+ import { existsSync as existsSync51, readFileSync as readFileSync55 } from "node:fs";
48598
+ import { join as join49 } from "node:path";
48396
48599
  function mobileUxIssues(dir, strict) {
48397
- const file = join48(dir, "MOBILE-UX.md");
48398
- if (!existsSync50(file)) return [];
48600
+ const file = join49(dir, "MOBILE-UX.md");
48601
+ if (!existsSync51(file)) return [];
48399
48602
  let text5;
48400
48603
  try {
48401
- text5 = readFileSync54(file, "utf8");
48604
+ text5 = readFileSync55(file, "utf8");
48402
48605
  } catch {
48403
48606
  return [];
48404
48607
  }
@@ -50073,8 +50276,8 @@ __export(functions_exports, {
50073
50276
  resolveFunctionsTarget: () => resolveFunctionsTarget,
50074
50277
  runRemoteFunctionOperation: () => runRemoteFunctionOperation
50075
50278
  });
50076
- import { readFileSync as readFileSync55, existsSync as existsSync51 } from "node:fs";
50077
- import { join as join49, resolve as resolve41 } from "node:path";
50279
+ import { readFileSync as readFileSync56, existsSync as existsSync52 } from "node:fs";
50280
+ import { join as join50, resolve as resolve41 } from "node:path";
50078
50281
  function resolveFunctionsTarget(input) {
50079
50282
  const explicitUrl = input.functionsUrl?.trim();
50080
50283
  if (explicitUrl) return { mode: "remote", baseUrl: explicitUrl };
@@ -50093,9 +50296,9 @@ async function loadFunctionsApp(dir) {
50093
50296
  }
50094
50297
  const sources = {};
50095
50298
  for (const definition of config.functions.functions) {
50096
- const source = [".ts", ".js", ".mts", ".mjs"].map((extension) => join49(loaded.dir, "functions", `${definition.name}${extension}`)).find(existsSync51);
50299
+ const source = [".ts", ".js", ".mts", ".mjs"].map((extension) => join50(loaded.dir, "functions", `${definition.name}${extension}`)).find(existsSync52);
50097
50300
  if (!source) throw new Error(`missing source for function ${definition.name}; expected functions/${definition.name}.ts`);
50098
- sources[definition.name] = readFileSync55(source, "utf8");
50301
+ sources[definition.name] = readFileSync56(source, "utf8");
50099
50302
  }
50100
50303
  const data = config.cms || config.data ? mergeDataResources(config) : void 0;
50101
50304
  const dataErrors = validateDataResources(data, loaded.name).filter((issue2) => issue2.severity === "error");
@@ -50255,10 +50458,10 @@ __export(studio_reel_exports, {
50255
50458
  utcDay: () => utcDay
50256
50459
  });
50257
50460
  import { createHash as createHash25 } from "node:crypto";
50258
- import { lstatSync as lstatSync9, readFileSync as readFileSync56 } from "node:fs";
50259
- import { basename as basename12, join as join50 } from "node:path";
50461
+ import { lstatSync as lstatSync9, readFileSync as readFileSync57 } from "node:fs";
50462
+ import { basename as basename13, join as join51 } from "node:path";
50260
50463
  function loadMemory2(dir) {
50261
- const m = readJson5(join50(dir, ".himi", "studio", "memory.json"));
50464
+ const m = readJson5(join51(dir, ".himi", "studio", "memory.json"));
50262
50465
  if (!isObj2(m) || m.version !== 1 || !isObj2(m.shots) || !Array.isArray(m.timeline)) return null;
50263
50466
  const shots = [];
50264
50467
  for (const [id, s] of Object.entries(m.shots)) {
@@ -50295,8 +50498,8 @@ function planReel(dir) {
50295
50498
  const memory = loadMemory2(dir);
50296
50499
  const { entries } = readProgress(dir);
50297
50500
  const summary = summarizeProgress(entries);
50298
- const manifest = readJson5(join50(dir, "himalaya.content.json"));
50299
- const tokens = readJson5(join50(dir, "tokens.json"));
50501
+ const manifest = readJson5(join51(dir, "himalaya.content.json"));
50502
+ const tokens = readJson5(join51(dir, "tokens.json"));
50300
50503
  const accentToken = tokens?.colors?.accentPrimary;
50301
50504
  const accentRaw = typeof accentToken === "string" ? accentToken : accentToken?.light;
50302
50505
  const accent = typeof accentRaw === "string" && ACCENT.test(accentRaw) ? accentRaw : DEFAULT_ACCENT;
@@ -50310,14 +50513,15 @@ function planReel(dir) {
50310
50513
  const postcards = entries.map((e) => ({
50311
50514
  ts: e.ts,
50312
50515
  message: e.message,
50313
- step: str(e.step),
50314
- files: (Array.isArray(e.images) ? e.images : []).map((i) => isObj2(i) && typeof i.file === "string" ? basename12(i.file) : "").filter((f) => HISTORY_NAME.test(f))
50516
+ step: isCliEntry(e) ? null : str(e.step),
50517
+ by: isCliEntry(e) ? CLI_AUTHOR : null,
50518
+ files: (Array.isArray(e.images) ? e.images : []).map((i) => isObj2(i) && typeof i.file === "string" ? basename13(i.file) : "").filter((f) => HISTORY_NAME.test(f))
50315
50519
  }));
50316
50520
  const times = [summary.startedAt, ...timeline.map((t) => t.ts), memory?.createdAt].filter((x) => !!x).sort();
50317
50521
  const startedAt = times[0] ?? null;
50318
50522
  const endedAt = [summary.lastAt, ...timeline.map((t) => t.ts)].filter((x) => !!x).sort().pop() ?? null;
50319
50523
  return {
50320
- app: typeof manifest?.name === "string" && manifest.name ? manifest.name : basename12(dir),
50524
+ app: typeof manifest?.name === "string" && manifest.name ? manifest.name : basename13(dir),
50321
50525
  accent,
50322
50526
  startedAt,
50323
50527
  endedAt,
@@ -50338,10 +50542,10 @@ function inlineImages(dir, plan, budget = REEL_BUDGET_BYTES) {
50338
50542
  const verified = (f) => {
50339
50543
  if (!HISTORY_NAME.test(f)) return null;
50340
50544
  try {
50341
- const path2 = join50(dir, HISTORY_DIR, f);
50545
+ const path2 = join51(dir, HISTORY_DIR, f);
50342
50546
  const st = lstatSync9(path2);
50343
50547
  if (!st.isFile() || st.nlink !== 1) return null;
50344
- const bytes2 = readFileSync56(path2);
50548
+ const bytes2 = readFileSync57(path2);
50345
50549
  return createHash25("sha256").update(bytes2).digest("hex") === f.slice(0, 64) && imageExtension(bytes2) === f.slice(65) ? bytes2 : null;
50346
50550
  } catch {
50347
50551
  return null;
@@ -50435,7 +50639,7 @@ function renderReel(plan, images) {
50435
50639
  .flipcap{flex:1;min-width:220px}.flipcap .t{font-size:40px;font-weight:700;font-variant-numeric:tabular-nums;color:var(--accent)}.bar{height:4px;background:var(--line);border-radius:2px;margin-top:16px}.bar div{height:4px;background:var(--accent);border-radius:2px;width:0}
50436
50640
  .grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:18px}.grid figure{margin:0;text-align:center}.grid img{width:100%;aspect-ratio:390/844;object-fit:cover;object-position:top;border:6px solid #111;border-radius:22px;background:#fff}
50437
50641
  figcaption{font-size:12px;color:var(--muted);margin-top:6px;word-break:break-all}
50438
- .log{list-style:none;padding:0;margin:0}.log li{display:flex;gap:14px;padding:12px 0;border-top:1px solid var(--line)}.log .t{min-width:52px;color:var(--muted);font-variant-numeric:tabular-nums}.log .s{font-size:12px;color:var(--muted);text-transform:capitalize}
50642
+ .log{list-style:none;padding:0;margin:0}.log li{display:flex;gap:14px;padding:12px 0;border-top:1px solid var(--line)}.log .t{min-width:52px;color:var(--muted);font-variant-numeric:tabular-nums}.log .s{font-size:12px;color:var(--muted);text-transform:capitalize}.log .by{font-size:12px;color:var(--muted)}
50439
50643
  .log .imgs{display:flex;gap:8px;margin-top:8px;flex-wrap:wrap}.log .imgs img{height:120px;border-radius:10px;border:1px solid var(--line)}
50440
50644
  footer{margin-top:48px;font-size:12px;color:var(--muted)}
50441
50645
  </style></head><body><div class="wrap">
@@ -50446,7 +50650,7 @@ ${flip}
50446
50650
  <h2>Where it ended up</h2>
50447
50651
  <div class="grid">${plan.finals.map((f) => `<figure>${img(f.file, f.id)}<figcaption>${escapeHtml(f.id)}</figcaption></figure>`).join("")}</div>
50448
50652
  <h2>Postcards</h2>
50449
- <ul class="log">${plan.postcards.map((c) => `<li><div class="t">${clock(c.ts)}</div><div><div>${escapeHtml(c.message)}</div>${c.step ? `<div class="s">step ${JOURNEY_STEPS.indexOf(c.step) + 1} \xB7 ${escapeHtml(c.step)}</div>` : ""}${c.files.length ? `<div class="imgs">${c.files.map((f) => img(f, c.message)).join("")}</div>` : ""}</div></li>`).join("") || `<li class="muted">No postcards were journaled.</li>`}</ul>
50653
+ <ul class="log">${plan.postcards.map((c) => `<li><div class="t">${clock(c.ts)}</div><div><div>${escapeHtml(c.message)}</div>${c.step ? `<div class="s">step ${JOURNEY_STEPS.indexOf(c.step) + 1} \xB7 ${escapeHtml(c.step)}</div>` : ""}${c.by ? `<div class="by">noted by ${escapeHtml(c.by)}</div>` : ""}${c.files.length ? `<div class="imgs">${c.files.map((f) => img(f, c.message)).join("")}</div>` : ""}</div></li>`).join("") || `<li class="muted">No postcards were journaled.</li>`}</ul>
50450
50654
  <footer>Built with himi.${plan.dropped ? ` ${count2(plan.dropped)} pictures were left out to keep this file small.` : ""}</footer>
50451
50655
  </div>
50452
50656
  <script>
@@ -50475,7 +50679,7 @@ var init_studio_reel = __esm({
50475
50679
  mimeOf = (file) => MIME2[file.slice(65)] ?? "image/png";
50476
50680
  readJson5 = (file) => {
50477
50681
  try {
50478
- return JSON.parse(readFileSync56(file, "utf8"));
50682
+ return JSON.parse(readFileSync57(file, "utf8"));
50479
50683
  } catch {
50480
50684
  return null;
50481
50685
  }
@@ -50506,9 +50710,9 @@ __export(native_decode_exports, {
50506
50710
  runNativeDecode: () => runNativeDecode
50507
50711
  });
50508
50712
  import { execFileSync as execFileSync5, spawnSync as spawnSync2 } from "node:child_process";
50509
- import { existsSync as existsSync52, mkdtempSync as mkdtempSync2, readFileSync as readFileSync57, rmSync as rmSync11, writeFileSync as writeFileSync21 } from "node:fs";
50713
+ import { existsSync as existsSync53, mkdtempSync as mkdtempSync2, readFileSync as readFileSync58, rmSync as rmSync11, writeFileSync as writeFileSync21 } from "node:fs";
50510
50714
  import { tmpdir as tmpdir3 } from "node:os";
50511
- import { join as join51 } from "node:path";
50715
+ import { join as join52 } from "node:path";
50512
50716
  import { fileURLToPath as fileURLToPath20 } from "node:url";
50513
50717
  function nativeDecodePackageDir() {
50514
50718
  return fileURLToPath20(new URL("../../../core/runtime/ios/HimalayaCore/", import.meta.url));
@@ -50518,8 +50722,8 @@ function nativeDecodeAndroidHarnessDir() {
50518
50722
  }
50519
50723
  function nativeOracleShipped() {
50520
50724
  return {
50521
- ios: existsSync52(join51(nativeDecodePackageDir(), "Package.swift")),
50522
- android: existsSync52(join51(nativeDecodeAndroidHarnessDir(), "gradlew"))
50725
+ ios: existsSync53(join52(nativeDecodePackageDir(), "Package.swift")),
50726
+ android: existsSync53(join52(nativeDecodeAndroidHarnessDir(), "gradlew"))
50523
50727
  };
50524
50728
  }
50525
50729
  function detectNativeDecode(options = {}) {
@@ -50530,7 +50734,7 @@ function detectNativeDecode(options = {}) {
50530
50734
  return { platform: "ios", available: false, reason: `the iOS decoder oracle requires macOS (darwin); this host is ${hostPlatform}` };
50531
50735
  }
50532
50736
  if (!swiftAvailable) return { platform: "ios", available: false, reason: "swift is not on PATH" };
50533
- const packageAvailable = options.packageAvailable ?? existsSync52(join51(packageDir, "Package.swift"));
50737
+ const packageAvailable = options.packageAvailable ?? existsSync53(join52(packageDir, "Package.swift"));
50534
50738
  if (!packageAvailable) {
50535
50739
  return {
50536
50740
  platform: "ios",
@@ -50542,7 +50746,7 @@ function detectNativeDecode(options = {}) {
50542
50746
  }
50543
50747
  function localPropertiesSdk(harnessDir) {
50544
50748
  try {
50545
- const match = readFileSync57(join51(harnessDir, "local.properties"), "utf8").match(/^sdk\.dir\s*=\s*(.+)$/m);
50749
+ const match = readFileSync58(join52(harnessDir, "local.properties"), "utf8").match(/^sdk\.dir\s*=\s*(.+)$/m);
50546
50750
  return match?.[1]?.trim().replace(/\\([ :\\])/g, "$1") ?? null;
50547
50751
  } catch {
50548
50752
  return null;
@@ -50550,8 +50754,8 @@ function localPropertiesSdk(harnessDir) {
50550
50754
  }
50551
50755
  function detectAndroidNativeDecode(options = {}) {
50552
50756
  const harnessDir = options.harnessDir ?? nativeDecodeAndroidHarnessDir();
50553
- const gradlew = join51(harnessDir, "gradlew");
50554
- const gradlewAvailable = options.gradlewAvailable ?? existsSync52(gradlew);
50757
+ const gradlew = join52(harnessDir, "gradlew");
50758
+ const gradlewAvailable = options.gradlewAvailable ?? existsSync53(gradlew);
50555
50759
  if (!gradlewAvailable) {
50556
50760
  return {
50557
50761
  platform: "android",
@@ -50561,7 +50765,7 @@ function detectAndroidNativeDecode(options = {}) {
50561
50765
  }
50562
50766
  const androidHome = options.androidHome ?? process.env.ANDROID_HOME ?? "";
50563
50767
  const sdk = androidHome.trim() || (options.localPropertiesSdk !== void 0 ? options.localPropertiesSdk : localPropertiesSdk(harnessDir));
50564
- const sdkAvailable = options.sdkAvailable ?? (typeof sdk === "string" && sdk.length > 0 && existsSync52(sdk));
50768
+ const sdkAvailable = options.sdkAvailable ?? (typeof sdk === "string" && sdk.length > 0 && existsSync53(sdk));
50565
50769
  if (!sdkAvailable) {
50566
50770
  return {
50567
50771
  platform: "android",
@@ -50633,14 +50837,14 @@ function parseNativeDecodeOutput(stdout, expectedScreenIds, platform = "ios") {
50633
50837
  return verdicts;
50634
50838
  }
50635
50839
  function runNativeDecode(options) {
50636
- const descriptorDir = mkdtempSync2(join51(tmpdir3(), "himi-native-decode-"));
50840
+ const descriptorDir = mkdtempSync2(join52(tmpdir3(), "himi-native-decode-"));
50637
50841
  const startedAt = Date.now();
50638
50842
  try {
50639
50843
  for (const descriptor of options.descriptors) {
50640
50844
  if (typeof descriptor.descriptorJson !== "string") {
50641
50845
  throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
50642
50846
  }
50643
- writeFileSync21(join51(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
50847
+ writeFileSync21(join52(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
50644
50848
  }
50645
50849
  options.progress?.("building cached Swift oracle");
50646
50850
  execFileSync5("swift", ["build", "--package-path", options.packageDir, "--product", "himi-decode-oracle"], {
@@ -50652,7 +50856,7 @@ function runNativeDecode(options) {
50652
50856
  encoding: "utf8"
50653
50857
  }).trim();
50654
50858
  options.progress?.(`running real Swift decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
50655
- const stdout = execFileSync5(join51(binPath, "himi-decode-oracle"), ["--dir", descriptorDir], {
50859
+ const stdout = execFileSync5(join52(binPath, "himi-decode-oracle"), ["--dir", descriptorDir], {
50656
50860
  stdio: ["ignore", "pipe", "pipe"],
50657
50861
  encoding: "utf8"
50658
50862
  });
@@ -50670,15 +50874,15 @@ function runNativeDecode(options) {
50670
50874
  }
50671
50875
  }
50672
50876
  function runAndroidNativeDecode(options) {
50673
- const descriptorDir = mkdtempSync2(join51(tmpdir3(), "himi-native-decode-android-"));
50674
- const outputPath = join51(descriptorDir, "verdicts.ndjson");
50877
+ const descriptorDir = mkdtempSync2(join52(tmpdir3(), "himi-native-decode-android-"));
50878
+ const outputPath = join52(descriptorDir, "verdicts.ndjson");
50675
50879
  const startedAt = Date.now();
50676
50880
  try {
50677
50881
  for (const descriptor of options.descriptors) {
50678
50882
  if (typeof descriptor.descriptorJson !== "string") {
50679
50883
  throw new Error(`native decode cannot judge ${descriptor.screenId}: the release builder did not return its exact baked JSON bytes`);
50680
50884
  }
50681
- writeFileSync21(join51(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
50885
+ writeFileSync21(join52(descriptorDir, `${encodeURIComponent(descriptor.screenId)}.json`), descriptor.descriptorJson);
50682
50886
  }
50683
50887
  options.progress?.(`running real Kotlin decoder over ${options.descriptors.length} screen${options.descriptors.length === 1 ? "" : "s"}`);
50684
50888
  execFileSync5(options.gradlew, [
@@ -50693,7 +50897,7 @@ function runAndroidNativeDecode(options) {
50693
50897
  stdio: ["ignore", "ignore", "pipe"],
50694
50898
  encoding: "utf8"
50695
50899
  });
50696
- const stdout = readFileSync57(outputPath, "utf8");
50900
+ const stdout = readFileSync58(outputPath, "utf8");
50697
50901
  return {
50698
50902
  platform: "android",
50699
50903
  verdicts: parseNativeDecodeOutput(stdout, options.descriptors.map((descriptor) => descriptor.screenId), "android"),
@@ -50763,13 +50967,13 @@ __export(browser_authoring_exports, {
50763
50967
  });
50764
50968
  import { createServer as createServer3 } from "node:http";
50765
50969
  import { createHash as createHash26, randomBytes as randomBytes4 } from "node:crypto";
50766
- import { existsSync as existsSync53, readdirSync as readdirSync27, readFileSync as readFileSync58, realpathSync as realpathSync6, statSync as statSync12 } from "node:fs";
50767
- import { join as join52, posix as posix2, relative as relative9, sep as sep12 } from "node:path";
50970
+ import { existsSync as existsSync54, readdirSync as readdirSync27, readFileSync as readFileSync59, realpathSync as realpathSync6, statSync as statSync12 } from "node:fs";
50971
+ import { join as join53, posix as posix2, relative as relative10, sep as sep12 } from "node:path";
50768
50972
  function humanMs(ms) {
50769
50973
  return ms < 1e3 ? `${ms}ms` : `${Math.round(ms / 1e3)}s`;
50770
50974
  }
50771
50975
  function collectWorkerSources(tier5SrcDir, bundleName) {
50772
- const root = join52(tier5SrcDir, bundleName);
50976
+ const root = join53(tier5SrcDir, bundleName);
50773
50977
  const files = {};
50774
50978
  const visitedDirs = /* @__PURE__ */ new Set();
50775
50979
  const walk2 = (abs, rel) => {
@@ -50777,16 +50981,16 @@ function collectWorkerSources(tier5SrcDir, bundleName) {
50777
50981
  if (visitedDirs.has(real)) return;
50778
50982
  visitedDirs.add(real);
50779
50983
  for (const name of readdirSync27(abs).sort()) {
50780
- const childAbs = join52(abs, name);
50984
+ const childAbs = join53(abs, name);
50781
50985
  const childRel = posix2.join(rel, name);
50782
50986
  if (statSync12(childAbs).isDirectory()) {
50783
50987
  walk2(childAbs, childRel);
50784
50988
  } else if (/\.(ts|tsx|js|mjs|json)$/.test(name)) {
50785
- files[childRel] = readFileSync58(childAbs, "utf8");
50989
+ files[childRel] = readFileSync59(childAbs, "utf8");
50786
50990
  }
50787
50991
  }
50788
50992
  };
50789
- if (!existsSync53(root)) return null;
50993
+ if (!existsSync54(root)) return null;
50790
50994
  walk2(root, `/tier5-src/${bundleName}`);
50791
50995
  const entry = [`/tier5-src/${bundleName}/index.ts`, `/tier5-src/${bundleName}/index.js`].find((p) => p in files);
50792
50996
  if (!entry) return null;
@@ -50803,20 +51007,20 @@ function collectRelativeSiblings(files, tier5SrcDir) {
50803
51007
  const spec = m[1];
50804
51008
  const virtual = posix2.normalize(posix2.join(dir, spec));
50805
51009
  if (!virtual.startsWith("/tier5-src/")) continue;
50806
- const onDisk = join52(tier5SrcDir, virtual.slice("/tier5-src/".length));
51010
+ const onDisk = join53(tier5SrcDir, virtual.slice("/tier5-src/".length));
50807
51011
  const candidates = [
50808
51012
  onDisk,
50809
51013
  `${onDisk}.ts`,
50810
51014
  `${onDisk}.js`,
50811
51015
  onDisk.replace(/\.js$/, ".ts"),
50812
- join52(onDisk, "index.ts"),
50813
- join52(onDisk, "index.js")
51016
+ join53(onDisk, "index.ts"),
51017
+ join53(onDisk, "index.js")
50814
51018
  ];
50815
51019
  for (const abs of candidates) {
50816
- if (!existsSync53(abs) || statSync12(abs).isDirectory()) continue;
50817
- const key2 = `/tier5-src/${relative9(tier5SrcDir, abs).split(sep12).join(posix2.sep)}`;
51020
+ if (!existsSync54(abs) || statSync12(abs).isDirectory()) continue;
51021
+ const key2 = `/tier5-src/${relative10(tier5SrcDir, abs).split(sep12).join(posix2.sep)}`;
50818
51022
  if (key2 in files) break;
50819
- files[key2] = readFileSync58(abs, "utf8");
51023
+ files[key2] = readFileSync59(abs, "utf8");
50820
51024
  queue.push(key2);
50821
51025
  break;
50822
51026
  }
@@ -50851,7 +51055,7 @@ function collectSdkModules(files, resolveSubpath) {
50851
51055
  function sdkSubpathReader(resolve48) {
50852
51056
  return (sub) => {
50853
51057
  try {
50854
- return readFileSync58(resolve48(`@wix/himalaya/${sub}`), "utf8");
51058
+ return readFileSync59(resolve48(`@wix/himalaya/${sub}`), "utf8");
50855
51059
  } catch {
50856
51060
  return null;
50857
51061
  }
@@ -51023,8 +51227,8 @@ __export(coverage_exports, {
51023
51227
  suppressedNativeKeys: () => suppressedNativeKeys,
51024
51228
  trendAndStore: () => trendAndStore
51025
51229
  });
51026
- import { existsSync as existsSync54, mkdirSync as mkdirSync25, readFileSync as readFileSync59, writeFileSync as writeFileSync22 } from "node:fs";
51027
- import { dirname as dirname28, join as join53 } from "node:path";
51230
+ import { existsSync as existsSync55, mkdirSync as mkdirSync25, readFileSync as readFileSync60, writeFileSync as writeFileSync22 } from "node:fs";
51231
+ import { dirname as dirname28, join as join54 } from "node:path";
51028
51232
  function suppressedNativeKeys(oracle) {
51029
51233
  return [...oracle.ios ? [] : ["native-decode|ios"], ...oracle.android ? [] : ["native-decode|android"]];
51030
51234
  }
@@ -51204,11 +51408,11 @@ function assembleLedger(r, options = {}) {
51204
51408
  return out;
51205
51409
  }
51206
51410
  function trendAndStore(dir, entries, suppressed) {
51207
- const p = join53(dir, LEDGER_FILE);
51411
+ const p = join54(dir, LEDGER_FILE);
51208
51412
  let previous = null;
51209
51413
  try {
51210
- if (existsSync54(p)) {
51211
- const parsed = JSON.parse(readFileSync59(p, "utf8"));
51414
+ if (existsSync55(p)) {
51415
+ const parsed = JSON.parse(readFileSync60(p, "utf8"));
51212
51416
  if (Array.isArray(parsed.keys)) previous = parsed.keys.filter((k) => typeof k === "string");
51213
51417
  }
51214
51418
  } catch {
@@ -51247,11 +51451,11 @@ function formatLedger(entries, trend) {
51247
51451
  return out.join("\n");
51248
51452
  }
51249
51453
  function diffAndStoreObservations(dir, current) {
51250
- const p = join53(dir, OBS_FILE);
51454
+ const p = join54(dir, OBS_FILE);
51251
51455
  let previous = null;
51252
51456
  try {
51253
- if (existsSync54(p)) {
51254
- const parsed = JSON.parse(readFileSync59(p, "utf8"));
51457
+ if (existsSync55(p)) {
51458
+ const parsed = JSON.parse(readFileSync60(p, "utf8"));
51255
51459
  if (parsed.screens && typeof parsed.screens === "object" && !Array.isArray(parsed.screens) && Object.values(parsed.screens).every(
51256
51460
  (v) => Array.isArray(v) && v.every((l) => typeof l === "string")
51257
51461
  )) {
@@ -51300,8 +51504,8 @@ var LEDGER_FILE, OBS_FILE;
51300
51504
  var init_coverage = __esm({
51301
51505
  "src/coverage.ts"() {
51302
51506
  init_native_decode();
51303
- LEDGER_FILE = join53(".himi", "test-ledger.json");
51304
- OBS_FILE = join53(".himi", "test-observations.json");
51507
+ LEDGER_FILE = join54(".himi", "test-ledger.json");
51508
+ OBS_FILE = join54(".himi", "test-observations.json");
51305
51509
  }
51306
51510
  });
51307
51511
 
@@ -51312,16 +51516,16 @@ __export(design_kits_exports, {
51312
51516
  kitsDir: () => kitsDir,
51313
51517
  listKits: () => listKits2
51314
51518
  });
51315
- import { existsSync as existsSync55, readFileSync as readFileSync60, readdirSync as readdirSync28 } from "node:fs";
51316
- import { join as join54, basename as basename13 } from "node:path";
51519
+ import { existsSync as existsSync56, readFileSync as readFileSync61, readdirSync as readdirSync28 } from "node:fs";
51520
+ import { join as join55, basename as basename14 } from "node:path";
51317
51521
  import { fileURLToPath as fileURLToPath21 } from "node:url";
51318
51522
  function kitsDir() {
51319
- return KIT_DIR_CANDIDATES.find((p) => existsSync55(p));
51523
+ return KIT_DIR_CANDIDATES.find((p) => existsSync56(p));
51320
51524
  }
51321
51525
  function listKits2() {
51322
51526
  const dir = kitsDir();
51323
51527
  if (!dir) return [];
51324
- return readdirSync28(dir).filter((f) => f.endsWith(".json")).map((f) => basename13(f, ".json")).sort();
51528
+ return readdirSync28(dir).filter((f) => f.endsWith(".json")).map((f) => basename14(f, ".json")).sort();
51325
51529
  }
51326
51530
  function kitReport(name) {
51327
51531
  const dir = kitsDir();
@@ -51340,7 +51544,7 @@ In a published @wix/himalaya-cli this is a packaging bug \u2014 the kits should
51340
51544
  if (!kits.includes(name)) {
51341
51545
  return { ok: false, kit: name, kits, problems: [`no design kit named "${name}". Available: ${kits.join(", ")}`] };
51342
51546
  }
51343
- const raw = readFileSync60(join54(dir, `${name}.json`), "utf8");
51547
+ const raw = readFileSync61(join55(dir, `${name}.json`), "utf8");
51344
51548
  let parsed;
51345
51549
  try {
51346
51550
  parsed = JSON.parse(raw);
@@ -51364,9 +51568,9 @@ var init_design_kits = __esm({
51364
51568
  "src/design-kits.ts"() {
51365
51569
  KIT_DIR_CANDIDATES = [
51366
51570
  // in-repo: tools/himi-cli/src/design-kits.ts → <repo>/stdlib/design-kits/
51367
- join54(fileURLToPath21(new URL("../../..", import.meta.url)), "stdlib", "design-kits"),
51571
+ join55(fileURLToPath21(new URL("../../..", import.meta.url)), "stdlib", "design-kits"),
51368
51572
  // published CLI: <pkg>/dist/cli.mjs → <pkg>/stdlib/design-kits/
51369
- join54(fileURLToPath21(new URL("..", import.meta.url)), "stdlib", "design-kits")
51573
+ join55(fileURLToPath21(new URL("..", import.meta.url)), "stdlib", "design-kits")
51370
51574
  ];
51371
51575
  }
51372
51576
  });
@@ -51380,11 +51584,11 @@ __export(assets_exports, {
51380
51584
  removeAssetPayload: () => removeAssetPayload,
51381
51585
  verifyReport: () => verifyReport
51382
51586
  });
51383
- import { readFileSync as readFileSync61 } from "node:fs";
51384
- import { basename as basename14 } from "node:path";
51587
+ import { readFileSync as readFileSync62 } from "node:fs";
51588
+ import { basename as basename15 } from "node:path";
51385
51589
  function declaredAssets(dir) {
51386
51590
  return collectAssets(dir).map((a) => {
51387
- const bytes2 = readFileSync61(a.path);
51591
+ const bytes2 = readFileSync62(a.path);
51388
51592
  return {
51389
51593
  name: a.name,
51390
51594
  sha256: assetSha256(bytes2),
@@ -51461,8 +51665,8 @@ function verifyReport(dir, app, manifest) {
51461
51665
  };
51462
51666
  }
51463
51667
  function addAssetPayload(manifest, filePath, opts) {
51464
- const bytes2 = readFileSync61(filePath);
51465
- const name = opts.name ?? basename14(filePath);
51668
+ const bytes2 = readFileSync62(filePath);
51669
+ const name = opts.name ?? basename15(filePath);
51466
51670
  const sha2565 = assetSha256(bytes2);
51467
51671
  const record2 = {
51468
51672
  sha256: sha2565,
@@ -53136,9 +53340,9 @@ __export(run_exports, {
53136
53340
  runPackage: () => runPackage,
53137
53341
  withoutProdServe: () => withoutProdServe
53138
53342
  });
53139
- import { cpSync as cpSync2, existsSync as existsSync56, mkdirSync as mkdirSync26, mkdtempSync as mkdtempSync3, readFileSync as readFileSync62, rmSync as rmSync12, statSync as statSync13 } from "node:fs";
53343
+ import { cpSync as cpSync2, existsSync as existsSync57, mkdirSync as mkdirSync26, mkdtempSync as mkdtempSync3, readFileSync as readFileSync63, rmSync as rmSync12, statSync as statSync13 } from "node:fs";
53140
53344
  import { networkInterfaces as osNetworkInterfaces, tmpdir as tmpdir4 } from "node:os";
53141
- import { dirname as dirname29, join as join55 } from "node:path";
53345
+ import { dirname as dirname29, join as join56 } from "node:path";
53142
53346
  import { fileURLToPath as fileURLToPath22 } from "node:url";
53143
53347
  function makePackageRunHandler(opts) {
53144
53348
  const store = new FileReleaseStore(opts.releaseRoot);
@@ -53166,9 +53370,9 @@ async function fetchBytes(url) {
53166
53370
  }
53167
53371
  async function resolvePackage(source, workDir) {
53168
53372
  const isUrl2 = /^https?:\/\//i.test(source);
53169
- if (!isUrl2 && existsSync56(source) && statSync13(source).isDirectory()) {
53170
- const appDirManifest = join55(source, "release-manifest.json");
53171
- if (existsSync56(appDirManifest)) {
53373
+ if (!isUrl2 && existsSync57(source) && statSync13(source).isDirectory()) {
53374
+ const appDirManifest = join56(source, "release-manifest.json");
53375
+ if (existsSync57(appDirManifest)) {
53172
53376
  const opened2 = openPackageDir(source);
53173
53377
  if (!opened2.validation.ok) {
53174
53378
  throw new Error(`package is not valid:
@@ -53177,9 +53381,9 @@ async function resolvePackage(source, workDir) {
53177
53381
  const app2 = opened2.header?.app ?? opened2.manifest?.app;
53178
53382
  if (!app2) throw new Error(`${source} has no app id (no himi-package.json and no manifest.app)`);
53179
53383
  if (!isSafeAppId(app2)) throw new Error(`${source} declares an unsafe app id ${JSON.stringify(app2)} \u2014 refusing to open it`);
53180
- const root2 = workDir ?? mkdtempSync3(join55(tmpdir4(), "himi-run-"));
53181
- const target = join55(root2, app2);
53182
- if (join55(source) !== target) {
53384
+ const root2 = workDir ?? mkdtempSync3(join56(tmpdir4(), "himi-run-"));
53385
+ const target = join56(root2, app2);
53386
+ if (join56(source) !== target) {
53183
53387
  mkdirSync26(root2, { recursive: true });
53184
53388
  rmSync12(target, { recursive: true, force: true });
53185
53389
  cpSync2(source, target, { recursive: true });
@@ -53196,8 +53400,8 @@ async function resolvePackage(source, workDir) {
53196
53400
  }
53197
53401
  throw new Error(`${source} is a directory but has no release-manifest.json \u2014 is it a package?`);
53198
53402
  }
53199
- const bytes2 = isUrl2 ? await fetchBytes(source) : readFileSync62(source);
53200
- const root = workDir ?? mkdtempSync3(join55(tmpdir4(), "himi-run-"));
53403
+ const bytes2 = isUrl2 ? await fetchBytes(source) : readFileSync63(source);
53404
+ const root = workDir ?? mkdtempSync3(join56(tmpdir4(), "himi-run-"));
53201
53405
  const { app, appDir: appDir2 } = unpackTo(bytes2, root);
53202
53406
  const opened = openPackageDir(appDir2);
53203
53407
  if (!opened.validation.ok) {
@@ -53220,14 +53424,14 @@ async function loadPreviewAssets() {
53220
53424
  const mod = await Promise.resolve().then(() => (init_preview_assets_generated(), preview_assets_generated_exports));
53221
53425
  const here = dirname29(fileURLToPath22(import.meta.url));
53222
53426
  const candidates = [
53223
- join55(here, "preview-assets.generated.bin"),
53427
+ join56(here, "preview-assets.generated.bin"),
53224
53428
  // published: copied next to the bundled CLI
53225
- join55(here, "../../../packages/serve/src/preview-assets.generated.bin")
53429
+ join56(here, "../../../packages/serve/src/preview-assets.generated.bin")
53226
53430
  // in-repo checkout
53227
53431
  ];
53228
53432
  for (const path2 of candidates) {
53229
- if (!existsSync56(path2)) continue;
53230
- const blob = readFileSync62(path2);
53433
+ if (!existsSync57(path2)) continue;
53434
+ const blob = readFileSync63(path2);
53231
53435
  return mod.assemblePreviewAssetsFromBlob(new Uint8Array(blob.buffer, blob.byteOffset, blob.byteLength));
53232
53436
  }
53233
53437
  return /* @__PURE__ */ new Map();
@@ -53374,9 +53578,9 @@ __export(run_watch_exports, {
53374
53578
  watchedContentDir: () => watchedContentDir,
53375
53579
  writeRunRecord: () => writeRunRecord
53376
53580
  });
53377
- import { closeSync as closeSync2, constants, existsSync as existsSync57, fstatSync, lstatSync as lstatSync10, statSync as statSync14, mkdirSync as mkdirSync27, openSync as openSync2, readFileSync as readFileSync63, realpathSync as realpathSync7, renameSync as renameSync6, rmSync as rmSync13, writeFileSync as writeFileSync23, watch as fsWatch } from "node:fs";
53581
+ import { closeSync as closeSync2, constants, existsSync as existsSync58, fstatSync, lstatSync as lstatSync10, statSync as statSync14, mkdirSync as mkdirSync27, openSync as openSync2, readFileSync as readFileSync64, realpathSync as realpathSync7, renameSync as renameSync6, rmSync as rmSync13, writeFileSync as writeFileSync23, watch as fsWatch } from "node:fs";
53378
53582
  import { randomUUID as randomUUID3 } from "node:crypto";
53379
- import { extname as extname4, join as join56, resolve as resolve42, sep as sep13 } from "node:path";
53583
+ import { extname as extname4, join as join57, resolve as resolve42, sep as sep13 } from "node:path";
53380
53584
  function diffReleases(prev, next) {
53381
53585
  const bundles = Object.keys(next.bundles).filter((b) => prev.bundles[b]?.sha256 !== next.bundles[b].sha256).sort();
53382
53586
  const changedBundles = new Set(bundles);
@@ -53417,7 +53621,7 @@ function watchContent(dir, onChange, opts = {}) {
53417
53621
  let timer = null;
53418
53622
  const pending = /* @__PURE__ */ new Set();
53419
53623
  const watcher2 = watch2(dir, { recursive: true }, (_event, file) => {
53420
- if (!file || ignoredChange(file, (rel) => isDir(join56(dir, rel)))) return;
53624
+ if (!file || ignoredChange(file, (rel) => isDir(join57(dir, rel)))) return;
53421
53625
  pending.add(file);
53422
53626
  if (timer) clearTimeout(timer);
53423
53627
  timer = setTimeout(() => {
@@ -53438,7 +53642,7 @@ function watchedContentDir(source) {
53438
53642
  if (!source || /^https?:\/\//i.test(source)) return null;
53439
53643
  const dir = resolve42(source);
53440
53644
  try {
53441
- return statSync14(dir).isDirectory() && existsSync57(join56(dir, "himalaya.content.json")) ? dir : null;
53645
+ return statSync14(dir).isDirectory() && existsSync58(join57(dir, "himalaya.content.json")) ? dir : null;
53442
53646
  } catch {
53443
53647
  return null;
53444
53648
  }
@@ -53495,7 +53699,7 @@ function serialize(task) {
53495
53699
  });
53496
53700
  }
53497
53701
  function himiDir(dir) {
53498
- const root = join56(dir, ".himi");
53702
+ const root = join57(dir, ".himi");
53499
53703
  try {
53500
53704
  return lstatSync10(root).isDirectory() ? root : null;
53501
53705
  } catch (e) {
@@ -53507,11 +53711,11 @@ function himiDir(dir) {
53507
53711
  function writeRunRecord(dir, record2) {
53508
53712
  const root = himiDir(dir);
53509
53713
  if (!root) return false;
53510
- const tmp = join56(root, `.run.json.${process.pid}.${randomUUID3()}.tmp`);
53714
+ const tmp = join57(root, `.run.json.${process.pid}.${randomUUID3()}.tmp`);
53511
53715
  try {
53512
53716
  writeFileSync23(tmp, `${JSON.stringify(record2, null, 2)}
53513
53717
  `, { flag: "wx" });
53514
- renameSync6(tmp, join56(root, "run.json"));
53718
+ renameSync6(tmp, join57(root, "run.json"));
53515
53719
  } catch (e) {
53516
53720
  rmSync13(tmp, { force: true });
53517
53721
  throw e;
@@ -53519,7 +53723,7 @@ function writeRunRecord(dir, record2) {
53519
53723
  return true;
53520
53724
  }
53521
53725
  function removeRunRecord(dir, owner) {
53522
- const root = join56(dir, ".himi");
53726
+ const root = join57(dir, ".himi");
53523
53727
  try {
53524
53728
  if (!lstatSync10(root).isDirectory()) return;
53525
53729
  } catch {
@@ -53534,16 +53738,16 @@ function removeRunRecord(dir, owner) {
53534
53738
  }
53535
53739
  if (typeof pid === "number" && pid !== owner) return;
53536
53740
  }
53537
- rmSync13(join56(root, "run.json"), { force: true });
53741
+ rmSync13(join57(root, "run.json"), { force: true });
53538
53742
  }
53539
53743
  function boardFile(dir, pathname) {
53540
53744
  let rel = null;
53541
- if (pathname === "/__himi/studio/" || pathname === "/__himi/studio/index.html") rel = join56("studio", "index.html");
53542
- else if (pathname === "/__himi/studio/state.js") rel = join56("studio", "state.js");
53543
- else if (pathname === "/__himi/studio/timeline.js") rel = join56("studio", "timeline.js");
53745
+ if (pathname === "/__himi/studio/" || pathname === "/__himi/studio/index.html") rel = join57("studio", "index.html");
53746
+ else if (pathname === "/__himi/studio/state.js") rel = join57("studio", "state.js");
53747
+ else if (pathname === "/__himi/studio/timeline.js") rel = join57("studio", "timeline.js");
53544
53748
  else {
53545
53749
  const m = /^\/__himi\/history\/([0-9a-f]{64}\.(png|jpg|gif|webp))$/.exec(pathname);
53546
- if (m) rel = join56("history", m[1]);
53750
+ if (m) rel = join57("history", m[1]);
53547
53751
  }
53548
53752
  if (!rel) return null;
53549
53753
  const bytes2 = readConfined(resolve42(dir, ".himi"), rel);
@@ -53559,7 +53763,7 @@ function readConfined(root, rel) {
53559
53763
  const parts = rel.split(sep13);
53560
53764
  let at = root;
53561
53765
  for (let i = 0; i < parts.length; i++) {
53562
- at = join56(at, parts[i]);
53766
+ at = join57(at, parts[i]);
53563
53767
  const st = lstatSync10(at);
53564
53768
  if (st.isSymbolicLink() || !(i === parts.length - 1 ? st.isFile() : st.isDirectory())) return null;
53565
53769
  }
@@ -53568,7 +53772,7 @@ function readConfined(root, rel) {
53568
53772
  const fd = openSync2(at, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
53569
53773
  try {
53570
53774
  if (!fstatSync(fd).isFile()) return null;
53571
- return readFileSync63(fd);
53775
+ return readFileSync64(fd);
53572
53776
  } finally {
53573
53777
  closeSync2(fd);
53574
53778
  }
@@ -53620,7 +53824,7 @@ __export(run_browser_exports, {
53620
53824
  runInBrowser: () => runInBrowser
53621
53825
  });
53622
53826
  import { createServer as createServer5 } from "node:http";
53623
- import { existsSync as existsSync58, readFileSync as readFileSync64 } from "node:fs";
53827
+ import { existsSync as existsSync59, readFileSync as readFileSync65 } from "node:fs";
53624
53828
  import { spawnSync as spawnSync3 } from "node:child_process";
53625
53829
  import { dirname as dirname30, resolve as resolve43 } from "node:path";
53626
53830
  import { fileURLToPath as fileURLToPath23 } from "node:url";
@@ -53628,7 +53832,7 @@ function stagedDir() {
53628
53832
  return process.env.HIMI_BROWSER_PLANE_OUT ? resolve43(process.env.HIMI_BROWSER_PLANE_OUT) : resolve43(REPO, "packages", "serve", "dist", "browser-plane");
53629
53833
  }
53630
53834
  function missingStaged() {
53631
- return [...SERVED].filter((f) => !existsSync58(resolve43(stagedDir(), f))).sort();
53835
+ return [...SERVED].filter((f) => !existsSync59(resolve43(stagedDir(), f))).sort();
53632
53836
  }
53633
53837
  function ensureBrowserPlaneStaged() {
53634
53838
  if (missingStaged().length === 0) return { ok: true, detail: "already staged" };
@@ -53713,7 +53917,7 @@ async function runInBrowser(releaseRoot, app, header, opts = {}) {
53713
53917
  }
53714
53918
  const name = path2 === "/" || path2 === "/index.html" ? "host.html" : path2.slice(1);
53715
53919
  if (SERVED.has(name)) {
53716
- if (!existsSync58(resolve43(stagedDir(), name))) {
53920
+ if (!existsSync59(resolve43(stagedDir(), name))) {
53717
53921
  const restaged = ensureBrowserPlaneStaged();
53718
53922
  if (!restaged.ok) {
53719
53923
  res.writeHead(503, { "content-type": "application/json" });
@@ -53723,7 +53927,7 @@ async function runInBrowser(releaseRoot, app, header, opts = {}) {
53723
53927
  }
53724
53928
  let bytes2;
53725
53929
  try {
53726
- bytes2 = readFileSync64(resolve43(stagedDir(), name));
53930
+ bytes2 = readFileSync65(resolve43(stagedDir(), name));
53727
53931
  } catch (e) {
53728
53932
  res.writeHead(503, { "content-type": "application/json" });
53729
53933
  res.end(JSON.stringify({ error: "browser_plane_unstaged", file: name, detail: String(e) }));
@@ -54184,11 +54388,11 @@ __export(preflight_exports, {
54184
54388
  parseStoreConfig: () => parseStoreConfig,
54185
54389
  requireEnv: () => requireEnv
54186
54390
  });
54187
- import { existsSync as existsSync59, readFileSync as readFileSync65 } from "node:fs";
54188
- import { isAbsolute as isAbsolute3, relative as relative10, resolve as resolve44 } from "node:path";
54391
+ import { existsSync as existsSync60, readFileSync as readFileSync66 } from "node:fs";
54392
+ import { isAbsolute as isAbsolute5, relative as relative11, resolve as resolve44 } from "node:path";
54189
54393
  function isInsideRepo(p) {
54190
- const rel = relative10(REPO_ROOT3, resolve44(p));
54191
- return rel !== "" && !rel.startsWith("..") && !isAbsolute3(rel);
54394
+ const rel = relative11(REPO_ROOT3, resolve44(p));
54395
+ return rel !== "" && !rel.startsWith("..") && !isAbsolute5(rel);
54192
54396
  }
54193
54397
  function assertCredentialOutsideRepo(label2, p) {
54194
54398
  if (isInsideRepo(p)) {
@@ -54241,10 +54445,10 @@ function parseStoreConfig(text5) {
54241
54445
  }
54242
54446
  function loadStoreConfig(app) {
54243
54447
  const p = storeConfigPath(app);
54244
- if (!existsSync59(p)) {
54448
+ if (!existsSync60(p)) {
54245
54449
  throw new Error(`No store config at ${p}. Create apps/${app}/store/store.config.yaml first (see /setup-store-signing).`);
54246
54450
  }
54247
- return parseStoreConfig(readFileSync65(p, "utf8"));
54451
+ return parseStoreConfig(readFileSync66(p, "utf8"));
54248
54452
  }
54249
54453
  var init_preflight = __esm({
54250
54454
  "../store-release/preflight.ts"() {
@@ -54263,19 +54467,19 @@ __export(credential_profile_exports, {
54263
54467
  profilePath: () => profilePath,
54264
54468
  resolveStoreCredentials: () => resolveStoreCredentials
54265
54469
  });
54266
- import { existsSync as existsSync60, readFileSync as readFileSync66 } from "node:fs";
54470
+ import { existsSync as existsSync61, readFileSync as readFileSync67 } from "node:fs";
54267
54471
  import { homedir as homedir5 } from "node:os";
54268
- import { join as join57 } from "node:path";
54472
+ import { join as join58 } from "node:path";
54269
54473
  function credentialsDir(home = homedir5()) {
54270
- return join57(home, ".himalaya", "store-credentials");
54474
+ return join58(home, ".himalaya", "store-credentials");
54271
54475
  }
54272
54476
  function profilePath(name, home = homedir5()) {
54273
54477
  if (!PROFILE_NAME.test(name)) {
54274
54478
  throw new Error(
54275
- `credential profile ${JSON.stringify(name)} is not a profile name \u2014 use letters, digits, ".", "_" or "-" starting with a letter or digit. It names ${join57(credentialsDir(home), "<name>.json")}; it is never a path.`
54479
+ `credential profile ${JSON.stringify(name)} is not a profile name \u2014 use letters, digits, ".", "_" or "-" starting with a letter or digit. It names ${join58(credentialsDir(home), "<name>.json")}; it is never a path.`
54276
54480
  );
54277
54481
  }
54278
- return join57(credentialsDir(home), `${name}.json`);
54482
+ return join58(credentialsDir(home), `${name}.json`);
54279
54483
  }
54280
54484
  function parseProfile(text5, label2) {
54281
54485
  let doc;
@@ -54306,8 +54510,8 @@ function resolveStoreCredentials(opts) {
54306
54510
  const { app, cfg } = opts;
54307
54511
  const env = opts.env ?? process.env;
54308
54512
  const home = opts.home ?? homedir5();
54309
- const read = opts.readProfile ?? ((p) => readFileSync66(p, "utf8"));
54310
- const exists = opts.profileExists ?? existsSync60;
54513
+ const read = opts.readProfile ?? ((p) => readFileSync67(p, "utf8"));
54514
+ const exists = opts.profileExists ?? existsSync61;
54311
54515
  const ios = cfg.ios ?? {};
54312
54516
  const flagName = opts.profileFlag?.trim();
54313
54517
  const named = namedProfile(cfg, opts.profileFlag);
@@ -54387,9 +54591,9 @@ __export(preflight_app_exports, {
54387
54591
  preflightApp: () => preflightApp,
54388
54592
  profileAccount: () => profileAccount
54389
54593
  });
54390
- import { readFileSync as readFileSync67, existsSync as existsSync61, readdirSync as readdirSync29, statSync as statSync15 } from "node:fs";
54594
+ import { readFileSync as readFileSync68, existsSync as existsSync62, readdirSync as readdirSync29, statSync as statSync15 } from "node:fs";
54391
54595
  import { createHash as createHash27 } from "node:crypto";
54392
- import { join as join58 } from "node:path";
54596
+ import { join as join59 } from "node:path";
54393
54597
  function analyze(projectYml, storeConfig, icon, account) {
54394
54598
  const f = [];
54395
54599
  const has = (re, s) => re.test(s);
@@ -54453,13 +54657,13 @@ function profileAccount(app, storeConfig, opts = {}) {
54453
54657
  }
54454
54658
  }
54455
54659
  async function preflightApp(app, opts = {}) {
54456
- const projectYml = existsSync61(iosProjectYml(app)) ? readFileSync67(iosProjectYml(app), "utf8") : "";
54457
- const storeConfig = existsSync61(storeConfigPath(app)) ? readFileSync67(storeConfigPath(app), "utf8") : "";
54660
+ const projectYml = existsSync62(iosProjectYml(app)) ? readFileSync68(iosProjectYml(app), "utf8") : "";
54661
+ const storeConfig = existsSync62(storeConfigPath(app)) ? readFileSync68(storeConfigPath(app), "utf8") : "";
54458
54662
  if (!storeConfig) return [{ level: "error", message: `no apps/${app}/store/store.config.yaml \u2014 run /setup-store-signing first.` }];
54459
54663
  const findings = analyze(
54460
54664
  projectYml,
54461
54665
  storeConfig,
54462
- await appIconState(join58(appDir(app), "ios")),
54666
+ await appIconState(join59(appDir(app), "ios")),
54463
54667
  profileAccount(app, storeConfig, opts)
54464
54668
  );
54465
54669
  findings.push(...analyzeListing(metadataFilesPresent(app), listScreenshots(app)));
@@ -54488,17 +54692,17 @@ function analyzeSubmissionRecord(appLevel, review, opts) {
54488
54692
  return f;
54489
54693
  }
54490
54694
  function appLevelMetadataFiles(app, locale = "en-US") {
54491
- const root = join58(storeDir(app), "metadata");
54695
+ const root = join59(storeDir(app), "metadata");
54492
54696
  const out = /* @__PURE__ */ new Set();
54493
- if (existsSync61(root)) {
54697
+ if (existsSync62(root)) {
54494
54698
  for (const n of readdirSync29(root)) if (n.endsWith(".txt")) out.add(n);
54495
54699
  }
54496
- if (existsSync61(join58(root, locale, "privacy_url.txt"))) out.add("privacy_url.txt");
54700
+ if (existsSync62(join59(root, locale, "privacy_url.txt"))) out.add("privacy_url.txt");
54497
54701
  return out;
54498
54702
  }
54499
54703
  function reviewInfoFiles(app) {
54500
- const dir = join58(storeDir(app), "review_information");
54501
- if (!existsSync61(dir)) return /* @__PURE__ */ new Set();
54704
+ const dir = join59(storeDir(app), "review_information");
54705
+ if (!existsSync62(dir)) return /* @__PURE__ */ new Set();
54502
54706
  return new Set(readdirSync29(dir).filter((n) => n.endsWith(".txt")));
54503
54707
  }
54504
54708
  function isValidAppStoreSize(w, h) {
@@ -54608,12 +54812,12 @@ function analyzePlayListing(assets) {
54608
54812
  return f;
54609
54813
  }
54610
54814
  function playListingAssets(app, locale = "en-US") {
54611
- const dir = join58(storeDir(app), "play", locale, "images");
54815
+ const dir = join59(storeDir(app), "play", locale, "images");
54612
54816
  const read = (names) => {
54613
54817
  for (const name of names) {
54614
- const p = join58(dir, name);
54818
+ const p = join59(dir, name);
54615
54819
  try {
54616
- if (statSync15(p).isFile()) return { name, bytes: readFileSync67(p) };
54820
+ if (statSync15(p).isFile()) return { name, bytes: readFileSync68(p) };
54617
54821
  } catch {
54618
54822
  }
54619
54823
  }
@@ -54644,26 +54848,26 @@ function analyzeListing(metaFiles, shots) {
54644
54848
  return f;
54645
54849
  }
54646
54850
  function metadataFilesPresent(app, locale = "en-US") {
54647
- const dir = join58(storeDir(app), "metadata", locale);
54648
- if (!existsSync61(dir)) return /* @__PURE__ */ new Set();
54851
+ const dir = join59(storeDir(app), "metadata", locale);
54852
+ if (!existsSync62(dir)) return /* @__PURE__ */ new Set();
54649
54853
  return new Set(readdirSync29(dir).filter((n) => n.endsWith(".txt")));
54650
54854
  }
54651
54855
  function listScreenshots(app, locale = "en-US") {
54652
54856
  const out = [];
54653
- const flat = join58(storeDir(app), "screenshots", locale);
54654
- if (existsSync61(flat)) {
54857
+ const flat = join59(storeDir(app), "screenshots", locale);
54858
+ if (existsSync62(flat)) {
54655
54859
  for (const png of readdirSync29(flat).filter((n) => n.endsWith(".png"))) {
54656
- const sz = pngSize(readFileSync67(join58(flat, png)));
54860
+ const sz = pngSize(readFileSync68(join59(flat, png)));
54657
54861
  if (sz) out.push({ name: png, w: sz.w, h: sz.h });
54658
54862
  }
54659
54863
  }
54660
- const legacy = join58(storeDir(app), "metadata", locale, "screenshots");
54661
- if (existsSync61(legacy)) {
54864
+ const legacy = join59(storeDir(app), "metadata", locale, "screenshots");
54865
+ if (existsSync62(legacy)) {
54662
54866
  for (const deviceDir of readdirSync29(legacy)) {
54663
- const d = join58(legacy, deviceDir);
54867
+ const d = join59(legacy, deviceDir);
54664
54868
  if (!statSync15(d).isDirectory()) continue;
54665
54869
  for (const png of readdirSync29(d).filter((n) => n.endsWith(".png"))) {
54666
- const sz = pngSize(readFileSync67(join58(d, png)));
54870
+ const sz = pngSize(readFileSync68(join59(d, png)));
54667
54871
  if (sz) out.push({ name: `${deviceDir}/${png}`, w: sz.w, h: sz.h });
54668
54872
  }
54669
54873
  }
@@ -54671,13 +54875,13 @@ function listScreenshots(app, locale = "en-US") {
54671
54875
  return out;
54672
54876
  }
54673
54877
  function placeholderHashes2() {
54674
- const templateIos = join58(appDir("_template"), "ios");
54675
- if (!existsSync61(templateIos)) return [LEGACY_PLACEHOLDER_SHA2562];
54878
+ const templateIos = join59(appDir("_template"), "ios");
54879
+ if (!existsSync62(templateIos)) return [LEGACY_PLACEHOLDER_SHA2562];
54676
54880
  for (const target of readdirSync29(templateIos)) {
54677
- const p = join58(templateIos, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
54881
+ const p = join59(templateIos, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
54678
54882
  try {
54679
54883
  if (!statSync15(p).isFile()) continue;
54680
- return [LEGACY_PLACEHOLDER_SHA2562, createHash27("sha256").update(readFileSync67(p)).digest("hex")];
54884
+ return [LEGACY_PLACEHOLDER_SHA2562, createHash27("sha256").update(readFileSync68(p)).digest("hex")];
54681
54885
  } catch {
54682
54886
  }
54683
54887
  }
@@ -54708,12 +54912,12 @@ async function pixelIssues(bytes2) {
54708
54912
  }
54709
54913
  }
54710
54914
  async function appIconState(iosDir) {
54711
- if (!existsSync61(iosDir)) return { exists: false, isPlaceholder: false, storeIssues: [] };
54915
+ if (!existsSync62(iosDir)) return { exists: false, isPlaceholder: false, storeIssues: [] };
54712
54916
  for (const target of readdirSync29(iosDir)) {
54713
- const p = join58(iosDir, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
54917
+ const p = join59(iosDir, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
54714
54918
  try {
54715
54919
  if (!statSync15(p).isFile()) continue;
54716
- const bytes2 = readFileSync67(p);
54920
+ const bytes2 = readFileSync68(p);
54717
54921
  const hash = createHash27("sha256").update(bytes2).digest("hex");
54718
54922
  const pixels = await pixelIssues(bytes2);
54719
54923
  return {
@@ -54785,7 +54989,7 @@ __export(version_exports, {
54785
54989
  setAndroidVersion: () => setAndroidVersion,
54786
54990
  setIosVersion: () => setIosVersion
54787
54991
  });
54788
- import { readFileSync as readFileSync68, writeFileSync as writeFileSync24 } from "node:fs";
54992
+ import { readFileSync as readFileSync69, writeFileSync as writeFileSync24 } from "node:fs";
54789
54993
  function readIosVersion(projectYml) {
54790
54994
  const marketing = matchScalar(projectYml, "CFBundleShortVersionString");
54791
54995
  const build2 = matchScalar(projectYml, "CFBundleVersion");
@@ -54843,8 +55047,8 @@ function setAndroidVersion(gradle, v) {
54843
55047
  function bumpVersion(app, opts) {
54844
55048
  const iosPath = iosProjectYml(app);
54845
55049
  const gradlePath = androidGradle(app);
54846
- const iosYml = readFileSync68(iosPath, "utf8");
54847
- const gradle = readFileSync68(gradlePath, "utf8");
55050
+ const iosYml = readFileSync69(iosPath, "utf8");
55051
+ const gradle = readFileSync69(gradlePath, "utf8");
54848
55052
  const current = readIosVersion(iosYml);
54849
55053
  const next = {
54850
55054
  marketing: opts.marketing ?? current.marketing,
@@ -54861,7 +55065,7 @@ var init_version = __esm({
54861
55065
  });
54862
55066
 
54863
55067
  // ../store-release/register-bundle.ts
54864
- import { readFileSync as readFileSync69 } from "node:fs";
55068
+ import { readFileSync as readFileSync70 } from "node:fs";
54865
55069
  import { createPrivateKey as createPrivateKey2, sign } from "node:crypto";
54866
55070
  function b64url2(b) {
54867
55071
  return Buffer.from(b).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
@@ -54879,7 +55083,7 @@ function bundleIdBody(identifier, name, platform = "IOS") {
54879
55083
  async function registerBundleId(opts) {
54880
55084
  const env = requireEnv(["ASC_KEY_ID", "ASC_ISSUER_ID", "ASC_KEY_PATH"]);
54881
55085
  assertCredentialOutsideRepo("ASC_KEY_PATH", env.ASC_KEY_PATH);
54882
- const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync69(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
55086
+ const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync70(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
54883
55087
  const res = await fetch(`${ASC_API}/v1/bundleIds`, {
54884
55088
  method: "POST",
54885
55089
  headers: { Authorization: `Bearer ${jwt}`, "Content-Type": "application/json" },
@@ -54933,7 +55137,7 @@ __export(asc_status_exports, {
54933
55137
  ascStatus: () => ascStatus,
54934
55138
  buildVerdict: () => buildVerdict
54935
55139
  });
54936
- import { readFileSync as readFileSync70 } from "node:fs";
55140
+ import { readFileSync as readFileSync71 } from "node:fs";
54937
55141
  function buildVerdict(builds) {
54938
55142
  if (!builds.length) {
54939
55143
  return "NO builds registered \u2014 if you uploaded recently and it's been >15 min, the binary was REJECTED IN PROCESSING (reason is email-only; run validate-binary.ts / scan-purpose-strings.ts to reproduce it).";
@@ -54947,7 +55151,7 @@ function buildVerdict(builds) {
54947
55151
  function client(creds) {
54948
55152
  const env = creds ?? requireEnv(["ASC_KEY_ID", "ASC_ISSUER_ID", "ASC_KEY_PATH"]);
54949
55153
  assertCredentialOutsideRepo("ASC_KEY_PATH", env.ASC_KEY_PATH);
54950
- const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync70(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
55154
+ const jwt = ascJwt(env.ASC_KEY_ID, env.ASC_ISSUER_ID, readFileSync71(env.ASC_KEY_PATH, "utf8"), Math.floor(Date.now() / 1e3));
54951
55155
  return async (path2) => {
54952
55156
  const r = await fetch(`${ASC}${path2}`, { headers: { Authorization: `Bearer ${jwt}` } });
54953
55157
  return { status: r.status, json: await r.json().catch(() => ({})) };
@@ -55107,7 +55311,7 @@ __export(secrets_exports, {
55107
55311
  runRemoteSecretsCommand: () => runRemoteSecretsCommand,
55108
55312
  runSecretsCommand: () => runSecretsCommand
55109
55313
  });
55110
- import { readFileSync as readFileSync71 } from "node:fs";
55314
+ import { readFileSync as readFileSync72 } from "node:fs";
55111
55315
  function environment(value) {
55112
55316
  if (value === "preview" || value === "production") return value;
55113
55317
  throw new Error("--environment must be preview or production");
@@ -55138,7 +55342,7 @@ async function runSecretsCommand(operation, flags, io, options = {}) {
55138
55342
  io.log(JSON.stringify(await vault.list({ app, environment: env })));
55139
55343
  return 0;
55140
55344
  }
55141
- const value = options.readValue ? options.readValue() : readFileSync71(0, "utf8").replace(/\r?\n$/, "");
55345
+ const value = options.readValue ? options.readValue() : readFileSync72(0, "utf8").replace(/\r?\n$/, "");
55142
55346
  if (operation === "set") await vault.set({ app, environment: env, name, value });
55143
55347
  else if (operation === "rotate") await vault.rotate({ app, environment: env, name, value });
55144
55348
  else throw new Error(`unknown secrets operation ${operation}`);
@@ -55167,7 +55371,7 @@ async function runRemoteSecretsCommand(operation, flags, io, options) {
55167
55371
  }
55168
55372
  let body;
55169
55373
  if (operation !== "list") {
55170
- const value = options.readValue ? options.readValue() : readFileSync71(0, "utf8").replace(/\r?\n$/, "");
55374
+ const value = options.readValue ? options.readValue() : readFileSync72(0, "utf8").replace(/\r?\n$/, "");
55171
55375
  body = JSON.stringify({ value });
55172
55376
  }
55173
55377
  const response = await (options.fetchImpl ?? fetch)(url, { method: operation === "list" ? "GET" : operation === "set" ? "PUT" : "POST", headers: headers2, body });
@@ -55203,15 +55407,15 @@ __export(notify_exports, {
55203
55407
  runNotify: () => runNotify,
55204
55408
  saveAdminSession: () => saveAdminSession
55205
55409
  });
55206
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync28, readFileSync as readFileSync72, writeFileSync as writeFileSync25 } from "node:fs";
55410
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync28, readFileSync as readFileSync73, writeFileSync as writeFileSync25 } from "node:fs";
55207
55411
  import { homedir as homedir6 } from "node:os";
55208
- import { dirname as dirname32, join as join59 } from "node:path";
55412
+ import { dirname as dirname32, join as join60 } from "node:path";
55209
55413
  function defaultSessionsPath() {
55210
- return process.env.HIMI_ADMIN_SESSIONS_FILE ?? join59(homedir6(), ".himalaya", "admin-sessions.json");
55414
+ return process.env.HIMI_ADMIN_SESSIONS_FILE ?? join60(homedir6(), ".himalaya", "admin-sessions.json");
55211
55415
  }
55212
55416
  function loadSessionsFile(path2 = defaultSessionsPath()) {
55213
55417
  try {
55214
- return JSON.parse(readFileSync72(path2, "utf8"));
55418
+ return JSON.parse(readFileSync73(path2, "utf8"));
55215
55419
  } catch {
55216
55420
  return {};
55217
55421
  }
@@ -56341,7 +56545,7 @@ function deriveAdoptedCmsCollections(cms) {
56341
56545
  return { collections, dropped };
56342
56546
  }
56343
56547
  function deriveTailoring(ctx, overrides = {}, revision = "site-derived") {
56344
- const appName = deriveAppName(ctx, overrides.appName);
56548
+ const appName2 = deriveAppName(ctx, overrides.appName);
56345
56549
  const supportUrl = clean(overrides.supportUrl) || clean(ctx.url.primary);
56346
56550
  const privacyUrl = clean(overrides.privacyUrl);
56347
56551
  const termsUrl = clean(overrides.termsUrl);
@@ -56361,8 +56565,8 @@ function deriveTailoring(ctx, overrides = {}, revision = "site-derived") {
56361
56565
  // empty; either way the key exists and the shell's list never survives the merge.
56362
56566
  cmsCollections: [],
56363
56567
  identity: {
56364
- appName,
56365
- businessName: clean(ctx.identity.businessName) || appName,
56568
+ appName: appName2,
56569
+ businessName: clean(ctx.identity.businessName) || appName2,
56366
56570
  tagline: clean(overrides.tagline) || clean(ctx.identity.description),
56367
56571
  // "" means UNKNOWN here, with `missing` above as the signal — the published overlay's
56368
56572
  // documented shape. Keeping an unknown url out of a MERGE is `apply.ts`'s job, at the
@@ -56372,7 +56576,7 @@ function deriveTailoring(ctx, overrides = {}, revision = "site-derived") {
56372
56576
  termsUrl
56373
56577
  }
56374
56578
  };
56375
- return { appName, iconSource: deriveIconSource(ctx), provisioning, missing };
56579
+ return { appName: appName2, iconSource: deriveIconSource(ctx), provisioning, missing };
56376
56580
  }
56377
56581
  function bounded(s, max) {
56378
56582
  if (s.length <= max) return s;
@@ -58468,11 +58672,11 @@ function crawledPageName(look, page) {
58468
58672
  const hit = (page?.pageId ? sitePages.find((p) => p.pageId === page.pageId) : void 0) ?? (look.mainPageId ? sitePages.find((p) => p.pageId === look.mainPageId) : void 0) ?? sitePages.find((p) => p.slug === "home");
58469
58673
  return hit?.title.trim() ?? "";
58470
58674
  }
58471
- function guessedNameWarning(appName, crawledTitle, ownerLookupError) {
58472
- const from = crawledTitle && crawledTitle !== appName ? ` off the crawled page title "${crawledTitle}"` : " from the site crawl";
58675
+ function guessedNameWarning(appName2, crawledTitle, ownerLookupError) {
58676
+ const from = crawledTitle && crawledTitle !== appName2 ? ` off the crawled page title "${crawledTitle}"` : " from the site crawl";
58473
58677
  const why = ownerLookupError ? `the owner site-properties read FAILED (${ownerLookupError}), so the owner-entered name was never read` : "no owner token was available, so site-properties was never read";
58474
58678
  const remedy = ownerLookupError ? "resolve that failure and re-run the same owner-authenticated command" : "re-run with owner auth (`himi site analyze --site <msid>`)";
58475
- return `identity.appName "${appName}" is a GUESS, read${from} rather than from the owner-entered business name \u2014 ${why}. --apply writes it into provisioning.json as-is and it reaches both app stores verbatim, so check it: ${remedy}, or set identity.appName in provisioning.json yourself.`;
58679
+ return `identity.appName "${appName2}" is a GUESS, read${from} rather than from the owner-entered business name \u2014 ${why}. --apply writes it into provisioning.json as-is and it reaches both app stores verbatim, so check it: ${remedy}, or set identity.appName in provisioning.json yourself.`;
58476
58680
  }
58477
58681
  function mergeLookFromPublished(look, published, siteUrl) {
58478
58682
  const sitePages = withPageUrls(
@@ -58980,7 +59184,7 @@ var init_brand_font = __esm({
58980
59184
 
58981
59185
  // src/site-analyze/write-artifacts.ts
58982
59186
  import { mkdirSync as mkdirSync29, writeFileSync as writeFileSync26 } from "node:fs";
58983
- import { join as join60 } from "node:path";
59187
+ import { join as join61 } from "node:path";
58984
59188
  function attachVeloSources(book, sources, put) {
58985
59189
  const byPath = new Map((sources ?? []).map((s) => [s.path, s.text]));
58986
59190
  for (const f of book.velo.files) {
@@ -59135,12 +59339,12 @@ async function downloadMedia(book, fetchImpl, put) {
59135
59339
  }
59136
59340
  async function writeBusinessBook(result, opts) {
59137
59341
  const out = opts.outDir;
59138
- mkdirSync29(join60(out, "evidence/pages"), { recursive: true });
59139
- mkdirSync29(join60(out, "media/catalog"), { recursive: true });
59342
+ mkdirSync29(join61(out, "evidence/pages"), { recursive: true });
59343
+ mkdirSync29(join61(out, "media/catalog"), { recursive: true });
59140
59344
  const files = [];
59141
59345
  const put = (rel, body) => {
59142
- const p = join60(out, rel);
59143
- mkdirSync29(join60(p, ".."), { recursive: true });
59346
+ const p = join61(out, rel);
59347
+ mkdirSync29(join61(p, ".."), { recursive: true });
59144
59348
  writeFileSync26(p, body);
59145
59349
  files.push(rel);
59146
59350
  };
@@ -59234,8 +59438,8 @@ var init_customer_build = __esm({
59234
59438
  });
59235
59439
 
59236
59440
  // src/site-analyze/apply.ts
59237
- import { existsSync as existsSync62, readFileSync as readFileSync73, writeFileSync as writeFileSync27, copyFileSync as copyFileSync3, mkdirSync as mkdirSync30, realpathSync as realpathSync8, statSync as statSync16 } from "node:fs";
59238
- import { dirname as dirname33, isAbsolute as isAbsolute4, join as join61, resolve as resolve45, sep as sep14 } from "node:path";
59441
+ import { existsSync as existsSync63, readFileSync as readFileSync74, writeFileSync as writeFileSync27, copyFileSync as copyFileSync3, mkdirSync as mkdirSync30, realpathSync as realpathSync8, statSync as statSync16 } from "node:fs";
59442
+ import { dirname as dirname33, isAbsolute as isAbsolute6, join as join62, resolve as resolve45, sep as sep14 } from "node:path";
59239
59443
  import { spawnSync as spawnSync4 } from "node:child_process";
59240
59444
  function unpairedRedirectNote(overlay) {
59241
59445
  if (!overlay.wix?.clientId || overlay.wix.redirectUri) return void 0;
@@ -59250,7 +59454,7 @@ function committedShellProvisioning(path2) {
59250
59454
  function findRepoRoot(cwd) {
59251
59455
  let dir = cwd;
59252
59456
  for (let i = 0; i < 8; i++) {
59253
- if (SHELL_NAMES2.some((name) => existsSync62(join61(dir, shellAppDir(name), "package.json")))) return dir;
59457
+ if (SHELL_NAMES2.some((name) => existsSync63(join62(dir, shellAppDir(name), "package.json")))) return dir;
59254
59458
  const next = dirname33(dir);
59255
59459
  if (next === dir) break;
59256
59460
  dir = next;
@@ -59319,24 +59523,24 @@ function mergeCustomerOwned(committed, overlay) {
59319
59523
  }
59320
59524
  function buildMergedContract(options) {
59321
59525
  const { appDir: appDir2, packageDir, overlay } = options;
59322
- const committedPath = join61(appDir2, "provisioning.json");
59323
- if (!existsSync62(committedPath)) {
59526
+ const committedPath = join62(appDir2, "provisioning.json");
59527
+ if (!existsSync63(committedPath)) {
59324
59528
  throw new Error(`shell has no committed provisioning.json at ${committedPath} to build a contract from`);
59325
59529
  }
59326
- const committed = JSON.parse(readFileSync73(committedPath, "utf8"));
59530
+ const committed = JSON.parse(readFileSync74(committedPath, "utf8"));
59327
59531
  const merged = mergeCustomerOwned(committed, overlay);
59328
59532
  const overlayFontFamily = (overlay.branding ?? {}).fontFamily;
59329
59533
  const branding = merged.branding ?? {};
59330
59534
  const staged = [];
59331
59535
  const contained = (root, rel) => {
59332
- if (isAbsolute4(rel)) return void 0;
59536
+ if (isAbsolute6(rel)) return void 0;
59333
59537
  const full = resolve45(root, rel);
59334
59538
  return full.startsWith(resolve45(root) + sep14) ? full : void 0;
59335
59539
  };
59336
59540
  const stage = (fromRel, toRel) => {
59337
59541
  const from = contained(packageDir, fromRel);
59338
- const to = contained(join61(appDir2, STAGED), toRel.slice(STAGED.length + 1));
59339
- if (!from || !to || !existsSync62(from)) return void 0;
59542
+ const to = contained(join62(appDir2, STAGED), toRel.slice(STAGED.length + 1));
59543
+ if (!from || !to || !existsSync63(from)) return void 0;
59340
59544
  let real;
59341
59545
  let realRoot;
59342
59546
  try {
@@ -59358,11 +59562,11 @@ function buildMergedContract(options) {
59358
59562
  };
59359
59563
  const icon = stage("media/icon.png", `${STAGED}/icon.png`);
59360
59564
  if (icon) branding.appIconPath = icon;
59361
- const facesPath = join61(packageDir, "media/fonts/faces.json");
59362
- if (existsSync62(facesPath)) {
59565
+ const facesPath = join62(packageDir, "media/fonts/faces.json");
59566
+ if (existsSync63(facesPath)) {
59363
59567
  let faces = [];
59364
59568
  try {
59365
- const parsed = JSON.parse(readFileSync73(facesPath, "utf8"));
59569
+ const parsed = JSON.parse(readFileSync74(facesPath, "utf8"));
59366
59570
  faces = Array.isArray(parsed) ? parsed : [];
59367
59571
  } catch {
59368
59572
  faces = [];
@@ -59383,9 +59587,9 @@ function buildMergedContract(options) {
59383
59587
  merged.branding = branding;
59384
59588
  const body = `${JSON.stringify(merged, null, 2)}
59385
59589
  `;
59386
- const path2 = join61(packageDir, "provisioning.contract.json");
59590
+ const path2 = join62(packageDir, "provisioning.contract.json");
59387
59591
  writeFileSync27(path2, body);
59388
- const localPath = join61(appDir2, LOCAL_CONTRACT_REL);
59592
+ const localPath = join62(appDir2, LOCAL_CONTRACT_REL);
59389
59593
  mkdirSync30(dirname33(localPath), { recursive: true });
59390
59594
  writeFileSync27(localPath, body);
59391
59595
  staged.push(localPath);
@@ -59420,10 +59624,10 @@ function catalogTypography(overlay, template) {
59420
59624
  function staticFaqFor(overlay, overlayPath) {
59421
59625
  const own3 = overlay.faq;
59422
59626
  if (own3 !== void 0) return deriveStaticFaq(own3);
59423
- const bookPath = join61(dirname33(resolve45(overlayPath)), "book.json");
59424
- if (!existsSync62(bookPath)) return void 0;
59627
+ const bookPath = join62(dirname33(resolve45(overlayPath)), "book.json");
59628
+ if (!existsSync63(bookPath)) return void 0;
59425
59629
  try {
59426
- const book = JSON.parse(readFileSync73(bookPath, "utf8"));
59630
+ const book = JSON.parse(readFileSync74(bookPath, "utf8"));
59427
59631
  const copy = book?.copy;
59428
59632
  if (!copy || typeof copy !== "object" || copy.unavailable) return void 0;
59429
59633
  const pairs = deriveStaticFaq(copy.faq);
@@ -59437,8 +59641,8 @@ async function applyOverlays(input) {
59437
59641
  const cwd = resolve45(input.cwd);
59438
59642
  const shell = input.shell ?? DEFAULT_BRANDABLE_SHELL;
59439
59643
  assertKnownShell(shell);
59440
- const overlay = JSON.parse(readFileSync73(input.overlayPath, "utf8"));
59441
- const tokens = JSON.parse(readFileSync73(input.tokensPath, "utf8"));
59644
+ const overlay = JSON.parse(readFileSync74(input.overlayPath, "utf8"));
59645
+ const tokens = JSON.parse(readFileSync74(input.tokensPath, "utf8"));
59442
59646
  const faq = staticFaqFor(overlay, input.overlayPath);
59443
59647
  if (faq !== void 0) overlay.faq = faq;
59444
59648
  const wrote = [];
@@ -59450,9 +59654,9 @@ async function applyOverlays(input) {
59450
59654
  `shell "${shell}" cannot bake a customer brand \u2014 it ships no prepare:customer script`
59451
59655
  );
59452
59656
  }
59453
- const appDir2 = join61(repoRoot2, shellAppDir(shell));
59454
- const committedPath = join61(appDir2, "provisioning.json");
59455
- const committedSite = existsSync62(committedPath) ? JSON.parse(readFileSync73(committedPath, "utf8")).site : void 0;
59657
+ const appDir2 = join62(repoRoot2, shellAppDir(shell));
59658
+ const committedPath = join62(appDir2, "provisioning.json");
59659
+ const committedSite = existsSync63(committedPath) ? JSON.parse(readFileSync74(committedPath, "utf8")).site : void 0;
59456
59660
  if (committedSite !== void 0 && overlay.site === void 0) {
59457
59661
  throw new Error(
59458
59662
  `shell "${shell}" is a site shell: its contract names the customer's website (a \`site\` block), which this analysis does not produce yet. Write the site block into a contract and run \`npm run prepare:customer -- --contract <file>\` in ${shellAppDir(shell)} (or \`himi dist --shell ${shell}\`).`
@@ -59479,11 +59683,11 @@ async function applyOverlays(input) {
59479
59683
  note: `prepare:customer --contract with a merged contract (committed provisioning.json untouched)${unpairedInRepo ? ` \xB7 ${unpairedInRepo}` : ""}`
59480
59684
  };
59481
59685
  }
59482
- const pkgProv = join61(cwd, "provisioning.json");
59686
+ const pkgProv = join62(cwd, "provisioning.json");
59483
59687
  assertNotCommitted(pkgProv, input.forbiddenProvisioning);
59484
59688
  let packageFaq = faq;
59485
- if (existsSync62(pkgProv)) {
59486
- const cur = JSON.parse(readFileSync73(pkgProv, "utf8"));
59689
+ if (existsSync63(pkgProv)) {
59690
+ const cur = JSON.parse(readFileSync74(pkgProv, "utf8"));
59487
59691
  const merged = deepMerge4(cur, overlay);
59488
59692
  packageFaq = merged.faq;
59489
59693
  writeFileSync27(pkgProv, JSON.stringify(merged, null, 2) + "\n");
@@ -59492,9 +59696,9 @@ async function applyOverlays(input) {
59492
59696
  writeFileSync27(pkgProv, JSON.stringify(overlay, null, 2) + "\n");
59493
59697
  wrote.push(pkgProv);
59494
59698
  }
59495
- const pkgTokens = join61(cwd, "tokens.json");
59496
- if (existsSync62(pkgTokens) && (tokens.colors || tokens.typography)) {
59497
- const cur = JSON.parse(readFileSync73(pkgTokens, "utf8"));
59699
+ const pkgTokens = join62(cwd, "tokens.json");
59700
+ if (existsSync63(pkgTokens) && (tokens.colors || tokens.typography)) {
59701
+ const cur = JSON.parse(readFileSync74(pkgTokens, "utf8"));
59498
59702
  let tokensIn = tokens;
59499
59703
  if (tokens.typography) {
59500
59704
  const fonts = catalogTypography(tokens.typography, cur.typography);
@@ -59519,9 +59723,9 @@ async function applyOverlays(input) {
59519
59723
  wrote.push(pkgTokens);
59520
59724
  }
59521
59725
  const homeAction = homeRefreshActionId(shell);
59522
- const homeSrc = join61(cwd, "dev/index.ts");
59523
- if (homeAction && existsSync62(homeSrc)) {
59524
- const prev = readFileSync73(homeSrc, "utf8");
59726
+ const homeSrc = join62(cwd, "dev/index.ts");
59727
+ if (homeAction && existsSync63(homeSrc)) {
59728
+ const prev = readFileSync74(homeSrc, "utf8");
59525
59729
  const patched = patchBrandParams(prev, overlay, homeAction);
59526
59730
  let next = patched.src;
59527
59731
  if (!patched.foreign && faq?.length && prev.includes('"tier5.himiBrandedFaq.load"') && !prev.includes('"staticFaq"')) {
@@ -59552,13 +59756,13 @@ async function applyOverlays(input) {
59552
59756
  notes.push(`${patched.unparsed} onAppear params block(s) in ${homeSrc} could not be read \u2014 any brand values inside them were not applied`);
59553
59757
  }
59554
59758
  }
59555
- if (!input.iconPath || !existsSync62(input.iconPath)) {
59759
+ if (!input.iconPath || !existsSync63(input.iconPath)) {
59556
59760
  const book = input.iconPath ? dirname33(dirname33(resolve45(input.iconPath))) : void 0;
59557
59761
  notes.push(
59558
59762
  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"
59559
59763
  );
59560
59764
  }
59561
- if (input.iconPath && existsSync62(input.iconPath)) {
59765
+ if (input.iconPath && existsSync63(input.iconPath)) {
59562
59766
  try {
59563
59767
  const installed = await installIcon(cwd, input.iconPath, { derivePlay: true });
59564
59768
  wrote.push(...installed.wrote);
@@ -63745,7 +63949,7 @@ __export(cli_exports, {
63745
63949
  runSiteAnalyze: () => runSiteAnalyze,
63746
63950
  runSiteFunctionCli: () => runSiteFunctionCli
63747
63951
  });
63748
- import { readFileSync as readFileSync74 } from "node:fs";
63952
+ import { readFileSync as readFileSync75 } from "node:fs";
63749
63953
  import { resolve as resolve46 } from "node:path";
63750
63954
  function assetFetchFor(_opts) {
63751
63955
  return pinnedFetch2;
@@ -63844,7 +64048,7 @@ async function runSiteFunctionCli(opts) {
63844
64048
  let source;
63845
64049
  if (opts.sourcePath) {
63846
64050
  const p = resolve46(opts.cwd ?? process.cwd(), opts.sourcePath);
63847
- source = readFileSync74(p, "utf8");
64051
+ source = readFileSync75(p, "utf8");
63848
64052
  }
63849
64053
  let body;
63850
64054
  if (opts.bodyRaw != null) {
@@ -64103,7 +64307,7 @@ __export(site_create_exports, {
64103
64307
  runSiteList: () => runSiteList,
64104
64308
  writeProvisioningBinding: () => writeProvisioningBinding
64105
64309
  });
64106
- import { existsSync as existsSync63, readFileSync as readFileSync75, writeFileSync as writeFileSync28 } from "node:fs";
64310
+ import { existsSync as existsSync64, readFileSync as readFileSync76, writeFileSync as writeFileSync28 } from "node:fs";
64107
64311
  import { resolve as resolvePath2 } from "node:path";
64108
64312
  function oauthClientName(projectName) {
64109
64313
  return `${projectName} client`;
@@ -64252,7 +64456,7 @@ async function runSiteCreate(opts) {
64252
64456
  if (!wantOauthApp) return { ...base, nextSteps: nextSteps(metaSiteId) };
64253
64457
  return { ...base, clientId, redirectUri, nextSteps: nextSteps(metaSiteId, clientId) };
64254
64458
  }
64255
- function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync63, readFileSync: readFileSync75, writeFileSync: writeFileSync28 }) {
64459
+ function writeProvisioningBinding(target, binding2, fs = { existsSync: existsSync64, readFileSync: readFileSync76, writeFileSync: writeFileSync28 }) {
64256
64460
  const msid = binding2.siteId;
64257
64461
  const path2 = resolvePath2(target);
64258
64462
  const committed = committedShellProvisioning(path2);
@@ -64925,11 +65129,11 @@ var init_context = __esm({
64925
65129
  });
64926
65130
 
64927
65131
  // src/cli.ts
64928
- import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync76, mkdirSync as mkdirSync31, writeFileSync as writeFileSync29, existsSync as existsSync64, rmSync as rmSync14, readdirSync as readdirSync30, statSync as statSync17, lstatSync as lstatSync11, accessSync, constants as constants2, renameSync as renameSync7, cpSync as cpSync3 } from "node:fs";
65132
+ import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync77, mkdirSync as mkdirSync31, writeFileSync as writeFileSync29, existsSync as existsSync65, rmSync as rmSync14, readdirSync as readdirSync30, statSync as statSync17, lstatSync as lstatSync11, accessSync, constants as constants2, renameSync as renameSync7, cpSync as cpSync3 } from "node:fs";
64929
65133
  import { execFile, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn6 } from "node:child_process";
64930
65134
  import { createHash as createHash28, randomUUID as randomUUID5 } from "node:crypto";
64931
65135
  import { tmpdir as tmpdir5, homedir as homedir7, hostname } from "node:os";
64932
- import { join as join62, resolve as resolve47, relative as relative11, basename as basename15, dirname as dirname34 } from "node:path";
65136
+ import { join as join63, resolve as resolve47, relative as relative12, basename as basename16, dirname as dirname34 } from "node:path";
64933
65137
  import { fileURLToPath as fileURLToPath24 } from "node:url";
64934
65138
 
64935
65139
  // ../serve-cli/src/client.ts
@@ -67045,8 +67249,8 @@ var str6 = (v) => typeof v === "string" ? v : void 0;
67045
67249
  var num2 = (v) => typeof v === "string" && v !== "" && Number.isFinite(Number(v)) ? Number(v) : void 0;
67046
67250
  function embeddedTemplateCatalog() {
67047
67251
  return availableTemplates().map((name) => {
67048
- const path2 = join62(templateSourceDir(name), TEMPLATE_META_FILE);
67049
- const meta = JSON.parse(readFileSync76(path2, "utf8"));
67252
+ const path2 = join63(templateSourceDir(name), TEMPLATE_META_FILE);
67253
+ const meta = JSON.parse(readFileSync77(path2, "utf8"));
67050
67254
  if (!meta || typeof meta.title !== "string" || typeof meta.description !== "string" || !Array.isArray(meta.tags)) {
67051
67255
  throw new Error(`${path2} must be { title: string, description: string, tags: string[], category?: string }`);
67052
67256
  }
@@ -67076,7 +67280,9 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
67076
67280
  scaffold a starter content package (--template <t> starts
67077
67281
  from a reference app; --remote fetches it off the serve plane),
67078
67282
  with the agent files inside it (AGENTS.md, CLAUDE.md, the himalaya
67079
- skill, a session-start hook); --no-agent skips them
67283
+ skill, a session-start hook); --no-agent skips them. Also makes
67284
+ the build board (no browser is opened), and its next[] leads with
67285
+ THIS session's steps: the live preview, the first picture, a postcard
67080
67286
  himi template list [--remote] list starter templates + metadata (title/description/tags) + each one's start prompt
67081
67287
  himi template prompt <name> print the prompt that starts an app from that template
67082
67288
  himi template publish [--dir <app>] [--source-dir <dir>] [--name <n>] [--icon <png>] [--visibility public|unlisted]
@@ -67667,7 +67873,7 @@ WHAT A PATCH DOES NOT CARRY \u2014 all reported in the output, never silent
67667
67873
  himi docs releasing-with-himi-serve`;
67668
67874
  var COMMANDS = [
67669
67875
  { name: "captcha", summary: "create and store native CAPTCHA keys in Serve, or inspect setup status; customer setup verifies Wix site ownership; does not enable member CAPTCHA SDKs", required: "setup|status --app <app> [--content <dir>] [--site <msid>] [--ios-bundle-id <id>] [--android-package-name <id>] [--target <t>]" },
67670
- { name: "init", summary: "scaffold a starter content package (OTA-only; no native shells). --template <t> starts from a full reference app (branded-lite = consumer storefront over a Wix site; owner-lite = owner CRUD/management) instead of the minimal counter; --templates lists bundled ones; --remote fetches the template off the serve plane (falls back to the bundled copy offline). The new app also gets its agent files (the same set `himi agent-init` writes: AGENTS.md, CLAUDE.md, the himalaya skill, HIMALAYA.md, a SessionStart hook) so the next session started inside it picks the work up; --no-agent skips them", required: "<name> [--dir] [--template <t>] [--remote] [--no-agent] | --templates" },
67876
+ { name: "init", summary: "scaffold a starter content package (OTA-only; no native shells). --template <t> starts from a full reference app (branded-lite = consumer storefront over a Wix site; owner-lite = owner CRUD/management) instead of the minimal counter; --templates lists bundled ones; --remote fetches the template off the serve plane (falls back to the bundled copy offline). The new app also gets its agent files (the same set `himi agent-init` writes: AGENTS.md, CLAUDE.md, the himalaya skill, HIMALAYA.md, a SessionStart hook) so the next session started inside it picks the work up; --no-agent skips them. It also makes the build board (.himi/studio/, exactly as `himi studio --no-open` does \u2014 it never opens a browser), so every later render, test, validate, push and progress entry refreshes it; and its `next[]` LEADS with the steps for THIS session \u2014 `himi run --watch &`, the first `himi render <homeScreenId> --png --device iphone`, and that picture's `himi progress` postcard \u2014 ahead of the next-session guidance", required: "<name> [--dir] [--template <t>] [--remote] [--no-agent] | --templates" },
67671
67877
  { name: "template", summary: "starter-template catalog + publishing. Every template is a SHOWCASE APP \u2014 a real published release you can open running before you start from it. `list` prints available templates with metadata (title/description/tags) plus each one's start prompt \u2014 pass --remote to include the serve-plane catalog so an agent can pick one; `prompt <name>` prints just that sentence for one template (the skill URL is the public door, or the plane named by --target/--serve-url); `publish` publishes an app AS a `himi init --template` (needs a himalaya.template.json; builds a previewable release + attaches the scaffold source; --source-dir overrides which tree becomes the source, --name sets the template name; bakes a launcher icon \u2014 --icon <png>, or the committed template-icons/<name>.png \u2014 and extracts accent/palette gallery meta from the scaffold's tokens.json; --visibility unlisted keeps it out of the /v1/templates catalog \u2014 templates default to public); `trim --keep <ids>` cuts an app scaffolded from a template down to those screens (the home, and any screen the navigation pins such as a drawer body, always stays) \u2014 it deletes the other SCREENS/TEMPLATED entries whole, prunes navigation (linkableScreens, tabBar, drawer, deep links, surfaces) and drops tier5-src workers no kept screen uses, then runs validate --strict and rolls everything back if it fails (--force keeps it, --dry-run writes nothing). A kept screen that links to a removed one is refused unless --links follow (keep those too) or --links drop (remove the taps)", required: "list [--remote] | prompt <name> | publish [--dir <app>] [--source-dir <dir>] [--name <n>] [--icon <png>] [--visibility public|unlisted] [--target <t>] | trim --keep <id,id,\u2026> [--content <dir>] [--links follow|drop] [--dry-run] [--force]" },
67672
67878
  { name: "build", summary: "compile tier5-src/<name>/index.ts -> dist/bundles/<name>.bundle.js with esbuild (--binary = resolve published @himalaya/*); semantic TypeScript checking belongs to himi validate", required: "[--content <dir>] [--binary]" },
67673
67879
  { name: "functions", summary: "inspect/invoke server functions and operate durable jobs or schedules", required: "list|describe|openapi|invoke | jobs list|get|cancel|retry|dlq | schedule describe|invoke [--app <a>] [--environment preview|production] [--release <id>]" },
@@ -67675,8 +67881,8 @@ var COMMANDS = [
67675
67881
  { name: "validate", summary: "lint the content package (no push): fixed-contract TypeScript checking for tier5-src workers + schema/proto + token refs + component allowlist + $service bindings + strict-enum foot-guns + capability decls + worker boot dry-run + build/compile failures. Type issues are advisory warnings normally; --strict promotes them and hygiene warnings to errors. Exit 1 on any error. Off-repo, the CLI supplies its compiler and SDK declarations.", required: "[--content <dir>] [--strict]" },
67676
67882
  { name: "test", summary: "DERIVED behavioral crawl \u2014 no test files to write. Boots each screen's real worker, replays its onAppear, then dispatches every action site the descriptor already declares (each from a fresh boot) and checks a narrow set of unambiguous invariants: no uncaught throw, never stuck on `loading`, no `$service:` token or NaN/undefined/[object Object] reaching visible text, no binding aimed at state the worker never defines, no nav param silently dropped, no dead nav target. Catches what validate/render cannot \u2014 a WRONG REDUCER passes both with ok:true. Runs the worker in a DEVICE-PARITY sandbox (no setTimeout/setInterval, as on JSC/WebView) so a bundle that would die on device fails here. Crawls on HERMETIC DATA by default \u2014 zero network: the app's own dev/net-mocks.json first (--net <file> overrides), then the config's CMS `seed:` rows, then a canned fixture of frozen real vertical responses shipped with the CLI (exercises code paths, not API-contract truth) \u2014 so the success/mapping path runs, not just the failure UI. Two boot-only HOSTILE passes (every list empty; every request 500) then check the failure/empty day \u2014 corpus-proven, so their findings are errors. NAV EDGES are followed too: every himi.open site ($item params resolved against row 0 of its list, $service against worker state) and worker _nav directive is harvested, and each baked target is booted WITH the params the source sends \u2014 detail screens finally run their content arm; edge findings are errors tagged edge:<source>. --native runs both real native decoders over the exact baked JSON bytes; --native ios or --native android selects one. It is explicit, runs only from a Himalaya SOURCE CHECKOUT (the decoder oracles are that tree's own HimalayaCore Swift package and Gradle harness, which a published install does not ship), and needs the selected local toolchain. From a source checkout, default runs name missing native verdicts in the coverage ledger; elsewhere the ledger stays silent about them rather than offering a remedy you cannot run. --offline strips all data layers (the old reject-all axis); --no-hostile skips the hostile passes. --record (EXPLICIT, never automatic) refreshes dev/net-mocks.json from live reads first: it boots each screen (load + root onAppear ONLY \u2014 nothing the app's own launch would not fire; the tap/action crawl never sees the live transport, and only proven reads \u2014 GET, or read-shaped POST (/query, /search, /list*, \u2026) \u2014 ever reach the wire), freezes the round-trips, then crawls hermetically on the fresh fixture; record against a QA site, not production. Nodes it cannot judge headless are named, never silently passed. Exit 1 on any error. The CLI carries the authoring runtime off-repo; no per-app SDK install is required. --browser runs the whole loop (build \u2192 extract \u2192 validate \u2192 render \u2192 test) in YOUR browser instead of here, so the source never reaches a pod: it opens one screen's worker + descriptor in a tab, blocks on a loopback callback, and prints the report it posts back. One screen per tab \u2014 name it. --no-open prints the URL rather than launching a browser (SSH); --timeout <ms> bounds the wait, which matters because the page has no execution deadline of its own and an infinite loop in the worker hangs the tab.", required: "[<screenId>\u2026] [--content <dir>] [--net <file>] [--budget <ms>] [--deep [n]] [--record] [--native [ios|android]] [--no-shots] [--offline] [--no-hostile] [--all-screens] [--browser] [--no-open] [--timeout <ms>] [--json]" },
67677
67883
  { name: "ux", summary: "audit or record mobile UX evidence. `audit` checks the public-safe contract, seeded blank acceptance rows, placeholder source copy, app-specific mocks, and iOS/Android audit records. It warns while existing templates migrate; `--strict` promotes missing evidence. `record` stores checks performed on an installed iOS/Android build: `--checks` is required for passed|failed and empty for unavailable, which drove nothing and needs a `--reason` instead. `record` also STAMPS the release it audited (it resolves the id from a build, the same one a push would publish), because an unforced `himi push` of a release with screens refuses when no drive is recorded for THAT release, or when the one that is recorded says `failed` \u2014 the artifact stopped being advice nobody reads (#2955). `unavailable` still satisfies it; only `failed` refuses.", required: "audit [--content <dir>] [--strict] [--json] | record --content <dir> --platform ios|android --status passed|failed|unavailable [--checks back,keyboard,...] [--reason <text>]" },
67678
- { name: "progress", summary: 'the build JOURNAL an agent keeps for the owner while it works. `himi progress "<what changed>" --step <1-8|name> --image <png>` appends one postcard to .himi/progress.jsonl \u2014 the step is one of the customer journey\'s eight (discover, classify, plan, implement, brand, validate, review, download) \u2014 and COPIES each image, content-addressed, into .himi/history/, because .himi/shots/ is overwritten by every render and cleared by every full `himi test`, so a path into it would later show a different picture than the one the owner was sent. --image repeats. --notify also raises a best-effort desktop notification for an owner who stepped away. A bare `himi progress` prints the journal with the current step and elapsed time \u2014 also how an agent recovers its place after its context was compacted. APPROVALS: `--approve <gate> "<what>"` records one the customer just gave (gates: site-create, connect, private-data, draft, promote, push-signing, qa-build) in .himi/approvals.jsonl, and `--done <gate>` / `--drop <gate>` closes it \u2014 an approval is owed until then, and the chat that held it does not survive a new session. `--brief` prints plain text for a session start: the current step, the last postcard, every open approval, and whether this app\'s agent files are stale \u2014 the SessionStart hook `himi agent-init` installs runs exactly this. Local only: nothing is sent anywhere.', required: '["<message>" [--step <1-8|name>] [--image <png>]\u2026 [--notify]] [--content <dir>] | --approve|--done|--drop <gate> ["<note>"] | --brief' },
67679
- { name: "studio", summary: "open the BUILD BOARD \u2014 one local page the owner can leave open while an agent builds their app, which fills itself in: every screen as it renders (NEW/CHANGED badges, and each earlier version kept for a filmstrip), the journey stepper and the latest postcard from `himi progress`, the validate/test/UX/draft checks as they run, the site next to the app's home screen with its palette, and the navigation the test crawl followed. Writes .himi/studio/index.html and opens it; the page re-reads its state every two seconds, so nothing needs to be served. OPT-IN BY EXISTENCE: until this command has run, no other command touches .himi/studio/ \u2014 after it, render/test/validate/push/icon/site/ux/progress each refresh the board as they finish (best-effort; never fails a command). Every image is a content-addressed copy in .himi/history/, never a live path into .himi/shots/. Local only, no secrets: it never reads a preview token, handover code or control token. --no-open prints the path instead (SSH, CI). `himi studio reel` writes the MAKING-OF: one self-contained HTML file (every image inlined, nothing fetched) with a flipbook of the home screen version by version, captioned by the postcard in effect at the time, the final screens, and every postcard \u2014 sized to stay attachable, thinning frames first. Defaults to .himi/making-of.html (never archived by push); --out to put it elsewhere.", required: "[--content <dir>] [--no-open] | reel [--out <file>] [--no-open]" },
67884
+ { name: "progress", summary: 'the build JOURNAL an agent keeps for the owner while it works. `himi progress "<what changed>" --step <1-8|name> --image <png>` appends one postcard to .himi/progress.jsonl \u2014 the step is one of the customer journey\'s eight (discover, classify, plan, implement, brand, validate, review, download) \u2014 and COPIES each image, content-addressed, into .himi/history/, because .himi/shots/ is overwritten by every render and cleared by every full `himi test`, so a path into it would later show a different picture than the one the owner was sent. --image repeats. --notify also raises a best-effort desktop notification for an owner who stepped away. A bare `himi progress` prints the journal with the current step and elapsed time \u2014 also how an agent recovers its place after its context was compacted. APPROVALS: `--approve <gate> "<what>"` records one the customer just gave (gates: site-create, connect, private-data, draft, promote, push-signing, qa-build) in .himi/approvals.jsonl, and `--done <gate>` / `--drop <gate>` closes it \u2014 an approval is owed until then, and the chat that held it does not survive a new session. `--brief` prints plain text for a session start: the current step, the last postcard, every open approval, and whether this app\'s agent files are stale \u2014 the SessionStart hook `himi agent-init` installs runs exactly this. The CLI also journals three milestones by itself \u2014 the first PNG render of the home screen (once per app), every completed `himi test` crawl, every draft push \u2014 marked `"by": "himi"`; they never move the journey step, carry no link or credential, and step aside (folded out of every reader) when the agent journals the same step or picture within five minutes. Local only: nothing is sent anywhere.', required: '["<message>" [--step <1-8|name>] [--image <png>]\u2026 [--notify]] [--content <dir>] | --approve|--done|--drop <gate> ["<note>"] | --brief' },
67885
+ { name: "studio", summary: "open the BUILD BOARD \u2014 one local page the owner can leave open while an agent builds their app, which fills itself in: every screen as it renders (NEW/CHANGED badges, and each earlier version kept for a filmstrip), the journey stepper and the latest postcard from `himi progress`, the validate/test/UX/draft checks as they run, the site next to the app's home screen with its palette, and the navigation the test crawl followed. Writes .himi/studio/index.html and opens it; the page re-reads its state every two seconds, so nothing needs to be served. OPT-IN BY EXISTENCE: until this command (or `himi init`, which makes the board for every app it creates, without opening a browser) has run, no other command touches .himi/studio/ \u2014 after it, render/test/validate/push/icon/site/ux/progress each refresh the board as they finish (best-effort; never fails a command). Every image is a content-addressed copy in .himi/history/, never a live path into .himi/shots/. Local only, no secrets: it never reads a preview token, handover code or control token. --no-open prints the path instead (SSH, CI). `himi studio reel` writes the MAKING-OF: one self-contained HTML file (every image inlined, nothing fetched) with a flipbook of the home screen version by version, captioned by the postcard in effect at the time, the final screens, and every postcard \u2014 sized to stay attachable, thinning frames first. Defaults to .himi/making-of.html (never archived by push); --out to put it elsewhere.", required: "[--content <dir>] [--no-open] | reel [--out <file>] [--no-open]" },
67680
67886
  { name: "render", summary: "headless-render a screen with the web renderer: render-tree + diagnostics, optional --png [<file>] (bare: .himi/shots/<id>.png, what the build board shows), --scheme dark, --tree. Local by default (content dir). Pass --remote --app --release to hit GET /v1/preview/\u2026/render on the serve plane after a push. Boots the worker so $service bindings resolve; --static raw bindings; --live real Wix fetches; --state injects worker state; --input supplies pre-boot navigation params; --width/--device for PNG viewport; --rtl for dir=rtl + $env:isRTL.", required: "<screenId> [--content <dir>] [--scheme dark] [--png [<file>]] [--tree] [--static] [--live] [--state <json|file>] [--input <json|file>] [--width <px> | --device <name>] [--rtl] [--remote --app --release]" },
67681
67887
  { name: "cms", summary: "inspect, provision, and seed the app's CMS (Wix Data) collections, declared as a `cms` block in config.ts. `query` is READ-ONLY \u2014 by default it fetches ONLY the physical ids `config.ts` declares (item counts + schema drift) and reports `siteScanned: false`; it never lists the site, so an UNDECLARED `himi_<app>_` collection this app owns is not reached without `--all`. `--all` SCANS the site \u2014 every collection it lists is labeled `owned`/`adopted`/`foreign`, and it describes every foreign one \u2014 id, non-system field keys + types, and the site's read role \u2014 so an app with no `cms` block at all can DISCOVER a collection to adopt (`--all` is the one case where a missing `cms` block is not 'nothing to do'). Foreign collections are described up to a cap and are never item-counted (`itemCount: null`), because a row count is one query each; `foreign.count` is how many the scan LISTED \u2014 it follows the list endpoint's pages, and `listingIncomplete: true` says when the walk stopped short of the end, with `unlistedCollections` counting what it left out when Wix gave a paging total \u2014 and `foreign.truncated` says how many of those went undescribed, each still NAMED in `foreign.others` (id + displayName), so a collection the site already has is never hidden behind the cap. The scan is OWNER-scoped \u2014 it needs --site <msid> (or WIX_SITE_ID) plus owner auth via `wix token`; a visitor token can read a public collection's ITEMS but can never enumerate collections, so there is no tokenless discovery path. `create` reconciles declared collections additively and idempotently, and REFUSES any id outside the app's `himi_<app>_` namespace, so it is safe against a production site. It reports declared-vs-site `permissionDrift` but never writes permissions unless asked with --apply-permissions, so a field reconcile can't silently revert access control changed in the dashboard. `seed` inserts the git-tracked `seed:` rows into EMPTY owned collections (--force to add to a non-empty one; --ai writes placeholder rows plus a refinement prompt for you to review). Adopted collections (declared by `wixId`) are read-only and never created, updated, or seeded. Site from --site <msid> or WIX_SITE_ID; owner auth via `wix token`.", required: "query|create|seed [--site <msid>] [--content <dir>] [--all] [--dry-run] [--force] [--allow-destructive] [--apply-permissions] [--only <id>] [--ai] [--count <n>]" },
67682
67888
  { name: "data", summary: "read-only plan or explicitly provision/seed declared KV and Wix Data resources. Preview and production are separate scopes. KV administration uses the operator data plane from --data-url/HIMI_DATA_ADMIN_URL and HIMI_DATA_ADMIN_TOKEN; draft push never calls it.", required: "plan|create|seed [--site <msid>] [--environment preview|production] [--content <dir>] [--data-url <url>] [--dry-run] [--force]" },
@@ -67725,7 +67931,7 @@ var COMMANDS = [
67725
67931
  ];
67726
67932
  function cliVersion() {
67727
67933
  try {
67728
- return JSON.parse(readFileSync76(new URL("../package.json", import.meta.url), "utf8")).version ?? "unknown";
67934
+ return JSON.parse(readFileSync77(new URL("../package.json", import.meta.url), "utf8")).version ?? "unknown";
67729
67935
  } catch {
67730
67936
  return "unknown";
67731
67937
  }
@@ -67843,7 +68049,7 @@ function ageInDays(publishedAt, now = Date.now()) {
67843
68049
  }
67844
68050
  function packageVersion(path2) {
67845
68051
  try {
67846
- const version2 = JSON.parse(readFileSync76(path2, "utf8")).version;
68052
+ const version2 = JSON.parse(readFileSync77(path2, "utf8")).version;
67847
68053
  return typeof version2 === "string" && version2 ? version2 : void 0;
67848
68054
  } catch {
67849
68055
  return void 0;
@@ -67852,7 +68058,7 @@ function packageVersion(path2) {
67852
68058
  function resolvedSdkVersion(fromDir = process.cwd(), bundledManifest = new URL("./authoring-runtime/node_modules/@wix/himalaya/package.json", import.meta.url)) {
67853
68059
  let dir = resolve47(fromDir);
67854
68060
  for (; ; ) {
67855
- const version2 = packageVersion(join62(dir, "node_modules", "@wix", "himalaya", "package.json"));
68061
+ const version2 = packageVersion(join63(dir, "node_modules", "@wix", "himalaya", "package.json"));
67856
68062
  if (version2) return { version: version2, source: "local" };
67857
68063
  const parent = dirname34(dir);
67858
68064
  if (parent === dir) break;
@@ -67862,7 +68068,7 @@ function resolvedSdkVersion(fromDir = process.cwd(), bundledManifest = new URL("
67862
68068
  if (bundled) return { version: bundled, source: "bundled" };
67863
68069
  try {
67864
68070
  const root = execFileSync7("npm", ["root", "-g"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
67865
- const version2 = root ? packageVersion(join62(root, "@wix", "himalaya", "package.json")) : void 0;
68071
+ const version2 = root ? packageVersion(join63(root, "@wix", "himalaya", "package.json")) : void 0;
67866
68072
  return version2 ? { version: version2, source: "global" } : {};
67867
68073
  } catch {
67868
68074
  return {};
@@ -67909,11 +68115,11 @@ var UPDATE_RETRY_AFTER_FAILURE_MS = 60 * 60 * 1e3;
67909
68115
  var UPDATE_SNOOZE_MS = 24 * 60 * 60 * 1e3;
67910
68116
  var UPDATE_REEXEC_ENV = "HIMI_UPDATED_ONCE";
67911
68117
  function updateCachePath(env = process.env) {
67912
- return env.HIMI_UPDATE_CACHE_FILE ?? join62(homedir7(), ".himalaya", "update-check.json");
68118
+ return env.HIMI_UPDATE_CACHE_FILE ?? join63(homedir7(), ".himalaya", "update-check.json");
67913
68119
  }
67914
68120
  function readUpdateCache(path2) {
67915
68121
  try {
67916
- const parsed = JSON.parse(readFileSync76(path2, "utf8"));
68122
+ const parsed = JSON.parse(readFileSync77(path2, "utf8"));
67917
68123
  return parsed && typeof parsed === "object" ? parsed : {};
67918
68124
  } catch {
67919
68125
  return {};
@@ -67981,12 +68187,12 @@ function createUpdateRunner(spawn7 = spawnSync5) {
67981
68187
  },
67982
68188
  installedVersion: () => {
67983
68189
  const root = globalNodeModulesRoot();
67984
- return root ? packageVersion(join62(root, "@wix", "himalaya-cli", "package.json")) : void 0;
68190
+ return root ? packageVersion(join63(root, "@wix", "himalaya-cli", "package.json")) : void 0;
67985
68191
  },
67986
68192
  reexec: (argv) => {
67987
68193
  const root = globalNodeModulesRoot();
67988
- const entry = root ? join62(root, "@wix", "himalaya-cli", "dist", "cli.mjs") : void 0;
67989
- if (!entry || !existsSync64(entry)) return void 0;
68194
+ const entry = root ? join63(root, "@wix", "himalaya-cli", "dist", "cli.mjs") : void 0;
68195
+ if (!entry || !existsSync65(entry)) return void 0;
67990
68196
  const result = spawnSync5(process.execPath, [entry, ...argv], {
67991
68197
  // inherit keeps PIPED STDIN working — commands that read stdin must survive a re-exec.
67992
68198
  stdio: "inherit",
@@ -68033,7 +68239,7 @@ function staleUpdateLockFile(cachePath, now) {
68033
68239
  for (const path2 of [`${cachePath}.lock.break`, `${cachePath}.lock`]) {
68034
68240
  let content;
68035
68241
  try {
68036
- content = readFileSync76(path2, "utf8");
68242
+ content = readFileSync77(path2, "utf8");
68037
68243
  } catch {
68038
68244
  continue;
68039
68245
  }
@@ -68056,7 +68262,7 @@ function acquireUpdateLock(cachePath, now, hooks = {}) {
68056
68262
  const mine = lockStamp(now);
68057
68263
  const readLock = (path2) => {
68058
68264
  try {
68059
- return readFileSync76(path2, "utf8");
68265
+ return readFileSync77(path2, "utf8");
68060
68266
  } catch {
68061
68267
  return void 0;
68062
68268
  }
@@ -68640,7 +68846,7 @@ function executableOnPath(name, env = process.env) {
68640
68846
  const suffixes = process.platform === "win32" ? (env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean) : [""];
68641
68847
  for (const dir of entries) {
68642
68848
  for (const suffix of suffixes) {
68643
- const candidate = join62(dir, `${name}${suffix}`);
68849
+ const candidate = join63(dir, `${name}${suffix}`);
68644
68850
  try {
68645
68851
  if (!statSync17(candidate).isFile()) continue;
68646
68852
  if (process.platform !== "win32") accessSync(candidate, constants2.X_OK);
@@ -68749,7 +68955,7 @@ async function nativeConfigAdvisories(app, configSource) {
68749
68955
  const { iosProjectYml: iosProjectYml2 } = await Promise.resolve().then(() => (init_paths2(), paths_exports));
68750
68956
  const { androidOptedIn: androidOptedIn3, LABEL_IOS_SPLASH: LABEL_IOS_SPLASH2 } = await Promise.resolve().then(() => (init_splash(), splash_exports));
68751
68957
  const iosYmlPath = iosProjectYml2(app);
68752
- const iosYml = existsSync64(iosYmlPath) ? readFileSync76(iosYmlPath, "utf8") : "";
68958
+ const iosYml = existsSync65(iosYmlPath) ? readFileSync77(iosYmlPath, "utf8") : "";
68753
68959
  let optedIn = iosYml.includes(LABEL_IOS_SPLASH2);
68754
68960
  if (!optedIn) {
68755
68961
  const { androidManifestPath: androidManifestPath3 } = await Promise.resolve().then(() => (init_gen_native_config(), gen_native_config_exports));
@@ -68763,7 +68969,7 @@ async function nativeConfigAdvisories(app, configSource) {
68763
68969
  }
68764
68970
  function readConfigSource(dir) {
68765
68971
  try {
68766
- return readFileSync76(join62(dir, "config.ts"), "utf8");
68972
+ return readFileSync77(join63(dir, "config.ts"), "utf8");
68767
68973
  } catch {
68768
68974
  return null;
68769
68975
  }
@@ -68799,7 +69005,7 @@ function keptPlan(kept) {
68799
69005
  }
68800
69006
  async function initFromRemoteTemplate(name, parentDir, template, c) {
68801
69007
  const dir = resolve47(parentDir, name);
68802
- if (existsSync64(join62(dir, "himalaya.content.json"))) {
69008
+ if (existsSync65(join63(dir, "himalaya.content.json"))) {
68803
69009
  throw new Error(`content package already exists at ${dir}`);
68804
69010
  }
68805
69011
  const r = await c.fetchTemplateSource(TEMPLATE_APP_PREFIX + template);
@@ -68842,7 +69048,7 @@ function symlinkDestinationIssue(dest) {
68842
69048
  function restoreDestinationIssue(dest, force) {
68843
69049
  const link = symlinkDestinationIssue(dest);
68844
69050
  if (link) return link;
68845
- if (!existsSync64(dest)) return null;
69051
+ if (!existsSync65(dest)) return null;
68846
69052
  if (!statSync17(dest).isDirectory()) {
68847
69053
  return { error: `${dest} exists and is not a directory`, hint: "pass --out/--name to land somewhere else, or remove the file" };
68848
69054
  }
@@ -68857,7 +69063,7 @@ function restoreDestinationIssue(dest, force) {
68857
69063
  function isHimalayaMonorepoCheckout(start) {
68858
69064
  let dir = resolve47(start);
68859
69065
  for (; ; ) {
68860
- if (existsSync64(join62(dir, "tools", "himi-cli", "src", "cli.ts")) && existsSync64(join62(dir, "core", "serve", "src", "server.ts"))) return true;
69066
+ if (existsSync65(join63(dir, "tools", "himi-cli", "src", "cli.ts")) && existsSync65(join63(dir, "core", "serve", "src", "server.ts"))) return true;
68861
69067
  const parent = dirname34(dir);
68862
69068
  if (parent === dir) return false;
68863
69069
  dir = parent;
@@ -68895,7 +69101,7 @@ function shellShaDivergenceWarnings(jobs) {
68895
69101
  }
68896
69102
  async function templatePublishName(sourceDir) {
68897
69103
  const id = await appId(sourceDir);
68898
- return IDENTITY_SENTINELS.some((s) => id.includes(s)) ? basename15(sourceDir) : id;
69104
+ return IDENTITY_SENTINELS.some((s) => id.includes(s)) ? basename16(sourceDir) : id;
68899
69105
  }
68900
69106
  function templateNameError(name, from) {
68901
69107
  if (name.startsWith(TEMPLATE_APP_PREFIX)) {
@@ -68918,7 +69124,7 @@ function materializeForPreview(appDir2, slug, displayName) {
68918
69124
  Object.entries(files).map(([rel, body]) => [rel, rel === "himalaya.content.json" ? substituteIdentity(body, slug) : body])
68919
69125
  );
68920
69126
  const repoRoot2 = resolve47(fileURLToPath24(new URL("../../..", import.meta.url)));
68921
- const root = join62(repoRoot2, "node_modules", ".cache", "himi-template-preview");
69127
+ const root = join63(repoRoot2, "node_modules", ".cache", "himi-template-preview");
68922
69128
  const stillRunning = (pid) => {
68923
69129
  try {
68924
69130
  process.kill(pid, 0);
@@ -68932,12 +69138,12 @@ function materializeForPreview(appDir2, slug, displayName) {
68932
69138
  if (!entry.isDirectory()) continue;
68933
69139
  const pid = Number(entry.name.startsWith(`${slug}-`) ? entry.name.slice(slug.length + 1) : NaN);
68934
69140
  if (Number.isInteger(pid) && pid > 0 && pid !== process.pid && !stillRunning(pid)) {
68935
- rmSync14(join62(root, entry.name), { recursive: true, force: true });
69141
+ rmSync14(join63(root, entry.name), { recursive: true, force: true });
68936
69142
  }
68937
69143
  }
68938
69144
  } catch {
68939
69145
  }
68940
- const dest = join62(root, `${slug}-${process.pid}`);
69146
+ const dest = join63(root, `${slug}-${process.pid}`);
68941
69147
  rmSync14(dest, { recursive: true, force: true });
68942
69148
  mkdirSync31(dest, { recursive: true });
68943
69149
  try {
@@ -68950,17 +69156,17 @@ function materializeForPreview(appDir2, slug, displayName) {
68950
69156
  }
68951
69157
  async function appId(dir) {
68952
69158
  try {
68953
- const { readFileSync: readFileSync77 } = await import("node:fs");
68954
- const m = JSON.parse(readFileSync77(join62(dir, "himalaya.content.json"), "utf8"));
69159
+ const { readFileSync: readFileSync78 } = await import("node:fs");
69160
+ const m = JSON.parse(readFileSync78(join63(dir, "himalaya.content.json"), "utf8"));
68955
69161
  if (m.name) return m.name;
68956
69162
  } catch {
68957
69163
  }
68958
- return basename15(dir);
69164
+ return basename16(dir);
68959
69165
  }
68960
69166
  async function contentVisibility(dir) {
68961
69167
  try {
68962
- const { readFileSync: readFileSync77 } = await import("node:fs");
68963
- const m = JSON.parse(readFileSync77(join62(dir, "himalaya.content.json"), "utf8"));
69168
+ const { readFileSync: readFileSync78 } = await import("node:fs");
69169
+ const m = JSON.parse(readFileSync78(join63(dir, "himalaya.content.json"), "utf8"));
68964
69170
  if (m.visibility === "public" || m.visibility === "unlisted") return m.visibility;
68965
69171
  } catch {
68966
69172
  }
@@ -69184,7 +69390,7 @@ Seed your coding agent with \`himi agent-init\`. Guides: ${idx.portalUrl}`);
69184
69390
  ...res.removed.length ? { removed: res.removed, note: "older copies at the app root, moved to .himi/legacy-agent-files/ (a backup, in case one became app code) \u2014 the current ones live in .agents/skills/himalaya/" } : {},
69185
69391
  // In a git repo the refresh moves the diff identity an earlier `himi test` attested (that
69186
69392
  // hash is shared with the repo gates and deliberately exempts nothing but its own metadata).
69187
- ...existsSync64(join62(dir, ".himi", "attestations", "latest.json")) ? { next: "if `himi push` then refuses the attestation as stale, re-run `himi test` \u2014 in a git checkout this refresh changed the tree it covered" } : {},
69393
+ ...existsSync65(join63(dir, ".himi", "attestations", "latest.json")) ? { next: "if `himi push` then refuses the attestation as stale, re-run `himi test` \u2014 in a git checkout this refresh changed the tree it covered" } : {},
69188
69394
  ...res.skipped.length ? { skipped: res.skipped } : {},
69189
69395
  portalUrl: idx.portalUrl
69190
69396
  });
@@ -69207,7 +69413,7 @@ async function recordedReleaseForContentQuietly(dir, resolution, onDrift) {
69207
69413
  try {
69208
69414
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
69209
69415
  const bres = await buildContentForValidation2(dir, { resolution });
69210
- out = mkdtempSync4(join62(tmpdir5(), "himi-ux-record-"));
69416
+ out = mkdtempSync4(join63(tmpdir5(), "himi-ux-record-"));
69211
69417
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
69212
69418
  const outcome = await buildReleaseFromContent2(dir, out, { ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {}, collect: { lint: true } });
69213
69419
  const releaseId = outcome.manifest.releaseId;
@@ -69257,8 +69463,8 @@ function uxRestoreCommands(ctx) {
69257
69463
  };
69258
69464
  }
69259
69465
  function uxHandoverHint(uxEvidence, ctx) {
69260
- const { releaseId, contentArg, door, app } = ctx;
69261
- const uxRemedy = uxRecordCommands(contentArg);
69466
+ const { releaseId, contentArg: contentArg2, door, app } = ctx;
69467
+ const uxRemedy = uxRecordCommands(contentArg2);
69262
69468
  if (uxEvidence.state === "inherited-screens") {
69263
69469
  const restore = uxRestoreCommands(ctx);
69264
69470
  const alsoOwed = uxEvidence.supersededState === "failed" ? ` This package's own drive FAILED on ${uxEvidence.failedPlatforms.join(", ")}, so you owe more than a record: fix what that drive found, then record it against the restored tree \u2014 re-recording a failure without changing the app writes the same failure down again.` : uxEvidence.supersededState ? ` This package also has no drive this gate accepts (${uxEvidence.supersededState}), so the record below is a step you take AFTER the restore, not instead of it.` : "";
@@ -69385,7 +69591,7 @@ async function producedWidgetFamilies(dir) {
69385
69591
  let js;
69386
69592
  for (const candidate of [`${name}.bundle.js`, `${name}.js`, name]) {
69387
69593
  try {
69388
- js = readFileSync76(join62(outDir, candidate), "utf8");
69594
+ js = readFileSync77(join63(outDir, candidate), "utf8");
69389
69595
  break;
69390
69596
  } catch {
69391
69597
  }
@@ -69416,7 +69622,7 @@ async function producedWidgetFamilies(dir) {
69416
69622
  }
69417
69623
  return { families, source: "dist/bundles" };
69418
69624
  }
69419
- var boardOpen = (dir) => existsSync64(join62(dir, ".himi", "studio", "index.html"));
69625
+ var boardOpen = (dir) => existsSync65(join63(dir, ".himi", "studio", "index.html"));
69420
69626
  var BOARD_REFRESHERS = /* @__PURE__ */ new Set(["render", "test", "validate", "build", "push", "icon", "site", "ux", "progress"]);
69421
69627
  async function leaveForBoard(dir, record2, value) {
69422
69628
  if (!boardOpen(dir)) return;
@@ -69711,12 +69917,18 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69711
69917
  const owed = openApprovals2(readApprovals2(dir).events).length;
69712
69918
  next.push(`carried the planning journal into the app (${carried.join(", ")}${owed ? `; ${owed} approval${owed === 1 ? "" : "s"} still owed` : ""}) \u2014 \`himi progress --brief\` there shows it`);
69713
69919
  }
69714
- const planInParent = ["SPEC.md", "MOBILE-UX.md"].filter((f) => existsSync64(join62(parentDir, f)));
69920
+ const planInParent = ["SPEC.md", "MOBILE-UX.md"].filter((f) => existsSync65(join63(parentDir, f)));
69715
69921
  if (planInParent.length) {
69716
69922
  next.push(`${planInParent.join(" and ")} in ${parentDir} ${planInParent.length > 1 ? "are" : "is"} not the app's \u2014 if that is the approved plan, move it over the template's boilerplate in ${dir}`);
69717
69923
  }
69718
69924
  return { ...seeded, next };
69719
69925
  };
69926
+ const startSession = async (dir, agent2) => {
69927
+ const { openBoardForSession: openBoardForSession2, sessionSteps: sessionSteps2, homeScreenIdOf: homeScreenIdOf2 } = await Promise.resolve().then(() => (init_milestones(), milestones_exports));
69928
+ const board = await openBoardForSession2(dir);
69929
+ const { next: later2, ...rest } = agent2;
69930
+ return { ...rest, board, next: [...sessionSteps2({ dir, home: homeScreenIdOf2(dir) }), ...later2 ?? []] };
69931
+ };
69720
69932
  if (template && flags.remote === true) {
69721
69933
  let fetched;
69722
69934
  try {
@@ -69729,14 +69941,14 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69729
69941
  io.error(JSON.stringify({ warning: `remote template fetch failed (${e.message}); falling back to the bundled "${template}"` }));
69730
69942
  }
69731
69943
  if (fetched) {
69732
- const agent2 = await withAgentFiles(fetched.dir);
69944
+ const agent2 = await startSession(fetched.dir, await withAgentFiles(fetched.dir));
69733
69945
  const pushSetup2 = await offerPushSetup2({ contentDir: fetched.dir, flags, io });
69734
69946
  print({ ok: true, app: name, dir: fetched.dir, template, source: "remote", files: fetched.files, ...keptPlan(fetched.kept), ...agent2, pushSetup: pushSetup2 });
69735
69947
  return 0;
69736
69948
  }
69737
69949
  }
69738
69950
  const res = scaffoldContentPackage(name, parentDir, template);
69739
- const agent = await withAgentFiles(res.dir);
69951
+ const agent = await startSession(res.dir, await withAgentFiles(res.dir));
69740
69952
  const pushSetup = await offerPushSetup2({ contentDir: res.dir, flags, io });
69741
69953
  print({ ok: true, app: name, dir: res.dir, ...template ? { template, source: "embedded" } : {}, files: res.files, ...keptPlan(res.kept), ...agent, pushSetup });
69742
69954
  return 0;
@@ -69838,7 +70050,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69838
70050
  const sourceDir = resolve47(str6(flags["source-dir"]) ?? appDir2);
69839
70051
  let meta;
69840
70052
  try {
69841
- meta = JSON.parse(readFileSync76(join62(sourceDir, TEMPLATE_META_FILE), "utf8"));
70053
+ meta = JSON.parse(readFileSync77(join63(sourceDir, TEMPLATE_META_FILE), "utf8"));
69842
70054
  } catch {
69843
70055
  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>)` }));
69844
70056
  return 2;
@@ -69848,7 +70060,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69848
70060
  return 2;
69849
70061
  }
69850
70062
  try {
69851
- const appearance = extractTemplateAppearance(JSON.parse(readFileSync76(join62(sourceDir, "tokens.json"), "utf8")));
70063
+ const appearance = extractTemplateAppearance(JSON.parse(readFileSync77(join63(sourceDir, "tokens.json"), "utf8")));
69852
70064
  meta = { ...appearance, ...meta };
69853
70065
  } catch {
69854
70066
  }
@@ -69870,7 +70082,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69870
70082
  const force = flags.force === true;
69871
70083
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
69872
70084
  const bres = await buildContentForValidation2(buildDir, { resolution: resolutionFlag(flags) });
69873
- const out = mkdtempSync4(join62(tmpdir5(), "himi-template-"));
70085
+ const out = mkdtempSync4(join63(tmpdir5(), "himi-template-"));
69874
70086
  publishScratch.push(out);
69875
70087
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
69876
70088
  const outcome = await buildReleaseFromContent2(buildDir, out, {
@@ -69924,7 +70136,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
69924
70136
  let iconHash;
69925
70137
  if (iconPath) {
69926
70138
  try {
69927
- const bytes2 = readFileSync76(iconPath);
70139
+ const bytes2 = readFileSync77(iconPath);
69928
70140
  payload.icon = bytes2.toString("base64");
69929
70141
  iconHash = createHash28("sha256").update(bytes2).digest("hex");
69930
70142
  } catch {
@@ -70167,7 +70379,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70167
70379
  const { navIssues: navIssues2 } = await Promise.resolve().then(() => (init_nav_lint(), nav_lint_exports));
70168
70380
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
70169
70381
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
70170
- const out = mkdtempSync4(join62(tmpdir5(), "himi-validate-"));
70382
+ const out = mkdtempSync4(join63(tmpdir5(), "himi-validate-"));
70171
70383
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
70172
70384
  const outcome = await buildReleaseFromContent2(dir, out, {
70173
70385
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -70210,7 +70422,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70210
70422
  const path2 = dpxSessionTokenPath();
70211
70423
  let raw;
70212
70424
  try {
70213
- raw = readFileSync76(path2, "utf8");
70425
+ raw = readFileSync77(path2, "utf8");
70214
70426
  } catch {
70215
70427
  throw new Error(`no DPX BO session at ${path2}; run dpx auth login, write dpx auth token to that path, then himi login --dpx --target bo`);
70216
70428
  }
@@ -70330,13 +70542,13 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70330
70542
  case "studio": {
70331
70543
  const { openStudio: openStudio2 } = await Promise.resolve().then(() => (init_studio(), studio_exports));
70332
70544
  const dir = contentDir(flags);
70333
- if (!existsSync64(join62(dir, "himalaya.content.json"))) {
70545
+ if (!existsSync65(join63(dir, "himalaya.content.json"))) {
70334
70546
  io.error(JSON.stringify({ ok: false, error: "not_a_content_package", dir, hint: "run from the app directory (or pass --content <dir>) \u2014 the board reads that package's .himi/" }));
70335
70547
  return 2;
70336
70548
  }
70337
70549
  if (pos[1] === "reel") {
70338
70550
  const { buildReel: buildReel2 } = await Promise.resolve().then(() => (init_studio_reel(), studio_reel_exports));
70339
- const out = resolve47(str6(flags.out) ?? join62(dir, ".himi", "making-of.html"));
70551
+ const out = resolve47(str6(flags.out) ?? join63(dir, ".himi", "making-of.html"));
70340
70552
  const { html, plan } = buildReel2(dir);
70341
70553
  mkdirSync31(dirname34(out), { recursive: true });
70342
70554
  writeFileSync29(out, html);
@@ -70402,8 +70614,8 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70402
70614
  formatBrief: formatBrief2
70403
70615
  } = await Promise.resolve().then(() => (init_progress(), progress_exports));
70404
70616
  const dir = contentDir(flags);
70405
- const journal = journalPath2(dir);
70406
- const isContentPackage = existsSync64(join62(dir, "himalaya.content.json"));
70617
+ const journal2 = journalPath2(dir);
70618
+ const isContentPackage = existsSync65(join63(dir, "himalaya.content.json"));
70407
70619
  const hint = isContentPackage ? {} : { hint: "no himalaya.content.json here \u2014 run from the app directory (or pass --content <dir>) so the build board finds this journal" };
70408
70620
  const message = pos.slice(1).join(" ").trim();
70409
70621
  const approvalFlags = ["approve", "done", "drop"].filter((f) => flags[f] !== void 0);
@@ -70455,7 +70667,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70455
70667
  }
70456
70668
  const { entries, skipped } = readProgress2(dir);
70457
70669
  const approvals = openApprovals2(readApprovals2(dir).events);
70458
- print({ ok: true, journal, ...summarizeProgress2(entries), entries, ...approvals.length ? { approvals } : {}, ...skipped ? { skipped } : {}, ...hint });
70670
+ print({ ok: true, journal: journal2, ...summarizeProgress2(entries), entries, ...approvals.length ? { approvals } : {}, ...skipped ? { skipped } : {}, ...hint });
70459
70671
  return 0;
70460
70672
  }
70461
70673
  let step;
@@ -70476,7 +70688,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70476
70688
  const entry = appendProgress2(dir, { message, ...step ? { step } : {}, images });
70477
70689
  const notified = flags.notify === true ? await notifyDesktop2(`himi \xB7 ${appLabel2(dir) ?? "your app"}`, message) : void 0;
70478
70690
  const { count: count2 } = summarizeProgress2(readProgress2(dir).entries);
70479
- print({ ok: true, entry, journal, count: count2, ...notified === void 0 ? {} : { notified }, ...hint });
70691
+ print({ ok: true, entry, journal: journal2, count: count2, ...notified === void 0 ? {} : { notified }, ...hint });
70480
70692
  return 0;
70481
70693
  } catch (e) {
70482
70694
  if (e instanceof ProgressError2) {
@@ -70539,7 +70751,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70539
70751
  case "test": {
70540
70752
  const dir = contentDir(flags);
70541
70753
  const structuredOutput = flags.json === true || io.isTTY !== true;
70542
- if (existsSync64(join62(dir, "functions"))) {
70754
+ if (existsSync65(join63(dir, "functions"))) {
70543
70755
  const { invokeLocalFunction: invokeLocalFunction2, loadFunctionsApp: loadFunctionsApp2 } = await Promise.resolve().then(() => (init_functions(), functions_exports));
70544
70756
  const loaded = await loadFunctionsApp2(dir);
70545
70757
  const results = [];
@@ -70626,7 +70838,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70626
70838
  }
70627
70839
  }
70628
70840
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
70629
- const out = mkdtempSync4(join62(tmpdir5(), "himi-test-"));
70841
+ const out = mkdtempSync4(join63(tmpdir5(), "himi-test-"));
70630
70842
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
70631
70843
  const outcome = await buildReleaseFromContent2(dir, out, {
70632
70844
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -70671,7 +70883,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70671
70883
  io.error(JSON.stringify({ error: `screen "${screen.screenId}" has no Tier-5 worker, so there is nothing for the browser loop to build` }));
70672
70884
  return 2;
70673
70885
  }
70674
- const sources = collectWorkerSources2(join62(dir, "tier5-src"), screen.bundleName);
70886
+ const sources = collectWorkerSources2(join63(dir, "tier5-src"), screen.bundleName);
70675
70887
  if (!sources) {
70676
70888
  io.error(JSON.stringify({ error: `no worker source found at tier5-src/${screen.bundleName} \u2014 the browser loop compiles SOURCE, not the built bundle` }));
70677
70889
  return 2;
@@ -70836,7 +71048,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70836
71048
  const sp = await runShotsPass2({
70837
71049
  descriptors: scopeDescriptors2(outcome.descriptors ?? [], pos.slice(1), invalidScreens),
70838
71050
  tokens: outcome.tokens,
70839
- outDir: join62(dir, ".himi", "shots"),
71051
+ outDir: join63(dir, ".himi", "shots"),
70840
71052
  width: 390,
70841
71053
  layers: { rules: shotRules, ...cmsSeed ? { cmsSeed } : {}, ...canned ? { canned: canned.byKey } : {} },
70842
71054
  ...budgetMs ? { budgetMs } : {},
@@ -70874,7 +71086,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70874
71086
  releaseId: result.releaseId,
70875
71087
  releasePayload: release,
70876
71088
  descriptors: attestedDescriptors,
70877
- screenshots: Object.fromEntries(writtenShots.filter((name) => crawledScreens.has(name.replace(/\.png$/, ""))).map((name) => [name.replace(/\.png$/, ""), join62(dir, ".himi", "shots", name)])),
71089
+ screenshots: Object.fromEntries(writtenShots.filter((name) => crawledScreens.has(name.replace(/\.png$/, ""))).map((name) => [name.replace(/\.png$/, ""), join63(dir, ".himi", "shots", name)])),
70878
71090
  coverage: {
70879
71091
  screens: crawledScreens.size,
70880
71092
  actionsDispatched: result.actions.exercised,
@@ -70886,14 +71098,30 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70886
71098
  counts: { errors: result.counts.errors, warnings: result.counts.warnings, gaps: ledger.length }
70887
71099
  }
70888
71100
  });
71101
+ const home = outcome.homeScreenId;
71102
+ const { afterTestRun: afterTestRun2 } = await Promise.resolve().then(() => (init_milestones(), milestones_exports));
71103
+ const afterRun = afterTestRun2(dir, {
71104
+ runId: attestation.runId,
71105
+ screens: crawledScreens.size,
71106
+ actions: result.actions.exercised,
71107
+ errors: result.counts.errors,
71108
+ ...result.navGraph ? { broken: result.navGraph.broken.length, unreached: result.navGraph.unreached.length } : {},
71109
+ ...home && writtenShots.includes(`${home}.png`) ? { homeShot: join63(dir, ".himi", "shots", `${home}.png`) } : {},
71110
+ boardOpen: boardOpen(dir)
71111
+ });
70889
71112
  if (structuredOutput) {
70890
- print(foldLegacyReport(legacy, {
70891
- source: "test",
70892
- evidence: { runId: attestation.runId, ts: attestation.runId.slice(0, 24), gitDiffHash },
70893
- gaps: ledger,
70894
- artifacts: result.shots?.dir ? [{ kind: "shots-dir", path: result.shots.dir }] : []
70895
- }));
70896
- } else io.log(formatReport2(result) + formatDelta2(delta) + formatLedger2(ledger, trend));
71113
+ print({
71114
+ ...foldLegacyReport(legacy, {
71115
+ source: "test",
71116
+ evidence: { runId: attestation.runId, ts: attestation.runId.slice(0, 24), gitDiffHash },
71117
+ gaps: ledger,
71118
+ artifacts: result.shots?.dir ? [{ kind: "shots-dir", path: result.shots.dir }] : []
71119
+ }),
71120
+ ...afterRun.postcard ? { postcard: afterRun.postcard } : {},
71121
+ next: afterRun.next
71122
+ });
71123
+ } else io.log(formatReport2(result) + formatDelta2(delta) + formatLedger2(ledger, trend) + `
71124
+ ${afterRun.next.join("\n")}`);
70897
71125
  await leaveForBoard(dir, "edges", result.edges ?? []);
70898
71126
  if (result.navGraph) await leaveForBoard(dir, "map", result.navGraph);
70899
71127
  return result.ok ? 0 : 1;
@@ -70965,7 +71193,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70965
71193
  return 2;
70966
71194
  }
70967
71195
  try {
70968
- const raw = stateArg.trim().startsWith("{") ? stateArg : readFileSync76(stateArg.replace(/^@/, ""), "utf8");
71196
+ const raw = stateArg.trim().startsWith("{") ? stateArg : readFileSync77(stateArg.replace(/^@/, ""), "utf8");
70969
71197
  injectedState = JSON.parse(raw);
70970
71198
  } catch (e) {
70971
71199
  io.error(JSON.stringify({ error: `--state must be inline JSON or a path to a .json file \u2014 ${e.message}` }));
@@ -70980,7 +71208,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
70980
71208
  return 2;
70981
71209
  }
70982
71210
  try {
70983
- const raw = inputArg.trim().startsWith("{") ? inputArg : readFileSync76(inputArg.replace(/^@/, ""), "utf8");
71211
+ const raw = inputArg.trim().startsWith("{") ? inputArg : readFileSync77(inputArg.replace(/^@/, ""), "utf8");
70984
71212
  const parsed = JSON.parse(raw);
70985
71213
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("must be a JSON object");
70986
71214
  injectedInput = parsed;
@@ -71029,7 +71257,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
71029
71257
  }
71030
71258
  const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
71031
71259
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
71032
- const out = mkdtempSync4(join62(tmpdir5(), "himi-render-"));
71260
+ const out = mkdtempSync4(join63(tmpdir5(), "himi-render-"));
71033
71261
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
71034
71262
  const outcome = await buildReleaseFromContent2(dir, out, {
71035
71263
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -71043,7 +71271,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
71043
71271
  const diagnostics = (outcome.lint ?? []).find((s) => s.screenId === screenId)?.issues ?? [];
71044
71272
  let pngFile;
71045
71273
  let attestationPngFile;
71046
- const shotPath = join62(dir, ".himi", "shots", `${screenId}.png`);
71274
+ const shotPath = join63(dir, ".himi", "shots", `${screenId}.png`);
71047
71275
  const pngTarget = typeof flags.png === "string" ? str6(flags.png) : flags.png === true ? shotPath : void 0;
71048
71276
  if (pngTarget && r.pngBase64) {
71049
71277
  pngFile = writePngFile(pngTarget, r.pngBase64);
@@ -71111,15 +71339,20 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
71111
71339
  coverage: { screens: 1, actionsDispatched: 0, uncoveredFns: 0, gaps: provisional.counts.gaps },
71112
71340
  report: { ok: provisional.ok, counts: provisional.counts }
71113
71341
  });
71114
- print(foldLegacyReport(legacyRender, {
71115
- source: "render",
71116
- app,
71117
- screenId,
71118
- releaseId: outcome.manifest.releaseId,
71119
- findings: diagnostics,
71120
- evidence: { runId: attestation.runId, ts: attestation.runId.slice(0, 24), gitDiffHash },
71121
- artifacts: pngFile ? [{ kind: "png", path: pngFile }] : []
71122
- }));
71342
+ const afterPng = pngFile ? (await Promise.resolve().then(() => (init_milestones(), milestones_exports))).afterRenderPng(dir, { screenId, homeScreenId: outcome.homeScreenId ?? null, pngFile, renderable: r.renderable, content: str6(flags.content) }) : null;
71343
+ print({
71344
+ ...foldLegacyReport(legacyRender, {
71345
+ source: "render",
71346
+ app,
71347
+ screenId,
71348
+ releaseId: outcome.manifest.releaseId,
71349
+ findings: diagnostics,
71350
+ evidence: { runId: attestation.runId, ts: attestation.runId.slice(0, 24), gitDiffHash },
71351
+ artifacts: pngFile ? [{ kind: "png", path: pngFile }] : []
71352
+ }),
71353
+ ...afterPng?.postcard ? { postcard: afterPng.postcard } : {},
71354
+ ...afterPng?.next.length ? { next: afterPng.next } : {}
71355
+ });
71123
71356
  return r.renderable ? 0 : 1;
71124
71357
  }
71125
71358
  case "tokens": {
@@ -71379,7 +71612,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71379
71612
  io.error(JSON.stringify(linked));
71380
71613
  return 1;
71381
71614
  }
71382
- if (existsSync64(dest)) {
71615
+ if (existsSync65(dest)) {
71383
71616
  if (!statSync17(dest).isDirectory()) {
71384
71617
  io.error(JSON.stringify({
71385
71618
  error: `${dest} exists and is not a directory`,
@@ -71392,7 +71625,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71392
71625
  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" }));
71393
71626
  return 1;
71394
71627
  }
71395
- if (replace && occupied && !existsSync64(join62(dest, "himalaya.content.json"))) {
71628
+ if (replace && occupied && !existsSync65(join63(dest, "himalaya.content.json"))) {
71396
71629
  io.error(JSON.stringify({
71397
71630
  error: `${dest} is not a content package \u2014 refusing to --replace it`,
71398
71631
  hint: "--replace only clears a directory that holds himalaya.content.json. Check --out/--name, or pass --force to merge into it instead."
@@ -71533,7 +71766,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71533
71766
  const force = flags.force === true;
71534
71767
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
71535
71768
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
71536
- const out = mkdtempSync4(join62(tmpdir5(), "himi-push-"));
71769
+ const out = mkdtempSync4(join63(tmpdir5(), "himi-push-"));
71537
71770
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
71538
71771
  const outcome = await buildReleaseFromContent2(dir, out, {
71539
71772
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -71559,8 +71792,8 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71559
71792
  const manifest = outcome.manifest;
71560
71793
  const payload = await loadReleaseFromDir(declared, out);
71561
71794
  const { shellQuote: shellQuote2 } = await Promise.resolve().then(() => (init_shell_quote(), shell_quote_exports));
71562
- const contentRel = relative11(process.cwd(), dir) || ".";
71563
- const contentArg = shellQuote2(contentRel);
71795
+ const contentRel = relative12(process.cwd(), dir) || ".";
71796
+ const contentArg2 = shellQuote2(contentRel);
71564
71797
  if (onlyRaw !== void 0) {
71565
71798
  const onlyDraft = flags.draft === true;
71566
71799
  const { parseOnlySelectors: parseOnlySelectors2, planFromPayload: planFromPayload2, notCarriedDiff: notCarriedDiff2 } = await Promise.resolve().then(() => (init_release_patch2(), release_patch_exports));
@@ -71604,7 +71837,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71604
71837
  });
71605
71838
  const patchUxContext = {
71606
71839
  releaseId: manifest.releaseId,
71607
- contentArg,
71840
+ contentArg: contentArg2,
71608
71841
  contentDir: contentRel,
71609
71842
  app,
71610
71843
  door: echoedInvocation("himi push", flags, shellQuote2),
@@ -71699,7 +71932,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71699
71932
  const staleEvidence = evidence.staleRuns > 0 ? `${evidence.staleRuns} retained attestation(s) were recorded at this commit, but none describes the content being published` : null;
71700
71933
  const attestationErrors = [...evidence.historyErrors, ...staleEvidence ? [staleEvidence] : []];
71701
71934
  if (attestationErrors.length > 0 && !force) {
71702
- const remedy = `\`himi test --content ${contentArg}\` (or \`himi render <screenId> --content ${contentArg}\`)`;
71935
+ const remedy = `\`himi test --content ${contentArg2}\` (or \`himi render <screenId> --content ${contentArg2}\`)`;
71703
71936
  const corrupted = evidence.historyErrors.length > 0;
71704
71937
  print({
71705
71938
  ok: false,
@@ -71737,7 +71970,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71737
71970
  // fixing one door and not the other is the drift the shared helpers exist to stop.
71738
71971
  hint: uxHandoverHint(uxEvidence, {
71739
71972
  releaseId: manifest.releaseId,
71740
- contentArg,
71973
+ contentArg: contentArg2,
71741
71974
  contentDir: contentRel,
71742
71975
  app,
71743
71976
  door: echoedInvocation("himi push", flags, shellQuote2),
@@ -71846,11 +72079,13 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71846
72079
  const base = serveBase();
71847
72080
  const explicitTarget = contextualDefaultTarget !== void 0 || flags.target !== void 0 || flags["serve-url"] !== void 0 || !!process.env.HIMI_SERVE_TARGET;
71848
72081
  const loopbackWarning = base ? loopbackTargetWarning(base, explicitTarget) : null;
72082
+ const afterDraft = draft ? (await Promise.resolve().then(() => (init_milestones(), milestones_exports))).afterDraftPush(dir, { app, releaseId: release, content: str6(flags.content) }) : null;
71849
72083
  print({
71850
72084
  ok: true,
71851
72085
  app,
71852
72086
  releaseId: release,
71853
72087
  draft,
72088
+ ...afterDraft?.postcard ? { postcard: afterDraft.postcard } : {},
71854
72089
  // Say so when the push acquired the slug on the caller's behalf. A claim takes a name
71855
72090
  // out of a global first-come namespace, counts against quota, and cannot be undone —
71856
72091
  // so it must never happen silently.
@@ -71891,7 +72126,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71891
72126
  // behind that: the plane keeps the previous attestation for this release id when a
71892
72127
  // push carries none, so the published release can look attested by an earlier run.
71893
72128
  ...!attestation && staleEvidence ? {
71894
- attestationNote: `${staleEvidence} \u2014 this release was published with no headless evidence of its own, and the serve plane may still show the attestation from an earlier push of this release id. Re-attest with \`himi test --content ${contentArg}\` and push again to attach one.`
72129
+ attestationNote: `${staleEvidence} \u2014 this release was published with no headless evidence of its own, and the serve plane may still show the attestation from an earlier push of this release id. Re-attest with \`himi test --content ${contentArg2}\` and push again to attach one.`
71895
72130
  } : {},
71896
72131
  // #2955 — the SAME honesty for the UX gate: `--force` published this preview without a
71897
72132
  // recorded mobile-UX drive for it, and the link below goes to a customer. Say it in the
@@ -71899,7 +72134,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71899
72134
  // the same silence one step apart.
71900
72135
  ...uxHandoverOverride(uxEvidence, {
71901
72136
  releaseId: manifest.releaseId,
71902
- contentArg,
72137
+ contentArg: contentArg2,
71903
72138
  contentDir: contentRel,
71904
72139
  app,
71905
72140
  door: echoedInvocation("himi push", flags, shellQuote2),
@@ -71945,7 +72180,8 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71945
72180
  next: [
71946
72181
  `himi context --app ${app} --release ${release}${targetName() !== "local" ? ` --target ${targetName()}` : ""}`,
71947
72182
  `himi screens --app ${app} --release ${release}${targetName() !== "local" ? ` --target ${targetName()}` : ""}`,
71948
- `himi telemetry --app ${app} --release ${release}${targetName() !== "local" ? ` --target ${targetName()}` : ""}`
72183
+ `himi telemetry --app ${app} --release ${release}${targetName() !== "local" ? ` --target ${targetName()}` : ""}`,
72184
+ ...afterDraft ? [afterDraft.next] : []
71949
72185
  ],
71950
72186
  promote: draft ? `himi promote --app ${app} --release ${release}${forcedPastErrors ? " --force" : ""}${targetName() !== "local" ? ` --target ${targetName()}` : ""}` : null,
71951
72187
  ...(outcome.manifest.functions?.length ?? 0) > 0 ? {
@@ -71989,7 +72225,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
71989
72225
  const displayName = str6(flags.name);
71990
72226
  const launchMode = flags.snapshot === true ? "snapshot" : "live";
71991
72227
  const packOpts = { createdBy: `himi-cli ${cliVersion()}`, launchMode, ...displayName ? { displayName } : {} };
71992
- const writePack = (root, appName, out, source) => format === "dir" ? packReleaseToDir2(root, appName, out, packOpts) : packRelease2(root, appName, out, { ...packOpts, ...source ? { source } : {} });
72228
+ const writePack = (root, appName2, out, source) => format === "dir" ? packReleaseToDir2(root, appName2, out, packOpts) : packRelease2(root, appName2, out, { ...packOpts, ...source ? { source } : {} });
71993
72229
  const releaseDir = str6(flags["release-dir"]);
71994
72230
  if (releaseDir) {
71995
72231
  const only = str6(flags.app);
@@ -72054,7 +72290,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72054
72290
  }
72055
72291
  const { buildContentForValidation: buildContentForValidation2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
72056
72292
  const bres = await buildContentForValidation2(dir, { resolution: resolutionFlag(flags) });
72057
- const built = mkdtempSync4(join62(tmpdir5(), "himi-pack-"));
72293
+ const built = mkdtempSync4(join63(tmpdir5(), "himi-pack-"));
72058
72294
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
72059
72295
  const outcome = await buildReleaseFromContent2(dir, built, {
72060
72296
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
@@ -72259,7 +72495,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72259
72495
  }));
72260
72496
  return 1;
72261
72497
  }
72262
- const archive = readFileSync76(resolve47(file));
72498
+ const archive = readFileSync77(resolve47(file));
72263
72499
  const result = await client2().createPackageShare({
72264
72500
  app: opened.header.app,
72265
72501
  releaseId: opened.header.releaseId,
@@ -72319,11 +72555,11 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72319
72555
  const app = await appId(dir2);
72320
72556
  const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
72321
72557
  const bres = await buildContentBundles2(dir2, { resolution: resolutionFlag(flags) });
72322
- temp = mkdtempSync4(join62(tmpdir5(), "himi-run-build-"));
72558
+ temp = mkdtempSync4(join63(tmpdir5(), "himi-run-build-"));
72323
72559
  const hasWorkers = bres.built.length > 0 || bres.compileErrors.length > 0;
72324
72560
  const initial = await buildReleaseFromContent2(dir2, temp, { ...hasWorkers ? { tier5Dir: bundleOutDir2(dir2) } : {} });
72325
72561
  servedMissing = initial.missingBundles;
72326
- source = join62(temp, app);
72562
+ source = join63(temp, app);
72327
72563
  }
72328
72564
  const chromeArg = str6(flags.chrome) ?? "preview";
72329
72565
  if (chromeArg !== "preview" && chromeArg !== "app") {
@@ -72359,7 +72595,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72359
72595
  if (!recordKit) return;
72360
72596
  startedAt = (/* @__PURE__ */ new Date()).toISOString();
72361
72597
  const failure = record2(served2, { ok: true, at: startedAt });
72362
- if (!recorded) io.error(JSON.stringify({ run: "run_record_skipped", message: `${failure ?? `${join62(dir, ".himi")} is not a real directory`}, so the build board will not frame this preview.` }));
72598
+ if (!recorded) io.error(JSON.stringify({ run: "run_record_skipped", message: `${failure ?? `${join63(dir, ".himi")} is not a real directory`}, so the build board will not frame this preview.` }));
72363
72599
  await refreshBoard();
72364
72600
  };
72365
72601
  const retract = async () => {
@@ -72390,7 +72626,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72390
72626
  }
72391
72627
  const watchKit = watch2 ? await Promise.resolve().then(() => (init_run_watch(), run_watch_exports)) : null;
72392
72628
  const bus = watchKit ? new watchKit.ReloadBus() : void 0;
72393
- if (watch2) served = mkdtempSync4(join62(tmpdir5(), "himi-run-served-"));
72629
+ if (watch2) served = mkdtempSync4(join63(tmpdir5(), "himi-run-served-"));
72394
72630
  const running = await runPackage2(source, {
72395
72631
  port,
72396
72632
  chrome: chromeArg,
@@ -72412,8 +72648,8 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72412
72648
  await announce(running);
72413
72649
  if (watchKit && bus) {
72414
72650
  const { buildContentBundles: buildContentBundles2, buildReleaseFromContent: buildReleaseFromContent2, bundleOutDir: bundleOutDir2 } = await loadAuthoring();
72415
- const live2 = join62(running.releaseRoot, running.app);
72416
- const manifestOf = (tree) => JSON.parse(readFileSync76(join62(tree, "release-manifest.json"), "utf8"));
72651
+ const live2 = join63(running.releaseRoot, running.app);
72652
+ const manifestOf = (tree) => JSON.parse(readFileSync77(join63(tree, "release-manifest.json"), "utf8"));
72417
72653
  let current = manifestOf(live2);
72418
72654
  const failed2 = async (message) => {
72419
72655
  if (stopping) return;
@@ -72424,7 +72660,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72424
72660
  };
72425
72661
  rebuild = watchKit.serialize(async () => {
72426
72662
  const t0 = Date.now();
72427
- const next = mkdtempSync4(join62(tmpdir5(), "himi-run-build-"));
72663
+ const next = mkdtempSync4(join63(tmpdir5(), "himi-run-build-"));
72428
72664
  try {
72429
72665
  const bres = await buildContentBundles2(dir, { resolution: resolutionFlag(flags) });
72430
72666
  if (bres.compileErrors.length) {
@@ -72441,16 +72677,16 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
72441
72677
  return;
72442
72678
  }
72443
72679
  if (stopping) return;
72444
- const fresh = manifestOf(join62(next, running.app));
72680
+ const fresh = manifestOf(join63(next, running.app));
72445
72681
  const diff = watchKit.diffReleases(current, fresh);
72446
72682
  const { describeChanges: describeChanges2, headline: headline2 } = await Promise.resolve().then(() => (init_build_changes(), build_changes_exports));
72447
- const changes = describeChanges2(live2, join62(next, running.app), diff);
72683
+ const changes = describeChanges2(live2, join63(next, running.app), diff);
72448
72684
  const retired = `${live2}.retired-${Date.now()}`;
72449
72685
  renameSync7(live2, retired);
72450
72686
  try {
72451
- renameSync7(join62(next, running.app), live2);
72687
+ renameSync7(join63(next, running.app), live2);
72452
72688
  } catch {
72453
- cpSync3(join62(next, running.app), live2, { recursive: true });
72689
+ cpSync3(join63(next, running.app), live2, { recursive: true });
72454
72690
  }
72455
72691
  rmSync14(retired, { recursive: true, force: true });
72456
72692
  current = fresh;
@@ -73060,7 +73296,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
73060
73296
  io.error(JSON.stringify({ error: `patch ${kind} needs --file`, usage: PATCH_USAGE }));
73061
73297
  return 2;
73062
73298
  }
73063
- if (!existsSync64(file)) {
73299
+ if (!existsSync65(file)) {
73064
73300
  io.error(JSON.stringify({ error: `no such file: ${file}` }));
73065
73301
  return 2;
73066
73302
  }
@@ -73116,7 +73352,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
73116
73352
  lintNote: "`himi patch` sends a raw file. Nothing here built or linted your app \u2014 `himi push --only <selector>` is the same edit with the full lint gate in front of it."
73117
73353
  };
73118
73354
  if (kind === "tokens" || kind === "app-config") {
73119
- const body = readFileSync76(file, "utf8");
73355
+ const body = readFileSync77(file, "utf8");
73120
73356
  const patch2 = kind === "tokens" ? { tokens: body } : { appConfig: body };
73121
73357
  return runReleasePatch({
73122
73358
  ...common,
@@ -73131,7 +73367,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
73131
73367
  io.error(JSON.stringify({ error: "patch screen needs a <screenId>", usage: PATCH_USAGE }));
73132
73368
  return 2;
73133
73369
  }
73134
- const descriptor = readFileSync76(file, "utf8");
73370
+ const descriptor = readFileSync77(file, "utf8");
73135
73371
  try {
73136
73372
  JSON.parse(descriptor);
73137
73373
  } catch (e) {
@@ -73171,7 +73407,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
73171
73407
  extra: { kind, screen: id, file: { path: file, bytes: Buffer.byteLength(descriptor, "utf8") } }
73172
73408
  });
73173
73409
  }
73174
- const bytes2 = readFileSync76(file);
73410
+ const bytes2 = readFileSync77(file);
73175
73411
  const sha = createHash28("sha256").update(bytes2).digest("hex");
73176
73412
  const forScreens = (str6(flags["for-screen"]) ?? "").split(",").map((s) => s.trim()).filter(Boolean);
73177
73413
  if (flags["no-rebind"] !== void 0 && flags["no-rebind"] !== true) {
@@ -73315,7 +73551,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
73315
73551
  }
73316
73552
  let def;
73317
73553
  try {
73318
- const parsed = JSON.parse(arg.startsWith("@") ? readFileSync76(arg.slice(1), "utf8") : arg);
73554
+ const parsed = JSON.parse(arg.startsWith("@") ? readFileSync77(arg.slice(1), "utf8") : arg);
73319
73555
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
73320
73556
  io.error(JSON.stringify({ error: "invalid_definition", problems: [`--definition must be a JSON object, got ${Array.isArray(parsed) ? "an array" : parsed === null ? "null" : typeof parsed}`] }));
73321
73557
  return 2;
@@ -73779,7 +74015,7 @@ ${label2}`);
73779
74015
  const platform = str6(flags.platform)?.split(",").map((p) => p.trim()).filter(Boolean);
73780
74016
  {
73781
74017
  const distDir = contentDir(flags);
73782
- if (existsSync64(join62(distDir, "himalaya.content.json"))) {
74018
+ if (existsSync65(join63(distDir, "himalaya.content.json"))) {
73783
74019
  const issues = await iconIssues(distDir, flags.strict === true, { requireDeclared: true, requireVerified: true });
73784
74020
  const errors = issues.filter((i) => i.severity === "error");
73785
74021
  for (const i of issues.filter((i2) => i2.severity === "warning")) {
@@ -74048,7 +74284,7 @@ ${label2}`);
74048
74284
  const dpxPath = dpxSessionTokenPath();
74049
74285
  let dpx;
74050
74286
  try {
74051
- dpx = readFileSync76(dpxPath, "utf8").trim();
74287
+ dpx = readFileSync77(dpxPath, "utf8").trim();
74052
74288
  } catch {
74053
74289
  io.error(JSON.stringify({ error: `no dpx session at ${dpxPath} \u2014 run \`dpx auth login\` then \`dpx auth token > ${dpxPath}\` (dpx 2.x keeps its own state in ~/.dpx/auth.json and does NOT write the legacy session file), or omit --dpx` }));
74054
74290
  return 1;
@@ -74456,7 +74692,7 @@ ${report2.ok ? "OK \u2014 data + serve plane reachable." : typeof report2.hint =
74456
74692
  const path2 = dpxSessionTokenPath();
74457
74693
  let raw;
74458
74694
  try {
74459
- raw = readFileSync76(path2, "utf8");
74695
+ raw = readFileSync77(path2, "utf8");
74460
74696
  } catch {
74461
74697
  io.error(JSON.stringify({ error: `no DPX BO session at ${path2}` }));
74462
74698
  return 1;