@wix/himalaya-cli 0.1154.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,16 +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"() {
6768
6786
  "use strict";
6769
6787
  SYSTEM_FONT_FAMILY = "$system";
6770
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
+ };
6771
6813
  }
6772
6814
  });
6773
6815
 
@@ -7861,6 +7903,47 @@ var init_validator = __esm({
7861
7903
  }
7862
7904
  });
7863
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
+
7864
7947
  // ../../core/dev-server/src/l10n-strings.ts
7865
7948
  function isUserVisibleCopy(s) {
7866
7949
  if (typeof s !== "string" || s.length === 0 || s.length > 200) return false;
@@ -10245,6 +10328,15 @@ function renderedWixMediaReferences(descriptor) {
10245
10328
  regions.forEach((region) => collectWixMediaReferences(region.value, region.path, found, false, false, "", region.atRoot));
10246
10329
  return found;
10247
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
+ }
10248
10340
  function validateScreenDescriptor(screenId, descriptor, opts = {}) {
10249
10341
  const issues = [];
10250
10342
  const strict = opts.strict ?? false;
@@ -10288,10 +10380,11 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
10288
10380
  indexIssue(at2, issue2);
10289
10381
  }
10290
10382
  }
10383
+ const tokenRegistry = opts.tokens ? buildTokenRegistry(opts.tokens) : void 0;
10291
10384
  for (const t of v.tokenViolations) {
10292
10385
  if (t.name.startsWith("$")) continue;
10293
10386
  if (pathCrossesOpaqueAuthorJson(t.path)) continue;
10294
- 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) });
10295
10388
  }
10296
10389
  for (const u of v.unknownComponents) {
10297
10390
  if (pathCrossesOpaqueAuthorJson(u.path)) continue;
@@ -10835,6 +10928,8 @@ var init_validate_descriptor_core = __esm({
10835
10928
  "../../core/dev-server/src/validate-descriptor.core.ts"() {
10836
10929
  init_validator();
10837
10930
  init_validator();
10931
+ init_tokens();
10932
+ init_token_suggest();
10838
10933
  init_l10n_strings();
10839
10934
  init_a11y_contract();
10840
10935
  init_bindings();
@@ -14387,8 +14482,8 @@ function rgbToHex([r, g, b]) {
14387
14482
  async function normalizeSourceIcon(input, opts) {
14388
14483
  const sharp = await sharpOrThrow();
14389
14484
  const size = opts.size ?? 1024;
14390
- const fit = opts.fit ?? NORMALIZE_FIT_FRACTION;
14391
- 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));
14392
14487
  const pad3 = Math.round((size - inner) / 2);
14393
14488
  let trimmed;
14394
14489
  try {
@@ -18282,7 +18377,7 @@ function agentCovers(agent, cli) {
18282
18377
  const gap = Math.abs(Date.parse(agent.ts) - Date.parse(cli.ts));
18283
18378
  if (!(gap <= AGENT_WINS_WITHIN_MS)) return false;
18284
18379
  if (cli.step && agent.step === cli.step) return true;
18285
- return [...shasOf(cli.images)].some((sha) => agentShas.has(sha));
18380
+ return [...shasOf(cli.images)].some((sha2) => agentShas.has(sha2));
18286
18381
  }
18287
18382
  function visibleEntries(entries) {
18288
18383
  return entries.filter((e) => !isCliEntry(e) || !entries.some((a) => agentCovers(a, e)));
@@ -34589,7 +34684,8 @@ var init_renderNode = __esm({
34589
34684
  if (v.startsWith("$item")) {
34590
34685
  const key2 = v.replace(/^\$item\.?/, "");
34591
34686
  const resolved2 = ctx.itemScope ? key2 ? ctx.itemScope[key2] : ctx.itemScope : void 0;
34592
- 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);
34593
34689
  }
34594
34690
  if (ctx.provider) return /* @__PURE__ */ jsx8(Bound, { ctx, binding: v, render: (val) => asTextL10n(val, ctx) });
34595
34691
  return bindingPlaceholder(v, ctx);
@@ -40514,9 +40610,9 @@ async function runScreenDiagnostics(state, screenId, descriptor) {
40514
40610
  evidence: { runId: `dev:${state.startedAt}:${screenId}`, ts: (/* @__PURE__ */ new Date()).toISOString() }
40515
40611
  });
40516
40612
  }
40517
- async function fontFaceBytesBySha(_appDir, sha) {
40613
+ async function fontFaceBytesBySha(_appDir, sha2) {
40518
40614
  try {
40519
- return await catalogFaceBytes(sha);
40615
+ return await catalogFaceBytes(sha2);
40520
40616
  } catch {
40521
40617
  }
40522
40618
  return void 0;
@@ -42877,13 +42973,13 @@ function createDevServer(opts = {}) {
42877
42973
  return handleAppIcon(req, res, state);
42878
42974
  }
42879
42975
  if (url.split("?")[0].startsWith("/v1/asset/") && req.method === "GET") {
42880
- const sha = url.split("?")[0].slice("/v1/asset/".length);
42976
+ const sha2 = url.split("?")[0].slice("/v1/asset/".length);
42881
42977
  const q = new URLSearchParams(url.split("?")[1] ?? "");
42882
42978
  const assetApp = q.get("app") ?? state.appName;
42883
42979
  const assetDir = state.app && assetApp === state.app.name ? state.app.dir : resolveAppDir(assetApp);
42884
- 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;
42885
42981
  if (!hit && assetDir) {
42886
- const fontBytes = await fontFaceBytesBySha(assetDir, sha);
42982
+ const fontBytes = await fontFaceBytesBySha(assetDir, sha2);
42887
42983
  if (fontBytes) {
42888
42984
  res.writeHead(200, {
42889
42985
  "content-type": "font/ttf",
@@ -42901,7 +42997,7 @@ function createDevServer(opts = {}) {
42901
42997
  if (serve) return await proxyToServe(serve, req, res);
42902
42998
  }
42903
42999
  res.writeHead(404, { "content-type": "application/json" });
42904
- res.end(JSON.stringify({ error: "asset_not_found", app: assetApp, sha }));
43000
+ res.end(JSON.stringify({ error: "asset_not_found", app: assetApp, sha: sha2 }));
42905
43001
  return;
42906
43002
  }
42907
43003
  const bytes2 = readFileSync44(hit.path);
@@ -43998,6 +44094,7 @@ __export(screen_crawl_exports, {
43998
44094
  isUnservedRequest: () => isUnservedRequest,
43999
44095
  layeredTransport: () => layeredTransport,
44000
44096
  mapBundleFrame: () => mapBundleFrame,
44097
+ missingItemFields: () => missingItemFields,
44001
44098
  netTransport: () => netTransport,
44002
44099
  parseWorkerAction: () => parseWorkerAction,
44003
44100
  recordScreenTraffic: () => recordScreenTraffic,
@@ -44614,6 +44711,96 @@ function renderedNameGaps(descriptor, state) {
44614
44711
  }
44615
44712
  return [...found.values()];
44616
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
+ }
44617
44804
  function collectObservations(descriptorOrPayload, state) {
44618
44805
  const out2 = [];
44619
44806
  const walk2 = (node) => {
@@ -45112,6 +45299,17 @@ async function crawlScreenInner(opts) {
45112
45299
  });
45113
45300
  }
45114
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
+ }
45115
45313
  for (const site of sites) {
45116
45314
  if (parseWorkerAction(site.actionId)) continue;
45117
45315
  const before = { ...site.params ?? {}, ...site.args ?? {} };
@@ -45735,7 +45933,7 @@ async function crawlScreenRelations(opts) {
45735
45933
  const sites = actionSitesForDescriptor(descriptor).filter(
45736
45934
  (s) => parseWorkerAction(s.actionId) && !opts.skipAction?.(s.actionId, s.componentId)
45737
45935
  );
45738
- const nameOf = (s) => parseWorkerAction(s.actionId).action;
45936
+ const nameOf2 = (s) => parseWorkerAction(s.actionId).action;
45739
45937
  const serviceOf = (s) => parseWorkerAction(s.actionId).service;
45740
45938
  const boot = () => bootScreenWorker({
45741
45939
  bundleJs,
@@ -45793,7 +45991,7 @@ async function crawlScreenRelations(opts) {
45793
45991
  )
45794
45992
  ];
45795
45993
  const trig = (s) => ({ tap: s.componentId, key: s.key, action: s.actionId });
45796
- 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")) {
45797
45995
  const after = await replay([s]);
45798
45996
  if (!after) continue;
45799
45997
  checked++;
@@ -45806,12 +46004,12 @@ async function crawlScreenRelations(opts) {
45806
46004
  invariant: "refresh-not-idempotent",
45807
46005
  severity: "warning",
45808
46006
  axis: "relation",
45809
- 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)}`,
45810
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"
45811
46009
  });
45812
46010
  }
45813
46011
  }
45814
- 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")) {
45815
46013
  const target = listPathOf(s.itemsPath);
45816
46014
  const after = target ? await replay([s]) : null;
45817
46015
  if (!after) continue;
@@ -45827,13 +46025,13 @@ async function crawlScreenRelations(opts) {
45827
46025
  axis: "relation",
45828
46026
  at: p,
45829
46027
  actual: `${baseLens[p]} \u2192 ${n}`,
45830
- 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`,
45831
46029
  hint: "append the new page to the existing rows (and de-duplicate by id); the user scrolled to see MORE"
45832
46030
  });
45833
46031
  }
45834
46032
  }
45835
46033
  }
45836
- 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 }))) {
45837
46035
  if (screenListPaths.length !== 1) continue;
45838
46036
  const after = await replay([s]);
45839
46037
  if (!after) continue;
@@ -45849,18 +46047,18 @@ async function crawlScreenRelations(opts) {
45849
46047
  axis: "relation",
45850
46048
  at: p,
45851
46049
  actual: `${baseLens[p]} \u2192 ${n}`,
45852
- 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`,
45853
46051
  hint: "filter from the FULL set you keep aside, and assign the narrowed result \u2014 not a concatenation"
45854
46052
  });
45855
46053
  }
45856
46054
  }
45857
46055
  }
45858
- const declaredNames = sites.map(nameOf);
46056
+ const declaredNames = sites.map(nameOf2);
45859
46057
  for (const a of sites) {
45860
- const partner = inversePartner(nameOf(a), declaredNames);
46058
+ const partner = inversePartner(nameOf2(a), declaredNames);
45861
46059
  if (!partner) continue;
45862
- if (isUndoAction(nameOf(a))) continue;
45863
- 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);
45864
46062
  if (!b) continue;
45865
46063
  const undoIsRowBound = isRowBound(b);
45866
46064
  const after = await replay([a, b], (i, bootState, prev) => {
@@ -45880,15 +46078,15 @@ async function crawlScreenRelations(opts) {
45880
46078
  invariant: "inverse-pair-diverges",
45881
46079
  severity: "warning",
45882
46080
  axis: "relation",
45883
- message: `${nameOf(a)} \u2192 ${nameOf(b)} does not return to the boot state: ${summarizeDiff(baseSig, sig)}`,
45884
- 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`
45885
46083
  });
45886
46084
  }
45887
46085
  }
45888
46086
  return { findings: dedupe(findings), checked, byKind };
45889
46087
  });
