@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 +52 -1
- package/guides/releasing-with-himi-serve.md +46 -1
- package/package.json +2 -2
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
|
-
|
|
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.
|
|
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": "
|
|
67
|
+
"falconPackageHash": "0449b6185090a3685450f4bf6e21424bd545c01791d527f37625af8c"
|
|
68
68
|
}
|