@wix/himalaya-cli 0.980.0 → 0.982.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
@@ -1901,6 +1901,20 @@ var init_himi_report = __esm({
1901
1901
  }
1902
1902
  });
1903
1903
 
1904
+ // ../../core/serve-storage/src/widget-identity.ts
1905
+ function stableWidgets(widgets) {
1906
+ if (!widgets || typeof widgets !== "object" || Array.isArray(widgets)) return widgets;
1907
+ return Object.fromEntries(Object.entries(widgets).map(([id, value]) => {
1908
+ if (!value || typeof value !== "object" || Array.isArray(value)) return [id, value];
1909
+ const { contentEtag: _regenerated, ...stable } = value;
1910
+ return [id, stable];
1911
+ }));
1912
+ }
1913
+ var init_widget_identity = __esm({
1914
+ "../../core/serve-storage/src/widget-identity.ts"() {
1915
+ }
1916
+ });
1917
+
1904
1918
  // ../../core/dev-server/src/wix-placeholders.ts
1905
1919
  var PLACEHOLDER_CLIENT_ID;
1906
1920
  var init_wix_placeholders = __esm({
@@ -5290,6 +5304,7 @@ function resolveTranslation(value, catalog, sourceCatalog) {
5290
5304
  var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
5291
5305
  var init_bindings = __esm({
5292
5306
  "../../core/ts/src/bindings.ts"() {
5307
+ "use strict";
5293
5308
  BINDING_SIGILS = ["service", "flag", "item", "t"];
5294
5309
  BINDING_SIGIL_RE = new RegExp(
5295
5310
  `^\\$(?:${BINDING_SIGILS.filter((s) => s !== "t").join("|")})\\b|^\\$t:`
@@ -7273,9 +7288,10 @@ function pathCrossesOpaqueAuthorJson(path) {
7273
7288
  function nativeRequiredFindings(descriptor) {
7274
7289
  const out = [];
7275
7290
  const shapeOf = (ref) => ref.startsWith("list:") ? "an array" : ref.startsWith("map:") ? "an object keyed by name" : "an object";
7291
+ const kindOf = (value) => Array.isArray(value) ? "an array" : value === null ? "null" : typeof value === "object" ? "an object" : `a ${typeof value} (${JSON.stringify(value)})`;
7276
7292
  const reportKind = (ref, value, path, from) => {
7277
7293
  const cls = bareNestedClass(ref);
7278
- const got = Array.isArray(value) ? "an array" : value === null ? "null" : typeof value === "object" ? "an object" : `a ${typeof value} (${JSON.stringify(value)})`;
7294
+ const got = kindOf(value);
7279
7295
  const expected = ref.startsWith("list:") ? "Expected JsonArray" : `Expected JsonObject for type with serial name 'io.himalaya.core.${cls}'`;
7280
7296
  out.push({
7281
7297
  path,
@@ -7321,6 +7337,25 @@ function nativeRequiredFindings(descriptor) {
7321
7337
  const inner = own(bag, name);
7322
7338
  if (inner !== void 0 && inner !== null) checkNested(inner, sub, `${path}.${name}`, from);
7323
7339
  }
7340
+ if (cls === "Action") checkActionParams(own(bag, "params"), `${path}.params`, from);
7341
+ };
7342
+ const checkActionParams = (params, path, from) => {
7343
+ if (params === void 0 || params === null) return;
7344
+ const cost = "Native decode rejects it and drops the WHOLE screen from the seed, while web renders it fine";
7345
+ if (typeof params !== "object" || Array.isArray(params)) {
7346
+ out.push({
7347
+ path,
7348
+ message: `Action.params must be an object of strings, but the descriptor has ${kindOf(params)} ("Expected JsonObject"). ${cost} (reached from ${from})`
7349
+ });
7350
+ return;
7351
+ }
7352
+ for (const [key2, value] of Object.entries(params)) {
7353
+ if (typeof value === "string" || value === void 0) continue;
7354
+ out.push({
7355
+ path: `${path}.${key2}`,
7356
+ message: `Action.params values must be strings, but params.${key2} is ${kindOf(value)} ("Expected JsonPrimitive at ${key2}" on Android, typeMismatch on iOS). ${cost}. Flatten it into string keys of params, or move structured data to Action.args (reached from ${from})`
7357
+ });
7358
+ }
7324
7359
  };
7325
7360
  const walk2 = (node, at) => {
7326
7361
  if (Array.isArray(node)) {
@@ -7422,6 +7457,7 @@ function componentPropFindings(descriptor) {
7422
7457
  out.push({
7423
7458
  path,
7424
7459
  kind: "decode",
7460
+ ...inArray ? { authoringOnly: true } : {},
7425
7461
  // TWO different defects share this branch, and they need DIFFERENT prose.
7426
7462
  //
7427
7463
  // For an ordinary typed slot the sentinel really is a screen-killer: kotlinx
@@ -8060,6 +8096,7 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
8060
8096
  if (!node || typeof node !== "object") return;
8061
8097
  for (const [k, val] of Object.entries(node)) {
8062
8098
  if (isOpaqueAuthorJson(k, val, at === "")) continue;
8099
+ if (k === "params" && typeof node.actionId === "string") continue;
8063
8100
  const here = at ? `${at}.${k}` : k;
8064
8101
  const allowed2 = own(NATIVE_PROP_ENUMS, k);
8065
8102
  if (typeof val === "string" && enums.has(k) && DYNAMIC_PLACEHOLDER.test(val)) {
@@ -8067,6 +8104,7 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
8067
8104
  severity: "error",
8068
8105
  pass: "strict-enum",
8069
8106
  path: here,
8107
+ nativeDecode: true,
8070
8108
  message: `enum field "${k}" carries a dynamic placeholder ("${val}") \u2014 native decode fails BEFORE substitution and kills the screen; use a literal (e.g. the spacer leadFill pattern for per-row alignment)`
8071
8109
  });
8072
8110
  } else if (allowed2 && val != null && typeof val !== "string") {
@@ -8074,6 +8112,7 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
8074
8112
  severity: "error",
8075
8113
  pass: "strict-enum",
8076
8114
  path: here,
8115
+ nativeDecode: true,
8077
8116
  message: `enum field "${k}" has non-string value ${JSON.stringify(val)} \u2014 native decode rejects it (allowed: ${[...allowed2].join("|")}) and drops the whole screen`
8078
8117
  });
8079
8118
  } else if (typeof val === "string" && !DYNAMIC_PLACEHOLDER.test(val)) {
@@ -8082,6 +8121,7 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
8082
8121
  severity: "error",
8083
8122
  pass: "strict-enum",
8084
8123
  path: here,
8124
+ nativeDecode: true,
8085
8125
  message: `enum field "${k}" has unknown value "${val}" \u2014 native decode rejects it (allowed: ${[...allowed2].join("|")}) and drops the whole screen; use one of those literals (HStack/VStack alignment: leading = top)`
8086
8126
  });
8087
8127
  }
@@ -8105,6 +8145,7 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
8105
8145
  issue2.pass = "strict-enum";
8106
8146
  issue2.path = finding.path;
8107
8147
  issue2.message = finding.message;
8148
+ if (!("authoringOnly" in finding && finding.authoringOnly)) issue2.nativeDecode = true;
8108
8149
  if (!duplicate) issues.push(issue2);
8109
8150
  for (const extra of alsoAboutThisField) if (!consumed.has(extra)) superseded.add(extra);
8110
8151
  }
@@ -11670,11 +11711,16 @@ var init_catalog = __esm({
11670
11711
  // ../font-bake/plan-fonts.ts
11671
11712
  function demandFromTokens(tokens) {
11672
11713
  const demand = /* @__PURE__ */ new Map();
11673
- for (const token of Object.values(tokens.typography ?? {})) {
11674
- const family = token?.family;
11675
- if (typeof family !== "string" || family.length === 0) continue;
11676
- const weight = typeof token?.weight === "number" ? token.weight : 400;
11677
- demand.set(family, (demand.get(family) ?? /* @__PURE__ */ new Set()).add(weight));
11714
+ const add = (family, weight) => {
11715
+ if (typeof family !== "string" || family.length === 0) return;
11716
+ demand.set(family, (demand.get(family) ?? /* @__PURE__ */ new Set()).add(typeof weight === "number" ? weight : 400));
11717
+ };
11718
+ const base = tokens.typography ?? {};
11719
+ for (const token of Object.values(base)) add(token?.family, token?.weight);
11720
+ for (const overlay of Object.values(tokens.idiom ?? {})) {
11721
+ for (const [role, token] of Object.entries(overlay?.typography ?? {})) {
11722
+ add(token?.family ?? base[role]?.family, token?.weight ?? base[role]?.weight);
11723
+ }
11678
11724
  }
11679
11725
  return demand;
11680
11726
  }
@@ -16207,7 +16253,7 @@ function assignStableIds(descriptor) {
16207
16253
  used.add(out);
16208
16254
  return out;
16209
16255
  };
16210
- const assign = (node, path) => {
16256
+ const assign2 = (node, path) => {
16211
16257
  const base = node.id && node.id.length > 0 ? node.id : path;
16212
16258
  node.id = uniquify(base);
16213
16259
  const typeCounts = {};
@@ -16216,12 +16262,12 @@ function assignStableIds(descriptor) {
16216
16262
  const n = typeCounts[t] ?? 0;
16217
16263
  typeCounts[t] = n + 1;
16218
16264
  const seg = n === 0 ? t : `${t}.${n}`;
16219
- assign(child, `${path}/${seg}`);
16265
+ assign2(child, `${path}/${seg}`);
16220
16266
  });
16221
16267
  };
16222
- if (isNode(descriptor.payload)) assign(descriptor.payload, descriptor.screenId);
16223
- if (isNode(descriptor.stickyHeader)) assign(descriptor.stickyHeader, `${descriptor.screenId}/stickyHeader`);
16224
- if (isNode(descriptor.stickyFooter)) assign(descriptor.stickyFooter, `${descriptor.screenId}/stickyFooter`);
16268
+ if (isNode(descriptor.payload)) assign2(descriptor.payload, descriptor.screenId);
16269
+ if (isNode(descriptor.stickyHeader)) assign2(descriptor.stickyHeader, `${descriptor.screenId}/stickyHeader`);
16270
+ if (isNode(descriptor.stickyFooter)) assign2(descriptor.stickyFooter, `${descriptor.screenId}/stickyFooter`);
16225
16271
  }
16226
16272
  var init_stable_ids = __esm({
16227
16273
  "../../core/dev-server/src/stable-ids.ts"() {
@@ -17606,6 +17652,70 @@ var init_env = __esm({
17606
17652
  }
17607
17653
  });
17608
17654
 
17655
+ // ../../core/ts/src/idiom.ts
17656
+ function authored(overlays, name) {
17657
+ return typeof name === "string" && overlays !== void 0 && Object.hasOwn(overlays, name) && overlays[name] !== null && typeof overlays[name] === "object" && !Array.isArray(overlays[name]);
17658
+ }
17659
+ function selectIdiomOverlay(doc, ctx = {}) {
17660
+ const policy = doc.idiomPolicy;
17661
+ const overlays = doc.idiom;
17662
+ const isRecord3 = (v) => !!v && typeof v === "object" && !Array.isArray(v);
17663
+ if (!isRecord3(policy) || !isRecord3(overlays)) return void 0;
17664
+ const kind = policy.policy ?? "follow-surface";
17665
+ if (kind === "none") return void 0;
17666
+ if (kind === "pinned") return authored(overlays, policy.pin) ? policy.pin : void 0;
17667
+ if (kind !== "follow-surface") return void 0;
17668
+ const surfaces = policy.surfaces;
17669
+ const candidates = [
17670
+ ctx.surface !== void 0 && isRecord3(surfaces) && Object.hasOwn(surfaces, ctx.surface) ? surfaces[ctx.surface] : void 0,
17671
+ ...ctx.native ?? [],
17672
+ policy.fallback
17673
+ ];
17674
+ return candidates.find((c) => authored(overlays, c));
17675
+ }
17676
+ function isPlainObject(v) {
17677
+ return v !== null && typeof v === "object" && !Array.isArray(v);
17678
+ }
17679
+ function deepMerge2(base, overlay) {
17680
+ if (!isPlainObject(overlay) || !isPlainObject(base)) return overlay;
17681
+ const out = { ...base };
17682
+ for (const [key2, value] of Object.entries(overlay)) {
17683
+ if (UNSAFE_KEYS.has(key2)) continue;
17684
+ out[key2] = Object.hasOwn(base, key2) ? deepMerge2(base[key2], value) : value;
17685
+ }
17686
+ return out;
17687
+ }
17688
+ function mergeIdiomOverlay(base, overlay) {
17689
+ const merged = deepMerge2(base, overlay);
17690
+ const baseColors = base.colors;
17691
+ const overlayColors = overlay.colors;
17692
+ if (isPlainObject(baseColors) && isPlainObject(overlayColors)) {
17693
+ const colors = { ...merged.colors };
17694
+ for (const [name, value] of Object.entries(overlayColors)) {
17695
+ if (UNSAFE_KEYS.has(name)) continue;
17696
+ const was = Object.hasOwn(baseColors, name) ? baseColors[name] : void 0;
17697
+ if (typeof was === "string" && isPlainObject(value)) colors[name] = { light: was, ...value };
17698
+ }
17699
+ merged.colors = colors;
17700
+ }
17701
+ return merged;
17702
+ }
17703
+ function resolveIdiom(tokens, ctx = {}) {
17704
+ const doc = tokens;
17705
+ const name = selectIdiomOverlay(doc, ctx);
17706
+ if (name === void 0) return tokens;
17707
+ const merged = mergeIdiomOverlay(tokens, doc.idiom[name]);
17708
+ const { idiomPolicy: _applied, ...final } = merged;
17709
+ return final;
17710
+ }
17711
+ var UNSAFE_KEYS;
17712
+ var init_idiom = __esm({
17713
+ "../../core/ts/src/idiom.ts"() {
17714
+ "use strict";
17715
+ UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
17716
+ }
17717
+ });
17718
+
17609
17719
  // ../../core/web-renderer/src/tokens.ts
17610
17720
  function isSerifFamily(family) {
17611
17721
  return !!family && SERIF_FAMILY_RE.test(family);
@@ -17731,7 +17841,8 @@ function resolveSchemeColor(o, scheme) {
17731
17841
  if (typeof o?.light === "string") return o.light;
17732
17842
  return typeof o?.dark === "string" ? o.dark : void 0;
17733
17843
  }
17734
- function makeTokenResolvers(tokens, scheme) {
17844
+ function makeTokenResolvers(source, scheme, idiom = WEB_IDIOM_CONTEXT) {
17845
+ const tokens = resolveIdiom(source, idiom);
17735
17846
  const color = (name, fallback = "#1a1a1a") => {
17736
17847
  if (name == null) return fallback;
17737
17848
  if (typeof name !== "string") {
@@ -17870,13 +17981,15 @@ function makeTokenResolvers(tokens, scheme) {
17870
17981
  scheme
17871
17982
  };
17872
17983
  }
17873
- var REM_ROOT_PX, pxToRem, DEFAULT_FONT_STACK, SERIF_FAMILY_RE;
17984
+ var REM_ROOT_PX, pxToRem, DEFAULT_FONT_STACK, SERIF_FAMILY_RE, WEB_IDIOM_CONTEXT;
17874
17985
  var init_tokens2 = __esm({
17875
17986
  "../../core/web-renderer/src/tokens.ts"() {
17987
+ init_idiom();
17876
17988
  REM_ROOT_PX = 16;
17877
17989
  pxToRem = (px) => `${px / REM_ROOT_PX}rem`;
17878
17990
  DEFAULT_FONT_STACK = '"Wix Madefor Text", system-ui';
17879
17991
  SERIF_FAMILY_RE = /\b(georgia|times|garamond|palatino|baskerville|didot|charter|bodoni|caslon|cambria|constantia|rockwell|playfair|merriweather|lora|literata|spectral|serif)\b/i;
17992
+ WEB_IDIOM_CONTEXT = { surface: "desktop" };
17880
17993
  }
17881
17994
  });
17882
17995
 
@@ -28862,6 +28975,7 @@ var init_src5 = __esm({
28862
28975
  init_widget_vocabulary();
28863
28976
  init_preview_surfaces();
28864
28977
  init_tokens();
28978
+ init_idiom();
28865
28979
  init_tier_caps();
28866
28980
  init_flags();
28867
28981
  init_flags_engine();
@@ -30225,11 +30339,11 @@ import { readFileSync as readFileSync40, existsSync as existsSync35, watch, mkdi
30225
30339
  import { extname, resolve as resolve30, normalize as normalize3, sep as sep7, join as join34 } from "node:path";
30226
30340
  import { createHash as createHash15, randomUUID } from "node:crypto";
30227
30341
  import { fileURLToPath as fileURLToPath18, pathToFileURL as pathToFileURL6 } from "node:url";
30228
- function deepMerge2(base, patch) {
30342
+ function deepMerge3(base, patch) {
30229
30343
  if (patch === null || typeof patch !== "object" || Array.isArray(patch)) return patch;
30230
30344
  const out = Array.isArray(base) ? [...base] : { ...base };
30231
30345
  for (const [k, v] of Object.entries(patch)) {
30232
- out[k] = k in out ? deepMerge2(out[k], v) : v;
30346
+ out[k] = k in out ? deepMerge3(out[k], v) : v;
30233
30347
  }
30234
30348
  return out;
30235
30349
  }
@@ -30290,15 +30404,15 @@ function effectiveAppConfigOverride(state) {
30290
30404
  }
30291
30405
  }
30292
30406
  if (!gitOverlay && !state.appConfigOverride) return void 0;
30293
- return deepMerge2(
30294
- deepMerge2({}, gitOverlay ?? {}),
30407
+ return deepMerge3(
30408
+ deepMerge3({}, gitOverlay ?? {}),
30295
30409
  state.appConfigOverride ?? {}
30296
30410
  );
30297
30411
  }
30298
30412
  function effectiveAppTokens(req, state) {
30299
30413
  const built = readAppTokens(state);
30300
30414
  if (!built) return null;
30301
- let tokens = state.tokensOverride ? deepMerge2(built, state.tokensOverride) : built;
30415
+ let tokens = state.tokensOverride ? deepMerge3(built, state.tokensOverride) : built;
30302
30416
  let motionToggles = [];
30303
30417
  try {
30304
30418
  const motionDefs = state.flagStore.list().filter((d) => d.enabled && d.key.startsWith("motion_"));
@@ -30315,6 +30429,9 @@ function effectiveAppTokens(req, state) {
30315
30429
  const fontFaces = servedFontFaces(state.app.dir, tokens);
30316
30430
  if (fontFaces) tokens = { ...tokens, fontFaces };
30317
30431
  }
30432
+ const config = state.app?.module.config;
30433
+ const idiomPolicy = config && "idiom" in config ? config.idiom : void 0;
30434
+ if (idiomPolicy !== void 0) tokens = { ...tokens, idiomPolicy };
30318
30435
  const body = JSON.stringify(tokens);
30319
30436
  return { tokens, etag: makeEtag(body), motionToggles };
30320
30437
  }
@@ -31137,6 +31254,8 @@ async function reloadAppModuleNow(state, opts) {
31137
31254
  }
31138
31255
  const oldMeta = current.meta;
31139
31256
  const oldNav = JSON.stringify(deriveNavigationConfig(oldMeta) ?? null);
31257
+ const idiomOf = (m) => JSON.stringify(m.config.idiom ?? null);
31258
+ const oldIdiom = idiomOf(current.module);
31140
31259
  state.app = {
31141
31260
  name: current.name,
31142
31261
  module: next,
@@ -31158,6 +31277,9 @@ async function reloadAppModuleNow(state, opts) {
31158
31277
  if (oldNav !== newNav) {
31159
31278
  publishSSE(state, "config.invalidated", { reason: opts.reason, at: Date.now() });
31160
31279
  }
31280
+ if (idiomOf(next) !== oldIdiom) {
31281
+ publishSSE(state, "tokens.changed", { reason: opts.reason, at: Date.now() });
31282
+ }
31161
31283
  console.log(`[himalaya-dev] reloaded app module in-process: ${current.name} (${opts.reason})`);
31162
31284
  return { ok: true };
31163
31285
  } catch (err) {
@@ -31288,7 +31410,7 @@ async function handleFlagsEval(req, res, state) {
31288
31410
  function currentFlagConfig(state) {
31289
31411
  if (!state.app) return void 0;
31290
31412
  const base = deriveNavigationConfig(state.app.meta);
31291
- const merged = state.appConfigOverride ? deepMerge2(base, state.appConfigOverride) : base;
31413
+ const merged = state.appConfigOverride ? deepMerge3(base, state.appConfigOverride) : base;
31292
31414
  return merged.flagConfig;
31293
31415
  }
31294
31416
  function attachAssignments(descriptor, state, ctx, overrides) {
@@ -32065,7 +32187,7 @@ async function handleAdminPlayground(req, res, state) {
32065
32187
  if (path === "/v1/admin/app-config" && state.app) {
32066
32188
  const built = deriveNavigationConfig(state.app.meta);
32067
32189
  if (req.method === "GET") {
32068
- const effective = state.appConfigOverride ? deepMerge2(built, state.appConfigOverride) : built;
32190
+ const effective = state.appConfigOverride ? deepMerge3(built, state.appConfigOverride) : built;
32069
32191
  return sendJson(res, 200, { source: state.appConfigOverride ? "override" : "built", built, effective });
32070
32192
  }
32071
32193
  if (req.method === "PUT") {
@@ -32077,9 +32199,9 @@ async function handleAdminPlayground(req, res, state) {
32077
32199
  }
32078
32200
  if (!patch || typeof patch !== "object" || Array.isArray(patch)) return sendJson(res, 400, { error: "object_required" });
32079
32201
  const nav = isNavControlPatch(patch);
32080
- state.appConfigOverride = nav && state.appConfigOverride ? deepMerge2(state.appConfigOverride, patch) : patch;
32202
+ state.appConfigOverride = nav && state.appConfigOverride ? deepMerge3(state.appConfigOverride, patch) : patch;
32081
32203
  publishSSE(state, "config.invalidated", { reason: nav ? "navigate" : "admin_override", at: Date.now() });
32082
- return sendJson(res, 200, { status: "ok", effective: deepMerge2(built, state.appConfigOverride) });
32204
+ return sendJson(res, 200, { status: "ok", effective: deepMerge3(built, state.appConfigOverride) });
32083
32205
  }
32084
32206
  if (req.method === "DELETE") {
32085
32207
  const existed = state.appConfigOverride !== void 0;
@@ -32090,7 +32212,7 @@ async function handleAdminPlayground(req, res, state) {
32090
32212
  if (path === "/v1/admin/tokens" && state.app) {
32091
32213
  const built = readAppTokens(state) ?? {};
32092
32214
  if (req.method === "GET") {
32093
- const effective = state.tokensOverride ? deepMerge2(built, state.tokensOverride) : built;
32215
+ const effective = state.tokensOverride ? deepMerge3(built, state.tokensOverride) : built;
32094
32216
  return sendJson(res, 200, { source: state.tokensOverride ? "override" : "built", built, effective });
32095
32217
  }
32096
32218
  if (req.method === "PUT") {
@@ -32103,7 +32225,7 @@ async function handleAdminPlayground(req, res, state) {
32103
32225
  if (!patch || typeof patch !== "object" || Array.isArray(patch)) return sendJson(res, 400, { error: "object_required" });
32104
32226
  state.tokensOverride = patch;
32105
32227
  publishSSE(state, "tokens.changed", { reason: "admin_override", at: Date.now() });
32106
- return sendJson(res, 200, { status: "ok", effective: deepMerge2(built, state.tokensOverride) });
32228
+ return sendJson(res, 200, { status: "ok", effective: deepMerge3(built, state.tokensOverride) });
32107
32229
  }
32108
32230
  if (req.method === "DELETE") {
32109
32231
  const existed = state.tokensOverride !== void 0;
@@ -37273,10 +37395,11 @@ function notCarriedDiff(local, base) {
37273
37395
  const CARRIED = /* @__PURE__ */ new Set(["screens", "bundles", "tokensHash", "appConfigHash"]);
37274
37396
  const PROVENANCE = /* @__PURE__ */ new Set(["releaseId", "gitSha", "shellSha", "label", "notes", "publishedAt", "publishedBy", "attestation", "forcedPastErrors", "app", "appSourceHash"]);
37275
37397
  const REPORTED_IN_DETAIL = /* @__PURE__ */ new Set(["assets", "minBinaryVersion", "iconHash"]);
37398
+ const comparable = (key2, value) => key2 === "widgets" ? stableWidgets(value) : value;
37276
37399
  for (const key2 of /* @__PURE__ */ new Set([...Object.keys(local), ...Object.keys(base)])) {
37277
37400
  if (CARRIED.has(key2) || PROVENANCE.has(key2) || REPORTED_IN_DETAIL.has(key2)) continue;
37278
- const l = local[key2] ?? null;
37279
- const b = base[key2] ?? null;
37401
+ const l = comparable(key2, local[key2] ?? null);
37402
+ const b = comparable(key2, base[key2] ?? null);
37280
37403
  const ls = JSON.stringify(l) ?? "null";
37281
37404
  const bs = JSON.stringify(b) ?? "null";
37282
37405
  if (ls === bs) continue;
@@ -37325,6 +37448,7 @@ function patchHonesty(opts) {
37325
37448
  var SELECTOR_KINDS;
37326
37449
  var init_release_patch = __esm({
37327
37450
  "src/release-patch.ts"() {
37451
+ init_widget_identity();
37328
37452
  SELECTOR_KINDS = ["changed", "screen:<id|glob>", "bundle:<name>", "tokens", "app-config"];
37329
37453
  }
37330
37454
  });
@@ -37485,7 +37609,7 @@ async function navIssues(dir, strict) {
37485
37609
  const nav = cfg.navigation;
37486
37610
  pushRouteIssues(cfg.push, nav, warn);
37487
37611
  if (nav === void 0 || nav === null) return issues;
37488
- if (!isPlainObject(nav)) {
37612
+ if (!isPlainObject2(nav)) {
37489
37613
  error("nav-not-an-object", `navigation must be an object (got ${Array.isArray(nav) ? "array" : typeof nav}).`);
37490
37614
  return issues;
37491
37615
  }
@@ -37515,10 +37639,10 @@ async function navIssues(dir, strict) {
37515
37639
  for (const key2 of ITEMS_BLOCKS) {
37516
37640
  const block = nav[key2];
37517
37641
  if (block === void 0) continue;
37518
- if (!isPlainObject(block) || !Array.isArray(block.items)) {
37642
+ if (!isPlainObject2(block) || !Array.isArray(block.items)) {
37519
37643
  error(
37520
37644
  "nav-chrome-shape",
37521
- `navigation.${key2} must be an object with an \`items\` array \u2014 \`${key2}: { items: [ \u2026 ] }\` (got ${Array.isArray(block) ? "an array" : isPlainObject(block) ? "an object with no `items` array" : typeof block}). The server forwards this verbatim, so the shell finds no destinations and draws none.`
37645
+ `navigation.${key2} must be an object with an \`items\` array \u2014 \`${key2}: { items: [ \u2026 ] }\` (got ${Array.isArray(block) ? "an array" : isPlainObject2(block) ? "an object with no `items` array" : typeof block}). The server forwards this verbatim, so the shell finds no destinations and draws none.`
37522
37646
  );
37523
37647
  }
37524
37648
  }
@@ -37534,7 +37658,7 @@ async function navIssues(dir, strict) {
37534
37658
  if (effectiveBottomTabs && !declaresOtherChrome && itemCount(nav.tabBar) === 0) {
37535
37659
  const linkables = Array.isArray(nav.linkableScreens) ? nav.linkableScreens : [];
37536
37660
  const topLevel = new Set(
37537
- linkables.filter((s) => isPlainObject(s) && !s.parentScreenId).map((s) => String(s.screenId ?? ""))
37661
+ linkables.filter((s) => isPlainObject2(s) && !s.parentScreenId).map((s) => String(s.screenId ?? ""))
37538
37662
  );
37539
37663
  topLevel.delete("");
37540
37664
  if (topLevel.size > 1) {
@@ -37552,8 +37676,8 @@ async function navIssues(dir, strict) {
37552
37676
  return issues;
37553
37677
  }
37554
37678
  function pushRouteIssues(push, nav, warn) {
37555
- if (!isPlainObject(push) || push.payloadAdapter !== "wix-ping") return;
37556
- const routes = Array.isArray(push.routes) ? push.routes.filter(isPlainObject) : [];
37679
+ if (!isPlainObject2(push) || push.payloadAdapter !== "wix-ping") return;
37680
+ const routes = Array.isArray(push.routes) ? push.routes.filter(isPlainObject2) : [];
37557
37681
  if (routes.length === 0) {
37558
37682
  warn(
37559
37683
  "push-routes-missing",
@@ -37561,10 +37685,10 @@ function pushRouteIssues(push, nav, warn) {
37561
37685
  );
37562
37686
  return;
37563
37687
  }
37564
- const linkables = isPlainObject(nav) ? nav.linkableScreens : void 0;
37688
+ const linkables = isPlainObject2(nav) ? nav.linkableScreens : void 0;
37565
37689
  if (!Array.isArray(linkables)) return;
37566
37690
  const declared = new Set(
37567
- linkables.filter(isPlainObject).map((s) => String(s.screenId ?? ""))
37691
+ linkables.filter(isPlainObject2).map((s) => String(s.screenId ?? ""))
37568
37692
  );
37569
37693
  for (const route of routes) {
37570
37694
  const screenId = /:\/\/screen\/([^?]+)/.exec(String(route.link ?? ""))?.[1];
@@ -37576,7 +37700,7 @@ function pushRouteIssues(push, nav, warn) {
37576
37700
  }
37577
37701
  }
37578
37702
  }
37579
- var NAVIGATION_KEYS, NAV_KINDS, ITEMS_BLOCKS, SERVED_ONLY_NAV_KEYS, MISPLACED_TOP_LEVEL_KEYS, isPlainObject, itemCount;
37703
+ var NAVIGATION_KEYS, NAV_KINDS, ITEMS_BLOCKS, SERVED_ONLY_NAV_KEYS, MISPLACED_TOP_LEVEL_KEYS, isPlainObject2, itemCount;
37580
37704
  var init_nav_lint = __esm({
37581
37705
  "src/nav-lint.ts"() {
37582
37706
  init_app_config_loader();
@@ -37621,8 +37745,8 @@ var init_nav_lint = __esm({
37621
37745
  "deepLinkPatterns",
37622
37746
  "linkableScreens"
37623
37747
  ];
37624
- isPlainObject = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
37625
- itemCount = (v) => isPlainObject(v) && Array.isArray(v.items) ? v.items.length : 0;
37748
+ isPlainObject2 = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
37749
+ itemCount = (v) => isPlainObject2(v) && Array.isArray(v.items) ? v.items.length : 0;
37626
37750
  }
37627
37751
  });
37628
37752
 
@@ -43361,29 +43485,49 @@ function scriptNonce() {
43361
43485
  function previewCsp(nonce) {
43362
43486
  return `script-src 'self' 'nonce-${nonce}'; base-uri 'self'; object-src 'none'`;
43363
43487
  }
43364
- function indexHtml(asset, pathname) {
43488
+ function inlineJson(value) {
43489
+ return JSON.stringify(value).replace(/</g, "\\u003c");
43490
+ }
43491
+ function previewCredentialsJs(creds) {
43492
+ return `(function(){var d=${inlineJson(creds)};window.__himalayaPreview__=d;try{sessionStorage.setItem("himalaya.preview."+d.app,JSON.stringify(d))}catch(e){}try{var u=new URL(location.href);if(u.searchParams.has("h")){u.searchParams.delete("h");history.replaceState(null,"",u.pathname+u.search+u.hash)}}catch(e){}})();`;
43493
+ }
43494
+ function stripHandoverParamJs() {
43495
+ return '(function(){window.__himalayaHandoverFailed__=true;try{var u=new URL(location.href);if(u.searchParams.has("h")){u.searchParams.delete("h");history.replaceState(null,"",u.pathname+u.search+u.hash)}}catch(e){}})();';
43496
+ }
43497
+ function indexHtml(asset, pathname, creds, handoverAttempted = false) {
43365
43498
  void pathname;
43366
43499
  const nonce = scriptNonce();
43367
43500
  const raw = new TextDecoder().decode(assetBytes(asset));
43368
- const html = raw.replace(/(=|url\()(["']?)\/preview\//g, "$1$2").replace(/<head(\s[^>]*)?>/i, (head) => `${head}<script nonce="${nonce}">${PREVIEW_BASE_JS}</script>`);
43501
+ const head = `<script nonce="${nonce}">${PREVIEW_BASE_JS}</script>` + (creds ? `<script nonce="${nonce}">${previewCredentialsJs(creds)}</script>` : handoverAttempted ? `<script nonce="${nonce}">${stripHandoverParamJs()}</script>` : "");
43502
+ const html = raw.replace(/(=|url\()(["']?)\/preview\//g, "$1$2").replace(/<head(\s[^>]*)?>/i, (tag) => `${tag}${head}`);
43369
43503
  return new Response(html, {
43370
43504
  status: 200,
43371
43505
  // The shell only changes on an artifact deploy, and everything it loads is
43372
43506
  // hash-addressed or release-pinned — so let repeat opens paint instantly from
43373
43507
  // cache while revalidating in the background (the measured cold TTFB through
43374
43508
  // the bo gateway was ~600ms, all of it this document).
43509
+ //
43510
+ // …UNLESS this document carries a redeemed bearer, OR reflects a FAILED handover attempt
43511
+ // (#3042). A redeemed bearer makes it a one-recipient credential delivery, where
43512
+ // `public, max-age=60` would authorize a shared cache to hand it to the next person who
43513
+ // asks. A failed attempt carries the same risk for a different reason: the request URL names
43514
+ // a code (`?h=`) that may still be live (a transient `store_unavailable`, say), and caching
43515
+ // an inert answer to that exact URL would let a shared cache stand between the code and a
43516
+ // legitimate retry. `no-store` also keeps either case out of the browser's own back/forward
43517
+ // cache on disk. The plain (no handover attempted at all) document is untouched and keeps
43518
+ // its cache.
43375
43519
  headers: {
43376
43520
  "content-type": asset.contentType || "text/html; charset=utf-8",
43377
- "cache-control": "public, max-age=60, stale-while-revalidate=600",
43521
+ "cache-control": creds || handoverAttempted ? "no-store" : "public, max-age=60, stale-while-revalidate=600",
43378
43522
  "content-security-policy": previewCsp(nonce)
43379
43523
  }
43380
43524
  });
43381
43525
  }
43382
- function previewResponse(pathname, assets, mount = "/preview") {
43526
+ function previewResponse(pathname, assets, mount = "/preview", creds, handoverAttempted = false) {
43383
43527
  const key2 = pathname.replace(MOUNT_STRIP[mount], "") || "index.html";
43384
43528
  const hit = assets.get(key2);
43385
43529
  if (hit) {
43386
- if (key2 === "index.html") return indexHtml(hit, pathname);
43530
+ if (key2 === "index.html") return indexHtml(hit, pathname, creds, handoverAttempted);
43387
43531
  if (key2 === "t5-frame.html") {
43388
43532
  return new Response(assetBytes(hit), {
43389
43533
  status: 200,
@@ -43400,7 +43544,7 @@ function previewResponse(pathname, assets, mount = "/preview") {
43400
43544
  });
43401
43545
  }
43402
43546
  const index = assets.get("index.html");
43403
- if (index) return indexHtml(index, pathname);
43547
+ if (index) return indexHtml(index, pathname, creds, handoverAttempted);
43404
43548
  return new Response(JSON.stringify({ error: "preview_not_built" }), {
43405
43549
  status: 503,
43406
43550
  headers: { "content-type": "application/json; charset=utf-8" }
@@ -43622,19 +43766,19 @@ var init_preview_assets_generated = __esm({
43622
43766
  "../../packages/serve/src/preview-assets.generated.ts"() {
43623
43767
  init_preview_assets_format();
43624
43768
  PREVIEW_ASSET_MANIFEST = [
43625
- { key: "assets/index-EUz_f6yu.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 987137, sha256: "7b526f22885a0dfed16aa63cc802e4d17c3565e78ba5a8a873bcb111ffe1a076" },
43626
- { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 987145, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
43627
- { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1078069, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
43628
- { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1169053, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
43629
- { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1260033, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
43630
- { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1350905, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
43631
- { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 1441885, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
43632
- { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 1550521, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
43633
- { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 1659605, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
43634
- { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 1768653, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
43635
- { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 1876353, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
43636
- { key: "index.html", contentType: "text/html; charset=utf-8", offset: 1964461, length: 4448, sha256: "78be80518ed0f28fbf085b806c0d4ef5ec796da109a3f2f257f4ee31e1c01214" },
43637
- { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 1968909, length: 13784, sha256: "cfb9465254fab84f0e084bb4542c282758ea29774b5f172840cc0b9e4194ef89" }
43769
+ { key: "assets/index-BpbA_kgY.js", contentType: "application/javascript; charset=utf-8", offset: 8, length: 989132, sha256: "56294e0b1e3ed17b553d17b61856896ae8865ae57bc76443d12c5d6437a37d22" },
43770
+ { key: "fonts/WixMadeforDisplay-Bold.ttf", contentType: "font/ttf", offset: 989140, length: 90924, sha256: "ec4c7d63772d980d6e147b2df6215fd2737e4b04cebe0cf7e11dc695fb63cdcf" },
43771
+ { key: "fonts/WixMadeforDisplay-ExtraBold.ttf", contentType: "font/ttf", offset: 1080064, length: 90984, sha256: "bf4c16a2774edd38a921edf3099469866cd74c094fd596f3fa4e81f54a04dfaa" },
43772
+ { key: "fonts/WixMadeforDisplay-Medium.ttf", contentType: "font/ttf", offset: 1171048, length: 90980, sha256: "368faca44fb9f7207e4af784e94750b1ee59c1098b8689e0f4a918926f244fb5" },
43773
+ { key: "fonts/WixMadeforDisplay-Regular.ttf", contentType: "font/ttf", offset: 1262028, length: 90872, sha256: "97ed8f1a6772abe26e3d8af5cecfc4b02faa94497d585b0398b5053b739a70c1" },
43774
+ { key: "fonts/WixMadeforDisplay-SemiBold.ttf", contentType: "font/ttf", offset: 1352900, length: 90980, sha256: "642feab8a48ddf6c7a89d7c2a7c0edfa0e6e773f671287e460e222703563191e" },
43775
+ { key: "fonts/WixMadeforText-Bold.ttf", contentType: "font/ttf", offset: 1443880, length: 108636, sha256: "37c179b4fefb63ea9637322b84583a2df53aa226c150294eea1675c14b1b8e29" },
43776
+ { key: "fonts/WixMadeforText-ExtraBold.ttf", contentType: "font/ttf", offset: 1552516, length: 109084, sha256: "62e2fe4718129f36ecdb0ae4de385bd6b81ecc876146447167c6c140fad699a9" },
43777
+ { key: "fonts/WixMadeforText-Medium.ttf", contentType: "font/ttf", offset: 1661600, length: 109048, sha256: "4ecf270136af928612c76fe4ebade91d8156975e5ea4c0cf9225501dd1788e02" },
43778
+ { key: "fonts/WixMadeforText-Regular.ttf", contentType: "font/ttf", offset: 1770648, length: 107700, sha256: "dee56f09fd3efc127c1ea95557edc74110c1460a46287da36e0af25efd341ef5" },
43779
+ { key: "fonts/WixMadeforText-SemiBold.ttf", contentType: "font/ttf", offset: 1878348, length: 88108, sha256: "dced97a77624bc5f8b1ae65850360908297f4588e09feb6f22792d29dc7e765b" },
43780
+ { key: "index.html", contentType: "text/html; charset=utf-8", offset: 1966456, length: 4448, sha256: "4b295aeb4c64dc9378313f44a384fca9054809e665c6a06e00c1b11f7a1596b7" },
43781
+ { key: "t5-frame.html", contentType: "text/html; charset=utf-8", offset: 1970904, length: 13784, sha256: "cfb9465254fab84f0e084bb4542c282758ea29774b5f172840cc0b9e4194ef89" }
43638
43782
  ];
43639
43783
  }
43640
43784
  });
@@ -49062,12 +49206,12 @@ function findRepoRoot(cwd) {
49062
49206
  }
49063
49207
  return null;
49064
49208
  }
49065
- function deepMerge3(a, b) {
49209
+ function deepMerge4(a, b) {
49066
49210
  const out = { ...a };
49067
49211
  for (const [k, v] of Object.entries(b)) {
49068
49212
  const cur = out[k];
49069
49213
  if (v && typeof v === "object" && !Array.isArray(v) && cur && typeof cur === "object" && !Array.isArray(cur)) {
49070
- out[k] = deepMerge3(cur, v);
49214
+ out[k] = deepMerge4(cur, v);
49071
49215
  } else {
49072
49216
  out[k] = v;
49073
49217
  }
@@ -49119,7 +49263,7 @@ function mergeCustomerOwned(committed, overlay) {
49119
49263
  if (typeof value === "string" && value.trim() === "") delete overlayIdentity[key2];
49120
49264
  }
49121
49265
  if (customer.identity !== void 0) customer.identity = overlayIdentity;
49122
- return deepMerge3(committed, customer);
49266
+ return deepMerge4(committed, customer);
49123
49267
  }
49124
49268
  function buildMergedContract(options) {
49125
49269
  const { appDir: appDir2, packageDir, overlay } = options;
@@ -49236,7 +49380,7 @@ async function applyOverlays(input) {
49236
49380
  assertNotCommitted(pkgProv, input.forbiddenProvisioning);
49237
49381
  if (existsSync52(pkgProv)) {
49238
49382
  const cur = JSON.parse(readFileSync62(pkgProv, "utf8"));
49239
- const merged = deepMerge3(cur, overlay);
49383
+ const merged = deepMerge4(cur, overlay);
49240
49384
  writeFileSync21(pkgProv, JSON.stringify(merged, null, 2) + "\n");
49241
49385
  wrote.push(pkgProv);
49242
49386
  } else {
@@ -49246,7 +49390,7 @@ async function applyOverlays(input) {
49246
49390
  const pkgTokens = join50(cwd, "tokens.json");
49247
49391
  if (existsSync52(pkgTokens) && (tokens.colors || tokens.typography)) {
49248
49392
  const cur = JSON.parse(readFileSync62(pkgTokens, "utf8"));
49249
- const merged = deepMerge3(cur, tokens);
49393
+ const merged = deepMerge4(cur, tokens);
49250
49394
  const mergedColors = merged.colors;
49251
49395
  if (mergedColors && typeof mergedColors === "object" && !Array.isArray(mergedColors)) {
49252
49396
  merged.colors = alignAccentsToSurfaces(mergedColors, tokens.colors);
@@ -52200,6 +52344,7 @@ if (process.argv[1] && basename(process.argv[1]) === "diff-hash.mjs" && import.m
52200
52344
  }
52201
52345
 
52202
52346
  // src/attestation.ts
52347
+ init_widget_identity();
52203
52348
  var DEFAULT_MAX_RUNS = 20;
52204
52349
  var ATTESTATION_VERSION = 2;
52205
52350
  function attestationVersionOf(value) {
@@ -52238,12 +52383,7 @@ function releasePayloadHash(payload) {
52238
52383
  const manifestRecord = manifest;
52239
52384
  const widgets = manifestRecord.widgets;
52240
52385
  if (!widgets || typeof widgets !== "object" || Array.isArray(widgets)) return sha256(canonical(payload));
52241
- const stableWidgets = Object.fromEntries(Object.entries(widgets).map(([id, value]) => {
52242
- if (!value || typeof value !== "object" || Array.isArray(value)) return [id, value];
52243
- const { contentEtag: _generatedContentEtag, ...stable } = value;
52244
- return [id, stable];
52245
- }));
52246
- return sha256(canonical({ ...record2, manifest: { ...manifestRecord, widgets: stableWidgets } }));
52386
+ return sha256(canonical({ ...record2, manifest: { ...manifestRecord, widgets: stableWidgets(widgets) } }));
52247
52387
  }
52248
52388
  function withoutSelfHash(attestation) {
52249
52389
  const { selfHash: _selfHash, ...body } = attestation;
@@ -54047,13 +54187,26 @@ function headers(session) {
54047
54187
  function truncate(token) {
54048
54188
  return token.length <= 20 ? token : `${token.slice(0, 8)}\u2026${token.slice(-8)}`;
54049
54189
  }
54190
+ function parseDataFlag(raw) {
54191
+ if (raw === void 0) return {};
54192
+ if (typeof raw === "boolean") return { error: "--data expects k=v" };
54193
+ const entries = Array.isArray(raw) ? raw : [raw];
54194
+ const out = {};
54195
+ for (const entry of entries) {
54196
+ if (typeof entry !== "string") return { error: "--data expects k=v" };
54197
+ const eq = entry.indexOf("=");
54198
+ if (eq <= 0) return { error: `--data expects k=v, got "${entry}"` };
54199
+ out[entry.slice(0, eq)] = entry.slice(eq + 1);
54200
+ }
54201
+ return Object.keys(out).length ? { value: out } : {};
54202
+ }
54050
54203
  async function runNotify(pos, flags, io, deps = {}) {
54051
54204
  const doFetch = deps.fetchImpl ?? fetch;
54052
54205
  const str6 = (v) => typeof v === "string" ? v : void 0;
54053
54206
  const json5 = flags.json === true;
54054
54207
  const print = (v) => io.log(JSON.stringify(v, null, 2));
54055
54208
  const sub = pos[1];
54056
- const USAGE2 = "himi notify send --app <app> (--token <t> | --device <id> | --all) --title <s> [--body <s>] [--link <url>]\n [--platform ios|android] [--category <c>] [--badge <n>] [--channel <id>] [--silent] [--dry-run] [--json]\nhimi notify tokens --app <app> [--platform ios|android] [--include-disabled] [--json]\nhimi notify doctor [--json]";
54209
+ const USAGE2 = "himi notify send --app <app> (--token <t> | --device <id> | --all) --title <s> [--body <s>] [--link <url>]\n [--platform ios|android] [--category <c>] [--badge <n>] [--channel <id>] [--silent] [--dry-run] [--json]\n [--data k=v ...]\nhimi notify tokens --app <app> [--platform ios|android] [--include-disabled] [--json]\nhimi notify doctor [--json]";
54057
54210
  if (!sub || !["send", "tokens", "doctor"].includes(sub)) {
54058
54211
  io.error(
54059
54212
  JSON.stringify({
@@ -54179,6 +54332,11 @@ async function runNotify(pos, flags, io, deps = {}) {
54179
54332
  io.error(JSON.stringify({ error: `targets are mutually exclusive \u2014 got ${targets.join(" and ")}`, usage: USAGE2 }));
54180
54333
  return 2;
54181
54334
  }
54335
+ const data = parseDataFlag(flags.data);
54336
+ if (data.error) {
54337
+ io.error(JSON.stringify({ error: data.error, usage: USAGE2 }));
54338
+ return 2;
54339
+ }
54182
54340
  const silent = flags.silent === true;
54183
54341
  const title = str6(flags.title);
54184
54342
  if (!silent && !title) {
@@ -54199,7 +54357,10 @@ async function runNotify(pos, flags, io, deps = {}) {
54199
54357
  ...flags["dry-run"] === true ? { dryRun: true } : {},
54200
54358
  ...all ? { all: true } : {},
54201
54359
  ...token ? { token } : {},
54202
- ...device ? { tokenId: Number(device) } : {}
54360
+ ...device ? { tokenId: Number(device) } : {},
54361
+ // Absent rather than `{}` when unset: the route distinguishes the two, and an
54362
+ // empty map is a request to send nothing rather than to send no map.
54363
+ ...data.value ? { data: data.value } : {}
54203
54364
  };
54204
54365
  if (device && !Number.isInteger(Number(device))) {
54205
54366
  io.error(JSON.stringify({ error: `--device takes a numeric registry id (from \`himi notify tokens\`), got "${device}"` }));
@@ -54754,6 +54915,7 @@ function writeAgentContext(targetDir) {
54754
54915
  // src/cli.ts
54755
54916
  init_wix_placeholders();
54756
54917
  var defaultIO = { log: (l) => console.log(l), error: (l) => console.error(l), isTTY: process.stdout.isTTY === true };
54918
+ var REPEATABLE_FLAGS = /* @__PURE__ */ new Set(["data"]);
54757
54919
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
54758
54920
  "browser",
54759
54921
  "no-open",
@@ -54845,6 +55007,14 @@ function switchFlagErrors(argv) {
54845
55007
  }
54846
55008
  return out;
54847
55009
  }
55010
+ function assign(flags, name, value) {
55011
+ if (!REPEATABLE_FLAGS.has(name)) {
55012
+ flags[name] = value;
55013
+ return;
55014
+ }
55015
+ const seen = flags[name];
55016
+ flags[name] = Array.isArray(seen) ? [...seen, value] : seen === void 0 ? value : [String(seen), value];
55017
+ }
54848
55018
  function parseFlags(argv) {
54849
55019
  const _ = [];
54850
55020
  const flags = {};
@@ -54853,13 +55023,13 @@ function parseFlags(argv) {
54853
55023
  if (a.startsWith("--")) {
54854
55024
  const eq = a.indexOf("=");
54855
55025
  if (eq >= 0) {
54856
- flags[a.slice(2, eq)] = a.slice(eq + 1);
55026
+ assign(flags, a.slice(2, eq), a.slice(eq + 1));
54857
55027
  } else {
54858
55028
  const next = argv[i + 1];
54859
55029
  const optionalNativeWithoutValue = a === "--native" && next !== "ios" && next !== "android";
54860
55030
  const booleanOnly = BOOLEAN_FLAGS.has(a.slice(2));
54861
55031
  if (!optionalNativeWithoutValue && !booleanOnly && next !== void 0 && !next.startsWith("--")) {
54862
- flags[a.slice(2)] = next;
55032
+ assign(flags, a.slice(2), next);
54863
55033
  i++;
54864
55034
  } else {
54865
55035
  flags[a.slice(2)] = true;
@@ -55537,6 +55707,34 @@ function trustedConfiguredRegistry(env) {
55537
55707
  const configured = normalizeRegistry(env.npm_config_registry ?? env.NPM_CONFIG_REGISTRY);
55538
55708
  return isTrustedRegistry(configured) ? configured : void 0;
55539
55709
  }
55710
+ function createScopedRegistryResolver(exec = execFileSync7) {
55711
+ let resolved2;
55712
+ return () => {
55713
+ if (resolved2) return resolved2.value;
55714
+ let value;
55715
+ try {
55716
+ const configured = (key2) => {
55717
+ const raw = exec("npm", ["config", "get", key2], {
55718
+ cwd: homedir7(),
55719
+ timeout: 5e3,
55720
+ stdio: ["ignore", "pipe", "ignore"]
55721
+ }).toString().trim();
55722
+ return raw && raw !== "undefined" ? raw : void 0;
55723
+ };
55724
+ const effective = normalizeRegistry(configured("@wix:registry") ?? configured("registry"));
55725
+ value = isTrustedRegistry(effective) ? effective : void 0;
55726
+ } catch {
55727
+ value = void 0;
55728
+ }
55729
+ resolved2 = { value };
55730
+ return value;
55731
+ };
55732
+ }
55733
+ var npmScopedWixRegistry = createScopedRegistryResolver();
55734
+ function registryCandidates(env, scoped) {
55735
+ const ordered = [isTrustedRegistry(scoped) ? normalizeRegistry(scoped) : void 0, trustedConfiguredRegistry(env), PUBLIC_NPM_REGISTRY];
55736
+ return [...new Set(ordered.filter((r) => r !== void 0))];
55737
+ }
55540
55738
  async function fetchPackumentFrom(registry, fetchImpl, timeoutMs) {
55541
55739
  const controller = new AbortController();
55542
55740
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -55557,10 +55755,8 @@ async function fetchPackumentFrom(registry, fetchImpl, timeoutMs) {
55557
55755
  clearTimeout(timer);
55558
55756
  }
55559
55757
  }
55560
- async function fetchCliPackument(fetchImpl = fetch, timeoutMs = DOCTOR_TIMEOUT_MS, env = process.env) {
55561
- const configured = trustedConfiguredRegistry(env);
55562
- const candidates = configured && configured !== PUBLIC_NPM_REGISTRY ? [configured, PUBLIC_NPM_REGISTRY] : [PUBLIC_NPM_REGISTRY];
55563
- for (const registry of candidates) {
55758
+ async function fetchCliPackument(fetchImpl = fetch, timeoutMs = DOCTOR_TIMEOUT_MS, env = process.env, scopedRegistry = npmScopedWixRegistry) {
55759
+ for (const registry of registryCandidates(env, scopedRegistry())) {
55564
55760
  const packument = await fetchPackumentFrom(registry, fetchImpl, timeoutMs);
55565
55761
  if (packument) return packument;
55566
55762
  }
@@ -55599,9 +55795,9 @@ function resolvedSdkVersion(fromDir = process.cwd(), bundledManifest = new URL("
55599
55795
  return {};
55600
55796
  }
55601
55797
  }
55602
- async function doctorVersionReport(running, fetchImpl = fetch, sdk = resolvedSdkVersion()) {
55798
+ async function doctorVersionReport(running, fetchImpl = fetch, sdk = resolvedSdkVersion(), scopedRegistry = npmScopedWixRegistry) {
55603
55799
  const minimumSdk = "0.33.0";
55604
- const packument = await fetchCliPackument(fetchImpl);
55800
+ const packument = await fetchCliPackument(fetchImpl, DOCTOR_TIMEOUT_MS, process.env, scopedRegistry);
55605
55801
  const latest = packument?.latest;
55606
55802
  const publishedAt = packument?.time[running];
55607
55803
  const ageDays = ageInDays(publishedAt);
@@ -55862,7 +56058,7 @@ async function maybeSelfUpdate(argv, io, opts = {}) {
55862
56058
  let answeredBy;
55863
56059
  let answeredLatest;
55864
56060
  if (!fresh) {
55865
- const packument = await fetchCliPackument(opts.fetchImpl ?? fetch, DOCTOR_TIMEOUT_MS, env);
56061
+ const packument = await fetchCliPackument(opts.fetchImpl ?? fetch, DOCTOR_TIMEOUT_MS, env, opts.scopedRegistry);
55866
56062
  if (packument) {
55867
56063
  latest = packument.latest;
55868
56064
  runningPublishedAt = packument.time[running];
@@ -55912,7 +56108,7 @@ async function maybeSelfUpdate(argv, io, opts = {}) {
55912
56108
  }
55913
56109
  try {
55914
56110
  if (!answeredBy) {
55915
- const again = await fetchCliPackument(opts.fetchImpl ?? fetch, DOCTOR_TIMEOUT_MS, env);
56111
+ const again = await fetchCliPackument(opts.fetchImpl ?? fetch, DOCTOR_TIMEOUT_MS, env, opts.scopedRegistry);
55916
56112
  answeredBy = again?.registry;
55917
56113
  answeredLatest = again?.latest;
55918
56114
  }
@@ -55969,10 +56165,11 @@ function helpJson() {
55969
56165
  ]
55970
56166
  };
55971
56167
  }
55972
- function previewWebUrl(serveBase, app, release, screenId, surface, previewToken) {
56168
+ function previewWebUrl(serveBase, app, release, screenId, surface, previewToken, handover) {
55973
56169
  const base = serveBase.replace(/\/$/, "");
55974
56170
  const path = screenId ? `/preview/a/${app}/screen/${screenId}` : `/preview/a/${app}`;
55975
- return `${base}${path}?release=${encodeURIComponent(release)}${surface ? `&surface=${encodeURIComponent(surface)}` : ""}${previewToken ? `&pvt=${encodeURIComponent(previewToken)}` : ""}`;
56171
+ const query = handover ? `?h=${encodeURIComponent(handover)}` : `?release=${encodeURIComponent(release)}`;
56172
+ return `${base}${path}${query}${surface ? `&surface=${encodeURIComponent(surface)}` : ""}${!handover && previewToken ? `&pvt=${encodeURIComponent(previewToken)}` : ""}`;
55976
56173
  }
55977
56174
  function renderDeviceWidths() {
55978
56175
  return {
@@ -56037,14 +56234,16 @@ function servesAll(served, asked) {
56037
56234
  return Object.entries(asked).every(([k, v]) => stable(served[k]) === stable(v));
56038
56235
  }
56039
56236
  var GIT_FLAG_STORE_HALF_WRITE_NOTE = "the plane reported a failure, but this store writes flags.json BEFORE it commits \u2014 so the change may already be on the pod that served this request, uncommitted and lost on its next restart. servedNow says what that pod answers now; other pods may differ.";
56237
+ var PREVIEW_HANDOVER_NOTE = "the preview link carries a single-use code, not the bearer itself \u2014 it opens the draft ONCE, and the credential behind it has a limited lifetime this call has no usable expiry for. Run the `previewRenew` command printed beside this note \u2014 from the same plane and credentials as this call (any `--serve-url`/`--control-url`/token flags you passed) \u2014 for a fresh link.";
56040
56238
  var PREVIEW_TOKEN_NOTE = "the preview link carries a bearer with a limited lifetime, and this call has no usable expiry for it \u2014 the plane stated none, or the one it stated is unreadable, already past, or beyond the hour this CLI will reason about. Run the `previewRenew` command printed beside this note \u2014 from the same plane and credentials as this call (any `--serve-url`/`--control-url`/token flags you passed) \u2014 for a fresh one.";
56041
- function previewTokenNote(expiresAt, now = Date.now()) {
56239
+ function previewTokenNote(expiresAt, now = Date.now(), viaHandover = false) {
56240
+ const carrier = viaHandover ? "the preview link carries a single-use code, not the bearer itself" : "the preview link carries a bearer";
56042
56241
  const at = expiresAt ? Date.parse(expiresAt) : NaN;
56043
- if (!Number.isFinite(at)) return PREVIEW_TOKEN_NOTE;
56242
+ if (!Number.isFinite(at)) return viaHandover ? PREVIEW_HANDOVER_NOTE : PREVIEW_TOKEN_NOTE;
56044
56243
  const seconds = (at - now) / 1e3;
56045
- if (seconds <= 0 || seconds > 3600) return PREVIEW_TOKEN_NOTE;
56244
+ if (seconds <= 0 || seconds > 3600) return viaHandover ? PREVIEW_HANDOVER_NOTE : PREVIEW_TOKEN_NOTE;
56046
56245
  const minutes = Math.max(1, Math.round(seconds / 60));
56047
- return `the preview link carries a bearer that expires at ${expiresAt} \u2014 about ${minutes} minute${minutes === 1 ? "" : "s"} from now; run the \`previewRenew\` command printed beside this note \u2014 from the same plane and credentials as this call (any \`--serve-url\`/\`--control-url\`/token flags you passed) \u2014 for a fresh one.`;
56246
+ return `${carrier}, and it expires at ${expiresAt} \u2014 about ${minutes} minute${minutes === 1 ? "" : "s"} from now; run the \`previewRenew\` command printed beside this note \u2014 from the same plane and credentials as this call (any \`--serve-url\`/\`--control-url\`/token flags you passed) \u2014 for a fresh one.`;
56048
56247
  }
56049
56248
  function previewRenewCommand(app, release, target, scope = {}) {
56050
56249
  return `himi preview --app ${app} --release ${release}` + (scope.screen ? ` --screen ${scope.screen}` : "") + (scope.surface ? ` --surface ${scope.surface}` : "") + (target !== "local" ? ` --target ${target}` : "");
@@ -57156,6 +57355,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
57156
57355
  const out = r.json ?? {};
57157
57356
  const release = out.releaseId ?? "";
57158
57357
  const previewToken = out.previewToken;
57358
+ const previewHandover = out.previewHandover;
57159
57359
  const sbase = serveBase();
57160
57360
  const sourceMatchesLocal = opts.localSourceHash === void 0 ? null : opts.localSourceHash === (base.appSourceHash ?? null);
57161
57361
  const honesty = patchHonesty2({ door: opts.door, base, derivedReleaseId: release, sourceMatchesLocal, app });
@@ -57188,7 +57388,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
57188
57388
  ...opts.ux ? uxHandoverOverride(opts.ux.evidence, opts.ux.ctx) : {},
57189
57389
  ...out.visibility ? { visibility: out.visibility } : {},
57190
57390
  ...opts.extra ?? {},
57191
- previewWeb: sbase && release ? previewWebUrl(sbase, app, release, void 0, void 0, previewToken) : null,
57391
+ previewWeb: sbase && release ? previewWebUrl(sbase, app, release, void 0, void 0, previewToken, previewHandover) : null,
57192
57392
  previewTokenExpiresAt: out.previewTokenExpiresAt ?? null,
57193
57393
  previewPlayerDebug: release ? previewPlayerDebugLink(app, release, void 0, previewToken) : null,
57194
57394
  context: sbase && release ? previewContextUrl(sbase, app, release) : null,
@@ -59130,6 +59330,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
59130
59330
  const sourceStored = r.json?.sourceStored;
59131
59331
  const previewToken = draft ? r.json?.previewToken : void 0;
59132
59332
  const previewTokenExpiresAt = draft ? r.json?.previewTokenExpiresAt : void 0;
59333
+ const previewHandover = draft ? r.json?.previewHandover : void 0;
59133
59334
  const functionPreviewToken = r.json?.functionPreviewToken;
59134
59335
  const functionPreviewExpiresAt = r.json?.functionPreviewExpiresAt;
59135
59336
  const effectiveVisibility = r.json?.visibility;
@@ -59197,7 +59398,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
59197
59398
  }),
59198
59399
  ...loopbackWarning ? { warning: loopbackWarning } : {},
59199
59400
  ...report.issues ? { issues: report.issues } : {},
59200
- previewWeb: base ? previewWebUrl(base, app, release, void 0, void 0, previewToken) : null,
59401
+ previewWeb: base ? previewWebUrl(base, app, release, void 0, void 0, previewToken, previewHandover) : null,
59201
59402
  // A timestamp alone leaves the reader to work out both how long they have and how to
59202
59403
  // get another one. Print the duration and the re-mint command beside it (#2502 item 3).
59203
59404
  // No `mintedHere` guard is needed here, unlike `himi preview`: this bearer can only
@@ -59206,7 +59407,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
59206
59407
  ...previewToken ? {
59207
59408
  previewTokenExpiresAt: previewTokenExpiresAt ?? null,
59208
59409
  previewRenew: previewRenewCommand(app, release, targetName()),
59209
- previewTokenNote: previewTokenNote(previewTokenExpiresAt)
59410
+ previewTokenNote: previewTokenNote(previewTokenExpiresAt, Date.now(), previewHandover !== void 0)
59210
59411
  } : {},
59211
59412
  // RFC 0051 — the surfaces THIS RELEASE targets, read from the manifest the
59212
59413
  // push just published (never a re-read of the local file: review found the
@@ -59668,6 +59869,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
59668
59869
  }
59669
59870
  let previewToken = str5(flags["preview-token"]);
59670
59871
  let previewTokenExpiresAt;
59872
+ let previewHandover;
59671
59873
  let mintedHere = false;
59672
59874
  let mintFailure;
59673
59875
  if (!previewToken) {
@@ -59695,6 +59897,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
59695
59897
  const body = minted.json;
59696
59898
  previewToken = typeof body.previewToken === "string" ? body.previewToken : void 0;
59697
59899
  previewTokenExpiresAt = typeof body.previewTokenExpiresAt === "string" ? body.previewTokenExpiresAt : void 0;
59900
+ previewHandover = typeof body.previewHandover === "string" ? body.previewHandover : void 0;
59698
59901
  mintedHere = previewToken !== void 0;
59699
59902
  if (!mintedHere) mintFailure = "the plane answered the mint request without a token \u2014 HIMALAYA_PREVIEW_TOKEN_SECRET may be unconfigured there. Your credential is fine";
59700
59903
  }
@@ -59709,7 +59912,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
59709
59912
  release,
59710
59913
  ...screen ? { screen } : {},
59711
59914
  ...surface ? { surface } : {},
59712
- previewWeb: base ? previewWebUrl(base, app, release, screen, surface, previewToken) : null,
59915
+ previewWeb: base ? previewWebUrl(base, app, release, screen, surface, previewToken, previewHandover) : null,
59713
59916
  // Gated on the MINT, like the two fields below: a plane that returns an expiry but no
59714
59917
  // token would otherwise print a lifetime for a bearer the same envelope says does not
59715
59918
  // exist.
@@ -59724,7 +59927,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
59724
59927
  // in with `--preview-token` is one this command cannot promise to reissue.
59725
59928
  ...mintedHere ? {
59726
59929
  previewRenew: previewRenewCommand(app, release, targetName(), { screen, surface }),
59727
- previewTokenNote: previewTokenNote(previewTokenExpiresAt)
59930
+ previewTokenNote: previewTokenNote(previewTokenExpiresAt, Date.now(), previewHandover !== void 0)
59728
59931
  } : previewToken || !mintFailure ? {} : { previewTokenNote: `no preview bearer was minted \u2014 ${mintFailure}. The link above carries no \`pvt\`, so it opens a LIVE release and will not open a draft.` },
59729
59932
  previewPlayerDebug: previewPlayerDebugLink(app, release, screen, previewToken),
59730
59933
  context: base ? previewContextUrl(base, app, release, screen) : null,
@@ -61408,8 +61611,9 @@ ${label2}`);
61408
61611
  return 1;
61409
61612
  }
61410
61613
  try {
61411
- const answered = await fetchCliPackument(up.fetchImpl ?? fetch, DOCTOR_TIMEOUT_MS, env);
61412
- const registry = answered?.registry ?? trustedConfiguredRegistry(env) ?? PUBLIC_NPM_REGISTRY;
61614
+ const scoped = up.scopedRegistry ?? npmScopedWixRegistry;
61615
+ const answered = await fetchCliPackument(up.fetchImpl ?? fetch, DOCTOR_TIMEOUT_MS, env, scoped);
61616
+ const registry = answered?.registry ?? registryCandidates(env, scoped())[0];
61413
61617
  if (answered?.latest && compareVersions2(answered.latest, before) <= 0) {
61414
61618
  if (flags.json) print({ ok: true, running: before, installed: before, changed: false, registry, latest: answered.latest });
61415
61619
  else io.log(`himi ${before} (unchanged \u2014 ${registry} has nothing newer; its latest is ${answered.latest})`);
@@ -61891,6 +62095,7 @@ export {
61891
62095
  GIT_FLAG_STORE_NOTE,
61892
62096
  PLACEHOLDER_CLIENT_ID,
61893
62097
  PLACEHOLDER_SITE_URL,
62098
+ PREVIEW_HANDOVER_NOTE,
61894
62099
  PREVIEW_TOKEN_NOTE,
61895
62100
  PUBLIC_NPM_REGISTRY,
61896
62101
  PUBLIC_WIX_API_BASE,
@@ -61900,6 +62105,7 @@ export {
61900
62105
  autoClaimSlug,
61901
62106
  buildDoctorReport,
61902
62107
  compareVersions2 as compareVersions,
62108
+ createScopedRegistryResolver,
61903
62109
  createUpdateRunner,
61904
62110
  credentialKind,
61905
62111
  credentialSwapNotice,
@@ -61915,6 +62121,7 @@ export {
61915
62121
  formatSdkVersionLine,
61916
62122
  formatStaleLine,
61917
62123
  globalPrefixWritable,
62124
+ handoverUxEvidence,
61918
62125
  humanDuration,
61919
62126
  inspectControlToken,
61920
62127
  isHimalayaMonorepoCheckout,
@@ -61929,6 +62136,7 @@ export {
61929
62136
  nativeConfigAdvisories,
61930
62137
  nativeConfigTokenNotice,
61931
62138
  npmInstallArgs,
62139
+ npmScopedWixRegistry,
61932
62140
  ownerPlaneHint,
61933
62141
  ownerPlaneIsDark,
61934
62142
  parseFlags,
@@ -61949,6 +62157,7 @@ export {
61949
62157
  probeReachable,
61950
62158
  pushAuthHint,
61951
62159
  readUpdateCache,
62160
+ registryCandidates,
61952
62161
  renderDataSource,
61953
62162
  renderDeviceWidths,
61954
62163
  resolveAppSlug,