45890
46088
  }
45891
- 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;
45892
46090
  var init_screen_crawl = __esm({
45893
46091
  "../../core/dev-server/src/screen-crawl.ts"() {
45894
46092
  init_validate_descriptor_core();
@@ -45947,6 +46145,7 @@ var init_screen_crawl = __esm({
45947
46145
  NAME_KEYS = new Set(ACCESSIBLE_NAME_KEYS);
45948
46146
  A11Y_ROWS_PER_LIST = 20;
45949
46147
  A11Y_LIST_DEPTH = 3;
46148
+ ITEM_PATH_RE = /\$item\.([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)/g;
45950
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?"';
45951
46150
  UNBINDABLE_ROW = Symbol("unbindable-row");
45952
46151
  }
@@ -46497,10 +46696,10 @@ function hydrationServices(tier5Dir, bundleName, input = {}) {
46497
46696
  const js = readBuiltBundle(tier5Dir, bundleName);
46498
46697
  return js ? servicesFromBundleJs(js, input) : null;
46499
46698
  }
46500
- function releaseIdFor(sha, content) {
46501
- if (sha === "dev") return contentReleaseId(content);
46699
+ function releaseIdFor(sha2, content) {
46700
+ if (sha2 === "dev") return contentReleaseId(content);
46502
46701
  const stored = JSON.parse(JSON.stringify(content));
46503
- return `${sha}-${releaseContentId(stored).slice(0, 12)}`;
46702
+ return `${sha2}-${releaseContentId(stored).slice(0, 12)}`;
46504
46703
  }
46505
46704
  function rebindReleaseId(manifest) {
46506
46705
  const { releaseId: _releaseId, gitSha: gitSha2, shellSha: _shellSha, ...content } = manifest;
@@ -47402,12 +47601,12 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
47402
47601
  // never re-fetch, and the "replace an asset without a store release" claim would be false.
47403
47602
  ...Object.keys(assets).length ? { assets } : {}
47404
47603
  };
47405
- const sha = gitSha();
47604
+ const sha2 = gitSha();
47406
47605
  const buildable = shellSha();
47407
47606
  const manifest = {
47408
- releaseId: releaseIdFor(sha, content),
47409
- gitSha: sha,
47410
- ...buildable && buildable !== sha ? { shellSha: buildable } : {},
47607
+ releaseId: releaseIdFor(sha2, content),
47608
+ gitSha: sha2,
47609
+ ...buildable && buildable !== sha2 ? { shellSha: buildable } : {},
47411
47610
  ...content
47412
47611
  };
47413
47612
  await store.putManifest(app, manifest);
@@ -47490,8 +47689,8 @@ async function buildFunctionsOnlyRelease(preloaded, dest) {
47490
47689
  return { ...schedule, bundleHash: target.bundleHash };
47491
47690
  }) } : {}
47492
47691
  };
47493
- const sha = gitSha();
47494
- const manifest = { releaseId: contentReleaseId(content), gitSha: sha, ...content };
47692
+ const sha2 = gitSha();
47693
+ const manifest = { releaseId: contentReleaseId(content), gitSha: sha2, ...content };
47495
47694
  await store.putManifest(app, manifest);
47496
47695
  return { manifest, failedToBuild: [], missingBundles: [], auth: {}, tierCapViolations: [] };
47497
47696
  }
@@ -48469,14 +48668,14 @@ function observeShots(dir, memory, now) {
48469
48668
  const archived = archiveFile(dir, memory, join43(shotsDir, f));
48470
48669
  if (!archived) continue;
48471
48670
  seen.add(id);
48472
- const { sha, ext } = archived;
48671
+ const { sha: sha2, ext } = archived;
48473
48672
  const prev = memory.shots[id];
48474
48673
  if (!prev) {
48475
- memory.shots[id] = { sha, ext, firstSeen: now, changedAt: now, versions: 1, present: true };
48476
- memory.timeline.push({ ts: now, id, sha, ext });
48477
- } else if (prev.sha !== sha) {
48478
- memory.shots[id] = { ...prev, sha, ext, changedAt: now, versions: prev.versions + 1, present: true };
48479
- 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 });
48480
48679
  } else if (!prev.present) {
48481
48680
  memory.shots[id] = { ...prev, present: true };
48482
48681
  }
@@ -48959,12 +49158,12 @@ function planFromPayload(payload, base, selectors) {
48959
49158
  if (wantBundles.size) {
48960
49159
  patch.bundles = {};
48961
49160
  for (const name of wantBundles) {
48962
- const sha = local.bundles?.[name]?.sha256;
48963
- const b64 = sha ? payload.bundles?.[sha] : void 0;
48964
- if (!sha || !b64) {
49161
+ const sha2 = local.bundles?.[name]?.sha256;
49162
+ const b64 = sha2 ? payload.bundles?.[sha2] : void 0;
49163
+ if (!sha2 || !b64) {
48965
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 } };
48966
49165
  }
48967
- patch.bundles[sha] = b64;
49166
+ patch.bundles[sha2] = b64;
48968
49167
  touched.push(`bundle:${name}`);
48969
49168
  }
48970
49169
  }
@@ -48987,13 +49186,13 @@ function planFromPayload(payload, base, selectors) {
48987
49186
  return { ok: false, error: { error: "selector_matched_nothing", selector: `screen:${id}`, message: `screen "${id}" has no built descriptor in this release.`, screens: localScreens } };
48988
49187
  }
48989
49188
  const localKey = local.screens?.[id]?.bundle ?? null;
48990
- const sha = resolveBundleSha(local, localKey);
48991
- 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;
48992
49191
  const baseKeyForSha = (s) => {
48993
49192
  for (const [k, e] of Object.entries(base.bundles ?? {})) if (e?.sha256 === s) return k;
48994
49193
  return null;
48995
49194
  };
48996
- const baseKey = sha ? baseKeyForSha(sha) : null;
49195
+ const baseKey = sha2 ? baseKeyForSha(sha2) : null;
48997
49196
  if (localKey !== null && !carriedHere && !baseKey) {
48998
49197
  return {
48999
49198
  ok: false,
@@ -49005,7 +49204,7 @@ function planFromPayload(payload, base, selectors) {
49005
49204
  }
49006
49205
  };
49007
49206
  }
49008
- const bundleRef = carriedHere ? sha : baseKey;
49207
+ const bundleRef = carriedHere ? sha2 : baseKey;
49009
49208
  patch.screens[id] = { descriptor, bundle: bundleRef };
49010
49209
  if (bundleRef && bundleRef !== (base.screens?.[id]?.bundle ?? null)) rebound[id] = bundleRef;
49011
49210
  touched.push(`screen:${id}`);
@@ -50994,7 +51193,7 @@ function shotStateFor(dir, image) {
50994
51193
  }
50995
51194
  }
50996
51195
  function recordedShots(dir, screenId) {
50997
- 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);
50998
51197
  }
