@wix/himalaya-cli 0.948.0 → 0.949.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
@@ -51150,6 +51150,18 @@ var ServeCliClient = class {
51150
51150
  removeRelease(app) {
51151
51151
  return this.request("control", "POST", "/v1/admin/release/remove", { body: { app }, headers: this.ctrlHeaders() });
51152
51152
  }
51153
+ /**
51154
+ * Undo a `removeRelease` inside its retention window (POST /v1/admin/release/restore).
51155
+ *
51156
+ * The route is as privileged as the remove was — same scope + role gates — and it is the reason
51157
+ * `removeRelease`'s response advertises `restorable` + `restoreBy`. Shipping the remove without
51158
+ * this leaves that promise unreachable from the CLI, which is how an un-publish starts reading
51159
+ * as final. Refuses with 404 `no_tombstone`, 409 `nothing_to_restore`, or 410 once the window
51160
+ * has closed; the CLI turns each into a named refusal rather than a bare status.
51161
+ */
51162
+ restoreRelease(app) {
51163
+ return this.request("control", "POST", "/v1/admin/release/restore", { body: { app }, headers: this.ctrlHeaders() });
51164
+ }
51153
51165
  /** Write-through an OTA app-config patch (POST /v1/admin/app-config). */
51154
51166
  setAppConfig(app, patch) {
51155
51167
  return this.request("control", "POST", "/v1/admin/app-config", { body: { app, patch }, headers: this.ctrlHeaders() });
@@ -55311,6 +55323,37 @@ function tokenCoversApp(token, app) {
55311
55323
  if (!claims || claims.apps === "*" || !Array.isArray(claims.apps)) return true;
55312
55324
  return claims.apps.includes(app);
55313
55325
  }
55326
+ function credentialKind(token) {
55327
+ const claims = decodeControlTokenClaims(token);
55328
+ if (!claims) return null;
55329
+ if (claims.selfServe) return "self-serve";
55330
+ return claims.sub ? "operator" : "shared";
55331
+ }
55332
+ var KIND_LABEL = {
55333
+ "self-serve": "self-serve",
55334
+ shared: "shared",
55335
+ operator: "operator/provisioned"
55336
+ };
55337
+ var KIND_TIER = {
55338
+ "self-serve": "self-serve",
55339
+ shared: "shared",
55340
+ operator: "employee"
55341
+ };
55342
+ var kindLoginBack = (kind, target) => {
55343
+ if (kind === "self-serve") return `himi login --browser --target ${target}`;
55344
+ if (kind === "operator") return `himi login --dpx --target ${target}`;
55345
+ return void 0;
55346
+ };
55347
+ function credentialSwapNotice(prevToken, nextToken, target) {
55348
+ if (!prevToken) return void 0;
55349
+ const prev = credentialKind(prevToken);
55350
+ const next = credentialKind(nextToken);
55351
+ if (!prev || !next || prev === next) return void 0;
55352
+ const tierLine = `\`himi dist\` will now charge the ${KIND_TIER[next]} tier instead of the ${KIND_TIER[prev]} one.`;
55353
+ const back = kindLoginBack(prev, target);
55354
+ const backLine = back ? ` \`${back}\` switches back.` : "";
55355
+ return `replacing the ${KIND_LABEL[prev]} credential for target "${target}" with a ${KIND_LABEL[next]} one. ${tierLine}${backLine}`;
55356
+ }
55314
55357
  var HARD_CLAIM_REFUSALS = /* @__PURE__ */ new Set(["slug_reserved", "invalid_slug", "account_ambiguous", "not_your_account"]);
55315
55358
  var AUTO_CLAIM_TIMEOUT_MS = 15e3;
55316
55359
  async function autoClaimSlug(opts) {
@@ -59898,6 +59941,7 @@ ${label2}`);
59898
59941
  }
59899
59942
  case "login": {
59900
59943
  const target = targetName();
59944
+ const prevControlToken = loadTargetsFile().targets?.[target]?.controlToken;
59901
59945
  let issue2;
59902
59946
  let ownerIssue;
59903
59947
  try {
@@ -59983,6 +60027,7 @@ ${label2}`);
59983
60027
  exp = JSON.parse(Buffer.from(body2.token.split(".")[0], "base64url").toString("utf8")).exp;
59984
60028
  } catch {
59985
60029
  }
60030
+ const swap2 = credentialSwapNotice(prevControlToken, body2.token, target);
59986
60031
  print({
59987
60032
  ok: true,
59988
60033
  via: "wix",
@@ -59991,6 +60036,7 @@ ${label2}`);
59991
60036
  exp,
59992
60037
  scope: body2.scope,
59993
60038
  storedAt: storedAt2,
60039
+ ...swap2 ? { notice: swap2 } : {},
59994
60040
  ...Array.isArray(body2.scope) && body2.scope.length === 0 ? { hint: "you own no apps yet \u2014 claim one with `himi apps claim <slug>`" } : {}
59995
60041
  });
59996
60042
  return 0;
@@ -60046,6 +60092,7 @@ ${label2}`);
60046
60092
  sub = c.sub;
60047
60093
  } catch {
60048
60094
  }
60095
+ const swap2 = credentialSwapNotice(prevControlToken, result.token, target);
60049
60096
  print({
60050
60097
  ok: true,
60051
60098
  via: "browser",
@@ -60054,6 +60101,7 @@ ${label2}`);
60054
60101
  exp,
60055
60102
  scope,
60056
60103
  storedAt: storedAt2,
60104
+ ...swap2 ? { notice: swap2 } : {},
60057
60105
  ...Array.isArray(scope) && scope.length === 0 ? { hint: "you own no apps yet \u2014 claim one with `himi apps claim <slug>`" } : {}
60058
60106
  });
60059
60107
  return 0;
@@ -60089,7 +60137,8 @@ ${label2}`);
60089
60137
  return 1;
60090
60138
  }
60091
60139
  const storedAt = saveControlToken(target, body.token);
60092
- print({ ok: true, target, exp: body.exp, scope: body.scope, storedAt });
60140
+ const swap = credentialSwapNotice(prevControlToken, body.token, target);
60141
+ print({ ok: true, target, exp: body.exp, scope: body.scope, storedAt, ...swap ? { notice: swap } : {} });
60093
60142
  return 0;
60094
60143
  }
