@wix/himalaya-cli 0.1187.0 → 0.1189.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
@@ -6818,7 +6818,6 @@ function walk(node, registry, path2, out2) {
6818
6818
  var SYSTEM_FONT_FAMILY, TONAL_ELEVATION_ALPHA, COLOR_TOKEN_PROPS, TYPOGRAPHY_TOKEN_PROPS;
6819
6819
  var init_tokens = __esm({
6820
6820
  "../../core/ts/src/tokens.ts"() {
6821
- "use strict";
6822
6821
  SYSTEM_FONT_FAMILY = "$system";
6823
6822
  TONAL_ELEVATION_ALPHA = [0, 0.05, 0.08, 0.11, 0.12, 0.14];
6824
6823
  COLOR_TOKEN_PROPS = {
@@ -17406,12 +17405,20 @@ function needsNativeBuild(id) {
17406
17405
  }
17407
17406
  function capabilitiesUsedBy(bundleJs) {
17408
17407
  const out2 = /* @__PURE__ */ new Set();
17409
- for (const m of bundleJs.matchAll(/["'`]capability\.([a-z0-9_]+)(\.[A-Za-z0-9]+)?["'`]/g)) {
17410
- if (m[2] || isKnownCapability(m[1])) out2.add(m[1]);
17408
+ for (const m of capabilityLiterals(bundleJs)) {
17409
+ if (m.verb || isKnownCapability(m.id)) out2.add(m.id);
17411
17410
  }
17412
17411
  for (const [id, marker] of BRIDGE_MARKERS) if (marker.test(bundleJs)) out2.add(id);
17413
17412
  return [...out2].sort();
17414
17413
  }
17414
+ function capabilityDispatchesIn(bundleJs) {
17415
+ const out2 = /* @__PURE__ */ new Set();
17416
+ for (const m of capabilityLiterals(bundleJs)) if (m.verb) out2.add(`capability.${m.id}.${m.verb}`);
17417
+ return [...out2].sort();
17418
+ }
17419
+ function capabilityLiterals(bundleJs) {
17420
+ return [...bundleJs.matchAll(CAPABILITY_LITERAL)].map((m) => ({ id: m[1], ...m[2] ? { verb: m[2] } : {} }));
17421
+ }
17415
17422
  function distAddRefusal(rawId) {
17416
17423
  const id = catalogKey(rawId);
17417
17424
  if (!isKnownCapability(id)) return "is not a capability id the platform knows";
@@ -17438,7 +17445,7 @@ function usageStringCopyFor(permissionCopy) {
17438
17445
  }
17439
17446
  return out2;
17440
17447
  }
17441
- var BRIDGE_CAPABILITY_IDS, KNOWN_CAPABILITY_IDS, BRIDGE_MARKERS, NOT_DIST_ADDABLE;
17448
+ var BRIDGE_CAPABILITY_IDS, KNOWN_CAPABILITY_IDS, CAPABILITY_LITERAL, BRIDGE_MARKERS, NOT_DIST_ADDABLE;
17442
17449
  var init_known = __esm({
17443
17450
  "../../core/sdk/capability-catalog/src/known.ts"() {
17444
17451
  init_builtins();
@@ -17456,6 +17463,7 @@ var init_known = __esm({
17456
17463
  ])
17457
17464
  ].sort()
17458
17465
  );
17466
+ CAPABILITY_LITERAL = /["'`]capability\.([a-z0-9_]+)(?:\.([A-Za-z0-9_]+))?["'`]/g;
17459
17467
  BRIDGE_MARKERS = [
17460
17468
  ["wix_api", /himalaya\.wixApi\b|\bwixApi\.call\b/],
17461
17469
  ["webview", /himalaya\.webview\b/]
@@ -22425,8 +22433,8 @@ var init_shell_capabilities_generated = __esm({
22425
22433
  appDir: "apps/himi-player",
22426
22434
  shared: true,
22427
22435
  extensible: false,
22428
- built: ["audio_record_background", "calls", "clipboard", "speech"],
22429
- serves: ["app_info", "audio_record", "audio_record_background", "browser_auth", "calls", "clipboard", "file_picker", "files", "himi_apps", "iap", "keyboard", "local_database", "on_device_ai", "open_url", "pdf", "presigned_upload", "release_control", "secure_storage", "share", "speaker_diarization", "speech", "stream", "wix_api", "wix_cashier", "wix_platform"]
22436
+ built: ["audio_record_background", "biometrics", "calendar", "calls", "camera", "clipboard", "contacts", "haptic", "location", "media_playback", "motion", "photos", "speech"],
22437
+ serves: ["app_info", "audio_record", "audio_record_background", "biometrics", "browser_auth", "calendar", "calls", "camera", "clipboard", "contacts", "file_picker", "files", "haptic", "himi_apps", "iap", "keyboard", "local_database", "location", "media_playback", "motion", "on_device_ai", "open_url", "pdf", "photos", "presigned_upload", "push", "release_control", "secure_storage", "share", "speaker_diarization", "speech", "stream", "wix_api", "wix_cashier", "wix_platform"]
22430
22438
  }
22431
22439
  };
22432
22440
  }
@@ -22436,6 +22444,7 @@ var init_shell_capabilities_generated = __esm({
22436
22444
  var capabilities_exports = {};
22437
22445
  __export(capabilities_exports, {
22438
22446
  DECLARE_HINT: () => DECLARE_HINT,
22447
+ LAST_DIST_FILE: () => LAST_DIST_FILE,
22439
22448
  SHELLS: () => SHELLS,
22440
22449
  allCapabilities: () => allCapabilities,
22441
22450
  capabilitiesOfPackage: () => capabilitiesOfPackage,
@@ -22447,11 +22456,15 @@ __export(capabilities_exports, {
22447
22456
  formatCapabilityList: () => formatCapabilityList,
22448
22457
  formatDistPlan: () => formatDistPlan,
22449
22458
  isShell: () => isShell,
22459
+ lastDistFromJob: () => lastDistFromJob,
22460
+ needsBinaryNotice: () => needsBinaryNotice,
22461
+ readLastDist: () => readLastDist,
22450
22462
  shellDistAdds: () => shellDistAdds,
22451
22463
  shellServes: () => shellServes,
22452
- shellSupport: () => shellSupport
22464
+ shellSupport: () => shellSupport,
22465
+ writeLastDist: () => writeLastDist
22453
22466
  });
22454
- import { existsSync as existsSync33, readFileSync as readFileSync33 } from "node:fs";
22467
+ import { existsSync as existsSync33, lstatSync as lstatSync9, readFileSync as readFileSync33 } from "node:fs";
22455
22468
  import { join as join35 } from "node:path";
22456
22469
  function isShell(name) {
22457
22470
  return Object.hasOwn(SHELL_CAPABILITIES, name);
@@ -22515,6 +22528,47 @@ function contentShell(dir, flag) {
22515
22528
  return null;
22516
22529
  }
22517
22530
  }
22531
+ async function writeLastDist(dir, rec) {
22532
+ const { realDirIn: realDirIn2, writeFileAtomic: writeFileAtomic2 } = await Promise.resolve().then(() => (init_progress(), progress_exports));
22533
+ const himi = realDirIn2(dir, ".himi");
22534
+ if (!himi) return null;
22535
+ const path2 = join35(himi, "last-dist.json");
22536
+ writeFileAtomic2(path2, `${JSON.stringify(rec, null, 2)}
22537
+ `);
22538
+ return path2;
22539
+ }
22540
+ function readLastDist(dir) {
22541
+ const path2 = join35(dir, LAST_DIST_FILE);
22542
+ try {
22543
+ if (lstatSync9(join35(dir, ".himi")).isSymbolicLink() || lstatSync9(path2).isSymbolicLink()) return null;
22544
+ } catch {
22545
+ return null;
22546
+ }
22547
+ try {
22548
+ const v = JSON.parse(readFileSync33(path2, "utf8"));
22549
+ if (typeof v.app !== "string" || typeof v.jobId !== "string" || typeof v.shell !== "string" || !Array.isArray(v.capabilities)) return null;
22550
+ return { app: v.app, jobId: v.jobId, shell: v.shell, capabilities: v.capabilities.filter((x) => typeof x === "string") };
22551
+ } catch {
22552
+ return null;
22553
+ }
22554
+ }
22555
+ function lastDistFromJob(job, now = /* @__PURE__ */ new Date()) {
22556
+ if (job.status !== "SUCCESS" || !Array.isArray(job.buildCapabilities)) return null;
22557
+ if (typeof job.app !== "string" || typeof job.jobId !== "string" || typeof job.shell !== "string") return null;
22558
+ const capabilities = [...new Set(job.buildCapabilities.filter((x) => typeof x === "string").map(catalogKey))].sort();
22559
+ return { app: job.app, jobId: job.jobId, shell: job.shell, capabilities, finishedAt: now.toISOString() };
22560
+ }
22561
+ function needsBinaryNotice(json6) {
22562
+ const nb = json6?.needsBinary;
22563
+ if (!nb || !Array.isArray(nb.missing)) return null;
22564
+ const missing = nb.missing.filter((x) => typeof x === "string");
22565
+ if (!missing.length) return null;
22566
+ return {
22567
+ missing,
22568
+ ...typeof nb.lastDistJobId === "string" ? { lastDistJobId: nb.lastDistJobId } : {},
22569
+ message: `needs a new binary: ${missing.join(", ")} \u2014 run himi dist`
22570
+ };
22571
+ }
22518
22572
  function noImportLabel(id) {
22519
22573
  return BRIDGE_CAPABILITY_IDS.includes(id) || id === "wix_platform" ? "a bridge capability (no @himalaya import of its own)" : "declare-only (no @himalaya import of its own)";
22520
22574
  }
@@ -22583,7 +22637,7 @@ function didYouMean(id) {
22583
22637
  const best = KNOWN_CAPABILITY_IDS.map((k) => [k, editDistance2(id, k)]).sort((x, y) => x[1] - y[1])[0];
22584
22638
  return best && best[1] <= 2 ? ` Did you mean "${best[0]}"?` : "";
22585
22639
  }
22586
- function capabilityIssues(usage, shell, strict) {
22640
+ function capabilityIssues(usage, shell, strict, lastDist) {
22587
22641
  if (!usage) return [];
22588
22642
  const warn = strict ? "error" : "warning";
22589
22643
  const issues = [];
@@ -22662,7 +22716,19 @@ function capabilityIssues(usage, shell, strict) {
22662
22716
  for (const sc of usage.screens) for (const id of [...sc.declared, ...sc.bundle ? usage.bundles[sc.bundle] ?? [] : []]) required3.add(catalogKey(id));
22663
22717
  const unserved = [...required3].filter((id) => isKnownCapability(id) && !shellServes(shell, id)).sort();
22664
22718
  const addable = s.extensible ? unserved.filter((id) => shellDistAdds(shell, id)) : [];
22665
- if (addable.length) {
22719
+ const installed = lastDist && lastDist.shell === shell ? expandCapabilities(lastDist.capabilities.map(catalogKey)) : null;
22720
+ if (installed) {
22721
+ const stale = [...required3].filter((id) => isKnownCapability(id) && !installed.has(id) && (shellServes(shell, id) || s.extensible && shellDistAdds(shell, id))).sort();
22722
+ if (stale.length) {
22723
+ issues.push({
22724
+ screen: null,
22725
+ pass: "capabilities",
22726
+ severity: "warning",
22727
+ capability: stale.join(","),
22728
+ message: `needs a new binary: ${stale.join(", ")} \u2014 your last \`himi dist\` (${lastDist.jobId}) was not built with ${stale.length === 1 ? "it" : "them"}, so that install cannot call ${stale.length === 1 ? "it" : "them"} until you run \`himi dist\` again. Preview with \`himi dist --plan\`.`
22729
+ });
22730
+ }
22731
+ } else if (addable.length) {
22666
22732
  issues.push({
22667
22733
  screen: null,
22668
22734
  pass: "capabilities",
@@ -22722,7 +22788,7 @@ function capabilityConfigFindings(sourceDir, builtDir) {
22722
22788
  }
22723
22789
  return out2;
22724
22790
  }
22725
- var DECLARE_HINT, REGISTRY, PACKAGES, SHELLS, servedSets, MAX_SUGGEST_LEN2, clip;
22791
+ var DECLARE_HINT, REGISTRY, PACKAGES, SHELLS, servedSets, LAST_DIST_FILE, MAX_SUGGEST_LEN2, clip;
22726
22792
  var init_capabilities = __esm({
22727
22793
  "src/capabilities.ts"() {
22728
22794
  init_src2();
@@ -22734,6 +22800,7 @@ var init_capabilities = __esm({
22734
22800
  PACKAGES = CAPABILITY_PACKAGES;
22735
22801
  SHELLS = Object.keys(SHELL_CAPABILITIES).sort();
22736
22802
  servedSets = /* @__PURE__ */ new Map();
22803
+ LAST_DIST_FILE = join35(".himi", "last-dist.json");
22737
22804
  MAX_SUGGEST_LEN2 = 64;
22738
22805
  clip = (id) => id.length > MAX_SUGGEST_LEN2 ? `${id.slice(0, MAX_SUGGEST_LEN2)}\u2026` : id;
22739
22806
  }
@@ -22828,7 +22895,7 @@ var init_provisioning_facts = __esm({
22828
22895
 
22829
22896
  // ../../core/dev-server/src/app-assets.ts
22830
22897
  import { createHash as createHash10 } from "node:crypto";
22831
- import { existsSync as existsSync34, lstatSync as lstatSync9, readFileSync as readFileSync35, readdirSync as readdirSync19, realpathSync as realpathSync5, statSync as statSync8 } from "node:fs";
22898
+ import { existsSync as existsSync34, lstatSync as lstatSync10, readFileSync as readFileSync35, readdirSync as readdirSync19, realpathSync as realpathSync5, statSync as statSync8 } from "node:fs";
22832
22899
  import { join as join36, resolve as resolve24, sep as sep8 } from "node:path";
22833
22900
  function contentTypeFor(name) {
22834
22901
  const ext = name.includes(".") ? name.slice(name.lastIndexOf(".") + 1).toLowerCase() : "";
@@ -22906,7 +22973,7 @@ function collectBrandFonts(appDir3) {
22906
22973
  if (rel.startsWith("/") || rel.split("/").includes("..")) continue;
22907
22974
  const path2 = join36(appDir3, rel);
22908
22975
  try {
22909
- if (!lstatSync9(path2).isFile()) continue;
22976
+ if (!lstatSync10(path2).isFile()) continue;
22910
22977
  const root = realpathSync5(appDir3);
22911
22978
  if (!realpathSync5(path2).startsWith(root + sep8)) continue;
22912
22979
  } catch {
@@ -28204,6 +28271,10 @@ function assertSiteShellConfig(site) {
28204
28271
  seen.add(page.id);
28205
28272
  sitePageUrl(site.origin, page.path);
28206
28273
  }
28274
+ for (const [key2, path2] of Object.entries(site.memberPages ?? {})) {
28275
+ if (typeof path2 !== "string" || !path2) throw new Error(`site memberPages.${key2} must be a path`);
28276
+ sitePageUrl(site.origin, path2);
28277
+ }
28207
28278
  if (site.layout !== void 0 && !SITE_LAYOUTS.includes(site.layout)) {
28208
28279
  throw new Error(`site layout ${JSON.stringify(site.layout)} must be one of ${SITE_LAYOUTS.join(", ")}`);
28209
28280
  }
@@ -31584,6 +31655,26 @@ function encodeDateFieldValue(raw) {
31584
31655
  const ms = Date.parse(raw);
31585
31656
  return Number.isFinite(ms) ? String(Math.trunc(ms / 1e3)) : "";
31586
31657
  }
31658
+ function clampDateFieldValue(encoded2, minUnix, maxUnix, mode) {
31659
+ if (!/^-?\d+$/.test(encoded2)) return encoded2;
31660
+ return String(clampDateUnix(Number(encoded2), minUnix, maxUnix, mode));
31661
+ }
31662
+ function clampDateUnix(unix, minUnix, maxUnix, mode) {
31663
+ const onDay = (bound) => {
31664
+ if (typeof bound !== "number") return null;
31665
+ if (mode !== "time") return bound;
31666
+ const b = new Date(bound * 1e3);
31667
+ const d = new Date(unix * 1e3);
31668
+ d.setHours(b.getHours(), b.getMinutes(), b.getSeconds(), 0);
31669
+ return Math.floor(d.getTime() / 1e3);
31670
+ };
31671
+ const lo = onDay(minUnix);
31672
+ const hi = onDay(maxUnix);
31673
+ let held = unix;
31674
+ if (hi != null && held > hi) held = hi;
31675
+ if (lo != null && held < lo) held = lo;
31676
+ return held;
31677
+ }
31587
31678
  var ISO_INTERNET_DATE_TIME;
31588
31679
  var init_form_bag = __esm({
31589
31680
  "../../core/ts/src/form-bag.ts"() {
@@ -37412,6 +37503,10 @@ var init_renderNode = __esm({
37412
37503
  InteractiveDatePicker = withExtras("InteractiveDatePicker", ({ props, ctx, wrap }) => {
37413
37504
  const C = ctx.color;
37414
37505
  const timeMode = props.mode === "time";
37506
+ const dateTimeMode = props.mode === "datetime";
37507
+ const minUnix = typeof props.minUnix === "number" ? props.minUnix : null;
37508
+ const maxUnix = typeof props.maxUnix === "number" ? props.maxUnix : null;
37509
+ const clampUnix = (unix) => clampDateUnix(unix, minUnix, maxUnix, props.mode);
37415
37510
  const boundRaw = useBoundValue(ctx, typeof props.valuePath === "string" && isBinding(props.valuePath) ? props.valuePath : "");
37416
37511
  const seedUnix = () => {
37417
37512
  if (typeof boundRaw === "number") return boundRaw;
@@ -37425,16 +37520,22 @@ var init_renderNode = __esm({
37425
37520
  if (unix == null) return "";
37426
37521
  const d = new Date(unix * 1e3);
37427
37522
  const p = (n) => String(n).padStart(2, "0");
37428
- return timeMode ? `${p(d.getHours())}:${p(d.getMinutes())}` : `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
37523
+ const day = `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`;
37524
+ const time = `${p(d.getHours())}:${p(d.getMinutes())}`;
37525
+ return timeMode ? time : dateTimeMode ? `${day}T${time}` : day;
37526
+ };
37527
+ const shownSeed = () => {
37528
+ const s = seedUnix();
37529
+ return s == null ? null : clampUnix(s);
37429
37530
  };
37430
- const [val, setVal] = React7.useState(ctx.empty ? "" : toInput(seedUnix()));
37531
+ const [val, setVal] = React7.useState(ctx.empty ? "" : toInput(shownSeed()));
37431
37532
  const dateServiceBound = typeof props.valuePath === "string" && props.valuePath.startsWith("$service:");
37432
37533
  const bagSource = () => dateServiceBound ? boundRaw : boundRaw !== void 0 && boundRaw !== null && boundRaw !== "" ? boundRaw : props.valueUnix;
37433
- const [dateBagVal, setDateBagVal] = React7.useState(ctx.empty ? "" : encodeDateFieldValue(bagSource()));
37534
+ const [dateBagVal, setDateBagVal] = React7.useState(ctx.empty ? "" : clampDateFieldValue(encodeDateFieldValue(bagSource()), props.minUnix, props.maxUnix, props.mode));
37434
37535
  React7.useEffect(() => {
37435
37536
  if (ctx.empty) return;
37436
- setVal(toInput(seedUnix()));
37437
- setDateBagVal(encodeDateFieldValue(bagSource()));
37537
+ setVal(toInput(shownSeed()));
37538
+ setDateBagVal(clampDateFieldValue(encodeDateFieldValue(bagSource()), props.minUnix, props.maxUnix, props.mode));
37438
37539
  }, [boundRaw, props.valueUnix, props.valuePath, ctx.empty]);
37439
37540
  const dateBag = React7.useContext(FormValuesContext);
37440
37541
  const dateFieldId = props.__formFieldId;
@@ -37451,7 +37552,7 @@ var init_renderNode = __esm({
37451
37552
  let label2 = "Select date";
37452
37553
  if (!ctx.empty && props.valueUnix) {
37453
37554
  const d = new Date(props.valueUnix * 1e3);
37454
- label2 = timeMode ? d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
37555
+ label2 = timeMode ? d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }) : dateTimeMode ? d.toLocaleString(void 0, { year: "numeric", month: "short", day: "numeric", hour: "2-digit", minute: "2-digit" }) : d.toLocaleDateString(void 0, { year: "numeric", month: "short", day: "numeric" });
37455
37556
  }
37456
37557
  return /* @__PURE__ */ jsxs5("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", width: "100%", background: C("surfacePrimary", "#fff"), border: `1px solid ${C("border", "#e3e3e3")}`, borderRadius: ctx.radius("md", 8), padding: "12px 14px", boxSizing: "border-box", fontFamily: bodyFont(ctx), fontSize: pxToRem(15), color: C("textPrimary", "#111"), ...wrap }, children: [
37457
37558
  /* @__PURE__ */ jsx8("span", { children: label2 }),
@@ -37465,12 +37566,14 @@ var init_renderNode = __esm({
37465
37566
  setDateBagVal("");
37466
37567
  return;
37467
37568
  }
37468
- const unix = timeMode ? (() => {
37569
+ const picked = timeMode ? (() => {
37469
37570
  const [h, m] = raw.split(":").map(Number);
37470
37571
  const d = /* @__PURE__ */ new Date();
37471
37572
  d.setHours(h || 0, m || 0, 0, 0);
37472
37573
  return Math.floor(d.getTime() / 1e3);
37473
- })() : Math.floor(Date.parse(`${raw}T00:00:00`) / 1e3);
37574
+ })() : dateTimeMode ? Math.floor(Date.parse(raw) / 1e3) : Math.floor(Date.parse(`${raw}T00:00:00`) / 1e3);
37575
+ const unix = Number.isFinite(picked) ? clampUnix(picked) : NaN;
37576
+ if (Number.isFinite(unix) && unix !== picked) setVal(toInput(unix));
37474
37577
  const value = Number.isFinite(unix) ? String(unix) : "";
37475
37578
  setDateBagVal(value);
37476
37579
  if (!onChange || !ctx.onAction) return;
@@ -37479,10 +37582,12 @@ var init_renderNode = __esm({
37479
37582
  return /* @__PURE__ */ jsx8(
37480
37583
  "input",
37481
37584
  {
37482
- type: timeMode ? "time" : "date",
37585
+ type: timeMode ? "time" : dateTimeMode ? "datetime-local" : "date",
37483
37586
  value: val,
37484
37587
  onChange: (e) => emit(e.currentTarget.value),
37485
- "aria-label": timeMode ? "Select time" : "Select date",
37588
+ min: minUnix != null ? toInput(minUnix) : void 0,
37589
+ max: maxUnix != null ? toInput(maxUnix) : void 0,
37590
+ "aria-label": timeMode ? "Select time" : dateTimeMode ? "Select date and time" : "Select date",
37486
37591
  style: { width: "100%", boxSizing: "border-box", background: C("surfacePrimary", "#fff"), border: `1px solid ${C("border", "#e3e3e3")}`, borderRadius: ctx.radius("md", 8), padding: "12px 14px", fontFamily: bodyFont(ctx), fontSize: pxToRem(15), color: C("textPrimary", "#111"), ...wrap }
37487
37592
  }
37488
37593
  );
@@ -44696,17 +44801,21 @@ var init_net_scenario = __esm({
44696
44801
  });
44697
44802
 
44698
44803
  // ../../core/dev-server/src/capability-fixtures.ts
44699
- function capabilityIdsIn(bundleJs) {
44700
- const out2 = /* @__PURE__ */ new Set();
44701
- for (const m of bundleJs.matchAll(/["'`]capability\.([a-z_0-9]+)\.([A-Za-z]+)["'`]/g)) {
44702
- if (STORE_BACKED.has(m[1])) continue;
44703
- out2.add(`capability.${m[1]}.${m[2]}`);
44804
+ function capabilityCoverageFor(bundleJs, granted) {
44805
+ const out2 = {};
44806
+ for (const action of capabilityDispatchesIn(bundleJs)) {
44807
+ const id = action.split(".")[1];
44808
+ out2[action] = !granted(id) ? "denied" : STORE_BACKED.has(id) ? "store" : CAPABILITY_SUCCESS[action] ? "success" : "unavailable-only";
44704
44809
  }
44705
- return [...out2].sort();
44810
+ return out2;
44811
+ }
44812
+ function capabilityIdsIn(bundleJs) {
44813
+ return capabilityDispatchesIn(bundleJs).filter((action) => !STORE_BACKED.has(action.split(".")[1]));
44706
44814
  }
44707
44815
  var decodeItem, MEDIA_EXT, MEDIA_MIME, FILE, CALL, MEDIA_STATUS, CAPABILITY_SUCCESS, STORE_BACKED;
44708
44816
  var init_capability_fixtures = __esm({
44709
44817
  "../../core/dev-server/src/capability-fixtures.ts"() {
44818
+ init_known();
44710
44819
  decodeItem = (raw) => {
44711
44820
  if (typeof raw !== "string") return {};
44712
44821
  try {
@@ -44889,7 +44998,34 @@ var init_capability_fixtures = __esm({
44889
44998
  const name = path2.split("/").pop() || path2;
44890
44999
  return { uri: `file:///${path2.replace(/^\/+/, "")}`, name, mimeType: typeof p.mimeType === "string" ? p.mimeType : "application/octet-stream", sizeBytes: 1024, modifiedAt: ctx.clock };
44891
45000
  },
44892
- "capability.files.remove": () => ({ removed: true })
45001
+ "capability.files.remove": () => ({ removed: true }),
45002
+ // --- camera -----------------------------------------------------------------------------------
45003
+ // `capturePhoto` THROWS on an envelope with no `uri` ("native handler returned no uri") and maps
45004
+ // `cancelled` to null, so the answer is a bare CapturedAsset — not wrapped, not cancelled.
45005
+ "capability.camera.capture": () => ({
45006
+ uri: "file:///crawl/photo-1.jpg",
45007
+ mime: "image/jpeg",
45008
+ width: 1024,
45009
+ height: 768,
45010
+ byteSize: 204800
45011
+ }),
45012
+ "capability.camera.pickDocument": () => ({
45013
+ uri: "file:///crawl/document-1.pdf",
45014
+ name: "document-1.pdf",
45015
+ mime: "application/pdf",
45016
+ byteSize: 40960
45017
+ }),
45018
+ "capability.camera.scanQR": () => ({ code: "crawl-qr-code" }),
45019
+ // --- location ---------------------------------------------------------------------------------
45020
+ // `getCurrent` reads `r.position` and returns null without it — the very "no fix" arm the main
45021
+ // axis already covers. The watch verbs are deliberately absent: `watchNext` is a long poll the
45022
+ // binding loops on until `stopped`, and a stateless fixture that always answered a position would
45023
+ // spin a `for await` for the crawl's whole budget.
45024
+ "capability.location.getPermissionStatus": () => ({ status: "granted" }),
45025
+ "capability.location.requestPermission": () => ({ status: "granted" }),
45026
+ "capability.location.getCurrent": (_p, ctx) => ({
45027
+ position: { lat: 32.0853, lng: 34.7818, accuracy: 10, timestamp: ctx.clock }
45028
+ })
44893
45029
  };
44894
45030
  STORE_BACKED = /* @__PURE__ */ new Set(["local_database", "secure_storage"]);
44895
45031
  }
@@ -45642,7 +45778,7 @@ function declaredCapabilitiesOf(descriptor) {
45642
45778
  const list = Array.isArray(raw) ? raw : [];
45643
45779
  return new Set(list.filter((c) => typeof c === "string"));
45644
45780
  }
45645
- function hostDispatch(localDbSchema, declaredCapabilities = /* @__PURE__ */ new Set(), capabilitySuccess = false, clock = 0) {
45781
+ function hostDispatch(localDbSchema, declaredCapabilities = /* @__PURE__ */ new Set(), capabilitySuccess = false, clock = 0, onDenied) {
45646
45782
  const localDb = /* @__PURE__ */ new Map();
45647
45783
  const writeQueue = [];
45648
45784
  const secureKv = /* @__PURE__ */ new Map();
@@ -45653,6 +45789,7 @@ function hostDispatch(localDbSchema, declaredCapabilities = /* @__PURE__ */ new
45653
45789
  const cap = CAPABILITY_ACTION.exec(action);
45654
45790
  if (cap) {
45655
45791
  if (!grants(declaredCapabilities, cap[1])) {
45792
+ onDenied?.(cap[1], action);
45656
45793
  throw new Error(
45657
45794
  `${cap[1]}: capability not declared \u2014 add "${cap[1]}" to this screen's logic_bundle.declaredCapabilities. An undeclared capability call is blocked on device.`
45658
45795
  );
@@ -46035,7 +46172,16 @@ async function bootScreenWorker(opts) {
46035
46172
  };
46036
46173
  const appear = opts.skipAppear ? null : rootOnAppear(opts.descriptor);
46037
46174
  const hostCalls = [];
46038
- const host = hostDispatch(opts.localDbSchema, declaredCapabilitiesOf(opts.descriptor), opts.capabilitySuccess === true, opts.clock ?? CRAWL_CLOCK);
46175
+ const deniedCapabilities = [];
46176
+ const host = hostDispatch(
46177
+ opts.localDbSchema,
46178
+ declaredCapabilitiesOf(opts.descriptor),
46179
+ opts.capabilitySuccess === true,
46180
+ opts.clock ?? CRAWL_CLOCK,
46181
+ (_id, action) => {
46182
+ deniedCapabilities.push(action);
46183
+ }
46184
+ );
46039
46185
  const w = await bootBundleWorker(opts.bundleJs, {
46040
46186
  wixCall: opts.wixCall,
46041
46187
  input,
@@ -46070,7 +46216,7 @@ async function bootScreenWorker(opts) {
46070
46216
  }
46071
46217
  for (let i = 0; i < 20; i++) await yieldMacrotask();
46072
46218
  const { state, defects } = snapshot(w);
46073
- return { ...w, hostCalls, state: Object.keys(state).length ? state : w.services, defects, ...deadLifecycle ? { deadLifecycle } : {} };
46219
+ return { ...w, hostCalls, deniedCapabilities, state: Object.keys(state).length ? state : w.services, defects, ...deadLifecycle ? { deadLifecycle } : {} };
46074
46220
  }
46075
46221
  function effectSince(w, mark, action, componentId) {
46076
46222
  const wix = w.calls.slice(mark.wix);
@@ -46131,12 +46277,14 @@ async function withSwallowedRejections(fn) {
46131
46277
  async function crawlScreenCapabilitySuccess(opts) {
46132
46278
  return withSwallowedRejections(async () => {
46133
46279
  const declared = declaredCapabilitiesOf(opts.descriptor);
46280
+ const coverage = opts.bundleJs ? capabilityCoverageFor(opts.bundleJs, (id) => grants(declared, id)) : {};
46134
46281
  const modelled = opts.bundleJs ? capabilityIdsIn(opts.bundleJs).filter((id) => CAPABILITY_SUCCESS[id] && grants(declared, id.split(".")[1])) : [];
46135
- if (!modelled.length) return { findings: [], modelled: 0 };
46282
+ if (!modelled.length) return { findings: [], modelled: 0, coverage };
46136
46283
  const r = await crawlScreenInner({ ...opts, capabilitySuccess: true });
46137
46284
  return {
46138
46285
  findings: r.findings.map((f) => ({ ...f, axis: f.axis ? `capability:success+${f.axis}` : "capability:success" })),
46139
46286
  modelled: modelled.length,
46287
+ coverage,
46140
46288
  // The success arm is where a paid flow goes on past `isAvailable` to `proceedToPayment`, so
46141
46289
  // its effects are the write inventory's too — unlike its counts, they cannot double anything.
46142
46290
  ...r.effects?.length ? { effects: r.effects } : {}
@@ -46530,11 +46678,29 @@ async function crawlScreenInner(opts) {
46530
46678
  hint: `${d.reason === "no-service" ? "services defined" : "actions declared"}: ${d.declared.join(", ") || "none"}. The host fires onAppear on device, so this screen stays on its initial state forever.`
46531
46679
  });
46532
46680
  };
46681
+ const deniedReported = /* @__PURE__ */ new Set();
46682
+ const reportDenied = (actions, trigger) => {
46683
+ for (const action of actions) {
46684
+ const id = CAPABILITY_ACTION.exec(action)?.[1] ?? action;
46685
+ if (deniedReported.has(id)) continue;
46686
+ deniedReported.add(id);
46687
+ findings.push({
46688
+ screen: screenId,
46689
+ trigger,
46690
+ invariant: "capability-undeclared",
46691
+ severity: "error",
46692
+ actual: action,
46693
+ message: `${action} was denied: "${id}" is not in this screen's logicBundle.declaredCapabilities \u2014 the call is blocked on device`,
46694
+ hint: `add "${id}" to this screen's logicBundle.declaredCapabilities (the runtime gate) and to config.ts capabilities (what the binary is built with). If this screen should never make the call, it is reaching a shared worker action meant for another screen. A try/catch around the call hides this on device: the control just does nothing.`
46695
+ });
46696
+ }
46697
+ };
46533
46698
  const first = await boot();
46534
46699
  if (first) {
46535
46700
  try {
46536
46701
  if (first.deadLifecycle) reportDeadLifecycle(first.deadLifecycle);
46537
46702
  check("boot", first.state, first.errors, first.defects);
46703
+ reportDenied(first.deniedCapabilities, "boot");
46538
46704
  observations = collectObservations(descriptor, first.state);
46539
46705
  seedWords = seedTokens(first.state);
46540
46706
  harvestStaticEdges(first.state);
@@ -46628,6 +46794,7 @@ async function crawlScreenInner(opts) {
46628
46794
  const before = JSON.stringify(preState);
46629
46795
  const callsBefore = w.calls.length;
46630
46796
  const hostBefore = w.hostCalls.length;
46797
+ const deniedBefore = w.deniedCapabilities.length;
46631
46798
  const errors = [];
46632
46799
  try {
46633
46800
  await handle.dispatch(parsed.action, ...workerArgs(dispatched));
@@ -46637,6 +46804,7 @@ async function crawlScreenInner(opts) {
46637
46804
  exercised += 1;
46638
46805
  const { state, defects } = snapshot(w);
46639
46806
  check(trigger, state, errors, defects);
46807
+ reportDenied(w.deniedCapabilities.slice(deniedBefore), trigger);
46640
46808
  const navCountBefore = navEdges.length;
46641
46809
  drainWorkerEdges(w, `worker:${site.actionId}`);
46642
46810
  drainPollDirectives(w, trigger);
@@ -51098,7 +51266,7 @@ __export(config_push_edit_exports, {
51098
51266
  enablePushInConfig: () => enablePushInConfig,
51099
51267
  previewPushConfigEdit: () => previewPushConfigEdit
51100
51268
  });
51101
- import { lstatSync as lstatSync10, readFileSync as readFileSync53 } from "node:fs";
51269
+ import { lstatSync as lstatSync11, readFileSync as readFileSync53 } from "node:fs";
51102
51270
  import { basename as basename13, join as join46 } from "node:path";
51103
51271
  import ts from "typescript";
51104
51272
  function enablePushInConfig(source, options) {
@@ -51328,7 +51496,7 @@ function backUp(dir, fileName, original) {
51328
51496
  }
51329
51497
  function isSymlink2(path2) {
51330
51498
  try {
51331
- return lstatSync10(path2).isSymbolicLink();
51499
+ return lstatSync11(path2).isSymbolicLink();
51332
51500
  } catch {
51333
51501
  return false;
51334
51502
  }
@@ -51991,7 +52159,7 @@ __export(app_source_exports, {
51991
52159
  zipAppSource: () => zipAppSource
51992
52160
  });
51993
52161
  import { createHash as createHash24 } from "node:crypto";
51994
- import { readdirSync as readdirSync27, lstatSync as lstatSync11, readFileSync as readFileSync55, mkdirSync as mkdirSync24, writeFileSync as writeFileSync19, existsSync as existsSync49, rmSync as rmSync11, rmdirSync as rmdirSync2, unlinkSync as unlinkSync3 } from "node:fs";
52162
+ import { readdirSync as readdirSync27, lstatSync as lstatSync12, readFileSync as readFileSync55, mkdirSync as mkdirSync24, writeFileSync as writeFileSync19, existsSync as existsSync49, rmSync as rmSync11, rmdirSync as rmdirSync2, unlinkSync as unlinkSync3 } from "node:fs";
51995
52163
  import { join as join48, resolve as resolve41, sep as sep11 } from "node:path";
51996
52164
  import { createRequire as createRequire3 } from "node:module";
51997
52165
  import { deflateRawSync as deflateRawSync2 } from "node:zlib";
@@ -52001,7 +52169,7 @@ function walkSourceTree(dir, file, skip, enter = () => {
52001
52169
  enter(relDir);
52002
52170
  for (const name of readdirSync27(join48(dir, relDir))) {
52003
52171
  const rel = relDir ? join48(relDir, name) : name;
52004
- const st = lstatSync11(join48(dir, rel));
52172
+ const st = lstatSync12(join48(dir, rel));
52005
52173
  if (st.isSymbolicLink()) {
52006
52174
  skip(rel);
52007
52175
  continue;
@@ -52082,7 +52250,7 @@ function newestSourceChangeMs(dir) {
52082
52250
  dir,
52083
52251
  (archivePath, hostRel) => {
52084
52252
  if (/\.md$/i.test(archivePath) || !counts(hostRel)) return;
52085
- const m = lstatSync11(join48(dir, hostRel)).mtimeMs;
52253
+ const m = lstatSync12(join48(dir, hostRel)).mtimeMs;
52086
52254
  if (m > newest) newest = m;
52087
52255
  },
52088
52256
  () => {
@@ -52094,7 +52262,7 @@ function newestSourceChangeMs(dir) {
52094
52262
  // Nor a root directory the render does not read: creating a file in `_qa/` moves `_qa/`.
52095
52263
  (hostRel) => {
52096
52264
  if (hostRel === "" || !inRead(hostRel)) return;
52097
- const m = lstatSync11(join48(dir, hostRel)).mtimeMs;
52265
+ const m = lstatSync12(join48(dir, hostRel)).mtimeMs;
52098
52266
  if (m > newest) newest = m;
52099
52267
  }
52100
52268
  );
@@ -52142,7 +52310,7 @@ function readAppSource(zip) {
52142
52310
  }
52143
52311
  function pathKind(p) {
52144
52312
  try {
52145
- return lstatSync11(p);
52313
+ return lstatSync12(p);
52146
52314
  } catch {
52147
52315
  return null;
52148
52316
  }
@@ -52239,7 +52407,7 @@ function pruneEmptyDirs(root, rel, needed, removed) {
52239
52407
  for (const name of readdirSync27(rel ? join48(root, rel) : root)) {
52240
52408
  const childRel = rel ? join48(rel, name) : name;
52241
52409
  const posix5 = childRel.split(sep11).join("/");
52242
- const kind = lstatSync11(join48(root, childRel));
52410
+ const kind = lstatSync12(join48(root, childRel));
52243
52411
  if (kind.isSymbolicLink() || !kind.isDirectory() || isDeniedSourceDir(name, rel)) {
52244
52412
  empty = false;
52245
52413
  continue;
@@ -52319,7 +52487,7 @@ __export(run_watch_exports, {
52319
52487
  watchedContentDir: () => watchedContentDir,
52320
52488
  writeRunRecord: () => writeRunRecord
52321
52489
  });
52322
- import { closeSync as closeSync3, constants, existsSync as existsSync50, fstatSync, lstatSync as lstatSync12, statSync as statSync13, mkdirSync as mkdirSync25, openSync as openSync3, readFileSync as readFileSync56, realpathSync as realpathSync7, renameSync as renameSync7, rmSync as rmSync12, writeFileSync as writeFileSync20, watch as fsWatch } from "node:fs";
52490
+ import { closeSync as closeSync3, constants, existsSync as existsSync50, fstatSync, lstatSync as lstatSync13, statSync as statSync13, mkdirSync as mkdirSync25, openSync as openSync3, readFileSync as readFileSync56, realpathSync as realpathSync7, renameSync as renameSync7, rmSync as rmSync12, writeFileSync as writeFileSync20, watch as fsWatch } from "node:fs";
52323
52491
  import { randomUUID as randomUUID3 } from "node:crypto";
52324
52492
  import { extname as extname4, join as join49, resolve as resolve42, sep as sep12 } from "node:path";
52325
52493
  function diffReleases(prev, next) {
@@ -52442,11 +52610,11 @@ function serialize(task) {
52442
52610
  function himiDir(dir) {
52443
52611
  const root = join49(dir, ".himi");
52444
52612
  try {
52445
- return lstatSync12(root).isDirectory() ? root : null;
52613
+ return lstatSync13(root).isDirectory() ? root : null;
52446
52614
  } catch (e) {
52447
52615
  if (e.code !== "ENOENT") return null;
52448
52616
  mkdirSync25(root, { recursive: true });
52449
- return lstatSync12(root).isDirectory() ? root : null;
52617
+ return lstatSync13(root).isDirectory() ? root : null;
52450
52618
  }
52451
52619
  }
52452
52620
  function writeRunRecord(dir, record2) {
@@ -52466,7 +52634,7 @@ function writeRunRecord(dir, record2) {
52466
52634
  function removeRunRecord(dir, owner) {
52467
52635
  const root = join49(dir, ".himi");
52468
52636
  try {
52469
- if (!lstatSync12(root).isDirectory()) return;
52637
+ if (!lstatSync13(root).isDirectory()) return;
52470
52638
  } catch {
52471
52639
  return;
52472
52640
  }
@@ -52484,7 +52652,7 @@ function removeRunRecord(dir, owner) {
52484
52652
  function removeInitWatchRecord(dir, owner) {
52485
52653
  const root = join49(dir, ".himi");
52486
52654
  try {
52487
- if (!lstatSync12(root).isDirectory()) return;
52655
+ if (!lstatSync13(root).isDirectory()) return;
52488
52656
  } catch {
52489
52657
  return;
52490
52658
  }
@@ -52521,12 +52689,12 @@ function boardFile(dir, pathname) {
52521
52689
  }
52522
52690
  function readConfined(root, rel) {
52523
52691
  try {
52524
- if (!lstatSync12(root).isDirectory()) return null;
52692
+ if (!lstatSync13(root).isDirectory()) return null;
52525
52693
  const parts = rel.split(sep12);
52526
52694
  let at2 = root;
52527
52695
  for (let i = 0; i < parts.length; i++) {
52528
52696
  at2 = join49(at2, parts[i]);
52529
- const st = lstatSync12(at2);
52697
+ const st = lstatSync13(at2);
52530
52698
  if (st.isSymbolicLink() || !(i === parts.length - 1 ? st.isFile() : st.isDirectory())) return null;
52531
52699
  }
52532
52700
  const canonicalRoot = realpathSync7(root);
@@ -52592,7 +52760,7 @@ __export(screen_state_exports, {
52592
52760
  withHomeCaveat: () => withHomeCaveat
52593
52761
  });
52594
52762
  import { createHash as createHash25 } from "node:crypto";
52595
- import { lstatSync as lstatSync13, readFileSync as readFileSync57 } from "node:fs";
52763
+ import { lstatSync as lstatSync14, readFileSync as readFileSync57 } from "node:fs";
52596
52764
  import { join as join50 } from "node:path";
52597
52765
  function quotableLine(text8) {
52598
52766
  if (!text8) return void 0;
@@ -52651,7 +52819,7 @@ function screenStateOf(services, html, opts = {}) {
52651
52819
  function readRecord(dir) {
52652
52820
  const file = join50(dir, SHOT_STATES_FILE);
52653
52821
  try {
52654
- if (!lstatSync13(file).isFile()) return {};
52822
+ if (!lstatSync14(file).isFile()) return {};
52655
52823
  const r = JSON.parse(readFileSync57(file, "utf8"));
52656
52824
  return r.v === 1 && r.shots && typeof r.shots === "object" ? r.shots : {};
52657
52825
  } catch {
@@ -52727,7 +52895,7 @@ __export(studio_reel_exports, {
52727
52895
  utcDay: () => utcDay
52728
52896
  });
52729
52897
  import { createHash as createHash26 } from "node:crypto";
52730
- import { lstatSync as lstatSync14, readFileSync as readFileSync58 } from "node:fs";
52898
+ import { lstatSync as lstatSync15, readFileSync as readFileSync58 } from "node:fs";
52731
52899
  import { basename as basename14, join as join51 } from "node:path";
52732
52900
  function loadMemory2(dir) {
52733
52901
  const m = readJson5(join51(dir, ".himi", "studio", "memory.json"));
@@ -52812,7 +52980,7 @@ function inlineImages(dir, plan, budget = REEL_BUDGET_BYTES) {
52812
52980
  if (!HISTORY_NAME.test(f)) return null;
52813
52981
  try {
52814
52982
  const path2 = join51(dir, HISTORY_DIR, f);
52815
- const st = lstatSync14(path2);
52983
+ const st = lstatSync15(path2);
52816
52984
  if (!st.isFile() || st.nlink !== 1) return null;
52817
52985
  const bytes2 = readFileSync58(path2);
52818
52986
  return createHash26("sha256").update(bytes2).digest("hex") === f.slice(0, 64) && imageExtension(bytes2) === f.slice(65) ? bytes2 : null;
@@ -53019,7 +53187,7 @@ __export(milestones_exports, {
53019
53187
  });
53020
53188
  import { createHash as createHash27, randomUUID as randomUUID4 } from "node:crypto";
53021
53189
  import { spawn as spawn5 } from "node:child_process";
53022
- import { closeSync as closeSync4, existsSync as existsSync51, lstatSync as lstatSync15, readFileSync as readFileSync59, statSync as statSync14 } from "node:fs";
53190
+ import { closeSync as closeSync4, existsSync as existsSync51, lstatSync as lstatSync16, readFileSync as readFileSync59, statSync as statSync14 } from "node:fs";
53023
53191
  import { createServer as createServer4 } from "node:net";
53024
53192
  import { basename as basename15, dirname as dirname29, isAbsolute as isAbsolute4, join as join52, relative as relative9, resolve as resolve43, sep as sep13 } from "node:path";
53025
53193
  function homeScreenIdOf(dir) {
@@ -53040,7 +53208,7 @@ function homeShotPath(dir, home) {
53040
53208
  if (!shot.startsWith(base + sep13)) return null;
53041
53209
  for (let p = shot; p !== root && p.startsWith(root + sep13); p = dirname29(p)) {
53042
53210
  try {
53043
- if (lstatSync15(p).isSymbolicLink()) return null;
53211
+ if (lstatSync16(p).isSymbolicLink()) return null;
53044
53212
  } catch {
53045
53213
  }
53046
53214
  }
@@ -53375,7 +53543,7 @@ function rememberDraft(dir, releaseId, m) {
53375
53543
  function lastDraft(dir) {
53376
53544
  try {
53377
53545
  const file = join52(dir, LAST_DRAFT_FILE);
53378
- if (!lstatSync15(file).isFile()) return null;
53546
+ if (!lstatSync16(file).isFile()) return null;
53379
53547
  const r = JSON.parse(readFileSync59(file, "utf8"));
53380
53548
  const m = r.manifest;
53381
53549
  if (r.v !== 1 || typeof r.releaseId !== "string" || !m || typeof m.screens !== "object" || !m.screens || typeof m.bundles !== "object" || !m.bundles) return null;
@@ -53437,7 +53605,7 @@ function recordInitSource(dir, opts = {}) {
53437
53605
  function readInitSource(dir) {
53438
53606
  const file = join52(dir, INIT_SOURCE_FILE);
53439
53607
  try {
53440
- if (!lstatSync15(file).isFile()) return null;
53608
+ if (!lstatSync16(file).isFile()) return null;
53441
53609
  const r = JSON.parse(readFileSync59(file, "utf8"));
53442
53610
  if (r.v !== 1 || typeof r.fingerprint !== "string" || !/^[0-9a-f]{64}$/.test(r.fingerprint) || typeof r.newestMs !== "number") return null;
53443
53611
  return {
@@ -53512,7 +53680,7 @@ function contentHomePictures(dir, home) {
53512
53680
  if (r.state.state !== "content") continue;
53513
53681
  for (const file of [shot, hist && join52(hist, `${r.sha256}.png`)]) {
53514
53682
  try {
53515
- if (!file || !lstatSync15(file).isFile() || sha256Of2(file) !== r.sha256) continue;
53683
+ if (!file || !lstatSync16(file).isFile() || sha256Of2(file) !== r.sha256) continue;
53516
53684
  } catch {
53517
53685
  continue;
53518
53686
  }
@@ -53525,7 +53693,7 @@ function contentHomePictures(dir, home) {
53525
53693
  function readInitWatch(dir) {
53526
53694
  const file = join52(dir, INIT_WATCH_FILE);
53527
53695
  try {
53528
- if (!lstatSync15(file).isFile()) return null;
53696
+ if (!lstatSync16(file).isFile()) return null;
53529
53697
  const r = JSON.parse(readFileSync59(file, "utf8"));
53530
53698
  if (r.v !== 1 || typeof r.pid !== "number" || !Number.isInteger(r.pid) || r.pid < 1) return null;
53531
53699
  if (typeof r.url !== "string" || !/^http:\/\/127\.0\.0\.1:\d+\//.test(r.url) || typeof r.board !== "string" || typeof r.log !== "string" || typeof r.startedAt !== "string") return null;
@@ -53558,7 +53726,7 @@ function watchReminder(dir, opts = {}) {
53558
53726
  const file = join52(dir, REMINDERS_FILE);
53559
53727
  let last = Number.NaN;
53560
53728
  try {
53561
- if (lstatSync15(file).isFile()) last = Date.parse(JSON.parse(readFileSync59(file, "utf8")).watch ?? "");
53729
+ if (lstatSync16(file).isFile()) last = Date.parse(JSON.parse(readFileSync59(file, "utf8")).watch ?? "");
53562
53730
  } catch {
53563
53731
  }
53564
53732
  if (Number.isFinite(last) && now - last >= 0 && now - last < WATCH_REMINDER_EVERY_MS) return void 0;
@@ -53681,7 +53849,7 @@ function afterHandoverPostcard(dir, opts = {}) {
53681
53849
  const file = join52(dir, ".himi", "making-of.html");
53682
53850
  let fresh = false;
53683
53851
  try {
53684
- const st = lstatSync15(file);
53852
+ const st = lstatSync16(file);
53685
53853
  fresh = st.isFile() && !st.isSymbolicLink() && st.mtimeMs >= statSync14(join52(dir, ".himi", "progress.jsonl")).mtimeMs;
53686
53854
  } catch {
53687
53855
  fresh = false;
@@ -54811,6 +54979,7 @@ var test_exports = {};
54811
54979
  __export(test_exports, {
54812
54980
  TEST_CONFIG_FILE: () => TEST_CONFIG_FILE,
54813
54981
  defaultNetRules: () => defaultNetRules,
54982
+ foldCapabilityCoverage: () => foldCapabilityCoverage,
54814
54983
  formatReport: () => formatReport,
54815
54984
  makeRecordingWixCall: () => makeRecordingWixCall,
54816
54985
  readCannedResponses: () => readCannedResponses,
@@ -55055,6 +55224,31 @@ async function runShotsPass(opts) {
55055
55224
  }
55056
55225
  return { dir: opts.outDir, written, ...Object.keys(states).length ? { states } : {} };
55057
55226
  }
55227
+ function declaredKey(descriptor) {
55228
+ return [...declaredCapabilitiesOf(descriptor)].sort().join(",");
55229
+ }
55230
+ function foldCapabilityCoverage(perScreen) {
55231
+ const acc = /* @__PURE__ */ new Map();
55232
+ for (const { screen, coverage } of perScreen) {
55233
+ for (const [call, arm] of Object.entries(coverage)) {
55234
+ const e = acc.get(call) ?? { screens: [], deniedOn: [] };
55235
+ if (!e.screens.includes(screen)) e.screens.push(screen);
55236
+ if (arm === "denied") {
55237
+ if (!e.deniedOn.includes(screen)) e.deniedOn.push(screen);
55238
+ } else e.granted = arm;
55239
+ acc.set(call, e);
55240
+ }
55241
+ }
55242
+ const counts = { success: 0, store: 0, "unavailable-only": 0, denied: 0 };
55243
+ const calls = {};
55244
+ for (const call of [...acc.keys()].sort()) {
55245
+ const e = acc.get(call);
55246
+ const arm = e.granted ?? "denied";
55247
+ counts[arm] += 1;
55248
+ calls[call] = { arm, screens: e.screens.sort(), ...e.granted && e.deniedOn.length ? { deniedOn: e.deniedOn.sort() } : {} };
55249
+ }
55250
+ return { counts, calls };
55251
+ }
55058
55252
  function withCovered(rows, coveredBy) {
55059
55253
  const byScreen = new Map(rows.map((r) => [r.screen, r]));
55060
55254
  const out2 = [...rows];
@@ -55079,7 +55273,7 @@ async function runCrawl(opts) {
55079
55273
  if (!opts.allScreens) {
55080
55274
  const groups = /* @__PURE__ */ new Map();
55081
55275
  for (const d of requested) {
55082
- const key2 = `${d.bundleName ?? "-"}|${deps.crawlSignature(d.descriptor)}`;
55276
+ const key2 = `${d.bundleName ?? "-"}|${deps.crawlSignature(d.descriptor)}|${declaredKey(d.descriptor)}`;
55083
55277
  const list = groups.get(key2) ?? [];
55084
55278
  list.push(d);
55085
55279
  groups.set(key2, list);
@@ -55105,6 +55299,7 @@ async function runCrawl(opts) {
55105
55299
  const relationFindings = [];
55106
55300
  let relationsChecked = 0;
55107
55301
  let capabilityModelled = 0;
55302
+ const capabilityCoverageByScreen = [];
55108
55303
  const relationsByKind = { inverse: 0, refresh: 0, loadMore: 0, filter: 0 };
55109
55304
  const deepFindings = [];
55110
55305
  const journeyFindings = [];
@@ -55197,6 +55392,7 @@ async function runCrawl(opts) {
55197
55392
  const cr = await deps.crawlScreenCapabilitySuccess(args);
55198
55393
  writes.push(...writesFromEffects(baked.screenId, cr.effects));
55199
55394
  capabilityModelled += cr.modelled;
55395
+ if (cr.coverage) capabilityCoverageByScreen.push({ screen: baked.screenId, coverage: cr.coverage });
55200
55396
  const keyOfC = deps.findingKey ?? ((f) => `${f.invariant}|${f.at ?? ""}|${f.screen}|${f.message}`);
55201
55397
  const seen = new Set([...r.findings, ...relationFindings].map(keyOfC));
55202
55398
  for (const f of cr.findings) if (!seen.has(keyOfC(f))) relationFindings.push(f);
@@ -55355,7 +55551,7 @@ async function runCrawl(opts) {
55355
55551
  ...retired.length ? { retired } : {},
55356
55552
  ...opts.deep && opts.deep >= 2 ? { deep: { depth: opts.deep, ...deepTotals } } : {},
55357
55553
  ...deps.crawlScreenRelations ? { relations: { checked: relationsChecked, byKind: relationsByKind } } : {},
55358
- ...deps.crawlScreenCapabilitySuccess ? { capabilities: { modelled: capabilityModelled } } : {},
55554
+ ...deps.crawlScreenCapabilitySuccess ? { capabilities: { modelled: capabilityModelled }, capabilityCoverage: foldCapabilityCoverage(capabilityCoverageByScreen) } : {},
55359
55555
  ...journeyResults.length ? { journeys: journeyResults } : {},
55360
55556
  writes: inventory,
55361
55557
  ...unreachedWrites.length ? { unreachedWrites } : {},
@@ -55507,6 +55703,7 @@ var TEST_CONFIG_FILE, mapResult, triggerLabel;
55507
55703
  var init_test = __esm({
55508
55704
  "src/test.ts"() {
55509
55705
  init_app_map();
55706
+ init_screen_crawl();
55510
55707
  init_wix_gateway_rules();
55511
55708
  init_crawl_layers();
55512
55709
  init_screen_state();
@@ -57420,7 +57617,7 @@ var init_cli_bridge = __esm({
57420
57617
  });
57421
57618
 
57422
57619
  // src/mcp/file-tools.ts
57423
- import { closeSync as closeSync5, constants as constants2, lstatSync as lstatSync16, mkdirSync as mkdirSync28, openSync as openSync4, readdirSync as readdirSync28, readFileSync as readFileSync67, realpathSync as realpathSync8, renameSync as renameSync8, rmSync as rmSync13, statSync as statSync15, writeSync, existsSync as existsSync58 } from "node:fs";
57620
+ import { closeSync as closeSync5, constants as constants2, lstatSync as lstatSync17, mkdirSync as mkdirSync28, openSync as openSync4, readdirSync as readdirSync28, readFileSync as readFileSync67, realpathSync as realpathSync8, renameSync as renameSync8, rmSync as rmSync13, statSync as statSync15, writeSync, existsSync as existsSync58 } from "node:fs";
57424
57621
  import { randomBytes as randomBytes4 } from "node:crypto";
57425
57622
  import { dirname as dirname31, isAbsolute as isAbsolute5, join as join60, relative as relative11, resolve as resolve47, sep as sep15 } from "node:path";
57426
57623
  import * as z from "zod";
@@ -57443,7 +57640,7 @@ function symlinkComponent(base, abs) {
57443
57640
  for (const part of relative11(base, abs).split(sep15).filter(Boolean)) {
57444
57641
  cur = join60(cur, part);
57445
57642
  try {
57446
- if (lstatSync16(cur).isSymbolicLink()) return relative11(base, cur);
57643
+ if (lstatSync17(cur).isSymbolicLink()) return relative11(base, cur);
57447
57644
  } catch {
57448
57645
  return void 0;
57449
57646
  }
@@ -57590,7 +57787,7 @@ function fileTools() {
57590
57787
  if (typeof app !== "string") return app;
57591
57788
  const p = confinedPath(app, a.path);
57592
57789
  if (typeof p !== "string") return refuse4("bad_path", p.error);
57593
- if (!existsSync58(p) || !lstatSync16(p).isFile()) return refuse4("not_found", `no file "${String(a.path)}"`);
57790
+ if (!existsSync58(p) || !lstatSync17(p).isFile()) return refuse4("not_found", `no file "${String(a.path)}"`);
57594
57791
  const size = statSync15(p).size;
57595
57792
  if (size > MAX_FILE_BYTES) return refuse4("too_large", `"${String(a.path)}" is ${size} bytes \u2014 over the ${MAX_FILE_BYTES}-byte read limit`);
57596
57793
  return ok({ ok: true, path: a.path, bytes: size, content: readFileSync67(p, "utf8") });
@@ -57617,7 +57814,7 @@ function fileTools() {
57617
57814
  if (typeof p !== "string") refused2.push({ path: f.path, error: p.error });
57618
57815
  else if (typeof f.content !== "string") refused2.push({ path: f.path, error: "content must be text" });
57619
57816
  else if (Buffer.byteLength(f.content) > MAX_FILE_BYTES) refused2.push({ path: f.path, error: `over the ${MAX_FILE_BYTES}-byte limit` });
57620
- else if (existsSync58(p) && !lstatSync16(p).isFile()) refused2.push({ path: f.path, error: "not a regular file" });
57817
+ else if (existsSync58(p) && !lstatSync17(p).isFile()) refused2.push({ path: f.path, error: "not a regular file" });
57621
57818
  else if (planned.some((q) => q.abs === p)) refused2.push({ path: f.path, error: "the same file twice in one call" });
57622
57819
  else planned.push({ abs: p, rel: String(f.path), content: f.content });
57623
57820
  }
@@ -57651,7 +57848,7 @@ function fileTools() {
57651
57848
  const p = confinedPath(app, rel);
57652
57849
  if (typeof p !== "string") refused2.push({ path: rel, error: p.error });
57653
57850
  else if (!existsSync58(p)) refused2.push({ path: rel, error: "no such file" });
57654
- else if (!lstatSync16(p).isFile()) refused2.push({ path: rel, error: "not a regular file" });
57851
+ else if (!lstatSync17(p).isFile()) refused2.push({ path: rel, error: "not a regular file" });
57655
57852
  else if (!planned.includes(p)) {
57656
57853
  planned.push(p);
57657
57854
  deleted.push(rel);
@@ -60956,21 +61153,21 @@ var init_preview_assets_generated = __esm({
60956
61153
  "../../packages/serve/src/preview-assets.generated.ts"() {
60957
61154
  init_preview_assets_format();
60958
61155
  PREVIEW_ASSET_MANIFEST = [
60959
- { key: "assets/index-CuJ-kgj4.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1134765, sha256: "a9096277792ac97bb0ef720bee2022174e7819c53db0d22ae6be2710e9412c1e" },
60960
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1134773, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
60961
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1139383, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
60962
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1627967, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
60963
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1718891, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
60964
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1809875, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
60965
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1900855, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
60966
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1991727, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
60967
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2082707, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
60968
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2191343, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
60969
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2300427, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
60970
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2409475, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
60971
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2517175, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
60972
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2605283, length: 4947, sha256: "2bd45a6fb7d319fcfb7bb44c472decfd2fb6780a7ad852fde57e144fb4f97c86" },
60973
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2610230, length: 17344, sha256: "d057298d75e7373e5e2c4d11b7d1bcbea88e03af555ba63d97641c78509c58e4" }
61156
+ { key: "assets/index-C_AIgKp4.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1135680, sha256: "dec95bd7419620a5edfb4e9cdcf7a027a81cce9009bc214cf03578d384fc1724" },
61157
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1135688, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
61158
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1140298, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
61159
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1628882, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
61160
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1719806, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
61161
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1810790, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
61162
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1901770, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
61163
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1992642, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
61164
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2083622, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
61165
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2192258, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
61166
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2301342, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
61167
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2410390, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
61168
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2518090, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
61169
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2606198, length: 4947, sha256: "c3f476b436d5ba5d2c7e928ba8da0472201b6353f25d6f73fb6cce9f5326c965" },
61170
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2611145, length: 17344, sha256: "d057298d75e7373e5e2c4d11b7d1bcbea88e03af555ba63d97641c78509c58e4" }
60974
61171
  ];
60975
61172
  }
60976
61173
  });
@@ -74566,6 +74763,16 @@ function isSystemPage(path2, title) {
74566
74763
  const last = path2.split("/").filter(Boolean).pop() ?? "";
74567
74764
  return SYSTEM_SEGMENT.test(last) || SYSTEM_TITLE.test(title);
74568
74765
  }
74766
+ function deriveMemberPages(origin, urls) {
74767
+ const found = {};
74768
+ for (const url of urls) {
74769
+ const path2 = pathOnSite(origin, url);
74770
+ if (!path2) continue;
74771
+ const key2 = MEMBER_PAGE_ROUTES[path2.split("/").filter(Boolean).join("/").toLowerCase()];
74772
+ if (key2 && !found[key2]) found[key2] = path2;
74773
+ }
74774
+ return found.orders || found.bookings ? found : void 0;
74775
+ }
74569
74776
  function boundedTitle(s) {
74570
74777
  const t = oneLine2(s).replace(/\s*\(new\)\s*$/i, "");
74571
74778
  return t.length <= SITE_PAGE_TITLE_MAX ? t : t.slice(0, SITE_PAGE_TITLE_MAX).trimEnd();
@@ -74698,10 +74905,15 @@ function deriveSiteShell(input) {
74698
74905
  if (!published.length && !navLinks.length && !routes.length) {
74699
74906
  notes.push("site pages: the analysis read no page list, so the map has only Home \u2014 add the site's pages to `site.pages` before --apply.");
74700
74907
  }
74908
+ const memberPages = deriveMemberPages(origin, navLinks.map((l) => l.href));
74909
+ if (memberPages) {
74910
+ notes.push(`member pages: ${Object.entries(memberPages).map(([k, v]) => `${k} \u2192 ${v}`).join(", ")} \u2014 order / booking notifications open them.`);
74911
+ }
74701
74912
  const site = {
74702
74913
  origin,
74703
74914
  kind,
74704
74915
  pages,
74916
+ ...memberPages ? { memberPages } : {},
74705
74917
  // Hidden by default — the product decision for the site shell (RFC 0062 §6); the owner can
74706
74918
  // turn any of them back on here, and the flags ride the OTA descriptor. A Base44 app has no
74707
74919
  // Wix ads to hide, so it never carries the flag (RFC 0062 §5.8).
@@ -74728,7 +74940,7 @@ function routeTitle(r) {
74728
74940
  const seg = r.path.split("/").filter(Boolean).pop() ?? r.path;
74729
74941
  return seg.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
74730
74942
  }
74731
- var DERIVED_SITE_MAX_PAGES, SITE_PAGE_TITLE_MAX, ROLE_ICON, TAB_ROLES, SYSTEM_SEGMENT, SYSTEM_TITLE, HOME_WORD, oneLine2, RESERVED_PAGE_IDS;
74943
+ var DERIVED_SITE_MAX_PAGES, SITE_PAGE_TITLE_MAX, ROLE_ICON, TAB_ROLES, SYSTEM_SEGMENT, SYSTEM_TITLE, HOME_WORD, MEMBER_PAGE_ROUTES, oneLine2, RESERVED_PAGE_IDS;
74732
74944
  var init_site_shell_derive = __esm({
74733
74945
  "src/site-analyze/site-shell-derive.ts"() {
74734
74946
  init_site_shell();
@@ -74755,6 +74967,10 @@ var init_site_shell_derive = __esm({
74755
74967
  SYSTEM_SEGMENT = /^(popup-.*|lightbox.*|.*thank-?you.*|checkout|cart(-page)?|payment-request(-page)?|expired-reset-link|access-restricted|reservation|(event|experience|reservation|order|item)-details|(product|service|category|group|project|collection|reviews-collection|donation-thank-you)-page|post|project|collection|group|booking-(form|calendar)|plan-customization|notifications?|followers?|following|my-(?!account$)[a-z-]+|profile|search(-results)?|blank(-\d+)?|404)$/i;
74756
74968
  SYSTEM_TITLE = /\b(thank you|\(popup\)|leave (a )?review|notifications|followers|side cart|order error|pending approval|my (rewards|subscriptions|bookings|orders|groups|wallet))\b|\b(group|service|product|project|collection|category|cart|payment request) pages?$|\b(event|experience|reservation|item) details\b/i;
74757
74969
  HOME_WORD = /^(home|home ?page|main|start|accueil|inicio|início|startseite|home ?sweet ?home|בית|דף הבית|ראשי|الرئيسية|ホーム|首页|홈)$/i;
74970
+ MEMBER_PAGE_ROUTES = {
74971
+ "account/my-orders": "orders",
74972
+ "account/my-bookings": "bookings"
74973
+ };
74758
74974
  oneLine2 = (s) => s.replace(/[\u0000-\u001F\u007F\s]+/g, " ").trim();
74759
74975
  RESERVED_PAGE_IDS = /* @__PURE__ */ new Set(["account", "notifications"]);
74760
74976
  }
@@ -76630,7 +76846,7 @@ var init_context = __esm({
76630
76846
  });
76631
76847
 
76632
76848
  // src/cli.ts
76633
- import { mkdtempSync as mkdtempSync5, readFileSync as readFileSync91, mkdirSync as mkdirSync39, writeFileSync as writeFileSync35, existsSync as existsSync74, rmSync as rmSync20, readdirSync as readdirSync32, statSync as statSync20, lstatSync as lstatSync17, accessSync, constants as constants3, renameSync as renameSync9, cpSync as cpSync3 } from "node:fs";
76849
+ import { mkdtempSync as mkdtempSync5, readFileSync as readFileSync91, mkdirSync as mkdirSync39, writeFileSync as writeFileSync35, existsSync as existsSync74, rmSync as rmSync20, readdirSync as readdirSync32, statSync as statSync20, lstatSync as lstatSync18, accessSync, constants as constants3, renameSync as renameSync9, cpSync as cpSync3 } from "node:fs";
76634
76850
  import { execFile as execFile2, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn7 } from "node:child_process";
76635
76851
  import { AsyncLocalStorage } from "node:async_hooks";
76636
76852
  import { createHash as createHash36, randomUUID as randomUUID8 } from "node:crypto";
@@ -80744,7 +80960,7 @@ function renameRestoredSource(restored, declaredApp, renamed) {
80744
80960
  function symlinkDestinationIssue(dest) {
80745
80961
  let kind;
80746
80962
  try {
80747
- kind = lstatSync17(dest);
80963
+ kind = lstatSync18(dest);
80748
80964
  } catch {
80749
80965
  return null;
80750
80966
  }
@@ -82080,9 +82296,9 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
82080
82296
  message: `scaffold source (--source-dir ${sourceDir}): ${issue2.message}`
82081
82297
  }));
82082
82298
  const { navIssues: navIssues2 } = await Promise.resolve().then(() => (init_nav_lint(), nav_lint_exports));
82083
- const { capabilityIssues: capabilityIssues2, contentShell: contentShell2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
82299
+ const { capabilityIssues: capabilityIssues2, contentShell: contentShell2, readLastDist: readLastDist2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
82084
82300
  const { report: report3, hasErrors } = summarizeLint(app, bres, outcome, strict, [
82085
- ...capabilityIssues2(outcome.capabilityUsage, contentShell2(buildDir, str9(flags.shell)), strict),
82301
+ ...capabilityIssues2(outcome.capabilityUsage, contentShell2(buildDir, str9(flags.shell)), strict, readLastDist2(buildDir)),
82086
82302
  ...await cmsIssues(buildDir, app, strict, { local: true }),
82087
82303
  ...surfaceIssues(buildDir, outcome.surfaceNav, strict),
82088
82304
  ...tokenColorIssues(buildDir),
@@ -82367,9 +82583,9 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
82367
82583
  ...hasWorkers ? { tier5Dir: bundleOutDir2(dir) } : {},
82368
82584
  collect: { lint: true, strict }
82369
82585
  });
82370
- const { capabilityIssues: capabilityIssues2, contentShell: contentShell2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
82586
+ const { capabilityIssues: capabilityIssues2, contentShell: contentShell2, readLastDist: readLastDist2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
82371
82587
  const { report: report3, hasErrors } = summarizeLint(app, bres, outcome, strict, [
82372
- ...capabilityIssues2(outcome.capabilityUsage, contentShell2(dir, str9(flags.shell)), strict),
82588
+ ...capabilityIssues2(outcome.capabilityUsage, contentShell2(dir, str9(flags.shell)), strict, readLastDist2(dir)),
82373
82589
  ...await cmsIssues(dir, app, strict, { local: true }),
82374
82590
  ...surfaceIssues(dir, outcome.surfaceNav, strict),
82375
82591
  ...tokenColorIssues(dir),
@@ -83890,9 +84106,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
83890
84106
  collect: { lint: true, strict }
83891
84107
  });
83892
84108
  const { navIssues: navIssues2 } = await Promise.resolve().then(() => (init_nav_lint(), nav_lint_exports));
83893
- const { capabilityIssues: capabilityIssues2, contentShell: contentShell2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
84109
+ const { capabilityIssues: capabilityIssues2, contentShell: contentShell2, readLastDist: readLastDist2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
83894
84110
  const { report: report3, hasErrors } = summarizeLint(declared, bres, outcome, strict, [
83895
- ...capabilityIssues2(outcome.capabilityUsage, contentShell2(dir, str9(flags.shell)), strict),
84111
+ ...capabilityIssues2(outcome.capabilityUsage, contentShell2(dir, str9(flags.shell)), strict, readLastDist2(dir)),
83896
84112
  ...await cmsIssues(dir, declared, strict, { local: true }),
83897
84113
  ...surfaceIssues(dir, outcome.surfaceNav, strict),
83898
84114
  ...tokenColorIssues(dir),
@@ -84217,11 +84433,13 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
84217
84433
  // Its screen hashes say what changed since the last draft, on the postcard.
84218
84434
  manifest
84219
84435
  }) : null;
84436
+ const needsBinary = (await Promise.resolve().then(() => (init_capabilities(), capabilities_exports))).needsBinaryNotice(r.json);
84220
84437
  print({
84221
84438
  ok: true,
84222
84439
  app,
84223
84440
  releaseId: release,
84224
84441
  draft,
84442
+ ...needsBinary ? { needsBinary } : {},
84225
84443
  ...unchanged ? { unchanged: true, unchangedNote: "the serve plane already holds this exact release under this id, so nothing was written" } : {},
84226
84444
  ...afterDraft?.firstLook ? { firstLookPostcard: afterDraft.firstLook } : {},
84227
84445
  ...afterDraft?.postcard ? { postcard: afterDraft.postcard } : {},
@@ -84440,9 +84658,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
84440
84658
  collect: { lint: true, strict }
84441
84659
  });
84442
84660
  const { navIssues: navIssues2 } = await Promise.resolve().then(() => (init_nav_lint(), nav_lint_exports));
84443
- const { capabilityIssues: capabilityIssues2, contentShell: contentShell2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
84661
+ const { capabilityIssues: capabilityIssues2, contentShell: contentShell2, readLastDist: readLastDist2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
84444
84662
  const { report: report3, hasErrors } = summarizeLint(app, bres, outcome, strict, [
84445
- ...capabilityIssues2(outcome.capabilityUsage, contentShell2(dir, str9(flags.shell)), strict),
84663
+ ...capabilityIssues2(outcome.capabilityUsage, contentShell2(dir, str9(flags.shell)), strict, readLastDist2(dir)),
84446
84664
  ...await cmsIssues(dir, app, strict, { local: true }),
84447
84665
  ...surfaceIssues(dir, outcome.surfaceNav, strict),
84448
84666
  ...tokenColorIssues(dir),
@@ -86043,6 +86261,20 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
86043
86261
  const sub = pos[1];
86044
86262
  const c = client2();
86045
86263
  const showDistQr = io.isTTY === true && flags.json !== true;
86264
+ const rememberLastDist = async (jobs2) => {
86265
+ try {
86266
+ const dir = contentDir(flags);
86267
+ if (!existsSync74(join75(dir, "himalaya.content.json"))) return;
86268
+ const { lastDistFromJob: lastDistFromJob2, writeLastDist: writeLastDist2 } = await Promise.resolve().then(() => (init_capabilities(), capabilities_exports));
86269
+ const own3 = await appId(dir);
86270
+ for (const job of jobs2) {
86271
+ const rec = lastDistFromJob2(job);
86272
+ if (!rec || rec.app !== own3) continue;
86273
+ await writeLastDist2(dir, rec);
86274
+ }
86275
+ } catch {
86276
+ }
86277
+ };
86046
86278
  const humanDistJob = (job) => {
86047
86279
  if (job.platform !== "ios" || !job.artifact || typeof job.artifact !== "object") return job;
86048
86280
  const { installLink: _hidden, ...safeArtifact } = job.artifact;
@@ -86138,6 +86370,7 @@ ${label2}`);
86138
86370
  return distUnreachable(e);
86139
86371
  }
86140
86372
  const body = { status: r2.status, ...r2.json ?? {} };
86373
+ if (r2.ok) await rememberLastDist([body]);
86141
86374
  print(flags.json === true ? body : humanDistJob(body));
86142
86375
  if (showDistQr && r2.ok) await printDistQrForJob(body, String(body.app ?? ""));
86143
86376
  return r2.ok ? 0 : 1;
@@ -86178,6 +86411,7 @@ ${label2}`);
86178
86411
  }
86179
86412
  if (TERMINAL_DIST_STATUSES.has(status)) {
86180
86413
  const body = { status: s.status, ...rec };
86414
+ await rememberLastDist([body]);
86181
86415
  print(flags.json === true ? body : humanDistJob(body));
86182
86416
  if (showDistQr && status === "SUCCESS") await printDistQrForJob(body, String(rec.app ?? ""));
86183
86417
  return status === "SUCCESS" ? 0 : 1;
@@ -86392,6 +86626,7 @@ ${label2}`);
86392
86626
  }
86393
86627
  }
86394
86628
  }
86629
+ await rememberLastDist(done);
86395
86630
  const allOk = failures.length === 0 && done.every((d) => d.status === "SUCCESS");
86396
86631
  print({ ok: allOk, jobs: flags.json === true ? done : done.map(humanDistJob), ...failures.length ? { failures } : {} });
86397
86632
  await printDistQrsForJobs(done);