@wix/himalaya-cli 0.925.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.
- package/dist/cli.mjs +236 -2
- package/package.json +2 -2
package/dist/cli.mjs
CHANGED
|
@@ -50982,6 +50982,48 @@ var ServeCliClient = class {
|
|
|
50982
50982
|
setAppConfig(app, patch) {
|
|
50983
50983
|
return this.request("control", "POST", "/v1/admin/app-config", { body: { app, patch }, headers: this.ctrlHeaders() });
|
|
50984
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
|
+
}
|
|
50985
51027
|
/** Write-through a design-tokens patch (POST /v1/admin/tokens). */
|
|
50986
51028
|
setTokens(app, tokens, kit) {
|
|
50987
51029
|
return this.request("control", "POST", "/v1/admin/tokens", { body: { app, tokens, ...kit ? { kit } : {} }, headers: this.ctrlHeaders() });
|
|
@@ -54384,6 +54426,9 @@ var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
|
54384
54426
|
plane, no dev-server. With no argument it packs the current
|
|
54385
54427
|
content package first \u2014 edit, run, look at it.
|
|
54386
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
|
|
54387
54432
|
himi visibility [public|unlisted] [--app <a>]
|
|
54388
54433
|
read (no value) or set whether the public registry lists the app
|
|
54389
54434
|
himi preview --app <app> --release <id> [--screen <id>] [--surface <id>] [--preview-token <token>]
|
|
@@ -54714,6 +54759,7 @@ var COMMANDS = [
|
|
|
54714
54759
|
{ name: "logs", summary: "worker console read-back (GET /v1/telemetry/logs); --follow tails via the seq cursor", required: "--app [--release] [--screen] [--level] [--follow]" },
|
|
54715
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)" },
|
|
54716
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)" },
|
|
54717
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)" },
|
|
54718
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>]" },
|
|
54719
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]" },
|
|
@@ -54888,6 +54934,12 @@ function diagnoseBody(body) {
|
|
|
54888
54934
|
if (emptied.length) out.foldedIntoFindings = emptied;
|
|
54889
54935
|
return out;
|
|
54890
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.";
|
|
54891
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.";
|
|
54892
54944
|
function previewTokenNote(expiresAt, now = Date.now()) {
|
|
54893
54945
|
const at = expiresAt ? Date.parse(expiresAt) : NaN;
|
|
@@ -54987,9 +55039,9 @@ function isPublicTarget(target, control) {
|
|
|
54987
55039
|
}
|
|
54988
55040
|
return isPublicServeOrigin(url ?? "");
|
|
54989
55041
|
}
|
|
54990
|
-
function serveUnreachableError(target, base, cause) {
|
|
55042
|
+
function serveUnreachableError(target, base, cause, plane = "serve") {
|
|
54991
55043
|
return {
|
|
54992
|
-
error: `cannot reach the
|
|
55044
|
+
error: `cannot reach the ${plane} plane for target "${target}" (${base || `no ${plane} url`}): ${cause}. ${serveUnreachableHint(target)}`,
|
|
54993
55045
|
code: "needs_vpn",
|
|
54994
55046
|
target
|
|
54995
55047
|
};
|
|
@@ -55750,6 +55802,7 @@ himi v${cliVersion()} \u2014 run \`himi --version\`. If authoring commands fail
|
|
|
55750
55802
|
})
|
|
55751
55803
|
);
|
|
55752
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 ?? "";
|
|
55753
55806
|
const foreignPlaneRefusal = async () => {
|
|
55754
55807
|
const base = serveBase();
|
|
55755
55808
|
if (!isLoopbackBase(base)) return null;
|
|
@@ -58953,6 +59006,184 @@ default (no --glyph): ${DEFAULT_GLYPH2} \u2014 a mark that claims nothing, so th
|
|
|
58953
59006
|
extra: { kind, file: { path: file, bytes: bytes2.length, sha256: sha } }
|
|
58954
59007
|
});
|
|
58955
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
|
+
}
|
|
58956
59187
|
case "visibility": {
|
|
58957
59188
|
const value = pos[1];
|
|
58958
59189
|
if (value !== void 0 && value !== "public" && value !== "unlisted") {
|
|
@@ -60236,6 +60467,8 @@ if (isMain) {
|
|
|
60236
60467
|
}
|
|
60237
60468
|
export {
|
|
60238
60469
|
ConfigLoadError,
|
|
60470
|
+
GIT_FLAG_STORE_HALF_WRITE_NOTE,
|
|
60471
|
+
GIT_FLAG_STORE_NOTE,
|
|
60239
60472
|
PLACEHOLDER_CLIENT_ID,
|
|
60240
60473
|
PLACEHOLDER_SITE_URL,
|
|
60241
60474
|
PREVIEW_TOKEN_NOTE,
|
|
@@ -60288,6 +60521,7 @@ export {
|
|
|
60288
60521
|
servePlaneAnswers,
|
|
60289
60522
|
serveUnreachableError,
|
|
60290
60523
|
serveUnreachableHint,
|
|
60524
|
+
servesAll,
|
|
60291
60525
|
switchFlagErrors,
|
|
60292
60526
|
templatePreviewAuthError,
|
|
60293
60527
|
tokenCoversApp,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "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": "
|
|
67
|
+
"falconPackageHash": "5a9a78922aa072426bb6e4169693f0fdd84817367970c02f4e036483"
|
|
68
68
|
}
|