@m-kopa/launchpad-cli 0.49.0 → 0.50.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/CHANGELOG.md +26 -0
- package/dist/cli.js +45 -6
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/recover.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/skills/launchpad-content-pr/SKILL.md +1 -1
- package/skills/launchpad-deploy/SKILL.md +1 -1
- package/skills/launchpad-deploy-status/SKILL.md +1 -1
- package/skills/launchpad-destroy/SKILL.md +1 -1
- package/skills/launchpad-identity/SKILL.md +1 -1
- package/skills/launchpad-onboard/SKILL.md +1 -1
- package/skills/launchpad-report/SKILL.md +1 -1
- package/skills/launchpad-status/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,25 @@ pre-1.0 minor bumps may carry breaking changes per ADR 0005.
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
Bot-side provisioning recovery safety spine: `launchpad recover` now classifies non-serving failed apps by their recorded provisioning stage.
|
|
12
|
+
Mutating dispatch is guarded by the original submission's single-flight lease, an original-Workflow terminal-state check, owner/co-owner/break-glass authorization for destructive or access-granting recovery, and the runtime `ROUTING_KV.__recovery_disabled` kill-switch.
|
|
13
|
+
Reconcile-only recovery remains available to editors.
|
|
14
|
+
No CLI version bump is required for this bot-side change.
|
|
15
|
+
|
|
16
|
+
Bot-side pre-Terraform recovery: an owner-run `launchpad recover` now abandons a wedged pre-TF workflow, archive-renames its app repo through the existing destroy finalizer, releases the destroyed registry record, and names `launchpad create` with the original manifest as the single next step.
|
|
17
|
+
`launchpad destroy` applies the same no-TF teardown when the persisted stage proves the wedge happened before Terraform, while post-TF failures continue through the existing Terraform destroy workflow so all credential and edge surfaces are revoked.
|
|
18
|
+
No CLI version bump is required for this bot-side change.
|
|
19
|
+
|
|
20
|
+
Bot-side post-Terraform recovery: `launchpad recover` now triggers a fresh Cloudflare Pages production deployment when infrastructure exists but content is not serving, then returns an async handoff to `launchpad status` and a final `launchpad recover` reconciliation once the deployment serves.
|
|
21
|
+
The driver reuses the established Pages deployment trigger, stays behind the recovery kill-switch, single-flight lease, and original-Workflow terminal check, and remains available to owner, co-owner, editor, and break-glass identities like `deploy` and `redeploy`.
|
|
22
|
+
No CLI version bump is required for this bot-side change.
|
|
23
|
+
|
|
24
|
+
Bot-side Terraform-PR recovery: owner, co-owner, and break-glass callers can now use `launchpad recover` to regenerate a wedged provisioning PR from the managed app repo's current `main` manifest.
|
|
25
|
+
The driver reuses the exact-SHA manifest reconcile engine, including fresh group resolution, ADR 0035 proof and attestation, shape validation, and gated auto-merge.
|
|
26
|
+
It then comments and closes only the stale `portal-bot/add-*` PR tied to the original submission, by exact PR number, and refuses retirement while an apply run for that PR head is active.
|
|
27
|
+
Failed `tf_pr_merged` resumes no longer skip forward to `tf_applied`; they direct the owner to the fresh-PR recovery path.
|
|
28
|
+
No CLI version bump is required for this bot-side change.
|
|
29
|
+
|
|
11
30
|
Platform-side delivery hardening (PS-2419): portal-bot production releases now deploy a verified, content-addressed CI artifact without rebuilding in the deploy job.
|
|
12
31
|
The authenticated `/version` endpoint and standing drift detector expose and monitor the served source and artifact identity.
|
|
13
32
|
No CLI version bump is required for this platform-side change.
|
|
@@ -22,6 +41,13 @@ No CLI version bump is required for this bot-side fix.
|
|
|
22
41
|
Bot-side reliability fix (PS-2418): all documented manifest status routes are now reachable, and read-only GitHub API calls retry bounded transient failures before failing closed.
|
|
23
42
|
No CLI version bump is required for this bot-side fix.
|
|
24
43
|
|
|
44
|
+
## 0.50.0 - 2026-07-17
|
|
45
|
+
|
|
46
|
+
**Async recovery initiation rendering.**
|
|
47
|
+
|
|
48
|
+
- `launchpad recover` now treats HTTP 202 as a successful async initiation, prints the server-provided watch and finalize message, and includes the deployment or Terraform PR identifiers returned by the bot.
|
|
49
|
+
- JSON output preserves the complete 202 response and exits successfully, so initiated post-Terraform and Terraform-PR recovery no longer appears to fail in scripts.
|
|
50
|
+
|
|
25
51
|
## 0.49.0 - 2026-07-15
|
|
26
52
|
|
|
27
53
|
**Exact-commit access reconcile and group-reference parity.**
|
package/dist/cli.js
CHANGED
|
@@ -19,7 +19,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
19
19
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
20
|
|
|
21
21
|
// src/version.ts
|
|
22
|
-
var CLI_VERSION = "0.
|
|
22
|
+
var CLI_VERSION = "0.50.0";
|
|
23
23
|
|
|
24
24
|
// src/config.ts
|
|
25
25
|
import * as os from "node:os";
|
|
@@ -11154,19 +11154,23 @@ async function runRecover(args, io) {
|
|
|
11154
11154
|
});
|
|
11155
11155
|
const body = await res.json().catch(() => null);
|
|
11156
11156
|
if (parsed.json) {
|
|
11157
|
-
io.out(JSON.stringify({ httpStatus: res.status, ...body
|
|
11158
|
-
return res.status === 200 ? 0 : 1;
|
|
11157
|
+
io.out(JSON.stringify({ httpStatus: res.status, ...isRecord3(body) ? body : {} }, null, 2));
|
|
11158
|
+
return res.status === 200 || res.status === 202 ? 0 : 1;
|
|
11159
11159
|
}
|
|
11160
|
-
if (res.status === 200 && body
|
|
11160
|
+
if (res.status === 200 && hasRecoverOutcome(body)) {
|
|
11161
11161
|
renderSuccess2(body, io);
|
|
11162
11162
|
return 0;
|
|
11163
11163
|
}
|
|
11164
|
+
if (res.status === 202 && isRecoverInitiatedBody(body)) {
|
|
11165
|
+
renderInitiated(body, io);
|
|
11166
|
+
return 0;
|
|
11167
|
+
}
|
|
11164
11168
|
if (res.status === 503) {
|
|
11165
|
-
const msg = body
|
|
11169
|
+
const msg = isRecord3(body) && typeof body.message === "string" ? body.message : "live state unavailable — nothing was changed; retry shortly.";
|
|
11166
11170
|
io.err(`launchpad recover: ${msg}`);
|
|
11167
11171
|
return 1;
|
|
11168
11172
|
}
|
|
11169
|
-
if (res.status === 409 && body
|
|
11173
|
+
if (res.status === 409 && hasRecoverRefusal(body)) {
|
|
11170
11174
|
renderRefusal(parsed.slug, body, io);
|
|
11171
11175
|
return 1;
|
|
11172
11176
|
}
|
|
@@ -11176,6 +11180,28 @@ async function runRecover(args, io) {
|
|
|
11176
11180
|
return mapError(e, parsed.slug, io);
|
|
11177
11181
|
}
|
|
11178
11182
|
}
|
|
11183
|
+
function isRecord3(value) {
|
|
11184
|
+
return typeof value === "object" && value !== null;
|
|
11185
|
+
}
|
|
11186
|
+
function hasRecoverOutcome(value) {
|
|
11187
|
+
return isRecord3(value) && "outcome" in value;
|
|
11188
|
+
}
|
|
11189
|
+
function isRecoverInitiatedBody(value) {
|
|
11190
|
+
if (!isRecord3(value))
|
|
11191
|
+
return false;
|
|
11192
|
+
return [
|
|
11193
|
+
typeof value.slug === "string",
|
|
11194
|
+
typeof value.outcome === "string",
|
|
11195
|
+
typeof value.message === "string",
|
|
11196
|
+
value.deploymentId === undefined || typeof value.deploymentId === "string",
|
|
11197
|
+
value.freshPrNumber === undefined || typeof value.freshPrNumber === "number",
|
|
11198
|
+
value.stalePrNumber === undefined || typeof value.stalePrNumber === "number",
|
|
11199
|
+
value.recoveryClass === undefined || value.recoveryClass === "post_tf" || value.recoveryClass === "tf_pr"
|
|
11200
|
+
].every(Boolean);
|
|
11201
|
+
}
|
|
11202
|
+
function hasRecoverRefusal(value) {
|
|
11203
|
+
return isRecord3(value) && "error" in value;
|
|
11204
|
+
}
|
|
11179
11205
|
function renderSuccess2(body, io) {
|
|
11180
11206
|
if (body.outcome === "noop_already_healthy") {
|
|
11181
11207
|
io.out(`${body.slug}: already healthy — nothing to recover.`);
|
|
@@ -11199,6 +11225,19 @@ function renderSuccess2(body, io) {
|
|
|
11199
11225
|
io.out("");
|
|
11200
11226
|
io.out(`Run \`launchpad status ${body.slug}\` — it now reports the live deployment truth.`);
|
|
11201
11227
|
}
|
|
11228
|
+
function renderInitiated(body, io) {
|
|
11229
|
+
io.out(`${body.slug}: recovery initiated (${body.outcome}).`);
|
|
11230
|
+
if (body.deploymentId !== undefined) {
|
|
11231
|
+
io.out(` deployment: ${body.deploymentId}`);
|
|
11232
|
+
}
|
|
11233
|
+
if (body.freshPrNumber !== undefined) {
|
|
11234
|
+
io.out(` fresh PR: #${body.freshPrNumber}`);
|
|
11235
|
+
}
|
|
11236
|
+
if (body.stalePrNumber !== undefined) {
|
|
11237
|
+
io.out(` stale PR: #${body.stalePrNumber}`);
|
|
11238
|
+
}
|
|
11239
|
+
io.out(` ${body.message}`);
|
|
11240
|
+
}
|
|
11202
11241
|
function renderRefusal(slug, body, io) {
|
|
11203
11242
|
io.err(`launchpad recover: refused — "${slug}" was NOT repaired.`);
|
|
11204
11243
|
if (typeof body.message === "string") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAAS,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAGjE,eAAO,MAAM,eAAe,EAAE,OAI7B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recover.d.ts","sourceRoot":"","sources":["../../src/commands/recover.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAS,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAEjE,eAAO,MAAM,cAAc,EAAE,OAI5B,CAAC;AAIF,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;
|
|
1
|
+
{"version":3,"file":"recover.d.ts","sourceRoot":"","sources":["../../src/commands/recover.ts"],"names":[],"mappings":"AAiCA,OAAO,KAAK,EAAS,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAEjE,eAAO,MAAM,cAAc,EAAE,OAI5B,CAAC;AAIF,UAAU,WAAW;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AA2OD;;kEAEkE;AAClE,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,GAAG,GAAE,MAAsB,EAC3B,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,GAC5B,WAAW,GAAG,MAAM,CAgDtB"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.
|
|
1
|
+
export declare const CLI_VERSION = "0.50.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-content-pr
|
|
3
3
|
description: Push a content change to a Launchpad app via `launchpad deploy` and verify it shipped via `launchpad status`. Covers the post-first-deploy iteration loop (edit → deploy → verify) — subsequent deploys commit directly to the app repo's main and the Pages build runs asynchronously, so verification is its own step. Use when someone says "push a content change", "ship an update", "/launchpad-content-pr", "verify my deploy", or after `/launchpad-deploy` reports `done` and they want to follow up with an edit.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.50.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-deploy
|
|
3
3
|
description: Walk a Launchpad user through deploying an app from their local working directory (Model A — `launchpad init` + `launchpad deploy`). Wraps the CLI verbs end-to-end: detects the app shape, scaffolds `launchpad.yaml`, resolves the allowed Entra group via `launchpad groups`, bundles the CWD via `launchpad deploy`, and watches the rollout via `launchpad status`. Use when someone says "deploy a new app", "ship my app to Launchpad", "/launchpad-deploy", "I have an app locally — get it on Launchpad", or any variant. Resume/abandon for legacy in-flight provisioning is at the bottom.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.50.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-deploy-status
|
|
3
3
|
description: Show the current provisioning stage + failure reason for a Launchpad app via `launchpad status` (Model A drift + deployment_verified) and `launchpad apps` (lifecycle bucket), or watch provisioning live with `launchpad watch`. Renders the M-892 stage trace for in-flight provisioning, and is the canonical home for `launchpad recover` (repair a terminal-failed app record that is actually live). Use when someone says "what's the status of demo-X", "/launchpad-deploy-status", "is my deploy stuck", "watch my deploy go live", "watch provisioning", "my app says failed but it's serving", or after `/launchpad-deploy` reports a non-`done` terminal stage.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.50.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-destroy
|
|
3
3
|
description: Tear down a Launchpad app end-to-end via `launchpad destroy` — Cloudflare Pages project, edge-auth wiring (gateway KV/audience entries, or the Access app for `auth: access` apps), custom hostname, platform-repo TF, and the app repo (archive-renamed). Owner-only verb with a two-step destructive confirmation. Use when someone says "destroy this app", "/launchpad-destroy", "tear down `<slug>`", "delete the app", or asks to clean up a smoke-test / orphan / retired app.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.50.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-identity
|
|
3
3
|
description: Teach an app author how to use the signed-in user's identity inside a Launchpad app — read the gateway-forwarded X-Launchpad-User-Assertion in a Pages Function, VERIFY it with @m-kopa/platform-auth (fail-closed), and show who's logged in (sub/email/name). Use when someone says "who is logged in", "show the current user", "get the user's email in my app", "auth in my launchpad app", "read the user identity", "/launchpad-identity", or is wiring up an /api/me for a gateway-fronted app.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.50.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-onboard
|
|
3
3
|
description: One-time setup for the Launchpad CLI + Claude Code skill bundle. Verifies the `launchpad` CLI is installed and current, runs `launchpad whoami` to confirm the session is fresh, and checks the bundled skills are installed and in lock-step with the CLI. Idempotent — safe to re-run any time. Use when someone says "set me up for Launchpad", "I just got a new machine and want to use Launchpad", "/launchpad-onboard", or any of the other launchpad-* skills fails on a prereq check.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.50.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-report
|
|
3
3
|
description: File a bug report or feature request to the Launchpad team's tracker from the CLI. Use when someone reports something broken, hits an error in a launchpad command, or wishes a feature existed — e.g. "this is broken", "report a bug", "can you file that", "I wish launchpad could…", "/launchpad-bug", "/launchpad-feature". Always confirm and show exactly what you'll send before filing; never file silently.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.50.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-status
|
|
3
3
|
description: Show whether a Launchpad app's local launchpad.yaml matches what's deployed, and read the deployed manifest. Wraps `launchpad pull` (fetch deployed YAML) and `launchpad status` (drift report). Use when someone says "is my app in sync", "what's deployed", "show drift", "/launchpad-status", "/launchpad-pull", or after `launchpad deploy` to verify the change landed.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.50.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|