50999
51198
  function offlineArtifact(state) {
51000
51199
  if (!state || state.state === "content") return false;
@@ -51268,7 +51467,7 @@ var init_studio_reel = __esm({
51268
51467
  };
51269
51468
  isObj3 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
51270
51469
  str4 = (v) => typeof v === "string" && v ? v : null;
51271
- 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;
51272
51471
  cited = (p) => [
51273
51472
  ...p.frames.map((f) => f.file),
51274
51473
  ...p.finals.flatMap((f) => f.file ? [f.file] : []),
@@ -51517,10 +51716,10 @@ function stillBuilding(dir, image, now) {
51517
51716
  if (!entries.length) return void 0;
51518
51717
  const times = entries.map((e) => Date.parse(e.ts)).filter(Number.isFinite);
51519
51718
  if (!times.length || !(now.getTime() - Math.max(...times) > STILL_BUILDING_AFTER_MS)) return void 0;
51520
- const sha = sha256Of2(image);
51719
+ const sha2 = sha256Of2(image);
51521
51720
  const latest = entries[entries.length - 1];
51522
- if (Array.isArray(latest.images) && latest.images.some((i) => !!i && typeof i === "object" && i.sha256 === sha)) return void 0;
51523
- 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 });
51524
51723
  } catch {
51525
51724
  return void 0;
51526
51725
  }
@@ -51807,10 +52006,10 @@ function homeShotForPostcard(dir, opts = {}) {
51807
52006
  }
51808
52007
  if (opts.repeatOk) return pick2;
51809
52008
  try {
51810
- const sha = sha256Of2(pick2.image);
52009
+ const sha2 = sha256Of2(pick2.image);
51811
52010
  const entries = readProgress(dir, { all: true }).entries;
51812
52011
  const latest = entries[entries.length - 1];
51813
- 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);
51814
52013
  if (shown2) return { skipped: "the latest postcard already shows this home picture" };
51815
52014
  } catch {
51816
52015
  return { skipped: "the home shot could not be read" };
@@ -58603,16 +58802,16 @@ ${releaseId}`;
58603
58802
  if (assetMatch) {
58604
58803
  if (!app) return json4(400, { error: "missing_app" });
58605
58804
  if (!store.getAsset) return json4(501, { error: "assets_unsupported" });
58606
- const sha = assetMatch[1];
58607
- if (!isSafeSegment(sha)) return json4(400, { error: "bad_asset" });
58805
+ const sha2 = assetMatch[1];
58806
+ if (!isSafeSegment(sha2)) return json4(400, { error: "bad_asset" });
58608
58807
  const m = pin ? await pinnedManifest(app, pin) : await manifest(app);
58609
- const record2 = Object.values(m?.assets ?? {}).find((a) => a.sha256 === sha);
58808
+ const record2 = Object.values(m?.assets ?? {}).find((a) => a.sha256 === sha2);
58610
58809
  if (!record2) return json4(404, { error: "asset_not_found", app });
58611
- const etag = strongEtagForSha256(sha);
58810
+ const etag = strongEtagForSha256(sha2);
58612
58811
  if (request.headers.get("if-none-match") === etag) {
58613
58812
  return new Response(null, { status: 304, headers: { etag, "cache-control": BUNDLE_CACHE_CONTROL } });
58614
58813
  }
58615
- const bytes2 = await store.getAsset(app, sha);
58814
+ const bytes2 = await store.getAsset(app, sha2);
58616
58815
  if (!bytes2) return json4(404, { error: "asset_not_found", app });
58617
58816
  return new Response(bytes2, {
58618
58817
  status: 200,
@@ -59189,21 +59388,21 @@ var init_preview_assets_generated = __esm({
59189
59388
  "../../packages/serve/src/preview-assets.generated.ts"() {
59190
59389
  init_preview_assets_format();
59191
59390
  PREVIEW_ASSET_MANIFEST = [
59192
- { key: "assets/index-DHO0WElX.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 1127482, sha256: "9a198f7af12c683263ba55641ec1802d4ca9addb9b8d38170e6b4f60387c61c3" },
59193
- { key: "fonts/Roboto-LICENSE.txt", contentType: "application/octet-stream", offset: 1127490, length: 4610, sha256: "994062dd7af4186a739de55dd9e151c63b6301f8e2cbafc608d35c2e87da0cd2" },
59194
- { key: "fonts/Roboto-Variable.ttf", contentType: "font/ttf", offset: 1132100, length: 488584, sha256: "d7598e12c5dbef095ff8272cfc55da0250bd07fbdecbac8a530b9b277872a134" },
59195
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 1620684, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
59196
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1711608, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
59197
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1802592, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
59198
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1893572, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
59199
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1984444, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
59200
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 2075424, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
59201
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 2184060, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
59202
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 2293144, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
59203
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 2402192, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
59204
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 2509892, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
59205
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 2598e3, length: 4947, sha256: "72e531f20412d0f07433cb52fac32ff7f5be87a4ef435c2d919cd5e21ec84ad3" },
59206
- { 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" }
59207
59406
  ];
59208
59407
  }
59209
59408
  });
@@ -59826,7 +60025,7 @@ var init_iap_client = __esm({
59826
60025
  "../store-release/iap-client.ts"() {
59827
60026
  init_register_bundle();
59828
60027
  ASC_ORIGIN = "https://api.appstoreconnect.apple.com";
59829
- MAX_PAGES = 50;
60028
+ MAX_PAGES = 100;
59830
60029
  MAX_READ_ATTEMPTS = 4;
59831
60030
  REQUEST_TIMEOUT_MS = 3e4;
59832
60031
  MAX_RETRY_WAIT_MS = 6e4;
@@ -59985,6 +60184,9 @@ var init_iap_client = __esm({
59985
60184
  });
59986
60185
 
59987
60186
  // ../store-release/iap-api.ts
60187
+ function appleProductType(type) {
60188
+ return APPLE_PRODUCT_TYPES[type];
60189
+ }
59988
60190
  function single(doc) {
59989
60191
  return doc.data && !Array.isArray(doc.data) ? doc.data : void 0;
59990
60192
  }
@@ -60038,10 +60240,12 @@ async function readOptional(read) {
60038
60240
  function createIapApi(client2) {
60039
60241
  return new AppStoreConnectIapApi(client2);
60040
60242
  }
60041
- 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;
60042
60244
  var init_iap_api = __esm({
60043
60245
  "../store-release/iap-api.ts"() {
60044
60246
  init_iap_client();
60247
+ APPLE_PRODUCT_TYPES = { nonConsumable: "NON_CONSUMABLE", consumable: "CONSUMABLE" };
60248
+ PRICE_POINT_PAGE = "limit=8000";
60045
60249
  INITIAL_PRICE_REF = "${initial-price}";
60046
60250
  PAGE = "limit=200";
60047
60251
  linkage = (type, id) => ({ type, id });
@@ -60078,7 +60282,7 @@ var init_iap_api = __esm({
60078
60282
  return (await this.client.list(`/v1/inAppPurchaseVersions/${enc(versionId)}/localizations?${PAGE}`)).map(toLocalization);
60079
60283
  }
60080
60284
  async listPricePoints(resourceId, territory) {
60081
- 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}`);
60082
60286
  return points.map((p) => ({ id: p.id, customerPrice: attr(p, "customerPrice") }));
60083
60287
  }
