@wix/himalaya-cli 0.1153.0 → 0.1155.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
@@ -850,7 +850,7 @@ var init_file_store = __esm({
850
850
  }
851
851
  return removed;
852
852
  };
853
- return sweep("bundles", new Set([...refs.bundles].map((sha) => `${sha}.bundle.js`))) + sweep("docs", new Set([...refs.docs].map((hash) => `${hash}.json`))) + sweep("assets", refs.assets);
853
+ return sweep("bundles", new Set([...refs.bundles].map((sha2) => `${sha2}.bundle.js`))) + sweep("docs", new Set([...refs.docs].map((hash) => `${hash}.json`))) + sweep("assets", refs.assets);
854
854
  }
855
855
  };
856
856
  }
@@ -6758,15 +6758,58 @@ function walk(node, registry, path2, out2) {
6758
6758
  });
6759
6759
  }
6760
6760
  }
6761
+ const type = typeof obj3.type === "string" ? obj3.type : "";
6762
+ const colorProps = Object.prototype.hasOwnProperty.call(COLOR_TOKEN_PROPS, type) ? COLOR_TOKEN_PROPS[type] : void 0;
6763
+ const typoProps = Object.prototype.hasOwnProperty.call(TYPOGRAPHY_TOKEN_PROPS, type) ? TYPOGRAPHY_TOKEN_PROPS[type] : void 0;
6764
+ if ((colorProps || typoProps) && obj3.props && typeof obj3.props === "object") {
6765
+ const props = obj3.props;
6766
+ const check = (spec, kind) => {
6767
+ const [arrayKey, leaf] = spec.includes("[].") ? spec.split("[].") : [void 0, spec];
6768
+ const sites = arrayKey ? (Array.isArray(props[arrayKey]) ? props[arrayKey] : []).map((el, i) => [`${path2}.props.${arrayKey}[${i}].${leaf}`, el && typeof el === "object" ? el[leaf] : void 0]) : [[`${path2}.props.${leaf}`, props[leaf]]];
6769
+ for (const [at2, v] of sites) {
6770
+ if (typeof v !== "string" || v === "") continue;
6771
+ if (kind === "color" ? !v.startsWith("#") && !registry.colors.has(v) : !registry.typography.has(v)) {
6772
+ out2.push({ path: at2, kind, name: v });
6773
+ }
6774
+ }
6775
+ };
6776
+ for (const spec of colorProps ?? []) check(spec, "color");
6777
+ for (const spec of typoProps ?? []) check(spec, "typography");
6778
+ }
6761
6779
  for (const [key2, value] of Object.entries(obj3)) {
6762
6780
  walk(value, registry, `${path2}.${key2}`, out2);
6763
6781
  }
6764
6782
  }
6765
- var SYSTEM_FONT_FAMILY, TONAL_ELEVATION_ALPHA;
6783
+ var SYSTEM_FONT_FAMILY, TONAL_ELEVATION_ALPHA, COLOR_TOKEN_PROPS, TYPOGRAPHY_TOKEN_PROPS;
6766
6784
  var init_tokens = __esm({
6767
6785
  "../../core/ts/src/tokens.ts"() {
6786
+ "use strict";
6768
6787
  SYSTEM_FONT_FAMILY = "$system";
6769
6788
  TONAL_ELEVATION_ALPHA = [0, 0.05, 0.08, 0.11, 0.12, 0.14];
6789
+ COLOR_TOKEN_PROPS = {
6790
+ Badge: ["token"],
6791
+ Card: ["background"],
6792
+ Image: ["tint", "placeholder"],
6793
+ Tag: ["token"],
6794
+ Icon: ["tint"],
6795
+ Toggle: ["trackOn", "trackOff", "thumb"],
6796
+ ProgressBar: ["tint", "trackColor"],
6797
+ ListItem: ["titleColor", "subtitleColor", "overlineColor"],
6798
+ Slider: ["tint"],
6799
+ ActivityIndicator: ["tint"],
6800
+ Label: ["color"],
6801
+ Gauge: ["tint", "rings[].tint"],
6802
+ Rating: ["color"],
6803
+ VoiceNote: ["tint"],
6804
+ Marquee: ["color"],
6805
+ Fab: ["tint"]
6806
+ };
6807
+ TYPOGRAPHY_TOKEN_PROPS = {
6808
+ ListItem: ["titleStyle", "subtitleStyle"],
6809
+ RichText: ["runs[].style"],
6810
+ Label: ["style"],
6811
+ Marquee: ["style"]
6812
+ };
6770
6813
  }
6771
6814
  });
6772
6815
 
