@wix/himalaya-cli 0.973.0 → 0.975.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.
Files changed (2) hide show
  1. package/dist/cli.mjs +482 -23
  2. package/package.json +2 -2
package/dist/cli.mjs CHANGED
@@ -18715,6 +18715,7 @@ function shouldRevealError(args) {
18715
18715
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
18716
18716
  var init_validation = __esm({
18717
18717
  "../../core/ts/src/validation.ts"() {
18718
+ "use strict";
18718
18719
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
18719
18720
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
18720
18721
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -22139,6 +22140,7 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
22139
22140
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
22140
22141
  var init_worker_subtree = __esm({
22141
22142
  "../../core/ts/src/worker-subtree.ts"() {
22143
+ "use strict";
22142
22144
  init_worker_subtree_safe_types_generated();
22143
22145
  WORKER_SUBTREE_CEILINGS = Object.freeze({
22144
22146
  maxEncodedBytes: 65536,
@@ -39644,7 +39646,7 @@ async function pinnedFetch(url, init, addresses, maxResponseBytes) {
39644
39646
  const body = init.body === void 0 || init.body === null ? void 0 : new Uint8Array(await new Response(init.body).arrayBuffer());
39645
39647
  const headers2 = Object.fromEntries(new Headers(init.headers).entries());
39646
39648
  const request = url.protocol === "https:" ? httpsRequest : httpRequest;
39647
- const hostname = url.hostname.replace(/^\[|\]$/g, "");
39649
+ const hostname2 = url.hostname.replace(/^\[|\]$/g, "");
39648
39650
  return await new Promise((resolve43, reject) => {
39649
39651
  let settled = false;
39650
39652
  const finish2 = (fn) => {
@@ -39654,13 +39656,13 @@ async function pinnedFetch(url, init, addresses, maxResponseBytes) {
39654
39656
  };
39655
39657
  const req = request({
39656
39658
  protocol: url.protocol,
39657
- hostname,
39659
+ hostname: hostname2,
39658
39660
  port: url.port || (url.protocol === "https:" ? 443 : 80),
39659
39661
  path: `${url.pathname}${url.search}`,
39660
39662
  method: init.method ?? "GET",
39661
39663
  headers: headers2,
39662
39664
  lookup: (_hostname, _options, callback) => callback(null, address, isIP(address)),
39663
- ...url.protocol === "https:" && isIP(hostname) === 0 ? { servername: hostname } : {}
39665
+ ...url.protocol === "https:" && isIP(hostname2) === 0 ? { servername: hostname2 } : {}
39664
39666
  }, (response) => {
39665
39667
  const chunks = [];
39666
39668
  let total = 0;
@@ -48234,9 +48236,9 @@ function isFetchableFontUrl(raw) {
48234
48236
  if (host.includes(":")) return !isPrivateV6(host);
48235
48237
  return true;
48236
48238
  }
48237
- function resolveAll(hostname) {
48239
+ function resolveAll(hostname2) {
48238
48240
  return new Promise((resolve43, reject) => {
48239
- dnsLookup(hostname, { all: true, verbatim: true }, (err, addresses) => {
48241
+ dnsLookup(hostname2, { all: true, verbatim: true }, (err, addresses) => {
48240
48242
  if (err) reject(err);
48241
48243
  else resolve43(addresses);
48242
48244
  });
@@ -51212,9 +51214,9 @@ var init_site_oauth_client = __esm({
51212
51214
 
51213
51215
  // src/cli.ts
51214
51216
  import { mkdtempSync as mkdtempSync4, readFileSync as readFileSync65, mkdirSync as mkdirSync25, writeFileSync as writeFileSync23, existsSync as existsSync54, rmSync as rmSync9, readdirSync as readdirSync25, statSync as statSync12, lstatSync as lstatSync6, accessSync, constants } from "node:fs";
51215
- import { execFileSync as execFileSync7 } from "node:child_process";
51216
- import { createHash as createHash24 } from "node:crypto";
51217
- import { tmpdir as tmpdir5 } from "node:os";
51217
+ import { execFileSync as execFileSync7, spawnSync as spawnSync5 } from "node:child_process";
51218
+ import { createHash as createHash24, randomUUID as randomUUID3 } from "node:crypto";
51219
+ import { tmpdir as tmpdir5, homedir as homedir7, hostname } from "node:os";
51218
51220
  import { join as join51, resolve as resolve42, relative as relative8, basename as basename11, dirname as dirname30 } from "node:path";
51219
51221
  import { fileURLToPath as fileURLToPath24 } from "node:url";
51220
51222
 
@@ -54580,6 +54582,19 @@ ${overview}
54580
54582
 
54581
54583
  ${loop}
54582
54584
 
54585
+ ## Stay on a current CLI
54586
+
54587
+ A stale global install is the single most common cause of "this doesn't work off-repo": the old
54588
+ CLI mislabels its own age as a platform limit, and the error it prints sends you hunting the
54589
+ wrong thing. himi now notices \u2014 when the running build is over a week old it updates itself and
54590
+ re-runs your command (at a terminal it asks first), so in an unattended run this takes care of
54591
+ itself. \`himi upgrade\` does it on demand, and \`himi doctor --json\` reports
54592
+ \`versions.cli\` \u2014 \`running\`, \`latest\`, \`ageDays\`, \`status\`.
54593
+
54594
+ Before concluding that a command is broken or a capability is missing, check you are not on an
54595
+ old build. If a \`--json\` envelope carries an \`upgrade\` key, that is himi telling you this
54596
+ directly. Set \`HIMI_NO_UPDATE_CHECK=1\` in images that must not self-update.
54597
+
54583
54598
  ## Import paths (in-repo vs off-repo)
54584
54599
 
54585
54600
  Off-repo content packages import \`@wix/himalaya/<pkg>\`. In-repo Himalaya sources import
@@ -55333,6 +55348,12 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
55333
55348
  stored control token). --dpx also runs a live
55334
55349
  supported "dpx auth status". --json for agents; an
55335
55350
  unreachable internal plane carries code:"needs_vpn".
55351
+ himi upgrade [--json] install the newest published CLI and say what moved.
55352
+ himi also does this ITSELF when the running build is
55353
+ over a week old \u2014 unattended it updates and re-runs
55354
+ your command, at a terminal it asks first. Turn that
55355
+ off with HIMI_NO_UPDATE_CHECK=1 (or per-run with
55356
+ --skip-version-check) and use this command instead.
55336
55357
 
55337
55358
  Learn (works offline \u2014 no serve stack needed):
55338
55359
  himi guide feature highlights + the headless dev loop + topics
@@ -55421,6 +55442,7 @@ var COMMANDS = [
55421
55442
  { name: "notify", summary: "send a REAL push notification to registered devices (RFC 0036 WS5) \u2014 APNs .p8 over HTTP/2 for iOS, FCM v1 for Android. `send` targets one device (--token / --device <registry id>) or fans out to the whole app (--all), reporting a per-device result; a token the provider says is uninstalled (APNs 410 Unregistered / FCM UNREGISTERED) is auto-disabled, while a merely wrong one (BadDeviceToken \u2014 usually a sandbox/production mismatch) is recorded and left enabled. `tokens` lists the registry. `doctor` reports which credentials resolve and which sender each platform picks. Talks to the admin-server via --admin-url/HIMALAYA_ADMIN_URL, authenticated with --admin-session/HIMALAYA_ADMIN_SESSION. NOT `himi push`, which publishes a release.", required: "send|tokens|doctor [--app <a>] [--token <t> | --device <id> | --all] [--title <s>] [--body <s>] [--link <url>] [--platform ios|android] [--badge <n>] [--channel <id>] [--silent] [--dry-run] [--json]" },
55422
55443
  { name: "site", summary: "PROVISION a Wix project, then analyze or extend it. `create` is the one door that makes a site instead of adopting one: POST /funnel/projects/v1/create (a WIX site, a HEADLESS backend, a BRANDED_APP, or a VIBE site), and for headless it also mints the OAuth app whose id IS the `WIX_CLIENT_ID` \u2014 with --redirect-uri already registered, because an unregistered redirect makes native signInDirect fail at redirect-session. `--verticals` REQUIRES `--template` and reports only what Wix confirmed: measured, installing onto a project created from the BLANK template returns 200 while the vertical's API answers 428 forever, because there is no site for it to attach to (the install asks for CREATE_SITE). A real template ships its verticals installed and populated, and installing further ones onto it then works. It MUTATES your Wix account and needs --yes; without it you get the payload and nothing is sent. `list` is the read-only site inventory (with each site's premium flag, which is what explains an installed-but-428 vertical). Auth is the account-scoped `wix token` (no --site), falling back to WIX_API_KEY + WIX_ACCOUNT_ID. `client` is the same OAuth step for a site that ALREADY exists \u2014 the one himi adopts rather than makes \u2014 so a clientId no longer means a manual trip to the dashboard: `client ensure` reconciles rather than mints, because a site can already carry a client with NO registered redirect and shipping that one fails signInDirect at runtime; it reuses a client that carries --redirect-uri, PATCHes one that matches --name but lacks it (unioning, so another app's redirect survives), and creates only when there is nothing to reuse (--new forces a fresh one). --name IS the idempotency key. `client` auth is SITE-scoped (`wix token --site <msid>`) because minting is owner-of-that-site, and --yes gates every mutation: a site that already has a usable client resolves WITHOUT it, but --write-provisioning needs --yes even then because it rewrites a manifest on disk (the dry run's planned.provisioning names the file). That query endpoint IGNORES paging (measured: query.paging, cursorPaging and a top-level paging all return the full set at offset 0), so when pagingMetadata reports more clients than came back, `ensure` REFUSES EVERY outcome \u2014 create, patch AND reuse. Reuse is refused too because pickClient breaks ties on the OLDEST createdDate, so an omitted older same-named MOBILE client with the same redirect would win a later complete read and the resolved clientId would MOVE between runs \u2014 and it gets written into provisioning.json; convergence is what makes a stray duplicate untidy rather than harmful. --name cannot narrow the query (no filter is sent; the name is applied to the rows after they arrive), so the recovery is the dashboard; --new skips the query entirely. A `patched` result is verified against what Wix STORED \u2014 the redirect list is read off the PATCH response, and re-read via GET when the response omits it, because falling back to the list himi just sent would make the guard pass vacuously on exactly the silent no-op it exists to catch. `client list` is the read-only inventory of a site's clients: act on its `shippable` column rather than `hasRedirect`, because shippable ALSO requires a MOBILE applicationType and `ensure` refuses a WEB client \u2014 a WEB one carrying your redirect looks right and is not a candidate \u2014 and it sets truncated:true rather than presenting a partial inventory as the whole one. `analyze` reads a site into a business-book folder (REST + Playwright, GET-only); `site function` list/get/put/publish/invokes that site's Velo HTTP functions and is not `himi functions` (serve-plane jobs). `--apply` bakes into a brandable shell; `--shell` takes the same names `himi dist --shell` does and defaults to branded-lite, and a shell that ships no prepare:customer is refused by name rather than half-built. It never overwrites any shell's committed apps/<shell>/provisioning.json.", required: "create <name> [--type wix|headless|branded-app|vibe] [--template <guid>] [--verticals stores,bookings,\u2026] [--folder <guid>] [--redirect-uri <uri>] [--no-oauth-app] [--write-provisioning [path]] [--site-url <url>] [--json] --yes | list | client ensure|list --site <msid> [--name <name>] [--redirect-uri <uri>] [--new] [--write-provisioning [path]] [--site-url <url>] [--json] --yes | analyze --client-id <id> --url <https> [--site <msid>] [--out <dir>] [--redirect-uri <uri>] [--apply] [--shell " + SHELL_ENUM + "] | function list|get|put|publish|invoke [--site <msid>] [--url <https>] [--method GET|POST|PUT|DELETE] [--source <file>] [--body <json>] [--publish] [--invoke]" },
55423
55444
  { name: "doctor", summary: 'off-VPN preflight in one shot: probe the PUBLIC data plane (www.wixapis.com \u2014 reachable off-VPN), the INTERNAL serve/control plane for the selected target (VPN-gated), and report auth state (dpx session on disk + stored control token). --dpx additionally runs a live, EADDRINUSE-safe `dpx whoami`. Machine-readable with --json (an unreachable internal plane carries a stable code:"needs_vpn" so an agent branches on the code, not the prose); human-readable otherwise. Never loads the authoring runtime \u2014 works off-repo.', required: "[--target <t>] [--dpx] [--json] [--data-url <url>] [--timeout <ms>]" },
55445
+ { name: "upgrade", summary: "install the newest published CLI (`npm i -g @wix/himalaya-cli@latest`) and report what moved. This is the explicit door: himi also updates itself when the running build is more than a week old \u2014 unattended it just does it and re-runs your command, at a terminal it asks first \u2014 and `HIMI_NO_UPDATE_CHECK=1` turns that off, leaving this command as the way to catch up. Reports `unchanged` rather than claiming success when npm exits 0 without moving the version, which is what a registry mirror serving its own pinned copy looks like.", required: "[--json]" },
55424
55446
  { name: "push", summary: "lint + build + publish a release (default --draft: servable via ?release= but NOT live). REFUSES to publish on any error unless --force; --strict promotes warnings. Prints preview + agent-context links. The published slug is NAMESPACED per owner \u2014 `<ownerPrefix>.<name>`, composed offline from the control token's ownerPrefix claim, so the CLI and the server derive the identical string and himalaya.content.json is NOT rewritten (a legacy token with no ownerPrefix publishes the declared name unchanged). An app claimed BEFORE namespacing is stored under its FLAT slug, so the claim route answers `alreadyYours` naming that slug and the push adopts it \u2014 reported as `adoptedSlug`, since it is the one case where the published `app` differs from what the declared name composes to. `--no-claim` skips the route that knows this, so a pre-namespacing app pushed with it still fails 403. --visibility public|unlisted decides whether /v1/apps + the Himi Player picker LIST the app (also declarable as `visibility` in himalaya.content.json; omitted, a brand-new app defaults to unlisted \u2014 share by link \u2014 and an existing setting is kept). --account-id names which account owns the app when your Wix session resolves to several (the auto-claim of a brand-new slug cannot guess \u2014 without it a multi-account caller is told to name one). An unforced push of a release that carries SCREENS also refuses when `.himi/ux-audit.json` holds no mobile-UX drive recorded against that release id, or holds one that FAILED (#2955) \u2014 a published draft IS the owner-private preview a customer is handed; `himi ux record` clears it and `--force` overrides it, which the success envelope then says out loud. `--only <selector>` publishes ONLY the named items as a release derived from the app's live release (or `--from-release <id>`), running the identical build + lint gate and simply publishing less; it requires an explicit `--draft` or `--live` because a partial publish has no safe default, never claims a slug, and carries neither attestation nor a source archive (both named in the output). Selectors are comma-separated in ONE `--only`: `changed` (every item whose bytes differ from the base), `screen:<id|glob>`, `bundle:<name>`, `tokens`, `app-config` \u2014 a selected screen drags its logic bundle and a selected bundle drags the screens that reference it, or the bytes land under a key nothing reads.", required: "[--content <dir>] [--draft] [--live] [--force] [--strict] [--label] [--notes] [--visibility public|unlisted] [--no-claim] [--account-id <id>] [--only <selector>] [--from-release <id>] [--accept-regressions] [--accept-partial] [--app <slug>] (--control-token)" },
55425
55447
  { name: "pack", summary: 'write the app as a PORTABLE .himi package \u2014 one self-contained file holding every screen, Tier-5 bundle, token, asset and the app-config. Packages are live by default: they boot bundled content offline, then Himi Player follows its baked-in production ring. `--snapshot` makes a frozen reproducible artifact. Package-supplied origins are never trusted. --format dir writes the same tree unpacked, for static hosting. --release-dir packs a release that is already built on disk (needs --app) instead of rebuilding from source. `--dev` ALSO packs the app\'s own source archive into the container (`source.zip`, header `contents: "dev"`), so the package can be handed to someone who then keeps building \u2014 collected through the same deny-list and secret scan `himi push` uses, and refused if a credential is found. A dev package IS the codebase: it is never the default, it is always labelled in the output, the web host will not open one, and it is archive-only (`--dev --format dir` is refused, because a reader that predates the source entry drops it before hashing a directory and would call the package tampered). Restore one with `himi import`.', required: "[--content <dir>] [--out <file|dir>] [--format zip|dir] [--name <title>] [--snapshot] [--dev] [--strict] [--force] | --release-dir <dir> --app <a>" },
55426
55448
  { name: "import", summary: "turn a `himi pack --dev` package back into an EDITABLE content package \u2014 the file counterpart of `himi pull`, needing no serve plane, no account and no network. Refuses an invalid package before trusting its source (the content hash is recomputed over every entry on open, so the archive is provably the one that was packed), and refuses a RELEASE package by naming the absence \u2014 it carries built output and no source \u2014 rather than reporting a missing file. `--name` re-substitutes the app identity through the same machinery `himi init --template` and `himi pull --name` use, so the restored tree is a NEW app rather than a second copy of the same one, and a name that also appears inside a screen id (`feed` in `feed_home`) is left alone. A non-empty destination needs `--force` and a merge NAMES what it kept (`left: [...]`), the same contract `himi pull --force` has; a shape clash is refused whole before anything is written. There is no `--replace`: a file you were handed is not the plane's per-release archive.", required: "<package.himi> [--out <dir>] [--name <new>] [--force]" },
@@ -55472,6 +55494,56 @@ function compareVersions2(a, b) {
55472
55494
  }
55473
55495
  return 0;
55474
55496
  }
55497
+ var PUBLIC_NPM_REGISTRY = "https://registry.npmjs.org";
55498
+ var WIX_NPM_MIRROR = "https://npm.dev.wixpress.com";
55499
+ var CLI_PACKAGE_PATH = "@wix%2fhimalaya-cli";
55500
+ var TRUSTED_REGISTRIES = /* @__PURE__ */ new Set([PUBLIC_NPM_REGISTRY, WIX_NPM_MIRROR]);
55501
+ function normalizeRegistry(url) {
55502
+ return url?.trim().replace(/\/+$/, "") || void 0;
55503
+ }
55504
+ function isTrustedRegistry(url) {
55505
+ const normalized = normalizeRegistry(url);
55506
+ return normalized !== void 0 && TRUSTED_REGISTRIES.has(normalized);
55507
+ }
55508
+ function trustedConfiguredRegistry(env) {
55509
+ const configured = normalizeRegistry(env.npm_config_registry ?? env.NPM_CONFIG_REGISTRY);
55510
+ return isTrustedRegistry(configured) ? configured : void 0;
55511
+ }
55512
+ async function fetchPackumentFrom(registry, fetchImpl, timeoutMs) {
55513
+ const controller = new AbortController();
55514
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
55515
+ try {
55516
+ const response = await fetchImpl(`${registry.replace(/\/$/, "")}/${CLI_PACKAGE_PATH}`, {
55517
+ headers: { accept: "application/json" },
55518
+ signal: controller.signal
55519
+ });
55520
+ if (!response.ok) return void 0;
55521
+ const json5 = await response.json();
55522
+ const latest = typeof json5?.["dist-tags"]?.latest === "string" ? json5["dist-tags"].latest : void 0;
55523
+ const time = {};
55524
+ for (const [version, when] of Object.entries(json5?.time ?? {})) if (typeof when === "string") time[version] = when;
55525
+ return latest ? { latest, time, registry: normalizeRegistry(registry) ?? registry } : void 0;
55526
+ } catch {
55527
+ return void 0;
55528
+ } finally {
55529
+ clearTimeout(timer);
55530
+ }
55531
+ }
55532
+ async function fetchCliPackument(fetchImpl = fetch, timeoutMs = DOCTOR_TIMEOUT_MS, env = process.env) {
55533
+ const configured = trustedConfiguredRegistry(env);
55534
+ const candidates = configured && configured !== PUBLIC_NPM_REGISTRY ? [configured, PUBLIC_NPM_REGISTRY] : [PUBLIC_NPM_REGISTRY];
55535
+ for (const registry of candidates) {
55536
+ const packument = await fetchPackumentFrom(registry, fetchImpl, timeoutMs);
55537
+ if (packument) return packument;
55538
+ }
55539
+ return void 0;
55540
+ }
55541
+ function ageInDays(publishedAt, now = Date.now()) {
55542
+ if (!publishedAt) return void 0;
55543
+ const at = Date.parse(publishedAt);
55544
+ if (!Number.isFinite(at)) return void 0;
55545
+ return Math.max(0, (now - at) / 864e5);
55546
+ }
55475
55547
  function packageVersion(path) {
55476
55548
  try {
55477
55549
  const version = JSON.parse(readFileSync65(path, "utf8")).version;
@@ -55501,18 +55573,10 @@ function resolvedSdkVersion(fromDir = process.cwd(), bundledManifest = new URL("
55501
55573
  }
55502
55574
  async function doctorVersionReport(running, fetchImpl = fetch, sdk = resolvedSdkVersion()) {
55503
55575
  const minimumSdk = "0.33.0";
55504
- const registry = (process.env.npm_config_registry ?? "https://npm.dev.wixpress.com").replace(/\/$/, "");
55505
- let latest;
55506
- const controller = new AbortController();
55507
- const timer = setTimeout(() => controller.abort(), DOCTOR_TIMEOUT_MS);
55508
- try {
55509
- const response = await fetchImpl(`${registry}/@wix%2fhimalaya-cli`, { headers: { accept: "application/json" }, signal: controller.signal });
55510
- const json5 = response.ok ? await response.json() : void 0;
55511
- latest = typeof json5?.["dist-tags"]?.latest === "string" ? json5["dist-tags"].latest : void 0;
55512
- } catch {
55513
- } finally {
55514
- clearTimeout(timer);
55515
- }
55576
+ const packument = await fetchCliPackument(fetchImpl);
55577
+ const latest = packument?.latest;
55578
+ const publishedAt = packument?.time[running];
55579
+ const ageDays = ageInDays(publishedAt);
55516
55580
  const cliOutdated = !!latest && compareVersions2(running, latest) < 0;
55517
55581
  const bundled = sdk.source === "bundled";
55518
55582
  const sdkOutdated = !bundled && !!sdk.version && compareVersions2(sdk.version, minimumSdk) < 0;
@@ -55520,6 +55584,8 @@ async function doctorVersionReport(running, fetchImpl = fetch, sdk = resolvedSdk
55520
55584
  cli: {
55521
55585
  running,
55522
55586
  ...latest ? { latest } : {},
55587
+ ...publishedAt ? { publishedAt } : {},
55588
+ ...ageDays === void 0 ? {} : { ageDays: Math.round(ageDays * 10) / 10 },
55523
55589
  status: latest ? cliOutdated ? "outdated" : "current" : "unknown",
55524
55590
  ...cliOutdated ? { hint: "Upgrade: npm i -g @wix/himalaya-cli@latest" } : {}
55525
55591
  },
@@ -55535,6 +55601,329 @@ function formatSdkVersionLine(sdk) {
55535
55601
  if (sdk.source === "bundled") return `sdk: bundled (${sdk.resolved ?? "unknown"})`;
55536
55602
  return `sdk: ${sdk.resolved ?? "not installed"} (minimum ${sdk.minimum}; ${sdk.status})`;
55537
55603
  }
55604
+ var CLI_PACKAGE = "@wix/himalaya-cli";
55605
+ var UNPUBLISHED_CLI_VERSION = "0.1.0";
55606
+ var UPDATE_MAX_AGE_DAYS = 7;
55607
+ var UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
55608
+ var UPDATE_SNOOZE_MS = 24 * 60 * 60 * 1e3;
55609
+ var UPDATE_REEXEC_ENV = "HIMI_UPDATED_ONCE";
55610
+ function updateCachePath(env = process.env) {
55611
+ return env.HIMI_UPDATE_CACHE_FILE ?? join51(homedir7(), ".himalaya", "update-check.json");
55612
+ }
55613
+ function readUpdateCache(path) {
55614
+ try {
55615
+ const parsed = JSON.parse(readFileSync65(path, "utf8"));
55616
+ return parsed && typeof parsed === "object" ? parsed : {};
55617
+ } catch {
55618
+ return {};
55619
+ }
55620
+ }
55621
+ function writeUpdateCache(path, cache) {
55622
+ try {
55623
+ mkdirSync25(dirname30(path), { recursive: true, mode: 448 });
55624
+ writeFileSync23(path, JSON.stringify(cache, null, 2) + "\n", { mode: 384 });
55625
+ } catch {
55626
+ }
55627
+ }
55628
+ function updateCheckSuppression(running, command, env = process.env, cwd = process.cwd()) {
55629
+ if (env[UPDATE_REEXEC_ENV] === "1") return "already-updated";
55630
+ if (env.HIMI_NO_UPDATE_CHECK === "1") return "opted-out";
55631
+ if (command === "upgrade" || command === "doctor") return "own-command";
55632
+ if (running === UNPUBLISHED_CLI_VERSION) return "unpublished";
55633
+ if (isHimalayaMonorepoCheckout(cwd)) return "monorepo";
55634
+ return void 0;
55635
+ }
55636
+ function decideUpdateAction(input) {
55637
+ if (!input.latest || compareVersions2(input.running, input.latest) >= 0) return "skip";
55638
+ if (input.ageDays === void 0 || input.ageDays < input.maxAgeDays) return "skip";
55639
+ if (!input.prefixWritable()) return "warn";
55640
+ return input.interactive ? "prompt" : "update";
55641
+ }
55642
+ function globalNodeModulesRoot() {
55643
+ try {
55644
+ const root = execFileSync7("npm", ["root", "-g"], { stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
55645
+ return root || void 0;
55646
+ } catch {
55647
+ return void 0;
55648
+ }
55649
+ }
55650
+ function globalPrefixWritable() {
55651
+ const root = globalNodeModulesRoot();
55652
+ if (!root) return false;
55653
+ try {
55654
+ accessSync(root, constants.W_OK);
55655
+ return true;
55656
+ } catch {
55657
+ return false;
55658
+ }
55659
+ }
55660
+ function npmInstallArgs(registry, version) {
55661
+ const pinned = `${normalizeRegistry(registry) ?? registry}/`;
55662
+ return ["i", "-g", `${CLI_PACKAGE}@${version}`, `--registry=${pinned}`, `--@wix:registry=${pinned}`];
55663
+ }
55664
+ var INSTALLABLE_SPEC = /^(?:latest|\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)$/;
55665
+ function createUpdateRunner(spawn5 = spawnSync5) {
55666
+ return {
55667
+ install: async (registry, version) => {
55668
+ if (!isTrustedRegistry(registry)) return { ok: false, detail: `refusing untrusted registry ${registry}` };
55669
+ if (!INSTALLABLE_SPEC.test(version)) return { ok: false, detail: `refusing install spec ${JSON.stringify(version)}` };
55670
+ const result = spawn5("npm", npmInstallArgs(registry, version), {
55671
+ // npm's stdout goes to OUR stderr: in `himi <cmd> --json | jq` (and `himi upgrade --json`) its
55672
+ // "added 212 packages" would otherwise land in stdout ahead of the envelope and break the
55673
+ // parse — the agent case this whole feature exists for (#2985 review). And no stdin: an
55674
+ // install needs none, and must not consume input piped to the command we are about to re-run.
55675
+ stdio: ["ignore", 2, 2],
55676
+ cwd: homedir7()
55677
+ });
55678
+ if (result.error) return { ok: false, detail: result.error.message };
55679
+ return result.status === 0 ? { ok: true } : { ok: false, detail: `npm exited ${result.status}` };
55680
+ },
55681
+ installedVersion: () => {
55682
+ const root = globalNodeModulesRoot();
55683
+ return root ? packageVersion(join51(root, "@wix", "himalaya-cli", "package.json")) : void 0;
55684
+ },
55685
+ reexec: (argv) => {
55686
+ const root = globalNodeModulesRoot();
55687
+ const entry = root ? join51(root, "@wix", "himalaya-cli", "dist", "cli.mjs") : void 0;
55688
+ if (!entry || !existsSync54(entry)) return void 0;
55689
+ const result = spawnSync5(process.execPath, [entry, ...argv], {
55690
+ // inherit keeps PIPED STDIN working — commands that read stdin must survive a re-exec.
55691
+ stdio: "inherit",
55692
+ env: { ...process.env, [UPDATE_REEXEC_ENV]: "1" }
55693
+ });
55694
+ return result.status ?? 1;
55695
+ },
55696
+ confirm: async (question) => {
55697
+ const { createInterface } = await import("node:readline/promises");
55698
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
55699
+ try {
55700
+ const answer = (await rl.question(question)).trim().toLowerCase();
55701
+ return answer === "" || answer === "y" || answer === "yes";
55702
+ } catch {
55703
+ return false;
55704
+ } finally {
55705
+ rl.close();
55706
+ }
55707
+ }
55708
+ };
55709
+ }
55710
+ var defaultUpdateRunner = createUpdateRunner();
55711
+ var UPDATE_LOCK_REPORT_AFTER_MS = 60 * 60 * 1e3;
55712
+ function thisHost() {
55713
+ return hostname().replace(/\s+/g, "_");
55714
+ }
55715
+ function lockStamp(now) {
55716
+ return `${now} ${process.pid} ${thisHost()} ${randomUUID3()}`;
55717
+ }
55718
+ function processAlive(pid) {
55719
+ try {
55720
+ process.kill(pid, 0);
55721
+ return true;
55722
+ } catch (error) {
55723
+ return error.code === "EPERM";
55724
+ }
55725
+ }
55726
+ function isAbandoned(content) {
55727
+ const [, pid, host] = content.split(" ");
55728
+ const owner = Number.parseInt(pid, 10);
55729
+ return Number.isFinite(owner) && owner > 0 && host === thisHost() && !processAlive(owner);
55730
+ }
55731
+ function staleUpdateLockFile(cachePath, now) {
55732
+ for (const path of [`${cachePath}.lock.break`, `${cachePath}.lock`]) {
55733
+ let content;
55734
+ try {
55735
+ content = readFileSync65(path, "utf8");
55736
+ } catch {
55737
+ continue;
55738
+ }
55739
+ const stamped = Number.parseInt(content.split(" ")[0], 10);
55740
+ let since = stamped;
55741
+ if (!Number.isFinite(since)) {
55742
+ try {
55743
+ since = statSync12(path).mtimeMs;
55744
+ } catch {
55745
+ continue;
55746
+ }
55747
+ }
55748
+ if (now - since >= UPDATE_LOCK_REPORT_AFTER_MS) return path;
55749
+ }
55750
+ return void 0;
55751
+ }
55752
+ function acquireUpdateLock(cachePath, now, hooks = {}) {
55753
+ const lockPath = `${cachePath}.lock`;
55754
+ const breakPath = `${cachePath}.lock.break`;
55755
+ const mine = lockStamp(now);
55756
+ const readLock = (path) => {
55757
+ try {
55758
+ return readFileSync65(path, "utf8");
55759
+ } catch {
55760
+ return void 0;
55761
+ }
55762
+ };
55763
+ const tryCreate = (path) => {
55764
+ try {
55765
+ writeFileSync23(path, mine, { flag: "wx", mode: 384 });
55766
+ return true;
55767
+ } catch {
55768
+ return false;
55769
+ }
55770
+ };
55771
+ const removeIfMine = (path) => {
55772
+ try {
55773
+ if (readLock(path) === mine) rmSync9(path, { force: true });
55774
+ } catch {
55775
+ }
55776
+ };
55777
+ const release = () => removeIfMine(lockPath);
55778
+ try {
55779
+ mkdirSync25(dirname30(lockPath), { recursive: true, mode: 448 });
55780
+ } catch {
55781
+ return () => {
55782
+ };
55783
+ }
55784
+ try {
55785
+ if (tryCreate(lockPath)) return release;
55786
+ const held = readLock(lockPath);
55787
+ if (held === void 0) return tryCreate(lockPath) ? release : void 0;
55788
+ if (!isAbandoned(held)) return void 0;
55789
+ hooks.beforeBreak?.();
55790
+ if (!tryCreate(breakPath)) return void 0;
55791
+ try {
55792
+ const current = readLock(lockPath);
55793
+ hooks.underBreakLock?.();
55794
+ if (current === void 0) return tryCreate(lockPath) ? release : void 0;
55795
+ if (!isAbandoned(current)) return void 0;
55796
+ rmSync9(lockPath, { force: true });
55797
+ return tryCreate(lockPath) ? release : void 0;
55798
+ } finally {
55799
+ removeIfMine(breakPath);
55800
+ }
55801
+ } catch {
55802
+ return void 0;
55803
+ }
55804
+ }
55805
+ function formatStaleLine(running, latest, ageDays) {
55806
+ const days = Math.round(ageDays);
55807
+ return `himi v${running} is ${days} day${days === 1 ? "" : "s"} old (latest ${latest})`;
55808
+ }
55809
+ function upgradeEnvelope(outcome) {
55810
+ return {
55811
+ running: outcome.running,
55812
+ ...outcome.latest ? { latest: outcome.latest } : {},
55813
+ ...outcome.ageDays === void 0 ? {} : { ageDays: outcome.ageDays },
55814
+ action: outcome.action,
55815
+ command: `npm i -g ${CLI_PACKAGE}@latest`,
55816
+ ...outcome.detail ? { detail: outcome.detail } : {}
55817
+ };
55818
+ }
55819
+ async function maybeSelfUpdate(argv, io, opts = {}) {
55820
+ const env = opts.env ?? process.env;
55821
+ const running = opts.running ?? cliVersion();
55822
+ const command = opts.command ?? "";
55823
+ if (updateCheckSuppression(running, command, env, opts.cwd ?? process.cwd())) return void 0;
55824
+ const now = opts.now ?? Date.now();
55825
+ const cachePath = opts.cachePath ?? updateCachePath(env);
55826
+ const cache = readUpdateCache(cachePath);
55827
+ if (cache.snoozedUntil && Date.parse(cache.snoozedUntil) > now) return void 0;
55828
+ let latest = cache.latest;
55829
+ let runningPublishedAt = cache.runningPublishedAt;
55830
+ const checkedAt = cache.checkedAt ? Date.parse(cache.checkedAt) : Number.NaN;
55831
+ const describesThisBuild = cache.running === running;
55832
+ const fresh = Number.isFinite(checkedAt) && now - checkedAt < UPDATE_CHECK_INTERVAL_MS && describesThisBuild;
55833
+ if (!describesThisBuild) runningPublishedAt = void 0;
55834
+ let answeredBy;
55835
+ let answeredLatest;
55836
+ if (!fresh) {
55837
+ const packument = await fetchCliPackument(opts.fetchImpl ?? fetch, DOCTOR_TIMEOUT_MS, env);
55838
+ if (packument) {
55839
+ latest = packument.latest;
55840
+ runningPublishedAt = packument.time[running];
55841
+ answeredBy = packument.registry;
55842
+ answeredLatest = packument.latest;
55843
+ writeUpdateCache(cachePath, { ...cache, running, checkedAt: new Date(now).toISOString(), latest, runningPublishedAt });
55844
+ }
55845
+ }
55846
+ const ageDays = ageInDays(runningPublishedAt, now);
55847
+ const fromEnv = Number(env.HIMI_UPDATE_MAX_AGE_DAYS);
55848
+ const maxAgeDays = opts.maxAgeDays ?? (fromEnv > 0 ? fromEnv : UPDATE_MAX_AGE_DAYS);
55849
+ const interactive = opts.interactive ?? (io.isTTY === true && process.stdin.isTTY === true);
55850
+ const action = decideUpdateAction({
55851
+ running,
55852
+ latest,
55853
+ ageDays,
55854
+ maxAgeDays,
55855
+ interactive,
55856
+ prefixWritable: opts.prefixWritable ?? globalPrefixWritable
55857
+ });
55858
+ if (action === "skip" || !latest || ageDays === void 0) return void 0;
55859
+ const base = { running, latest, ageDays: Math.round(ageDays * 10) / 10, command };
55860
+ const stale = formatStaleLine(running, latest, ageDays);
55861
+ if (action === "warn") {
55862
+ io.error(`${stale} \u2014 npm i -g ${CLI_PACKAGE}@latest`);
55863
+ return { ...base, action: "warned", detail: "global prefix is not writable" };
55864
+ }
55865
+ const runner2 = opts.runner ?? defaultUpdateRunner;
55866
+ if (action === "prompt") {
55867
+ const yes = await runner2.confirm(`${stale}. Update now? [Y/n] `);
55868
+ if (!yes) {
55869
+ writeUpdateCache(cachePath, {
55870
+ running,
55871
+ latest,
55872
+ runningPublishedAt,
55873
+ checkedAt: new Date(now).toISOString(),
55874
+ snoozedUntil: new Date(now + UPDATE_SNOOZE_MS).toISOString()
55875
+ });
55876
+ return { ...base, action: "declined" };
55877
+ }
55878
+ }
55879
+ const releaseLock = opts.acquireLock === void 0 ? acquireUpdateLock(cachePath, now) : opts.acquireLock();
55880
+ if (!releaseLock) {
55881
+ const blocker = staleUpdateLockFile(cachePath, now);
55882
+ if (blocker) io.error(`${stale}, but self-update has been blocked for over an hour by ${blocker} \u2014 if no himi is running, delete it`);
55883
+ return void 0;
55884
+ }
55885
+ try {
55886
+ if (!answeredBy) {
55887
+ const again = await fetchCliPackument(opts.fetchImpl ?? fetch, DOCTOR_TIMEOUT_MS, env);
55888
+ answeredBy = again?.registry;
55889
+ answeredLatest = again?.latest;
55890
+ }
55891
+ if (!answeredBy || !answeredLatest || compareVersions2(answeredLatest, running) <= 0) {
55892
+ if (answeredBy && answeredLatest) {
55893
+ writeUpdateCache(cachePath, { ...cache, running, latest: answeredLatest, runningPublishedAt, checkedAt: new Date(now).toISOString() });
55894
+ }
55895
+ return void 0;
55896
+ }
55897
+ const installFrom = answeredBy;
55898
+ const target = answeredLatest;
55899
+ io.error(`${stale} \u2014 updating to ${target}\u2026`);
55900
+ const installed = await runner2.install(installFrom, target);
55901
+ if (!installed.ok) {
55902
+ io.error(` update failed (${installed.detail ?? "unknown error"}) via ${installFrom} \u2014 continuing on v${running}`);
55903
+ return { ...base, action: "failed", detail: installed.detail };
55904
+ }
55905
+ const after = runner2.installedVersion();
55906
+ if (!after || compareVersions2(after, running) <= 0) {
55907
+ io.error(` npm reported success but v${running} is still installed \u2014 continuing without re-running`);
55908
+ return { ...base, action: "failed", detail: "version did not change" };
55909
+ }
55910
+ writeUpdateCache(cachePath, {
55911
+ running: after,
55912
+ latest: target,
55913
+ checkedAt: new Date(now).toISOString(),
55914
+ ...cache.snoozedUntil ? { snoozedUntil: cache.snoozedUntil } : {}
55915
+ });
55916
+ io.error(` installed ${after} \u2014 re-running: himi ${argv.join(" ")}`);
55917
+ const exitCode = runner2.reexec(argv);
55918
+ if (exitCode === void 0) {
55919
+ io.error(` could not locate the new install to re-run \u2014 continuing on v${running}`);
55920
+ return { ...base, action: "failed", detail: "new entry not found" };
55921
+ }
55922
+ return { ...base, latest: after, action: "updated", exitCode };
55923
+ } finally {
55924
+ releaseLock();
55925
+ }
55926
+ }
55538
55927
  function helpJson() {
55539
55928
  return {
55540
55929
  cli: "himi",
@@ -56587,11 +56976,16 @@ async function producedWidgetFamilies(dir) {
56587
56976
  }
56588
56977
  return { families, source: "dist/bundles" };
56589
56978
  }
56590
- async function run(argv, io = defaultIO) {
56979
+ async function run(argv, io = defaultIO, deps = {}) {
56591
56980
  const { _: pos, flags } = parseFlags(argv);
56592
56981
  const flagErrors = switchFlagErrors(argv);
56593
56982
  const command = pos[0];
56594
- const print = (o) => io.log(JSON.stringify(o, null, 2));
56983
+ let updateNotice;
56984
+ const print = (o) => io.log(JSON.stringify(
56985
+ updateNotice && o && typeof o === "object" && !Array.isArray(o) ? { ...o, upgrade: upgradeEnvelope(updateNotice) } : o,
56986
+ null,
56987
+ 2
56988
+ ));
56595
56989
  if (flags.version === true || command === "version" || pos.includes("-v")) {
56596
56990
  const v = cliVersion();
56597
56991
  io.log(flags.json ? JSON.stringify({ himi: v }, null, 2) : `himi v${v}`);
@@ -56609,6 +57003,11 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
56609
57003
  io.error(JSON.stringify({ ok: false, error: "invalid_flag_value", problems: flagErrors, hint: "run `himi help` for the flag grammar." }));
56610
57004
  return 2;
56611
57005
  }
57006
+ if (flags["skip-version-check"] !== true) {
57007
+ const update = await maybeSelfUpdate(argv, io, { command, ...deps.selfUpdate });
57008
+ if (update?.exitCode !== void 0) return update.exitCode;
57009
+ if (update) updateNotice = update;
57010
+ }
56612
57011
  const contextualDefaultTarget = defaultTargetForCommand(command, contentDir(flags));
56613
57012
  const targetName = () => resolveTargetName({ target: str5(flags.target), defaultTarget: contextualDefaultTarget });
56614
57013
  const client2 = () => new ServeCliClient(
@@ -60967,6 +61366,45 @@ ${label2}`);
60967
61366
  });
60968
61367
  return 0;
60969
61368
  }
61369
+ case "upgrade": {
61370
+ const up = deps.selfUpdate ?? {};
61371
+ const runner2 = up.runner ?? defaultUpdateRunner;
61372
+ const env = up.env ?? process.env;
61373
+ const before = cliVersion();
61374
+ const lockFile = `${up.cachePath ?? updateCachePath(env)}.lock`;
61375
+ const releaseLock = up.acquireLock ? up.acquireLock() : acquireUpdateLock(up.cachePath ?? updateCachePath(env), Date.now());
61376
+ if (!releaseLock) {
61377
+ const hint = `another himi process is updating right now \u2014 try again in a minute. If no himi is running, delete ${lockFile} (and ${lockFile}.break if present)`;
61378
+ if (flags.json) print({ ok: false, error: "update_in_progress", running: before, lock: lockFile, hint });
61379
+ else io.error(hint);
61380
+ return 1;
61381
+ }
61382
+ try {
61383
+ const answered = await fetchCliPackument(up.fetchImpl ?? fetch, DOCTOR_TIMEOUT_MS, env);
61384
+ const registry = answered?.registry ?? trustedConfiguredRegistry(env) ?? PUBLIC_NPM_REGISTRY;
61385
+ if (answered?.latest && compareVersions2(answered.latest, before) <= 0) {
61386
+ if (flags.json) print({ ok: true, running: before, installed: before, changed: false, registry, latest: answered.latest });
61387
+ else io.log(`himi ${before} (unchanged \u2014 ${registry} has nothing newer; its latest is ${answered.latest})`);
61388
+ return 0;
61389
+ }
61390
+ const version = answered?.latest ?? "latest";
61391
+ io.error(`upgrading ${CLI_PACKAGE} (running v${before}) to ${version} from ${registry}\u2026`);
61392
+ const installed = await runner2.install(registry, version);
61393
+ if (!installed.ok) {
61394
+ const detail = installed.detail ?? "unknown error";
61395
+ if (flags.json) print({ ok: false, error: "upgrade_failed", running: before, registry, detail });
61396
+ else io.error(`upgrade failed (${detail}) via ${registry}`);
61397
+ return 1;
61398
+ }
61399
+ const after = runner2.installedVersion();
61400
+ const moved = !!after && compareVersions2(after, before) > 0;
61401
+ if (flags.json) print({ ok: true, running: before, installed: after, changed: moved, registry });
61402
+ else io.log(moved ? `himi ${before} \u2192 ${after}` : `himi ${before} (unchanged \u2014 the registry has nothing newer to install)`);
61403
+ return 0;
61404
+ } finally {
61405
+ releaseLock();
61406
+ }
61407
+ }
60970
61408
  case "doctor": {
60971
61409
  const target = targetName();
60972
61410
  const timeoutMs = Number(str5(flags.timeout)) > 0 ? Number(str5(flags.timeout)) : DOCTOR_TIMEOUT_MS;
@@ -61419,36 +61857,50 @@ if (isMain) {
61419
61857
  });
61420
61858
  }
61421
61859
  export {
61860
+ CLI_PACKAGE,
61422
61861
  ConfigLoadError,
61423
61862
  GIT_FLAG_STORE_HALF_WRITE_NOTE,
61424
61863
  GIT_FLAG_STORE_NOTE,
61425
61864
  PLACEHOLDER_CLIENT_ID,
61426
61865
  PLACEHOLDER_SITE_URL,
61427
61866
  PREVIEW_TOKEN_NOTE,
61867
+ PUBLIC_NPM_REGISTRY,
61428
61868
  PUBLIC_WIX_API_BASE,
61869
+ WIX_NPM_MIRROR,
61870
+ acquireUpdateLock,
61871
+ ageInDays,
61429
61872
  autoClaimSlug,
61430
61873
  buildDoctorReport,
61431
61874
  compareVersions2 as compareVersions,
61875
+ createUpdateRunner,
61432
61876
  credentialKind,
61433
61877
  credentialSwapNotice,
61878
+ decideUpdateAction,
61434
61879
  decodeControlTokenClaims,
61435
61880
  defaultTargetForCommand,
61881
+ defaultUpdateRunner,
61436
61882
  diagnoseBody,
61437
61883
  doctorVersionReport,
61438
61884
  executableOnPath,
61885
+ fetchCliPackument,
61439
61886
  foreignPlaneError,
61440
61887
  formatSdkVersionLine,
61888
+ formatStaleLine,
61889
+ globalPrefixWritable,
61441
61890
  humanDuration,
61442
61891
  inspectControlToken,
61443
61892
  isHimalayaMonorepoCheckout,
61444
61893
  isLoopbackBase,
61445
61894
  isPublicTarget,
61895
+ isTrustedRegistry,
61446
61896
  loginModeMark,
61447
61897
  loginModeReport,
61448
61898
  loopbackTargetWarning,
61449
61899
  materializeForPreview,
61900
+ maybeSelfUpdate,
61450
61901
  nativeConfigAdvisories,
61451
61902
  nativeConfigTokenNotice,
61903
+ npmInstallArgs,
61452
61904
  ownerPlaneHint,
61453
61905
  ownerPlaneIsDark,
61454
61906
  parseFlags,
@@ -61468,6 +61920,7 @@ export {
61468
61920
  probeControlToken,
61469
61921
  probeReachable,
61470
61922
  pushAuthHint,
61923
+ readUpdateCache,
61471
61924
  renderDataSource,
61472
61925
  renderDeviceWidths,
61473
61926
  resolveAppSlug,
@@ -61478,12 +61931,18 @@ export {
61478
61931
  serveUnreachableHint,
61479
61932
  servesAll,
61480
61933
  shellShaDivergenceWarnings,
61934
+ staleUpdateLockFile,
61481
61935
  switchFlagErrors,
61482
61936
  templatePreviewAuthError,
61483
61937
  tokenCoversApp,
61938
+ trustedConfiguredRegistry,
61939
+ updateCachePath,
61940
+ updateCheckSuppression,
61941
+ upgradeEnvelope,
61484
61942
  uxHandoverHint,
61485
61943
  uxHandoverNote,
61486
61944
  uxHandoverOverride,
61487
61945
  wixPlayerTestflightTarget,
61488
- writePngFile
61946
+ writePngFile,
61947
+ writeUpdateCache
61489
61948
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.973.0",
3
+ "version": "0.975.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": [
@@ -64,5 +64,5 @@
64
64
  "artifactId": "himalaya-cli"
65
65
  }
66
66
  },
67
- "falconPackageHash": "555534516afcd5d59dd7e53839ae5548e01ea913dfdd2fd025ff48ec"
67
+ "falconPackageHash": "ac291974b49df3a7c0844b15d82c21cb6d2af2409ad4d2b53aa35b5b"
68
68
  }