60095
60144
  // --- #814 Stage 3: self-serve app ownership + local token introspection -------
@@ -60742,6 +60791,8 @@ export {
60742
60791
  autoClaimSlug,
60743
60792
  buildDoctorReport,
60744
60793
  compareVersions2 as compareVersions,
60794
+ credentialKind,
60795
+ credentialSwapNotice,
60745
60796
  decodeControlTokenClaims,
60746
60797
  defaultTargetForCommand,
60747
60798
  diagnoseBody,
@@ -80,6 +80,49 @@ traffic until its gateway route was deleted on 2026-09-05; it now 404s with no r
80
80
  ```
81
81
  Kill-switch a bad bundle: `himi-serve release revoke --app <app> --integrity <sri> --target bo --control-token "$TOK"`.
82
82
 
83
+ ### Retiring an app
84
+
85
+ `release revoke` kills one bundle; `release remove` unpublishes the **app** — it drops the
86
+ manifest and takes the app out of the `/v1/apps` registry, so devices resolving it stop getting
87
+ a release:
88
+
89
+ ```
90
+ himi-serve release remove --app <app> --yes --target bo --control-token "$TOK"
91
+ himi-serve release restore --app <app> --target bo --control-token "$TOK"
92
+ ```
93
+
94
+ **`--yes` is required**, and nothing is sent without it. Every other release verb is narrower —
95
+ `revoke` needs an integrity, `promote` needs a release, and both leave the app resolvable — so
96
+ this is the one where a typo'd `--app` unpublishes whatever it names.
97
+
98
+ **Whether a remove can be undone is the BACKEND's answer, not a property of the verb.** A store
99
+ that implements `restore` (the cloud store) records a tombstone and the remove reports
100
+ `restorable: true` with a `restoreBy` deadline. A store that does not — including the
101
+ `FileReleaseStore` a local serve-stack runs on, which implements `remove` and no `restore` —
102
+ reports `restorable: false`, and `release restore` answers `501 restore_unsupported`. **There the
103
+ un-publish is permanent.** So read the `restorable` field in the response before treating a remove
104
+ as reversible; the CLI prints the `restoreWith` command line only when the plane says the undo
105
+ exists, and stays silent when it does not.
106
+
107
+ That is also why the confirmation is `--yes` rather than `apps backfill`'s
108
+ rehearse-unless-`--apply`: on a store with restore the cost of getting it wrong is a restore rather
109
+ than a permanent loss, and on a store without one the confirmation is the only thing standing in
110
+ front of an irreversible write.
111
+
112
+ `restore` replays the tombstone inside that window. It takes no `--yes`, since making the recovery
113
+ harder to type than the destructive verb has the asymmetry backwards. It is **not** gated
114
+ identically to `remove`: `remove` is in serve-control's `PUBLISH_CONTENT_PATHS`, so an `owner`-role
115
+ token is refused, while `restore` is not in that set and an owner-scoped token may restore an app
116
+ it owns. Both still require control-plane scope authentication. Past the window it refuses, and each refusal says which case it
117
+ is — nothing recorded (404), recorded but carrying no release to put back (409), or the window
118
+ closed (410, the only way back being `release push`).
119
+
120
+ **This is the general door; `template remove` is the same route narrowed to the
121
+ `himi-template-` namespace.** An app with an *owner* can also be retired owner-side with
122
+ `himi apps release <slug> --yes`, but that needs ownership — so an **unclaimed** app (a QA
123
+ scaffold, anything published before auto-claiming) has nobody to supply it and this is the only
124
+ command that reaches it.
125
+
83
126
  ### Curating templates
84
127
 
85
128
  A starter template is an app in the reserved `himi-template-<name>` namespace, so it is
@@ -110,7 +153,9 @@ name may legally begin with `himi-template-` (the name pattern is lowercase alph
110
153
  inner hyphens), and such a name publishes to a **doubly** prefixed app id — so `--name` cannot
111
154
  express it without becoming ambiguous. The CLI refuses the ambiguous form and points at `--app`. `--app` only accepts ids inside the
112
155
  reserved namespace: a non-template id is refused, so this group cannot unpublish or
113
- kill-switch an ordinary app by mistake — use the `release` group for those.
156
+ kill-switch an ordinary app by mistake — use the `release` group for those, which the refusal
157
+ names by the verb you asked for (`release remove --app <app> --yes` for a remove, `release
158
+ revoke … --integrity <sri>` for a revoke).
114
159
 
115
160
  `remove` unpublishes the app; `revoke` is the reversible kill-switch, and it resolves the
116
161
  bundle integrities from the template's current release manifest so you never paste an SRI. A
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.948.0",
3
+ "version": "0.949.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": "c8b30ba70c8fce59bdc7ac8a6ca63b0f2f35e34869de1e880606f116"
67
+ "falconPackageHash": "0449b6185090a3685450f4bf6e21424bd545c01791d527f37625af8c"
68
68
  }