@m-kopa/launchpad-cli 0.42.0 → 0.43.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 +23 -0
- package/dist/cli.js +244 -11
- package/dist/commands/deploy-modes.d.ts.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/deploy/manifest-diff.d.ts +30 -0
- package/dist/deploy/manifest-diff.d.ts.map +1 -0
- package/dist/deploy/status-polling.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
|
@@ -6,6 +6,29 @@ 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.43.0 — 2026-07-05
|
|
10
|
+
|
|
11
|
+
**`launchpad deploy` now shows a pre-deploy config diff (PS-1920).** Before a
|
|
12
|
+
content deploy sends your bundle, the CLI prints a git-style, key-level diff of
|
|
13
|
+
how your local `launchpad.yaml` differs from the **currently deployed**
|
|
14
|
+
manifest — added / removed / changed keys, with `old → new` for changed
|
|
15
|
+
scalars — so a config change (intended or accidental) is visible up front. When
|
|
16
|
+
your manifest already matches what's deployed, it says so in one line.
|
|
17
|
+
|
|
18
|
+
- **Advisory only:** the diff never prompts, never blocks, and never changes the
|
|
19
|
+
deploy's exit code — a manifest edit is usually intentional. The commit
|
|
20
|
+
lost-update guard (stale-clone) and the declared-vs-enforced ACCESS DRIFT
|
|
21
|
+
warning are unchanged and stay separate; this covers the manifest-config axis
|
|
22
|
+
neither did.
|
|
23
|
+
- **Secret-safe:** values under secret-shaped keys (env-var values, tokens) are
|
|
24
|
+
masked, never printed. Redaction is applied before the diff is computed.
|
|
25
|
+
- **Fail-open:** if the deployed manifest can't be read (older bot, first deploy
|
|
26
|
+
with nothing deployed yet, transient error) the check prints a one-line
|
|
27
|
+
"skipping" note and the deploy proceeds unchanged.
|
|
28
|
+
- **In-sync check is semantic:** key order, comments, whitespace, and the
|
|
29
|
+
singular-vs-plural access-group form don't register as changes; group lists
|
|
30
|
+
diff by set membership.
|
|
31
|
+
|
|
9
32
|
## 0.42.0 — 2026-06-30
|
|
10
33
|
|
|
11
34
|
**`launchpad validate --strict-groups` and `launchpad groups resolve`/`show`
|
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.43.0";
|
|
23
23
|
|
|
24
24
|
// src/config.ts
|
|
25
25
|
import * as os from "node:os";
|
|
@@ -5562,7 +5562,7 @@ async function verifyFirstDeployServed(args, liveUrl, io, deps) {
|
|
|
5562
5562
|
}
|
|
5563
5563
|
|
|
5564
5564
|
// src/commands/deploy.ts
|
|
5565
|
-
import { parse as
|
|
5565
|
+
import { parse as parseYaml6 } from "yaml";
|
|
5566
5566
|
import { readFileSync as readFileSync10 } from "node:fs";
|
|
5567
5567
|
|
|
5568
5568
|
// src/deploy/git-files.ts
|
|
@@ -5619,6 +5619,178 @@ function runGit2(sp, cwd, args) {
|
|
|
5619
5619
|
});
|
|
5620
5620
|
}
|
|
5621
5621
|
|
|
5622
|
+
// src/deploy/manifest-diff.ts
|
|
5623
|
+
import { parse as parseYaml4 } from "yaml";
|
|
5624
|
+
var MASK = "«redacted»";
|
|
5625
|
+
var SECRET_KEY_SEGMENT = /(secret|token|api[_-]?key|access[_-]?key|private[_-]?key|secret[_-]?key|password|passwd|pwd|authorization|auth[_-]?token|bearer|client[_-]?secret|session|credential|value)/i;
|
|
5626
|
+
function isSecretPath(path8) {
|
|
5627
|
+
const last2 = path8.split(".").pop() ?? path8;
|
|
5628
|
+
return SECRET_KEY_SEGMENT.test(last2);
|
|
5629
|
+
}
|
|
5630
|
+
function normalize(v) {
|
|
5631
|
+
if (Array.isArray(v)) {
|
|
5632
|
+
const items = v.map(normalize);
|
|
5633
|
+
const allScalar = items.every((x) => x === null || typeof x !== "object");
|
|
5634
|
+
return allScalar ? [...items].sort((a, b) => String(a).localeCompare(String(b))) : items;
|
|
5635
|
+
}
|
|
5636
|
+
if (v !== null && typeof v === "object") {
|
|
5637
|
+
const obj = v;
|
|
5638
|
+
const out = {};
|
|
5639
|
+
for (const [k, val] of Object.entries(obj)) {
|
|
5640
|
+
if (k === "allowed_entra_group" && typeof val === "string") {
|
|
5641
|
+
out.allowed_entra_groups = normalize([val]);
|
|
5642
|
+
} else {
|
|
5643
|
+
out[k] = normalize(val);
|
|
5644
|
+
}
|
|
5645
|
+
}
|
|
5646
|
+
return out;
|
|
5647
|
+
}
|
|
5648
|
+
return v;
|
|
5649
|
+
}
|
|
5650
|
+
function stableStringify(v) {
|
|
5651
|
+
if (Array.isArray(v))
|
|
5652
|
+
return `[${v.map(stableStringify).join(",")}]`;
|
|
5653
|
+
if (v !== null && typeof v === "object") {
|
|
5654
|
+
const obj = v;
|
|
5655
|
+
return `{${Object.keys(obj).sort().map((k) => `${JSON.stringify(k)}:${stableStringify(obj[k])}`).join(",")}}`;
|
|
5656
|
+
}
|
|
5657
|
+
return JSON.stringify(v ?? null);
|
|
5658
|
+
}
|
|
5659
|
+
function isScalarSet(v) {
|
|
5660
|
+
return typeof v === "object" && v !== null && "__set" in v;
|
|
5661
|
+
}
|
|
5662
|
+
function flatten(v, prefix, out) {
|
|
5663
|
+
if (Array.isArray(v)) {
|
|
5664
|
+
if (v.length === 0) {
|
|
5665
|
+
out.set(prefix, "[]");
|
|
5666
|
+
return;
|
|
5667
|
+
}
|
|
5668
|
+
const allScalar = v.every((x) => x === null || typeof x !== "object");
|
|
5669
|
+
if (allScalar) {
|
|
5670
|
+
out.set(prefix, { __set: v.map((x) => String(x)) });
|
|
5671
|
+
return;
|
|
5672
|
+
}
|
|
5673
|
+
v.forEach((item, i) => flatten(item, prefix === "" ? String(i) : `${prefix}.${i}`, out));
|
|
5674
|
+
return;
|
|
5675
|
+
}
|
|
5676
|
+
if (v !== null && typeof v === "object") {
|
|
5677
|
+
const entries = Object.entries(v);
|
|
5678
|
+
if (entries.length === 0) {
|
|
5679
|
+
out.set(prefix, "{}");
|
|
5680
|
+
return;
|
|
5681
|
+
}
|
|
5682
|
+
for (const [k, val] of entries) {
|
|
5683
|
+
flatten(val, prefix === "" ? k : `${prefix}.${k}`, out);
|
|
5684
|
+
}
|
|
5685
|
+
return;
|
|
5686
|
+
}
|
|
5687
|
+
out.set(prefix, v);
|
|
5688
|
+
}
|
|
5689
|
+
function renderValue(path8, value) {
|
|
5690
|
+
if (isSecretPath(path8))
|
|
5691
|
+
return MASK;
|
|
5692
|
+
if (value === null)
|
|
5693
|
+
return "null";
|
|
5694
|
+
const raw = isScalarSet(value) ? renderScalar(value) : String(value);
|
|
5695
|
+
const scrubbed = redactDiagnostic(raw, 120);
|
|
5696
|
+
return scrubbed === "" ? MASK : scrubbed;
|
|
5697
|
+
}
|
|
5698
|
+
function renderScalar(value) {
|
|
5699
|
+
return isScalarSet(value) ? `[${[...value.__set].sort().join(",")}]` : String(value);
|
|
5700
|
+
}
|
|
5701
|
+
function pushSideChange(changes, path8, kind, value) {
|
|
5702
|
+
if (isScalarSet(value)) {
|
|
5703
|
+
for (const m of value.__set) {
|
|
5704
|
+
changes.push(kind === "added" ? { path: `${path8}[]`, kind, after: renderValue(path8, m) } : { path: `${path8}[]`, kind, before: renderValue(path8, m) });
|
|
5705
|
+
}
|
|
5706
|
+
return;
|
|
5707
|
+
}
|
|
5708
|
+
changes.push(kind === "added" ? { path: path8, kind, after: renderValue(path8, value) } : { path: path8, kind, before: renderValue(path8, value) });
|
|
5709
|
+
}
|
|
5710
|
+
function diffManifests(localYaml, deployedYaml) {
|
|
5711
|
+
let local;
|
|
5712
|
+
let deployed;
|
|
5713
|
+
try {
|
|
5714
|
+
local = normalize(parseYaml4(localYaml));
|
|
5715
|
+
} catch (e) {
|
|
5716
|
+
return {
|
|
5717
|
+
inSync: false,
|
|
5718
|
+
changes: [],
|
|
5719
|
+
parseError: `local launchpad.yaml did not parse: ${e.message}`
|
|
5720
|
+
};
|
|
5721
|
+
}
|
|
5722
|
+
try {
|
|
5723
|
+
deployed = normalize(parseYaml4(deployedYaml));
|
|
5724
|
+
} catch (e) {
|
|
5725
|
+
return {
|
|
5726
|
+
inSync: false,
|
|
5727
|
+
changes: [],
|
|
5728
|
+
parseError: `deployed manifest did not parse: ${e.message}`
|
|
5729
|
+
};
|
|
5730
|
+
}
|
|
5731
|
+
if (stableStringify(local) === stableStringify(deployed)) {
|
|
5732
|
+
return { inSync: true, changes: [], parseError: null };
|
|
5733
|
+
}
|
|
5734
|
+
const localFlat = new Map;
|
|
5735
|
+
const deployedFlat = new Map;
|
|
5736
|
+
flatten(local, "", localFlat);
|
|
5737
|
+
flatten(deployed, "", deployedFlat);
|
|
5738
|
+
const paths = [
|
|
5739
|
+
...new Set([...localFlat.keys(), ...deployedFlat.keys()])
|
|
5740
|
+
].sort();
|
|
5741
|
+
const changes = [];
|
|
5742
|
+
for (const p of paths) {
|
|
5743
|
+
const hasLocal = localFlat.has(p);
|
|
5744
|
+
const hasDeployed = deployedFlat.has(p);
|
|
5745
|
+
const localVal = localFlat.get(p);
|
|
5746
|
+
const deployedVal = deployedFlat.get(p);
|
|
5747
|
+
if (isScalarSet(localVal) && isScalarSet(deployedVal)) {
|
|
5748
|
+
const local2 = new Set(localVal.__set);
|
|
5749
|
+
const deployed2 = new Set(deployedVal.__set);
|
|
5750
|
+
for (const m of localVal.__set) {
|
|
5751
|
+
if (!deployed2.has(m)) {
|
|
5752
|
+
changes.push({ path: `${p}[]`, kind: "added", after: renderValue(p, m) });
|
|
5753
|
+
}
|
|
5754
|
+
}
|
|
5755
|
+
for (const m of deployedVal.__set) {
|
|
5756
|
+
if (!local2.has(m)) {
|
|
5757
|
+
changes.push({ path: `${p}[]`, kind: "removed", before: renderValue(p, m) });
|
|
5758
|
+
}
|
|
5759
|
+
}
|
|
5760
|
+
continue;
|
|
5761
|
+
}
|
|
5762
|
+
if (hasLocal && !hasDeployed) {
|
|
5763
|
+
pushSideChange(changes, p, "added", localVal);
|
|
5764
|
+
} else if (!hasLocal && hasDeployed) {
|
|
5765
|
+
pushSideChange(changes, p, "removed", deployedVal);
|
|
5766
|
+
} else if (renderScalar(localVal) !== renderScalar(deployedVal)) {
|
|
5767
|
+
changes.push({
|
|
5768
|
+
path: p,
|
|
5769
|
+
kind: "changed",
|
|
5770
|
+
before: renderValue(p, deployedVal),
|
|
5771
|
+
after: renderValue(p, localVal)
|
|
5772
|
+
});
|
|
5773
|
+
}
|
|
5774
|
+
}
|
|
5775
|
+
return { inSync: false, changes, parseError: null };
|
|
5776
|
+
}
|
|
5777
|
+
function renderManifestDiffLines(diff, max = 20) {
|
|
5778
|
+
const lines = [];
|
|
5779
|
+
for (const c of diff.changes.slice(0, max)) {
|
|
5780
|
+
if (c.kind === "added") {
|
|
5781
|
+
lines.push(` + ${c.path}: ${c.after}`);
|
|
5782
|
+
} else if (c.kind === "removed") {
|
|
5783
|
+
lines.push(` - ${c.path}: ${c.before}`);
|
|
5784
|
+
} else {
|
|
5785
|
+
lines.push(` ~ ${c.path}: ${c.before} → ${c.after}`);
|
|
5786
|
+
}
|
|
5787
|
+
}
|
|
5788
|
+
if (diff.changes.length > max) {
|
|
5789
|
+
lines.push(` … and ${diff.changes.length - max} more`);
|
|
5790
|
+
}
|
|
5791
|
+
return lines;
|
|
5792
|
+
}
|
|
5793
|
+
|
|
5622
5794
|
// src/commands/deploy-flags.ts
|
|
5623
5795
|
var SLUG_RE4 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
5624
5796
|
var GROUP_KEY_RE2 = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -6008,7 +6180,7 @@ import { resolve as resolvePath, join as join10 } from "node:path";
|
|
|
6008
6180
|
|
|
6009
6181
|
// src/manifest/load.ts
|
|
6010
6182
|
import { readFileSync as readFileSync9 } from "node:fs";
|
|
6011
|
-
import { parse as
|
|
6183
|
+
import { parse as parseYaml5, YAMLParseError } from "yaml";
|
|
6012
6184
|
function loadManifest(path8) {
|
|
6013
6185
|
let raw;
|
|
6014
6186
|
try {
|
|
@@ -6025,7 +6197,7 @@ function loadManifest(path8) {
|
|
|
6025
6197
|
function parseManifest2(yamlText, path8) {
|
|
6026
6198
|
let parsed;
|
|
6027
6199
|
try {
|
|
6028
|
-
parsed =
|
|
6200
|
+
parsed = parseYaml5(yamlText);
|
|
6029
6201
|
} catch (err) {
|
|
6030
6202
|
const message = err instanceof YAMLParseError ? err.message : err.message ?? String(err);
|
|
6031
6203
|
return { kind: "yaml-parse-error", path: path8, message };
|
|
@@ -6557,12 +6729,25 @@ async function runResume(slug, io) {
|
|
|
6557
6729
|
io.err(`launchpad deploy --resume: workflow at stage "${body.currentStage}" is not resumable. ${body.message}`);
|
|
6558
6730
|
return exitCodeForStage(body.currentStage);
|
|
6559
6731
|
}
|
|
6732
|
+
if (body !== null && body.error === "not_refirable") {
|
|
6733
|
+
io.err(`launchpad deploy --resume: ${body.message}`);
|
|
6734
|
+
return 1;
|
|
6735
|
+
}
|
|
6736
|
+
const msg = body?.message;
|
|
6737
|
+
if (typeof msg === "string" && msg.length > 0) {
|
|
6738
|
+
io.err(`launchpad deploy --resume: ${msg}`);
|
|
6739
|
+
return 1;
|
|
6740
|
+
}
|
|
6560
6741
|
io.err(`launchpad deploy --resume: bot returned 409 without parseable body`);
|
|
6561
6742
|
return 1;
|
|
6562
6743
|
}
|
|
6563
6744
|
const parsed = await res.json();
|
|
6564
|
-
if (parsed.refiring === true) {
|
|
6565
|
-
io.out(`
|
|
6745
|
+
if (parsed.refiring === true && parsed.alreadyRunning === true) {
|
|
6746
|
+
io.out(`A provisioning run for "${slug}" is already in flight — leaving it to finish. Watch \`launchpad status ${slug}\`.`);
|
|
6747
|
+
} else if (parsed.refiring === true) {
|
|
6748
|
+
const stageHint = parsed.fromStage !== undefined ? ` from stage "${parsed.fromStage}"` : "";
|
|
6749
|
+
const viaHint = parsed.via !== undefined ? ` (${parsed.via})` : "";
|
|
6750
|
+
io.out(`Re-driving provisioning for "${slug}"${stageHint}${viaHint}. Watch \`launchpad status ${slug}\`; every stage is idempotent, so it picks up where it left off.`);
|
|
6566
6751
|
} else if (parsed.idempotent === true) {
|
|
6567
6752
|
io.out(`Workflow already at \`done\` for "${slug}" — nothing to resume.`);
|
|
6568
6753
|
} else {
|
|
@@ -6981,12 +7166,57 @@ function surfaceStaleBlock(body, io) {
|
|
|
6981
7166
|
io.err(` launchpad deploy --override-stale ${head.slice(0, 8)}`);
|
|
6982
7167
|
}
|
|
6983
7168
|
}
|
|
7169
|
+
var MANIFEST_DIFF_READ_TIMEOUT_MS = 5000;
|
|
7170
|
+
function withTimeout(p, ms) {
|
|
7171
|
+
return new Promise((resolve8, reject) => {
|
|
7172
|
+
const timer = setTimeout(() => reject(new Error(`manifest read timed out after ${ms}ms`)), ms);
|
|
7173
|
+
p.then((v) => {
|
|
7174
|
+
clearTimeout(timer);
|
|
7175
|
+
resolve8(v);
|
|
7176
|
+
}, (e) => {
|
|
7177
|
+
clearTimeout(timer);
|
|
7178
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
7179
|
+
});
|
|
7180
|
+
});
|
|
7181
|
+
}
|
|
7182
|
+
async function surfaceManifestDiff(cfg, slug, manifestPath, io) {
|
|
7183
|
+
let localYaml;
|
|
7184
|
+
try {
|
|
7185
|
+
localYaml = readFileSync10(manifestPath, "utf8");
|
|
7186
|
+
} catch {
|
|
7187
|
+
return;
|
|
7188
|
+
}
|
|
7189
|
+
let deployedYaml;
|
|
7190
|
+
try {
|
|
7191
|
+
const state = await withTimeout(fetchManifestState(cfg, slug, { includeManifest: true }), MANIFEST_DIFF_READ_TIMEOUT_MS);
|
|
7192
|
+
deployedYaml = state.manifestYaml ?? null;
|
|
7193
|
+
} catch {
|
|
7194
|
+
io.out(" config diff: could not read the deployed manifest — skipping (does not affect this deploy).");
|
|
7195
|
+
return;
|
|
7196
|
+
}
|
|
7197
|
+
if (deployedYaml === null || deployedYaml.trim() === "") {
|
|
7198
|
+
io.out(" config diff: nothing deployed yet to compare against — skipping.");
|
|
7199
|
+
return;
|
|
7200
|
+
}
|
|
7201
|
+
const diff = diffManifests(localYaml, deployedYaml);
|
|
7202
|
+
if (diff.parseError !== null) {
|
|
7203
|
+
io.out(` config diff: skipped (${diff.parseError}).`);
|
|
7204
|
+
return;
|
|
7205
|
+
}
|
|
7206
|
+
if (diff.inSync) {
|
|
7207
|
+
io.out(" config: launchpad.yaml matches the deployed manifest (in sync).");
|
|
7208
|
+
return;
|
|
7209
|
+
}
|
|
7210
|
+
io.out(" config diff — local launchpad.yaml vs the DEPLOYED manifest (this file, not gateway enforcement):");
|
|
7211
|
+
for (const line of renderManifestDiffLines(diff))
|
|
7212
|
+
io.out(line);
|
|
7213
|
+
}
|
|
6984
7214
|
async function runModelADeploy(args) {
|
|
6985
7215
|
const { cwd, manifestPath, io } = args;
|
|
6986
7216
|
let slug;
|
|
6987
7217
|
let appType = "static";
|
|
6988
7218
|
try {
|
|
6989
|
-
const manifestObj =
|
|
7219
|
+
const manifestObj = parseYaml6(readFileSync10(manifestPath, "utf8"));
|
|
6990
7220
|
const metaSlug = resolveManifestSlug(manifestObj);
|
|
6991
7221
|
if (metaSlug === null) {
|
|
6992
7222
|
io.err(`launchpad deploy: launchpad.yaml is missing metadata.slug (v2) / metadata.name (v1). ` + `Run \`launchpad init\` again to regenerate the manifest.`);
|
|
@@ -7031,6 +7261,9 @@ async function runModelADeploy(args) {
|
|
|
7031
7261
|
io.err(`launchpad deploy: ${describe15(e)}`);
|
|
7032
7262
|
return 1;
|
|
7033
7263
|
}
|
|
7264
|
+
try {
|
|
7265
|
+
await surfaceManifestDiff(cfg, slug, manifestPath, io);
|
|
7266
|
+
} catch {}
|
|
7034
7267
|
const deployBaseline = noVerify ? null : await readDeploymentBaseline(realCommitWaitDeps(cfg), slug);
|
|
7035
7268
|
let result;
|
|
7036
7269
|
try {
|
|
@@ -7204,7 +7437,7 @@ async function runModelADeploy(args) {
|
|
|
7204
7437
|
// src/commands/redeploy.ts
|
|
7205
7438
|
import { existsSync as existsSync7, readFileSync as readFileSync11 } from "node:fs";
|
|
7206
7439
|
import { resolve as resolvePath2 } from "node:path";
|
|
7207
|
-
import { parse as
|
|
7440
|
+
import { parse as parseYaml7 } from "yaml";
|
|
7208
7441
|
var SLUG_RE6 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
7209
7442
|
var DEFAULT_VERIFY_TIMEOUT_MS = 180000;
|
|
7210
7443
|
var redeployCommand = {
|
|
@@ -7283,7 +7516,7 @@ function readLocalAppType(cwd, file) {
|
|
|
7283
7516
|
if (!existsSync7(manifestPath))
|
|
7284
7517
|
return null;
|
|
7285
7518
|
try {
|
|
7286
|
-
const obj =
|
|
7519
|
+
const obj = parseYaml7(readFileSync11(manifestPath, "utf8"));
|
|
7287
7520
|
return coerceAppType(obj?.deployment?.type);
|
|
7288
7521
|
} catch {
|
|
7289
7522
|
return null;
|
|
@@ -11230,7 +11463,7 @@ function renderManifestError5(result, io) {
|
|
|
11230
11463
|
// src/secrets/set.ts
|
|
11231
11464
|
import { existsSync as existsSync12, readFileSync as readFileSync17 } from "node:fs";
|
|
11232
11465
|
import { resolve as resolvePath6 } from "node:path";
|
|
11233
|
-
import { parse as
|
|
11466
|
+
import { parse as parseYaml8 } from "yaml";
|
|
11234
11467
|
var CELL_LABEL2 = {
|
|
11235
11468
|
present: "PRESENT",
|
|
11236
11469
|
missing: "MISSING",
|
|
@@ -11245,7 +11478,7 @@ function loadSet(fleetFile, setName, io) {
|
|
|
11245
11478
|
}
|
|
11246
11479
|
let obj;
|
|
11247
11480
|
try {
|
|
11248
|
-
obj =
|
|
11481
|
+
obj = parseYaml8(readFileSync17(path13, "utf8"));
|
|
11249
11482
|
} catch (e) {
|
|
11250
11483
|
io.err(`✗ ${path13}`);
|
|
11251
11484
|
io.err(` YAML parse error: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy-modes.d.ts","sourceRoot":"","sources":["../../src/commands/deploy-modes.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"deploy-modes.d.ts","sourceRoot":"","sources":["../../src/commands/deploy-modes.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAwC3C;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,KAAK,GACR,OAAO,CAAC,QAAQ,CAAC,CAwBnB;AAED;;;;;;;GAOG;AACH,wBAAsB,SAAS,CAC7B,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,KAAK,GACR,OAAO,CAAC,QAAQ,CAAC,CAiEnB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,MAAM,CAC1B,IAAI,EAAE;IACJ,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CACxC,EACD,EAAE,EAAE,KAAK;AACT;;;;;GAKG;AACH,aAAa,GAAE;IACb,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3C,GACL,OAAO,CAAC,QAAQ,CAAC,CAmFnB"}
|
|
@@ -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":"AAqEA,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;AAkMD,2BAA2B;AAC3B,OAAO,EAAE,4BAA4B,EAAE,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type ChangeKind = "added" | "removed" | "changed";
|
|
2
|
+
export interface ManifestKeyChange {
|
|
3
|
+
/** Dotted key path, e.g. `access.allowed_entra_groups.0`. */
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly kind: ChangeKind;
|
|
6
|
+
/** Redacted rendering of the DEPLOYED value (present for removed/changed). */
|
|
7
|
+
readonly before?: string;
|
|
8
|
+
/** Redacted rendering of the LOCAL value (present for added/changed). */
|
|
9
|
+
readonly after?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ManifestDiff {
|
|
12
|
+
/** True when the parsed + normalised documents are equal. */
|
|
13
|
+
readonly inSync: boolean;
|
|
14
|
+
readonly changes: readonly ManifestKeyChange[];
|
|
15
|
+
/** Set when a document could not be parsed as YAML — the caller fails open. */
|
|
16
|
+
readonly parseError: string | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Diff the local `launchpad.yaml` against the deployed manifest body. Both are
|
|
20
|
+
* raw YAML strings. On a parse failure of either side, returns `parseError` set
|
|
21
|
+
* and no changes — the caller treats that as "could not determine" and proceeds
|
|
22
|
+
* (fail open, AC4).
|
|
23
|
+
*/
|
|
24
|
+
export declare function diffManifests(localYaml: string, deployedYaml: string): ManifestDiff;
|
|
25
|
+
/**
|
|
26
|
+
* Render the diff as terminal lines (added `+`, removed `-`, changed `~`),
|
|
27
|
+
* capped at `max` entries. Returns `[]` when there is nothing to show.
|
|
28
|
+
*/
|
|
29
|
+
export declare function renderManifestDiffLines(diff: ManifestDiff, max?: number): string[];
|
|
30
|
+
//# sourceMappingURL=manifest-diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest-diff.d.ts","sourceRoot":"","sources":["../../src/deploy/manifest-diff.ts"],"names":[],"mappings":"AAqBA,MAAM,MAAM,UAAU,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAEzD,MAAM,WAAW,iBAAiB;IAChC,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,8EAA8E;IAC9E,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,yEAAyE;IACzE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC/C,+EAA+E;IAC/E,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAiJD;;;;;GAKG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,MAAM,EACjB,YAAY,EAAE,MAAM,GACnB,YAAY,CAyEd;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,YAAY,EAClB,GAAG,SAAK,GACP,MAAM,EAAE,CAeV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status-polling.d.ts","sourceRoot":"","sources":["../../src/deploy/status-polling.ts"],"names":[],"mappings":"AA8BA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACxD,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAKD;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,
|
|
1
|
+
{"version":3,"file":"status-polling.d.ts","sourceRoot":"","sources":["../../src/deploy/status-polling.ts"],"names":[],"mappings":"AA8BA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnE,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,WAAW,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACxD,QAAQ,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,QAAQ,CAAC,GAAG,EAAE,MAAM,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3C;AAKD;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,CA4B5E"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.
|
|
1
|
+
export declare const CLI_VERSION = "0.43.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.43.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.43.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.43.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.43.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.43.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.43.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.43.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.43.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|