@wix/himalaya-cli 0.1418.0 → 0.1419.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
@@ -10096,6 +10096,7 @@ var validate_descriptor_core_exports = {};
10096
10096
  __export(validate_descriptor_core_exports, {
10097
10097
  CORE_PACKAGES: () => CORE_PACKAGES,
10098
10098
  CRAWL_BLOCKING_PASSES: () => CRAWL_BLOCKING_PASSES,
10099
+ DESCRIPTOR_DEPTH_BUDGETS: () => DESCRIPTOR_DEPTH_BUDGETS,
10099
10100
  DEVICE_PARITY_WITHHELD: () => DEVICE_PARITY_WITHHELD,
10100
10101
  EMBEDDED_BINDING: () => EMBEDDED_BINDING,
10101
10102
  INTEGER_MODIFIERS: () => INTEGER_MODIFIERS,
@@ -10118,6 +10119,8 @@ __export(validate_descriptor_core_exports, {
10118
10119
  bootBundleWorker: () => bootBundleWorker,
10119
10120
  collectServiceBindings: () => collectServiceBindings,
10120
10121
  componentPropFindings: () => componentPropFindings,
10122
+ depthBudgetFindings: () => depthBudgetFindings,
10123
+ depthBudgetIssues: () => depthBudgetIssues,
10121
10124
  dryRunBundle: () => dryRunBundle,
10122
10125
  enumFieldNames: () => enumFieldNames,
10123
10126
  extractBundleContract: () => extractBundleContract,
@@ -11612,6 +11615,100 @@ function bottomSheetFindings(descriptor) {
11612
11615
  visit(descriptor, "", true);
11613
11616
  return out2;
11614
11617
  }
11618
+ function depthBudgetIssues(findings) {
11619
+ return findings.map((f2) => ({
11620
+ severity: "error",
11621
+ pass: "depth-budget",
11622
+ path: f2.path,
11623
+ message: f2.message,
11624
+ limit: f2.limit,
11625
+ actual: f2.actual
11626
+ }));
11627
+ }
11628
+ function depthBudgetFindings(descriptor) {
11629
+ const out2 = [];
11630
+ const templates = [];
11631
+ const isComponent = (v2) => {
11632
+ const body = v2?.body;
11633
+ return !!body && typeof body === "object" && typeof body.type === "string" && typeof body.package === "string";
11634
+ };
11635
+ const hostedTemplates = (v2, path2) => {
11636
+ const props = v2.body.props;
11637
+ const found = [];
11638
+ if (!props) return found;
11639
+ const itemsPath = props.itemsPath ?? props.items_path;
11640
+ const rowHost = v2.body.package === "himalaya" ? ROW_TEMPLATE_HOST_TYPES.has(v2.body.type) : true;
11641
+ if (rowHost && typeof itemsPath === "string" && itemsPath !== "" && Array.isArray(props.items) && isComponent(props.items[0])) {
11642
+ found.push({ node: props.items[0], path: `${path2}.body.props.items[0]`, host: `${v2.body.type} at ${path2}` });
11643
+ }
11644
+ if (isComponent(props.eventTemplate)) {
11645
+ found.push({ node: props.eventTemplate, path: `${path2}.body.props.eventTemplate`, host: `${v2.body.type} at ${path2}` });
11646
+ }
11647
+ return found;
11648
+ };
11649
+ const measure2 = (root, rootPath, mode) => {
11650
+ let depth = 0, nodes = 0, deepest = rootPath;
11651
+ const skip = /* @__PURE__ */ new Set();
11652
+ const stack = [
11653
+ { v: root, path: rootPath, depth: 0, atRoot: mode === "screen" }
11654
+ ];
11655
+ while (stack.length) {
11656
+ const { v: v2, path: path2, depth: parentDepth, atRoot } = stack.pop();
11657
+ if (skip.has(v2)) continue;
11658
+ if (Array.isArray(v2)) {
11659
+ for (let i2 = v2.length - 1; i2 >= 0; i2--) stack.push({ v: v2[i2], path: `${path2}[${i2}]`, depth: parentDepth, atRoot: false });
11660
+ continue;
11661
+ }
11662
+ if (!v2 || typeof v2 !== "object") continue;
11663
+ let here = parentDepth;
11664
+ if (isComponent(v2)) {
11665
+ here = parentDepth + 1;
11666
+ nodes += 1;
11667
+ if (here > depth) {
11668
+ depth = here;
11669
+ deepest = path2;
11670
+ }
11671
+ for (const t2 of hostedTemplates(v2, path2)) {
11672
+ if (mode === "screen") templates.push(t2);
11673
+ else skip.add(t2.node);
11674
+ }
11675
+ }
11676
+ const entries = Object.entries(v2);
11677
+ for (let i2 = entries.length - 1; i2 >= 0; i2--) {
11678
+ const [key2, child] = entries[i2];
11679
+ if (!child || typeof child !== "object" || isOpaqueAuthorJson(key2, child, atRoot)) continue;
11680
+ stack.push({ v: child, path: `${path2}.${key2}`, depth: here, atRoot: false });
11681
+ }
11682
+ }
11683
+ return { depth, nodes, deepest };
11684
+ };
11685
+ const flatten = "flatten the layout \u2014 drop wrapper stacks, or move a section into its own screen";
11686
+ const report3 = (what, m2, budget, rootPath) => {
11687
+ if (m2.depth > budget.maxDepth) {
11688
+ out2.push({
11689
+ measure: "depth",
11690
+ path: m2.deepest,
11691
+ limit: budget.maxDepth,
11692
+ actual: m2.depth,
11693
+ message: `${what} nests ${m2.depth} component levels; the limit is ${budget.maxDepth} (deepest node at ${m2.deepest}) \u2014 ${flatten}`
11694
+ });
11695
+ }
11696
+ if (m2.nodes > budget.maxNodes) {
11697
+ out2.push({
11698
+ measure: "nodes",
11699
+ path: rootPath,
11700
+ limit: budget.maxNodes,
11701
+ actual: m2.nodes,
11702
+ message: `${what} has ${m2.nodes} components; the limit is ${budget.maxNodes} \u2014 ${flatten}`
11703
+ });
11704
+ }
11705
+ };
11706
+ report3("screen", measure2(descriptor, "$", "screen"), DESCRIPTOR_DEPTH_BUDGETS.screen, "$");
11707
+ for (const t2 of templates) {
11708
+ report3(`row template of the ${t2.host}`, measure2(t2.node, t2.path, "template"), DESCRIPTOR_DEPTH_BUDGETS.rowTemplate, t2.path);
11709
+ }
11710
+ return out2;
11711
+ }
11615
11712
  function topBarFindings(descriptor) {
11616
11713
  const out2 = [];
11617
11714
  const bar = descriptor?.topBar;
@@ -11763,6 +11860,11 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
11763
11860
  const issues = [];
11764
11861
  const strict = opts.strict ?? false;
11765
11862
  const hygiene = strict ? "error" : "warning";
11863
+ const depthFindings = depthBudgetFindings(descriptor);
11864
+ const depthIssues = depthBudgetIssues(depthFindings);
11865
+ if (depthFindings.some((f2) => f2.measure === "depth" && f2.actual > UNSAFE_TO_RECURSE_DEPTH)) {
11866
+ return { screenId, ok: false, strict, issues: depthIssues, services: [] };
11867
+ }
11766
11868
  const fixture = new FixtureValidator({ tokens: opts.tokens, allowedComponents: opts.allowedComponents });
11767
11869
  const v2 = fixture.validate(descriptor);
11768
11870
  const lbIntegrity = descriptor?.logicBundle?.integrity;
@@ -11866,6 +11968,7 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
11866
11968
  for (const finding of topBarFindings(descriptor)) {
11867
11969
  issues.push({ severity: "warning", pass: "top-bar", path: finding.path, message: finding.message });
11868
11970
  }
11971
+ issues.push(...depthIssues);
11869
11972
  const bindings = collectServiceBindings(descriptor);
11870
11973
  const descriptorActions = collectDescriptorActions(descriptor);
11871
11974
  const workerActions = descriptorActions.filter(({ actionId }) => actionId.startsWith("tier5."));
@@ -12365,7 +12468,7 @@ function validateScreenDescriptor(screenId, descriptor, opts = {}) {
12365
12468
  services: Object.keys(services)
12366
12469
  };
12367
12470
  }
12368
- var CRAWL_BLOCKING_PASSES, BOOT_EVAL_TIMEOUT_MS, DRIFT_PROBES, DEVICE_PARITY_WITHHELD, defaultEvaluator, bootEvaluator, actionEvaluator, NATIVE_PROP_ENUMS, KNOWN_PROPS, IN_ARRAY_BINDING_PROPS, SERVICE_SIGIL, INTEGER_PROPS, NATIVE_REQUIRED_PROPS, bareNestedClass, NATIVE_NESTED_CLASSES, NATIVE_MODIFIERS_CLASS, NATIVE_DESCRIPTOR_CLASS, NATIVE_DESCRIPTOR_NESTED, NATIVE_NESTED_PROPS, LENIENT_TOP_BAR_BLOCKS, MODIFIER_KEYS, INTEGER_MODIFIERS, MODIFIER_KINDS, SCHEMA_SCORED_NODES, CORE_PACKAGES, LEGACY_UNSCORED_KEYS, DYNAMIC_PLACEHOLDER, LITERAL_ONLY_PROPS, SERVICE_ONLY_BINDING_PROPS, WHOLE_VALUE_BINDING, EMBEDDED_BINDING, OPTIONS_BINDING_PROPS, EDITOR_SERVICE_NAME, SHEET_PLACEMENTS, TOP_BAR_TRAILING_LIMIT, TOP_BAR_NON_TRAILING, BUTTON_STYLES, EMPTY_STATE_ACTION_STYLES, EMPTY_STATE_ACTION_SIZES;
12471
+ var CRAWL_BLOCKING_PASSES, BOOT_EVAL_TIMEOUT_MS, DRIFT_PROBES, DEVICE_PARITY_WITHHELD, defaultEvaluator, bootEvaluator, actionEvaluator, NATIVE_PROP_ENUMS, KNOWN_PROPS, IN_ARRAY_BINDING_PROPS, SERVICE_SIGIL, INTEGER_PROPS, NATIVE_REQUIRED_PROPS, bareNestedClass, NATIVE_NESTED_CLASSES, NATIVE_MODIFIERS_CLASS, NATIVE_DESCRIPTOR_CLASS, NATIVE_DESCRIPTOR_NESTED, NATIVE_NESTED_PROPS, LENIENT_TOP_BAR_BLOCKS, MODIFIER_KEYS, INTEGER_MODIFIERS, MODIFIER_KINDS, SCHEMA_SCORED_NODES, CORE_PACKAGES, LEGACY_UNSCORED_KEYS, DYNAMIC_PLACEHOLDER, LITERAL_ONLY_PROPS, SERVICE_ONLY_BINDING_PROPS, WHOLE_VALUE_BINDING, EMBEDDED_BINDING, OPTIONS_BINDING_PROPS, EDITOR_SERVICE_NAME, SHEET_PLACEMENTS, DESCRIPTOR_DEPTH_BUDGETS, UNSAFE_TO_RECURSE_DEPTH, ROW_TEMPLATE_HOST_TYPES, TOP_BAR_TRAILING_LIMIT, TOP_BAR_NON_TRAILING, BUTTON_STYLES, EMPTY_STATE_ACTION_STYLES, EMPTY_STATE_ACTION_SIZES;
12369
12472
  var init_validate_descriptor_core = __esm({
12370
12473
  "../../core/dev-server/src/validate-descriptor.core.ts"() {
12371
12474
  init_validator();
@@ -12379,6 +12482,7 @@ var init_validate_descriptor_core = __esm({
12379
12482
  init_media_value();
12380
12483
  init_frame_limits();
12381
12484
  init_rich_content();
12485
+ init_worker_subtree();
12382
12486
  CRAWL_BLOCKING_PASSES = /* @__PURE__ */ new Set([
12383
12487
  // A screen whose bindings do not resolve has no state to crawl.
12384
12488
  "bindings",
@@ -12386,7 +12490,11 @@ var init_validate_descriptor_core = __esm({
12386
12490
  "worker-subtree",
12387
12491
  // A value outside a strict native enum decodes to nothing on device, so the crawl would be
12388
12492
  // exercising a screen the device cannot render.
12389
- "strict-enum"
12493
+ "strict-enum",
12494
+ // #4498 — a screen or row template over the nesting/node budget overflows a native stack on
12495
+ // decode/render, so there is no screen to crawl. (The publish refusal is separate: the pass is
12496
+ // always an error, and `himi push` refuses a depth-budget error even under `--force`.)
12497
+ "depth-budget"
12390
12498
  ]);
12391
12499
  BOOT_EVAL_TIMEOUT_MS = 2e3;
12392
12500
  DRIFT_PROBES = [
@@ -13358,6 +13466,12 @@ var init_validate_descriptor_core = __esm({
13358
13466
  OPTIONS_BINDING_PROPS = /* @__PURE__ */ new Set(["optionsPath"]);
13359
13467
  EDITOR_SERVICE_NAME = /^[A-Za-z][A-Za-z0-9_-]{0,63}$/;
13360
13468
  SHEET_PLACEMENTS = /* @__PURE__ */ new Set(["overlay", "inline"]);
13469
+ DESCRIPTOR_DEPTH_BUDGETS = Object.freeze({
13470
+ screen: Object.freeze({ maxDepth: 40, maxNodes: 2e3 }),
13471
+ rowTemplate: Object.freeze({ maxDepth: WORKER_SUBTREE_CEILINGS.maxDepth, maxNodes: WORKER_SUBTREE_CEILINGS.maxNodes })
13472
+ });
13473
+ UNSAFE_TO_RECURSE_DEPTH = 128;
13474
+ ROW_TEMPLATE_HOST_TYPES = /* @__PURE__ */ new Set(["LazyList", "Grid", "Carousel", "Menu"]);
13361
13475
  TOP_BAR_TRAILING_LIMIT = 2;
13362
13476
  TOP_BAR_NON_TRAILING = /* @__PURE__ */ new Set(["leading", "principal", "bottom", "keyboard", "overflow"]);
13363
13477
  BUTTON_STYLES = /* @__PURE__ */ new Set(["filled", "tonal", "outlined", "text", "elevated", "bordered", "prominent", "plain", "glass", "primary", "secondary", "tertiary", "link", "destructive"]);
@@ -13882,6 +13996,7 @@ var validate_descriptor_exports = {};
13882
13996
  __export(validate_descriptor_exports, {
13883
13997
  CORE_PACKAGES: () => CORE_PACKAGES,
13884
13998
  CRAWL_BLOCKING_PASSES: () => CRAWL_BLOCKING_PASSES,
13999
+ DESCRIPTOR_DEPTH_BUDGETS: () => DESCRIPTOR_DEPTH_BUDGETS,
13885
14000
  DEVICE_PARITY_WITHHELD: () => DEVICE_PARITY_WITHHELD,
13886
14001
  EMBEDDED_BINDING: () => EMBEDDED_BINDING,
13887
14002
  INTEGER_MODIFIERS: () => INTEGER_MODIFIERS,
@@ -13906,6 +14021,8 @@ __export(validate_descriptor_exports, {
13906
14021
  bundleNameFromUrl: () => bundleNameFromUrl,
13907
14022
  collectServiceBindings: () => collectServiceBindings,
13908
14023
  componentPropFindings: () => componentPropFindings,
14024
+ depthBudgetFindings: () => depthBudgetFindings,
14025
+ depthBudgetIssues: () => depthBudgetIssues,
13909
14026
  discoverNativeActions: () => discoverNativeActions,
13910
14027
  dryRunBundle: () => dryRunBundle,
13911
14028
  enumFieldNames: () => enumFieldNames,
@@ -141168,7 +141285,8 @@ var init_push_errors = __esm({
141168
141285
  "not_carried_by_patch",
141169
141286
  "authoring_unavailable",
141170
141287
  "push_failed",
141171
- "binary_capability_gap"
141288
+ "binary_capability_gap",
141289
+ "depth_budget_exceeded"
141172
141290
  ];
141173
141291
  UX_CODES = {
141174
141292
  absent: "ux_evidence_absent",
@@ -180506,6 +180624,10 @@ function describeNondeterministicWorkers(workers) {
180506
180624
  return workers.length <= 1 ? workers.map(one).join("") : `${workers.slice(0, -1).map(one).join(", ")} and ${one(workers[workers.length - 1])}`;
180507
180625
  }
180508
180626
  var NONDETERMINISTIC_FIX = "move those clock or random values out of the service's initial state and compute them in an action instead (for example one the screen's onAppear dispatches)";
180627
+ function depthBudgetExceeded(report3) {
180628
+ return (report3.issues ?? []).some((i2) => i2.severity === "error" && i2.pass === "depth-budget");
180629
+ }
180630
+ 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";
180509
180631
  function summarizeLint(app, bres, outcome, strict, extraIssues = []) {
180510
180632
  const issues = (outcome.lint ?? []).flatMap((s2) => s2.issues.map((i2) => ({ screen: s2.screenId, ...i2 })));
180511
180633
  issues.push(...extraIssues);
@@ -184630,6 +184752,10 @@ ${doNow.map((l2) => ` ${l2}`).join("\n")}`);
184630
184752
  ...await navIssues2(buildDir, strict),
184631
184753
  ...scaffoldSourceFindings
184632
184754
  ]);
184755
+ if (depthBudgetExceeded(report3)) {
184756
+ print({ ...report3, published: false, code: "depth_budget_exceeded", hint: DEPTH_BUDGET_HINT });
184757
+ return 1;
184758
+ }
184633
184759
  if (hasErrors && !force) {
184634
184760
  print({ ...report3, published: false, hint: "lint found errors \u2014 fix them, or re-run with --force to publish anyway" });
184635
184761
  return 1;
@@ -186445,6 +186571,10 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
186445
186571
  ...storesV1Issues(dir, strict),
186446
186572
  ...await navIssues2(dir, strict)
186447
186573
  ]);
186574
+ if (depthBudgetExceeded(report3)) {
186575
+ print(pushFailure2("depth_budget_exceeded", { ...report3, pushed: false, hint: DEPTH_BUDGET_HINT }));
186576
+ return 1;
186577
+ }
186448
186578
  if (hasErrors && !force) {
186449
186579
  print(pushFailure2("lint_errors", { ...report3, pushed: false, hint: "lint found errors \u2014 fix them, or re-run `himi push --force` to publish anyway" }));
186450
186580
  return 1;
@@ -187012,6 +187142,11 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
187012
187142
  ...storesV1Issues(dir, strict),
187013
187143
  ...await navIssues2(dir, strict)
187014
187144
  ]);
187145
+ if (depthBudgetExceeded(report3)) {
187146
+ rmSync24(built, { recursive: true, force: true });
187147
+ print({ ...report3, packed: false, code: "depth_budget_exceeded", hint: DEPTH_BUDGET_HINT });
187148
+ return 1;
187149
+ }
187015
187150
  if (hasErrors && !force) {
187016
187151
  rmSync24(built, { recursive: true, force: true });
187017
187152
  print({ ...report3, packed: false, hint: "lint found errors \u2014 fix them, or re-run `himi pack --force` to pack anyway" });
@@ -188143,12 +188278,29 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
188143
188278
  return 2;
188144
188279
  }
188145
188280
  const descriptor = readFileSync103(file2, "utf8");
188281
+ let parsed;
188146
188282
  try {
188147
- JSON.parse(descriptor);
188283
+ parsed = JSON.parse(descriptor);
188148
188284
  } catch (e2) {
188149
188285
  io2.error(JSON.stringify({ ok: false, error: "invalid_descriptor", file: file2, screen: id, message: `${file2} is not valid JSON: ${e2.message}` }));
188150
188286
  return 1;
188151
188287
  }
188288
+ const { depthBudgetFindings: depthBudgetFindings2, depthBudgetIssues: depthBudgetIssues2 } = await Promise.resolve().then(() => (init_validate_descriptor_core(), validate_descriptor_core_exports));
188289
+ const tooDeep = depthBudgetIssues2(depthBudgetFindings2(parsed));
188290
+ if (tooDeep.length > 0) {
188291
+ io2.error(JSON.stringify({
188292
+ ok: false,
188293
+ code: "depth_budget_exceeded",
188294
+ error: "depth_budget_exceeded",
188295
+ patched: false,
188296
+ app,
188297
+ screen: id,
188298
+ file: file2,
188299
+ issues: tooDeep,
188300
+ hint: "flatten the descriptor below the depth budget (see each issue's path and limit); `--force` does not override this"
188301
+ }, null, 2));
188302
+ return 1;
188303
+ }
188152
188304
  const baseFound = await fetchReleaseManifest(client2, app, fromRelease);
188153
188305
  if ("error" in baseFound) {
188154
188306
  io2.error(JSON.stringify(baseFound));
@@ -47,6 +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
51
  | `control_token_expired` | The selected control token has expired. |
51
52
  | `app_source_contains_secrets` | Source archiving detected a possible credential. |
52
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.1418.0",
3
+ "version": "0.1419.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": "94d6b78b2f9a4cc26ea336364cbbfa4d015375f8a456cf857359fa46"
74
+ "falconPackageHash": "ee7c2063084b2cc9cfe97bd9969ad339ad9ea61d026dcfcbe7b4f9e5"
75
75
  }