@wix/himalaya-cli 0.926.0 → 0.927.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 +238 -2
  2. package/package.json +2 -2
package/dist/cli.mjs CHANGED
@@ -18525,6 +18525,7 @@ function shouldRevealError(args) {
18525
18525
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
18526
18526
  var init_validation = __esm({
18527
18527
  "../../core/ts/src/validation.ts"() {
18528
+ "use strict";
18528
18529
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
18529
18530
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
18530
18531
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -21948,6 +21949,7 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
21948
21949
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
21949
21950
  var init_worker_subtree = __esm({
21950
21951
  "../../core/ts/src/worker-subtree.ts"() {
21952
+ "use strict";
21951
21953
  init_worker_subtree_safe_types_generated();
21952
21954
  WORKER_SUBTREE_CEILINGS = Object.freeze({
21953
21955
  maxEncodedBytes: 65536,
@@ -50980,6 +50982,48 @@ var ServeCliClient = class {
50980
50982
  setAppConfig(app, patch) {
50981
50983
  return this.request("control", "POST", "/v1/admin/app-config", { body: { app, patch }, headers: this.ctrlHeaders() });
50982
50984
  }
50985
+ // ---- flags (RFC 0013) ----
50986
+ //
50987
+ // ALL FOUR ARE ON THE CONTROL PLANE, the read included, and that is not an oversight about
50988
+ // which door a device uses. `/v1/flags/{app}` is implemented in serve-control; the deployed
50989
+ // artifact happens to answer it on the serve origin too, because one process hosts both
50990
+ // (`isControlPath` in packages/serve/src/index.ts). Locally they are two processes, and the
50991
+ // one on the serve port answers `/v1/flags/` with a STUB — `{definitions: {}}`, unconditional
50992
+ // 200, documented as such in core/serve/src/run-handler.ts. So reading "the device door" from
50993
+ // the serve base is right on prod and silently wrong everywhere else: the list comes back
50994
+ // empty and every write reports that it is not being served.
50995
+ //
50996
+ // The read is still the point of the pairing: it is the same handler and the same file the
50997
+ // write touched, which is what makes it able to contradict a 200.
50998
+ /** Flag definitions as the plane would serve them (GET /v1/flags/{app}). No credential. */
50999
+ flags(app) {
51000
+ return this.request("control", "GET", `/v1/flags/${encodeURIComponent(app)}`);
51001
+ }
51002
+ /** Create or replace one flag definition (POST /v1/admin/flags). */
51003
+ setFlag(app, key2, definition) {
51004
+ return this.request("control", "POST", "/v1/admin/flags", { body: { app, key: key2, definition }, headers: this.ctrlHeaders() });
51005
+ }
51006
+ /** Ramp or kill a flag in place (POST /v1/admin/flags/ramp) — 404s an unknown key. */
51007
+ rampFlag(app, key2, change) {
51008
+ return this.request("control", "POST", "/v1/admin/flags/ramp", {
51009
+ body: {
51010
+ app,
51011
+ key: key2,
51012
+ // Spread-if-present, never `rolloutPercentage: change.rolloutPercentage`: the route reads
51013
+ // `typeof === "number"`, so an explicit `undefined` is harmless — but `enabled` is read as
51014
+ // `typeof === "boolean"`, and sending `{enabled: undefined}` in JSON drops the key anyway.
51015
+ // Being explicit here keeps the wire body equal to what the caller asked for, so a ramp
51016
+ // that only sets a percentage cannot also be read as "and leave enabled alone" by luck.
51017
+ ...typeof change.rolloutPercentage === "number" ? { rolloutPercentage: change.rolloutPercentage } : {},
51018
+ ...typeof change.enabled === "boolean" ? { enabled: change.enabled } : {}
51019
+ },
51020
+ headers: this.ctrlHeaders()
51021
+ });
51022
+ }
51023
+ /** Delete one flag definition (POST /v1/admin/flags/remove); echoes the definition it removed. */
51024
+ removeFlag(app, key2) {
51025
+ return this.request("control", "POST", "/v1/admin/flags/remove", { body: { app, key: key2 }, headers: this.ctrlHeaders() });
51026
+ }
50983
51027
  /** Write-through a design-tokens patch (POST /v1/admin/tokens). */
50984
51028
  setTokens(app, tokens, kit) {
50985
51029
  return this.request("control", "POST", "/v1/admin/tokens", { body: { app, tokens, ...kit ? { kit } : {} }, headers: this.ctrlHeaders() });
@@ -54382,6 +54426,9 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
54382
54426
  plane, no dev-server. With no argument it packs the current
54383
54427
  content package first \u2014 edit, run, look at it.
54384
54428
  Blocks until Ctrl-C.
54429
+ himi flags list|set|ramp|rm [<key>] [--app <a>]
54430
+ read/change operational flags (RFC 0013); writes report
54431
+ servedNow from the device door, never just ok
54385
54432
  himi visibility [public|unlisted] [--app <a>]
54386
54433
  read (no value) or set whether the public registry lists the app
54387
54434
  himi preview --app <app> --release <id> [--screen <id>] [--surface <id>] [--preview-token <token>]
@@ -54712,6 +54759,7 @@ var COMMANDS = [
54712
54759
  { name: "logs", summary: "worker console read-back (GET /v1/telemetry/logs); --follow tails via the seq cursor", required: "--app [--release] [--screen] [--level] [--follow]" },
54713
54760
  { name: "promote", summary: "flip a stored draft LIVE (POST /v1/admin/release/activate); --ring also points a rollout ring at it in the same command (POST /v1/admin/promote), which ring-based clients need in order to resolve at all \u2014 without it they stay unresolved and the warning goes to stderr; --percent sets that ring's rollout share; a release pushed past errors needs an explicit --force override", required: "--app --release [--ring <ring>] [--percent 0-100] [--force] (--control-token)" },
54714
54761
  { name: "patch", summary: 'change ONE item of a published app \u2014 a screen descriptor, a Tier-5 bundle, tokens, or the app-config baseline \u2014 from a RAW FILE, with no content package and no build. The low-level door: it does not lint your package, because there is no package here; `himi push --only <selector>` is the same edit with the full build + lint gate and is what to reach for when the package is in hand. Derives a new release from the app\'s current LIVE release, or from `--from-release <id>`. An explicit `--draft` or `--live` is REQUIRED \u2014 a patch has no default blast radius \u2014 and `--notes` is required with `--live` on a screen or app-config patch, because off-repo `gitSha` is "dev" and the ledger notes are then the only durable record of bytes that correspond to no commit. A branch does not merge: activating one derived from an older release reverts every item the patch does not move, so that case is refused until `--accept-regressions`. A bundle patch must repoint a screen (`--for-screen`) or the bytes land under a manifest key nothing reads. It cannot ADD a screen (screens are git-only, RFC 0012), never claims a slug \u2014 the app must already be published \u2014 and carries no attestation, source archive, assets, icon or minBinaryVersion, each named in the output.', required: "screen <id> --file <descriptor.json> [--bundle <sha256|none>] | bundle --file <bundle.js> (--for-screen <id>[,<id>] | --no-rebind) | tokens --file <tokens.json> | app-config --file <app-config.json> ; (--draft|--live) --app <a> [--from-release <id>] [--accept-regressions] [--label <n>] [--notes <t>] [--force] [--target <t>] (--control-token)" },
54762
+ { name: "flags", summary: "read and change the app's OPERATIONAL flags (RFC 0013) \u2014 `list` prints what the DEVICE door serves, `set <key> --definition` creates or replaces one, `ramp <key>` moves its rollout share or kills it, `rm <key>` deletes it and echoes the definition back so the removal is reversible. Every write is followed by a read of the same door the device uses, and the answer is reported as `servedNow`: flags are stored in the app's git-committed flags.json, so a control plane with no checkout accepts a write it will never serve.", required: "list|set|ramp|rm [<key>] [--definition <json|@file>] [--percent 0-100] [--enabled true|false] [--app <a>] (--control-token)" },
54715
54763
  { name: "visibility", summary: "read (no value) or set whether the public registry (/v1/apps + the Himi Player picker) LISTS the app. `public` lists it for everyone; `unlisted` hides it from listings while direct reads by app id \u2014 deep links, previews \u2014 keep working. App from --app or the content dir. Setting it needs no republish.", required: "[public|unlisted] [--app <a>] [--content <dir>] (--control-token)" },
54716
54764
  { name: "dist", summary: "build an INSTALLABLE QA binary (signed IPA / APK) on the Wix Job Service Mac pool and return an install link \u2014 the serve plane brokers the job, the pool builds it. WAITS for the build by default (~4 min) and prints the install link; `--no-wait` returns a jobId immediately instead. Omitting `--release` builds whatever is LIVE for the app. A named draft is refused unless `--allow-draft` makes that exceptional build explicit. `status <jobId>` / `watch <jobId>` / `list --app <a>` / `cancel <jobId>` inspect and stop builds (`watch` reports up front whether the job mints its own signing identity or borrows one, so a doomed iOS build is cancellable in seconds rather than after the archive). Off-repo, name a target \u2014 `--target wix` for a self-serve Wix login or `--target bo` for the internal employee path (the in-repo default is a local dev plane that isn't reachable off-repo); self-serve callers may build their OWN apps under a tighter budget (2 in flight / 5 per 24h), while a manage-only owner-role credential is refused. `--shell` picks the native host to sign into (branded-lite | owner-lite | player | demo-app). `--signing` defaults to `wix-managed` \u2014 the Wix Apple account mints a per-app signing identity for the build, so the caller provisions no Apple credentials of their own; `--signing byo` (sign with your own Apple account) is reserved and 501s. `--android-signing` is the Android counterpart and a SEPARATE axis (a keystore, not an Apple account): `qa-keystore` (default \u2014 the repo's committed QA key, identical on every pool machine, so a build installs over the previous one), `wix-keystore` (the Wix key the pool machine carries; the build FAILS naming the paths searched if it carries none, rather than quietly signing with something else), or `debug` (the old per-machine key \u2014 QA must uninstall any previous build first). NOT the normal way to ship: screens, Tier-5 bundles, tokens and app-config all go over the air with `himi push` in seconds \u2014 a binary trip is only needed for a NATIVE delta (a new SDUI component, a new declared capability/entitlement, a new bundle id, or a minBinaryVersion floor bump). Refuses to build an app whose launcher icon is missing, a placeholder, or one a store would reject \u2014 the one thing a binary carries that no later OTA push can fix; `--force` builds anyway", required: "[--app <a>] [--platform ios,android] [--release <id>] [--allow-draft] [--target <t>] [--shell branded-lite|owner-lite|player|demo-app] [--signing wix-managed|byo] [--android-signing qa-keystore|wix-keystore|debug] [--borrow-ios-id <bundleId>] [--no-wait] [--force] | status <jobId> [--target <t>] | watch <jobId> [--target <t>] [--interval <ms>] | list --app <a> [--target <t>] | cancel <jobId> [--target <t>]" },
54717
54765
  { name: "testflight", summary: 'build, sign and upload an app to TestFlight (internal testers \u2014 no Beta App Review), then WAIT until App Store Connect finishes processing it. Sugar for `store upload --track testflight`. Prompts first: a build number is consumed permanently on a real Apple account and cannot be reused \u2014 `--yes` for CI, `--dry-run` to see the plan without touching anything. The wait is the real check, not a nicety: fastlane prints "Successfully uploaded" for a binary App Store Connect then rejects in processing, and a rejected binary leaves NO build record at all (the reason is emailed). Public App Store review is deliberately NOT automatable here \u2014 `--track appstore` is refused; use /submit-ios-app when you mean to go public', required: "<app> [--track testflight|internal] [--profile <name>] [--yes] [--dry-run] [--skip-build] [--no-wait] [--json]" },
@@ -54886,6 +54934,12 @@ function diagnoseBody(body) {
54886
54934
  if (emptied.length) out.foldedIntoFindings = emptied;
54887
54935
  return out;
54888
54936
  }
54937
+ var GIT_FLAG_STORE_NOTE = "the plane accepted the write but is not serving it. Operational flags live in the app's git-committed flags.json (RFC 0013), so a control plane with no checkout \u2014 a hosted pod \u2014 writes to a filesystem that is not read back and is lost on restart. Commit the change to the app's flags.json, or point this command at a control plane that has the repo.";
54938
+ function servesAll(served, asked) {
54939
+ const stable = (v) => JSON.stringify(v, (_k, x) => x && typeof x === "object" && !Array.isArray(x) ? Object.fromEntries(Object.entries(x).sort(([a], [b]) => a.localeCompare(b))) : x);
54940
+ return Object.entries(asked).every(([k, v]) => stable(served[k]) === stable(v));
54941
+ }
54942
+ var GIT_FLAG_STORE_HALF_WRITE_NOTE = "the plane reported a failure, but this store writes flags.json BEFORE it commits \u2014 so the change may already be on the pod that served this request, uncommitted and lost on its next restart. servedNow says what that pod answers now; other pods may differ.";
54889
54943
  var PREVIEW_TOKEN_NOTE = "the preview link carries a bearer with a limited lifetime, and this call has no usable expiry for it \u2014 the plane stated none, or the one it stated is unreadable, already past, or beyond the hour this CLI will reason about. Run the `previewRenew` command printed beside this note \u2014 from the same plane and credentials as this call (any `--serve-url`/`--control-url`/token flags you passed) \u2014 for a fresh one.";
54890
54944
  function previewTokenNote(expiresAt, now = Date.now()) {
54891
54945
  const at = expiresAt ? Date.parse(expiresAt) : NaN;
@@ -54985,9 +55039,9 @@ function isPublicTarget(target, control) {
54985
55039
  }
54986
55040
  return isPublicServeOrigin(url ?? "");
54987
55041
  }
54988
- function serveUnreachableError(target, base, cause) {
55042
+ function serveUnreachableError(target, base, cause, plane = "serve") {
54989
55043
  return {
54990
- error: `cannot reach the serve plane for target "${target}" (${base || "no serve url"}): ${cause}. ${serveUnreachableHint(target)}`,
55044
+ error: `cannot reach the ${plane} plane for target "${target}" (${base || `no ${plane} url`}): ${cause}. ${serveUnreachableHint(target)}`,
54991
55045
  code: "needs_vpn",
54992
55046
  target
54993
55047
  };
@@ -55748,6 +55802,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
55748
55802
  })
55749
55803
  );
55750
55804
  const serveBase = () => resolveTarget({ target: str5(flags.target), defaultTarget: contextualDefaultTarget, serveUrl: str5(flags["serve-url"]) }).serve;
55805
+ const controlBase = () => resolveTarget({ target: str5(flags.target), defaultTarget: contextualDefaultTarget, controlUrl: str5(flags["control-url"]) }).control ?? "";
55751
55806
  const foreignPlaneRefusal = async () => {
55752
55807
  const base = serveBase();
55753
55808
  if (!isLoopbackBase(base)) return null;
@@ -58951,6 +59006,184 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
58951
59006
  extra: { kind, file: { path: file, bytes: bytes2.length, sha256: sha } }
58952
59007
  });
58953
59008
  }
59009
+ // OPERATIONAL FLAGS (RFC 0013) — the surface the admin portal's FlagsPanel used to own.
59010
+ //
59011
+ // The panel is going; this is what replaces it, and it is deliberately not a port. The panel
59012
+ // wrote `flags.json` into a pod filesystem and reported success, which is why "flags don't
59013
+ // work in production" was true and invisible for so long. Every write here is followed by a
59014
+ // READ of the device-facing door, and what that read says is what gets printed.
59015
+ case "flags": {
59016
+ const sub = pos[1];
59017
+ const app = str5(flags.app) ?? await appId(contentDir(flags));
59018
+ const usage = "usage: himi flags list | set <key> --definition <json|@file> | ramp <key> [--percent 0-100] [--enabled true|false] | rm <key>";
59019
+ if (sub !== "list" && sub !== "set" && sub !== "ramp" && sub !== "rm") {
59020
+ io.error(JSON.stringify({ error: `unknown flags subcommand: ${sub ?? "(none)"}`, hint: usage }));
59021
+ return 2;
59022
+ }
59023
+ const servedDefs = async () => {
59024
+ try {
59025
+ const r = await client2().flags(app);
59026
+ if (!r.ok) return null;
59027
+ const defs = r.json?.definitions;
59028
+ return Array.isArray(defs) ? defs : null;
59029
+ } catch {
59030
+ return null;
59031
+ }
59032
+ };
59033
+ if (sub === "list") {
59034
+ let r;
59035
+ try {
59036
+ r = await client2().flags(app);
59037
+ } catch (e) {
59038
+ io.error(JSON.stringify({
59039
+ ...serveUnreachableError(targetName(), controlBase(), e.message, "control"),
59040
+ // The deployed artifact answers control routes on its public serve origin too
59041
+ // (one process, `isControlPath`), so a reader who cannot reach the control host has
59042
+ // somewhere to point. Said as an override rather than done automatically: the local
59043
+ // serve port answers /v1/flags/ with a stub, and silently reading that is the
59044
+ // failure this whole command is built to avoid.
59045
+ hint: `flag routes live on the control plane; on a deployed target the same route is answered by the serve origin, so \`--control-url <serve origin>\` is the read-only way round a control host you cannot reach`
59046
+ }));
59047
+ return 1;
59048
+ }
59049
+ if (!r.ok) {
59050
+ print({ ok: false, status: r.status, body: r.json ?? r.text });
59051
+ return 1;
59052
+ }
59053
+ const defs = r.json?.definitions ?? [];
59054
+ print({ ok: true, app, count: defs.length, definitions: defs });
59055
+ return 0;
59056
+ }
59057
+ const key2 = pos[2];
59058
+ if (!key2) {
59059
+ io.error(JSON.stringify({ error: `flags ${sub} needs a key`, hint: usage }));
59060
+ return 2;
59061
+ }
59062
+ if (sub === "set") {
59063
+ const arg = str5(flags.definition);
59064
+ if (!arg) {
59065
+ io.error(JSON.stringify({ error: "flags set needs --definition <json|@file>", hint: usage }));
59066
+ return 2;
59067
+ }
59068
+ let def;
59069
+ try {
59070
+ const parsed = JSON.parse(arg.startsWith("@") ? readFileSync64(arg.slice(1), "utf8") : arg);
59071
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
59072
+ io.error(JSON.stringify({ error: "invalid_definition", problems: [`--definition must be a JSON object, got ${Array.isArray(parsed) ? "an array" : parsed === null ? "null" : typeof parsed}`] }));
59073
+ return 2;
59074
+ }
59075
+ def = parsed;
59076
+ } catch (e) {
59077
+ io.error(JSON.stringify({ error: "invalid_definition_json", detail: e.message }));
59078
+ return 2;
59079
+ }
59080
+ if (typeof def.key === "string" && def.key !== key2) {
59081
+ io.error(JSON.stringify({
59082
+ error: "definition_key_mismatch",
59083
+ key: key2,
59084
+ definitionKey: def.key,
59085
+ hint: `the stored flag takes its key from the command, so this would save "${def.key}"'s definition under "${key2}"`
59086
+ }));
59087
+ return 2;
59088
+ }
59089
+ const problems = [];
59090
+ if (typeof def.enabled !== "boolean") problems.push("enabled must be true or false");
59091
+ const variants = Array.isArray(def.variants) ? def.variants : null;
59092
+ if (!variants || variants.length === 0) problems.push("variants must be a non-empty array");
59093
+ if (def.rolloutPercentage !== void 0 && (typeof def.rolloutPercentage !== "number" || def.rolloutPercentage < 0 || def.rolloutPercentage > 100)) {
59094
+ problems.push(`rolloutPercentage must be 0..100, got ${JSON.stringify(def.rolloutPercentage)}`);
59095
+ }
59096
+ if (typeof def.defaultVariant !== "string") problems.push("defaultVariant must name a variant");
59097
+ else if (variants && variants.length > 0 && !variants.some((v) => v?.key === def.defaultVariant)) {
59098
+ problems.push(`defaultVariant "${def.defaultVariant}" is not one of the variants`);
59099
+ }
59100
+ if (problems.length > 0) {
59101
+ io.error(JSON.stringify({ error: "invalid_definition", problems, hint: 'e.g. {"enabled":true,"variants":[{"key":"on","weight":100},{"key":"off","weight":0}],"defaultVariant":"off"}' }));
59102
+ return 2;
59103
+ }
59104
+ const sent = { ...def, key: key2 };
59105
+ return await writeFlag(() => client2().setFlag(app, key2, sent), (now) => !!now && servesAll(now, sent));
59106
+ }
59107
+ if (sub === "ramp") {
59108
+ const percentGiven = flags.percent !== void 0;
59109
+ const percent = num(flags.percent);
59110
+ if (percentGiven && (percent === void 0 || percent < 0 || percent > 100)) {
59111
+ io.error(JSON.stringify({ error: `--percent must be 0..100, got ${String(flags.percent)}` }));
59112
+ return 2;
59113
+ }
59114
+ const enabledGiven = flags.enabled !== void 0;
59115
+ const enabledRaw = flags.enabled === true ? "true" : str5(flags.enabled);
59116
+ if (enabledGiven && enabledRaw !== "true" && enabledRaw !== "false") {
59117
+ io.error(JSON.stringify({ error: `--enabled must be true or false, got ${String(flags.enabled)}` }));
59118
+ return 2;
59119
+ }
59120
+ if (!percentGiven && !enabledGiven) {
59121
+ io.error(JSON.stringify({ error: "flags ramp needs --percent and/or --enabled", hint: usage }));
59122
+ return 2;
59123
+ }
59124
+ const change = {
59125
+ ...percentGiven ? { rolloutPercentage: percent } : {},
59126
+ ...enabledGiven ? { enabled: enabledRaw === "true" } : {}
59127
+ };
59128
+ return await writeFlag(() => client2().rampFlag(app, key2, change), (now) => !!now && servesAll(now, change));
59129
+ }
59130
+ const removedBefore = (await servedDefs())?.find((d) => d?.key === key2);
59131
+ return await writeFlag(() => client2().removeFlag(app, key2), (now) => now === void 0, removedBefore);
59132
+ async function writeFlag(send2, expected, undoCopy) {
59133
+ let r;
59134
+ try {
59135
+ r = await send2();
59136
+ } catch (e) {
59137
+ io.error(JSON.stringify(serveUnreachableError(targetName(), controlBase(), e.message, "control")));
59138
+ return 1;
59139
+ }
59140
+ if (!r.ok) {
59141
+ const served2 = r.status >= 500 ? await servedDefs() : null;
59142
+ const now2 = served2?.find((d) => d?.key === key2);
59143
+ const removed = r.json?.removed ?? (r.status >= 500 ? undoCopy : void 0);
59144
+ print({
59145
+ ok: false,
59146
+ status: r.status,
59147
+ app,
59148
+ key: key2,
59149
+ body: r.json ?? r.text,
59150
+ ...removed !== void 0 ? { removed } : {},
59151
+ ...r.status >= 500 ? {
59152
+ // The same two fields the success path prints, for the same reason: presence
59153
+ // alone would call a 500 that left the OLD definition in place "served".
59154
+ servedNow: served2 === null ? null : !!now2,
59155
+ asRequested: served2 === null ? null : expected(now2),
59156
+ warning: GIT_FLAG_STORE_HALF_WRITE_NOTE,
59157
+ hint: `check what is actually served: himi flags list --app ${app} --target ${targetName()}`
59158
+ } : {}
59159
+ });
59160
+ return 1;
59161
+ }
59162
+ const served = await servedDefs();
59163
+ const now = served?.find((d) => d?.key === key2);
59164
+ const wanted = expected(now);
59165
+ print({
59166
+ ok: true,
59167
+ app,
59168
+ key: key2,
59169
+ ...r.json ?? {},
59170
+ // A plane too old to echo what it deleted still leaves the caller with the copy this
59171
+ // command read before writing — otherwise `rm` succeeds against it and prints nothing
59172
+ // to paste back, which is the whole reason the pre-read exists.
59173
+ ...undoCopy !== void 0 && r.json?.removed === void 0 ? { removed: undoCopy } : {},
59174
+ // TWO fields, because one boolean cannot carry both facts without meaning different
59175
+ // things per verb. `servedNow` is plain presence — is the door serving this key at
59176
+ // all. `asRequested` is the judgement: does what it serves match what was just asked
59177
+ // for (for `rm`, that the key is gone). A replaced definition the plane kept serving
59178
+ // unchanged is servedNow:true / asRequested:false, which is the case a presence-only
59179
+ // check reported as success.
59180
+ servedNow: served === null ? null : !!now,
59181
+ asRequested: served === null ? null : wanted,
59182
+ ...served === null ? { note: "could not read the device door to confirm; the write was accepted" } : wanted ? {} : { warning: GIT_FLAG_STORE_NOTE, hint: `commit apps/${app}/flags.json instead` }
59183
+ });
59184
+ return 0;
59185
+ }
59186
+ }
58954
59187
  case "visibility": {
58955
59188
  const value = pos[1];
58956
59189
  if (value !== void 0 && value !== "public" && value !== "unlisted") {
@@ -60234,6 +60467,8 @@ if (isMain) {
60234
60467
  }
60235
60468
  export {
60236
60469
  ConfigLoadError,
60470
+ GIT_FLAG_STORE_HALF_WRITE_NOTE,
60471
+ GIT_FLAG_STORE_NOTE,
60237
60472
  PLACEHOLDER_CLIENT_ID,
60238
60473
  PLACEHOLDER_SITE_URL,
60239
60474
  PREVIEW_TOKEN_NOTE,
@@ -60286,6 +60521,7 @@ export {
60286
60521
  servePlaneAnswers,
60287
60522
  serveUnreachableError,
60288
60523
  serveUnreachableHint,
60524
+ servesAll,
60289
60525
  switchFlagErrors,
60290
60526
  templatePreviewAuthError,
60291
60527
  tokenCoversApp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.926.0",
3
+ "version": "0.927.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": "3b5786b0529fb62a083ffdac2a31ee756590bef38c0e1316c78dc50f"
67
+ "falconPackageHash": "5a9a78922aa072426bb6e4169693f0fdd84817367970c02f4e036483"
68
68
  }