@wix/himalaya-cli 0.1422.0 → 0.1424.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
@@ -7715,6 +7715,7 @@ function resolveTranslation(value, catalog3, sourceCatalog) {
7715
7715
  var BINDING_SIGILS, BINDING_SIGIL_RE, TRANSLATION_PREFIX, TRANSLATION_KEY_RE;
7716
7716
  var init_bindings = __esm({
7717
7717
  "../../core/ts/src/bindings.ts"() {
7718
+ "use strict";
7718
7719
  BINDING_SIGILS = ["service", "flag", "item", "t"];
7719
7720
  BINDING_SIGIL_RE = new RegExp(
7720
7721
  `^\\$(?:${BINDING_SIGILS.filter((s2) => s2 !== "t").join("|")})\\b|^\\$t:`
@@ -10165,16 +10166,104 @@ var init_rich_content = __esm({
10165
10166
  }
10166
10167
  });
10167
10168
 
10169
+ // ../../core/dev-server/src/raw-json-depth.ts
10170
+ function rawJsonDepthFinding(value, limit = RAW_JSON_MAX_DEPTH) {
10171
+ const EXIT = Symbol("exit");
10172
+ const stack = [];
10173
+ const trail = [];
10174
+ const onTrail = /* @__PURE__ */ new Set();
10175
+ let max = 0;
10176
+ let path2;
10177
+ if (value && typeof value === "object") stack.push({ v: value, depth: 1, key: "" });
10178
+ while (stack.length) {
10179
+ const step = stack.pop();
10180
+ if (step === EXIT) {
10181
+ onTrail.delete(trail.pop());
10182
+ trail.pop();
10183
+ continue;
10184
+ }
10185
+ const v2 = step.v;
10186
+ if (onTrail.has(v2)) continue;
10187
+ onTrail.add(v2);
10188
+ trail.push(step.key, v2);
10189
+ stack.push(EXIT);
10190
+ if (step.depth > max) max = step.depth;
10191
+ if (step.depth > limit && path2 === void 0) {
10192
+ let p2 = "$";
10193
+ for (let i2 = 2; i2 < trail.length; i2 += 2) {
10194
+ const k2 = trail[i2];
10195
+ p2 += typeof k2 === "number" ? `[${k2}]` : /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(k2) ? `.${k2}` : `[${JSON.stringify(k2)}]`;
10196
+ }
10197
+ path2 = p2;
10198
+ }
10199
+ if (Array.isArray(v2)) {
10200
+ for (let i2 = v2.length - 1; i2 >= 0; i2--) {
10201
+ const c2 = v2[i2];
10202
+ if (c2 && typeof c2 === "object") stack.push({ v: c2, depth: step.depth + 1, key: i2 });
10203
+ }
10204
+ } else {
10205
+ const keys = Object.keys(v2);
10206
+ for (let i2 = keys.length - 1; i2 >= 0; i2--) {
10207
+ const c2 = v2[keys[i2]];
10208
+ if (c2 && typeof c2 === "object") stack.push({ v: c2, depth: step.depth + 1, key: keys[i2] });
10209
+ }
10210
+ }
10211
+ }
10212
+ if (path2 === void 0) return null;
10213
+ return {
10214
+ measure: "json",
10215
+ path: path2,
10216
+ limit,
10217
+ actual: max,
10218
+ message: `JSON nests ${max} levels deep; the limit is ${limit} (it crosses the limit at ${path2}). Native decoders recurse once per level and iOS refuses JSON nested past 512 \u2014 flatten that value (store it as a flat list or a string) or move it out of the descriptor`
10219
+ };
10220
+ }
10221
+ var RAW_JSON_MAX_DEPTH;
10222
+ var init_raw_json_depth = __esm({
10223
+ "../../core/dev-server/src/raw-json-depth.ts"() {
10224
+ RAW_JSON_MAX_DEPTH = 256;
10225
+ }
10226
+ });
10227
+
10168
10228
  // ../../core/dev-server/src/realm-parity.ts
10169
- function withheldGlobalsSource(hide) {
10170
- if (!hide?.length) return "";
10171
- return `${JSON.stringify(hide)}.forEach(function (n) { try { delete globalThis[n]; } catch (e) { /* non-configurable: leave it */ } });`;
10229
+ function allowlistSweepSource(keep = []) {
10230
+ const key2 = [...keep].sort().join(",");
10231
+ let source = sweepSources.get(key2);
10232
+ if (source === void 0) {
10233
+ source = buildSweepSource([.../* @__PURE__ */ new Set([...WORKER_ALLOWLIST, ...keep])].sort());
10234
+ if (sweepSources.size >= 32) sweepSources.clear();
10235
+ sweepSources.set(key2, source);
10236
+ }
10237
+ return source;
10238
+ }
10239
+ function buildSweepSource(allow) {
10240
+ return `(function (allow) {
10241
+ var keep = Object.create(null);
10242
+ for (var i = 0; i < allow.length; i++) keep[allow[i]] = true;
10243
+ for (var o = globalThis; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) {
10244
+ var names = Object.getOwnPropertyNames(o);
10245
+ for (var j = 0; j < names.length; j++) {
10246
+ var n = names[j];
10247
+ if (keep[n] || n === "constructor" || n.indexOf("himi.quickjs.") === 0) continue;
10248
+ try { delete o[n]; } catch (e) { /* non-configurable: leave it */ }
10249
+ }
10250
+ }
10251
+ })(${JSON.stringify(allow)});`;
10252
+ }
10253
+ function explainWithheldError(message, deviceParity) {
10254
+ if (!deviceParity || message.includes(FETCH_ADVICE)) return message;
10255
+ return BARE_FETCH_UNDEFINED.test(message) ? `${message} \u2014 ${FETCH_ADVICE}` : message;
10172
10256
  }
10173
10257
  function realmInstallsIntl(intl, hide) {
10174
10258
  return intl === true && !(hide ?? []).includes("Intl");
10175
10259
  }
10260
+ var sweepSources, FETCH_ADVICE, BARE_FETCH_UNDEFINED;
10176
10261
  var init_realm_parity = __esm({
10177
10262
  "../../core/dev-server/src/realm-parity.ts"() {
10263
+ init_allowlist();
10264
+ sweepSources = /* @__PURE__ */ new Map();
10265
+ FETCH_ADVICE = "`fetch` is not available to a Tier-5 worker (no device has it: iOS's JSContext never did and the Android worker strips it, #4499) \u2014 load data through the sanctioned Wix transport (`@himalaya/wix-*` or `globalThis.himalaya.wixApi.call`).";
10266
+ BARE_FETCH_UNDEFINED = /(?:^|[^.\w$'])'?fetch'? is not (?:defined|a function)\b/;
10178
10267
  }
10179
10268
  });
10180
10269
 
@@ -10204,8 +10293,10 @@ __export(validate_descriptor_core_exports, {
10204
10293
  NATIVE_NESTED_PROPS: () => NATIVE_NESTED_PROPS,
10205
10294
  NATIVE_PROP_ENUMS: () => NATIVE_PROP_ENUMS,
10206
10295
  NATIVE_REQUIRED_PROPS: () => NATIVE_REQUIRED_PROPS,
10296
+ RAW_JSON_MAX_DEPTH: () => RAW_JSON_MAX_DEPTH,
10207
10297
  SERVICE_ONLY_BINDING_PROPS: () => SERVICE_ONLY_BINDING_PROPS,
10208
10298
  WHOLE_VALUE_BINDING: () => WHOLE_VALUE_BINDING,
10299
+ allowlistSweepSource: () => allowlistSweepSource,
10209
10300
  bareNestedClass: () => bareNestedClass,
10210
10301
  bootBundleWorker: () => bootBundleWorker,
10211
10302
  collectServiceBindings: () => collectServiceBindings,
@@ -10215,6 +10306,7 @@ __export(validate_descriptor_core_exports, {
10215
10306
  deviceParityWithheldFrom: () => deviceParityWithheldFrom,
10216
10307
  dryRunBundle: () => dryRunBundle,
10217
10308
  enumFieldNames: () => enumFieldNames,
10309
+ explainWithheldError: () => explainWithheldError,
10218
10310
  extractBundleContract: () => extractBundleContract,
10219
10311
  extractBundleServices: () => extractBundleServices,
10220
10312
  findSkipDerive: () => findSkipDerive,
@@ -10229,17 +10321,18 @@ __export(validate_descriptor_core_exports, {
10229
10321
  pathCrossesOpaqueAuthorJson: () => pathCrossesOpaqueAuthorJson,
10230
10322
  pathValue: () => pathValue,
10231
10323
  pinnedIntrinsicsSource: () => pinnedIntrinsicsSource,
10324
+ rawJsonDepthFinding: () => rawJsonDepthFinding,
10232
10325
  realmInstallsIntl: () => realmInstallsIntl,
10233
10326
  resolveActionEvaluator: () => resolveActionEvaluator,
10234
10327
  resolveBootEvaluator: () => resolveBootEvaluator,
10235
10328
  resolveEvaluator: () => resolveEvaluator,
10329
+ screenDepthFindings: () => screenDepthFindings,
10236
10330
  serviceOnlyBindingValues: () => serviceOnlyBindingValues,
10237
10331
  setActionEvaluator: () => setActionEvaluator,
10238
10332
  setBootEvaluator: () => setBootEvaluator,
10239
10333
  setDefaultEvaluator: () => setDefaultEvaluator,
10240
10334
  topBarFindings: () => topBarFindings,
10241
10335
  validateScreenDescriptor: () => validateScreenDescriptor,
10242
- withheldGlobalsSource: () => withheldGlobalsSource,
10243
10336
  workerSubtreeAllowed: () => workerSubtreeAllowed
10244
10337
  });
10245
10338
  function makeInert() {
@@ -10637,6 +10730,7 @@ async function bootBundleWorker(bundleJs, opts) {
10637
10730
  timeoutMs: 2e3 + budgetMs,
10638
10731
  filename: "himi-bundle.js",
10639
10732
  hide,
10733
+ deviceParity: opts.deviceParity === true,
10640
10734
  wixCall: wixApi.call,
10641
10735
  dispatch: dispatchHost,
10642
10736
  skipLoad: opts.skipLoad === true,
@@ -10647,13 +10741,13 @@ async function bootBundleWorker(bundleJs, opts) {
10647
10741
  guestActions = ran.actions;
10648
10742
  guestSession = ran.session;
10649
10743
  if (ran.error) {
10650
- errors.push({ service: "(boot)", message: ran.error });
10744
+ errors.push({ service: "(boot)", message: explainWithheldError(ran.error, opts.deviceParity === true) });
10651
10745
  }
10652
10746
  } else {
10653
- realm.run(bundleJs, sandbox, { timeoutMs: 2e3, filename: "himi-bundle.js", hide, ...pins });
10747
+ realm.run(bundleJs, sandbox, { timeoutMs: 2e3, filename: "himi-bundle.js", hide, deviceParity: opts.deviceParity === true, ...pins });
10654
10748
  }
10655
10749
  } catch (err) {
10656
- errors.push({ service: "(boot)", message: err?.message ?? String(err), ...err?.stack ? { stack: err.stack } : {} });
10750
+ errors.push({ service: "(boot)", message: explainWithheldError(err?.message ?? String(err), opts.deviceParity === true), ...err?.stack ? { stack: err.stack } : {} });
10657
10751
  }
10658
10752
  const clone = (v2) => {
10659
10753
  try {
@@ -10726,6 +10820,17 @@ async function bootBundleWorker(bundleJs, opts) {
10726
10820
  delete state._error;
10727
10821
  return state;
10728
10822
  }
10823
+ const explainActionError = (err) => {
10824
+ if (!(err && typeof err === "object" && typeof err.message === "string")) return;
10825
+ const e2 = err;
10826
+ const explained = explainWithheldError(e2.message, opts.deviceParity === true);
10827
+ if (explained !== e2.message) {
10828
+ try {
10829
+ e2.message = explained;
10830
+ } catch {
10831
+ }
10832
+ }
10833
+ };
10729
10834
  const commitActionFailure = (def, err) => {
10730
10835
  const prev = def.state;
10731
10836
  if (!prev || typeof prev !== "object" || Array.isArray(prev)) return;
@@ -10756,6 +10861,7 @@ async function bootBundleWorker(bundleJs, opts) {
10756
10861
  const next = await Promise.race([Promise.resolve(fn2(def.state, ...args)), timeout]);
10757
10862
  applyResult(def, next);
10758
10863
  } catch (err) {
10864
+ explainActionError(err);
10759
10865
  commitActionFailure(def, err);
10760
10866
  throw err;
10761
10867
  } finally {
@@ -10771,6 +10877,7 @@ async function bootBundleWorker(bundleJs, opts) {
10771
10877
  def.state = entry.state;
10772
10878
  }
10773
10879
  if (entry.error !== void 0) {
10880
+ entry.error = explainWithheldError(entry.error, opts.deviceParity === true);
10774
10881
  commitActionFailure(def, entry.error);
10775
10882
  if (recordErrors && entry.reportError) errors.push({ service: entry.name, message: entry.error });
10776
10883
  continue;
@@ -11721,6 +11828,10 @@ function bottomSheetFindings(descriptor) {
11721
11828
  visit(descriptor, "", true);
11722
11829
  return out2;
11723
11830
  }
11831
+ function screenDepthFindings(descriptor) {
11832
+ const raw = rawJsonDepthFinding(descriptor);
11833
+ return raw ? [raw] : depthBudgetFindings(descriptor);
11834
+ }
11724
11835
  function depthBudgetIssues(findings) {
11725
11836
  return findings.map((f2) => ({
11726
11837
  severity: "error",
@@ -11966,9 +12077,9 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
11966
12077
  const issues = [];
11967
12078
  const strict = opts.strict ?? false;
11968
12079
  const hygiene = strict ? "error" : "warning";
11969
- const depthFindings = depthBudgetFindings(descriptor);
12080
+ const depthFindings = screenDepthFindings(descriptor);
11970
12081
  const depthIssues = depthBudgetIssues(depthFindings);
11971
- if (depthFindings.some((f2) => f2.measure === "depth" && f2.actual > UNSAFE_TO_RECURSE_DEPTH)) {
12082
+ if (depthFindings.some((f2) => f2.measure === "json" || f2.measure === "depth" && f2.actual > UNSAFE_TO_RECURSE_DEPTH)) {
11972
12083
  return { screenId, ok: false, strict, issues: depthIssues, services: [] };
11973
12084
  }
11974
12085
  const fixture = new FixtureValidator({ tokens: opts.tokens, allowedComponents: opts.allowedComponents });
@@ -12590,6 +12701,8 @@ var init_validate_descriptor_core = __esm({
12590
12701
  init_frame_limits();
12591
12702
  init_rich_content();
12592
12703
  init_worker_subtree();
12704
+ init_raw_json_depth();
12705
+ init_realm_parity();
12593
12706
  init_realm_parity();
12594
12707
  CRAWL_BLOCKING_PASSES = /* @__PURE__ */ new Set([
12595
12708
  // A screen whose bindings do not resolve has no state to crawl.
@@ -14171,8 +14284,10 @@ __export(validate_descriptor_exports, {
14171
14284
  NATIVE_NESTED_PROPS: () => NATIVE_NESTED_PROPS,
14172
14285
  NATIVE_PROP_ENUMS: () => NATIVE_PROP_ENUMS,
14173
14286
  NATIVE_REQUIRED_PROPS: () => NATIVE_REQUIRED_PROPS,
14287
+ RAW_JSON_MAX_DEPTH: () => RAW_JSON_MAX_DEPTH,
14174
14288
  SERVICE_ONLY_BINDING_PROPS: () => SERVICE_ONLY_BINDING_PROPS,
14175
14289
  WHOLE_VALUE_BINDING: () => WHOLE_VALUE_BINDING,
14290
+ allowlistSweepSource: () => allowlistSweepSource,
14176
14291
  appWorkerActionsFor: () => appWorkerActionsFor,
14177
14292
  bareNestedClass: () => bareNestedClass,
14178
14293
  bootBundleWorker: () => bootBundleWorker,
@@ -14185,6 +14300,7 @@ __export(validate_descriptor_exports, {
14185
14300
  discoverNativeActions: () => discoverNativeActions,
14186
14301
  dryRunBundle: () => dryRunBundle,
14187
14302
  enumFieldNames: () => enumFieldNames,
14303
+ explainWithheldError: () => explainWithheldError,
14188
14304
  extractBundleContract: () => extractBundleContract,
14189
14305
  extractBundleServices: () => extractBundleServices,
14190
14306
  findSkipDerive: () => findSkipDerive,
@@ -14202,6 +14318,7 @@ __export(validate_descriptor_exports, {
14202
14318
  pathCrossesOpaqueAuthorJson: () => pathCrossesOpaqueAuthorJson,
14203
14319
  pathValue: () => pathValue,
14204
14320
  pinnedIntrinsicsSource: () => pinnedIntrinsicsSource,
14321
+ rawJsonDepthFinding: () => rawJsonDepthFinding,
14205
14322
  readBuiltBundle: () => readBuiltBundle,
14206
14323
  readBuiltBundleMap: () => readBuiltBundleMap,
14207
14324
  realmInstallsIntl: () => realmInstallsIntl,
@@ -14209,13 +14326,13 @@ __export(validate_descriptor_exports, {
14209
14326
  resolveActionEvaluator: () => resolveActionEvaluator,
14210
14327
  resolveBootEvaluator: () => resolveBootEvaluator,
14211
14328
  resolveEvaluator: () => resolveEvaluator,
14329
+ screenDepthFindings: () => screenDepthFindings,
14212
14330
  serviceOnlyBindingValues: () => serviceOnlyBindingValues,
14213
14331
  setActionEvaluator: () => setActionEvaluator,
14214
14332
  setBootEvaluator: () => setBootEvaluator,
14215
14333
  setDefaultEvaluator: () => setDefaultEvaluator,
14216
14334
  topBarFindings: () => topBarFindings,
14217
14335
  validateScreenDescriptor: () => validateScreenDescriptor,
14218
- withheldGlobalsSource: () => withheldGlobalsSource,
14219
14336
  workerSubtreeAllowed: () => workerSubtreeAllowed
14220
14337
  });
14221
14338
  import { readFileSync as readFileSync15, existsSync as existsSync13, readdirSync as readdirSync11 } from "node:fs";
@@ -14317,8 +14434,9 @@ var init_validate_descriptor = __esm({
14317
14434
  nodeVmEvaluator = {
14318
14435
  run(code, sandbox, opts) {
14319
14436
  const context = createContext(sandbox);
14320
- const hideSource = withheldGlobalsSource((opts.hide ?? []).filter((name) => !(name in sandbox)));
14321
- if (hideSource) runInContext(hideSource, context, { filename: "himi-hide.js" });
14437
+ if (opts.deviceParity) {
14438
+ runInContext(allowlistSweepSource(Object.keys(sandbox)), context, { filename: "himi-sweep.js" });
14439
+ }
14322
14440
  const pins = pinnedIntrinsicsSource(opts);
14323
14441
  if (pins) {
14324
14442
  const pinned = runInContext(`(${pins})(Date, Math)`, context, { timeout: opts.timeoutMs, filename: "himi-pins.js" });
@@ -16057,6 +16175,10 @@ ${opts.liveTransport ? ` // Every action completion crosses as a RAW, ordered r
16057
16175
  // minimum local overlay needed for the worker's own subsequent reads; the host replays these
16058
16176
  // records through applyResult, which remains the authoritative merge/directive dialect.
16059
16177
  var actionBody = "";
16178
+ // The host bridges, captured into this closure: under deviceParity the closing sweep deletes
16179
+ // them from globalThis, so a bundle cannot call a bridge that exists on no device (#4537).
16180
+ var hostWixCall = globalThis.__himiWixCall;
16181
+ var hostDispatch = globalThis.__himiDispatch;
16060
16182
  var isArray = Array.isArray;
16061
16183
  var promiseResolve = Promise.resolve.bind(Promise);
16062
16184
  var promiseReject = Promise.reject.bind(Promise);
@@ -16305,7 +16427,7 @@ ${opts.liveTransport ? ` var handle = { getState: function () { return def
16305
16427
  call: function (method, path, body, headers) {
16306
16428
  var raw;
16307
16429
  try {
16308
- raw = __himiWixCall(
16430
+ raw = hostWixCall(
16309
16431
  String(method),
16310
16432
  String(path),
16311
16433
  stringify(body === undefined ? null : body),
@@ -16327,7 +16449,7 @@ ${opts.liveTransport ? ` var handle = { getState: function () { return def
16327
16449
  dispatch: function (action, params) {
16328
16450
  var raw;
16329
16451
  try {
16330
- raw = __himiDispatch(stringValue(action), stringify(params === undefined ? null : params));
16452
+ raw = hostDispatch(stringValue(action), stringify(params === undefined ? null : params));
16331
16453
  } catch (e) {
16332
16454
  return promiseReject(e instanceof Error ? e : new Error(stringValue(e)));
16333
16455
  }
@@ -16456,9 +16578,11 @@ ${opts.clock === void 0 ? "" : opts.freezeDate ? ` globalThis.Date = (${pinnedI
16456
16578
  return sweepPromise;
16457
16579
  },
16458
16580
  });` : ""}
16459
- // LAST, so nothing the prelude installed (the \`fetch\` stub) or the engine carries natively
16460
- // (QuickJS-ng 0.15's \`performance\`, \`queueMicrotask\`) survives deviceParity (#4511).
16461
- ${withheldGlobalsSource(opts.hide)}
16581
+ // LAST, under deviceParity: the realm sweeps its OWN globals against the worker allow-list
16582
+ // (#4537), so nothing the prelude installed (the \`fetch\` stub), nothing the engine carries
16583
+ // natively (QuickJS-ng 0.15's \`performance\`, \`queueMicrotask\`) and none of the host bridges
16584
+ // (\`__himiWixCall\`, \`__himiDispatch\` \u2014 captured into this closure above) survives.
16585
+ ${opts.deviceParity ? allowlistSweepSource() : ""}
16462
16586
  return ${opts.liveTransport ? "{ readback: readback, dispatch: dispatchAction, actions: readActions }" : "readback"};
16463
16587
  })();
16464
16588
  `;
@@ -16527,6 +16651,7 @@ async function createQuickJsEvaluator(options = {}) {
16527
16651
  clock: opts.clock ?? options.clock,
16528
16652
  freezeDate: opts.clock !== void 0,
16529
16653
  hide: opts.hide,
16654
+ deviceParity: opts.deviceParity === true,
16530
16655
  ...opts.seed === void 0 ? {} : { seed: opts.seed },
16531
16656
  ...opts.mirrorRandom ? { mirrorRandom: true } : {}
16532
16657
  }),
@@ -130765,6 +130890,10 @@ function composeStickyTree(payload, header, footer) {
130765
130890
  }
130766
130891
  async function renderDescriptorWeb(descriptor, tokens2, scheme, wantPng, services = null, width, isRTL, idiomContext) {
130767
130892
  const desc = descriptor;
130893
+ const tooDeep = rawJsonDepthFinding(descriptor);
130894
+ if (tooDeep) return { renderable: false, reason: `depth_budget_exceeded: ${tooDeep.message}` };
130895
+ const tooDeepState = services ? rawJsonDepthFinding(services) : null;
130896
+ if (tooDeepState) return { renderable: false, reason: `depth_budget_exceeded: worker state \u2014 ${tooDeepState.message}` };
130768
130897
  const payload = desc.payload;
130769
130898
  if (!payload || typeof payload !== "object" || !payload.body) {
130770
130899
  const renderer = desc.renderer ?? "unknown";
@@ -130932,6 +131061,7 @@ var init_render_descriptor = __esm({
130932
131061
  init_FakeServiceProvider();
130933
131062
  init_env();
130934
131063
  init_media_value();
131064
+ init_raw_json_depth();
130935
131065
  init_message_format();
130936
131066
  init_a11y_contract();
130937
131067
  A11Y_MODIFIER_KEYS = [
@@ -132515,15 +132645,22 @@ async function handleBootRequest(req, res, state) {
132515
132645
  const tokenDocument = effectiveAppTokens(req, state);
132516
132646
  const homeScreenId = config.homeScreenId ?? state.app.meta.homeScreenId ?? null;
132517
132647
  let screen = null;
132648
+ let homeScreenRefusal;
132518
132649
  if (homeScreenId) {
132519
132650
  const evalCtx = evalContextFromReq(req, state.appName);
132520
132651
  const caller = await resolveCaller(state.app.module, req);
132521
- const descriptor = await buildDescriptorForScreenId(
132522
- reqState,
132523
- homeScreenId,
132524
- new URLSearchParams(),
132525
- { ctx: evalCtx, caller }
132526
- );
132652
+ let descriptor = null;
132653
+ try {
132654
+ descriptor = await buildDescriptorForScreenId(
132655
+ reqState,
132656
+ homeScreenId,
132657
+ new URLSearchParams(),
132658
+ { ctx: evalCtx, caller }
132659
+ );
132660
+ } catch (err) {
132661
+ if (!(err instanceof DescriptorDepthRefusal)) throw err;
132662
+ homeScreenRefusal = { error: "depth_budget_exceeded", screenId: err.screenId, finding: err.finding };
132663
+ }
132527
132664
  if (descriptor && typeof descriptor === "object") {
132528
132665
  assignStableIds(descriptor);
132529
132666
  const acceptLanguage = req.headers["accept-language"];
@@ -132540,7 +132677,8 @@ async function handleBootRequest(req, res, state) {
132540
132677
  tokens: tokenDocument?.tokens ?? null,
132541
132678
  homeScreenId,
132542
132679
  screen,
132543
- homeBundle: null
132680
+ homeBundle: null,
132681
+ ...homeScreenRefusal ? { screenRefusal: homeScreenRefusal } : {}
132544
132682
  };
132545
132683
  const body = JSON.stringify(payload);
132546
132684
  const etag = makeEtag(body);
@@ -132738,10 +132876,15 @@ function descriptorCacheKey(reqBaseUrl, localeKey, flagKey, caller) {
132738
132876
  const who = caller.memberId ? `::mid=${caller.memberId}` : "";
132739
132877
  return `${reqBaseUrl}::${localeKey}::${flagKey}::ent=${caller.entitled ? 1 : 0}${who}`;
132740
132878
  }
132879
+ function refuseTooDeep(screenId, descriptor) {
132880
+ const finding = rawJsonDepthFinding(descriptor);
132881
+ if (finding) throw new DescriptorDepthRefusal(screenId, finding);
132882
+ return descriptor;
132883
+ }
132741
132884
  async function buildDescriptorForScreenId(state, screenId, query = new URLSearchParams(), options = {}) {
132742
132885
  if (!options.skipOverride) {
132743
132886
  const preview = state.localOverrides.get(screenId);
132744
- if (preview) return { ...preview, screenId };
132887
+ if (preview) return refuseTooDeep(screenId, { ...preview, screenId });
132745
132888
  }
132746
132889
  if (state.app) {
132747
132890
  await state.flagSync.refresh(state.app?.dir, state.flagStore, Date.now());
@@ -132767,7 +132910,7 @@ async function buildDescriptorForScreenId(state, screenId, query = new URLSearch
132767
132910
  }
132768
132911
  return null;
132769
132912
  })();
132770
- return built ? postprocessDescriptor(built, {
132913
+ return built ? postprocessDescriptor(refuseTooDeep(screenId, built), {
132771
132914
  config: uiConfig(state.app.module.config),
132772
132915
  flagDefinitions: state.flagStore.list(),
132773
132916
  evalContext: evalCtx,
@@ -135488,6 +135631,9 @@ function createDevServer(opts = {}) {
135488
135631
  send(res, 404, "not found");
135489
135632
  } catch (err) {
135490
135633
  console.error("[himalaya-dev] handler error:", err);
135634
+ if (err instanceof DescriptorDepthRefusal) {
135635
+ return sendJson(res, 422, { error: "depth_budget_exceeded", screenId: err.screenId, finding: err.finding });
135636
+ }
135491
135637
  if (isContainedCapabilityRefusal(err)) {
135492
135638
  return sendJson(res, 422, {
135493
135639
  error: "contained_capability_refused",
@@ -135539,7 +135685,7 @@ ${err.message}`);
135539
135685
  });
135540
135686
  return httpServer;
135541
135687
  }
135542
- var ROOT6, TIER3_ROOT, TIER4_ROOT, tier4Verifier, TIER5_ROOT2, ACTION_HANDLERS_ROOT, SAMPLES_ROOT, TELEMETRY_BUFFER_CAP, overrideHistorySeq, CACHE_CONTROL, TIER5_SRC_ROOT, HMR_TMP_DIR, appReloadSeq, appReloadChain, WEBWIX_CLIENT_ID, WEBWIX_REDIRECT, WEBWIX_COOKIE, webOwnerPreviewInstances, webAccountMemberInstances, MIME, widgetOverrides;
135688
+ var ROOT6, TIER3_ROOT, TIER4_ROOT, tier4Verifier, TIER5_ROOT2, ACTION_HANDLERS_ROOT, SAMPLES_ROOT, TELEMETRY_BUFFER_CAP, overrideHistorySeq, CACHE_CONTROL, DescriptorDepthRefusal, TIER5_SRC_ROOT, HMR_TMP_DIR, appReloadSeq, appReloadChain, WEBWIX_CLIENT_ID, WEBWIX_REDIRECT, WEBWIX_COOKIE, webOwnerPreviewInstances, webAccountMemberInstances, MIME, widgetOverrides;
135543
135689
  var init_src6 = __esm({
135544
135690
  "../../core/dev-server/src/index.ts"() {
135545
135691
  init_inject();
@@ -135568,6 +135714,7 @@ var init_src6 = __esm({
135568
135714
  init_player_proxy();
135569
135715
  init_app_icon();
135570
135716
  init_app_assets();
135717
+ init_raw_json_depth();
135571
135718
  init_app_module();
135572
135719
  init_provisioning_facts();
135573
135720
  init_validate();
@@ -135594,6 +135741,14 @@ var init_src6 = __esm({
135594
135741
  TELEMETRY_BUFFER_CAP = 1e3;
135595
135742
  overrideHistorySeq = 0;
135596
135743
  CACHE_CONTROL = "max-age=600, stale-while-revalidate=86400";
135744
+ DescriptorDepthRefusal = class extends Error {
135745
+ constructor(screenId, finding) {
135746
+ super(finding.message);
135747
+ this.screenId = screenId;
135748
+ this.finding = finding;
135749
+ this.name = "DescriptorDepthRefusal";
135750
+ }
135751
+ };
135597
135752
  TIER5_SRC_ROOT = resolve38(ROOT6, "core/dev-server/tier5-bundles-src");
135598
135753
  HMR_TMP_DIR = resolve38(ROOT6, "node_modules/.cache/himalaya-hmr");
135599
135754
  appReloadSeq = 0;
@@ -139631,11 +139786,33 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
139631
139786
  base = BAKE_OFFLINE_BASE;
139632
139787
  }
139633
139788
  const devFetch = (url, init) => dev.inject(new Request(url, init));
139789
+ const depthRefusalFromText = (status, text9) => {
139790
+ if (status !== 422) return null;
139791
+ try {
139792
+ const body = JSON.parse(text9);
139793
+ return body?.error === "depth_budget_exceeded" && body.finding ? body.finding : null;
139794
+ } catch {
139795
+ return null;
139796
+ }
139797
+ };
139798
+ const depthRefusalOf = async (res) => depthRefusalFromText(res.status, await res.text());
139799
+ const recordUnbuilt = async (screenId, res) => {
139800
+ const text9 = await res.text();
139801
+ const finding = depthRefusalFromText(res.status, text9);
139802
+ if (finding) {
139803
+ failedToBuild.push({ screenId, status: res.status, error: finding.message });
139804
+ if (collect2?.lint && !lint.some((l2) => l2.screenId === screenId)) {
139805
+ lint.push({ screenId, ok: false, issues: depthBudgetIssues([finding]) });
139806
+ }
139807
+ return;
139808
+ }
139809
+ failedToBuild.push({ screenId, status: res.status, error: text9.slice(0, 2e3) });
139810
+ };
139634
139811
  try {
139635
139812
  for (const screenId of screenIds) {
139636
139813
  const res = await devFetch(`${base}/v1/screen/${screenId}`, { headers: { "X-Himalaya-App": app } });
139637
139814
  if (!res.ok) {
139638
- failedToBuild.push({ screenId, status: res.status, error: (await res.text()).slice(0, 2e3) });
139815
+ await recordUnbuilt(screenId, res);
139639
139816
  continue;
139640
139817
  }
139641
139818
  const descriptor = await res.json();
@@ -139728,6 +139905,10 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
139728
139905
  const { services, liveCalls, liveReturned, liveErrors, mockCalls, mockUnserved, mockBootFailed, liveFailed } = await renderServicesFor(bundleName, descriptor, wantedRender);
139729
139906
  const rr2 = await renderDescriptorWeb(descriptor, renderTokens, renderScheme, collect2.render.png === true, services, collect2.render.width, collect2.render.rtl === true);
139730
139907
  renders.push({ screenId: wantedRender, scheme: renderScheme, ...rr2, ...await renderCompare(descriptor, services), ...collect2.render.dumpState && services ? { serviceState: services } : {}, ...liveCalls ? { liveCalls } : {}, ...liveReturned !== void 0 ? { liveReturned } : {}, ...liveFailed ? { liveFailed } : {}, ...liveErrors ? { liveErrors } : {}, ...mockCalls ? { mockCalls, mockUnserved } : {}, ...mockBootFailed ? { mockBootFailed } : {} });
139908
+ } else if (res.status === 422 && collect2?.lint) {
139909
+ const finding = await depthRefusalOf(res);
139910
+ const recorded = lint.some((l2) => l2.screenId === wantedRender) || failedToBuild.some((f2) => f2.screenId === wantedRender);
139911
+ if (finding && !recorded) lint.push({ screenId: wantedRender, ok: false, issues: depthBudgetIssues([finding]) });
139731
139912
  }
139732
139913
  }
139733
139914
  for (const t2 of preloaded.module.screens.templated ?? []) {
@@ -139782,7 +139963,7 @@ async function buildFromLoaded(preloaded, dest, opts = {}) {
139782
139963
  if (screenId.includes("/") || screenId.includes("..")) continue;
139783
139964
  const res = await devFetch(`${base}/v1/screen/${encodeURIComponent(screenId)}`, { headers: { "X-Himalaya-App": app } });
139784
139965
  if (!res.ok) {
139785
- failedToBuild.push({ screenId, status: res.status, error: (await res.text()).slice(0, 2e3) });
139966
+ await recordUnbuilt(screenId, res);
139786
139967
  continue;
139787
139968
  }
139788
139969
  const descriptor = await res.json();
@@ -180791,7 +180972,7 @@ var NONDETERMINISTIC_FIX = "move those clock or random values out of the service
180791
180972
  function depthBudgetExceeded(report3) {
180792
180973
  return (report3.issues ?? []).some((i2) => i2.severity === "error" && i2.pass === "depth-budget");
180793
180974
  }
180794
- var DEPTH_BUDGET_HINT = "a screen or row template is over the depth budget (see the depth-budget issues' path and limit) \u2014 flatten it; `--force` does not override this";
180975
+ var DEPTH_BUDGET_HINT = "a screen or row template nests too many components, or JSON in its props, modifiers, `input` or action `args` nests past the 256-level ceiling (see the depth-budget issues' path and limit) \u2014 flatten it; `--force` does not override this";
180795
180976
  function summarizeLint(app, bres, outcome, strict, extraIssues = []) {
180796
180977
  const issues = (outcome.lint ?? []).flatMap((s2) => s2.issues.map((i2) => ({ screen: s2.screenId, ...i2 })));
180797
180978
  issues.push(...extraIssues);
@@ -186198,7 +186379,8 @@ ${[...afterRun.next, ...testReminder ? [testReminder] : []].join("\n")}`);
186198
186379
  });
186199
186380
  const r2 = (outcome.renders ?? [])[0];
186200
186381
  if (!r2) {
186201
- print({ ok: false, screenId, error: "screen not found or did not build", failedToBuild: outcome.failedToBuild });
186382
+ const why = (outcome.lint ?? []).find((s2) => s2.screenId === screenId)?.issues ?? [];
186383
+ print({ ok: false, screenId, error: "screen not found or did not build", failedToBuild: outcome.failedToBuild, ...why.length ? { issues: why.map((i2) => ({ screen: screenId, ...i2 })) } : {} });
186202
186384
  return 1;
186203
186385
  }
186204
186386
  const diagnostics = (outcome.lint ?? []).find((s2) => s2.screenId === screenId)?.issues ?? [];
@@ -188449,8 +188631,8 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
188449
188631
  io2.error(JSON.stringify({ ok: false, error: "invalid_descriptor", file: file2, screen: id, message: `${file2} is not valid JSON: ${e2.message}` }));
188450
188632
  return 1;
188451
188633
  }
188452
- const { depthBudgetFindings: depthBudgetFindings2, depthBudgetIssues: depthBudgetIssues2 } = await Promise.resolve().then(() => (init_validate_descriptor_core(), validate_descriptor_core_exports));
188453
- const tooDeep = depthBudgetIssues2(depthBudgetFindings2(parsed));
188634
+ const { screenDepthFindings: screenDepthFindings2, depthBudgetIssues: depthBudgetIssues2 } = await Promise.resolve().then(() => (init_validate_descriptor_core(), validate_descriptor_core_exports));
188635
+ const tooDeep = depthBudgetIssues2(screenDepthFindings2(parsed));
188454
188636
  if (tooDeep.length > 0) {
188455
188637
  io2.error(JSON.stringify({
188456
188638
  ok: false,
@@ -188461,7 +188643,7 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
188461
188643
  screen: id,
188462
188644
  file: file2,
188463
188645
  issues: tooDeep,
188464
- hint: "flatten the descriptor below the depth budget (see each issue's path and limit); `--force` does not override this"
188646
+ hint: DEPTH_BUDGET_HINT
188465
188647
  }, null, 2));
188466
188648
  return 1;
188467
188649
  }
@@ -47,7 +47,7 @@ keeps its existing fields and adds the code:
47
47
  | `ux_evidence_inherited_screens` | A partial push inherits screens not covered by local UX evidence. |
48
48
  | `ux_evidence_unreadable` | The UX evidence artifact cannot be read. |
49
49
  | `lint_errors` | Build/lint validation found errors. |
50
- | `depth_budget_exceeded` | A screen or a bound list/grid row template nests deeper, or has more components, than the depth budget (`depth-budget` validator pass). Each `issues[]` entry with `pass: "depth-budget"` carries the JSON `path` of the deepest node (or the tree root for a node count), the `limit` and the `actual` value. Takes precedence over `lint_errors`, and `--force` does not override it: content over the budget overflows the renderer's stack on devices already in the field. Flatten the layout and push again. `himi patch screen` refuses an over-deep raw descriptor with the same code, before any network request, and `himi pack` / `himi template publish` refuse with it even under `--force`. |
50
+ | `depth_budget_exceeded` | A screen or a bound list/grid row template nests deeper, or has more components, than the depth budget (`depth-budget` validator pass), or any of its JSON — props, modifiers, `input`, action `args` — nests past the raw-JSON ceiling of 256 levels. Each `issues[]` entry with `pass: "depth-budget"` carries the JSON `path` of the deepest node (the tree root for a node count, the first value past the ceiling for raw JSON), the `limit` and the `actual` value. Takes precedence over `lint_errors`, and `--force` does not override it: content over the budget overflows the renderer's stack on devices already in the field. Flatten the layout and push again. `himi patch screen` refuses an over-deep raw descriptor with the same code, before any network request, and `himi pack` / `himi template publish` refuse with it even under `--force`. |
51
51
  | `control_token_expired` | The selected control token has expired. |
52
52
  | `app_source_contains_secrets` | Source archiving detected a possible credential. |
53
53
  | `app_source_too_large` | The encoded source archive exceeds its limit. |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1422.0",
3
+ "version": "0.1424.0",
4
4
  "type": "module",
5
5
  "description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
6
6
  "keywords": [
@@ -71,5 +71,5 @@
71
71
  "artifactId": "himalaya-cli"
72
72
  }
73
73
  },
74
- "falconPackageHash": "1e2ddc2aad76a7124c849d88d5862e6b66a03a169e9c066cea398548"
74
+ "falconPackageHash": "a0147b33ea7375fa7cf11b0027a0388feef592ede284346c3d5c9919"
75
75
  }