@@ -6791,6 +6834,7 @@ function resolveTranslation(value, catalog, sourceCatalog) {
6791
6834
  var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
6792
6835
  var init_bindings = __esm({
6793
6836
  "../../core/ts/src/bindings.ts"() {
6837
+ "use strict";
6794
6838
  BINDING_SIGILS = ["service", "flag", "item", "t"];
6795
6839
  BINDING_SIGIL_RE = new RegExp(
6796
6840
  `^\\$(?:${BINDING_SIGILS.filter((s) => s !== "t").join("|")})\\b|^\\$t:`
@@ -7859,6 +7903,47 @@ var init_validator = __esm({
7859
7903
  }
7860
7904
  });
7861
7905
 
7906
+ // ../../core/dev-server/src/token-suggest.ts
7907
+ function suggestTokenNames(name, known, max = 3) {
7908
+ if (name.length > MAX_SUGGEST_LEN) return [];
7909
+ const n = name.toLowerCase();
7910
+ const scored = [];
7911
+ for (const k of known) {
7912
+ if (k.length > MAX_SUGGEST_LEN) continue;
7913
+ const l = k.toLowerCase();
7914
+ const d = editDistance(n, l);
7915
+ let score;
7916
+ if (d <= 2) score = d;
7917
+ else if (l.startsWith(n) || n.startsWith(l)) score = 3;
7918
+ else if (l.includes(n) || n.includes(l)) score = 4;
7919
+ else if (d <= Math.floor(Math.max(n.length, l.length) / 3)) score = 5 + d;
7920
+ else continue;
7921
+ scored.push([k, score]);
7922
+ }
7923
+ scored.sort((a, b) => a[1] - b[1] || a[0].localeCompare(b[0]));
7924
+ const best = scored.length && scored[0][1] <= 2 ? scored.filter(([, sc]) => sc <= 2) : scored;
7925
+ return best.slice(0, max).map(([k]) => k);
7926
+ }
7927
+ function editDistance(a, b) {
7928
+ const prev = Array.from({ length: b.length + 1 }, (_, i) => i);
7929
+ for (let i = 1; i <= a.length; i++) {
7930
+ let diag = prev[0];
7931
+ prev[0] = i;
7932
+ for (let j = 1; j <= b.length; j++) {
7933
+ const up = prev[j];
7934
+ prev[j] = Math.min(prev[j] + 1, prev[j - 1] + 1, diag + (a[i - 1] === b[j - 1] ? 0 : 1));
7935
+ diag = up;
7936
+ }
7937
+ }
7938
+ return prev[b.length];
7939
+ }
7940
+ var MAX_SUGGEST_LEN;
7941
+ var init_token_suggest = __esm({
7942
+ "../../core/dev-server/src/token-suggest.ts"() {
7943
+ MAX_SUGGEST_LEN = 64;
7944
+ }
7945
+ });
7946
+
7862
7947
  // ../../core/dev-server/src/l10n-strings.ts
7863
7948
  function isUserVisibleCopy(s) {
7864
7949
  if (typeof s !== "string" || s.length === 0 || s.length > 200) return false;
@@ -10243,6 +10328,15 @@ function renderedWixMediaReferences(descriptor) {
10243
10328
  regions.forEach((region) => collectWixMediaReferences(region.value, region.path, found, false, false, "", region.atRoot));
10244
10329
  return found;
10245
10330
  }
10331
+ function unknownTokenMessage(kind, name, registry) {
10332
+ const base = `unknown ${kind} token "${name}" (not in tokens.json)`;
10333
+ const group = registry ? { color: registry.colors, typography: registry.typography, spacing: registry.spacing, radius: registry.radius }[kind] : void 0;
10334
+ if (!group || group.size === 0) return base;
10335
+ const near = suggestTokenNames(name, group);
10336
+ const all = [...group].sort();
10337
+ const list = all.length > 14 ? `${all.slice(0, 14).join(", ")}, \u2026 (${all.length})` : all.join(", ");
10338
+ return `${base}${near.length ? ` \u2014 did you mean ${near.map((n) => `"${n}"`).join(" or ")}?` : ""} Defined ${kind} tokens: ${list}`;
10339
+ }
10246
10340
  function validateScreenDescriptor(screenId, descriptor, opts = {}) {
10247
10341
  const issues = [];
10248
10342
  const strict = opts.strict ?? false;
@@ -10286,10 +10380,11 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
10286
10380
  indexIssue(at2, issue2);
10287
10381
  }
10288
10382
  }
10383
+ const tokenRegistry = opts.tokens ? buildTokenRegistry(opts.tokens) : void 0;
10289
10384
  for (const t of v.tokenViolations) {
10290
10385
  if (t.name.startsWith("$")) continue;
10291
10386
  if (pathCrossesOpaqueAuthorJson(t.path)) continue;
10292
- issues.push({ severity: hygiene, pass: "tokens", path: t.path, message: `unknown ${t.kind} token "${t.name}" (not in tokens.json)` });
10387
+ issues.push({ severity: hygiene, pass: "tokens", path: t.path, message: unknownTokenMessage(t.kind, t.name, tokenRegistry) });
10293
10388
  }
10294
10389
  for (const u of v.unknownComponents) {
10295
10390
  if (pathCrossesOpaqueAuthorJson(u.path)) continue;
@@ -10833,6 +10928,8 @@ var init_validate_descriptor_core = __esm({
10833
10928
  "../../core/dev-server/src/validate-descriptor.core.ts"() {
10834
10929
  init_validator();
10835
10930
  init_validator();
10931
+ init_tokens();
10932
+ init_token_suggest();
10836
10933
  init_l10n_strings();
10837
10934
  init_a11y_contract();
10838
10935
  init_bindings();
@@ -14385,8 +14482,8 @@ function rgbToHex([r, g, b]) {
14385
14482
  async function normalizeSourceIcon(input, opts) {
14386
14483
  const sharp = await sharpOrThrow();
14387
14484
  const size = opts.size ?? 1024;
14388
- const fit = opts.fit ?? NORMALIZE_FIT_FRACTION;
14389
- const inner = Math.max(1, Math.round(size * fit));
14485
+ const fit2 = opts.fit ?? NORMALIZE_FIT_FRACTION;
14486
+ const inner = Math.max(1, Math.round(size * fit2));
14390
14487
  const pad3 = Math.round((size - inner) / 2);
14391
14488
  let trimmed;
14392
14489
  try {
@@ -18280,7 +18377,7 @@ function agentCovers(agent, cli) {
18280
18377
  const gap = Math.abs(Date.parse(agent.ts) - Date.parse(cli.ts));
18281
18378
  if (!(gap <= AGENT_WINS_WITHIN_MS)) return false;
18282
18379
  if (cli.step && agent.step === cli.step) return true;
18283
- return [...shasOf(cli.images)].some((sha) => agentShas.has(sha));
18380
+ return [...shasOf(cli.images)].some((sha2) => agentShas.has(sha2));
18284
18381
  }
18285
18382
  function visibleEntries(entries) {
18286
18383
  return entries.filter((e) => !isCliEntry(e) || !entries.some((a) => agentCovers(a, e)));
@@ -34587,7 +34684,8 @@ var init_renderNode = __esm({
34587
34684
  if (v.startsWith("$item")) {
34588
34685
  const key2 = v.replace(/^\$item\.?/, "");
34589
34686
  const resolved2 = ctx.itemScope ? key2 ? ctx.itemScope[key2] : ctx.itemScope : void 0;
34590
- return resolved2 != null ? asTextL10n(resolved2, ctx) : bindingPlaceholder(v, ctx);
34687
+ if (resolved2 != null) return asTextL10n(resolved2, ctx);
34688
+ return ctx.itemScope ? null : bindingPlaceholder(v, ctx);
34591
34689
  }
34592
34690
  if (ctx.provider) return /* @__PURE__ */ jsx8(Bound, { ctx, binding: v, render: (val) => asTextL10n(val, ctx) });
34593
34691
  return bindingPlaceholder(v, ctx);
@@ -40512,9 +40610,9 @@ async function runScreenDiagnostics(state, screenId, descriptor) {
40512
40610
  evidence: { runId: `dev:${state.startedAt}:${screenId}`, ts: (/* @__PURE__ */ new Date()).toISOString() }
40513
40611
  });
40514
40612
  }
40515
- async function fontFaceBytesBySha(_appDir, sha) {
40613
+ async function fontFaceBytesBySha(_appDir, sha2) {
40516
40614
  try {
40517
- return await catalogFaceBytes(sha);
40615
+ return await catalogFaceBytes(sha2);
40518
40616
  } catch {
40519
40617
  }
40520
40618
  return void 0;
@@ -42875,13 +42973,13 @@ function createDevServer(opts = {}) {
42875
42973
  return handleAppIcon(req, res, state);
42876
42974
  }
42877
42975
  if (url.split("?")[0].startsWith("/v1/asset/") && req.method === "GET") {
42878
- const sha = url.split("?")[0].slice("/v1/asset/".length);
42976
+ const sha2 = url.split("?")[0].slice("/v1/asset/".length);
42879
42977
  const q = new URLSearchParams(url.split("?")[1] ?? "");
42880
42978
  const assetApp = q.get("app") ?? state.appName;
42881
42979
  const assetDir = state.app && assetApp === state.app.name ? state.app.dir : resolveAppDir(assetApp);
42882
- const hit = assetDir ? collectAssets(assetDir).find((a) => assetSha256(readFileSync44(a.path)) === sha) : void 0;
42980
+ const hit = assetDir ? collectAssets(assetDir).find((a) => assetSha256(readFileSync44(a.path)) === sha2) : void 0;
42883
42981
  if (!hit && assetDir) {
42884
- const fontBytes = await fontFaceBytesBySha(assetDir, sha);
42982
+ const fontBytes = await fontFaceBytesBySha(assetDir, sha2);
42885
42983
  if (fontBytes) {
42886
42984
  res.writeHead(200, {
42887
42985
  "content-type": "font/ttf",
@@ -42899,7 +42997,7 @@ function createDevServer(opts = {}) {
42899
42997
  if (serve) return await proxyToServe(serve, req, res);
42900
42998
  }
42901
42999
  res.writeHead(404, { "content-type": "application/json" });
42902
- res.end(JSON.stringify({ error: "asset_not_found", app: assetApp, sha }));
43000
+ res.end(JSON.stringify({ error: "asset_not_found", app: assetApp, sha: sha2 }));
42903
43001
  return;
42904
43002
  }
42905
43003
  const bytes2 = readFileSync44(hit.path);
@@ -43996,6 +44094,7 @@ __export(screen_crawl_exports, {
43996
44094
  isUnservedRequest: () => isUnservedRequest,
43997
44095
  layeredTransport: () => layeredTransport,
43998
44096
  mapBundleFrame: () => mapBundleFrame,
44097
+ missingItemFields: () => missingItemFields,
43999
44098
  netTransport: () => netTransport,
44000
44099
  parseWorkerAction: () => parseWorkerAction,
44001
44100
  recordScreenTraffic: () => recordScreenTraffic,
@@ -44612,6 +44711,96 @@ function renderedNameGaps(descriptor, state) {
44612
44711
  }
44613
44712
  return [...found.values()];
44614
44713
  }
44714
+ function gateTrue(v) {
44715
+ if (typeof v === "boolean") return v;
44716
+ if (typeof v === "number") return v !== 0;
44717
+ if (typeof v === "string") {
44718
+ const l = v.trim().toLowerCase();
44719
+ return l === "true" || l === "1";
44720
+ }
44721
+ return false;
44722
+ }
44723
+ function rowScopeOf(el, i) {
44724
+ return el && typeof el === "object" && !Array.isArray(el) ? { ...el, __index: i } : { value: el, __index: i };
44725
+ }
44726
+ function missingItemFields(descriptor, state) {
44727
+ const provider = new FakeServiceProvider(state);
44728
+ const out2 = /* @__PURE__ */ new Map();
44729
+ const report3 = (m) => {
44730
+ if (!out2.has(`${m.at}|${m.binding}`)) out2.set(`${m.at}|${m.binding}`, m);
44731
+ };
44732
+ const judgeList = (template, templateAt, rawRows, depth) => {
44733
+ if (!rawRows.length) return;
44734
+ const rows = rawRows.map(rowScopeOf);
44735
+ const seen = new Set(rows.flatMap((r) => Object.keys(r)).filter((k) => k !== "__index"));
44736
+ const walk2 = (v, rel, collapse) => {
44737
+ if (typeof v === "string") {
44738
+ for (const m of v.matchAll(ITEM_PATH_RE)) {
44739
+ const path2 = m[1];
44740
+ const at2 = joinPath(templateAt, rel);
44741
+ if (path2.includes(".")) {
44742
+ report3({ at: at2, binding: m[0], field: path2, rowFields: [...seen].sort(), rows: rows.length, collapseWhenEmpty: collapse, dotted: true });
44743
+ continue;
44744
+ }
44745
+ if (path2 === "__index" || seen.has(path2)) continue;
44746
+ report3({ at: at2, binding: m[0], field: path2, rowFields: [...seen].sort(), rows: rows.length, collapseWhenEmpty: collapse });
44747
+ }
44748
+ return;
44749
+ }
44750
+ if (Array.isArray(v)) return v.forEach((x, i) => walk2(x, `${rel}[${i}]`, collapse));
44751
+ if (!v || typeof v !== "object") return;
44752
+ const o = v;
44753
+ const hp = o.modifiers?.hiddenPath;
44754
+ if (typeof hp === "string" && hp.startsWith("$item.")) {
44755
+ const gate = hp.slice("$item.".length);
44756
+ if (rows.every((r) => gateTrue(pathValue(r, gate)))) return;
44757
+ }
44758
+ const c = collapse || o.collapseWhenEmpty === true;
44759
+ const nested = repeaterOf(o);
44760
+ for (const [k, x] of Object.entries(o)) {
44761
+ const at2 = rel ? `${rel}.${k}` : k;
44762
+ if (k === "id" && typeof x === "string") continue;
44763
+ if (nested && k === "props") {
44764
+ for (const [pk, px] of Object.entries(x)) if (pk !== "items") walk2(px, `${at2}.${pk}`, c);
44765
+ continue;
44766
+ }
44767
+ walk2(x, at2, c);
44768
+ }
44769
+ if (nested && depth < A11Y_LIST_DEPTH) {
44770
+ const innerAt = joinPath(templateAt, rel ? `${rel}.props.items[0]` : "props.items[0]");
44771
+ rows.forEach((r) => {
44772
+ const innerRows = nested.itemsPath.startsWith("$item.") ? pathValue(r, nested.itemsPath.slice("$item.".length)) : void 0;
44773
+ if (Array.isArray(innerRows)) judgeList(nested.template, innerAt, innerRows, depth + 1);
44774
+ });
44775
+ }
44776
+ };
44777
+ walk2(template, "", false);
44778
+ };
44779
+ const scan = (v, at2) => {
44780
+ if (Array.isArray(v)) return v.forEach((x, i) => scan(x, `${at2}[${i}]`));
44781
+ if (!v || typeof v !== "object" || isActionEnvelope(v)) return;
44782
+ const o = v;
44783
+ const list = repeaterOf(o);
44784
+ if (list) {
44785
+ const rows = list.itemsPath.startsWith("$service:") ? provider.value(list.itemsPath) : void 0;
44786
+ if (Array.isArray(rows)) judgeList(list.template, joinPath(at2, "props.items[0]"), rows, 1);
44787
+ return;
44788
+ }
44789
+ for (const [k, x] of Object.entries(o)) {
44790
+ if (!at2 && k === "input") continue;
44791
+ scan(x, at2 ? `${at2}.${k}` : k);
44792
+ }
44793
+ };
44794
+ scan(descriptor, "");
44795
+ return [...out2.values()];
44796
+ }
44797
+ function repeaterOf(o) {
44798
+ if (typeof o.type !== "string" || !o.props || typeof o.props !== "object") return null;
44799
+ const p = o.props;
44800
+ const itemsPath = typeof p.itemsPath === "string" ? p.itemsPath : typeof p.items_path === "string" ? p.items_path : null;
44801
+ const template = Array.isArray(p.items) ? p.items[0] : void 0;
44802
+ return itemsPath && template !== void 0 ? { itemsPath, template } : null;
44803
+ }
44615
44804
  function collectObservations(descriptorOrPayload, state) {
44616
44805
  const out2 = [];
44617
44806
  const walk2 = (node) => {
@@ -45110,6 +45299,17 @@ async function crawlScreenInner(opts) {
45110
45299
  });
45111
45300
  }
45112
45301
  }
45302
+ for (const m of missingItemFields(descriptor, state)) {
45303
+ findings.push({
45304
+ screen: screenId,
45305
+ trigger,
45306
+ invariant: "item-field-missing",
45307
+ severity: m.collapseWhenEmpty ? "warning" : "error",
45308
+ message: m.dotted ? `row binding "${m.binding}" is a dotted path \u2014 row templates bind FLAT row fields only, so it does not resolve on any renderer` : `row binding "${m.binding}" reads "${m.field}", which none of the list's ${m.rows} row(s) carries \u2014 it renders blank on every renderer`,
45309
+ at: m.at,
45310
+ hint: m.dotted ? `have the worker flatten it onto the row (e.g. emit "${m.field.replace(/\./g, "_")}") and bind that. Row fields: ${m.rowFields.join(", ") || "none"}` : `row fields: ${m.rowFields.join(", ") || "none"} \u2014 rename the binding or have the worker emit "${m.field}"`
45311
+ });
45312
+ }
45113
45313
  for (const site of sites) {
45114
45314
  if (parseWorkerAction(site.actionId)) continue;
45115
45315
  const before = { ...site.params ?? {}, ...site.args ?? {} };
@@ -45733,7 +45933,7 @@ async function crawlScreenRelations(opts) {
45733
45933
  const sites = actionSitesForDescriptor(descriptor).filter(
45734
45934
  (s) => parseWorkerAction(s.actionId) && !opts.skipAction?.(s.actionId, s.componentId)
45735
45935
  );
45736
- const nameOf = (s) => parseWorkerAction(s.actionId).action;
45936
+ const nameOf2 = (s) => parseWorkerAction(s.actionId).action;
45737
45937
  const serviceOf = (s) => parseWorkerAction(s.actionId).service;
45738
45938
  const boot = () => bootScreenWorker({
45739
45939
  bundleJs,
@@ -45791,7 +45991,7 @@ async function crawlScreenRelations(opts) {
45791
45991
  )
45792
45992
  ];
45793
45993
  const trig = (s) => ({ tap: s.componentId, key: s.key, action: s.actionId });
45794
- for (const s of sites.filter((s2) => REFRESH_ACTION.test(nameOf(s2)) || s2.key === "onPullToRefresh")) {
45994
+ for (const s of sites.filter((s2) => REFRESH_ACTION.test(nameOf2(s2)) || s2.key === "onPullToRefresh")) {
45795
45995
  const after = await replay([s]);
45796
45996
  if (!after) continue;
45797
45997
  checked++;
@@ -45804,12 +46004,12 @@ async function crawlScreenRelations(opts) {
45804
46004
  invariant: "refresh-not-idempotent",
45805
46005
  severity: "warning",
45806
46006
  axis: "relation",
45807
- message: `${nameOf(s)} from a fresh boot settles on a different state than the boot itself, under identical data: ${summarizeDiff(baseSig, sig)}`,
46007
+ message: `${nameOf2(s)} from a fresh boot settles on a different state than the boot itself, under identical data: ${summarizeDiff(baseSig, sig)}`,
45808
46008
  hint: "a refresh that appends instead of replacing, or clears a flag the loader set \u2014 REPLACE collections on refresh; ids, *At and _counters are already ignored by this comparison"
45809
46009
  });
45810
46010
  }
45811
46011
  }
45812
- for (const s of sites.filter((s2) => LOAD_MORE_ACTION.test(nameOf(s2)) || s2.key === "onLoadMore")) {
46012
+ for (const s of sites.filter((s2) => LOAD_MORE_ACTION.test(nameOf2(s2)) || s2.key === "onLoadMore")) {
45813
46013
  const target = listPathOf(s.itemsPath);
45814
46014
  const after = target ? await replay([s]) : null;
45815
46015
  if (!after) continue;
@@ -45825,13 +46025,13 @@ async function crawlScreenRelations(opts) {
45825
46025
  axis: "relation",
45826
46026
  at: p,
45827
46027
  actual: `${baseLens[p]} \u2192 ${n}`,
45828
- message: `${nameOf(s)} shrank ${p} from ${baseLens[p]} to ${n} rows \u2014 a "load more" that replaces the page instead of appending it`,
46028
+ message: `${nameOf2(s)} shrank ${p} from ${baseLens[p]} to ${n} rows \u2014 a "load more" that replaces the page instead of appending it`,
45829
46029
  hint: "append the new page to the existing rows (and de-duplicate by id); the user scrolled to see MORE"
45830
46030
  });
45831
46031
  }
45832
46032
  }
45833
46033
  }
45834
- for (const s of sites.filter((s2) => FILTER_ACTION.test(nameOf(s2)) && !isOptionSelection({ ...s2.params, ...s2.args }))) {
46034
+ for (const s of sites.filter((s2) => FILTER_ACTION.test(nameOf2(s2)) && !isOptionSelection({ ...s2.params, ...s2.args }))) {
45835
46035
  if (screenListPaths.length !== 1) continue;
45836
46036
  const after = await replay([s]);
45837
46037
  if (!after) continue;
@@ -45847,18 +46047,18 @@ async function crawlScreenRelations(opts) {
45847
46047
  axis: "relation",
45848
46048
  at: p,
45849
46049
  actual: `${baseLens[p]} \u2192 ${n}`,
45850
- message: `${nameOf(s)} grew ${p} from ${baseLens[p]} to ${n} rows \u2014 a filter/search/sort can narrow or reorder a list, never enlarge it`,
46050
+ message: `${nameOf2(s)} grew ${p} from ${baseLens[p]} to ${n} rows \u2014 a filter/search/sort can narrow or reorder a list, never enlarge it`,
45851
46051
  hint: "filter from the FULL set you keep aside, and assign the narrowed result \u2014 not a concatenation"
45852
46052
  });
45853
46053
  }
45854
46054
  }
45855
46055
  }
45856
- const declaredNames = sites.map(nameOf);
46056
+ const declaredNames = sites.map(nameOf2);
45857
46057
  for (const a of sites) {
45858
- const partner = inversePartner(nameOf(a), declaredNames);
46058
+ const partner = inversePartner(nameOf2(a), declaredNames);
45859
46059
  if (!partner) continue;
45860
- if (isUndoAction(nameOf(a))) continue;
45861
- const b = sites.find((s) => nameOf(s) === partner && serviceOf(s) === serviceOf(a) && s !== a);
46060
+ if (isUndoAction(nameOf2(a))) continue;
46061
+ const b = sites.find((s) => nameOf2(s) === partner && serviceOf(s) === serviceOf(a) && s !== a);
45862
46062
  if (!b) continue;
45863
46063
  const undoIsRowBound = isRowBound(b);
45864
46064
  const after = await replay([a, b], (i, bootState, prev) => {
@@ -45878,15 +46078,15 @@ async function crawlScreenRelations(opts) {
45878
46078
  invariant: "inverse-pair-diverges",
45879
46079
  severity: "warning",
45880
46080
  axis: "relation",
45881
- message: `${nameOf(a)} \u2192 ${nameOf(b)} does not return to the boot state: ${summarizeDiff(baseSig, sig)}`,
45882
- hint: `${nameOf(b)} should undo exactly what ${nameOf(a)} did \u2014 check which row it removes (by the id it was given) and every field ${nameOf(a)} touched; ids, *At and _counters are already ignored`
46081
+ message: `${nameOf2(a)} \u2192 ${nameOf2(b)} does not return to the boot state: ${summarizeDiff(baseSig, sig)}`,
46082
+ hint: `${nameOf2(b)} should undo exactly what ${nameOf2(a)} did \u2014 check which row it removes (by the id it was given) and every field ${nameOf2(a)} touched; ids, *At and _counters are already ignored`
45883
46083
  });
45884
46084
  }
45885
46085
  }
45886
46086
  return { findings: dedupe(findings), checked, byKind };
45887
46087
  });
45888
46088
  }
45889
- var ACTION_KEYS, UNTRACKED_ITEMS, NESTED_ITEMS_SEP, REJECT_ALL_MESSAGE, VLQ, WIX_DATA_QUERY, WIX_DATA_ANY, CAPABILITY_ACTION, detached, CRAWL_CLOCK, MEMBER_IDENTITY_ACTION, BLANK_WHEN_BOUND, CONTROL_ROLES, NAME_KEYS, A11Y_ROWS_PER_LIST, A11Y_LIST_DEPTH, STALE_SNAPSHOT_HINT, UNBINDABLE_ROW;
46089
+ var ACTION_KEYS, UNTRACKED_ITEMS, NESTED_ITEMS_SEP, REJECT_ALL_MESSAGE, VLQ, WIX_DATA_QUERY, WIX_DATA_ANY, CAPABILITY_ACTION, detached, CRAWL_CLOCK, MEMBER_IDENTITY_ACTION, BLANK_WHEN_BOUND, CONTROL_ROLES, NAME_KEYS, A11Y_ROWS_PER_LIST, A11Y_LIST_DEPTH, ITEM_PATH_RE, STALE_SNAPSHOT_HINT, UNBINDABLE_ROW;
45890
46090
  var init_screen_crawl = __esm({
45891
46091
  "../../core/dev-server/src/screen-crawl.ts"() {
45892
46092
  init_validate_descriptor_core();
@@ -45945,6 +46145,7 @@ var init_screen_crawl = __esm({
45945
46145
  NAME_KEYS = new Set(ACCESSIBLE_NAME_KEYS);
45946
46146
  A11Y_ROWS_PER_LIST = 20;
45947
46147
  A11Y_LIST_DEPTH = 3;
46148
+ ITEM_PATH_RE = /\$item\.([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)/g;
45948
46149
  STALE_SNAPSHOT_HINT = 'if every path already does, look for a stale-snapshot write-back: `return s` (or `{ ...s }`) after an await or from an early-return guard writes back the state as it was BEFORE the await, loading flag included \u2014 to change nothing, return `undefined` instead (not `null` or `{}`); see `himi docs tier-5-add-a-screen`, "Should this result be committed at all?"';
45949
46150
  UNBINDABLE_ROW = Symbol("unbindable-row");
45950
46151
  }
@@ -46495,10 +46696,10 @@ function hydrationServices(tier5Dir, bundleName, input = {}) {
46495
46696
  const js = readBuiltBundle(tier5Dir, bundleName);
46496
46697
  return js ? servicesFromBundleJs(js, input) : null;
46497
46698
  }
46498
- function releaseIdFor(sha, content) {
46499
- if (sha === "dev") return contentReleaseId(content);
46699
+ function releaseIdFor(sha2, content) {
46700
+ if (sha2 === "dev") return contentReleaseId(content);
46500
46701
  const stored = JSON.parse(JSON.stringify(content));
46501
- return `${sha}-${releaseContentId(stored).slice(0, 12)}`;
46702
+ return `${sha2}-${releaseContentId(stored).slice(0, 12)}`;
46502
46703
  }
46503
46704
  function rebindReleaseId(manifest) {
46504
46705
  const { releaseId: _releaseId, gitSha: gitSha2, shellSha: _shellSha, ...content } = manifest;
@@ -47400,12 +47601,12 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
47400
47601
  // never re-fetch, and the "replace an asset without a store release" claim would be false.
47401
47602
  ...Object.keys(assets).length ? { assets } : {}
47402
47603
  };
47403
- const sha = gitSha();
47604
+ const sha2 = gitSha();
47404
47605
  const buildable = shellSha();
47405
47606
  const manifest = {
47406
- releaseId: releaseIdFor(sha, content),
47407
- gitSha: sha,
47408
- ...buildable && buildable !== sha ? { shellSha: buildable } : {},
47607
+ releaseId: releaseIdFor(sha2, content),
47608
+ gitSha: sha2,
47609
+ ...buildable && buildable !== sha2 ? { shellSha: buildable } : {},
47409
47610
  ...content
47410
47611
  };
47411
47612
  await store.putManifest(app, manifest);
@@ -47488,8 +47689,8 @@ async function buildFunctionsOnlyRelease(preloaded, dest) {
47488
47689
  return { ...schedule, bundleHash: target.bundleHash };
47489
47690
  }) } : {}
47490
47691
  };
47491
- const sha = gitSha();
47492
- const manifest = { releaseId: contentReleaseId(content), gitSha: sha, ...content };
47692
+ const sha2 = gitSha();
47693
+ const manifest = { releaseId: contentReleaseId(content), gitSha: sha2, ...content };
47493
47694
  await store.putManifest(app, manifest);
47494
47695
  return { manifest, failedToBuild: [], missingBundles: [], auth: {}, tierCapViolations: [] };
47495
47696
  }
@@ -48467,14 +48668,14 @@ function observeShots(dir, memory, now) {
48467
48668
  const archived = archiveFile(dir, memory, join43(shotsDir, f));
48468
48669
  if (!archived) continue;
48469
48670
  seen.add(id);
48470
- const { sha, ext } = archived;
48671
+ const { sha: sha2, ext } = archived;
48471
48672
  const prev = memory.shots[id];
48472
48673
  if (!prev) {
48473
- memory.shots[id] = { sha, ext, firstSeen: now, changedAt: now, versions: 1, present: true };
48474
- memory.timeline.push({ ts: now, id, sha, ext });
48475
- } else if (prev.sha !== sha) {
48476
- memory.shots[id] = { ...prev, sha, ext, changedAt: now, versions: prev.versions + 1, present: true };
48477
- memory.timeline.push({ ts: now, id, sha, ext });
48674
+ memory.shots[id] = { sha: sha2, ext, firstSeen: now, changedAt: now, versions: 1, present: true };
48675
+ memory.timeline.push({ ts: now, id, sha: sha2, ext });
48676
+ } else if (prev.sha !== sha2) {
48677
+ memory.shots[id] = { ...prev, sha: sha2, ext, changedAt: now, versions: prev.versions + 1, present: true };
48678
+ memory.timeline.push({ ts: now, id, sha: sha2, ext });
48478
48679
  } else if (!prev.present) {
48479
48680
  memory.shots[id] = { ...prev, present: true };
48480
48681
  }
@@ -48957,12 +49158,12 @@ function planFromPayload(payload, base, selectors) {
48957
49158
  if (wantBundles.size) {
48958
49159
  patch.bundles = {};
48959
49160
  for (const name of wantBundles) {
48960
- const sha = local.bundles?.[name]?.sha256;
48961
- const b64 = sha ? payload.bundles?.[sha] : void 0;
48962
- if (!sha || !b64) {
49161
+ const sha2 = local.bundles?.[name]?.sha256;
49162
+ const b64 = sha2 ? payload.bundles?.[sha2] : void 0;
49163
+ if (!sha2 || !b64) {
48963
49164
  return { ok: false, error: { error: "selector_matched_nothing", selector: `bundle:${name}`, message: `bundle "${name}" has no built bytes in this release \u2014 run \`himi build\` first.`, bundles: localBundles } };
48964
49165
  }
48965
- patch.bundles[sha] = b64;
49166
+ patch.bundles[sha2] = b64;
48966
49167
  touched.push(`bundle:${name}`);
48967
49168
  }
48968
49169
  }
@@ -48985,13 +49186,13 @@ function planFromPayload(payload, base, selectors) {
48985
49186
  return { ok: false, error: { error: "selector_matched_nothing", selector: `screen:${id}`, message: `screen "${id}" has no built descriptor in this release.`, screens: localScreens } };
48986
49187
  }
48987
49188
  const localKey = local.screens?.[id]?.bundle ?? null;
48988
- const sha = resolveBundleSha(local, localKey);
48989
- const carriedHere = sha ? patch.bundles?.[sha] !== void 0 : false;
49189
+ const sha2 = resolveBundleSha(local, localKey);
49190
+ const carriedHere = sha2 ? patch.bundles?.[sha2] !== void 0 : false;
48990
49191
  const baseKeyForSha = (s) => {
48991
49192
  for (const [k, e] of Object.entries(base.bundles ?? {})) if (e?.sha256 === s) return k;
48992
49193
  return null;
48993
49194
  };
48994
- const baseKey = sha ? baseKeyForSha(sha) : null;
49195
+ const baseKey = sha2 ? baseKeyForSha(sha2) : null;
48995
49196
  if (localKey !== null && !carriedHere && !baseKey) {
48996
49197
  return {
48997
49198
  ok: false,
@@ -49003,7 +49204,7 @@ function planFromPayload(payload, base, selectors) {
49003
49204
  }
49004
49205
  };
49005
49206
  }
49006
- const bundleRef = carriedHere ? sha : baseKey;
49207
+ const bundleRef = carriedHere ? sha2 : baseKey;
49007
49208
  patch.screens[id] = { descriptor, bundle: bundleRef };
49008
49209
  if (bundleRef && bundleRef !== (base.screens?.[id]?.bundle ?? null)) rebound[id] = bundleRef;
49009
49210
  touched.push(`screen:${id}`);
@@ -50992,7 +51193,7 @@ function shotStateFor(dir, image) {
50992
51193
  }
50993
51194
  }
50994
51195
  function recordedShots(dir, screenId) {
50995
- return Object.entries(readRecord(dir)).filter(([sha, r]) => /^[0-9a-f]{64}$/.test(sha) && !!r && r.screen === screenId && typeof r.at === "string" && ["content", "error", "loading", "empty"].includes(r.state)).map(([sha, r]) => ({ sha256: sha, at: r.at, state: { state: r.state, ...r.initial === true ? { initial: true } : {}, ...r.offline === true ? { offline: true } : {} } })).sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : 0);
51196
+ return Object.entries(readRecord(dir)).filter(([sha2, r]) => /^[0-9a-f]{64}$/.test(sha2) && !!r && r.screen === screenId && typeof r.at === "string" && ["content", "error", "loading", "empty"].includes(r.state)).map(([sha2, r]) => ({ sha256: sha2, at: r.at, state: { state: r.state, ...r.initial === true ? { initial: true } : {}, ...r.offline === true ? { offline: true } : {} } })).sort((a, b) => a.at < b.at ? 1 : a.at > b.at ? -1 : 0);
50996
51197
  }
50997
51198
  function offlineArtifact(state) {
50998
51199
  if (!state || state.state === "content") return false;
@@ -51266,7 +51467,7 @@ var init_studio_reel = __esm({
51266
51467
  };
51267
51468
  isObj3 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
51268
51469
  str4 = (v) => typeof v === "string" && v ? v : null;
51269
- historyName = (sha, ext) => typeof sha === "string" && typeof ext === "string" && HISTORY_NAME.test(`${sha}.${ext}`) ? `${sha}.${ext}` : null;
51470
+ historyName = (sha2, ext) => typeof sha2 === "string" && typeof ext === "string" && HISTORY_NAME.test(`${sha2}.${ext}`) ? `${sha2}.${ext}` : null;
51270
51471
  cited = (p) => [
51271
51472
  ...p.frames.map((f) => f.file),
51272
51473
  ...p.finals.flatMap((f) => f.file ? [f.file] : []),
@@ -51515,10 +51716,10 @@ function stillBuilding(dir, image, now) {
51515
51716
  if (!entries.length) return void 0;
51516
51717
  const times = entries.map((e) => Date.parse(e.ts)).filter(Number.isFinite);
51517
51718
  if (!times.length || !(now.getTime() - Math.max(...times) > STILL_BUILDING_AFTER_MS)) return void 0;
51518
- const sha = sha256Of2(image);
51719
+ const sha2 = sha256Of2(image);
51519
51720
  const latest = entries[entries.length - 1];
51520
- if (Array.isArray(latest.images) && latest.images.some((i) => !!i && typeof i === "object" && i.sha256 === sha)) return void 0;
51521
- return journal(dir, "still-building", withHomeCaveat(STILL_BUILDING_MESSAGE, homeCaveat(dir, image)), { ref: `home-${sha.slice(0, 16)}`, image, now });
51721
+ if (Array.isArray(latest.images) && latest.images.some((i) => !!i && typeof i === "object" && i.sha256 === sha2)) return void 0;
51722
+ return journal(dir, "still-building", withHomeCaveat(STILL_BUILDING_MESSAGE, homeCaveat(dir, image)), { ref: `home-${sha2.slice(0, 16)}`, image, now });
51522
51723
  } catch {
51523
51724
  return void 0;
51524
51725
  }
@@ -51805,10 +52006,10 @@ function homeShotForPostcard(dir, opts = {}) {
51805
52006
  }
51806
52007
  if (opts.repeatOk) return pick2;
51807
52008
  try {
51808
- const sha = sha256Of2(pick2.image);
52009
+ const sha2 = sha256Of2(pick2.image);
51809
52010
  const entries = readProgress(dir, { all: true }).entries;
51810
52011
  const latest = entries[entries.length - 1];
51811
- const shown2 = Array.isArray(latest?.images) && latest.images.some((i) => !!i && typeof i === "object" && i.sha256 === sha);
52012
+ const shown2 = Array.isArray(latest?.images) && latest.images.some((i) => !!i && typeof i === "object" && i.sha256 === sha2);
51812
52013
  if (shown2) return { skipped: "the latest postcard already shows this home picture" };
51813
52014
  } catch {
51814
52015
  return { skipped: "the home shot could not be read" };
@@ -58601,16 +58802,16 @@ ${releaseId}`;
58601
58802
  if (assetMatch) {
58602
58803
  if (!app) return json4(400, { error: "missing_app" });
58603
58804
  if (!store.getAsset) return json4(501, { error: "assets_unsupported" });
58604
- const sha = assetMatch[1];
58605
- if (!isSafeSegment(sha)) return json4(400, { error: "bad_asset" });
58805
+ const sha2 = assetMatch[1];
58806
+ if (!isSafeSegment(sha2)) return json4(400, { error: "bad_asset" });
58606
58807
  const m = pin ? await pinnedManifest(app, pin) : await manifest(app);
58607
- const record2 = Object.values(m?.assets ?? {}).find((a) => a.sha256 === sha);
58808
+ const record2 = Object.values(m?.assets ?? {}).find((a) => a.sha256 === sha2);
58608
58809
  if (!record2) return json4(404, { error: "asset_not_found", app });
58609
- const etag = strongEtagForSha256(sha);
58810
+ const etag = strongEtagForSha256(sha2);
58610
58811
  if (request.headers.get("if-none-match") === etag) {
58611
58812
  return new Response(null, { status: 304, headers: { etag, "cache-control": BUNDLE_CACHE_CONTROL } });
58612
58813
  }
58613
- const bytes2 = await store.getAsset(app, sha);
58814
+ const bytes2 = await store.getAsset(app, sha2);
58614
58815
  if (!bytes2) return json4(404, { error: "asset_not_found", app });
58615
58816
  return new Response(bytes2, {
58616
58817
  status: 200,
@@ -59187,21 +59388,21 @@ var init_preview_assets_generated = __esm({
59187
59388
  "../../packages/serve/src/preview-assets.generated.ts"() {
59188
59389
  init_preview_assets_format();
59189
59390
  PREVIEW_ASSET_MANIFEST = [
59190
- { key: "assets/index-DHO0WElX.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1127482, sha256: "9a198f7af12c683263ba55641ec1802d4ca9addb9b8d38170e6b4f60387c61c3" },
59191
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1127490, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
59192
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1132100, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
59193
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1620684, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
59194
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1711608, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
59195
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1802592, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
59196
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1893572, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
59197
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1984444, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
59198
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2075424, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
59199
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2184060, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
59200
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2293144, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
59201
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2402192, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
59202
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2509892, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
59203
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2598e3, length: 4947, sha256: "72e531f20412d0f07433cb52fac32ff7f5be87a4ef435c2d919cd5e21ec84ad3" },
59204
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2602947, length: 17344, sha256: "d057298d75e7373e5e2c4d11b7d1bcbea88e03af555ba63d97641c78509c58e4" }
59391
+ { key: "assets/index-CKwdlTbT.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1127499, sha256: "24d1f9f9ee47c8d4ea8f88ebfca0b78f46d3e64d26b2dd202ae97cbdcf3ede2b" },
59392
+ { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1127507, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
59393
+ { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1132117, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
59394
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1620701, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
59395
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1711625, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
59396
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1802609, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
59397
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1893589, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
59398
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1984461, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
59399
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2075441, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
59400
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2184077, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
59401
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2293161, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
59402
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2402209, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
59403
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2509909, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
59404
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2598017, length: 4947, sha256: "1d32807684d4cf8de017b14f9b1c6faf05c08f04a8d5d19372d4122c0c66b0bf" },
59405
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 2602964, length: 17344, sha256: "d057298d75e7373e5e2c4d11b7d1bcbea88e03af555ba63d97641c78509c58e4" }
59205
59406
  ];
59206
59407
  }
59207
59408
  });
@@ -59824,7 +60025,7 @@ var init_iap_client = __esm({
59824
60025
  "../store-release/iap-client.ts"() {
59825
60026
  init_register_bundle();
59826
60027
  ASC_ORIGIN = "https://api.appstoreconnect.apple.com";
59827
- MAX_PAGES = 50;
60028
+ MAX_PAGES = 100;
59828
60029
  MAX_READ_ATTEMPTS = 4;
59829
60030
  REQUEST_TIMEOUT_MS = 3e4;
59830
60031
  MAX_RETRY_WAIT_MS = 6e4;
@@ -59983,6 +60184,9 @@ var init_iap_client = __esm({
59983
60184
  });
59984
60185
 
59985
60186
  // ../store-release/iap-api.ts
60187
+ function appleProductType(type) {
60188
+ return APPLE_PRODUCT_TYPES[type];
60189
+ }
59986
60190
  function single(doc) {
59987
60191
  return doc.data && !Array.isArray(doc.data) ? doc.data : void 0;
59988
60192
  }
@@ -60036,10 +60240,12 @@ async function readOptional(read) {
60036
60240
  function createIapApi(client2) {
60037
60241
  return new AppStoreConnectIapApi(client2);
60038
60242
  }
60039
- var INITIAL_PRICE_REF, PAGE, linkage, related, enc, AppStoreConnectIapApi;
60243
+ var APPLE_PRODUCT_TYPES, PRICE_POINT_PAGE, INITIAL_PRICE_REF, PAGE, linkage, related, enc, AppStoreConnectIapApi;
60040
60244
  var init_iap_api = __esm({
60041
60245
  "../store-release/iap-api.ts"() {
60042
60246
  init_iap_client();
60247
+ APPLE_PRODUCT_TYPES = { nonConsumable: "NON_CONSUMABLE", consumable: "CONSUMABLE" };
60248
+ PRICE_POINT_PAGE = "limit=8000";
60043
60249
  INITIAL_PRICE_REF = "${initial-price}";
60044
60250
  PAGE = "limit=200";
60045
60251
  linkage = (type, id) => ({ type, id });
@@ -60076,7 +60282,7 @@ var init_iap_api = __esm({
60076
60282
  return (await this.client.list(`/v1/inAppPurchaseVersions/${enc(versionId)}/localizations?${PAGE}`)).map(toLocalization);
60077
60283
  }
60078
60284
  async listPricePoints(resourceId, territory) {
60079
- const points = await this.client.list(`/v2/inAppPurchases/${enc(resourceId)}/pricePoints?filter[territory]=${enc(territory)}&${PAGE}`);
60285
+ const points = await this.client.list(`/v2/inAppPurchases/${enc(resourceId)}/pricePoints?filter[territory]=${enc(territory)}&${PRICE_POINT_PAGE}`);
60080
60286
  return points.map((p) => ({ id: p.id, customerPrice: attr(p, "customerPrice") }));
60081
60287
  }
60082
60288
  async readPriceSchedule(resourceId) {
@@ -60105,7 +60311,7 @@ var init_iap_api = __esm({
60105
60311
  const attributes = {
60106
60312
  name: product.referenceName,
60107
60313
  productId: product.productId,
60108
- inAppPurchaseType: "NON_CONSUMABLE",
60314
+ inAppPurchaseType: appleProductType(product.type),
60109
60315
  familySharable: false,
60110
60316
  ...product.reviewNote ? { reviewNote: product.reviewNote } : {}
60111
60317
  };
@@ -60189,11 +60395,13 @@ function readLocalizations(reader, item, path2) {
60189
60395
  const localizations = reader.list(item, "localizations", path2).map((value, i) => {
60190
60396
  const at2 = `${path2}.localizations[${i}]`;
60191
60397
  const obj3 = reader.object(value, at2, LOCALIZATION_KEYS);
60192
- return {
60193
- locale: reader.text(obj3, "locale", at2, LOCALE),
60194
- name: reader.text(obj3, "name", at2),
60195
- description: reader.text(obj3, "description", at2)
60196
- };
60398
+ const name = reader.text(obj3, "name", at2);
60399
+ const length = [...name].length;
60400
+ const { min, max } = APPLE_LIMITS.displayName;
60401
+ if (name && (length < min || length > max)) reader.problems.push(`${at2}.name must be ${min}\u2013${max} characters (got ${length})`);
60402
+ const description = reader.text(obj3, "description", at2);
60403
+ reader.maxLength(description, `${at2}.description`, APPLE_LIMITS.description);
60404
+ return { locale: reader.text(obj3, "locale", at2, LOCALE), name, description };
60197
60405
  });
60198
60406
  reader.duplicates(localizations.map((l) => l.locale), `${path2}.localizations`, "locale");
60199
60407
  return localizations;
@@ -60201,10 +60409,12 @@ function readLocalizations(reader, item, path2) {
60201
60409
  function readPrice(reader, item, path2) {
60202
60410
  const at2 = `${path2}.price`;
60203
60411
  const obj3 = reader.object(item.price, at2, PRICE_KEYS);
60412
+ const rule = obj3.rule === void 0 ? void 0 : reader.oneOf(obj3, "rule", at2, PRICE_RULES, "exact matches Apple's price exactly; atLeast takes the next Apple price point up");
60204
60413
  return {
60205
60414
  baseTerritory: reader.text(obj3, "baseTerritory", at2, TERRITORY),
60206
60415
  currency: reader.text(obj3, "currency", at2, CURRENCY),
60207
- amount: reader.text(obj3, "amount", at2, AMOUNT)
60416
+ amount: reader.text(obj3, "amount", at2, AMOUNT),
60417
+ ...rule ? { rule } : {}
60208
60418
  };
60209
60419
  }
60210
60420
  function readTerritories(reader, item, path2, baseTerritory) {
@@ -60216,34 +60426,66 @@ function readTerritories(reader, item, path2, baseTerritory) {
60216
60426
  }
60217
60427
  return territories;
60218
60428
  }
60429
+ function readWixEntity(reader, value, at2) {
60430
+ const obj3 = reader.object(value, at2, WIX_KEYS);
60431
+ return { entityType: reader.text(obj3, "entityType", at2), entityId: reader.text(obj3, "entityId", at2) };
60432
+ }
60433
+ function readUnlocks(reader, obj3, path2) {
60434
+ return reader.list(obj3, "unlocks", path2).map((value, i) => {
60435
+ const at2 = `${path2}.unlocks[${i}]`;
60436
+ const unlock = reader.object(value, at2, UNLOCK_KEYS);
60437
+ return { entityType: reader.text(unlock, "entityType", at2), entityId: reader.text(unlock, "entityId", at2), entitlement: reader.text(unlock, "entitlement", at2) };
60438
+ });
60439
+ }
60440
+ function readSubject(reader, obj3, path2, type) {
60441
+ if (obj3.wix === void 0 === (obj3.unlocks === void 0)) {
60442
+ reader.problems.push(`${path2} must name exactly one of "wix" or "unlocks"`);
60443
+ return {};
60444
+ }
60445
+ if (obj3.wix !== void 0) return { wix: readWixEntity(reader, obj3.wix, `${path2}.wix`) };
60446
+ if (type !== "consumable") reader.problems.push(`${path2}: only a consumable may carry "unlocks" \u2014 a non-consumable sells exactly one Wix item`);
60447
+ return { unlocks: readUnlocks(reader, obj3, path2) };
60448
+ }
60449
+ function limitedText(reader, obj3, key2, path2, max, rule) {
60450
+ const value = reader.text(obj3, key2, path2, rule);
60451
+ reader.maxLength(value, childPath(path2, key2), max);
60452
+ return value;
60453
+ }
60219
60454
  function readItem(reader, value, index) {
60220
60455
  const path2 = `items[${index}]`;
60221
60456
  const obj3 = reader.object(value, path2, ITEM_KEYS);
60222
- const wix = reader.object(obj3.wix, `${path2}.wix`, WIX_KEYS);
60457
+ const type = reader.oneOf(obj3, "type", path2, PRODUCT_TYPES, "subscriptions are not supported by this version");
60223
60458
  const price = readPrice(reader, obj3, path2);
60224
60459
  const reviewNote = reader.optionalText(obj3, "reviewNote", path2);
60460
+ if (reviewNote) reader.maxLength(reviewNote, `${path2}.reviewNote`, APPLE_LIMITS.reviewNote);
60225
60461
  return {
60226
- wix: { entityType: reader.text(wix, "entityType", `${path2}.wix`), entityId: reader.text(wix, "entityId", `${path2}.wix`) },
60462
+ ...readSubject(reader, obj3, path2, type),
60227
60463
  classification: reader.literal(obj3, "classification", path2, "digital", "physical goods and unclassified products are never registered as in-app purchases"),
60228
60464
  entitlement: reader.text(obj3, "entitlement", path2),
60229
- productId: reader.text(obj3, "productId", path2, PRODUCT_ID),
60230
- type: reader.literal(obj3, "type", path2, "nonConsumable", "other product types are not supported by this version"),
60231
- referenceName: reader.text(obj3, "referenceName", path2),
60465
+ productId: limitedText(reader, obj3, "productId", path2, APPLE_LIMITS.productId, PRODUCT_ID),
60466
+ type,
60467
+ referenceName: limitedText(reader, obj3, "referenceName", path2, APPLE_LIMITS.referenceName),
60232
60468
  ...reviewNote === void 0 ? {} : { reviewNote },
60233
60469
  localizations: readLocalizations(reader, obj3, path2),
60234
60470
  price,
60235
60471
  territories: readTerritories(reader, obj3, path2, price.baseTerritory)
60236
60472
  };
60237
60473
  }
60474
+ function wixEntitiesOf(item) {
60475
+ return item.unlocks ?? (item.wix ? [item.wix] : []);
60476
+ }
60238
60477
  function reportDuplicateMappings(reader, items) {
60239
60478
  reader.duplicates(items.map((i) => i.productId), "items", "productId");
60240
- reader.duplicates(items.map((i) => `${i.wix.entityType}:${i.wix.entityId}`), "items", "Wix entity");
60241
- reader.duplicates(items.map((i) => i.entitlement), "items", "entitlement");
60479
+ reader.duplicates(items.flatMap((i) => wixEntitiesOf(i).map((e) => `${e.entityType}:${e.entityId}`)), "items", "Wix entity");
60480
+ reader.duplicates(items.flatMap((i) => [i.entitlement, ...(i.unlocks ?? []).map((u) => u.entitlement)]), "items", "entitlement");
60242
60481
  reader.duplicates(items.map((i) => i.referenceName), "items", "referenceName");
60243
60482
  }
60483
+ function looksLikeCredential(text8) {
60484
+ return CREDENTIAL_SHAPES.some((shape) => shape.test(text8));
60485
+ }
60244
60486
  function reportCredentialMaterial(reader, value, path2) {
60245
60487
  if (typeof value === "string") {
60246
- if (CREDENTIAL_SHAPES.some((shape) => shape.test(value))) {
60488
+ if (looksLikeCredential(value)) {
60247
60489
  reader.problems.push(`${path2} looks like credential material \u2014 keys and tokens never belong in a manifest`);
60248
60490
  }
60249
60491
  } else if (Array.isArray(value)) {
@@ -60286,7 +60528,7 @@ function sha256Digest(value) {
60286
60528
  function manifestDigest(manifest) {
60287
60529
  return sha256Digest(manifest);
60288
60530
  }
60289
- var IapManifestError, APPLE_APP_ID, TEAM_ID, BUNDLE_ID, PRODUCT_ID, TERRITORY, CURRENCY, AMOUNT, LOCALE, CREDENTIAL_SHAPES, MANIFEST_KEYS, TARGET_KEYS, ITEM_KEYS, WIX_KEYS, LOCALIZATION_KEYS, PRICE_KEYS, childPath, ManifestReader;
60531
+ var IapManifestError, APPLE_APP_ID, TEAM_ID, BUNDLE_ID, PRODUCT_ID, TERRITORY, CURRENCY, AMOUNT, LOCALE, CREDENTIAL_SHAPES, MANIFEST_KEYS, TARGET_KEYS, ITEM_KEYS, WIX_KEYS, UNLOCK_KEYS, LOCALIZATION_KEYS, PRICE_KEYS, PRODUCT_TYPES, PRICE_RULES, APPLE_LIMITS, childPath, ManifestReader;
60290
60532
  var init_iap_manifest = __esm({
60291
60533
  "../store-release/iap-manifest.ts"() {
60292
60534
  IapManifestError = class extends Error {
@@ -60300,7 +60542,7 @@ var init_iap_manifest = __esm({
60300
60542
  APPLE_APP_ID = { pattern: /^\d+$/, hint: "the numeric App Store Connect app id" };
60301
60543
  TEAM_ID = { pattern: /^[A-Z0-9]{10}$/, hint: "a 10-character Apple team id" };
60302
60544
  BUNDLE_ID = { pattern: /^[A-Za-z0-9.-]+$/, hint: "a bundle id" };
60303
- PRODUCT_ID = { pattern: /^[A-Za-z0-9._]+$/, hint: "alphanumerics, underscores and periods only" };
60545
+ PRODUCT_ID = { pattern: /^[A-Za-z0-9._-]+$/, hint: "letters, digits, hyphens, periods and underscores only" };
60304
60546
  TERRITORY = { pattern: /^[A-Z]{3}$/, hint: "an ISO 3166-1 alpha-3 territory code such as USA" };
60305
60547
  CURRENCY = { pattern: /^[A-Z]{3}$/, hint: "an ISO 4217 currency code such as USD" };
60306
60548
  AMOUNT = { pattern: /^\d+(\.\d+)?$/, hint: 'an exact decimal string such as "0.99"' };
@@ -60314,6 +60556,7 @@ var init_iap_manifest = __esm({
60314
60556
  TARGET_KEYS = ["himiApp", "wixSiteId", "appleAppId", "bundleId", "teamId", "profile"];
60315
60557
  ITEM_KEYS = [
60316
60558
  "wix",
60559
+ "unlocks",
60317
60560
  "classification",
60318
60561
  "entitlement",
60319
60562
  "productId",
@@ -60325,8 +60568,12 @@ var init_iap_manifest = __esm({
60325
60568
  "territories"
60326
60569
  ];
60327
60570
  WIX_KEYS = ["entityType", "entityId"];
60571
+ UNLOCK_KEYS = ["entityType", "entityId", "entitlement"];
60328
60572
  LOCALIZATION_KEYS = ["locale", "name", "description"];
60329
- PRICE_KEYS = ["baseTerritory", "currency", "amount"];
60573
+ PRICE_KEYS = ["baseTerritory", "currency", "amount", "rule"];
60574
+ PRODUCT_TYPES = ["nonConsumable", "consumable"];
60575
+ PRICE_RULES = ["exact", "atLeast"];
60576
+ APPLE_LIMITS = { displayName: { min: 2, max: 30 }, description: 45, referenceName: 64, productId: 100, reviewNote: 4e3 };
60330
60577
  childPath = (path2, key2) => path2 ? `${path2}.${key2}` : key2;
60331
60578
  ManifestReader = class {
60332
60579
  problems = [];
@@ -60366,6 +60613,15 @@ var init_iap_manifest = __esm({
60366
60613
  if (obj3[key2] !== expected) this.problems.push(`${childPath(path2, key2)} must be ${JSON.stringify(expected)} \u2014 ${why}`);
60367
60614
  return expected;
60368
60615
  }
60616
+ oneOf(obj3, key2, path2, allowed2, why) {
60617
+ const value = obj3[key2];
60618
+ if (allowed2.includes(value)) return value;
60619
+ this.problems.push(`${childPath(path2, key2)} must be ${allowed2.map((a) => JSON.stringify(a)).join(" or ")} \u2014 ${why}`);
60620
+ return allowed2[0];
60621
+ }
60622
+ maxLength(value, at2, max) {
60623
+ if ([...value].length > max) this.problems.push(`${at2} must be at most ${max} characters (got ${[...value].length})`);
60624
+ }
60369
60625
  duplicates(values, path2, what) {
60370
60626
  const seen = /* @__PURE__ */ new Set();
60371
60627
  for (const value of values) {
@@ -60377,6 +60633,84 @@ var init_iap_manifest = __esm({
60377
60633
  }
60378
60634
  });
60379
60635
 
60636
+ // ../store-release/iap-pricing.ts
60637
+ function normalizeAmount(amount) {
60638
+ const [whole, fraction = ""] = amount.trim().split(".");
60639
+ const integer = whole.replace(/^0+(?=\d)/, "");
60640
+ const decimals = fraction.replace(/0+$/, "");
60641
+ return decimals ? `${integer}.${decimals}` : integer;
60642
+ }
60643
+ function compareAmounts(a, b) {
60644
+ const [aWhole, aFraction = ""] = normalizeAmount(a).split(".");
60645
+ const [bWhole, bFraction = ""] = normalizeAmount(b).split(".");
60646
+ if (aWhole.length !== bWhole.length) return aWhole.length - bWhole.length;
60647
+ if (aWhole !== bWhole) return aWhole < bWhole ? -1 : 1;
60648
+ const width = Math.max(aFraction.length, bFraction.length);
60649
+ const [x, y] = [aFraction.padEnd(width, "0"), bFraction.padEnd(width, "0")];
60650
+ return x === y ? 0 : x < y ? -1 : 1;
60651
+ }
60652
+ function pickPoint(points, price) {
60653
+ if (price.rule !== "atLeast") return points.find((p) => compareAmounts(p.customerPrice, price.amount) === 0);
60654
+ return points.filter((p) => compareAmounts(p.customerPrice, price.amount) >= 0).sort((a, b) => compareAmounts(a.customerPrice, b.customerPrice))[0];
60655
+ }
60656
+ function nearestPrices(amounts, amount) {
60657
+ const distance = (a) => Math.abs(Number(a) - Number(amount));
60658
+ return [...amounts].sort((a, b) => distance(a) - distance(b)).slice(0, 2).sort(compareAmounts);
60659
+ }
60660
+ function describeRequestedPrice(price, target) {
60661
+ const where = `${price.currency} in ${price.baseTerritory}`;
60662
+ if (price.rule !== "atLeast") return `${price.amount} ${where}`;
60663
+ return target ? `${target} ${where} (the lowest point at or above ${price.amount})` : `the lowest point at or above ${price.amount} ${where}`;
60664
+ }
60665
+ function noPricePointMessage(price, amounts) {
60666
+ if (price.rule === "atLeast") {
60667
+ const highest = [...amounts].sort((a, b) => compareAmounts(b, a))[0] ?? "none";
60668
+ return `no Apple price point at or above ${price.amount} ${price.currency} in ${price.baseTerritory} (highest: ${highest}) \u2014 lower the manifest's amount and make a new plan.`;
60669
+ }
60670
+ return `no Apple price point of exactly ${price.amount} ${price.currency} in ${price.baseTerritory} (nearest: ${nearestPrices(amounts, price.amount).join(", ") || "none"}) \u2014 Apple sells only fixed price points; change the manifest's amount (or set price.rule "atLeast") and make a new plan. Nothing is rounded here.`;
60671
+ }
60672
+ var asPoints, PriceGrid;
60673
+ var init_iap_pricing = __esm({
60674
+ "../store-release/iap-pricing.ts"() {
60675
+ asPoints = (amounts) => amounts.map((customerPrice) => ({ customerPrice }));
60676
+ PriceGrid = class {
60677
+ constructor(api2) {
60678
+ this.api = api2;
60679
+ }
60680
+ amounts = /* @__PURE__ */ new Map();
60681
+ async pointsFor(resourceId, territory) {
60682
+ const points = await this.api.listPricePoints(resourceId, territory);
60683
+ this.amounts.set(territory, points.map((p) => p.customerPrice));
60684
+ return points;
60685
+ }
60686
+ /** The amounts Apple offers in a territory, read through `viaResourceId` at most once per run. */
60687
+ async amountsIn(territory, viaResourceId) {
60688
+ const cached3 = this.amounts.get(territory);
60689
+ if (cached3 || !viaResourceId) return cached3;
60690
+ return (await this.pointsFor(viaResourceId, territory)).map((p) => p.customerPrice);
60691
+ }
60692
+ /** The Apple amount a price resolves to; undefined for atLeast until some product can show the grid. */
60693
+ async targetAmount(price, viaResourceId) {
60694
+ if (price.rule !== "atLeast") return price.amount;
60695
+ const amounts = await this.amountsIn(price.baseTerritory, viaResourceId);
60696
+ return amounts && pickPoint(asPoints(amounts), price)?.customerPrice;
60697
+ }
60698
+ /** Plan time: does Apple offer a point for this price? Answered from the cached grid. */
60699
+ async check(price, viaResourceId) {
60700
+ const amounts = await this.amountsIn(price.baseTerritory, viaResourceId) ?? [];
60701
+ const amount = pickPoint(asPoints(amounts), price)?.customerPrice;
60702
+ return amount ? { amount } : { failure: noPricePointMessage(price, amounts) };
60703
+ }
60704
+ /** Apply time: the point to write. Point ids are per product, so this lists the product's own. */
60705
+ async resolve(resourceId, price) {
60706
+ const points = await this.pointsFor(resourceId, price.baseTerritory);
60707
+ const point = pickPoint(points, price);
60708
+ return point ? { point } : { failure: noPricePointMessage(price, points.map((p) => p.customerPrice)) };
60709
+ }
60710
+ };
60711
+ }
60712
+ });
60713
+
60380
60714
  // ../store-release/iap-result.ts
60381
60715
  function remainingRequirements(observed) {
60382
60716
  if (!observed) return ["the product does not exist in App Store Connect yet"];
@@ -60389,8 +60723,8 @@ function mappingFor(target, item, appleResourceId) {
60389
60723
  wixSiteId: target.wixSiteId,
60390
60724
  appleAppId: target.appleAppId,
60391
60725
  bundleId: target.bundleId,
60392
- wixEntityType: item.wix.entityType,
60393
- wixEntityId: item.wix.entityId,
60726
+ ...item.wix ? { wixEntityType: item.wix.entityType, wixEntityId: item.wix.entityId } : {},
60727
+ ...item.unlocks ? { unlocks: item.unlocks } : {},
60394
60728
  entitlement: item.entitlement,
60395
60729
  appleProductId: item.productId,
60396
60730
  ...appleResourceId ? { appleResourceId } : {},
@@ -60418,7 +60752,7 @@ function itemResult(input) {
60418
60752
  ...observed?.availability ? { observed: observed.availability.territories, availableInNewTerritories: observed.availability.availableInNewTerritories } : {}
60419
60753
  },
60420
60754
  localizations: { requested: item.localizations.map((l) => l.locale), observed: observed?.localizations.map((l) => l.locale) ?? [] },
60421
- remainingRequirements: remainingRequirements(observed),
60755
+ remainingRequirements: [...input.requirements ?? [], ...remainingRequirements(observed)],
60422
60756
  lastReadAt: input.readAt.toISOString()
60423
60757
  }
60424
60758
  };
@@ -60456,11 +60790,10 @@ var init_iap_result = __esm({
60456
60790
  });
60457
60791
 
60458
60792
  // ../store-release/iap-plan.ts
60459
- function normalizeAmount(amount) {
60460
- const [whole, fraction = ""] = amount.trim().split(".");
60461
- const integer = whole.replace(/^0+(?=\d)/, "");
60462
- const decimals = fraction.replace(/0+$/, "");
60463
- return decimals ? `${integer}.${decimals}` : integer;
60793
+ function priceNote(price, target) {
60794
+ if (price.rule !== "atLeast") return CONDITIONAL_PRICE_NOTE;
60795
+ const where = `${price.currency} in ${price.baseTerritory}`;
60796
+ return target ? `Apple price ${target} ${where} \u2014 the lowest price point at or above ${price.amount}` : `the lowest Apple price point at or above ${price.amount} ${where} is chosen once the product exists`;
60464
60797
  }
60465
60798
  function describeOp(op) {
60466
60799
  return "locale" in op ? `${op.kind} ${op.locale}` : op.kind;
@@ -60561,8 +60894,8 @@ async function observeProduct(api2, product) {
60561
60894
  ...availability ? { availability } : {}
60562
60895
  };
60563
60896
  }
60564
- function priceMatches(observed, requested) {
60565
- return observed !== void 0 && observed.baseTerritory === requested.baseTerritory && observed.customerPrices.length === 1 && normalizeAmount(observed.customerPrices[0]) === normalizeAmount(requested.amount);
60897
+ function priceMatches(observed, requested, target) {
60898
+ return target !== void 0 && observed !== void 0 && observed.baseTerritory === requested.baseTerritory && observed.customerPrices.length === 1 && compareAmounts(observed.customerPrices[0], target) === 0;
60566
60899
  }
60567
60900
  function availabilityMatches(observed, territories) {
60568
60901
  return observed !== void 0 && !observed.availableInNewTerritories && sameSet(observed.territories, territories);
@@ -60574,12 +60907,11 @@ function localizationDiffs(item, observed) {
60574
60907
  return found.name === wanted.name && found.description === wanted.description ? [] : [`localization ${wanted.locale} differs`];
60575
60908
  });
60576
60909
  }
60577
- function priceDiff(item, observed) {
60578
- if (priceMatches(observed.price, item.price)) return [];
60910
+ function priceDiff(item, observed, target) {
60911
+ if (priceMatches(observed.price, item.price, target)) return [];
60579
60912
  if (!observed.price) return ["no price is set"];
60580
60913
  const prices = observed.price.customerPrices.join("/") || "none";
60581
- const requested = `${item.price.amount} ${item.price.currency} in ${item.price.baseTerritory}`;
60582
- return [`price is ${prices} in ${observed.price.baseTerritory}; the manifest requests ${requested}`];
60914
+ return [`price is ${prices} in ${observed.price.baseTerritory}; the manifest requests ${describeRequestedPrice(item.price, target)}`];
60583
60915
  }
60584
60916
  function availabilityDiff(item, observed) {
60585
60917
  if (availabilityMatches(observed.availability, item.territories)) return [];
@@ -60587,8 +60919,8 @@ function availabilityDiff(item, observed) {
60587
60919
  const auto = observed.availability.availableInNewTerritories ? " (plus new territories automatically)" : "";
60588
60920
  return [`available in ${observed.availability.territories.join(", ")}${auto}; the manifest requests ${item.territories.join(", ")}`];
60589
60921
  }
60590
- function metadataDiffs(item, observed) {
60591
- return [...localizationDiffs(item, observed), ...priceDiff(item, observed), ...availabilityDiff(item, observed)];
60922
+ function metadataDiffs(item, observed, target = exactTarget(item.price)) {
60923
+ return [...localizationDiffs(item, observed), ...priceDiff(item, observed, target), ...availabilityDiff(item, observed)];
60592
60924
  }
60593
60925
  function isLocked(observed) {
60594
60926
  return !EDITABLE_PRODUCT_STATES.has(observed.state) || observed.versions.some((v) => LOCKED_VERSION_STATES.has(v.state));
@@ -60596,8 +60928,9 @@ function isLocked(observed) {
60596
60928
  function itemPlan(item, decision, fields = {}) {
60597
60929
  return { productId: item.productId, decision, ops: [], conflicts: [], notes: [], ...fields };
60598
60930
  }
60599
- function creationPlan(item) {
60931
+ function creationPlan(item, target) {
60600
60932
  return itemPlan(item, "create", {
60933
+ ...target ? { applePrice: target, targetPrice: target } : {},
60601
60934
  ops: [
60602
60935
  { kind: "createProduct" },
60603
60936
  { kind: "createVersion" },
@@ -60605,17 +60938,20 @@ function creationPlan(item) {
60605
60938
  { kind: "setPrice" },
60606
60939
  { kind: "setAvailability" }
60607
60940
  ],
60608
- notes: [CONDITIONAL_PRICE_NOTE]
60941
+ notes: [priceNote(item.price, target)]
60609
60942
  });
60610
60943
  }
60611
60944
  function identityConflicts(item, observed) {
60612
60945
  const conflicts = [];
60613
- if (observed.type !== APPLE_NON_CONSUMABLE) conflicts.push(`Apple has ${item.productId} as ${observed.type}; product ids and types are immutable, so it cannot become a non-consumable`);
60946
+ if (observed.type !== appleProductType(item.type)) {
60947
+ const wanted = item.type === "consumable" ? "a consumable" : "a non-consumable";
60948
+ conflicts.push(`Apple has ${item.productId} as ${observed.type}; product ids and types are immutable, so it cannot become ${wanted}`);
60949
+ }
60614
60950
  if (observed.referenceName !== item.referenceName) conflicts.push(`Apple's reference name is "${observed.referenceName}"; the manifest says "${item.referenceName}"`);
60615
60951
  return conflicts;
60616
60952
  }
60617
- function lockedPlan(item, observed) {
60618
- const diffs = metadataDiffs(item, observed);
60953
+ function lockedPlan(item, observed, target) {
60954
+ const diffs = metadataDiffs(item, observed, target);
60619
60955
  if (!diffs.length) return itemPlan(item, "reuse", { observed });
60620
60956
  const conflict = `${item.productId} is ${observed.state} and this command never changes a live or in-review product: ${diffs.join("; ")}`;
60621
60957
  return itemPlan(item, "conflict", { observed, conflicts: [conflict] });
@@ -60631,58 +60967,54 @@ function localizationOps(item, observed) {
60631
60967
  return found.name === wanted.name && found.description === wanted.description ? [] : [{ kind: "updateLocalization", locale: wanted.locale }];
60632
60968
  });
60633
60969
  }
60634
- async function exactPricePoint(api2, resourceId, price) {
60635
- const points = await api2.listPricePoints(resourceId, price.baseTerritory);
60636
- const wanted = normalizeAmount(price.amount);
60637
- const point = points.find((p) => normalizeAmount(p.customerPrice) === wanted);
60638
- const distance = (p) => Math.abs(Number(p.customerPrice) - Number(price.amount));
60639
- const nearest = [...points].sort((a, b) => distance(a) - distance(b)).slice(0, 2).map((p) => p.customerPrice).sort((a, b) => Number(a) - Number(b));
60640
- return { point, nearest };
60641
- }
60642
- function noExactPriceMessage(price, nearest) {
60643
- return `no Apple price point of exactly ${price.amount} ${price.currency} in ${price.baseTerritory} (nearest: ${nearest.join(", ") || "none"}) \u2014 Apple sells only fixed price points; change the manifest's amount and make a new plan. Nothing is rounded here.`;
60644
- }
60645
- async function priceOps(api2, item, observed) {
60970
+ async function priceOps(pricing, item, observed, target) {
60646
60971
  if (observed.price) {
60647
- return { ops: [], conflicts: priceMatches(observed.price, item.price) ? [] : [`${priceDiff(item, observed)[0]} \u2014 an existing price is never changed here`] };
60972
+ const matches = priceMatches(observed.price, item.price, target);
60973
+ return { ops: [], conflicts: matches ? [] : [`${priceDiff(item, observed, target)[0]} \u2014 an existing price is never changed here`] };
60648
60974
  }
60649
- const { point, nearest } = await exactPricePoint(api2, observed.resourceId, item.price);
60650
- return point ? { ops: [{ kind: "setPrice" }], conflicts: [] } : { ops: [], conflicts: [noExactPriceMessage(item.price, nearest)] };
60975
+ const { amount, failure } = await pricing.check(item.price, observed.resourceId);
60976
+ return amount ? { ops: [{ kind: "setPrice" }], conflicts: [], applePrice: amount } : { ops: [], conflicts: [failure ?? "no Apple price point"] };
60651
60977
  }
60652
60978
  function availabilityOps(item, observed) {
60653
60979
  if (!observed.availability) return { ops: [{ kind: "setAvailability" }], conflicts: [] };
60654
60980
  const conflicts = availabilityMatches(observed.availability, item.territories) ? [] : [`${availabilityDiff(item, observed)[0]} \u2014 existing availability is never changed here`];
60655
60981
  return { ops: [], conflicts };
60656
60982
  }
60657
- async function editablePlan(api2, item, observed) {
60658
- const price = await priceOps(api2, item, observed);
60983
+ async function editablePlan(pricing, item, observed, target) {
60984
+ const price = await priceOps(pricing, item, observed, target);
60659
60985
  const availability = availabilityOps(item, observed);
60660
60986
  const conflicts = [...price.conflicts, ...availability.conflicts];
60661
60987
  if (conflicts.length) return itemPlan(item, "conflict", { observed, conflicts });
60662
60988
  const ops = [...localizationOps(item, observed), ...price.ops, ...availability.ops];
60663
- return itemPlan(item, ops.length ? "update" : "reuse", { observed, ops });
60989
+ return itemPlan(item, ops.length ? "update" : "reuse", { observed, ops, ...price.applePrice ? { applePrice: price.applePrice } : {} });
60664
60990
  }
60665
- async function planForProduct(api2, item, product) {
60991
+ async function planForProduct(api2, item, product, pricing) {
60666
60992
  const observed = await observeProduct(api2, product);
60993
+ const target = await pricing.targetAmount(item.price, product.id);
60667
60994
  const conflicts = identityConflicts(item, observed);
60668
- if (conflicts.length) return itemPlan(item, "conflict", { observed, conflicts });
60669
- return isLocked(observed) ? lockedPlan(item, observed) : editablePlan(api2, item, observed);
60995
+ const decided = conflicts.length ? itemPlan(item, "conflict", { observed, conflicts }) : isLocked(observed) ? lockedPlan(item, observed, target) : await editablePlan(pricing, item, observed, target);
60996
+ return target ? { ...decided, targetPrice: target } : decided;
60670
60997
  }
60671
- async function planItem(api2, item, products) {
60998
+ async function planItem(pricing, api2, item, products) {
60672
60999
  const nameClash = products.find((p) => p.referenceName === item.referenceName && p.productId !== item.productId);
60673
61000
  if (nameClash) return itemPlan(item, "conflict", { conflicts: [`reference name "${item.referenceName}" is already used by ${nameClash.productId}`] });
60674
61001
  const match = products.find((p) => p.productId === item.productId);
60675
- return match ? planForProduct(api2, item, match) : creationPlan(item);
61002
+ if (match) return planForProduct(api2, item, match, pricing);
61003
+ return creationPlan(item, await pricing.targetAmount(item.price, products[0]?.id));
60676
61004
  }
60677
- async function planItems(context, manifest) {
61005
+ async function planItemsAt(context, manifest, indices = manifest.items.map((_, i) => i)) {
61006
+ const pricing = context.pricing ?? new PriceGrid(context.api);
60678
61007
  return withAccessContext(context.identity, manifest.target, async () => {
60679
61008
  await verifyTarget(context.api, manifest, context.identity);
60680
61009
  const products = await discoverProducts(context, manifest);
60681
- const plans = [];
60682
- for (const item of manifest.items) plans.push(await planItem(context.api, item, products));
61010
+ const plans = /* @__PURE__ */ new Map();
61011
+ for (const i of indices) plans.set(i, await planItem(pricing, context.api, manifest.items[i], products));
60683
61012
  return plans;
60684
61013
  });
60685
61014
  }
61015
+ async function planItems(context, manifest) {
61016
+ return [...(await planItemsAt(context, manifest)).values()];
61017
+ }
60686
61018
  function sealPlan(unsealed) {
60687
61019
  const normalized = JSON.parse(JSON.stringify(unsealed));
60688
61020
  return { ...normalized, planDigest: sha256Digest(normalized) };
@@ -60724,26 +61056,32 @@ function verifyPlan2(doc) {
60724
61056
  return plan;
60725
61057
  }
60726
61058
  async function statusIap(manifest, context) {
60727
- const plans = await planItems(context, manifest);
61059
+ const pricing = context.pricing ?? new PriceGrid(context.api);
61060
+ const plans = await planItems({ ...context, pricing }, manifest);
60728
61061
  const readAt = context.now();
60729
- const items = plans.map(
60730
- (p, i) => itemResult({
61062
+ const items = [];
61063
+ for (const [i, p] of plans.entries()) {
61064
+ const item = manifest.items[i];
61065
+ const target = p.observed ? await pricing.targetAmount(item.price, p.observed.resourceId) : void 0;
61066
+ items.push(itemResult({
60731
61067
  target: manifest.target,
60732
- item: manifest.items[i],
61068
+ item,
60733
61069
  observed: p.observed,
60734
61070
  outcome: STATUS_OUTCOMES[p.decision],
60735
61071
  conflicts: p.conflicts,
60736
- unmet: p.observed ? metadataDiffs(manifest.items[i], p.observed) : [],
61072
+ unmet: p.observed ? metadataDiffs(item, p.observed, target) : [],
60737
61073
  readAt
60738
- })
60739
- );
61074
+ }));
61075
+ }
60740
61076
  return iapResult({ target: manifest.target, identity: context.identity, items, generatedAt: readAt });
60741
61077
  }
60742
- var IapProvisionError, EDITABLE_VERSION_STATE, APPLE_NON_CONSUMABLE, EDITABLE_PRODUCT_STATES, LOCKED_VERSION_STATES, OP_KINDS, LOCALE_OPS, CONDITIONAL_PRICE_NOTE, STATUS_OUTCOMES, isoDay, planError;
61078
+ var IapProvisionError, EDITABLE_VERSION_STATE, EDITABLE_PRODUCT_STATES, LOCKED_VERSION_STATES, OP_KINDS, LOCALE_OPS, CONDITIONAL_PRICE_NOTE, STATUS_OUTCOMES, isoDay, exactTarget, planError;
60743
61079
  var init_iap_plan = __esm({
60744
61080
  "../store-release/iap-plan.ts"() {
61081
+ init_iap_api();
60745
61082
  init_iap_client();
60746
61083
  init_iap_manifest();
61084
+ init_iap_pricing();
60747
61085
  init_iap_result();
60748
61086
  IapProvisionError = class extends Error {
60749
61087
  constructor(reason, message) {
@@ -60753,7 +61091,6 @@ var init_iap_plan = __esm({
60753
61091
  }
60754
61092
  };
60755
61093
  EDITABLE_VERSION_STATE = "PREPARE_FOR_SUBMISSION";
60756
- APPLE_NON_CONSUMABLE = "NON_CONSUMABLE";
60757
61094
  EDITABLE_PRODUCT_STATES = /* @__PURE__ */ new Set(["MISSING_METADATA", "READY_TO_SUBMIT", "DEVELOPER_ACTION_NEEDED", "REJECTED"]);
60758
61095
  LOCKED_VERSION_STATES = /* @__PURE__ */ new Set(["READY_FOR_REVIEW", "WAITING_FOR_REVIEW", "IN_REVIEW", "ACCEPTED", "APPROVED"]);
60759
61096
  OP_KINDS = /* @__PURE__ */ new Set(["createProduct", "createVersion", "createLocalization", "updateLocalization", "setPrice", "setAvailability"]);
@@ -60761,6 +61098,7 @@ var init_iap_plan = __esm({
60761
61098
  CONDITIONAL_PRICE_NOTE = "the exact price point can only be looked up after the product exists \u2014 apply stops with a partial result if Apple has no point of exactly the requested amount";
60762
61099
  STATUS_OUTCOMES = { create: "absent", update: "incomplete", reuse: "reused", conflict: "conflict" };
60763
61100
  isoDay = (date) => date.toISOString().slice(0, 10);
61101
+ exactTarget = (price) => price.rule === "atLeast" ? void 0 : price.amount;
60764
61102
  planError = (message) => new IapProvisionError("plan", message);
60765
61103
  }
60766
61104
  });
@@ -60783,7 +61121,7 @@ function driftOf(planned, live2) {
60783
61121
  return unplanned.length ? `${live2.productId} now needs ${unplanned.map(describeOp).join(", ")}, which the plan did not list` : void 0;
60784
61122
  }
60785
61123
  function assertNoDrift(plan, live2) {
60786
- const drift = plan.items.map((planned, i) => driftOf(planned, live2[i])).filter((d) => d !== void 0);
61124
+ const drift = [...live2].map(([i, current]) => driftOf(plan.items[i], current)).filter((d) => d !== void 0);
60787
61125
  if (!drift.length) return;
60788
61126
  throw new IapProvisionError("drift", `App Store Connect changed since the plan was made \u2014 nothing was written:
60789
61127
  - ${drift.join("\n - ")}
@@ -60834,12 +61172,13 @@ async function findLocalization(api2, versionId, locale, matchText) {
60834
61172
  if (!found || matchText && (found.name !== matchText.name || found.description !== matchText.description)) return void 0;
60835
61173
  return found;
60836
61174
  }
60837
- function operationRunners(api2, manifest, sleep) {
61175
+ function operationRunners(context, manifest) {
61176
+ const { api: api2, sleep, pricing } = context;
60838
61177
  const appId2 = manifest.target.appleAppId;
60839
61178
  return {
60840
61179
  createProduct: async (run2) => {
60841
61180
  const { item } = run2;
60842
- const newProduct = { productId: item.productId, referenceName: item.referenceName, ...item.reviewNote ? { reviewNote: item.reviewNote } : {} };
61181
+ const newProduct = { productId: item.productId, referenceName: item.referenceName, type: item.type, ...item.reviewNote ? { reviewNote: item.reviewNote } : {} };
60843
61182
  const { value, state } = await ensure({ write: () => api2.createProduct(appId2, newProduct), find: () => findProduct(api2, appId2, item.productId) }, sleep);
60844
61183
  run2.resourceId = value.id;
60845
61184
  return { state, resourceId: value.id };
@@ -60867,9 +61206,9 @@ function operationRunners(api2, manifest, sleep) {
60867
61206
  setPrice: async (run2) => {
60868
61207
  const resourceId = required(run2.resourceId, "the product id");
60869
61208
  const { price } = run2.item;
60870
- const { point, nearest } = await exactPricePoint(api2, resourceId, price);
60871
- if (!point) throw new UnresolvedWriteError(noExactPriceMessage(price, nearest));
60872
- const matches = async () => priceMatches(await readObservedPrice(api2, resourceId), price) ? true : void 0;
61209
+ const { point, failure } = await pricing.resolve(resourceId, price);
61210
+ if (!point) throw new UnresolvedWriteError(failure ?? "no Apple price point");
61211
+ const matches = async () => priceMatches(await readObservedPrice(api2, resourceId), price, point.customerPrice) ? true : void 0;
60873
61212
  const { state } = await ensure({ write: async () => (await api2.createPriceSchedule(resourceId, price.baseTerritory, point.id), true), find: matches }, sleep);
60874
61213
  return { state };
60875
61214
  },
@@ -60902,10 +61241,10 @@ async function runStep(runner2, run2, op, context, manifest) {
60902
61241
  context.journal.record({ productId: run2.item.productId, ...run2.resourceId ? { productResourceId: run2.resourceId } : {}, ...entry, at: context.now().toISOString() });
60903
61242
  return step;
60904
61243
  }
60905
- async function resumeFromApple(run2, remaining, api2) {
60906
- const product = await api2.readProduct(required(run2.resourceId, "the product id"));
61244
+ async function resumeFromApple(run2, remaining, context) {
61245
+ const product = await context.api.readProduct(required(run2.resourceId, "the product id"));
60907
61246
  if (!product) return { reason: "the product exists but cannot be read back yet \u2014 rerun apply later" };
60908
- const replanned = await planForProduct(api2, run2.item, product);
61247
+ const replanned = await planForProduct(context.api, run2.item, product, context.pricing);
60909
61248
  if (replanned.decision === "conflict") return { reason: `the product already existed: ${replanned.conflicts.join("; ")}` };
60910
61249
  const unplanned = replanned.ops.filter((op) => !remaining.some((r) => sameOp(r, op)));
60911
61250
  if (unplanned.length) return { reason: `the product already existed and needs ${unplanned.map(describeOp).join(", ")}, which the plan did not list \u2014 make a new plan` };
@@ -60914,7 +61253,7 @@ async function resumeFromApple(run2, remaining, api2) {
60914
61253
  return { ops: replanned.ops };
60915
61254
  }
60916
61255
  async function runOps(run2, ops, context, manifest) {
60917
- const runners = operationRunners(context.api, manifest, context.sleep);
61256
+ const runners = operationRunners(context, manifest);
60918
61257
  const steps = [];
60919
61258
  let queue = [...ops];
60920
61259
  for (let op = queue.shift(); op; op = queue.shift()) {
@@ -60922,7 +61261,7 @@ async function runOps(run2, ops, context, manifest) {
60922
61261
  steps.push(step);
60923
61262
  if (step.state === "failed") return [...steps, ...queue.map((rest) => stepFor(rest, "notRun"))];
60924
61263
  if (op.kind === "createProduct" && step.state === "reconciled") {
60925
- const resumed = await resumeFromApple(run2, queue, context.api).catch((e) => ({ reason: `could not re-read the product: ${explainFailure(e, context, manifest)}` }));
61264
+ const resumed = await resumeFromApple(run2, queue, context).catch((e) => ({ reason: `could not re-read the product: ${explainFailure(e, context, manifest)}` }));
60926
61265
  if ("reason" in resumed) return [...steps, ...queue.map((rest, i) => stepFor(rest, "notRun", i === 0 ? { detail: resumed.reason } : {}))];
60927
61266
  queue = resumed.ops;
60928
61267
  }
@@ -60945,10 +61284,17 @@ async function readBack(run2, context, manifest) {
60945
61284
  return { failure: explainFailure(e, context, manifest) };
60946
61285
  }
60947
61286
  }
61287
+ async function unmetFor(item, observed, context) {
61288
+ return observed ? metadataDiffs(item, observed, await context.pricing.targetAmount(item.price, observed.resourceId)) : [];
61289
+ }
61290
+ async function knownUnmetFor(item, planned, context) {
61291
+ if (!planned.observed) return [];
61292
+ return metadataDiffs(item, planned.observed, planned.targetPrice ?? await context.pricing.targetAmount(item.price));
61293
+ }
60948
61294
  async function applyItem(item, live2, context, manifest) {
60949
61295
  const base = { target: manifest.target, item, readAt: context.now() };
60950
61296
  if (live2.decision === "conflict") {
60951
- const unmet2 = live2.observed ? metadataDiffs(item, live2.observed) : [];
61297
+ const unmet2 = await knownUnmetFor(item, live2, context);
60952
61298
  return itemResult({ ...base, observed: live2.observed, outcome: "conflict", conflicts: live2.conflicts, unmet: unmet2 });
60953
61299
  }
60954
61300
  const run2 = {
@@ -60959,29 +61305,55 @@ async function applyItem(item, live2, context, manifest) {
60959
61305
  };
60960
61306
  const steps = await runOps(run2, live2.ops, context, manifest);
60961
61307
  const { observed, failure } = await readBack(run2, context, manifest);
60962
- const unmet = failure ? [`could not read the product back: ${failure}`] : observed ? metadataDiffs(item, observed) : [];
61308
+ const unmet = failure ? [`could not read the product back: ${failure}`] : await unmetFor(item, observed, context);
60963
61309
  const outcome = outcomeOf(live2.ops, steps, unmet);
60964
61310
  return itemResult({ ...base, readAt: context.now(), observed, outcome, steps, unmet, appleResourceId: run2.resourceId });
60965
61311
  }
60966
- function effectivePlan(planned, live2) {
60967
- return planned.decision === "conflict" ? { ...live2, decision: "conflict", ops: [], conflicts: planned.conflicts } : live2;
61312
+ function deferredResult(item, live2, context, manifest) {
61313
+ const steps = live2.ops.map((op) => stepFor(op, "notRun"));
61314
+ return itemResult({ target: manifest.target, item, observed: live2.observed, outcome: "deferred", steps, requirements: [DEFERRED_REQUIREMENT], readAt: context.now() });
60968
61315
  }
60969
- async function applyIap(planDoc, context) {
61316
+ function needsRefresh(planned) {
61317
+ return planned.decision !== "conflict" && planned.ops.length > 0;
61318
+ }
61319
+ function plannedResult(item, planned, plan, context, manifest) {
61320
+ if (planned.decision === "conflict") return applyItem(item, planned, context, manifest);
61321
+ return itemResult({ target: manifest.target, item, observed: planned.observed, outcome: "reused", readAt: new Date(plan.createdAt) });
61322
+ }
61323
+ async function applyIap(planDoc, applyContext) {
60970
61324
  const plan = verifyPlan2(planDoc);
60971
- assertSameAccount(plan.identity, context.identity);
61325
+ assertSameAccount(plan.identity, applyContext.identity);
61326
+ const context = { ...applyContext, pricing: applyContext.pricing ?? new PriceGrid(applyContext.api) };
60972
61327
  const manifest = plan.manifest;
60973
- const live2 = await planItems(context, manifest);
61328
+ const refresh = plan.items.flatMap((planned, i) => needsRefresh(planned) ? [i] : []);
61329
+ const live2 = await planItemsAt(context, manifest, refresh);
60974
61330
  assertNoDrift(plan, live2);
60975
61331
  const items = [];
60976
- for (const [i, item] of manifest.items.entries()) items.push(await applyItem(item, effectivePlan(plan.items[i], live2[i]), context, manifest));
61332
+ let created = 0;
61333
+ for (const [i, item] of manifest.items.entries()) {
61334
+ const itemPlan2 = live2.get(i);
61335
+ if (!itemPlan2) {
61336
+ items.push(await plannedResult(item, plan.items[i], plan, context, manifest));
61337
+ continue;
61338
+ }
61339
+ const needsCreate = itemPlan2.ops.some((op) => op.kind === "createProduct");
61340
+ if (needsCreate && context.createLimit !== void 0 && created >= context.createLimit) {
61341
+ items.push(deferredResult(item, itemPlan2, context, manifest));
61342
+ continue;
61343
+ }
61344
+ if (needsCreate) created++;
61345
+ items.push(await applyItem(item, itemPlan2, context, manifest));
61346
+ }
60977
61347
  return iapResult({ target: manifest.target, identity: context.identity, items, generatedAt: context.now() });
60978
61348
  }
60979
- var RECONCILE_READS, RECONCILE_BASE_WAIT_MS, AMBIGUOUS_WRITE_KINDS, UnresolvedWriteError, required;
61349
+ var DEFERRED_REQUIREMENT, RECONCILE_READS, RECONCILE_BASE_WAIT_MS, AMBIGUOUS_WRITE_KINDS, UnresolvedWriteError, required;
60980
61350
  var init_iap_apply = __esm({
60981
61351
  "../store-release/iap-apply.ts"() {
60982
61352
  init_iap_client();
60983
61353
  init_iap_plan();
61354
+ init_iap_pricing();
60984
61355
  init_iap_result();
61356
+ DEFERRED_REQUIREMENT = "deferred by --limit \u2014 rerun apply to continue; nothing was written for this product in this run";
60985
61357
  RECONCILE_READS = 4;
60986
61358
  RECONCILE_BASE_WAIT_MS = 1e3;
60987
61359
  AMBIGUOUS_WRITE_KINDS = /* @__PURE__ */ new Set(["uncertain", "rateLimited", "conflict"]);
@@ -61182,6 +61554,182 @@ var init_iap_credentials = __esm({
61182
61554
  }
61183
61555
  });
61184
61556
 
61557
+ // ../store-release/iap-draft.ts
61558
+ import { createHash as createHash33 } from "node:crypto";
61559
+ function fit(text8, max) {
61560
+ const chars = [...text8];
61561
+ return chars.length <= max ? text8 : `${chars.slice(0, max - 1).join("").trimEnd()}${ELLIPSIS}`;
61562
+ }
61563
+ function plainText2(html) {
61564
+ return html.replace(/<[^>]*>/g, " ").replace(/&(amp|nbsp|lt|gt|quot|#39);/g, (entity) => ENTITIES[entity]).replace(/\s+/g, " ").trim();
61565
+ }
61566
+ function displayName(name) {
61567
+ const plain2 = plainText2(name);
61568
+ return fit([...plain2].length >= APPLE_LIMITS.displayName.min ? plain2 : `${plain2} item`, APPLE_LIMITS.displayName.max);
61569
+ }
61570
+ function boundedProductId(bundleId, slug, tail, hashSource) {
61571
+ const readable = `${bundleId}.${slug}.${tail}`;
61572
+ if (tail && readable.length <= APPLE_LIMITS.productId) return readable;
61573
+ const suffix = `.${slug}.${sha(hashSource).slice(0, 24)}`;
61574
+ return `${bundleId.slice(0, APPLE_LIMITS.productId - suffix.length)}${suffix}`;
61575
+ }
61576
+ function productIdFor(bundleId, slug, wixId) {
61577
+ return boundedProductId(bundleId, slug, wixId.toLowerCase().replace(/[^a-z0-9]/g, ""), wixId);
61578
+ }
61579
+ function applePointFor(amount, options) {
61580
+ if (!options.applePrices) return void 0;
61581
+ const price = { baseTerritory: options.baseTerritory, currency: options.currency, amount, rule: "atLeast" };
61582
+ return pickPoint(options.applePrices.map((customerPrice) => ({ customerPrice })), price)?.customerPrice;
61583
+ }
61584
+ function highestApplePrice(options) {
61585
+ return [...options.applePrices ?? []].sort((a, b) => Number(b) - Number(a))[0] ?? "none";
61586
+ }
61587
+ function skipReason(item, options) {
61588
+ if (item.hidden) return "hidden in the Wix catalog (unpublished, archived or not visible)";
61589
+ if (item.kind !== "digital") return SKIP_REASONS[item.kind];
61590
+ if (!item.price || normalizeAmount(item.price.amount) === "0") return "free \u2014 there is nothing to sell";
61591
+ if (item.price.currency !== options.currency) {
61592
+ return `priced in ${item.price.currency}; the base territory ${options.baseTerritory} prices in ${options.currency}`;
61593
+ }
61594
+ if (options.applePrices && !applePointFor(item.price.amount, options)) {
61595
+ return `priced above Apple's highest price point (${highestApplePrice(options)} ${options.currency}) in ${options.baseTerritory}`;
61596
+ }
61597
+ if ([item.name, item.description ?? ""].some(looksLikeCredential)) return "its name or description looks like a credential \u2014 fix the text in Wix";
61598
+ return void 0;
61599
+ }
61600
+ function priceFor(amount, options) {
61601
+ return { baseTerritory: options.baseTerritory, currency: options.currency, amount: normalizeAmount(amount), rule: "atLeast" };
61602
+ }
61603
+ function perItemProduct(item, options, names) {
61604
+ const plainDescription = item.description ? plainText2(item.description) : "";
61605
+ const name = nameOf(item);
61606
+ return {
61607
+ wix: { entityType: item.source, entityId: item.id },
61608
+ classification: "digital",
61609
+ entitlement: entityKey(item.source, item.id),
61610
+ productId: productIdFor(options.target.bundleId, SOURCE_SLUGS[item.source], item.id),
61611
+ type: "nonConsumable",
61612
+ referenceName: names.claim(name, item.id),
61613
+ localizations: [{ locale: options.locale, name: displayName(name), description: fit(plainDescription || `Unlock ${name}`, APPLE_LIMITS.description) }],
61614
+ price: priceFor(item.price.amount, options),
61615
+ territories: options.territories
61616
+ };
61617
+ }
61618
+ function tierKey(amount, currency) {
61619
+ return `${currency.toLowerCase()}_${normalizeAmount(amount).replace(".", "_")}`;
61620
+ }
61621
+ function unlockFor(item) {
61622
+ return { entityType: item.source, entityId: item.id, entitlement: entityKey(item.source, item.id) };
61623
+ }
61624
+ function tierPrice(amount, applePoint, options) {
61625
+ if (!applePoint) return priceFor(amount, options);
61626
+ return { baseTerritory: options.baseTerritory, currency: options.currency, amount: normalizeAmount(applePoint) };
61627
+ }
61628
+ function newTier(key2, price, options, names) {
61629
+ const label2 = `${price.amount} ${options.currency}`;
61630
+ return {
61631
+ unlocks: [],
61632
+ classification: "digital",
61633
+ entitlement: `tier:${key2}`,
61634
+ productId: tierProductId(options, key2),
61635
+ type: "consumable",
61636
+ referenceName: names.claim(`Credit ${label2}`, key2),
61637
+ localizations: [{ locale: options.locale, name: displayName(`Unlock (${label2})`), description: TIER_DESCRIPTION }],
61638
+ price,
61639
+ territories: options.territories
61640
+ };
61641
+ }
61642
+ function draftTiers(items, options, names, existingItems) {
61643
+ const byProductId = new Map(existingItems.map((i) => [i.productId, i]));
61644
+ const created = [];
61645
+ for (const item of items) {
61646
+ const applePoint = applePointFor(item.price.amount, options);
61647
+ const price = tierPrice(item.price.amount, applePoint, options);
61648
+ const key2 = tierKey(price.amount, options.currency);
61649
+ const productId = tierProductId(options, key2);
61650
+ let tier = byProductId.get(productId);
61651
+ if (!tier) {
61652
+ tier = newTier(key2, price, options, names);
61653
+ byProductId.set(productId, tier);
61654
+ created.push(tier);
61655
+ }
61656
+ tier.unlocks = [...tier.unlocks ?? [], unlockFor(item)];
61657
+ }
61658
+ return created;
61659
+ }
61660
+ function draftManifest(catalog, options) {
61661
+ const existingItems = structuredClone(options.existing?.items ?? []);
61662
+ const mapped = new Set(existingItems.flatMap((i) => wixEntitiesOf(i).map((e) => entityKey(e.entityType, e.entityId))));
61663
+ const names = new ReferenceNames(existingItems);
61664
+ const skipped = [];
61665
+ const toDraft = [];
61666
+ let kept = 0;
61667
+ for (const item of catalog) {
61668
+ if (mapped.has(entityKey(item.source, item.id))) kept++;
61669
+ else {
61670
+ const reason = skipReason(item, options);
61671
+ if (reason) skipped.push({ source: item.source, id: item.id, name: item.name, reason });
61672
+ else toDraft.push(item);
61673
+ }
61674
+ }
61675
+ const drafted = options.mode === "tiers" ? draftTiers(toDraft, options, names, existingItems) : toDraft.map((item) => perItemProduct(item, options, names));
61676
+ const items = [...existingItems, ...drafted];
61677
+ const newlyMapped = toDraft.length;
61678
+ if (!items.length) return { skipped, kept, newlyMapped };
61679
+ const manifest = { manifestVersion: 1, target: options.target, items };
61680
+ return { manifest: parseIapManifest(JSON.stringify(manifest), "the drafted manifest"), skipped, kept, newlyMapped };
61681
+ }
61682
+ var SOURCE_SLUGS, SKIP_REASONS, TIER_DESCRIPTION, ELLIPSIS, entityKey, sha, ENTITIES, MAX_SUFFIX, ReferenceNames, nameOf, tierProductId;
61683
+ var init_iap_draft = __esm({
61684
+ "../store-release/iap-draft.ts"() {
61685
+ init_iap_manifest();
61686
+ init_iap_pricing();
61687
+ SOURCE_SLUGS = { storesProduct: "product", onlineProgram: "program", pricingPlan: "plan" };
61688
+ SKIP_REASONS = {
61689
+ physical: "physical goods are never sold through in-app purchase",
61690
+ subscription: "a recurring plan needs an App Store subscription, which this version does not register",
61691
+ unknown: "the catalog does not say whether it is digital"
61692
+ };
61693
+ TIER_DESCRIPTION = "Unlocks one item in the app";
61694
+ ELLIPSIS = "\u2026";
61695
+ entityKey = (entityType, entityId) => `${entityType}:${entityId}`;
61696
+ sha = (text8) => createHash33("sha256").update(text8).digest("hex");
61697
+ ENTITIES = { "&amp;": "&", "&nbsp;": " ", "&lt;": "<", "&gt;": ">", "&quot;": '"', "&#39;": "'" };
61698
+ MAX_SUFFIX = 24;
61699
+ ReferenceNames = class {
61700
+ taken = /* @__PURE__ */ new Set();
61701
+ constructor(existing) {
61702
+ for (const item of existing) this.taken.add(item.referenceName);
61703
+ }
61704
+ /** The name itself, else name · id prefix, lengthening the prefix (then a hash, then counting) until unused. */
61705
+ claim(name, disambiguator) {
61706
+ const plain2 = plainText2(name);
61707
+ for (const suffix of this.suffixes(disambiguator)) {
61708
+ const room = Math.max(1, APPLE_LIMITS.referenceName - suffix.length - 3);
61709
+ const candidate = suffix ? `${fit(plain2, room)} \xB7 ${suffix}` : fit(plain2, APPLE_LIMITS.referenceName);
61710
+ if (!this.taken.has(candidate)) {
61711
+ this.taken.add(candidate);
61712
+ return candidate;
61713
+ }
61714
+ }
61715
+ throw new Error(`could not find a unique reference name for "${plain2}"`);
61716
+ }
61717
+ /** Suffixes stay short (at most MAX_SUFFIX characters) so the name keeps room within Apple's 64. */
61718
+ *suffixes(disambiguator) {
61719
+ yield "";
61720
+ const compact = disambiguator.replace(/[^A-Za-z0-9]/g, "");
61721
+ for (let length = 8; length < Math.min(compact.length, MAX_SUFFIX); length += 4) yield compact.slice(0, length);
61722
+ if (compact.length <= MAX_SUFFIX) yield compact;
61723
+ const hash = sha(disambiguator).slice(0, 12);
61724
+ yield hash;
61725
+ for (let n = 2; n < 1e4; n++) yield `${hash}-${n}`;
61726
+ }
61727
+ };
61728
+ nameOf = (item) => plainText2(item.name) || item.id;
61729
+ tierProductId = (options, key2) => boundedProductId(options.target.bundleId, "tier", key2, key2);
61730
+ }
61731
+ });
61732
+
61185
61733
  // src/base44-analyze/redact.ts
61186
61734
  function isSecretKey(key2) {
61187
61735
  const words = key2.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
@@ -61265,6 +61813,161 @@ var init_redact = __esm({
61265
61813
  }
61266
61814
  });
61267
61815
 
61816
+ // src/store-iap-catalog.ts
61817
+ function amountText(value) {
61818
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
61819
+ return typeof value === "string" && value.trim() ? value.trim() : void 0;
61820
+ }
61821
+ function priceOf(amount, currency) {
61822
+ const text8 = amountText(amount);
61823
+ return text8 && typeof currency === "string" && currency ? { amount: text8, currency } : void 0;
61824
+ }
61825
+ function catalogItem(fields) {
61826
+ return Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== void 0 && v !== "" && v !== false));
61827
+ }
61828
+ function productKind(productType) {
61829
+ const type = String(productType ?? "").toLowerCase();
61830
+ return type === "digital" || type === "physical" ? type : "unknown";
61831
+ }
61832
+ async function allByCursor(wix, path2, key2, extra = {}) {
61833
+ const rows = [];
61834
+ let cursor;
61835
+ for (let page = 0; page < MAX_PAGES2; page++) {
61836
+ const cursorPaging = cursor ? { limit: PAGE2, cursor } : { limit: PAGE2 };
61837
+ const res = await withRetry(path2, () => wix("POST", path2, { ...extra, query: { cursorPaging } }));
61838
+ rows.push(...res[key2] ?? []);
61839
+ cursor = res.pagingMetadata?.cursors?.next;
61840
+ if (!cursor) return rows;
61841
+ }
61842
+ throw new Error(`${path2} had more than ${MAX_PAGES2} pages \u2014 refusing to draft from a partial catalog`);
61843
+ }
61844
+ async function allByOffset(fetchPage, label2) {
61845
+ const rows = [];
61846
+ for (let page = 0; page < MAX_PAGES2; page++) {
61847
+ const { rows: batch, total } = await withRetry(label2, () => fetchPage(rows.length));
61848
+ rows.push(...batch);
61849
+ const done = batch.length === 0 || (total !== void 0 ? rows.length >= total : batch.length < PAGE2);
61850
+ if (done) return rows;
61851
+ }
61852
+ throw new Error(`${label2} had more than ${MAX_PAGES2} pages \u2014 refusing to draft from a partial catalog`);
61853
+ }
61854
+ function fromV3Product(p) {
61855
+ return catalogItem({
61856
+ source: "storesProduct",
61857
+ id: p.id,
61858
+ name: p.name || p.id,
61859
+ description: p.plainDescription,
61860
+ kind: productKind(p.productType),
61861
+ price: priceOf(p.actualPriceRange?.minValue?.amount, p.currency),
61862
+ hidden: p.visible === false || void 0
61863
+ });
61864
+ }
61865
+ function fromV1Product(p) {
61866
+ return catalogItem({
61867
+ source: "storesProduct",
61868
+ id: p.id,
61869
+ name: p.name || p.id,
61870
+ description: typeof p.description === "string" ? p.description : void 0,
61871
+ kind: productKind(p.productType),
61872
+ price: priceOf(p.priceData?.price ?? p.price?.price, p.priceData?.currency ?? p.price?.currency),
61873
+ hidden: p.visible === false || void 0
61874
+ });
61875
+ }
61876
+ async function readStores(wix, notes) {
61877
+ try {
61878
+ return (await allByCursor(wix, "/stores/v3/products/query", "products", { fields: ["CURRENCY", "PLAIN_DESCRIPTION"] })).map(fromV3Product);
61879
+ } catch (e) {
61880
+ if (isNotInstalled2(e)) return notInstalled(notes, "Wix Stores");
61881
+ if (!isWrongCatalogVersion(e)) throw e;
61882
+ }
61883
+ notes.push("Wix Stores uses Catalog V1 on this site; read through the V1 product query.");
61884
+ try {
61885
+ const rows = await allByOffset(async (offset) => {
61886
+ const res = await wix("POST", "/stores/v1/products/query", { query: { paging: { limit: PAGE2, offset } } });
61887
+ return { rows: res.products ?? [], total: res.totalResults };
61888
+ }, "/stores/v1/products/query");
61889
+ return rows.map(fromV1Product);
61890
+ } catch (e) {
61891
+ if (isNotInstalled2(e)) return notInstalled(notes, "Wix Stores");
61892
+ throw e;
61893
+ }
61894
+ }
61895
+ function notInstalled(notes, vertical) {
61896
+ notes.push(`${vertical} is not installed on this site.`);
61897
+ return [];
61898
+ }
61899
+ function fromProgram(p) {
61900
+ return catalogItem({
61901
+ source: "onlineProgram",
61902
+ id: p.id,
61903
+ name: p.description?.title || p.id,
61904
+ description: p.description?.details,
61905
+ kind: "digital",
61906
+ price: priceOf(p.price?.value, p.price?.currency),
61907
+ hidden: p.status && p.status !== "PUBLISHED" || void 0
61908
+ });
61909
+ }
61910
+ async function readPrograms(wix, notes) {
61911
+ let listed;
61912
+ try {
61913
+ listed = await allByCursor(wix, "/online-programs/v3/programs/query", "programs");
61914
+ } catch (e) {
61915
+ if (isNotInstalled2(e)) return notInstalled(notes, "Online Programs");
61916
+ throw e;
61917
+ }
61918
+ const full = await mapLimit(
61919
+ listed,
61920
+ PROGRAM_READ_CONCURRENCY,
61921
+ (p) => withRetry(`program ${p.id}`, () => wix("GET", `/online-programs/v3/programs/${encodeURIComponent(p.id)}`))
61922
+ );
61923
+ return full.map((res) => fromProgram(res.program ?? {}));
61924
+ }
61925
+ function planKind(pricing) {
61926
+ if (pricing?.singlePaymentUnlimited) return "digital";
61927
+ return pricing?.subscription || pricing?.singlePaymentForDuration ? "subscription" : "unknown";
61928
+ }
61929
+ function fromPlan(p) {
61930
+ return catalogItem({
61931
+ source: "pricingPlan",
61932
+ id: p.id,
61933
+ name: p.name || p.id,
61934
+ description: p.description,
61935
+ kind: planKind(p.pricing),
61936
+ price: priceOf(p.pricing?.price?.value, p.pricing?.price?.currency),
61937
+ hidden: p.archived === true || p.public === false || void 0
61938
+ });
61939
+ }
61940
+ async function readPlans(wix, notes) {
61941
+ try {
61942
+ const rows = await allByOffset(async (offset) => {
61943
+ const res = await wix("GET", `/pricing-plans/v2/plans?limit=${PAGE2}&offset=${offset}`);
61944
+ return { rows: res.plans ?? [], total: res.pagingMetadata?.total };
61945
+ }, "/pricing-plans/v2/plans");
61946
+ return rows.map(fromPlan);
61947
+ } catch (e) {
61948
+ if (isNotInstalled2(e)) return notInstalled(notes, "Pricing Plans");
61949
+ throw e;
61950
+ }
61951
+ }
61952
+ async function readWixCatalog(wix) {
61953
+ const notes = [];
61954
+ const items = [...await readStores(wix, notes), ...await readPrograms(wix, notes), ...await readPlans(wix, notes)];
61955
+ notes.push(NEVER_DRAFTED_NOTE);
61956
+ return { items, notes };
61957
+ }
61958
+ var PAGE2, MAX_PAGES2, PROGRAM_READ_CONCURRENCY, NEVER_DRAFTED_NOTE, isNotInstalled2, isWrongCatalogVersion;
61959
+ var init_store_iap_catalog = __esm({
61960
+ "src/store-iap-catalog.ts"() {
61961
+ init_wix_owner();
61962
+ PAGE2 = 100;
61963
+ MAX_PAGES2 = 500;
61964
+ PROGRAM_READ_CONCURRENCY = 4;
61965
+ NEVER_DRAFTED_NOTE = "Bookings services and events are real-world services, which Apple does not allow as in-app purchases \u2014 they are never drafted.";
61966
+ isNotInstalled2 = (e) => e instanceof WixApiError && e.isAppNotInstalled;
61967
+ isWrongCatalogVersion = (e) => e instanceof WixApiError && e.status === 428 && /CATALOG_V1/.test(e.message);
61968
+ }
61969
+ });
61970
+
61268
61971
  // src/store-iap.ts
61269
61972
  var store_iap_exports = {};
61270
61973
  __export(store_iap_exports, {
@@ -61275,7 +61978,7 @@ __export(store_iap_exports, {
61275
61978
  runStoreIap: () => runStoreIap
61276
61979
  });
61277
61980
  import { randomUUID as randomUUID5 } from "node:crypto";
61278
- import { mkdirSync as mkdirSync32, readFileSync as readFileSync77, rmSync as rmSync16, writeFileSync as writeFileSync26 } from "node:fs";
61981
+ import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync32, readFileSync as readFileSync77, rmSync as rmSync16, writeFileSync as writeFileSync26 } from "node:fs";
61279
61982
  import { homedir as homedir7, hostname } from "node:os";
61280
61983
  import { join as join65 } from "node:path";
61281
61984
  function scrubDeep(value, scrub) {
@@ -61346,9 +62049,27 @@ function printItemPlan(out2, item) {
61346
62049
  for (const note of item.notes) out2.line(` note: ${note}`);
61347
62050
  for (const conflict of item.conflicts) out2.line(` \u2717 conflict: ${conflict}`);
61348
62051
  }
62052
+ function countsLine(values, order) {
62053
+ return order.map((key2) => [key2, values.filter((v) => v === key2).length]).filter(([, n]) => n > 0).map(([key2, n]) => `${key2} ${n}`).join(" \xB7 ");
62054
+ }
62055
+ function priceIncreases(plan) {
62056
+ return plan.items.flatMap((item, i) => {
62057
+ const requested = plan.manifest.items[i].price;
62058
+ if (!item.applePrice || normalizeAmount(item.applePrice) === normalizeAmount(requested.amount)) return [];
62059
+ return [`${item.productId} ${requested.amount} \u2192 ${item.applePrice} ${requested.currency}`];
62060
+ });
62061
+ }
61349
62062
  function printPlanItems(out2, plan) {
61350
62063
  printHeader(out2, plan.identity, plan.target);
61351
- for (const item of plan.items) printItemPlan(out2, item);
62064
+ if (plan.items.length <= SUMMARY_THRESHOLD) {
62065
+ for (const item of plan.items) printItemPlan(out2, item);
62066
+ } else {
62067
+ out2.line(` ${plan.items.length} products: ${countsLine(plan.items.map((i) => i.decision), DECISION_ORDER)}`);
62068
+ const increases = priceIncreases(plan);
62069
+ if (increases.length) out2.line(` price: ${increases.length} take a higher Apple price point than requested, e.g. ${increases.slice(0, 3).join(" \xB7 ")}`);
62070
+ for (const item of plan.items.filter((i) => i.decision === "conflict").slice(0, SUMMARY_DETAIL_LIMIT)) printItemPlan(out2, item);
62071
+ out2.line(" (every product is listed in the plan file)");
62072
+ }
61352
62073
  out2.line();
61353
62074
  }
61354
62075
  function printPlan(out2, plan, planPath) {
@@ -61384,14 +62105,22 @@ function printItemResult(out2, item) {
61384
62105
  }
61385
62106
  function printResult(out2, result, resultPath) {
61386
62107
  printHeader(out2, result.identity, result.target);
61387
- for (const item of result.items) printItemResult(out2, item);
62108
+ if (result.items.length <= SUMMARY_THRESHOLD) {
62109
+ for (const item of result.items) printItemResult(out2, item);
62110
+ } else {
62111
+ out2.line(` ${result.items.length} products: ${countsLine(result.items.map((i) => i.provisioning.outcome), OUTCOME_ORDER)}`);
62112
+ const unfinished = result.items.filter((i) => !COMPLETE.has(i.provisioning.outcome));
62113
+ for (const item of unfinished.slice(0, SUMMARY_DETAIL_LIMIT)) printItemResult(out2, item);
62114
+ if (unfinished.length > SUMMARY_DETAIL_LIMIT) out2.line(` \u2026and ${unfinished.length - SUMMARY_DETAIL_LIMIT} more not complete (see the result file)`);
62115
+ }
61388
62116
  out2.line();
61389
62117
  if (resultPath) out2.line(` result written to ${resultPath}`);
61390
62118
  out2.line(" backend sync: not configured \xB7 checkout: not active \u2014 the result is a mapping candidate, not an activation.");
61391
62119
  }
61392
- function applyQuestion(plan) {
62120
+ function applyQuestion(plan, limit) {
61393
62121
  const operations = plan.items.reduce((n, item) => n + item.ops.length, 0);
61394
- return ` As profile "${plan.identity.profile}" (team ${plan.identity.team}), apply ${operations} operation(s) to App Store Connect app ${plan.target.appleAppId} (${plan.target.bundleId})?`;
62122
+ const cap = limit === void 0 ? "" : `, creating at most ${limit} new product(s) in this run`;
62123
+ return ` As profile "${plan.identity.profile}" (team ${plan.identity.team}), apply ${operations} planned operation(s) to App Store Connect app ${plan.target.appleAppId} (${plan.target.bundleId})${cap}?`;
61395
62124
  }
61396
62125
  async function planCommand(flags, deps, out2) {
61397
62126
  const manifestPath = requireFlag(flags, "manifest", "plan");
@@ -61406,10 +62135,17 @@ async function planCommand(flags, deps, out2) {
61406
62135
  else printPlan(out2, plan, planPath);
61407
62136
  return plan.items.some((i) => i.decision === "conflict") ? 1 : 0;
61408
62137
  }
61409
- async function applyWithLock(deps, plan, session) {
62138
+ function createLimit(flags) {
62139
+ const value = textFlag(flags, "limit");
62140
+ if (value === void 0) return void 0;
62141
+ if (!/^[1-9]\d*$/.test(value)) throw new UsageError(`--limit must be a positive whole number (got ${JSON.stringify(value)})`);
62142
+ return Number(value);
62143
+ }
62144
+ async function applyWithLock(deps, plan, session, limit) {
61410
62145
  const release = deps.acquireLock(plan.target.appleAppId);
61411
62146
  try {
61412
- return await applyIap(plan, { api: session.api, identity: session.identity, now: deps.now, sleep: deps.sleep, journal: session.journal });
62147
+ const context = { api: session.api, identity: session.identity, now: deps.now, sleep: deps.sleep, journal: session.journal };
62148
+ return await applyIap(plan, limit === void 0 ? context : { ...context, createLimit: limit });
61413
62149
  } finally {
61414
62150
  release();
61415
62151
  }
@@ -61418,6 +62154,7 @@ async function applyCommand(flags, deps, out2) {
61418
62154
  const planPath = requireFlag(flags, "plan", "apply");
61419
62155
  const profile = requireFlag(flags, "profile", "apply");
61420
62156
  const resultPath = requireFlag(flags, "out", "apply");
62157
+ const limit = createLimit(flags);
61421
62158
  const confirmed = flags.yes === true;
61422
62159
  const canAsk = out2.interactive && !out2.json && deps.canPrompt();
61423
62160
  if (!confirmed && !canAsk) {
@@ -61429,11 +62166,11 @@ async function applyCommand(flags, deps, out2) {
61429
62166
  const plan = readPlan(deps, planPath);
61430
62167
  const session = openSession(deps, out2, plan.target, profile);
61431
62168
  if (!out2.json) printApplyPreview(out2, plan, planPath);
61432
- if (!confirmed && !await deps.confirm(applyQuestion(plan))) {
62169
+ if (!confirmed && !await deps.confirm(applyQuestion(plan, limit))) {
61433
62170
  out2.line(" aborted \u2014 nothing was changed in App Store Connect.");
61434
62171
  return 1;
61435
62172
  }
61436
- const result = await applyWithLock(deps, plan, session);
62173
+ const result = await applyWithLock(deps, plan, session, limit);
61437
62174
  writeJson(deps, out2, resultPath, result);
61438
62175
  if (out2.json) out2.print({ ok: result.complete, result: resultPath, items: result.items });
61439
62176
  else printResult(out2, result, resultPath);
@@ -61451,6 +62188,104 @@ async function statusCommand(flags, deps, out2) {
61451
62188
  else printResult(out2, result, resultPath);
61452
62189
  return result.complete ? 0 : 1;
61453
62190
  }
62191
+ function draftMode(flags) {
62192
+ const value = textFlag(flags, "mode") ?? "per-item";
62193
+ const mode = DRAFT_MODES[value];
62194
+ if (!mode) throw new UsageError(`--mode must be per-item or tiers (got ${JSON.stringify(value)})`);
62195
+ return mode;
62196
+ }
62197
+ function territoriesFlag(flags, base) {
62198
+ const territories = (textFlag(flags, "territories") ?? base).split(",").map((t) => t.trim().toUpperCase()).filter(Boolean);
62199
+ const bad2 = territories.find((t) => !TERRITORY_CODE.test(t));
62200
+ if (bad2) throw new UsageError(`--territories must be ISO 3166-1 alpha-3 codes such as USA (got ${JSON.stringify(bad2)})`);
62201
+ const duplicate = territories.find((t, i) => territories.indexOf(t) !== i);
62202
+ if (duplicate) throw new UsageError(`--territories: duplicate territory ${duplicate}`);
62203
+ if (!territories.includes(base)) throw new UsageError(`--territories must include the base territory ${base}`);
62204
+ return territories;
62205
+ }
62206
+ function localeFlag(flags) {
62207
+ const locale = textFlag(flags, "locale") ?? "en-US";
62208
+ if (!LOCALE_CODE.test(locale)) throw new UsageError(`--locale must be a locale such as en-US (got ${JSON.stringify(locale)})`);
62209
+ return locale;
62210
+ }
62211
+ function baseTerritoryFlag(flags) {
62212
+ const base = requireFlag(flags, "base-territory", "draft").toUpperCase();
62213
+ if (!TERRITORY_CODE.test(base)) throw new UsageError(`--base-territory must be an ISO 3166-1 alpha-3 code such as USA (got ${JSON.stringify(base)})`);
62214
+ return base;
62215
+ }
62216
+ async function applePriceGrid(session, target, baseTerritory) {
62217
+ return withAccessContext(session.identity, target, async () => {
62218
+ const [anyProduct] = await session.api.listProducts(target.appleAppId);
62219
+ return anyProduct ? new PriceGrid(session.api).amountsIn(baseTerritory, anyProduct.id) : void 0;
62220
+ });
62221
+ }
62222
+ function readMergeManifest(deps, path2, target) {
62223
+ if (!path2) return void 0;
62224
+ const existing = readManifest2(deps, path2);
62225
+ if (existing.target.wixSiteId !== target.wixSiteId) {
62226
+ throw new Error(`${path2} is for Wix site ${existing.target.wixSiteId}, not ${target.wixSiteId} \u2014 refusing to carry another site's mappings into this one.`);
62227
+ }
62228
+ if (existing.target.appleAppId !== target.appleAppId || existing.target.bundleId !== target.bundleId) {
62229
+ throw new Error(`${path2} is for app ${existing.target.appleAppId} (${existing.target.bundleId}), not ${target.appleAppId} (${target.bundleId}) \u2014 refusing to merge another app's mapping.`);
62230
+ }
62231
+ return existing;
62232
+ }
62233
+ async function baseCurrency(session, target, baseTerritory) {
62234
+ const territories = await withAccessContext(session.identity, target, () => session.api.listTerritories());
62235
+ const currency = territories.find((t) => t.id === baseTerritory)?.currency;
62236
+ if (!currency) throw new Error(`App Store Connect has no territory ${baseTerritory} \u2014 pass an ISO 3166-1 alpha-3 code such as USA or DEU.`);
62237
+ return currency;
62238
+ }
62239
+ function skippedSummary(draft) {
62240
+ const counts = /* @__PURE__ */ new Map();
62241
+ for (const s of draft.skipped) counts.set(s.reason, (counts.get(s.reason) ?? 0) + 1);
62242
+ return [...counts].map(([reason, n]) => `${reason} \xD7${n}`).join(" \xB7 ");
62243
+ }
62244
+ function printDraft(out2, catalog, draft, args) {
62245
+ out2.line(` ${pad("catalog")}${catalog.items.length} Wix item(s) read`);
62246
+ for (const note of args.notes) out2.line(` ${pad("")}${note}`);
62247
+ const kept = args.mergePath ? ` \xB7 kept ${draft.kept} from ${args.mergePath}` : "";
62248
+ out2.line(` ${pad("drafted")}${args.drafted} new product(s) for ${draft.newlyMapped} Wix item(s)${kept}`);
62249
+ if (draft.skipped.length) out2.line(` ${pad("skipped")}${draft.skipped.length} \u2014 ${skippedSummary(draft)}`);
62250
+ out2.line();
62251
+ if (!draft.manifest) {
62252
+ out2.line(" nothing in the catalog can be sold through in-app purchase \u2014 no manifest written.");
62253
+ return;
62254
+ }
62255
+ out2.line(` manifest written to ${args.outPath} \u2014 review or trim it if you like; nothing was changed in App Store Connect.`);
62256
+ out2.line(` next: himi store iap plan --manifest ${args.outPath} --profile ${args.profile} --out ./iap-plan.json`);
62257
+ }
62258
+ async function draftCommand(flags, deps, out2) {
62259
+ const site = requireFlag(flags, "site", "draft");
62260
+ const profile = requireFlag(flags, "profile", "draft");
62261
+ const appleAppId = requireFlag(flags, "apple-app", "draft");
62262
+ const bundleId = requireFlag(flags, "bundle", "draft");
62263
+ const baseTerritory = baseTerritoryFlag(flags);
62264
+ const outPath = requireFlag(flags, "out", "draft");
62265
+ const mode = draftMode(flags);
62266
+ const territories = territoriesFlag(flags, baseTerritory);
62267
+ const locale = localeFlag(flags);
62268
+ const mergePath = textFlag(flags, "merge");
62269
+ const target = { himiApp: textFlag(flags, "himi-app") ?? bundleId, wixSiteId: site, appleAppId, bundleId, teamId: deps.profileTeam(profile), profile };
62270
+ const existing = readMergeManifest(deps, mergePath, target);
62271
+ const session = openSession(deps, out2, target, profile);
62272
+ const currency = await baseCurrency(session, target, baseTerritory);
62273
+ const applePrices = await applePriceGrid(session, target, baseTerritory);
62274
+ const catalog = await readWixCatalog(deps.wixTransport(site));
62275
+ const notes = mode === "tiers" && !applePrices ? [...catalog.notes, GRID_UNAVAILABLE_NOTE] : catalog.notes;
62276
+ const draft = draftManifest(catalog.items, { target, mode, baseTerritory, currency, territories, locale, existing, ...applePrices ? { applePrices } : {} });
62277
+ const drafted = (draft.manifest?.items.length ?? 0) - (existing?.items.length ?? 0);
62278
+ if (draft.manifest) writeJson(deps, out2, outPath, draft.manifest);
62279
+ const report3 = { ok: draft.manifest !== void 0, manifest: draft.manifest ? outPath : null, mode: textFlag(flags, "mode") ?? "per-item", drafted, newlyMapped: draft.newlyMapped, kept: draft.kept, skipped: draft.skipped, notes };
62280
+ const reportPath = textFlag(flags, "report");
62281
+ if (reportPath) writeJson(deps, out2, reportPath, report3);
62282
+ if (out2.json) out2.print(report3);
62283
+ else {
62284
+ printHeader(out2, session.identity, target);
62285
+ printDraft(out2, catalog, draft, { drafted, outPath, profile, mergePath, notes });
62286
+ }
62287
+ return draft.manifest ? 0 : 1;
62288
+ }
61454
62289
  async function runStoreIap(pos, flags, io, deps) {
61455
62290
  const out2 = new CommandOutput(io, flags.json === true);
61456
62291
  const sub = pos[2];
@@ -61463,18 +62298,10 @@ async function runStoreIap(pos, flags, io, deps) {
61463
62298
  return failFrom(e, out2);
61464
62299
  }
61465
62300
  }
61466
- function readJournalEntries(path2) {
61467
- try {
61468
- const doc = JSON.parse(readFileSync77(path2, "utf8"));
61469
- return Array.isArray(doc?.entries) ? doc.entries : [];
61470
- } catch {
61471
- return [];
61472
- }
61473
- }
61474
62301
  function fileJournal(path2) {
61475
62302
  return {
61476
- record: (entry) => writeFileAtomic(path2, `${JSON.stringify({ journalVersion: 1, entries: [...readJournalEntries(path2), entry] }, null, 2)}
61477
- `)
62303
+ record: (entry) => appendFileSync2(path2, `${JSON.stringify(entry)}
62304
+ `, { mode: 384 })
61478
62305
  };
61479
62306
  }
61480
62307
  function readLockSummary(path2) {
@@ -61527,26 +62354,35 @@ function defaultStoreIapDeps() {
61527
62354
  writeFileAtomic,
61528
62355
  resolveCredentials: ({ profileFlag, target }) => resolveIapCredentials({ profileFlag, target }),
61529
62356
  createApi: (key2) => createIapApi(createAscClient({ key: key2 })),
61530
- openJournal: (appleAppId) => fileJournal(join65(stateDir(appleAppId), "journal.json")),
62357
+ openJournal: (appleAppId) => fileJournal(join65(stateDir(appleAppId), "journal.jsonl")),
61531
62358
  acquireLock: (appleAppId) => acquireApplyLock(stateDir(appleAppId), appleAppId),
61532
62359
  canPrompt: () => process.stdin.isTTY === true,
62360
+ wixTransport: (siteId) => ownerTransport(siteId),
62361
+ profileTeam: (profile) => resolveStoreCredentials({ app: profile, cfg: {}, profileFlag: profile, env: {} }).team,
61533
62362
  confirm: confirmOnTerminal,
61534
62363
  now: () => /* @__PURE__ */ new Date(),
61535
62364
  sleep: (ms) => new Promise((resolve56) => setTimeout(resolve56, ms))
61536
62365
  };
61537
62366
  }
61538
- var STORE_IAP_USAGE, UsageError, CommandOutput, pad, COMPLETE, COMMANDS;
62367
+ var STORE_IAP_USAGE, SUMMARY_THRESHOLD, SUMMARY_DETAIL_LIMIT, UsageError, CommandOutput, pad, DECISION_ORDER, OUTCOME_ORDER, COMPLETE, DRAFT_MODES, TERRITORY_CODE, LOCALE_CODE, GRID_UNAVAILABLE_NOTE, COMMANDS;
61539
62368
  var init_store_iap = __esm({
61540
62369
  "src/store-iap.ts"() {
61541
62370
  init_iap_api();
61542
62371
  init_iap_apply();
61543
62372
  init_iap_client();
62373
+ init_credential_profile();
61544
62374
  init_iap_credentials();
62375
+ init_iap_draft();
61545
62376
  init_iap_manifest();
62377
+ init_iap_pricing();
61546
62378
  init_iap_plan();
61547
62379
  init_redact();
61548
62380
  init_progress();
61549
- STORE_IAP_USAGE = "himi store iap plan|apply|status \u2014 plan --manifest <file> --profile <name> --out <plan.json> \xB7 apply --plan <plan.json> --profile <name> --out <result.json> [--yes] \xB7 status --manifest <file> --profile <name> [--out <file>]";
62381
+ init_store_iap_catalog();
62382
+ init_wix_owner();
62383
+ STORE_IAP_USAGE = "himi store iap draft|plan|apply|status \u2014 draft --site <msid> --profile <name> --apple-app <id> --bundle <id> --base-territory <ISO3> --out <manifest.json> [--territories <a,b>] [--locale <l>] [--mode per-item|tiers] [--merge <manifest.json>] [--report <file>] \xB7 plan --manifest <file> --profile <name> --out <plan.json> \xB7 apply --plan <plan.json> --profile <name> --out <result.json> [--yes] [--limit <n>] \xB7 status --manifest <file> --profile <name> [--out <file>]";
62384
+ SUMMARY_THRESHOLD = 50;
62385
+ SUMMARY_DETAIL_LIMIT = 20;
61550
62386
  UsageError = class extends Error {
61551
62387
  };
61552
62388
  CommandOutput = class {
@@ -61585,8 +62421,15 @@ var init_store_iap = __esm({
61585
62421
  }
61586
62422
  };
61587
62423
  pad = (label2, width = 12) => label2.padEnd(width);
62424
+ DECISION_ORDER = ["create", "update", "reuse", "conflict"];
62425
+ OUTCOME_ORDER = ["created", "updated", "reused", "conflict", "partial", "failed", "deferred", "absent", "incomplete"];
61588
62426
  COMPLETE = /* @__PURE__ */ new Set(["created", "updated", "reused"]);
62427
+ DRAFT_MODES = { "per-item": "perItem", tiers: "tiers" };
62428
+ TERRITORY_CODE = /^[A-Z]{3}$/;
62429
+ LOCALE_CODE = /^[a-z]{2,3}(-[A-Za-z0-9]{2,8})*$/;
62430
+ GRID_UNAVAILABLE_NOTE = "the app has no in-app purchase yet to read Apple's price grid through, so tiers are keyed by Wix price \u2014 prices that resolve to the same Apple point get separate tiers; redraft after the first product exists to merge them";
61589
62431
  COMMANDS = {
62432
+ draft: draftCommand,
61590
62433
  plan: planCommand,
61591
62434
  apply: applyCommand,
61592
62435
  status: statusCommand
@@ -61971,7 +62814,7 @@ __export(preflight_app_exports, {
61971
62814
  profileAccount: () => profileAccount
61972
62815
  });
61973
62816
  import { readFileSync as readFileSync78, existsSync as existsSync67, readdirSync as readdirSync31, statSync as statSync18 } from "node:fs";
61974
- import { createHash as createHash33 } from "node:crypto";
62817
+ import { createHash as createHash34 } from "node:crypto";
61975
62818
  import { join as join66 } from "node:path";
61976
62819
  function analyze(projectYml, storeConfig, icon, account) {
61977
62820
  const f = [];
@@ -62260,7 +63103,7 @@ function placeholderHashes2() {
62260
63103
  const p = join66(templateIos, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
62261
63104
  try {
62262
63105
  if (!statSync18(p).isFile()) continue;
62263
- return [LEGACY_PLACEHOLDER_SHA2562, createHash33("sha256").update(readFileSync78(p)).digest("hex")];
63106
+ return [LEGACY_PLACEHOLDER_SHA2562, createHash34("sha256").update(readFileSync78(p)).digest("hex")];
62264
63107
  } catch {
62265
63108
  }
62266
63109
  }
@@ -62297,7 +63140,7 @@ async function appIconState(iosDir) {
62297
63140
  try {
62298
63141
  if (!statSync18(p).isFile()) continue;
62299
63142
  const bytes2 = readFileSync78(p);
62300
- const hash = createHash33("sha256").update(bytes2).digest("hex");
63143
+ const hash = createHash34("sha256").update(bytes2).digest("hex");
62301
63144
  const pixels = await pixelIssues(bytes2);
62302
63145
  return {
62303
63146
  exists: true,
@@ -62870,7 +63713,7 @@ var init_site_member = __esm({
62870
63713
  });
62871
63714
 
62872
63715
  // src/notify-ping-all.ts
62873
- import { createHash as createHash34 } from "node:crypto";
63716
+ import { createHash as createHash35 } from "node:crypto";
62874
63717
  async function listApprovedMembers(input) {
62875
63718
  const first = await scanMembers(input);
62876
63719
  const second = await scanMembers(input);
@@ -62924,7 +63767,7 @@ async function scanMembers(input) {
62924
63767
  }
62925
63768
  }
62926
63769
  function campaignRequestId(campaignId, memberId) {
62927
- const bytes2 = createHash34("sha256").update(JSON.stringify(["himi-push-campaign", campaignId, memberId])).digest().subarray(0, 16);
63770
+ const bytes2 = createHash35("sha256").update(JSON.stringify(["himi-push-campaign", campaignId, memberId])).digest().subarray(0, 16);
62928
63771
  bytes2[6] = bytes2[6] & 15 | 80;
62929
63772
  bytes2[8] = bytes2[8] & 63 | 128;
62930
63773
  const hex2 = bytes2.toString("hex");
@@ -64084,7 +64927,7 @@ var init_copy_clean = __esm({
64084
64927
 
64085
64928
  // src/site-analyze/published-html.ts
64086
64929
  function textFromHtml(fragment) {
64087
- return fragment.replace(/<[^>]*>/g, " ").replace(/&(amp|lt|gt|quot|#39|apos|nbsp);/g, (e) => ENTITIES[e] ?? " ").replace(/&#x?[0-9a-f]+;/gi, " ");
64930
+ return fragment.replace(/<[^>]*>/g, " ").replace(/&(amp|lt|gt|quot|#39|apos|nbsp);/g, (e) => ENTITIES2[e] ?? " ").replace(/&#x?[0-9a-f]+;/gi, " ");
64088
64931
  }
64089
64932
  function unescapeJsonString(s) {
64090
64933
  return s.replace(/\\"/g, '"').replace(/\\n/g, " ").replace(/\\\\/g, "\\");
@@ -64223,7 +65066,7 @@ function appsFromDefIds(ids) {
64223
65066
  return name ? { id, name } : { id };
64224
65067
  });
64225
65068
  }
64226
- var GRID_APP_ID_RE, MAIN_PAGE_RE, APP_DEF_RE, PAGE_ENTRY_RE, OG_IMAGE_RE, ICON_RE, HTML_LANG_RE, META_DESC_RE, OG_TITLE_RE, OG_DESC_RE, HEADING_RE, SKIP_SLUG, POPUP, WELL_KNOWN_WIX_APPS, ENTITIES;
65069
+ var GRID_APP_ID_RE, MAIN_PAGE_RE, APP_DEF_RE, PAGE_ENTRY_RE, OG_IMAGE_RE, ICON_RE, HTML_LANG_RE, META_DESC_RE, OG_TITLE_RE, OG_DESC_RE, HEADING_RE, SKIP_SLUG, POPUP, WELL_KNOWN_WIX_APPS, ENTITIES2;
64227
65070
  var init_published_html = __esm({
64228
65071
  "src/site-analyze/published-html.ts"() {
64229
65072
  init_rest();
@@ -64254,7 +65097,7 @@ var init_published_html = __esm({
64254
65097
  "14cc59bc-f0b7-15b8-e1c7-89ce41d0e0c9": "Wix Bookings Service Pages",
64255
65098
  "215238eb-22a5-4c36-9e7b-e7c08025e04e": "Wix Forms"
64256
65099
  };
64257
- ENTITIES = {
65100
+ ENTITIES2 = {
64258
65101
  "&amp;": "&",
64259
65102
  "&lt;": "<",
64260
65103
  "&gt;": ">",
@@ -73098,10 +73941,10 @@ async function installVerticals(msid, wanted, token, fetchImpl = fetch, timeoutM
73098
73941
  body = text8 ? JSON.parse(text8) : {};
73099
73942
  } catch {
73100
73943
  }
73101
- const nameOf = (appDefId) => wanted.find((w) => w.appDefId === appDefId)?.vertical ?? appDefId;
73944
+ const nameOf2 = (appDefId) => wanted.find((w) => w.appDefId === appDefId)?.vertical ?? appDefId;
73102
73945
  const out2 = { installed: [], errors: [], needsSetup: [] };
73103
73946
  for (const [appDefId, info2] of Object.entries(body.installedApps ?? {})) {
73104
- const vertical = nameOf(appDefId);
73947
+ const vertical = nameOf2(appDefId);
73105
73948
  out2.installed.push({ vertical, appDefId, version: info2?.version });
73106
73949
  const actions = (info2?.requiredActions ?? []).filter((a) => a?.actionType || a?.link);
73107
73950
  for (const a of actions) {
@@ -73114,7 +73957,7 @@ async function installVerticals(msid, wanted, token, fetchImpl = fetch, timeoutM
73114
73957
  }
73115
73958
  }
73116
73959
  for (const [appDefId, err] of Object.entries(body.installErrors ?? {})) {
73117
- out2.errors.push({ vertical: nameOf(appDefId), appDefId, message: err?.message ?? "unknown" });
73960
+ out2.errors.push({ vertical: nameOf2(appDefId), appDefId, message: err?.message ?? "unknown" });
73118
73961
  }
73119
73962
  return out2;
73120
73963
  }
@@ -73981,7 +74824,7 @@ var init_context = __esm({
73981
74824
  import { mkdtempSync as mkdtempSync5, readFileSync as readFileSync88, mkdirSync as mkdirSync39, writeFileSync as writeFileSync35, existsSync as existsSync72, rmSync as rmSync20, readdirSync as readdirSync32, statSync as statSync20, lstatSync as lstatSync16, accessSync, constants as constants3, renameSync as renameSync9, cpSync as cpSync3 } from "node:fs";
73982
74825
  import { execFile as execFile2, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn7 } from "node:child_process";
73983
74826
  import { AsyncLocalStorage } from "node:async_hooks";
73984
- import { createHash as createHash35, randomUUID as randomUUID8 } from "node:crypto";
74827
+ import { createHash as createHash36, randomUUID as randomUUID8 } from "node:crypto";
73985
74828
  import { tmpdir as tmpdir6, homedir as homedir10, hostname as hostname2 } from "node:os";
73986
74829
  import { join as join73, resolve as resolve55, relative as relative14, basename as basename17, dirname as dirname40 } from "node:path";
73987
74830
  import { fileURLToPath as fileURLToPath26 } from "node:url";
@@ -74550,11 +75393,11 @@ async function loadReleaseFromDir(app, dir) {
74550
75393
  const bundles = {};
74551
75394
  const seenSha = /* @__PURE__ */ new Set();
74552
75395
  for (const meta of Object.values(manifest.bundles ?? {})) {
74553
- const sha = meta?.sha256;
74554
- if (!sha || seenSha.has(sha)) continue;
74555
- seenSha.add(sha);
74556
- const b = await store.getBundle(app, sha);
74557
- if (b) bundles[sha] = asBuffer(b).toString("base64");
75396
+ const sha2 = meta?.sha256;
75397
+ if (!sha2 || seenSha.has(sha2)) continue;
75398
+ seenSha.add(sha2);
75399
+ const b = await store.getBundle(app, sha2);
75400
+ if (b) bundles[sha2] = asBuffer(b).toString("base64");
74558
75401
  }
74559
75402
  const tokensBytes = await store.getTokens(app);
74560
75403
  const tokens2 = tokensBytes ? utf8Decode(tokensBytes) : void 0;
@@ -76621,16 +77464,28 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
76621
77464
  team (guideline 4.2.6), declared per-app as
76622
77465
  ios.credentialProfile in store.config.yaml and resolved
76623
77466
  from ~/.himalaya/store-credentials/<name>.json.
77467
+ himi store iap draft --site <msid> --profile <name> --apple-app <id> --bundle <id> --base-territory <ISO3>
77468
+ --out <manifest.json> [--territories <a,b>] [--locale <l>] [--mode per-item|tiers]
77469
+ [--merge <manifest.json>] [--report <file>] [--json]
77470
+ READ-ONLY. Read the site's whole Wix catalog (Stores, Online
77471
+ Programs, Pricing Plans) and write the manifest for you:
77472
+ digital, priced items only (physical, recurring, free and
77473
+ hidden ones are skipped with a reason), stable product ids,
77474
+ prices at the next Apple price point up. --mode tiers makes
77475
+ one consumable per price instead. --merge keeps an earlier
77476
+ manifest's mappings (their product ids are permanent).
76624
77477
  himi store iap plan --manifest <file> --profile <name> --out <plan.json> [--json]
76625
77478
  READ-ONLY. Verify the owner's App Store Connect app and
76626
77479
  decide which selected digital products (one-time
76627
77480
  non-consumables) to register; writes a reviewable plan.
76628
- himi store iap apply --plan <plan.json> --profile <name> --out <result.json> [--yes] [--json]
77481
+ himi store iap apply --plan <plan.json> --profile <name> --out <result.json> [--yes] [--limit <n>] [--json]
76629
77482
  create the planned products with draft metadata, an
76630
77483
  exact price and explicit territories. Re-reads Apple
76631
77484
  first, never edits a live product, never duplicates on
76632
77485
  a rerun; prompts unless --yes. Writes the Wix\u2192Apple
76633
77486
  mapping \u2014 backend sync not configured, checkout off.
77487
+ --limit <n> creates at most n new products per run (big
77488
+ catalogs go in batches; a rerun continues).
76634
77489
  himi store iap status --manifest <file> --profile <name> [--out <file>] [--json]
76635
77490
  READ-ONLY: what App Store Connect has for each product.
76636
77491
  No build, signing or repo checkout. Guide: himi docs iap-products
@@ -76916,7 +77771,7 @@ var COMMANDS2 = [
76916
77771
  { name: "visibility", summary: "read (no value) or set whether the public registry (/v1/apps + the Himi Player picker) LISTS the app. `public` lists it for everyone; `unlisted` hides it from listings while direct reads by app id \u2014 deep links, previews \u2014 keep working. App from --app or the content dir. Setting it needs no republish.", required: "[public|unlisted] [--app <a>] [--content <dir>] (--control-token)" },
76917
77772
  { name: "dist", summary: "build an INSTALLABLE QA binary (signed IPA / APK) on the Wix Job Service Mac pool and return an install link \u2014 the serve plane brokers the job, the pool builds it. WAITS for the build by default (~4 min) and prints the install link; `--no-wait` returns a jobId immediately instead. Omitting `--release` builds whatever is LIVE for the app. A named draft is refused unless `--allow-draft` makes that exceptional build explicit. `status <jobId>` / `watch <jobId>` / `list --app <a>` / `cancel <jobId>` inspect and stop builds (`watch` reports up front whether the job mints its own signing identity or borrows one, so a doomed iOS build is cancellable in seconds rather than after the archive). Off-repo, name a target \u2014 `--target wix` for a self-serve Wix login or `--target bo` for the internal employee path (the in-repo default is a local dev plane that isn't reachable off-repo); self-serve callers may build their OWN apps under a tighter budget (2 in flight / 5 per 24h), while a manage-only owner-role credential is refused. `--shell` picks the native host to sign into (branded-lite | owner-lite | brandy | player | demo-app); `brandy` is the site shell \u2014 the customer's own website as native-framed tabs. `--signing` defaults to `wix-managed` \u2014 the Wix Apple account mints a per-app signing identity for the build, so the caller provisions no Apple credentials of their own; `--signing byo --site <id>` reuses an existing customer ad-hoc profile and certificate for iOS; requires an owned site and a completed personal signing binding. `--android-signing` is the Android counterpart and a SEPARATE axis (a keystore, not an Apple account): `qa-keystore` (default \u2014 the repo's committed QA key, identical on every pool machine, so a build installs over the previous one), `wix-keystore` (the Wix key the pool machine carries; the build FAILS naming the paths searched if it carries none, rather than quietly signing with something else), or `debug` (the old per-machine key \u2014 QA must uninstall any previous build first). NOT the normal way to ship: screens, Tier-5 bundles, tokens and app-config all go over the air with `himi push` in seconds \u2014 a binary trip is only needed for a NATIVE delta (a new SDUI component, a new declared capability/entitlement, a new bundle id, or a minBinaryVersion floor bump). Refuses to build an app whose launcher icon is missing, a placeholder, or one a store would reject \u2014 the one thing a binary carries that no later OTA push can fix; `--force` builds anyway", required: "[--app <a>] [--platform ios,android] [--release <id>] [--allow-draft] [--target <t>] [--shell branded-lite|owner-lite|brandy|player|demo-app] [--signing wix-managed|byo] [--site <id>] [--android-signing qa-keystore|wix-keystore|debug] [--borrow-ios-id <bundleId>] [--no-wait] [--force] | status <jobId> [--target <t>] | watch <jobId> [--target <t>] [--interval <ms>] | list --app <a> [--target <t>] | cancel <jobId> [--target <t>]" },
76918
77773
  { name: "testflight", summary: 'build, sign and upload an app to TestFlight (internal testers \u2014 no Beta App Review), then WAIT until App Store Connect finishes processing it. Sugar for `store upload --track testflight`. Prompts first: a build number is consumed permanently on a real Apple account and cannot be reused \u2014 `--yes` for CI, `--dry-run` to see the plan without touching anything. The wait is the real check, not a nicety: fastlane prints "Successfully uploaded" for a binary App Store Connect then rejects in processing, and a rejected binary leaves NO build record at all (the reason is emailed). Public App Store review is deliberately NOT automatable here \u2014 `--track appstore` is refused; use /submit-ios-app when you mean to go public', required: "<app> [--track testflight|internal] [--profile <name>] [--yes] [--dry-run] [--skip-build] [--no-wait] [--json]" },
76919
- { name: "store", summary: "the App Store Connect group behind `testflight`. `preflight <app>` checks readiness offline (icon, entitlements, encryption flag, version, store.config ids, listing + review record); `upload <app>` is what `testflight` runs; `status <app>` reads live build + processing state. `--profile` selects WHICH App Store Connect account: an app built for a Wix customer ships under the CUSTOMER's Apple team (Apple guideline 4.2.6 forbids an app-generation service submitting on a client's behalf), declared per-app as ios.credentialProfile in store.config.yaml and resolved from ~/.himalaya/store-credentials/<name>.json \u2014 the .p8 itself never enters the repo. `iap plan|apply|status` registers an app owner's selected digital products (one-time non-consumables) as in-app purchases from a manifest: plan and status only read, apply is the one mutation boundary, and none of them builds, signs or needs a checkout (`himi docs iap-products`)", required: "preflight <app> [--profile <n>] | upload <app> [--track <t>] [--profile <n>] [--yes] [--dry-run] | status <app> [--profile <n>] | iap plan --manifest <f> --profile <n> --out <f> [--json] | iap apply --plan <f> --profile <n> --out <f> [--yes] [--json] | iap status --manifest <f> --profile <n> [--out <f>] [--json]" },
77774
+ { name: "store", summary: "the App Store Connect group behind `testflight`. `preflight <app>` checks readiness offline (icon, entitlements, encryption flag, version, store.config ids, listing + review record); `upload <app>` is what `testflight` runs; `status <app>` reads live build + processing state. `--profile` selects WHICH App Store Connect account: an app built for a Wix customer ships under the CUSTOMER's Apple team (Apple guideline 4.2.6 forbids an app-generation service submitting on a client's behalf), declared per-app as ios.credentialProfile in store.config.yaml and resolved from ~/.himalaya/store-credentials/<name>.json \u2014 the .p8 itself never enters the repo. `iap draft|plan|apply|status` registers an app owner's digital products as in-app purchases (non-consumables, or consumable price tiers): draft reads the Wix catalog and writes the manifest, plan and status only read, apply is the one mutation boundary, and none of them builds, signs or needs a checkout (`himi docs iap-products`)", required: "preflight <app> [--profile <n>] | upload <app> [--track <t>] [--profile <n>] [--yes] [--dry-run] | status <app> [--profile <n>] | iap draft --site <msid> --profile <n> --apple-app <id> --bundle <id> --base-territory <t> --out <f> [--territories <a,b>] [--locale <l>] [--mode per-item|tiers] [--merge <f>] [--report <f>] [--json] | iap plan --manifest <f> --profile <n> --out <f> [--json] | iap apply --plan <f> --profile <n> --out <f> [--yes] [--limit <n>] [--json] | iap status --manifest <f> --profile <n> [--out <f>] [--json]" },
76920
77775
  { name: "guide", summary: "print the Himalaya overview + headless dev loop + available topics (offline)", required: "[--json]" },
76921
77776
  { name: "docs", summary: "print an authoring guide; no topic = list them (offline)", required: "[<topic>] [--json]" },
76922
77777
  { name: "components", summary: "print the SDUI component vocabulary + props; a name = that component (offline)", required: "[<Name>] [--json]" },
@@ -78141,7 +78996,7 @@ function templateNameError(name, from) {
78141
78996
  const composed2 = TEMPLATE_APP_PREFIX + name;
78142
78997
  return isValidAppId(composed2) ? null : `template name "${name}" (${from}) makes app id "${composed2}", which is over the serve plane's app-id length limit; pass a shorter --name`;
78143
78998
  }
78144
- function materializeForPreview(appDir3, slug, displayName) {
78999
+ function materializeForPreview(appDir3, slug, displayName2) {
78145
79000
  if (!isTemplateSourceTree(appDir3)) return appDir3;
78146
79001
  const slugError = templateNameError(slug, "preview slug");
78147
79002
  if (slugError) throw Object.assign(new Error(slugError), { exitCode: 2 });
@@ -78173,7 +79028,7 @@ function materializeForPreview(appDir3, slug, displayName) {
78173
79028
  rmSync20(dest, { recursive: true, force: true });
78174
79029
  mkdirSync39(dest, { recursive: true });
78175
79030
  try {
78176
- materializeSource(forBuild, displayName, dest, slug);
79031
+ materializeSource(forBuild, displayName2, dest, slug);
78177
79032
  } catch (e) {
78178
79033
  rmSync20(dest, { recursive: true, force: true });
78179
79034
  throw e;
@@ -79363,7 +80218,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
79363
80218
  try {
79364
80219
  const bytes2 = readFileSync88(iconPath);
79365
80220
  payload.icon = bytes2.toString("base64");
79366
- iconHash = createHash35("sha256").update(bytes2).digest("hex");
80221
+ iconHash = createHash36("sha256").update(bytes2).digest("hex");
79367
80222
  } catch {
79368
80223
  }
79369
80224
  }
@@ -81212,7 +82067,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
81212
82067
  let localSourceHash;
81213
82068
  try {
81214
82069
  const { collectAppSource: collectAppSource2, zipAppSource: zipAppSource2 } = await Promise.resolve().then(() => (init_app_source(), app_source_exports));
81215
- localSourceHash = createHash35("sha256").update(zipAppSource2(collectAppSource2(dir).entries).toString("base64")).digest("hex");
82070
+ localSourceHash = createHash36("sha256").update(zipAppSource2(collectAppSource2(dir).entries).toString("base64")).digest("hex");
81216
82071
  } catch {
81217
82072
  }
81218
82073
  return runReleasePatch({
@@ -81604,9 +82459,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
81604
82459
  return 2;
81605
82460
  }
81606
82461
  const { packRelease: packRelease2, packReleaseToDir: packReleaseToDir2 } = await Promise.resolve().then(() => (init_pack(), pack_exports));
81607
- const displayName = str9(flags.name);
82462
+ const displayName2 = str9(flags.name);
81608
82463
  const launchMode = flags.snapshot === true ? "snapshot" : "live";
81609
- const packOpts = { createdBy: `himi-cli ${cliVersion()}`, launchMode, ...displayName ? { displayName } : {} };
82464
+ const packOpts = { createdBy: `himi-cli ${cliVersion()}`, launchMode, ...displayName2 ? { displayName: displayName2 } : {} };
81610
82465
  const writePack = (root, appName2, out2, source) => format === "dir" ? packReleaseToDir2(root, appName2, out2, packOpts) : packRelease2(root, appName2, out2, { ...packOpts, ...source ? { source } : {} });
81611
82466
  const releaseDir = str9(flags["release-dir"]);
81612
82467
  if (releaseDir) {
@@ -82858,7 +83713,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
82858
83713
  });
82859
83714
  }
82860
83715
  const bytes2 = readFileSync88(file);
82861
- const sha = createHash35("sha256").update(bytes2).digest("hex");
83716
+ const sha2 = createHash36("sha256").update(bytes2).digest("hex");
82862
83717
  const forScreens = (str9(flags["for-screen"]) ?? "").split(",").map((s) => s.trim()).filter(Boolean);
82863
83718
  if (flags["no-rebind"] !== void 0 && flags["no-rebind"] !== true) {
82864
83719
  io.error(JSON.stringify({ ok: false, error: "no_rebind_takes_no_value", message: "--no-rebind is a switch and takes no value; pass it bare.", usage: PATCH_USAGE }));
@@ -82884,7 +83739,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
82884
83739
  ok: false,
82885
83740
  error: "orphan_bundle",
82886
83741
  app,
82887
- sha256: sha,
83742
+ sha256: sha2,
82888
83743
  message: "publishing bundle bytes does not repoint any screen at them: the patch adds the bundle to the manifest and leaves every screen's `bundle` field where it was, so this patch would land bytes that nothing renders.",
82889
83744
  hint: "name the screens to repoint: --for-screen cart,orders. Pass --no-rebind if you really mean to upload bytes nothing references yet.",
82890
83745
  alternative: "`himi push --only bundle:<name>` infers this from the package and drags the referencing screens in for you.",
@@ -82893,9 +83748,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
82893
83748
  return 2;
82894
83749
  }
82895
83750
  const patch = {
82896
- bundles: { [sha]: bytes2.toString("base64") }
83751
+ bundles: { [sha2]: bytes2.toString("base64") }
82897
83752
  };
82898
- const touched = [`bundle:${sha.slice(0, 12)}`];
83753
+ const touched = [`bundle:${sha2.slice(0, 12)}`];
82899
83754
  const rebound = {};
82900
83755
  const bundleBase = await fetchReleaseManifest(client2, app, fromRelease);
82901
83756
  if ("error" in bundleBase) {
@@ -82928,8 +83783,8 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
82928
83783
  }, null, 2));
82929
83784
  return 1;
82930
83785
  }
82931
- patch.screens[id] = { descriptor: got.text, bundle: sha };
82932
- rebound[id] = sha;
83786
+ patch.screens[id] = { descriptor: got.text, bundle: sha2 };
83787
+ rebound[id] = sha2;
82933
83788
  touched.push(`screen:${id}`);
82934
83789
  }
82935
83790
  }
@@ -82937,7 +83792,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
82937
83792
  ...common,
82938
83793
  fromRelease: baseId,
82939
83794
  plan: { patch, touched, included: [], rebound },
82940
- extra: { kind, file: { path: file, bytes: bytes2.length, sha256: sha } }
83795
+ extra: { kind, file: { path: file, bytes: bytes2.length, sha256: sha2 } }
82941
83796
  });
82942
83797
  }
82943
83798
  // OPERATIONAL FLAGS (RFC 0013) — the surface the admin portal's FlagsPanel used to own.