60084
60288
  async readPriceSchedule(resourceId) {
@@ -60107,7 +60311,7 @@ var init_iap_api = __esm({
60107
60311
  const attributes = {
60108
60312
  name: product.referenceName,
60109
60313
  productId: product.productId,
60110
- inAppPurchaseType: "NON_CONSUMABLE",
60314
+ inAppPurchaseType: appleProductType(product.type),
60111
60315
  familySharable: false,
60112
60316
  ...product.reviewNote ? { reviewNote: product.reviewNote } : {}
60113
60317
  };
@@ -60191,11 +60395,13 @@ function readLocalizations(reader, item, path2) {
60191
60395
  const localizations = reader.list(item, "localizations", path2).map((value, i) => {
60192
60396
  const at2 = `${path2}.localizations[${i}]`;
60193
60397
  const obj3 = reader.object(value, at2, LOCALIZATION_KEYS);
60194
- return {
60195
- locale: reader.text(obj3, "locale", at2, LOCALE),
60196
- name: reader.text(obj3, "name", at2),
60197
- description: reader.text(obj3, "description", at2)
60198
- };
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 };
60199
60405
  });
60200
60406
  reader.duplicates(localizations.map((l) => l.locale), `${path2}.localizations`, "locale");
60201
60407
  return localizations;
@@ -60203,10 +60409,12 @@ function readLocalizations(reader, item, path2) {
60203
60409
  function readPrice(reader, item, path2) {
60204
60410
  const at2 = `${path2}.price`;
60205
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");
60206
60413
  return {
60207
60414
  baseTerritory: reader.text(obj3, "baseTerritory", at2, TERRITORY),
60208
60415
  currency: reader.text(obj3, "currency", at2, CURRENCY),
60209
- amount: reader.text(obj3, "amount", at2, AMOUNT)
60416
+ amount: reader.text(obj3, "amount", at2, AMOUNT),
60417
+ ...rule ? { rule } : {}
60210
60418
  };
60211
60419
  }
60212
60420
  function readTerritories(reader, item, path2, baseTerritory) {
@@ -60218,34 +60426,66 @@ function readTerritories(reader, item, path2, baseTerritory) {
60218
60426
  }
60219
60427
  return territories;
60220
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
+ }
60221
60454
  function readItem(reader, value, index) {
60222
60455
  const path2 = `items[${index}]`;
60223
60456
  const obj3 = reader.object(value, path2, ITEM_KEYS);
60224
- 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");
60225
60458
  const price = readPrice(reader, obj3, path2);
60226
60459
  const reviewNote = reader.optionalText(obj3, "reviewNote", path2);
60460
+ if (reviewNote) reader.maxLength(reviewNote, `${path2}.reviewNote`, APPLE_LIMITS.reviewNote);
60227
60461
  return {
60228
- wix: { entityType: reader.text(wix, "entityType", `${path2}.wix`), entityId: reader.text(wix, "entityId", `${path2}.wix`) },
60462
+ ...readSubject(reader, obj3, path2, type),
60229
60463
  classification: reader.literal(obj3, "classification", path2, "digital", "physical goods and unclassified products are never registered as in-app purchases"),
60230
60464
  entitlement: reader.text(obj3, "entitlement", path2),
60231
- productId: reader.text(obj3, "productId", path2, PRODUCT_ID),
60232
- type: reader.literal(obj3, "type", path2, "nonConsumable", "other product types are not supported by this version"),
60233
- 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),
60234
60468
  ...reviewNote === void 0 ? {} : { reviewNote },
60235
60469
  localizations: readLocalizations(reader, obj3, path2),
60236
60470
  price,
60237
60471
  territories: readTerritories(reader, obj3, path2, price.baseTerritory)
60238
60472
  };
60239
60473
  }
60474
+ function wixEntitiesOf(item) {
60475
+ return item.unlocks ?? (item.wix ? [item.wix] : []);
60476
+ }
60240
60477
  function reportDuplicateMappings(reader, items) {
60241
60478
  reader.duplicates(items.map((i) => i.productId), "items", "productId");
60242
- reader.duplicates(items.map((i) => `${i.wix.entityType}:${i.wix.entityId}`), "items", "Wix entity");
60243
- 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");
60244
60481
  reader.duplicates(items.map((i) => i.referenceName), "items", "referenceName");
60245
60482
  }
60483
+ function looksLikeCredential(text8) {
60484
+ return CREDENTIAL_SHAPES.some((shape) => shape.test(text8));
60485
+ }
60246
60486
  function reportCredentialMaterial(reader, value, path2) {
60247
60487
  if (typeof value === "string") {
60248
- if (CREDENTIAL_SHAPES.some((shape) => shape.test(value))) {
60488
+ if (looksLikeCredential(value)) {
60249
60489
  reader.problems.push(`${path2} looks like credential material \u2014 keys and tokens never belong in a manifest`);
60250
60490
  }
60251
60491
  } else if (Array.isArray(value)) {
@@ -60288,7 +60528,7 @@ function sha256Digest(value) {
60288
60528
  function manifestDigest(manifest) {
60289
60529
  return sha256Digest(manifest);
60290
60530
  }
60291
- 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;
60292
60532
  var init_iap_manifest = __esm({
60293
60533
  "../store-release/iap-manifest.ts"() {
60294
60534
  IapManifestError = class extends Error {
@@ -60302,7 +60542,7 @@ var init_iap_manifest = __esm({
60302
60542
  APPLE_APP_ID = { pattern: /^\d+$/, hint: "the numeric App Store Connect app id" };
60303
60543
  TEAM_ID = { pattern: /^[A-Z0-9]{10}$/, hint: "a 10-character Apple team id" };
60304
60544
  BUNDLE_ID = { pattern: /^[A-Za-z0-9.-]+$/, hint: "a bundle id" };
60305
- 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" };
60306
60546
  TERRITORY = { pattern: /^[A-Z]{3}$/, hint: "an ISO 3166-1 alpha-3 territory code such as USA" };
60307
60547
  CURRENCY = { pattern: /^[A-Z]{3}$/, hint: "an ISO 4217 currency code such as USD" };
60308
60548
  AMOUNT = { pattern: /^\d+(\.\d+)?$/, hint: 'an exact decimal string such as "0.99"' };
@@ -60316,6 +60556,7 @@ var init_iap_manifest = __esm({
60316
60556
  TARGET_KEYS = ["himiApp", "wixSiteId", "appleAppId", "bundleId", "teamId", "profile"];
60317
60557
  ITEM_KEYS = [
60318
60558
  "wix",
60559
+ "unlocks",
60319
60560
  "classification",
60320
60561
  "entitlement",
60321
60562
  "productId",
@@ -60327,8 +60568,12 @@ var init_iap_manifest = __esm({
60327
60568
  "territories"
60328
60569
  ];
60329
60570
  WIX_KEYS = ["entityType", "entityId"];
60571
+ UNLOCK_KEYS = ["entityType", "entityId", "entitlement"];
60330
60572
  LOCALIZATION_KEYS = ["locale", "name", "description"];
60331
- 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 };
60332
60577
  childPath = (path2, key2) => path2 ? `${path2}.${key2}` : key2;
60333
60578
  ManifestReader = class {
60334
60579
  problems = [];
@@ -60368,6 +60613,15 @@ var init_iap_manifest = __esm({
60368
60613
  if (obj3[key2] !== expected) this.problems.push(`${childPath(path2, key2)} must be ${JSON.stringify(expected)} \u2014 ${why}`);
60369
60614
  return expected;
60370
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
+ }
60371
60625
  duplicates(values, path2, what) {
60372
60626
  const seen = /* @__PURE__ */ new Set();
60373
60627
  for (const value of values) {
@@ -60379,6 +60633,84 @@ var init_iap_manifest = __esm({
60379
60633
  }
60380
60634
  });
60381
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
+
60382
60714
  // ../store-release/iap-result.ts
60383
60715
  function remainingRequirements(observed) {
60384
60716
  if (!observed) return ["the product does not exist in App Store Connect yet"];
@@ -60391,8 +60723,8 @@ function mappingFor(target, item, appleResourceId) {
60391
60723
  wixSiteId: target.wixSiteId,
60392
60724
  appleAppId: target.appleAppId,
60393
60725
  bundleId: target.bundleId,
60394
- wixEntityType: item.wix.entityType,
60395
- wixEntityId: item.wix.entityId,
60726
+ ...item.wix ? { wixEntityType: item.wix.entityType, wixEntityId: item.wix.entityId } : {},
60727
+ ...item.unlocks ? { unlocks: item.unlocks } : {},
60396
60728
  entitlement: item.entitlement,
60397
60729
  appleProductId: item.productId,
60398
60730
  ...appleResourceId ? { appleResourceId } : {},
@@ -60420,7 +60752,7 @@ function itemResult(input) {
60420
60752
  ...observed?.availability ? { observed: observed.availability.territories, availableInNewTerritories: observed.availability.availableInNewTerritories } : {}
60421
60753
  },
60422
60754
  localizations: { requested: item.localizations.map((l) => l.locale), observed: observed?.localizations.map((l) => l.locale) ?? [] },
60423
- remainingRequirements: remainingRequirements(observed),
60755
+ remainingRequirements: [...input.requirements ?? [], ...remainingRequirements(observed)],
60424
60756
  lastReadAt: input.readAt.toISOString()
60425
60757
  }
60426
60758
  };
@@ -60458,11 +60790,10 @@ var init_iap_result = __esm({
60458
60790
  });
60459
60791
 
60460
60792
  // ../store-release/iap-plan.ts
60461
- function normalizeAmount(amount) {
60462
- const [whole, fraction = ""] = amount.trim().split(".");
60463
- const integer = whole.replace(/^0+(?=\d)/, "");
60464
- const decimals = fraction.replace(/0+$/, "");
60465
- 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`;
60466
60797
  }
60467
60798
  function describeOp(op) {
60468
60799
  return "locale" in op ? `${op.kind} ${op.locale}` : op.kind;
@@ -60563,8 +60894,8 @@ async function observeProduct(api2, product) {
60563
60894
  ...availability ? { availability } : {}
60564
60895
  };
60565
60896
  }
60566
- function priceMatches(observed, requested) {
60567
- 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;
60568
60899
  }
60569
60900
  function availabilityMatches(observed, territories) {
60570
60901
  return observed !== void 0 && !observed.availableInNewTerritories && sameSet(observed.territories, territories);
@@ -60576,12 +60907,11 @@ function localizationDiffs(item, observed) {
60576
60907
  return found.name === wanted.name && found.description === wanted.description ? [] : [`localization ${wanted.locale} differs`];
60577
60908
  });
60578
60909
  }
60579
- function priceDiff(item, observed) {
60580
- if (priceMatches(observed.price, item.price)) return [];
60910
+ function priceDiff(item, observed, target) {
60911
+ if (priceMatches(observed.price, item.price, target)) return [];
60581
60912
  if (!observed.price) return ["no price is set"];
60582
60913
  const prices = observed.price.customerPrices.join("/") || "none";
60583
- const requested = `${item.price.amount} ${item.price.currency} in ${item.price.baseTerritory}`;
60584
- 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)}`];
60585
60915
  }
60586
60916
  function availabilityDiff(item, observed) {
60587
60917
  if (availabilityMatches(observed.availability, item.territories)) return [];
@@ -60589,8 +60919,8 @@ function availabilityDiff(item, observed) {
60589
60919
  const auto = observed.availability.availableInNewTerritories ? " (plus new territories automatically)" : "";
60590
60920
  return [`available in ${observed.availability.territories.join(", ")}${auto}; the manifest requests ${item.territories.join(", ")}`];
60591
60921
  }
60592
- function metadataDiffs(item, observed) {
60593
- 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)];
60594
60924
  }
60595
60925
  function isLocked(observed) {
60596
60926
  return !EDITABLE_PRODUCT_STATES.has(observed.state) || observed.versions.some((v) => LOCKED_VERSION_STATES.has(v.state));
@@ -60598,8 +60928,9 @@ function isLocked(observed) {
60598
60928
  function itemPlan(item, decision, fields = {}) {
60599
60929
  return { productId: item.productId, decision, ops: [], conflicts: [], notes: [], ...fields };
60600
60930
  }
60601
- function creationPlan(item) {
60931
+ function creationPlan(item, target) {
60602
60932
  return itemPlan(item, "create", {
60933
+ ...target ? { applePrice: target, targetPrice: target } : {},
60603
60934
  ops: [
60604
60935
  { kind: "createProduct" },
60605
60936
  { kind: "createVersion" },
@@ -60607,17 +60938,20 @@ function creationPlan(item) {
60607
60938
  { kind: "setPrice" },
60608
60939
  { kind: "setAvailability" }
60609
60940
  ],
60610
- notes: [CONDITIONAL_PRICE_NOTE]
60941
+ notes: [priceNote(item.price, target)]
60611
60942
  });
60612
60943
  }
60613
60944
  function identityConflicts(item, observed) {
60614
60945
  const conflicts = [];
60615
- 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
+ }
60616
60950
  if (observed.referenceName !== item.referenceName) conflicts.push(`Apple's reference name is "${observed.referenceName}"; the manifest says "${item.referenceName}"`);
60617
60951
  return conflicts;
60618
60952
  }
60619
- function lockedPlan(item, observed) {
60620
- const diffs = metadataDiffs(item, observed);
60953
+ function lockedPlan(item, observed, target) {
60954
+ const diffs = metadataDiffs(item, observed, target);
60621
60955
  if (!diffs.length) return itemPlan(item, "reuse", { observed });
60622
60956
  const conflict = `${item.productId} is ${observed.state} and this command never changes a live or in-review product: ${diffs.join("; ")}`;
60623
60957
  return itemPlan(item, "conflict", { observed, conflicts: [conflict] });
@@ -60633,58 +60967,54 @@ function localizationOps(item, observed) {
60633
60967
  return found.name === wanted.name && found.description === wanted.description ? [] : [{ kind: "updateLocalization", locale: wanted.locale }];
60634
60968
  });
60635
60969
  }
60636
- async function exactPricePoint(api2, resourceId, price) {
60637
- const points = await api2.listPricePoints(resourceId, price.baseTerritory);
60638
- const wanted = normalizeAmount(price.amount);
60639
- const point = points.find((p) => normalizeAmount(p.customerPrice) === wanted);
60640
- const distance = (p) => Math.abs(Number(p.customerPrice) - Number(price.amount));
60641
- const nearest = [...points].sort((a, b) => distance(a) - distance(b)).slice(0, 2).map((p) => p.customerPrice).sort((a, b) => Number(a) - Number(b));
60642
- return { point, nearest };
60643
- }
60644
- function noExactPriceMessage(price, nearest) {
60645
- 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.`;
60646
- }
60647
- async function priceOps(api2, item, observed) {
60970
+ async function priceOps(pricing, item, observed, target) {
60648
60971
  if (observed.price) {
60649
- 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`] };
60650
60974
  }
60651
- const { point, nearest } = await exactPricePoint(api2, observed.resourceId, item.price);
60652
- 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"] };
60653
60977
  }
60654
60978
  function availabilityOps(item, observed) {
60655
60979
  if (!observed.availability) return { ops: [{ kind: "setAvailability" }], conflicts: [] };
60656
60980
  const conflicts = availabilityMatches(observed.availability, item.territories) ? [] : [`${availabilityDiff(item, observed)[0]} \u2014 existing availability is never changed here`];
60657
60981
  return { ops: [], conflicts };
60658
60982
  }
60659
- async function editablePlan(api2, item, observed) {
60660
- const price = await priceOps(api2, item, observed);
60983
+ async function editablePlan(pricing, item, observed, target) {
60984
+ const price = await priceOps(pricing, item, observed, target);
60661
60985
  const availability = availabilityOps(item, observed);
60662
60986
  const conflicts = [...price.conflicts, ...availability.conflicts];
60663
60987
  if (conflicts.length) return itemPlan(item, "conflict", { observed, conflicts });
60664
60988
  const ops = [...localizationOps(item, observed), ...price.ops, ...availability.ops];
60665
- return itemPlan(item, ops.length ? "update" : "reuse", { observed, ops });
60989
+ return itemPlan(item, ops.length ? "update" : "reuse", { observed, ops, ...price.applePrice ? { applePrice: price.applePrice } : {} });
60666
60990
  }
60667
- async function planForProduct(api2, item, product) {
60991
+ async function planForProduct(api2, item, product, pricing) {
60668
60992
  const observed = await observeProduct(api2, product);
60993
+ const target = await pricing.targetAmount(item.price, product.id);
60669
60994
  const conflicts = identityConflicts(item, observed);
60670
- if (conflicts.length) return itemPlan(item, "conflict", { observed, conflicts });
60671
- 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;
60672
60997
  }
60673
- async function planItem(api2, item, products) {
60998
+ async function planItem(pricing, api2, item, products) {
60674
60999
  const nameClash = products.find((p) => p.referenceName === item.referenceName && p.productId !== item.productId);
60675
61000
  if (nameClash) return itemPlan(item, "conflict", { conflicts: [`reference name "${item.referenceName}" is already used by ${nameClash.productId}`] });
60676
61001
  const match = products.find((p) => p.productId === item.productId);
60677
- 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));
60678
61004
  }
60679
- 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);
60680
61007
  return withAccessContext(context.identity, manifest.target, async () => {
60681
61008
  await verifyTarget(context.api, manifest, context.identity);
60682
61009
  const products = await discoverProducts(context, manifest);
60683
- const plans = [];
60684
- 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));
60685
61012
  return plans;
60686
61013
  });
60687
61014
  }
61015
+ async function planItems(context, manifest) {
61016
+ return [...(await planItemsAt(context, manifest)).values()];
61017
+ }
60688
61018
  function sealPlan(unsealed) {
60689
61019
  const normalized = JSON.parse(JSON.stringify(unsealed));
60690
61020
  return { ...normalized, planDigest: sha256Digest(normalized) };
@@ -60726,26 +61056,32 @@ function verifyPlan2(doc) {
60726
61056
  return plan;
60727
61057
  }
60728
61058
  async function statusIap(manifest, context) {
60729
- const plans = await planItems(context, manifest);
61059
+ const pricing = context.pricing ?? new PriceGrid(context.api);
61060
+ const plans = await planItems({ ...context, pricing }, manifest);
60730
61061
  const readAt = context.now();
60731
- const items = plans.map(
60732
- (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({
60733
61067
  target: manifest.target,
60734
- item: manifest.items[i],
61068
+ item,
60735
61069
  observed: p.observed,
60736
61070
  outcome: STATUS_OUTCOMES[p.decision],
60737
61071
  conflicts: p.conflicts,
60738
- unmet: p.observed ? metadataDiffs(manifest.items[i], p.observed) : [],
61072
+ unmet: p.observed ? metadataDiffs(item, p.observed, target) : [],
60739
61073
  readAt
60740
- })
60741
- );
61074
+ }));
61075
+ }
60742
61076
  return iapResult({ target: manifest.target, identity: context.identity, items, generatedAt: readAt });
60743
61077
  }
60744
- 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;
60745
61079
  var init_iap_plan = __esm({
60746
61080
  "../store-release/iap-plan.ts"() {
61081
+ init_iap_api();
60747
61082
  init_iap_client();
60748
61083
  init_iap_manifest();
61084
+ init_iap_pricing();
60749
61085
  init_iap_result();
60750
61086
  IapProvisionError = class extends Error {
60751
61087
  constructor(reason, message) {
@@ -60755,7 +61091,6 @@ var init_iap_plan = __esm({
60755
61091
  }
60756
61092
  };
60757
61093
  EDITABLE_VERSION_STATE = "PREPARE_FOR_SUBMISSION";
60758
- APPLE_NON_CONSUMABLE = "NON_CONSUMABLE";
60759
61094
  EDITABLE_PRODUCT_STATES = /* @__PURE__ */ new Set(["MISSING_METADATA", "READY_TO_SUBMIT", "DEVELOPER_ACTION_NEEDED", "REJECTED"]);
60760
61095
  LOCKED_VERSION_STATES = /* @__PURE__ */ new Set(["READY_FOR_REVIEW", "WAITING_FOR_REVIEW", "IN_REVIEW", "ACCEPTED", "APPROVED"]);
60761
61096
  OP_KINDS = /* @__PURE__ */ new Set(["createProduct", "createVersion", "createLocalization", "updateLocalization", "setPrice", "setAvailability"]);
@@ -60763,6 +61098,7 @@ var init_iap_plan = __esm({
60763
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";
60764
61099
  STATUS_OUTCOMES = { create: "absent", update: "incomplete", reuse: "reused", conflict: "conflict" };
60765
61100
  isoDay = (date) => date.toISOString().slice(0, 10);
61101
+ exactTarget = (price) => price.rule === "atLeast" ? void 0 : price.amount;
60766
61102
  planError = (message) => new IapProvisionError("plan", message);
60767
61103
  }
60768
61104
  });
@@ -60785,7 +61121,7 @@ function driftOf(planned, live2) {
60785
61121
  return unplanned.length ? `${live2.productId} now needs ${unplanned.map(describeOp).join(", ")}, which the plan did not list` : void 0;
60786
61122
  }
60787
61123
  function assertNoDrift(plan, live2) {
60788
- 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);
60789
61125
  if (!drift.length) return;
60790
61126
  throw new IapProvisionError("drift", `App Store Connect changed since the plan was made \u2014 nothing was written:
60791
61127
  - ${drift.join("\n - ")}
@@ -60836,12 +61172,13 @@ async function findLocalization(api2, versionId, locale, matchText) {
60836
61172
  if (!found || matchText && (found.name !== matchText.name || found.description !== matchText.description)) return void 0;
60837
61173
  return found;
60838
61174
  }
60839
- function operationRunners(api2, manifest, sleep) {
61175
+ function operationRunners(context, manifest) {
61176
+ const { api: api2, sleep, pricing } = context;
60840
61177
  const appId2 = manifest.target.appleAppId;
60841
61178
  return {
60842
61179
  createProduct: async (run2) => {
60843
61180
  const { item } = run2;
60844
- 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 } : {} };
60845
61182
  const { value, state } = await ensure({ write: () => api2.createProduct(appId2, newProduct), find: () => findProduct(api2, appId2, item.productId) }, sleep);
60846
61183
  run2.resourceId = value.id;
60847
61184
  return { state, resourceId: value.id };
@@ -60869,9 +61206,9 @@ function operationRunners(api2, manifest, sleep) {
60869
61206
  setPrice: async (run2) => {
60870
61207
  const resourceId = required(run2.resourceId, "the product id");
60871
61208
  const { price } = run2.item;
60872
- const { point, nearest } = await exactPricePoint(api2, resourceId, price);
60873
- if (!point) throw new UnresolvedWriteError(noExactPriceMessage(price, nearest));
60874
- 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;
60875
61212
  const { state } = await ensure({ write: async () => (await api2.createPriceSchedule(resourceId, price.baseTerritory, point.id), true), find: matches }, sleep);
60876
61213
  return { state };
60877
61214
  },
@@ -60904,10 +61241,10 @@ async function runStep(runner2, run2, op, context, manifest) {
60904
61241
  context.journal.record({ productId: run2.item.productId, ...run2.resourceId ? { productResourceId: run2.resourceId } : {}, ...entry, at: context.now().toISOString() });
60905
61242
  return step;
60906
61243
  }
60907
- async function resumeFromApple(run2, remaining, api2) {
60908
- 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"));
60909
61246
  if (!product) return { reason: "the product exists but cannot be read back yet \u2014 rerun apply later" };
60910
- const replanned = await planForProduct(api2, run2.item, product);
61247
+ const replanned = await planForProduct(context.api, run2.item, product, context.pricing);
60911
61248
  if (replanned.decision === "conflict") return { reason: `the product already existed: ${replanned.conflicts.join("; ")}` };
60912
61249
  const unplanned = replanned.ops.filter((op) => !remaining.some((r) => sameOp(r, op)));
60913
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` };
@@ -60916,7 +61253,7 @@ async function resumeFromApple(run2, remaining, api2) {
60916
61253
  return { ops: replanned.ops };
60917
61254
  }
60918
61255
  async function runOps(run2, ops, context, manifest) {
60919
- const runners = operationRunners(context.api, manifest, context.sleep);
61256
+ const runners = operationRunners(context, manifest);
60920
61257
  const steps = [];
60921
61258
  let queue = [...ops];
60922
61259
  for (let op = queue.shift(); op; op = queue.shift()) {
@@ -60924,7 +61261,7 @@ async function runOps(run2, ops, context, manifest) {
60924
61261
  steps.push(step);
60925
61262
  if (step.state === "failed") return [...steps, ...queue.map((rest) => stepFor(rest, "notRun"))];
60926
61263
  if (op.kind === "createProduct" && step.state === "reconciled") {
60927
- 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)}` }));
60928
61265
  if ("reason" in resumed) return [...steps, ...queue.map((rest, i) => stepFor(rest, "notRun", i === 0 ? { detail: resumed.reason } : {}))];
60929
61266
  queue = resumed.ops;
60930
61267
  }
@@ -60947,10 +61284,17 @@ async function readBack(run2, context, manifest) {
60947
61284
  return { failure: explainFailure(e, context, manifest) };
60948
61285
  }
60949
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
+ }
60950
61294
  async function applyItem(item, live2, context, manifest) {
60951
61295
  const base = { target: manifest.target, item, readAt: context.now() };
60952
61296
  if (live2.decision === "conflict") {
60953
- const unmet2 = live2.observed ? metadataDiffs(item, live2.observed) : [];
61297
+ const unmet2 = await knownUnmetFor(item, live2, context);
60954
61298
  return itemResult({ ...base, observed: live2.observed, outcome: "conflict", conflicts: live2.conflicts, unmet: unmet2 });
60955
61299
  }
60956
61300
  const run2 = {
@@ -60961,29 +61305,55 @@ async function applyItem(item, live2, context, manifest) {
60961
61305
  };
60962
61306
  const steps = await runOps(run2, live2.ops, context, manifest);
60963
61307
  const { observed, failure } = await readBack(run2, context, manifest);
60964
- 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);
60965
61309
  const outcome = outcomeOf(live2.ops, steps, unmet);
60966
61310
  return itemResult({ ...base, readAt: context.now(), observed, outcome, steps, unmet, appleResourceId: run2.resourceId });
60967
61311
  }
60968
- function effectivePlan(planned, live2) {
60969
- 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() });
61315
+ }
61316
+ function needsRefresh(planned) {
61317
+ return planned.decision !== "conflict" && planned.ops.length > 0;
60970
61318
  }
60971
- async function applyIap(planDoc, context) {
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) {
60972
61324
  const plan = verifyPlan2(planDoc);
60973
- assertSameAccount(plan.identity, context.identity);
61325
+ assertSameAccount(plan.identity, applyContext.identity);
61326
+ const context = { ...applyContext, pricing: applyContext.pricing ?? new PriceGrid(applyContext.api) };
60974
61327
  const manifest = plan.manifest;
60975
- 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);
60976
61330
  assertNoDrift(plan, live2);
60977
61331
  const items = [];
60978
- 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
+ }
60979
61347
  return iapResult({ target: manifest.target, identity: context.identity, items, generatedAt: context.now() });
60980
61348
  }
60981
- 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;
60982
61350
  var init_iap_apply = __esm({
60983
61351
  "../store-release/iap-apply.ts"() {
60984
61352
  init_iap_client();
60985
61353
  init_iap_plan();
61354
+ init_iap_pricing();
60986
61355
  init_iap_result();
61356
+ DEFERRED_REQUIREMENT = "deferred by --limit \u2014 rerun apply to continue; nothing was written for this product in this run";
60987
61357
  RECONCILE_READS = 4;
60988
61358
  RECONCILE_BASE_WAIT_MS = 1e3;
60989
61359
  AMBIGUOUS_WRITE_KINDS = /* @__PURE__ */ new Set(["uncertain", "rateLimited", "conflict"]);
@@ -61184,6 +61554,182 @@ var init_iap_credentials = __esm({
61184
61554
  }
61185
61555
  });
61186
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
+
61187
61733
  // src/base44-analyze/redact.ts
61188
61734
  function isSecretKey(key2) {
61189
61735
  const words = key2.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase().split(/[^a-z0-9]+/).filter(Boolean);
@@ -61267,6 +61813,161 @@ var init_redact = __esm({
61267
61813
  }
61268
61814
  });
61269
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
+
61270
61971
  // src/store-iap.ts
61271
61972
  var store_iap_exports = {};
61272
61973
  __export(store_iap_exports, {
@@ -61277,7 +61978,7 @@ __export(store_iap_exports, {
61277
61978
  runStoreIap: () => runStoreIap
61278
61979
  });
61279
61980
  import { randomUUID as randomUUID5 } from "node:crypto";
61280
- 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";
61281
61982
  import { homedir as homedir7, hostname } from "node:os";
61282
61983
  import { join as join65 } from "node:path";
61283
61984
  function scrubDeep(value, scrub) {
@@ -61348,9 +62049,27 @@ function printItemPlan(out2, item) {
61348
62049
  for (const note of item.notes) out2.line(` note: ${note}`);
61349
62050
  for (const conflict of item.conflicts) out2.line(` \u2717 conflict: ${conflict}`);
61350
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
+ }
61351
62062
  function printPlanItems(out2, plan) {
61352
62063
  printHeader(out2, plan.identity, plan.target);
61353
- 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
+ }
61354
62073
  out2.line();
61355
62074
  }
61356
62075
  function printPlan(out2, plan, planPath) {
@@ -61386,14 +62105,22 @@ function printItemResult(out2, item) {
61386
62105
  }
61387
62106
  function printResult(out2, result, resultPath) {
61388
62107
  printHeader(out2, result.identity, result.target);
61389
- 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
+ }
61390
62116
  out2.line();
61391
62117
  if (resultPath) out2.line(` result written to ${resultPath}`);
61392
62118
  out2.line(" backend sync: not configured \xB7 checkout: not active \u2014 the result is a mapping candidate, not an activation.");
61393
62119
  }
61394
- function applyQuestion(plan) {
62120
+ function applyQuestion(plan, limit) {
61395
62121
  const operations = plan.items.reduce((n, item) => n + item.ops.length, 0);
61396
- 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}?`;
61397
62124
  }
61398
62125
  async function planCommand(flags, deps, out2) {
61399
62126
  const manifestPath = requireFlag(flags, "manifest", "plan");
@@ -61408,10 +62135,17 @@ async function planCommand(flags, deps, out2) {
61408
62135
  else printPlan(out2, plan, planPath);
61409
62136
  return plan.items.some((i) => i.decision === "conflict") ? 1 : 0;
61410
62137
  }
61411
- 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) {
61412
62145
  const release = deps.acquireLock(plan.target.appleAppId);
61413
62146
  try {
61414
- 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 });
61415
62149
  } finally {
61416
62150
  release();
61417
62151
  }
@@ -61420,6 +62154,7 @@ async function applyCommand(flags, deps, out2) {
61420
62154
  const planPath = requireFlag(flags, "plan", "apply");
61421
62155
  const profile = requireFlag(flags, "profile", "apply");
61422
62156
  const resultPath = requireFlag(flags, "out", "apply");
62157
+ const limit = createLimit(flags);
61423
62158
  const confirmed = flags.yes === true;
61424
62159
  const canAsk = out2.interactive && !out2.json && deps.canPrompt();
61425
62160
  if (!confirmed && !canAsk) {
@@ -61431,11 +62166,11 @@ async function applyCommand(flags, deps, out2) {
61431
62166
  const plan = readPlan(deps, planPath);
61432
62167
  const session = openSession(deps, out2, plan.target, profile);
61433
62168
  if (!out2.json) printApplyPreview(out2, plan, planPath);
61434
- if (!confirmed && !await deps.confirm(applyQuestion(plan))) {
62169
+ if (!confirmed && !await deps.confirm(applyQuestion(plan, limit))) {
61435
62170
  out2.line(" aborted \u2014 nothing was changed in App Store Connect.");
61436
62171
  return 1;
61437
62172
  }
61438
- const result = await applyWithLock(deps, plan, session);
62173
+ const result = await applyWithLock(deps, plan, session, limit);
61439
62174
  writeJson(deps, out2, resultPath, result);
61440
62175
  if (out2.json) out2.print({ ok: result.complete, result: resultPath, items: result.items });
61441
62176
  else printResult(out2, result, resultPath);
@@ -61453,6 +62188,104 @@ async function statusCommand(flags, deps, out2) {
61453
62188
  else printResult(out2, result, resultPath);
61454
62189
  return result.complete ? 0 : 1;
61455
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
+ }
61456
62289
  async function runStoreIap(pos, flags, io, deps) {
61457
62290
  const out2 = new CommandOutput(io, flags.json === true);
61458
62291
  const sub = pos[2];
@@ -61465,18 +62298,10 @@ async function runStoreIap(pos, flags, io, deps) {
61465
62298
  return failFrom(e, out2);
61466
62299
  }
61467
62300
  }
61468
- function readJournalEntries(path2) {
61469
- try {
61470
- const doc = JSON.parse(readFileSync77(path2, "utf8"));
61471
- return Array.isArray(doc?.entries) ? doc.entries : [];
61472
- } catch {
61473
- return [];
61474
- }
61475
- }
61476
62301
  function fileJournal(path2) {
61477
62302
  return {
61478
- record: (entry) => writeFileAtomic(path2, `${JSON.stringify({ journalVersion: 1, entries: [...readJournalEntries(path2), entry] }, null, 2)}
61479
- `)
62303
+ record: (entry) => appendFileSync2(path2, `${JSON.stringify(entry)}
62304
+ `, { mode: 384 })
61480
62305
  };
61481
62306
  }
61482
62307
  function readLockSummary(path2) {
@@ -61529,26 +62354,35 @@ function defaultStoreIapDeps() {
61529
62354
  writeFileAtomic,
61530
62355
  resolveCredentials: ({ profileFlag, target }) => resolveIapCredentials({ profileFlag, target }),
61531
62356
  createApi: (key2) => createIapApi(createAscClient({ key: key2 })),
61532
- openJournal: (appleAppId) => fileJournal(join65(stateDir(appleAppId), "journal.json")),
62357
+ openJournal: (appleAppId) => fileJournal(join65(stateDir(appleAppId), "journal.jsonl")),
61533
62358
  acquireLock: (appleAppId) => acquireApplyLock(stateDir(appleAppId), appleAppId),
61534
62359
  canPrompt: () => process.stdin.isTTY === true,
62360
+ wixTransport: (siteId) => ownerTransport(siteId),
62361
+ profileTeam: (profile) => resolveStoreCredentials({ app: profile, cfg: {}, profileFlag: profile, env: {} }).team,
61535
62362
  confirm: confirmOnTerminal,
61536
62363
  now: () => /* @__PURE__ */ new Date(),
61537
62364
  sleep: (ms) => new Promise((resolve56) => setTimeout(resolve56, ms))
61538
62365
  };
61539
62366
  }
61540
- 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;
61541
62368
  var init_store_iap = __esm({
61542
62369
  "src/store-iap.ts"() {
61543
62370
  init_iap_api();
61544
62371
  init_iap_apply();
61545
62372
  init_iap_client();
62373
+ init_credential_profile();
61546
62374
  init_iap_credentials();
62375
+ init_iap_draft();
61547
62376
  init_iap_manifest();
62377
+ init_iap_pricing();
61548
62378
  init_iap_plan();
61549
62379
  init_redact();
61550
62380
  init_progress();
61551
- 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;
61552
62386
  UsageError = class extends Error {
61553
62387
  };
61554
62388
  CommandOutput = class {
@@ -61587,8 +62421,15 @@ var init_store_iap = __esm({
61587
62421
  }
61588
62422
  };
61589
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"];
61590
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";
61591
62431
  COMMANDS = {
62432
+ draft: draftCommand,
61592
62433
  plan: planCommand,
61593
62434
  apply: applyCommand,
61594
62435
  status: statusCommand
@@ -61973,7 +62814,7 @@ __export(preflight_app_exports, {
61973
62814
  profileAccount: () => profileAccount
61974
62815
  });
61975
62816
  import { readFileSync as readFileSync78, existsSync as existsSync67, readdirSync as readdirSync31, statSync as statSync18 } from "node:fs";
61976
- import { createHash as createHash33 } from "node:crypto";
62817
+ import { createHash as createHash34 } from "node:crypto";
61977
62818
  import { join as join66 } from "node:path";
61978
62819
  function analyze(projectYml, storeConfig, icon, account) {
61979
62820
  const f = [];
@@ -62262,7 +63103,7 @@ function placeholderHashes2() {
62262
63103
  const p = join66(templateIos, target, "Assets.xcassets", "AppIcon.appiconset", "icon_1024.png");
62263
63104
  try {
62264
63105
  if (!statSync18(p).isFile()) continue;
62265
- return [LEGACY_PLACEHOLDER_SHA2562, createHash33("sha256").update(readFileSync78(p)).digest("hex")];
63106
+ return [LEGACY_PLACEHOLDER_SHA2562, createHash34("sha256").update(readFileSync78(p)).digest("hex")];
62266
63107
  } catch {
62267
63108
  }
62268
63109
  }
@@ -62299,7 +63140,7 @@ async function appIconState(iosDir) {
62299
63140
  try {
62300
63141
  if (!statSync18(p).isFile()) continue;
62301
63142
  const bytes2 = readFileSync78(p);
62302
- const hash = createHash33("sha256").update(bytes2).digest("hex");
63143
+ const hash = createHash34("sha256").update(bytes2).digest("hex");
62303
63144
  const pixels = await pixelIssues(bytes2);
62304
63145
  return {
62305
63146
  exists: true,
@@ -62872,7 +63713,7 @@ var init_site_member = __esm({
62872
63713
  });
62873
63714
 
62874
63715
  // src/notify-ping-all.ts
62875
- import { createHash as createHash34 } from "node:crypto";
63716
+ import { createHash as createHash35 } from "node:crypto";
62876
63717
  async function listApprovedMembers(input) {
62877
63718
  const first = await scanMembers(input);
62878
63719
  const second = await scanMembers(input);
@@ -62926,7 +63767,7 @@ async function scanMembers(input) {
62926
63767
  }
62927
63768
  }
62928
63769
  function campaignRequestId(campaignId, memberId) {
62929
- 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);
62930
63771
  bytes2[6] = bytes2[6] & 15 | 80;
62931
63772
  bytes2[8] = bytes2[8] & 63 | 128;
62932
63773
  const hex2 = bytes2.toString("hex");
@@ -64086,7 +64927,7 @@ var init_copy_clean = __esm({
64086
64927
 
64087
64928
  // src/site-analyze/published-html.ts
64088
64929
  function textFromHtml(fragment) {
64089
- 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, " ");
64090
64931
  }
64091
64932
  function unescapeJsonString(s) {
64092
64933
  return s.replace(/\\"/g, '"').replace(/\\n/g, " ").replace(/\\\\/g, "\\");
@@ -64225,7 +65066,7 @@ function appsFromDefIds(ids) {
64225
65066
  return name ? { id, name } : { id };
64226
65067
  });
64227
65068
  }
64228
- 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;
64229
65070
  var init_published_html = __esm({
64230
65071
  "src/site-analyze/published-html.ts"() {
64231
65072
  init_rest();
@@ -64256,7 +65097,7 @@ var init_published_html = __esm({
64256
65097
  "14cc59bc-f0b7-15b8-e1c7-89ce41d0e0c9": "Wix Bookings Service Pages",
64257
65098
  "215238eb-22a5-4c36-9e7b-e7c08025e04e": "Wix Forms"
64258
65099
  };
64259
- ENTITIES = {
65100
+ ENTITIES2 = {
64260
65101
  "&amp;": "&",
64261
65102
  "&lt;": "<",
64262
65103
  "&gt;": ">",
@@ -73100,10 +73941,10 @@ async function installVerticals(msid, wanted, token, fetchImpl = fetch, timeoutM
73100
73941
  body = text8 ? JSON.parse(text8) : {};
73101
73942
  } catch {
73102
73943
  }
73103
- const nameOf = (appDefId) => wanted.find((w) => w.appDefId === appDefId)?.vertical ?? appDefId;
73944
+ const nameOf2 = (appDefId) => wanted.find((w) => w.appDefId === appDefId)?.vertical ?? appDefId;
73104
73945
  const out2 = { installed: [], errors: [], needsSetup: [] };
73105
73946
  for (const [appDefId, info2] of Object.entries(body.installedApps ?? {})) {
73106
- const vertical = nameOf(appDefId);
73947
+ const vertical = nameOf2(appDefId);
73107
73948
  out2.installed.push({ vertical, appDefId, version: info2?.version });
73108
73949
  const actions = (info2?.requiredActions ?? []).filter((a) => a?.actionType || a?.link);
73109
73950
  for (const a of actions) {
@@ -73116,7 +73957,7 @@ async function installVerticals(msid, wanted, token, fetchImpl = fetch, timeoutM
73116
73957
  }
73117
73958
  }
73118
73959
  for (const [appDefId, err] of Object.entries(body.installErrors ?? {})) {
73119
- out2.errors.push({ vertical: nameOf(appDefId), appDefId, message: err?.message ?? "unknown" });
73960
+ out2.errors.push({ vertical: nameOf2(appDefId), appDefId, message: err?.message ?? "unknown" });
73120
73961
  }
73121
73962
  return out2;
73122
73963
  }
@@ -73983,7 +74824,7 @@ var init_context = __esm({
73983
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";
73984
74825
  import { execFile as execFile2, execFileSync as execFileSync7, spawnSync as spawnSync5, spawn as spawn7 } from "node:child_process";
73985
74826
  import { AsyncLocalStorage } from "node:async_hooks";
73986
- import { createHash as createHash35, randomUUID as randomUUID8 } from "node:crypto";
74827
+ import { createHash as createHash36, randomUUID as randomUUID8 } from "node:crypto";
73987
74828
  import { tmpdir as tmpdir6, homedir as homedir10, hostname as hostname2 } from "node:os";
73988
74829
  import { join as join73, resolve as resolve55, relative as relative14, basename as basename17, dirname as dirname40 } from "node:path";
73989
74830
  import { fileURLToPath as fileURLToPath26 } from "node:url";
@@ -74552,11 +75393,11 @@ async function loadReleaseFromDir(app, dir) {
74552
75393
  const bundles = {};
74553
75394
  const seenSha = /* @__PURE__ */ new Set();
74554
75395
  for (const meta of Object.values(manifest.bundles ?? {})) {
74555
- const sha = meta?.sha256;
74556
- if (!sha || seenSha.has(sha)) continue;
74557
- seenSha.add(sha);
74558
- const b = await store.getBundle(app, sha);
74559
- 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");
74560
75401
  }
74561
75402
  const tokensBytes = await store.getTokens(app);
74562
75403
  const tokens2 = tokensBytes ? utf8Decode(tokensBytes) : void 0;
@@ -76623,16 +77464,28 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
76623
77464
  team (guideline 4.2.6), declared per-app as
76624
77465
  ios.credentialProfile in store.config.yaml and resolved
76625
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).
76626
77477
  himi store iap plan --manifest <file> --profile <name> --out <plan.json> [--json]
76627
77478
  READ-ONLY. Verify the owner's App Store Connect app and
76628
77479
  decide which selected digital products (one-time
76629
77480
  non-consumables) to register; writes a reviewable plan.
76630
- 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]
76631
77482
  create the planned products with draft metadata, an
76632
77483
  exact price and explicit territories. Re-reads Apple
76633
77484
  first, never edits a live product, never duplicates on
76634
77485
  a rerun; prompts unless --yes. Writes the Wix\u2192Apple
76635
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).
76636
77489
  himi store iap status --manifest <file> --profile <name> [--out <file>] [--json]
76637
77490
  READ-ONLY: what App Store Connect has for each product.
76638
77491
  No build, signing or repo checkout. Guide: himi docs iap-products
@@ -76918,7 +77771,7 @@ var COMMANDS2 = [
76918
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)" },
76919
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>]" },
76920
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]" },
76921
- { 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]" },
76922
77775
  { name: "guide", summary: "print the Himalaya overview + headless dev loop + available topics (offline)", required: "[--json]" },
76923
77776
  { name: "docs", summary: "print an authoring guide; no topic = list them (offline)", required: "[<topic>] [--json]" },
76924
77777
  { name: "components", summary: "print the SDUI component vocabulary + props; a name = that component (offline)", required: "[<Name>] [--json]" },
@@ -78143,7 +78996,7 @@ function templateNameError(name, from) {
78143
78996
  const composed2 = TEMPLATE_APP_PREFIX + name;
78144
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`;
78145
78998
  }
78146
- function materializeForPreview(appDir3, slug, displayName) {
78999
+ function materializeForPreview(appDir3, slug, displayName2) {
78147
79000
  if (!isTemplateSourceTree(appDir3)) return appDir3;
78148
79001
  const slugError = templateNameError(slug, "preview slug");
78149
79002
  if (slugError) throw Object.assign(new Error(slugError), { exitCode: 2 });
@@ -78175,7 +79028,7 @@ function materializeForPreview(appDir3, slug, displayName) {
78175
79028
  rmSync20(dest, { recursive: true, force: true });
78176
79029
  mkdirSync39(dest, { recursive: true });
78177
79030
  try {
78178
- materializeSource(forBuild, displayName, dest, slug);
79031
+ materializeSource(forBuild, displayName2, dest, slug);
78179
79032
  } catch (e) {
78180
79033
  rmSync20(dest, { recursive: true, force: true });
78181
79034
  throw e;
@@ -79365,7 +80218,7 @@ ${doNow.map((l) => ` ${l}`).join("\n")}`);
79365
80218
  try {
79366
80219
  const bytes2 = readFileSync88(iconPath);
79367
80220
  payload.icon = bytes2.toString("base64");
79368
- iconHash = createHash35("sha256").update(bytes2).digest("hex");
80221
+ iconHash = createHash36("sha256").update(bytes2).digest("hex");
79369
80222
  } catch {
79370
80223
  }
79371
80224
  }
@@ -81214,7 +82067,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
81214
82067
  let localSourceHash;
81215
82068
  try {
81216
82069
  const { collectAppSource: collectAppSource2, zipAppSource: zipAppSource2 } = await Promise.resolve().then(() => (init_app_source(), app_source_exports));
81217
- 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");
81218
82071
  } catch {
81219
82072
  }
81220
82073
  return runReleasePatch({
@@ -81606,9 +82459,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
81606
82459
  return 2;
81607
82460
  }
81608
82461
  const { packRelease: packRelease2, packReleaseToDir: packReleaseToDir2 } = await Promise.resolve().then(() => (init_pack(), pack_exports));
81609
- const displayName = str9(flags.name);
82462
+ const displayName2 = str9(flags.name);
81610
82463
  const launchMode = flags.snapshot === true ? "snapshot" : "live";
81611
- const packOpts = { createdBy: `himi-cli ${cliVersion()}`, launchMode, ...displayName ? { displayName } : {} };
82464
+ const packOpts = { createdBy: `himi-cli ${cliVersion()}`, launchMode, ...displayName2 ? { displayName: displayName2 } : {} };
81612
82465
  const writePack = (root, appName2, out2, source) => format === "dir" ? packReleaseToDir2(root, appName2, out2, packOpts) : packRelease2(root, appName2, out2, { ...packOpts, ...source ? { source } : {} });
81613
82466
  const releaseDir = str9(flags["release-dir"]);
81614
82467
  if (releaseDir) {
@@ -82860,7 +83713,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
82860
83713
  });
82861
83714
  }
82862
83715
  const bytes2 = readFileSync88(file);
82863
- const sha = createHash35("sha256").update(bytes2).digest("hex");
83716
+ const sha2 = createHash36("sha256").update(bytes2).digest("hex");
82864
83717
  const forScreens = (str9(flags["for-screen"]) ?? "").split(",").map((s) => s.trim()).filter(Boolean);
82865
83718
  if (flags["no-rebind"] !== void 0 && flags["no-rebind"] !== true) {
82866
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 }));
@@ -82886,7 +83739,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
82886
83739
  ok: false,
82887
83740
  error: "orphan_bundle",
82888
83741
  app,
82889
- sha256: sha,
83742
+ sha256: sha2,
82890
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.",
82891
83744
  hint: "name the screens to repoint: --for-screen cart,orders. Pass --no-rebind if you really mean to upload bytes nothing references yet.",
82892
83745
  alternative: "`himi push --only bundle:<name>` infers this from the package and drags the referencing screens in for you.",
@@ -82895,9 +83748,9 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
82895
83748
  return 2;
82896
83749
  }
82897
83750
  const patch = {
82898
- bundles: { [sha]: bytes2.toString("base64") }
83751
+ bundles: { [sha2]: bytes2.toString("base64") }
82899
83752
  };
82900
- const touched = [`bundle:${sha.slice(0, 12)}`];
83753
+ const touched = [`bundle:${sha2.slice(0, 12)}`];
82901
83754
  const rebound = {};
82902
83755
  const bundleBase = await fetchReleaseManifest(client2, app, fromRelease);
82903
83756
  if ("error" in bundleBase) {
@@ -82930,8 +83783,8 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
82930
83783
  }, null, 2));
82931
83784
  return 1;
82932
83785
  }
82933
- patch.screens[id] = { descriptor: got.text, bundle: sha };
82934
- rebound[id] = sha;
83786
+ patch.screens[id] = { descriptor: got.text, bundle: sha2 };
83787
+ rebound[id] = sha2;
82935
83788
  touched.push(`screen:${id}`);
82936
83789
  }
82937
83790
  }
@@ -82939,7 +83792,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
82939
83792
  ...common,
82940
83793
  fromRelease: baseId,
82941
83794
  plan: { patch, touched, included: [], rebound },
82942
- extra: { kind, file: { path: file, bytes: bytes2.length, sha256: sha } }
83795
+ extra: { kind, file: { path: file, bytes: bytes2.length, sha256: sha2 } }
82943
83796
  });
82944
83797
  }
82945
83798
  // OPERATIONAL FLAGS (RFC 0013) — the surface the admin portal's FlagsPanel used to own.