@m-kopa/launchpad-cli 0.56.0 → 0.57.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 +16 -0
- package/dist/cli.js +25 -3
- 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 +3 -8
- package/skills/launchpad-deploy-status/SKILL.md +1 -1
- package/skills/launchpad-destroy/SKILL.md +19 -21
- 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,22 @@ pre-1.0 minor bumps may carry breaking changes per ADR 0005.
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 0.57.0 - 2026-08-20
|
|
12
|
+
|
|
13
|
+
**Apps can declare a default-off DSR sign-in policy.**
|
|
14
|
+
|
|
15
|
+
- Gateway manifests may set `access.identity_types: [employee, dsr]`; omitting the field remains employee-only, and `employee` cannot be removed.
|
|
16
|
+
- The generated gateway routing policy carries the opt-in for the gateway rollout.
|
|
17
|
+
- This policy does not enable DSR login by itself; apps must wait for the DSR canary and gateway release before adding `dsr`.
|
|
18
|
+
|
|
19
|
+
## 0.56.1 - 2026-08-17
|
|
20
|
+
|
|
21
|
+
**Destroy now removes bot-owned resources without sacrificing D1 recovery.**
|
|
22
|
+
|
|
23
|
+
- After Terraform succeeds, the portal bot exports the recorded D1 UUID to private R2, verifies the stored SQL object, deletes that database and declared cron Workers, and only then archives the app repository.
|
|
24
|
+
- D1 exports are retained for 30 days under `destroy-exports/<slug>/<uuid>.sql`, with a platform runbook for restoring an accidentally destroyed database.
|
|
25
|
+
- Unknown resource identity, export failure, cross-app Worker ownership conflict, or deletion failure leaves the app in `destroy_failed` with its repository unarchived.
|
|
26
|
+
|
|
11
27
|
## 0.56.0 - 2026-08-17
|
|
12
28
|
|
|
13
29
|
**Container runtime validation and deploy status pages are now available from the CLI.**
|
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.57.0";
|
|
23
23
|
|
|
24
24
|
// src/config.ts
|
|
25
25
|
import { readFileSync } from "node:fs";
|
|
@@ -2570,6 +2570,12 @@ import { parse as parseYaml2 } from "yaml";
|
|
|
2570
2570
|
import { z } from "zod";
|
|
2571
2571
|
var APP_TYPES2 = ["static", "react", "react+api", "container"];
|
|
2572
2572
|
var AUTH_MODES = ["access", "gateway"];
|
|
2573
|
+
var IDENTITY_TYPES = ["employee", "dsr"];
|
|
2574
|
+
var IdentityTypesSchema = z.array(z.enum(IDENTITY_TYPES)).min(1).refine((values) => new Set(values).size === values.length, {
|
|
2575
|
+
message: "identity_types must not contain duplicates"
|
|
2576
|
+
}).refine((values) => values.includes("employee"), {
|
|
2577
|
+
message: "identity_types must include employee"
|
|
2578
|
+
});
|
|
2573
2579
|
var RUNTIMES = ["cloudflare-containers", "aca"];
|
|
2574
2580
|
var SECRET_SOURCES = ["env-file", "platform-managed"];
|
|
2575
2581
|
var TARGET_KINDS = ["pages", "worker"];
|
|
@@ -2590,7 +2596,8 @@ var DeploymentSchema = z.object({
|
|
|
2590
2596
|
var AccessSchema = z.object({
|
|
2591
2597
|
allowed_entra_group: z.string().min(1).optional(),
|
|
2592
2598
|
allowed_entra_groups: z.array(z.string().min(1)).min(1).refine((arr) => new Set(arr).size === arr.length, { message: "allowed_entra_groups must not contain duplicates" }).optional(),
|
|
2593
|
-
session_duration: z.string().regex(SESSION_DURATION_REGEX, "must match Go duration format, e.g. 24h / 30m / 60s").optional()
|
|
2599
|
+
session_duration: z.string().regex(SESSION_DURATION_REGEX, "must match Go duration format, e.g. 24h / 30m / 60s").optional(),
|
|
2600
|
+
identity_types: IdentityTypesSchema.optional()
|
|
2594
2601
|
}).strict().superRefine((access, ctx) => {
|
|
2595
2602
|
const hasSingular = access.allowed_entra_group !== undefined;
|
|
2596
2603
|
const hasPlural = access.allowed_entra_groups !== undefined;
|
|
@@ -2678,6 +2685,13 @@ var ManifestSchema = z.object({
|
|
|
2678
2685
|
sensitive: z.boolean().optional()
|
|
2679
2686
|
}).strict().superRefine((m, ctx) => {
|
|
2680
2687
|
const isContainer = m.deployment.type === "container";
|
|
2688
|
+
if (m.access.identity_types !== undefined && (m.auth ?? "access") !== "gateway") {
|
|
2689
|
+
ctx.addIssue({
|
|
2690
|
+
code: z.ZodIssueCode.custom,
|
|
2691
|
+
path: ["access", "identity_types"],
|
|
2692
|
+
message: "access.identity_types is only valid with auth: gateway"
|
|
2693
|
+
});
|
|
2694
|
+
}
|
|
2681
2695
|
if (m.confine_origin !== undefined) {
|
|
2682
2696
|
if (isContainer) {
|
|
2683
2697
|
ctx.addIssue({
|
|
@@ -3302,7 +3316,8 @@ var SpecSchema = z2.object({
|
|
|
3302
3316
|
allowed_entra_groups: z2.array(z2.string().min(1)).min(1).refine((a) => new Set(a).size === a.length, {
|
|
3303
3317
|
message: "allowed_entra_groups must not contain duplicates"
|
|
3304
3318
|
}),
|
|
3305
|
-
require_email_domain: z2.string().min(1).optional()
|
|
3319
|
+
require_email_domain: z2.string().min(1).optional(),
|
|
3320
|
+
identity_types: IdentityTypesSchema.optional()
|
|
3306
3321
|
}).strict(),
|
|
3307
3322
|
auth: z2.enum(AUTH_MODES).optional(),
|
|
3308
3323
|
build: BuildSchema.optional(),
|
|
@@ -3317,6 +3332,13 @@ var SpecSchema = z2.object({
|
|
|
3317
3332
|
secrets: SecretsSchema.optional(),
|
|
3318
3333
|
fronted_by_concierge: z2.boolean().optional()
|
|
3319
3334
|
}).strict().superRefine((s, ctx) => {
|
|
3335
|
+
if (s.access.identity_types !== undefined && (s.auth ?? "access") !== "gateway") {
|
|
3336
|
+
ctx.addIssue({
|
|
3337
|
+
code: z2.ZodIssueCode.custom,
|
|
3338
|
+
path: ["access", "identity_types"],
|
|
3339
|
+
message: "access.identity_types is only valid with auth: gateway"
|
|
3340
|
+
});
|
|
3341
|
+
}
|
|
3320
3342
|
for (const [k, v] of Object.entries(s.env_vars ?? {})) {
|
|
3321
3343
|
if (/_(TOKEN|SECRET)$/.test(k) && v.type !== "secret_text") {
|
|
3322
3344
|
ctx.addIssue({
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.
|
|
1
|
+
export declare const CLI_VERSION = "0.57.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.57.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.57.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -458,13 +458,8 @@ targets:
|
|
|
458
458
|
d1_binding: DB # env binding name your /api code reads
|
|
459
459
|
```
|
|
460
460
|
|
|
461
|
-
On `launchpad deploy` the bot auto-provisions the shared D1 named after the
|
|
462
|
-
|
|
463
|
-
existing database) and binds it to the Pages app, so `env.DB` works with no
|
|
464
|
-
manual `wrangler d1 create`. The bot also pins the matching
|
|
465
|
-
`[[d1_databases]]` block into the **committed** `wrangler.toml` — Pages
|
|
466
|
-
git-source builds read bindings from that file, and a build without the
|
|
467
|
-
block silently resets the binding to empty.
|
|
461
|
+
On `launchpad deploy` the bot auto-provisions the shared D1 named after the slug (**create-or-adopt by slug during deploy** - a successful destroy exports and deletes the recorded UUID before repository archive, so platform recovery creates a fresh destination database) and binds it to the Pages app, so `env.DB` works with no manual `wrangler d1 create`.
|
|
462
|
+
The bot also pins the matching `[[d1_databases]]` block into the **committed** `wrangler.toml` - Pages git-source builds read bindings from that file, and a build without the block silently resets the binding to empty.
|
|
468
463
|
|
|
469
464
|
**Empty-DB gotcha:** the platform provisions an **empty** database — schema
|
|
470
465
|
and migrations are the app's job. The usual pattern is idempotent
|
|
@@ -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.57.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.57.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -162,15 +162,15 @@ Still shipped, for apps whose TF is the shared-root `<slug>.tf` +
|
|
|
162
162
|
before (AC8). If tf-apply fails, the repo is untouched and
|
|
163
163
|
lifecycle moves to `destroy_failed`.
|
|
164
164
|
|
|
165
|
-
##
|
|
165
|
+
## D1 export and recovery window
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
platform-
|
|
167
|
+
The bot owns D1 databases and declared cron Workers that Terraform cannot remove.
|
|
168
|
+
After Terraform succeeds, destroy exports the recorded D1 UUID to the private `mkopa-launchpad-registry-snapshots-prod` R2 bucket, verifies the stored SQL object, deletes the D1 and Workers, then archive-renames the repo.
|
|
169
|
+
The deterministic object key is `destroy-exports/<slug>/<uuid>.sql` and the bucket lifecycle retains it for 30 days.
|
|
170
|
+
|
|
171
|
+
Unknown D1 identity, a failed R2 export, a failed delete, or an ownership conflict leaves the app in `destroy_failed` with the repo unarchived.
|
|
172
|
+
Never bypass that failure by deleting a D1 by name.
|
|
173
|
+
Use the platform runbook `docs/runbooks/d1-destroy-export-restore.md` to recover an accidentally destroyed database within the retention window.
|
|
174
174
|
|
|
175
175
|
## Lifecycle states
|
|
176
176
|
|
|
@@ -180,7 +180,7 @@ platform-team request, not a destroy side-effect.
|
|
|
180
180
|
| `provisioning` | Initial create still running. | Wait, then destroy. |
|
|
181
181
|
| `failed` | Provisioning failed. | Destroy works (cleans up partial state). If the app is actually live and serving, consider `launchpad recover <slug>` first — it repairs the record instead of tearing down. |
|
|
182
182
|
| `destroying` | Teardown in flight (workspace: destroy run dispatched; legacy: destroy PR open, tf-apply pending). | Re-running `launchpad destroy` is idempotent — workspace: re-dispatches the run; legacy: returns the existing PR. |
|
|
183
|
-
| `destroyed` | Teardown succeeded + repo archive-renamed. | Re-running exits 0 with `already destroyed at <ts>`.
|
|
183
|
+
| `destroyed` | Teardown succeeded + repo archive-renamed. | Re-running exits 0 with `already destroyed at <ts>`. The slug remains retired until platform recovery proves live infrastructure. |
|
|
184
184
|
| `destroy_failed` | The teardown failed or hung. | **Workspace path:** re-run `launchpad destroy` — it re-dispatches from the failure point. **Legacy path:** re-run is rejected with `platform-team intervention required` (409). On both, the app repo is **not** archive-renamed (recovery path stays clean). |
|
|
185
185
|
|
|
186
186
|
## Carve-outs
|
|
@@ -234,24 +234,22 @@ GitHub's `archived: true` flag set. **There is no `launchpad
|
|
|
234
234
|
undestroy` verb.** Recovery is operator self-service via `gh`:
|
|
235
235
|
|
|
236
236
|
```bash
|
|
237
|
-
#
|
|
237
|
+
# Unarchive, then rename back while the dated repository still exists.
|
|
238
238
|
# `gh repo rename` takes a single positional <new-name>; the OLD
|
|
239
239
|
# repo is specified via --repo (or -R).
|
|
240
|
+
gh repo unarchive M-KOPA/launchpad-app-<slug>-archived-<YYYYMMDD>
|
|
240
241
|
gh repo rename launchpad-app-<slug> \
|
|
241
242
|
--repo M-KOPA/launchpad-app-<slug>-archived-<YYYYMMDD>
|
|
242
|
-
gh repo unarchive M-KOPA/launchpad-app-<slug>
|
|
243
243
|
```
|
|
244
244
|
|
|
245
|
-
This restores the **repo**, not the Cloudflare infrastructure
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
app back online. Remember the app's D1 database survived the destroy
|
|
249
|
-
(see "What destroy does NOT remove") — a same-slug re-provision
|
|
250
|
-
re-adopts it.
|
|
245
|
+
This restores the **repo**, not the Cloudflare infrastructure.
|
|
246
|
+
The destroyed tombstone blocks `launchpad init` and `launchpad deploy` until live infrastructure is proven.
|
|
247
|
+
Follow `docs/runbooks/d1-destroy-export-restore.md`: create and seed the replacement D1, commit its new UUID in the restored app repository, restore the app's last per-app Terraform workspace through a reviewed platform PR, verify the resulting Pages deployment and gateway route, then run `launchpad recover <slug>`.
|
|
251
248
|
|
|
252
|
-
**
|
|
253
|
-
the
|
|
254
|
-
|
|
249
|
+
**Recovery has two independent horizons.**
|
|
250
|
+
The configured R2 lifecycle expires the D1 SQL export after 30 days and is the authoritative database recovery limit.
|
|
251
|
+
The archived GitHub repository follows the organisation's separate retention policy.
|
|
252
|
+
Start either recovery within hours, not weeks.
|
|
255
253
|
|
|
256
254
|
## Exit codes
|
|
257
255
|
|
|
@@ -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.57.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.57.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.57.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: Safely refresh a Launchpad clone, show manifest drift, or read the deployed manifest. Wraps bare `launchpad pull` for source sync, `launchpad pull --manifest` for deployed YAML, and `launchpad status` for drift. Use for "sync my clone", "what's deployed", "show drift", "/launchpad-status", "/launchpad-pull", or post-deploy verification.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.57.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|