@m-kopa/launchpad-cli 0.38.1 → 0.40.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 +47 -0
- package/dist/cli.js +274 -19
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/redeploy.d.ts +3 -3
- package/dist/commands/redeploy.d.ts.map +1 -1
- package/dist/deploy/commit-match-settle.d.ts +77 -0
- package/dist/deploy/commit-match-settle.d.ts.map +1 -0
- package/dist/deploy/redact-diagnostic.d.ts +6 -0
- package/dist/deploy/redact-diagnostic.d.ts.map +1 -0
- package/dist/deploy/verify-deploy.d.ts +30 -2
- package/dist/deploy/verify-deploy.d.ts.map +1 -1
- package/dist/http/api-client.d.ts.map +1 -1
- package/dist/report/classify-fault.d.ts +28 -0
- package/dist/report/classify-fault.d.ts.map +1 -0
- package/dist/report/fault-signal.d.ts +23 -0
- package/dist/report/fault-signal.d.ts.map +1 -0
- package/dist/report/report-nudge.d.ts +33 -0
- package/dist/report/report-nudge.d.ts.map +1 -0
- 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 +16 -1
- package/skills/launchpad-deploy-status/SKILL.md +14 -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
|
@@ -6,6 +6,53 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
|
6
6
|
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html);
|
|
7
7
|
pre-1.0 minor bumps may carry breaking changes per ADR 0005.
|
|
8
8
|
|
|
9
|
+
## 0.40.0 — 2026-06-21
|
|
10
|
+
|
|
11
|
+
Feature (sp-rptndg, PS-1601): `launchpad` now nudges you toward `launchpad bug`
|
|
12
|
+
when a command fails because the **platform** misbehaved — so the reporting verbs
|
|
13
|
+
are discoverable at the moment they're needed, not only to people who already
|
|
14
|
+
know they exist.
|
|
15
|
+
|
|
16
|
+
- A single one-line, **non-actionable** reminder prints (stderr) on platform
|
|
17
|
+
faults only: a bot 5xx, a network error, `✗ NO NEW DEPLOYMENT WAS CREATED`
|
|
18
|
+
(exit 69), or an unexpected crash. Classified by the typed HTTP error, **not**
|
|
19
|
+
an exit-code allowlist — the common platform faults (5xx / network) are caught
|
|
20
|
+
and returned as exit 1 inside commands, so an exit-code check would miss them.
|
|
21
|
+
- Stays **quiet** for your-side failures (auth/401, forbidden/403,
|
|
22
|
+
not-found/404, `ApiError` 4xx, bad input, usage errors) and on non-interactive
|
|
23
|
+
output (CI, pipes, `--json`).
|
|
24
|
+
- It **never files anything** — filing still shows you the report and asks you to
|
|
25
|
+
confirm first; the report carries only the last command name, never its args.
|
|
26
|
+
- The `launchpad-deploy` and `launchpad-deploy-status` skills now point an agent
|
|
27
|
+
at `/launchpad-report` on a platform fault, preserving "never file silently".
|
|
28
|
+
|
|
29
|
+
No portal-bot change.
|
|
30
|
+
|
|
31
|
+
## 0.39.0 — 2026-06-20
|
|
32
|
+
|
|
33
|
+
Feature (sp-bld9kq, PS-1590): `launchpad deploy` now confirms a Cloudflare
|
|
34
|
+
deployment built from the **exact commit you just pushed** actually went live —
|
|
35
|
+
not merely that *some* build succeeded. This closes a silent-failure class where
|
|
36
|
+
a commit landed but triggered **no build at all** (e.g. the app's
|
|
37
|
+
Cloudflare↔GitHub connection was broken) and the deploy reported a false green
|
|
38
|
+
against the stale, still-serving old build (the ai-audit incident).
|
|
39
|
+
|
|
40
|
+
- Snapshots the live deployment **before** committing, then requires the live
|
|
41
|
+
deployment to advance to your commit's SHA before reporting success.
|
|
42
|
+
- New outcome **`✗ NO NEW DEPLOYMENT WAS CREATED for your commit.`** (exit `69`)
|
|
43
|
+
when no deployment registers for your commit within the registration window —
|
|
44
|
+
it can't be self-fixed from the CLI (needs a platform admin); the message
|
|
45
|
+
routes you to `launchpad status` + `launchpad bug`.
|
|
46
|
+
- Two new soft (exit `0`) outcomes: *no deployment registered yet* (slow queue /
|
|
47
|
+
short timeout) and *superseded* (a concurrent deploy of a different commit
|
|
48
|
+
went live).
|
|
49
|
+
- New `--registration-bound=<seconds>` flag (default `120`) to extend the wait
|
|
50
|
+
for slow-to-register builds.
|
|
51
|
+
- Build-failure diagnostics (`logExcerpt`) are now **bounded and redacted** —
|
|
52
|
+
Cloudflare build logs can echo secret values.
|
|
53
|
+
- `launchpad redeploy` keeps its existing verification (it rebuilds the current
|
|
54
|
+
commit, so commit-SHA matching doesn't apply) — unchanged.
|
|
55
|
+
|
|
9
56
|
## 0.38.1 — 2026-06-19
|
|
10
57
|
|
|
11
58
|
Fix (sp-rpt9kd): `launchpad bug` / `launchpad feature` no longer truncate an
|
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.40.0";
|
|
23
23
|
|
|
24
24
|
// src/config.ts
|
|
25
25
|
import * as os from "node:os";
|
|
@@ -75,6 +75,28 @@ class TransportError extends Error {
|
|
|
75
75
|
code = "transport_error";
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
// src/report/fault-signal.ts
|
|
79
|
+
var last = null;
|
|
80
|
+
function recordFault(err) {
|
|
81
|
+
if (err === null || typeof err !== "object")
|
|
82
|
+
return;
|
|
83
|
+
const code = err.code;
|
|
84
|
+
if (typeof code !== "string")
|
|
85
|
+
return;
|
|
86
|
+
const status = err.status;
|
|
87
|
+
last = typeof status === "number" ? { code, status } : { code };
|
|
88
|
+
}
|
|
89
|
+
function recordedFault(err) {
|
|
90
|
+
recordFault(err);
|
|
91
|
+
return err;
|
|
92
|
+
}
|
|
93
|
+
function clearFault() {
|
|
94
|
+
last = null;
|
|
95
|
+
}
|
|
96
|
+
function peekFault() {
|
|
97
|
+
return last;
|
|
98
|
+
}
|
|
99
|
+
|
|
78
100
|
// src/auth/flow.ts
|
|
79
101
|
import { randomBytes as randomBytes4 } from "node:crypto";
|
|
80
102
|
|
|
@@ -838,7 +860,7 @@ async function apiJson(cfg, opts, fetcher = fetch) {
|
|
|
838
860
|
try {
|
|
839
861
|
parsed = await res.json();
|
|
840
862
|
} catch (e) {
|
|
841
|
-
throw new TransportError(`bot returned non-JSON body for ${opts.path}: ${describe7(e)}`);
|
|
863
|
+
throw recordedFault(new TransportError(`bot returned non-JSON body for ${opts.path}: ${describe7(e)}`));
|
|
842
864
|
}
|
|
843
865
|
return parsed;
|
|
844
866
|
}
|
|
@@ -880,27 +902,28 @@ async function apiRaw(cfg, opts, fetcher = fetch) {
|
|
|
880
902
|
try {
|
|
881
903
|
res = await fetcher(url, init);
|
|
882
904
|
} catch (e) {
|
|
883
|
-
throw new TransportError(`network error calling ${url}: ${describe7(e)}`);
|
|
905
|
+
throw recordedFault(new TransportError(`network error calling ${url}: ${describe7(e)}`));
|
|
884
906
|
}
|
|
885
907
|
if (res.status === 401) {
|
|
886
908
|
const detail = await peek(res);
|
|
887
|
-
throw new UnauthenticatedError(`bot returned 401 for ${opts.path}: ${detail} — run \`launchpad login\``);
|
|
909
|
+
throw recordedFault(new UnauthenticatedError(`bot returned 401 for ${opts.path}: ${detail} — run \`launchpad login\``));
|
|
888
910
|
}
|
|
889
911
|
if (res.status === 403) {
|
|
890
912
|
const detail = await peek(res);
|
|
891
|
-
throw new ForbiddenError(`bot returned 403 for ${opts.path}: ${detail}`);
|
|
913
|
+
throw recordedFault(new ForbiddenError(`bot returned 403 for ${opts.path}: ${detail}`));
|
|
892
914
|
}
|
|
893
915
|
if (res.status === 404) {
|
|
894
916
|
const detail = await peek(res);
|
|
895
|
-
throw new NotFoundError(`bot returned 404 for ${opts.path}: ${detail}`);
|
|
917
|
+
throw recordedFault(new NotFoundError(`bot returned 404 for ${opts.path}: ${detail}`));
|
|
896
918
|
}
|
|
897
919
|
if (!res.ok) {
|
|
898
920
|
if (opts.nonThrowingStatuses?.includes(res.status) === true) {
|
|
899
921
|
return res;
|
|
900
922
|
}
|
|
901
923
|
const detail = await peek(res);
|
|
902
|
-
throw new ApiError(`bot returned HTTP ${res.status} for ${opts.path}: ${detail}`, res.status);
|
|
924
|
+
throw recordedFault(new ApiError(`bot returned HTTP ${res.status} for ${opts.path}: ${detail}`, res.status));
|
|
903
925
|
}
|
|
926
|
+
clearFault();
|
|
904
927
|
return res;
|
|
905
928
|
}
|
|
906
929
|
async function peek(res) {
|
|
@@ -3657,8 +3680,83 @@ async function fetchDeploymentStatus(cfg, slug, fetcher = fetch) {
|
|
|
3657
3680
|
};
|
|
3658
3681
|
}
|
|
3659
3682
|
|
|
3660
|
-
// src/deploy/
|
|
3683
|
+
// src/deploy/commit-match-settle.ts
|
|
3684
|
+
var DEFAULT_REGISTRATION_BOUND_MS = 120000;
|
|
3661
3685
|
var DEFAULT_POLL_INTERVAL_MS = 5000;
|
|
3686
|
+
function realCommitWaitDeps(cfg) {
|
|
3687
|
+
return {
|
|
3688
|
+
fetchStatus: (slug) => fetchDeploymentStatus(cfg, slug),
|
|
3689
|
+
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
3690
|
+
now: () => Date.now()
|
|
3691
|
+
};
|
|
3692
|
+
}
|
|
3693
|
+
function shaMatches(a, b) {
|
|
3694
|
+
if (!a || !b)
|
|
3695
|
+
return false;
|
|
3696
|
+
const x = a.toLowerCase();
|
|
3697
|
+
const y = b.toLowerCase();
|
|
3698
|
+
const n = Math.min(x.length, y.length);
|
|
3699
|
+
if (n < 7)
|
|
3700
|
+
return x === y;
|
|
3701
|
+
return x.slice(0, n) === y.slice(0, n);
|
|
3702
|
+
}
|
|
3703
|
+
async function readDeploymentBaseline(deps, slug) {
|
|
3704
|
+
let status;
|
|
3705
|
+
try {
|
|
3706
|
+
status = await deps.fetchStatus(slug);
|
|
3707
|
+
} catch {
|
|
3708
|
+
return null;
|
|
3709
|
+
}
|
|
3710
|
+
if (status === null || !status.supported || status.liveDeployment === null)
|
|
3711
|
+
return null;
|
|
3712
|
+
const live = status.liveDeployment;
|
|
3713
|
+
return {
|
|
3714
|
+
id: live.id,
|
|
3715
|
+
commitHash: live.commit?.hash ?? null,
|
|
3716
|
+
createdOn: live.createdOn
|
|
3717
|
+
};
|
|
3718
|
+
}
|
|
3719
|
+
async function waitForCommitDeployment(args, deps) {
|
|
3720
|
+
const pollIntervalMs = args.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
3721
|
+
const start = deps.now();
|
|
3722
|
+
const baselineId = args.baseline?.id ?? null;
|
|
3723
|
+
let matchedInProgress = false;
|
|
3724
|
+
for (;; ) {
|
|
3725
|
+
let status;
|
|
3726
|
+
try {
|
|
3727
|
+
status = await deps.fetchStatus(args.slug);
|
|
3728
|
+
} catch {
|
|
3729
|
+
return { kind: "unknown" };
|
|
3730
|
+
}
|
|
3731
|
+
if (status === null)
|
|
3732
|
+
return { kind: "unknown" };
|
|
3733
|
+
if (!status.supported)
|
|
3734
|
+
return { kind: "unsupported" };
|
|
3735
|
+
const live = status.liveDeployment;
|
|
3736
|
+
const elapsed = deps.now() - start;
|
|
3737
|
+
if (live !== null && shaMatches(live.commit?.hash, args.shippedSha)) {
|
|
3738
|
+
if (live.buildStatus === "success") {
|
|
3739
|
+
return { kind: "success", deploymentUrl: live.url };
|
|
3740
|
+
}
|
|
3741
|
+
if (live.buildStatus === "failure") {
|
|
3742
|
+
return { kind: "failure", logExcerpt: live.logExcerpt, failedStage: live.failedStage };
|
|
3743
|
+
}
|
|
3744
|
+
matchedInProgress = true;
|
|
3745
|
+
} else if (args.baseline !== null && live !== null && live.id !== baselineId) {
|
|
3746
|
+
return { kind: "superseded" };
|
|
3747
|
+
} else {
|
|
3748
|
+
if (elapsed >= args.registrationBoundMs)
|
|
3749
|
+
return { kind: "no-new-deployment" };
|
|
3750
|
+
}
|
|
3751
|
+
if (elapsed >= args.settleTimeoutMs) {
|
|
3752
|
+
return matchedInProgress ? { kind: "pending" } : { kind: "not-registered-yet" };
|
|
3753
|
+
}
|
|
3754
|
+
await deps.sleep(pollIntervalMs);
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3757
|
+
|
|
3758
|
+
// src/deploy/wait-for-deployment.ts
|
|
3759
|
+
var DEFAULT_POLL_INTERVAL_MS2 = 5000;
|
|
3662
3760
|
function realWaitDeps(cfg) {
|
|
3663
3761
|
return {
|
|
3664
3762
|
fetchStatus: (slug) => fetchDeploymentStatus(cfg, slug),
|
|
@@ -3667,7 +3765,7 @@ function realWaitDeps(cfg) {
|
|
|
3667
3765
|
};
|
|
3668
3766
|
}
|
|
3669
3767
|
async function waitForDeployment(args, deps) {
|
|
3670
|
-
const pollIntervalMs = args.pollIntervalMs ??
|
|
3768
|
+
const pollIntervalMs = args.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS2;
|
|
3671
3769
|
const start = deps.now();
|
|
3672
3770
|
for (;; ) {
|
|
3673
3771
|
let status;
|
|
@@ -3746,6 +3844,21 @@ async function verifyServed(args, deps = { fetch }) {
|
|
|
3746
3844
|
};
|
|
3747
3845
|
}
|
|
3748
3846
|
|
|
3847
|
+
// src/deploy/redact-diagnostic.ts
|
|
3848
|
+
var MAX_DIAGNOSTIC_LEN = 600;
|
|
3849
|
+
var SECRET_KV = /\b(secret|secrets?|token|api[_-]?key|access[_-]?key|secret[_-]?key|private[_-]?key|password|passwd|pwd|authorization|auth[_-]?token|bearer|client[_-]?secret|session)\b(\s*[=:]\s*|\s+)(\S+)/gi;
|
|
3850
|
+
var LONG_TOKEN = /[A-Za-z0-9_\-+/.]{28,}={0,2}/g;
|
|
3851
|
+
function redactDiagnostic(text, maxLen = MAX_DIAGNOSTIC_LEN) {
|
|
3852
|
+
if (text === undefined || text === null)
|
|
3853
|
+
return "";
|
|
3854
|
+
let t = text.replace(SECRET_KV, (_m, key, sep2) => `${key}${sep2}***`);
|
|
3855
|
+
t = t.replace(LONG_TOKEN, "***");
|
|
3856
|
+
t = t.trim();
|
|
3857
|
+
if (t.length > maxLen)
|
|
3858
|
+
t = `${t.slice(0, maxLen - 1)}…`;
|
|
3859
|
+
return t;
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3749
3862
|
// src/commands/status.ts
|
|
3750
3863
|
import { readFileSync as readFileSync6 } from "node:fs";
|
|
3751
3864
|
|
|
@@ -4924,6 +5037,7 @@ function isEnoent(e) {
|
|
|
4924
5037
|
|
|
4925
5038
|
// src/deploy/verify-deploy.ts
|
|
4926
5039
|
var VERIFY_MISMATCH_EXIT = 65;
|
|
5040
|
+
var NO_DEPLOYMENT_EXIT = 69;
|
|
4927
5041
|
function readLocalEntrypoint(cwd) {
|
|
4928
5042
|
for (const rel of ["index.html", join8("static", "index.html")]) {
|
|
4929
5043
|
const p = join8(cwd, rel);
|
|
@@ -4933,6 +5047,70 @@ function readLocalEntrypoint(cwd) {
|
|
|
4933
5047
|
return null;
|
|
4934
5048
|
}
|
|
4935
5049
|
function realVerifyDeployDeps(cfg) {
|
|
5050
|
+
return {
|
|
5051
|
+
wait: waitForCommitDeployment,
|
|
5052
|
+
verify: verifyServed,
|
|
5053
|
+
readLocalEntrypoint,
|
|
5054
|
+
waitDeps: realCommitWaitDeps(cfg),
|
|
5055
|
+
verifyDeps: { fetch }
|
|
5056
|
+
};
|
|
5057
|
+
}
|
|
5058
|
+
async function runDeployVerification(args, io, deps) {
|
|
5059
|
+
io.out("");
|
|
5060
|
+
io.out("Verifying a build of your commit went live …");
|
|
5061
|
+
const settle = await deps.wait({
|
|
5062
|
+
slug: args.slug,
|
|
5063
|
+
shippedSha: args.shippedSha,
|
|
5064
|
+
baseline: args.baseline,
|
|
5065
|
+
registrationBoundMs: args.registrationBoundMs,
|
|
5066
|
+
settleTimeoutMs: args.timeoutMs
|
|
5067
|
+
}, deps.waitDeps);
|
|
5068
|
+
switch (settle.kind) {
|
|
5069
|
+
case "unsupported":
|
|
5070
|
+
io.out(" (skipped — this app has no Pages deployment surface)");
|
|
5071
|
+
return 0;
|
|
5072
|
+
case "unknown":
|
|
5073
|
+
io.out(` (couldn't read deployment status — verify manually with \`launchpad status ${args.slug}\`)`);
|
|
5074
|
+
return 0;
|
|
5075
|
+
case "no-new-deployment":
|
|
5076
|
+
io.err("");
|
|
5077
|
+
io.err("✗ NO NEW DEPLOYMENT WAS CREATED for your commit.");
|
|
5078
|
+
io.err(" Your content committed, but no new deployment was created for it");
|
|
5079
|
+
io.err(` within ${Math.round(args.registrationBoundMs / 1000)}s — the Pages build may not have run at all.`);
|
|
5080
|
+
io.err(" This usually means the app's build integration needs attention from a");
|
|
5081
|
+
io.err(" platform admin — it can't be fixed from the CLI.");
|
|
5082
|
+
io.err(` Confirm the live state with \`launchpad status ${args.slug}\`, and report it`);
|
|
5083
|
+
io.err(" with `launchpad bug` if it persists.");
|
|
5084
|
+
return NO_DEPLOYMENT_EXIT;
|
|
5085
|
+
case "superseded":
|
|
5086
|
+
io.out("⚠ Couldn't confirm your commit went live — a newer deployment for a different");
|
|
5087
|
+
io.out(` commit is now the latest. If ${args.slug} was deployed by someone else too, that's`);
|
|
5088
|
+
io.out(` expected. Re-check: \`launchpad status ${args.slug}\``);
|
|
5089
|
+
return 0;
|
|
5090
|
+
case "not-registered-yet":
|
|
5091
|
+
io.out("⏳ No deployment for your commit has registered yet — couldn't verify in time.");
|
|
5092
|
+
io.out(` It may still be queued. Re-check the outcome: \`launchpad status ${args.slug}\``);
|
|
5093
|
+
return 0;
|
|
5094
|
+
case "pending":
|
|
5095
|
+
io.out(`⏳ Your commit's build is still in progress after ${Math.round(args.timeoutMs / 1000)}s — couldn't verify yet.`);
|
|
5096
|
+
io.out(` Re-check the outcome: \`launchpad status ${args.slug}\``);
|
|
5097
|
+
return 0;
|
|
5098
|
+
case "failure":
|
|
5099
|
+
io.err("✗ The Cloudflare Pages build for your commit FAILED — your content is NOT live.");
|
|
5100
|
+
if (settle.failedStage)
|
|
5101
|
+
io.err(` failed stage: ${redactDiagnostic(settle.failedStage)}`);
|
|
5102
|
+
if (settle.logExcerpt) {
|
|
5103
|
+
const safe = redactDiagnostic(settle.logExcerpt);
|
|
5104
|
+
if (safe)
|
|
5105
|
+
io.err(` ${safe}`);
|
|
5106
|
+
}
|
|
5107
|
+
io.err(` Full logs: \`launchpad status ${args.slug}\``);
|
|
5108
|
+
return 1;
|
|
5109
|
+
case "success":
|
|
5110
|
+
return verifySuccess(args, settle.deploymentUrl, io, deps);
|
|
5111
|
+
}
|
|
5112
|
+
}
|
|
5113
|
+
function realRedeployVerifyDeps(cfg) {
|
|
4936
5114
|
return {
|
|
4937
5115
|
wait: waitForDeployment,
|
|
4938
5116
|
verify: verifyServed,
|
|
@@ -4941,7 +5119,7 @@ function realVerifyDeployDeps(cfg) {
|
|
|
4941
5119
|
verifyDeps: { fetch }
|
|
4942
5120
|
};
|
|
4943
5121
|
}
|
|
4944
|
-
async function
|
|
5122
|
+
async function runRedeployVerification(args, io, deps) {
|
|
4945
5123
|
io.out("");
|
|
4946
5124
|
io.out("Verifying the live deployment serves what you shipped …");
|
|
4947
5125
|
const settle = await deps.wait({ slug: args.slug, timeoutMs: args.timeoutMs }, deps.waitDeps);
|
|
@@ -4959,9 +5137,12 @@ async function runDeployVerification(args, io, deps) {
|
|
|
4959
5137
|
case "failure":
|
|
4960
5138
|
io.err("✗ The Cloudflare Pages build FAILED — your content is NOT live.");
|
|
4961
5139
|
if (settle.failedStage)
|
|
4962
|
-
io.err(` failed stage: ${settle.failedStage}`);
|
|
4963
|
-
if (settle.logExcerpt)
|
|
4964
|
-
|
|
5140
|
+
io.err(` failed stage: ${redactDiagnostic(settle.failedStage)}`);
|
|
5141
|
+
if (settle.logExcerpt) {
|
|
5142
|
+
const safe = redactDiagnostic(settle.logExcerpt);
|
|
5143
|
+
if (safe)
|
|
5144
|
+
io.err(` ${safe}`);
|
|
5145
|
+
}
|
|
4965
5146
|
io.err(` Full logs: \`launchpad status ${args.slug}\``);
|
|
4966
5147
|
return 1;
|
|
4967
5148
|
case "success":
|
|
@@ -5718,13 +5899,13 @@ async function pollUntilApplied(args) {
|
|
|
5718
5899
|
continue;
|
|
5719
5900
|
}
|
|
5720
5901
|
const prGone = state.openPr === null || state.openPr.number !== args.prNumber;
|
|
5721
|
-
const
|
|
5722
|
-
if (prGone &&
|
|
5902
|
+
const shaMatches2 = args.manifestSha === null ? state.hasAppFile && state.lastAppliedManifestSha !== null : state.lastAppliedManifestSha === args.manifestSha;
|
|
5903
|
+
if (prGone && shaMatches2) {
|
|
5723
5904
|
if (dotsThisLine > 0)
|
|
5724
5905
|
args.io.out("");
|
|
5725
5906
|
return { kind: "applied" };
|
|
5726
5907
|
}
|
|
5727
|
-
if (prGone && !
|
|
5908
|
+
if (prGone && !shaMatches2 && lastPrSeen) {
|
|
5728
5909
|
if (dotsThisLine > 0)
|
|
5729
5910
|
args.io.out("");
|
|
5730
5911
|
return {
|
|
@@ -6533,6 +6714,13 @@ async function runModelADeploy(args) {
|
|
|
6533
6714
|
provisionTimeoutMs = secs * 1000;
|
|
6534
6715
|
}
|
|
6535
6716
|
}
|
|
6717
|
+
let registrationBoundMs = DEFAULT_REGISTRATION_BOUND_MS;
|
|
6718
|
+
const rbFlag = args.argv.find((a) => a.startsWith("--registration-bound="));
|
|
6719
|
+
if (rbFlag !== undefined) {
|
|
6720
|
+
const secs = Number.parseInt(rbFlag.slice("--registration-bound=".length), 10);
|
|
6721
|
+
if (Number.isFinite(secs) && secs > 0)
|
|
6722
|
+
registrationBoundMs = secs * 1000;
|
|
6723
|
+
}
|
|
6536
6724
|
if (!SLUG_RE5.test(slug)) {
|
|
6537
6725
|
io.err(`launchpad deploy: invalid slug "${slug}" in manifest — expected ${SLUG_RE5.source}`);
|
|
6538
6726
|
return 64;
|
|
@@ -6545,6 +6733,7 @@ async function runModelADeploy(args) {
|
|
|
6545
6733
|
io.err(`launchpad deploy: ${describe14(e)}`);
|
|
6546
6734
|
return 1;
|
|
6547
6735
|
}
|
|
6736
|
+
const deployBaseline = noVerify ? null : await readDeploymentBaseline(realCommitWaitDeps(cfg), slug);
|
|
6548
6737
|
let result;
|
|
6549
6738
|
try {
|
|
6550
6739
|
result = await bundleAndDeploy({
|
|
@@ -6684,7 +6873,15 @@ async function runModelADeploy(args) {
|
|
|
6684
6873
|
io.out(`Run \`launchpad status ${slug}\` to confirm the build outcome (success / failure + log excerpt).`);
|
|
6685
6874
|
return 0;
|
|
6686
6875
|
}
|
|
6687
|
-
return runDeployVerification({
|
|
6876
|
+
return runDeployVerification({
|
|
6877
|
+
slug,
|
|
6878
|
+
cwd,
|
|
6879
|
+
appType,
|
|
6880
|
+
shippedSha: success.commit_sha,
|
|
6881
|
+
baseline: deployBaseline,
|
|
6882
|
+
registrationBoundMs,
|
|
6883
|
+
timeoutMs: verifyTimeoutMs
|
|
6884
|
+
}, io, realVerifyDeployDeps(cfg));
|
|
6688
6885
|
}
|
|
6689
6886
|
}
|
|
6690
6887
|
}
|
|
@@ -6762,8 +6959,8 @@ async function runRedeploy(opts, io, deps = {}) {
|
|
|
6762
6959
|
io.out("re-runs the build. The check below is reachability-only (it can't byte-compare a");
|
|
6763
6960
|
io.out("transformed bundle) — confirm the app still behaves as expected after it completes.");
|
|
6764
6961
|
}
|
|
6765
|
-
const verify = deps.runVerification ??
|
|
6766
|
-
const verifyDeps = deps.verifyDeps ??
|
|
6962
|
+
const verify = deps.runVerification ?? runRedeployVerification;
|
|
6963
|
+
const verifyDeps = deps.verifyDeps ?? realRedeployVerifyDeps(cfg);
|
|
6767
6964
|
return verify({ slug, cwd, appType, timeoutMs: opts.verifyTimeoutMs }, io, verifyDeps);
|
|
6768
6965
|
}
|
|
6769
6966
|
function readLocalAppType(cwd, file) {
|
|
@@ -13546,6 +13743,52 @@ function printHelp6(io, commands) {
|
|
|
13546
13743
|
io.out("silent until the grant session expires.");
|
|
13547
13744
|
}
|
|
13548
13745
|
|
|
13746
|
+
// src/report/classify-fault.ts
|
|
13747
|
+
var PLATFORM_FAULT_EXIT_CODES = [69];
|
|
13748
|
+
function classifyFault(input3) {
|
|
13749
|
+
if (input3.exitCode === 0)
|
|
13750
|
+
return false;
|
|
13751
|
+
if (PLATFORM_FAULT_EXIT_CODES.includes(input3.exitCode))
|
|
13752
|
+
return true;
|
|
13753
|
+
const f = input3.lastFault;
|
|
13754
|
+
if (f === null)
|
|
13755
|
+
return false;
|
|
13756
|
+
if (f.code === "transport_error")
|
|
13757
|
+
return true;
|
|
13758
|
+
if (f.code === "api_error")
|
|
13759
|
+
return (f.status ?? 0) >= 500;
|
|
13760
|
+
return false;
|
|
13761
|
+
}
|
|
13762
|
+
|
|
13763
|
+
// src/report/report-nudge.ts
|
|
13764
|
+
function nudgeSuppressed(ctx) {
|
|
13765
|
+
if (ctx.env.CI)
|
|
13766
|
+
return true;
|
|
13767
|
+
if (!ctx.stderrIsTTY)
|
|
13768
|
+
return true;
|
|
13769
|
+
if (ctx.argv.includes("--json"))
|
|
13770
|
+
return true;
|
|
13771
|
+
const verb = ctx.argv[0];
|
|
13772
|
+
if (verb === "bug" || verb === "feature")
|
|
13773
|
+
return true;
|
|
13774
|
+
return false;
|
|
13775
|
+
}
|
|
13776
|
+
function printNudge(io) {
|
|
13777
|
+
io.err('This looks like a platform issue, not something you did — you can report it with `launchpad bug "<what you were trying to do>"` (the last command is attached automatically; don\'t paste secrets).');
|
|
13778
|
+
}
|
|
13779
|
+
function maybeReportNudge(io, ctx) {
|
|
13780
|
+
if (nudgeSuppressed(ctx))
|
|
13781
|
+
return;
|
|
13782
|
+
if (!classifyFault({ exitCode: ctx.exitCode, lastFault: ctx.lastFault }))
|
|
13783
|
+
return;
|
|
13784
|
+
printNudge(io);
|
|
13785
|
+
}
|
|
13786
|
+
function maybeCrashReportNudge(io, ctx) {
|
|
13787
|
+
if (nudgeSuppressed(ctx))
|
|
13788
|
+
return;
|
|
13789
|
+
printNudge(io);
|
|
13790
|
+
}
|
|
13791
|
+
|
|
13549
13792
|
// src/cli.ts
|
|
13550
13793
|
var io = {
|
|
13551
13794
|
out: (line) => {
|
|
@@ -13563,6 +13806,13 @@ dispatch(args, io).then((code) => {
|
|
|
13563
13806
|
const elapsedMs = Date.now() - startedAt;
|
|
13564
13807
|
process.exitCode = code;
|
|
13565
13808
|
notifyAfterCommand(io, args);
|
|
13809
|
+
maybeReportNudge(io, {
|
|
13810
|
+
argv: args,
|
|
13811
|
+
env: process.env,
|
|
13812
|
+
stderrIsTTY: Boolean(process.stderr.isTTY),
|
|
13813
|
+
exitCode: code,
|
|
13814
|
+
lastFault: peekFault()
|
|
13815
|
+
});
|
|
13566
13816
|
maybeFirstRunNoticeReal(io, args);
|
|
13567
13817
|
recordAfterCommandReal(args, code, elapsedMs);
|
|
13568
13818
|
}).catch((err) => {
|
|
@@ -13571,6 +13821,11 @@ dispatch(args, io).then((code) => {
|
|
|
13571
13821
|
process.stderr.write(`launchpad: unexpected error: ${msg}
|
|
13572
13822
|
`);
|
|
13573
13823
|
process.exitCode = 70;
|
|
13824
|
+
maybeCrashReportNudge(io, {
|
|
13825
|
+
argv: args,
|
|
13826
|
+
env: process.env,
|
|
13827
|
+
stderrIsTTY: Boolean(process.stderr.isTTY)
|
|
13828
|
+
});
|
|
13574
13829
|
maybeFirstRunNoticeReal(io, args);
|
|
13575
13830
|
recordAfterCommandReal(args, 70, elapsedMs);
|
|
13576
13831
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAmEA,OAAO,EAGL,4BAA4B,EAC7B,MAAM,uBAAuB,CAAC;AAM/B,OAAO,EAAa,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,KAAK,EAAS,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAKjE,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;AAEjD,eAAO,MAAM,aAAa,EAAE,OAI3B,CAAC;AAEF,UAAU,UAAU;IAClB,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAmQD;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAoBpC;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,GAAG,IAAI,CAwBpE;AA+KD,2BAA2B;AAC3B,OAAO,EAAE,4BAA4B,EAAE,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type CliConfig } from "../config.js";
|
|
2
2
|
import type { CliIo, Command, ExitCode } from "../dispatcher.js";
|
|
3
|
-
import { type
|
|
3
|
+
import { type RedeployVerifyArgs, type RedeployVerifyDeps } from "../deploy/verify-deploy.js";
|
|
4
4
|
/** Bot response from `POST /apps/<slug>/redeploy`. */
|
|
5
5
|
export interface RedeployResponse {
|
|
6
6
|
readonly slug: string;
|
|
@@ -26,8 +26,8 @@ export interface RedeployDeps {
|
|
|
26
26
|
readonly fetcher?: typeof fetch;
|
|
27
27
|
readonly cwd?: string;
|
|
28
28
|
/** Override the served-verify step (tests inject a stub). */
|
|
29
|
-
readonly runVerification?: (args:
|
|
30
|
-
readonly verifyDeps?:
|
|
29
|
+
readonly runVerification?: (args: RedeployVerifyArgs, io: CliIo, deps: RedeployVerifyDeps) => Promise<ExitCode>;
|
|
30
|
+
readonly verifyDeps?: RedeployVerifyDeps;
|
|
31
31
|
}
|
|
32
32
|
export declare const redeployCommand: Command;
|
|
33
33
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redeploy.d.ts","sourceRoot":"","sources":["../../src/commands/redeploy.ts"],"names":[],"mappings":"AAsCA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAS1D,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjE,OAAO,EAGL,KAAK,
|
|
1
|
+
{"version":3,"file":"redeploy.d.ts","sourceRoot":"","sources":["../../src/commands/redeploy.ts"],"names":[],"mappings":"AAsCA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAS1D,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEjE,OAAO,EAGL,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACxB,MAAM,4BAA4B,CAAC;AAKpC,sDAAsD;AACtD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IACnC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9C,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,oEAAoE;IACpE,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,yEAAyE;IACzE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IAChC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,6DAA6D;IAC7D,QAAQ,CAAC,eAAe,CAAC,EAAE,CACzB,IAAI,EAAE,kBAAkB,EACxB,EAAE,EAAE,KAAK,EACT,IAAI,EAAE,kBAAkB,KACrB,OAAO,CAAC,QAAQ,CAAC,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,kBAAkB,CAAC;CAC1C;AAID,eAAO,MAAM,eAAe,EAAE,OAI7B,CAAC;AAiBF;;;;GAIG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,eAAe,EACrB,EAAE,EAAE,KAAK,EACT,IAAI,GAAE,YAAiB,GACtB,OAAO,CAAC,QAAQ,CAAC,CAoFnB"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { CliConfig } from "../config.js";
|
|
2
|
+
import { type DeploymentStatusView } from "./deployment-status.js";
|
|
3
|
+
/** Snapshot of the live deployment taken BEFORE deploying, so the waiter can
|
|
4
|
+
* tell "the live deployment advanced to my commit" from "nothing changed". */
|
|
5
|
+
export interface DeploymentBaseline {
|
|
6
|
+
readonly id: string;
|
|
7
|
+
readonly commitHash: string | null;
|
|
8
|
+
readonly createdOn: string;
|
|
9
|
+
}
|
|
10
|
+
export type CommitSettleOutcome = {
|
|
11
|
+
readonly kind: "success";
|
|
12
|
+
readonly deploymentUrl: string | null;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: "failure";
|
|
15
|
+
readonly logExcerpt?: string | undefined;
|
|
16
|
+
readonly failedStage?: string | undefined;
|
|
17
|
+
} | {
|
|
18
|
+
readonly kind: "pending";
|
|
19
|
+
} | {
|
|
20
|
+
readonly kind: "no-new-deployment";
|
|
21
|
+
} | {
|
|
22
|
+
readonly kind: "superseded";
|
|
23
|
+
} | {
|
|
24
|
+
readonly kind: "not-registered-yet";
|
|
25
|
+
} | {
|
|
26
|
+
readonly kind: "unsupported";
|
|
27
|
+
} | {
|
|
28
|
+
readonly kind: "unknown";
|
|
29
|
+
};
|
|
30
|
+
export interface CommitWaitDeps {
|
|
31
|
+
/** Read the live deployment status (defaults to the real bot call). */
|
|
32
|
+
readonly fetchStatus: (slug: string) => Promise<DeploymentStatusView | null>;
|
|
33
|
+
/** Sleep between polls (injected so tests don't actually wait). */
|
|
34
|
+
readonly sleep: (ms: number) => Promise<void>;
|
|
35
|
+
/** Monotonic clock in ms (injected so tests control elapsed time). */
|
|
36
|
+
readonly now: () => number;
|
|
37
|
+
}
|
|
38
|
+
export interface CommitWaitArgs {
|
|
39
|
+
readonly slug: string;
|
|
40
|
+
/** The git SHA the deploy just pushed (the bot's `commit_sha`). */
|
|
41
|
+
readonly shippedSha: string;
|
|
42
|
+
/** The live deployment as it was BEFORE this deploy (null = first deploy). */
|
|
43
|
+
readonly baseline: DeploymentBaseline | null;
|
|
44
|
+
/** How long to wait for a build of MY commit to first REGISTER before
|
|
45
|
+
* concluding nothing fired. Must be ≤ settleTimeoutMs for the hard
|
|
46
|
+
* "no-new-deployment" verdict to be reachable. */
|
|
47
|
+
readonly registrationBoundMs: number;
|
|
48
|
+
/** Total budget; once a build of my commit registers, how long to let it
|
|
49
|
+
* settle. */
|
|
50
|
+
readonly settleTimeoutMs: number;
|
|
51
|
+
readonly pollIntervalMs?: number;
|
|
52
|
+
}
|
|
53
|
+
/** CF normally registers a github:push build within seconds; 120s is a
|
|
54
|
+
* conservative ceiling before we conclude no build fired. Overridable (a
|
|
55
|
+
* contended CF queue can be extended via the deploy flag). */
|
|
56
|
+
export declare const DEFAULT_REGISTRATION_BOUND_MS = 120000;
|
|
57
|
+
export declare function realCommitWaitDeps(cfg: CliConfig): CommitWaitDeps;
|
|
58
|
+
/**
|
|
59
|
+
* Normalised commit-SHA equality. CF's `commit_hash` and the deploy's
|
|
60
|
+
* `commit_sha` are both the full git SHA of the same commit, but be lenient to
|
|
61
|
+
* short/long forms by matching on the shorter prefix (min 7 chars, else exact).
|
|
62
|
+
*/
|
|
63
|
+
export declare function shaMatches(a: string | null | undefined, b: string | null | undefined): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Read the pre-deploy baseline: the current live deployment, if any. Returns
|
|
66
|
+
* null for first-ever deploy / container / old bot / a read hiccup — any later
|
|
67
|
+
* deployment of my commit then counts as an advance (and a transient null here
|
|
68
|
+
* never blocks the deploy).
|
|
69
|
+
*/
|
|
70
|
+
export declare function readDeploymentBaseline(deps: Pick<CommitWaitDeps, "fetchStatus">, slug: string): Promise<DeploymentBaseline | null>;
|
|
71
|
+
/**
|
|
72
|
+
* Poll until a build of `shippedSha` settles, or the live deployment proves
|
|
73
|
+
* nothing built. Never throws on a transient status read; an old bot (null)
|
|
74
|
+
* degrades to `unknown`, a non-Pages app to `unsupported`.
|
|
75
|
+
*/
|
|
76
|
+
export declare function waitForCommitDeployment(args: CommitWaitArgs, deps: CommitWaitDeps): Promise<CommitSettleOutcome>;
|
|
77
|
+
//# sourceMappingURL=commit-match-settle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commit-match-settle.d.ts","sourceRoot":"","sources":["../../src/deploy/commit-match-settle.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,EAAyB,KAAK,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAE1F;+EAC+E;AAC/E,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GACnE;IACE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C,GACD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAA;CAAE,GACtC;IAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;CAAE,GAC/B;IAAE,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAA;CAAE,GACvC;IAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAA;CAAE,GAChC;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAEjC,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,QAAQ,CAAC,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,oBAAoB,GAAG,IAAI,CAAC,CAAC;IAC7E,mEAAmE;IACnE,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,sEAAsE;IACtE,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,8EAA8E;IAC9E,QAAQ,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAC7C;;uDAEmD;IACnD,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC;kBACc;IACd,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAED;;+DAE+D;AAC/D,eAAO,MAAM,6BAA6B,SAAU,CAAC;AAIrD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,SAAS,GAAG,cAAc,CAMjE;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAO9F;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,IAAI,CAAC,cAAc,EAAE,aAAa,CAAC,EACzC,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAcpC;AAED;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,mBAAmB,CAAC,CAmD9B"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bound + redact a free-text diagnostic before display. Masks secret-shaped
|
|
3
|
+
* values and truncates to a safe length. Returns "" for empty/whitespace.
|
|
4
|
+
*/
|
|
5
|
+
export declare function redactDiagnostic(text: string | undefined | null, maxLen?: number): string;
|
|
6
|
+
//# sourceMappingURL=redact-diagnostic.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redact-diagnostic.d.ts","sourceRoot":"","sources":["../../src/deploy/redact-diagnostic.ts"],"names":[],"mappings":"AAiBA;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,EAAE,MAAM,SAAqB,GAAG,MAAM,CAOrG"}
|
|
@@ -1,28 +1,56 @@
|
|
|
1
1
|
import type { CliIo, ExitCode } from "../dispatcher.js";
|
|
2
|
+
import { type CommitSettleOutcome, type CommitWaitArgs, type CommitWaitDeps, type DeploymentBaseline } from "./commit-match-settle.js";
|
|
2
3
|
import { type SettleOutcome, type WaitArgs, type WaitDeps } from "./wait-for-deployment.js";
|
|
3
4
|
import { type VerifyVerdict, type VerifyArgs, type VerifyDeps, type VerifyAppType } from "./served-verify.js";
|
|
4
5
|
import type { CliConfig } from "../config.js";
|
|
5
6
|
import { type LifecycleView } from "../commands/status.js";
|
|
6
7
|
/** EX_DATAERR — the live deployment serves content that doesn't match what shipped. */
|
|
7
8
|
export declare const VERIFY_MISMATCH_EXIT = 65;
|
|
9
|
+
/** EX_UNAVAILABLE — the commit committed, but no Cloudflare deployment was ever
|
|
10
|
+
* created for it (the Pages build never ran — the ai-audit class). Distinct
|
|
11
|
+
* from a build that ran and failed (exit 1) or served-mismatch (65). */
|
|
12
|
+
export declare const NO_DEPLOYMENT_EXIT = 69;
|
|
8
13
|
export interface VerifyDeployArgs {
|
|
9
14
|
readonly slug: string;
|
|
10
15
|
readonly cwd: string;
|
|
11
16
|
readonly appType: VerifyAppType;
|
|
17
|
+
/** The git SHA this deploy just pushed (the bot's `commit_sha`). */
|
|
18
|
+
readonly shippedSha: string;
|
|
19
|
+
/** The live deployment as it was BEFORE this deploy (null = first/none). */
|
|
20
|
+
readonly baseline: DeploymentBaseline | null;
|
|
21
|
+
/** How long to wait for a build of this commit to register before
|
|
22
|
+
* concluding nothing fired. */
|
|
23
|
+
readonly registrationBoundMs: number;
|
|
24
|
+
/** Total settle budget once a build of this commit registers. */
|
|
12
25
|
readonly timeoutMs: number;
|
|
13
26
|
}
|
|
14
27
|
export interface VerifyDeployDeps {
|
|
15
|
-
readonly wait: (args:
|
|
28
|
+
readonly wait: (args: CommitWaitArgs, deps: CommitWaitDeps) => Promise<CommitSettleOutcome>;
|
|
16
29
|
readonly verify: (args: VerifyArgs, deps: VerifyDeps) => Promise<VerifyVerdict>;
|
|
17
30
|
/** Read the local shipped entrypoint (root `index.html`, else `static/index.html`). */
|
|
18
31
|
readonly readLocalEntrypoint: (cwd: string) => Uint8Array | null;
|
|
19
|
-
readonly waitDeps:
|
|
32
|
+
readonly waitDeps: CommitWaitDeps;
|
|
20
33
|
readonly verifyDeps: VerifyDeps;
|
|
21
34
|
}
|
|
22
35
|
/** Real entrypoint reader: prefer root `index.html`, fall back to `static/index.html`. */
|
|
23
36
|
export declare function readLocalEntrypoint(cwd: string): Uint8Array | null;
|
|
24
37
|
export declare function realVerifyDeployDeps(cfg: CliConfig): VerifyDeployDeps;
|
|
25
38
|
export declare function runDeployVerification(args: VerifyDeployArgs, io: CliIo, deps: VerifyDeployDeps): Promise<ExitCode>;
|
|
39
|
+
export interface RedeployVerifyArgs {
|
|
40
|
+
readonly slug: string;
|
|
41
|
+
readonly cwd: string;
|
|
42
|
+
readonly appType: VerifyAppType;
|
|
43
|
+
readonly timeoutMs: number;
|
|
44
|
+
}
|
|
45
|
+
export interface RedeployVerifyDeps {
|
|
46
|
+
readonly wait: (args: WaitArgs, deps: WaitDeps) => Promise<SettleOutcome>;
|
|
47
|
+
readonly verify: (args: VerifyArgs, deps: VerifyDeps) => Promise<VerifyVerdict>;
|
|
48
|
+
readonly readLocalEntrypoint: (cwd: string) => Uint8Array | null;
|
|
49
|
+
readonly waitDeps: WaitDeps;
|
|
50
|
+
readonly verifyDeps: VerifyDeps;
|
|
51
|
+
}
|
|
52
|
+
export declare function realRedeployVerifyDeps(cfg: CliConfig): RedeployVerifyDeps;
|
|
53
|
+
export declare function runRedeployVerification(args: RedeployVerifyArgs, io: CliIo, deps: RedeployVerifyDeps): Promise<ExitCode>;
|
|
26
54
|
export interface FirstDeployVerifyArgs {
|
|
27
55
|
readonly slug: string;
|
|
28
56
|
readonly cwd: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify-deploy.d.ts","sourceRoot":"","sources":["../../src/deploy/verify-deploy.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAGL,KAAK,aAAa,EAClB,KAAK,QAAQ,EACb,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,UAAU,EACf,KAAK,UAAU,EACf,KAAK,aAAa,EACnB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"verify-deploy.d.ts","sourceRoot":"","sources":["../../src/deploy/verify-deploy.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/http/api-client.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"api-client.d.ts","sourceRoot":"","sources":["../../src/http/api-client.ts"],"names":[],"mappings":"AA4BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAO9C,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,OAAO,CAAC;IAC9D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,+DAA+D;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACpD;;;;;;;OAOG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAClD;AAED;;;GAGG;AACH,wBAAsB,OAAO,CAAC,CAAC,EAC7B,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,CAAC,CAAC,CAWZ;AAED;;;;;;;GAOG;AACH,wBAAsB,MAAM,CAC1B,GAAG,EAAE,SAAS,EACd,IAAI,EAAE,iBAAiB,EACvB,OAAO,GAAE,OAAO,KAAa,GAC5B,OAAO,CAAC,QAAQ,CAAC,CA8FnB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RecordedFault } from "./fault-signal.js";
|
|
2
|
+
/**
|
|
3
|
+
* Exit codes that are themselves platform-fault signals, independent of any
|
|
4
|
+
* recorded HTTP fault. Today only 69 = no-new-deployment (sp-bld9kq): the
|
|
5
|
+
* commit committed but Cloudflare never built it.
|
|
6
|
+
*
|
|
7
|
+
* NB the unhandled-crash exit (70, EX_SOFTWARE) is NOT here — it is emitted
|
|
8
|
+
* only from `cli.ts`'s top-level `.catch`, which nudges directly; a returned
|
|
9
|
+
* code never carries it.
|
|
10
|
+
*/
|
|
11
|
+
export declare const PLATFORM_FAULT_EXIT_CODES: readonly number[];
|
|
12
|
+
export interface FaultClassifierInput {
|
|
13
|
+
readonly exitCode: number;
|
|
14
|
+
readonly lastFault: RecordedFault | null;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* True iff the failure warrants a "file a bug" nudge.
|
|
18
|
+
*
|
|
19
|
+
* - exit 0 (incl. the soft pending/superseded/not-registered-yet outcomes)
|
|
20
|
+
* is never a fault.
|
|
21
|
+
* - A platform-fault exit code (69) is a direct signal.
|
|
22
|
+
* - Otherwise classify by the last typed HTTP fault: `TransportError`
|
|
23
|
+
* (network) and `ApiError` with status >= 500 (bot/server 5xx) are platform
|
|
24
|
+
* faults; `ApiError` 4xx, 401/403/404, and any non-HTTP failure (local input,
|
|
25
|
+
* usage) are NOT.
|
|
26
|
+
*/
|
|
27
|
+
export declare function classifyFault(input: FaultClassifierInput): boolean;
|
|
28
|
+
//# sourceMappingURL=classify-fault.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"classify-fault.d.ts","sourceRoot":"","sources":["../../src/report/classify-fault.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAAS,CAAC;AAEjE,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;CAC1C;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAUlE"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** The discriminant + (for `ApiError`) status of the last typed HTTP error. */
|
|
2
|
+
export interface RecordedFault {
|
|
3
|
+
readonly code: string;
|
|
4
|
+
readonly status?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Record the last typed error. Accepts any of the `http/errors.ts` classes
|
|
8
|
+
* (all carry a string `.code`; `ApiError` also carries `.status`). A non-error
|
|
9
|
+
* or code-less value is ignored, so a stray throw can never poison the signal.
|
|
10
|
+
*/
|
|
11
|
+
export declare function recordFault(err: unknown): void;
|
|
12
|
+
/**
|
|
13
|
+
* Record then return the error — sugar for `throw recordedFault(new ApiError(…))`
|
|
14
|
+
* at the HTTP client throw sites, so recording can't be forgotten next to a throw.
|
|
15
|
+
*/
|
|
16
|
+
export declare function recordedFault<T>(err: T): T;
|
|
17
|
+
/** Clear the recorded fault — called by the HTTP client on a 2xx success. */
|
|
18
|
+
export declare function clearFault(): void;
|
|
19
|
+
/** Read the last recorded fault (null if none / cleared). */
|
|
20
|
+
export declare function peekFault(): RecordedFault | null;
|
|
21
|
+
/** Test-only: reset module state between cases. */
|
|
22
|
+
export declare function _resetFaultForTesting(): void;
|
|
23
|
+
//# sourceMappingURL=fault-signal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fault-signal.d.ts","sourceRoot":"","sources":["../../src/report/fault-signal.ts"],"names":[],"mappings":"AAgBA,+EAA+E;AAC/E,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAID;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAM9C;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAG1C;AAED,6EAA6E;AAC7E,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,6DAA6D;AAC7D,wBAAgB,SAAS,IAAI,aAAa,GAAG,IAAI,CAEhD;AAED,mDAAmD;AACnD,wBAAgB,qBAAqB,IAAI,IAAI,CAE5C"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { CliIo } from "../dispatcher.js";
|
|
2
|
+
import type { RecordedFault } from "./fault-signal.js";
|
|
3
|
+
export interface NudgeContext {
|
|
4
|
+
/** The raw command args; only argv[0] (the verb) is ever read. */
|
|
5
|
+
readonly argv: readonly string[];
|
|
6
|
+
readonly env: Record<string, string | undefined>;
|
|
7
|
+
readonly stderrIsTTY: boolean;
|
|
8
|
+
readonly exitCode: number;
|
|
9
|
+
readonly lastFault: RecordedFault | null;
|
|
10
|
+
}
|
|
11
|
+
/** Quiet in non-interactive / machine contexts, and never nudge the report
|
|
12
|
+
* verbs themselves. Mirrors update-notifier's isSuppressed. */
|
|
13
|
+
export declare function nudgeSuppressed(ctx: {
|
|
14
|
+
readonly argv: readonly string[];
|
|
15
|
+
readonly env: Record<string, string | undefined>;
|
|
16
|
+
readonly stderrIsTTY: boolean;
|
|
17
|
+
}): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Print the nudge for a finished command iff its failure classifies as a
|
|
20
|
+
* platform fault and we're on an interactive terminal. No-op otherwise.
|
|
21
|
+
*/
|
|
22
|
+
export declare function maybeReportNudge(io: CliIo, ctx: NudgeContext): void;
|
|
23
|
+
/**
|
|
24
|
+
* Print the nudge for the unhandled-crash path (cli.ts top-level `.catch`,
|
|
25
|
+
* exit 70). A crash is a platform fault by definition, so no classifier call —
|
|
26
|
+
* but still TTY/CI-gated and report-verb-skipped.
|
|
27
|
+
*/
|
|
28
|
+
export declare function maybeCrashReportNudge(io: CliIo, ctx: {
|
|
29
|
+
readonly argv: readonly string[];
|
|
30
|
+
readonly env: Record<string, string | undefined>;
|
|
31
|
+
readonly stderrIsTTY: boolean;
|
|
32
|
+
}): void;
|
|
33
|
+
//# sourceMappingURL=report-nudge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"report-nudge.d.ts","sourceRoot":"","sources":["../../src/report/report-nudge.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAE9C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAEvD,MAAM,WAAW,YAAY;IAC3B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACjD,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;CAC1C;AAED;gEACgE;AAChE,wBAAgB,eAAe,CAAC,GAAG,EAAE;IACnC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACjD,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B,GAAG,OAAO,CAOV;AAYD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,GAAG,IAAI,CAInE;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE;IACpD,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACjD,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;CAC/B,GAAG,IAAI,CAGP"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.
|
|
1
|
+
export declare const CLI_VERSION = "0.40.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.40.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.40.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -511,3 +511,18 @@ the in-flight case.
|
|
|
511
511
|
strips the user JWT and 401s every user (see "Gateway auth").
|
|
512
512
|
- Do **not** `wrangler secret put` for a two-tier app — `launchpad secrets
|
|
513
513
|
push` with `targets: all` seeds both tiers via the bot.
|
|
514
|
+
|
|
515
|
+
## When a deploy fails because the platform misbehaved
|
|
516
|
+
|
|
517
|
+
If `launchpad deploy` (or `status`) fails for a reason that is **not** the
|
|
518
|
+
user's — a bot 5xx, a network error, `✗ NO NEW DEPLOYMENT WAS CREATED` /
|
|
519
|
+
exit 69, or an unexpected crash — i.e. it looks like **our** fault, not a typo
|
|
520
|
+
or a missing permission — offer to file it via the **`/launchpad-report`** skill
|
|
521
|
+
(`launchpad bug "<what they were trying to do>"`). The CLI prints a one-line
|
|
522
|
+
nudge on these; offer proactively.
|
|
523
|
+
|
|
524
|
+
Follow `/launchpad-report`'s rules: **never file silently** — show the user the
|
|
525
|
+
exact title + body you'll send and get their confirmation first, and never paste
|
|
526
|
+
secret-bearing commands/values into the report body. Do **not** offer to file
|
|
527
|
+
for a user-side failure (auth/401, not-found/404, a bad manifest, a dirty tree) —
|
|
528
|
+
fix those instead.
|
|
@@ -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.40.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -360,3 +360,16 @@ such a link anyway.
|
|
|
360
360
|
`launchpad apps`. Use `launchpad status <slug> --json` for any
|
|
361
361
|
downstream automation (`launchpad apps` has no `--json`; for
|
|
362
362
|
per-app automation go through `status --json` instead).
|
|
363
|
+
|
|
364
|
+
## When a status check fails because the platform misbehaved
|
|
365
|
+
|
|
366
|
+
If `launchpad status` / `watch` / `recover` fails for a reason that is **not**
|
|
367
|
+
the user's — a bot 5xx, a network error, `✗ NO NEW DEPLOYMENT WAS CREATED` /
|
|
368
|
+
exit 69, or an unexpected crash — offer to file it via the
|
|
369
|
+
**`/launchpad-report`** skill (`launchpad bug "<what they were trying to do>"`).
|
|
370
|
+
The CLI prints a one-line nudge on these; offer proactively.
|
|
371
|
+
|
|
372
|
+
Follow `/launchpad-report`'s rules: **never file silently** — show the exact
|
|
373
|
+
title + body and confirm first, and never paste secret-bearing commands/values
|
|
374
|
+
into the body. Do **not** offer to file for a user-side failure (auth/401,
|
|
375
|
+
not-found/404) — resolve those instead.
|
|
@@ -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.40.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.40.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.40.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.40.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.40.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|