@m-kopa/launchpad-cli 0.25.0 → 0.26.1
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 +71 -0
- package/dist/cli.js +473 -231
- package/dist/commands/deploy.d.ts +10 -13
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/destroy.d.ts +1 -1
- package/dist/commands/destroy.d.ts.map +1 -1
- package/dist/commands/envvars.d.ts +2 -2
- package/dist/commands/envvars.d.ts.map +1 -1
- package/dist/commands/infer-slug.d.ts +37 -0
- package/dist/commands/infer-slug.d.ts.map +1 -0
- package/dist/commands/logs.d.ts +2 -2
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/merge.d.ts +2 -2
- package/dist/commands/merge.d.ts.map +1 -1
- package/dist/commands/pull.d.ts +1 -1
- package/dist/commands/pull.d.ts.map +1 -1
- package/dist/commands/recover.d.ts +12 -0
- package/dist/commands/recover.d.ts.map +1 -0
- package/dist/commands/review.d.ts +2 -2
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/status.d.ts +13 -3
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/dispatcher.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 +6 -4
- package/skills/launchpad-destroy/SKILL.md +1 -1
- package/skills/launchpad-onboard/SKILL.md +1 -1
- package/skills/launchpad-status/SKILL.md +13 -6
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.26.1";
|
|
23
23
|
|
|
24
24
|
// src/config.ts
|
|
25
25
|
import * as os from "node:os";
|
|
@@ -1219,8 +1219,8 @@ function describe9(e) {
|
|
|
1219
1219
|
}
|
|
1220
1220
|
|
|
1221
1221
|
// src/commands/deploy.ts
|
|
1222
|
-
import { existsSync as
|
|
1223
|
-
import * as
|
|
1222
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
1223
|
+
import * as path7 from "node:path";
|
|
1224
1224
|
|
|
1225
1225
|
// src/bundle/orchestrate.ts
|
|
1226
1226
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
@@ -3306,8 +3306,8 @@ async function bundleAndDeploy(args) {
|
|
|
3306
3306
|
}
|
|
3307
3307
|
|
|
3308
3308
|
// src/commands/deploy.ts
|
|
3309
|
-
import { parse as
|
|
3310
|
-
import { readFileSync as
|
|
3309
|
+
import { parse as parseYaml5 } from "yaml";
|
|
3310
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
3311
3311
|
|
|
3312
3312
|
// src/deploy/git-files.ts
|
|
3313
3313
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -4367,6 +4367,46 @@ function handleNetworkError(e, io, slug, verb) {
|
|
|
4367
4367
|
return 1;
|
|
4368
4368
|
}
|
|
4369
4369
|
|
|
4370
|
+
// src/commands/infer-slug.ts
|
|
4371
|
+
import * as path6 from "node:path";
|
|
4372
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "node:fs";
|
|
4373
|
+
import { parse as parseYaml4 } from "yaml";
|
|
4374
|
+
var DIRNAME_RE = /^launchpad-app-([a-z0-9][a-z0-9-]*[a-z0-9])$/;
|
|
4375
|
+
function inferSlugFromCwd(cwd) {
|
|
4376
|
+
const base = path6.basename(cwd);
|
|
4377
|
+
const m = base.match(DIRNAME_RE);
|
|
4378
|
+
return m === null ? null : m[1];
|
|
4379
|
+
}
|
|
4380
|
+
function resolveManifestSlug(parsed) {
|
|
4381
|
+
if (parsed === null || typeof parsed !== "object" || typeof parsed.metadata !== "object" || parsed.metadata === null) {
|
|
4382
|
+
return null;
|
|
4383
|
+
}
|
|
4384
|
+
const meta = parsed.metadata;
|
|
4385
|
+
if (typeof meta.slug === "string")
|
|
4386
|
+
return meta.slug;
|
|
4387
|
+
if (typeof meta.name === "string")
|
|
4388
|
+
return meta.name;
|
|
4389
|
+
return null;
|
|
4390
|
+
}
|
|
4391
|
+
function inferSlugFromManifestFile(manifestPath) {
|
|
4392
|
+
if (!existsSync4(manifestPath))
|
|
4393
|
+
return null;
|
|
4394
|
+
try {
|
|
4395
|
+
return resolveManifestSlug(parseYaml4(readFileSync6(manifestPath, "utf8")));
|
|
4396
|
+
} catch {
|
|
4397
|
+
return null;
|
|
4398
|
+
}
|
|
4399
|
+
}
|
|
4400
|
+
function inferSlug(opts) {
|
|
4401
|
+
const manifestPath = path6.resolve(opts.cwd, opts.file ?? "launchpad.yaml");
|
|
4402
|
+
const fromManifest = inferSlugFromManifestFile(manifestPath);
|
|
4403
|
+
const fromDir = inferSlugFromCwd(opts.cwd);
|
|
4404
|
+
if (fromManifest !== null && fromDir !== null && fromManifest !== fromDir) {
|
|
4405
|
+
opts.warn?.(`note: directory name suggests "${fromDir}" but ${path6.basename(manifestPath)} ` + `declares "${fromManifest}" — using the manifest. ` + `(Pass <slug> or --slug to override.)`);
|
|
4406
|
+
}
|
|
4407
|
+
return fromManifest ?? fromDir;
|
|
4408
|
+
}
|
|
4409
|
+
|
|
4370
4410
|
// src/commands/deploy.ts
|
|
4371
4411
|
var deployCommand = {
|
|
4372
4412
|
name: "deploy",
|
|
@@ -4374,27 +4414,17 @@ var deployCommand = {
|
|
|
4374
4414
|
run: runDeploy
|
|
4375
4415
|
};
|
|
4376
4416
|
var SLUG_RE4 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
4377
|
-
var DIRNAME_RE = /^launchpad-app-([a-z0-9][a-z0-9-]*[a-z0-9])$/;
|
|
4378
4417
|
async function runDeploy(args, io) {
|
|
4379
4418
|
const flags = parseDeployFlags(args);
|
|
4380
4419
|
if (typeof flags === "string") {
|
|
4381
4420
|
io.err(`launchpad deploy: ${flags}`);
|
|
4421
|
+
const isFlagMisuse = flags.includes("is only valid with") || flags.includes("does not accept") || flags.includes("mutually exclusive");
|
|
4422
|
+
if (isFlagMisuse) {
|
|
4423
|
+
io.err(" (run `launchpad deploy` with no flags from your app directory for a content deploy)");
|
|
4424
|
+
return 64;
|
|
4425
|
+
}
|
|
4382
4426
|
io.err("");
|
|
4383
|
-
io.err(
|
|
4384
|
-
` + " (slug defaults to the current directory's `launchpad-app-<slug>` suffix)\n" + `
|
|
4385
|
-
` + `M-892 modes:
|
|
4386
|
-
` + ` launchpad deploy --resume <slug>
|
|
4387
|
-
` + ` launchpad deploy --abandon <slug>
|
|
4388
|
-
` + ` launchpad deploy --new --slug <slug> --display-name <name>
|
|
4389
|
-
` + ` --app-type <${APP_TYPES.join("|")}>
|
|
4390
|
-
` + ` --allowed-group <G_KEY> [--allowed-group ...]
|
|
4391
|
-
` + `
|
|
4392
|
-
` + `Scope 6 dry-run (manifest-driven preview, read-only):
|
|
4393
|
-
` + ` launchpad deploy --dry-run [--file <manifest>] [--json]
|
|
4394
|
-
` + `
|
|
4395
|
-
` + `Scope 6 apply (manifest-driven, writes TF + runs terraform apply):
|
|
4396
|
-
` + ` launchpad deploy --apply --platform-repo <path>
|
|
4397
|
-
` + " [--file <manifest>] [--re-pin] [--yes]");
|
|
4427
|
+
io.err(deployUsage());
|
|
4398
4428
|
return 64;
|
|
4399
4429
|
}
|
|
4400
4430
|
if (flags.mode.kind === "dry-run") {
|
|
@@ -4428,8 +4458,8 @@ async function runDeploy(args, io) {
|
|
|
4428
4458
|
}, io);
|
|
4429
4459
|
}
|
|
4430
4460
|
const cwd = process.cwd();
|
|
4431
|
-
const manifestPath =
|
|
4432
|
-
if (
|
|
4461
|
+
const manifestPath = path7.join(cwd, "launchpad.yaml");
|
|
4462
|
+
if (existsSync5(manifestPath)) {
|
|
4433
4463
|
return runModelADeploy({ cwd, manifestPath, argv: args, io });
|
|
4434
4464
|
}
|
|
4435
4465
|
const parsed = parseArgs2(args);
|
|
@@ -4441,9 +4471,9 @@ async function runDeploy(args, io) {
|
|
|
4441
4471
|
if (parsed.slug !== null) {
|
|
4442
4472
|
slug = parsed.slug;
|
|
4443
4473
|
} else {
|
|
4444
|
-
const inferred =
|
|
4474
|
+
const inferred = inferSlug({ cwd: process.cwd(), warn: (l) => io.err(l) });
|
|
4445
4475
|
if (inferred === null) {
|
|
4446
|
-
io.err(`launchpad deploy: could not infer slug from cwd (${
|
|
4476
|
+
io.err(`launchpad deploy: could not infer slug from cwd (${path7.basename(process.cwd())});
|
|
4447
4477
|
` + ` pass --slug <slug>, or cd into a directory named launchpad-app-<slug>.`);
|
|
4448
4478
|
return 64;
|
|
4449
4479
|
}
|
|
@@ -4463,10 +4493,10 @@ async function runDeploy(args, io) {
|
|
|
4463
4493
|
return 1;
|
|
4464
4494
|
}
|
|
4465
4495
|
let contentManifestYaml = null;
|
|
4466
|
-
const contentManifestPath =
|
|
4467
|
-
if (
|
|
4496
|
+
const contentManifestPath = path7.join(process.cwd(), "launchpad.yaml");
|
|
4497
|
+
if (existsSync5(contentManifestPath)) {
|
|
4468
4498
|
try {
|
|
4469
|
-
contentManifestYaml =
|
|
4499
|
+
contentManifestYaml = readFileSync7(contentManifestPath, "utf8");
|
|
4470
4500
|
} catch {
|
|
4471
4501
|
contentManifestYaml = null;
|
|
4472
4502
|
}
|
|
@@ -4544,6 +4574,24 @@ async function runDeploy(args, io) {
|
|
|
4544
4574
|
return 1;
|
|
4545
4575
|
}
|
|
4546
4576
|
}
|
|
4577
|
+
function deployUsage() {
|
|
4578
|
+
return `usage: launchpad deploy [--message <text>] [--slug <slug>]
|
|
4579
|
+
` + ` (slug comes from launchpad.yaml at cwd when present — Model A —
|
|
4580
|
+
` + " else from the current directory's `launchpad-app-<slug>` suffix)\n" + `
|
|
4581
|
+
` + `provisioning modes:
|
|
4582
|
+
` + ` launchpad deploy --resume <slug>
|
|
4583
|
+
` + ` launchpad deploy --abandon <slug>
|
|
4584
|
+
` + ` launchpad deploy --new --slug <slug> --display-name <name>
|
|
4585
|
+
` + ` --app-type <${APP_TYPES.join("|")}>
|
|
4586
|
+
` + ` --allowed-group <G_KEY> [--allowed-group ...]
|
|
4587
|
+
` + `
|
|
4588
|
+
` + `manifest-driven dry-run (read-only preview):
|
|
4589
|
+
` + ` launchpad deploy --dry-run [--file <manifest>] [--json]
|
|
4590
|
+
` + `
|
|
4591
|
+
` + `manifest-driven apply (runs server-side via portal-bot):
|
|
4592
|
+
` + ` launchpad deploy --apply [--file <manifest>] [--at <sha>] [--re-pin]
|
|
4593
|
+
` + " [--yes] [--resume-pr <n>] [--timeout-minutes <n>]";
|
|
4594
|
+
}
|
|
4547
4595
|
function parseArgs2(args) {
|
|
4548
4596
|
let message = null;
|
|
4549
4597
|
let slug = null;
|
|
@@ -4570,11 +4618,6 @@ function parseArgs2(args) {
|
|
|
4570
4618
|
}
|
|
4571
4619
|
return { slug, message };
|
|
4572
4620
|
}
|
|
4573
|
-
function inferSlugFromCwd(cwd) {
|
|
4574
|
-
const base = path6.basename(cwd);
|
|
4575
|
-
const m = base.match(DIRNAME_RE);
|
|
4576
|
-
return m === null ? null : m[1];
|
|
4577
|
-
}
|
|
4578
4621
|
function makeBytesFetcher(bytes, pathSuffix) {
|
|
4579
4622
|
return async (input, init) => {
|
|
4580
4623
|
const targetUrl = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
@@ -4624,22 +4667,11 @@ function surfaceDeployExtras(body, io, slug) {
|
|
|
4624
4667
|
io.out(` Full list: \`launchpad status ${slug}\`.`);
|
|
4625
4668
|
}
|
|
4626
4669
|
}
|
|
4627
|
-
function resolveManifestSlug(parsed) {
|
|
4628
|
-
if (parsed === null || typeof parsed !== "object" || typeof parsed.metadata !== "object" || parsed.metadata === null) {
|
|
4629
|
-
return null;
|
|
4630
|
-
}
|
|
4631
|
-
const meta = parsed.metadata;
|
|
4632
|
-
if (typeof meta.slug === "string")
|
|
4633
|
-
return meta.slug;
|
|
4634
|
-
if (typeof meta.name === "string")
|
|
4635
|
-
return meta.name;
|
|
4636
|
-
return null;
|
|
4637
|
-
}
|
|
4638
4670
|
async function runModelADeploy(args) {
|
|
4639
4671
|
const { cwd, manifestPath, io } = args;
|
|
4640
4672
|
let slug;
|
|
4641
4673
|
try {
|
|
4642
|
-
const metaSlug = resolveManifestSlug(
|
|
4674
|
+
const metaSlug = resolveManifestSlug(parseYaml5(readFileSync7(manifestPath, "utf8")));
|
|
4643
4675
|
if (metaSlug === null) {
|
|
4644
4676
|
io.err(`launchpad deploy: launchpad.yaml is missing metadata.slug (v2) / metadata.name (v1). ` + `Run \`launchpad init\` again to regenerate the manifest.`);
|
|
4645
4677
|
return 64;
|
|
@@ -4715,6 +4747,11 @@ async function runModelADeploy(args) {
|
|
|
4715
4747
|
io.err(` - ${String(f.path)} [${String(f.rule)}]`);
|
|
4716
4748
|
}
|
|
4717
4749
|
}
|
|
4750
|
+
if (errorCode === "bundle_policy_violation" || errorCode === "app_boundary_violation" || errorCode === "bad_build_command") {
|
|
4751
|
+
io.err("");
|
|
4752
|
+
io.err(" Nothing was committed or claimed by this attempt — fix the");
|
|
4753
|
+
io.err(" listed file(s) and re-run `launchpad deploy`; the retry is clean.");
|
|
4754
|
+
}
|
|
4718
4755
|
return 1;
|
|
4719
4756
|
}
|
|
4720
4757
|
case "ok": {
|
|
@@ -4771,14 +4808,13 @@ async function runModelADeploy(args) {
|
|
|
4771
4808
|
}
|
|
4772
4809
|
|
|
4773
4810
|
// src/commands/envvars.ts
|
|
4774
|
-
import * as
|
|
4811
|
+
import * as path8 from "node:path";
|
|
4775
4812
|
var envvarsCommand = {
|
|
4776
4813
|
name: "envvars",
|
|
4777
4814
|
summary: "list / set / remove production env vars (slug-scoped)",
|
|
4778
4815
|
run: runEnvvars
|
|
4779
4816
|
};
|
|
4780
4817
|
var SLUG_RE5 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
4781
|
-
var DIRNAME_RE2 = /^launchpad-app-([a-z0-9][a-z0-9-]*[a-z0-9])$/;
|
|
4782
4818
|
var ENV_KEY_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
4783
4819
|
async function runEnvvars(args, io) {
|
|
4784
4820
|
const parsed = parseArgs3(args);
|
|
@@ -4790,9 +4826,9 @@ async function runEnvvars(args, io) {
|
|
|
4790
4826
|
if (parsed.slug !== null) {
|
|
4791
4827
|
slug = parsed.slug;
|
|
4792
4828
|
} else {
|
|
4793
|
-
const inferred =
|
|
4829
|
+
const inferred = inferSlug({ cwd: process.cwd(), warn: (l) => io.err(l) });
|
|
4794
4830
|
if (inferred === null) {
|
|
4795
|
-
io.err(`launchpad envvars: could not infer slug from cwd (${
|
|
4831
|
+
io.err(`launchpad envvars: could not infer slug from cwd (${path8.basename(process.cwd())});
|
|
4796
4832
|
` + ` pass --slug <slug>, or cd into a directory named launchpad-app-<slug>.`);
|
|
4797
4833
|
return 64;
|
|
4798
4834
|
}
|
|
@@ -4938,11 +4974,6 @@ function parseArgs3(args) {
|
|
|
4938
4974
|
}
|
|
4939
4975
|
return null;
|
|
4940
4976
|
}
|
|
4941
|
-
function inferSlugFromCwd2(cwd) {
|
|
4942
|
-
const base = path7.basename(cwd);
|
|
4943
|
-
const m = base.match(DIRNAME_RE2);
|
|
4944
|
-
return m === null ? null : m[1];
|
|
4945
|
-
}
|
|
4946
4977
|
function renderList(envVars, io) {
|
|
4947
4978
|
if (envVars.length === 0) {
|
|
4948
4979
|
io.out("(no env vars set)");
|
|
@@ -4967,8 +4998,8 @@ function describe13(e) {
|
|
|
4967
4998
|
}
|
|
4968
4999
|
|
|
4969
5000
|
// src/commands/generate.ts
|
|
4970
|
-
import { mkdirSync, readFileSync as
|
|
4971
|
-
import { dirname as dirname4, resolve as
|
|
5001
|
+
import { mkdirSync, readFileSync as readFileSync8, writeFileSync } from "node:fs";
|
|
5002
|
+
import { dirname as dirname4, resolve as resolve7, relative as relative3 } from "node:path";
|
|
4972
5003
|
var generateCommand = {
|
|
4973
5004
|
name: "generate",
|
|
4974
5005
|
summary: "emit derived artefacts (wrangler.toml, deploy.yml) from launchpad.yaml",
|
|
@@ -4981,14 +5012,14 @@ async function runGenerate(args, io) {
|
|
|
4981
5012
|
io.err("Usage: launchpad generate [--file <path>] [--dry-run] [--force] [--json]");
|
|
4982
5013
|
return 64;
|
|
4983
5014
|
}
|
|
4984
|
-
const manifestPath =
|
|
5015
|
+
const manifestPath = resolve7(process.cwd(), flags.file ?? "launchpad.yaml");
|
|
4985
5016
|
const result = loadManifest(manifestPath);
|
|
4986
5017
|
if (result.kind !== "ok") {
|
|
4987
5018
|
return flags.json ? renderManifestErrorJson(result, io) : renderManifestErrorHuman(result, io);
|
|
4988
5019
|
}
|
|
4989
5020
|
const appRoot = dirname4(manifestPath);
|
|
4990
|
-
const wranglerPath =
|
|
4991
|
-
const workflowPath =
|
|
5021
|
+
const wranglerPath = resolve7(appRoot, "container", "wrangler.toml");
|
|
5022
|
+
const workflowPath = resolve7(appRoot, ".github", "workflows", "deploy.yml");
|
|
4992
5023
|
const wranglerOut = generateWranglerToml(result.manifest);
|
|
4993
5024
|
const workflowOut = generateGithubDeployWorkflow(result.manifest);
|
|
4994
5025
|
if (flags.dryRun) {
|
|
@@ -5003,7 +5034,7 @@ async function runGenerate(args, io) {
|
|
|
5003
5034
|
];
|
|
5004
5035
|
return flags.json ? renderApplyJson(reports, appRoot, io) : renderApplyHuman(reports, appRoot, io);
|
|
5005
5036
|
}
|
|
5006
|
-
function applyOne(artefact,
|
|
5037
|
+
function applyOne(artefact, path9, out, force) {
|
|
5007
5038
|
if (out.kind === "not-applicable") {
|
|
5008
5039
|
return {
|
|
5009
5040
|
artefact,
|
|
@@ -5011,32 +5042,32 @@ function applyOne(artefact, path8, out, force) {
|
|
|
5011
5042
|
warnings: []
|
|
5012
5043
|
};
|
|
5013
5044
|
}
|
|
5014
|
-
const existing = readIfExists(
|
|
5045
|
+
const existing = readIfExists(path9);
|
|
5015
5046
|
if (existing.kind === "read-error") {
|
|
5016
5047
|
return {
|
|
5017
5048
|
artefact,
|
|
5018
|
-
action: { kind: "write-error", path:
|
|
5049
|
+
action: { kind: "write-error", path: path9, message: existing.message },
|
|
5019
5050
|
warnings: out.warnings
|
|
5020
5051
|
};
|
|
5021
5052
|
}
|
|
5022
5053
|
if (existing.kind === "ok" && existing.content === out.content) {
|
|
5023
|
-
return { artefact, action: { kind: "unchanged", path:
|
|
5054
|
+
return { artefact, action: { kind: "unchanged", path: path9 }, warnings: out.warnings };
|
|
5024
5055
|
}
|
|
5025
5056
|
if (existing.kind === "ok" && existing.content !== out.content && !force) {
|
|
5026
5057
|
return {
|
|
5027
5058
|
artefact,
|
|
5028
|
-
action: { kind: "would-overwrite", path:
|
|
5059
|
+
action: { kind: "would-overwrite", path: path9 },
|
|
5029
5060
|
warnings: out.warnings
|
|
5030
5061
|
};
|
|
5031
5062
|
}
|
|
5032
5063
|
try {
|
|
5033
|
-
mkdirSync(dirname4(
|
|
5034
|
-
writeFileSync(
|
|
5064
|
+
mkdirSync(dirname4(path9), { recursive: true });
|
|
5065
|
+
writeFileSync(path9, out.content, "utf8");
|
|
5035
5066
|
return {
|
|
5036
5067
|
artefact,
|
|
5037
5068
|
action: {
|
|
5038
5069
|
kind: "written",
|
|
5039
|
-
path:
|
|
5070
|
+
path: path9,
|
|
5040
5071
|
bytes: Buffer.byteLength(out.content, "utf8")
|
|
5041
5072
|
},
|
|
5042
5073
|
warnings: out.warnings
|
|
@@ -5046,16 +5077,16 @@ function applyOne(artefact, path8, out, force) {
|
|
|
5046
5077
|
artefact,
|
|
5047
5078
|
action: {
|
|
5048
5079
|
kind: "write-error",
|
|
5049
|
-
path:
|
|
5080
|
+
path: path9,
|
|
5050
5081
|
message: err.message ?? String(err)
|
|
5051
5082
|
},
|
|
5052
5083
|
warnings: out.warnings
|
|
5053
5084
|
};
|
|
5054
5085
|
}
|
|
5055
5086
|
}
|
|
5056
|
-
function readIfExists(
|
|
5087
|
+
function readIfExists(path9) {
|
|
5057
5088
|
try {
|
|
5058
|
-
return { kind: "ok", content:
|
|
5089
|
+
return { kind: "ok", content: readFileSync8(path9, "utf8") };
|
|
5059
5090
|
} catch (err) {
|
|
5060
5091
|
const e = err;
|
|
5061
5092
|
if (e.code === "ENOENT")
|
|
@@ -5266,7 +5297,7 @@ function parseFlags(args) {
|
|
|
5266
5297
|
}
|
|
5267
5298
|
|
|
5268
5299
|
// src/groups/client.ts
|
|
5269
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
5300
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync2 } from "node:fs";
|
|
5270
5301
|
import { dirname as dirname5, join as join9 } from "node:path";
|
|
5271
5302
|
var CACHE_TTL_MS = 60 * 60 * 1000;
|
|
5272
5303
|
var CACHE_FILENAME = "groups.json";
|
|
@@ -5307,10 +5338,10 @@ async function fetchGroups(cfg, opts = {}) {
|
|
|
5307
5338
|
writeCache(cachePath, { fetchedAt, groups });
|
|
5308
5339
|
return { kind: "ok", source: "fresh", fetchedAt, groups };
|
|
5309
5340
|
}
|
|
5310
|
-
function readCache(
|
|
5341
|
+
function readCache(path9) {
|
|
5311
5342
|
let raw;
|
|
5312
5343
|
try {
|
|
5313
|
-
raw =
|
|
5344
|
+
raw = readFileSync9(path9, "utf8");
|
|
5314
5345
|
} catch {
|
|
5315
5346
|
return null;
|
|
5316
5347
|
}
|
|
@@ -5338,10 +5369,10 @@ function isEntraGroup(value) {
|
|
|
5338
5369
|
const g = value;
|
|
5339
5370
|
return typeof g.id === "string" && typeof g.displayName === "string" && (typeof g.mailNickname === "string" || g.mailNickname === null);
|
|
5340
5371
|
}
|
|
5341
|
-
function writeCache(
|
|
5372
|
+
function writeCache(path9, envelope) {
|
|
5342
5373
|
try {
|
|
5343
|
-
mkdirSync2(dirname5(
|
|
5344
|
-
writeFileSync2(
|
|
5374
|
+
mkdirSync2(dirname5(path9), { recursive: true });
|
|
5375
|
+
writeFileSync2(path9, JSON.stringify(envelope), "utf8");
|
|
5345
5376
|
} catch {}
|
|
5346
5377
|
}
|
|
5347
5378
|
function describe14(e) {
|
|
@@ -5811,17 +5842,17 @@ function describe17(e) {
|
|
|
5811
5842
|
|
|
5812
5843
|
// src/commands/init.ts
|
|
5813
5844
|
import { createInterface } from "node:readline/promises";
|
|
5814
|
-
import { existsSync as
|
|
5815
|
-
import { resolve as
|
|
5845
|
+
import { existsSync as existsSync7, readFileSync as readFileSync11, writeFileSync as writeFileSync3 } from "node:fs";
|
|
5846
|
+
import { resolve as resolve8 } from "node:path";
|
|
5816
5847
|
import { stringify as yamlStringify } from "yaml";
|
|
5817
5848
|
|
|
5818
5849
|
// src/detect/index.ts
|
|
5819
|
-
import { existsSync as
|
|
5850
|
+
import { existsSync as existsSync6, readFileSync as readFileSync10, statSync } from "node:fs";
|
|
5820
5851
|
import { join as join10 } from "node:path";
|
|
5821
5852
|
function detectAppShape(cwd) {
|
|
5822
|
-
const hasPackageJson =
|
|
5823
|
-
const hasAnyLockfile = LOCKFILES.some(({ file }) =>
|
|
5824
|
-
const hasViteConfig = VITE_CONFIG_NAMES.some((n) =>
|
|
5853
|
+
const hasPackageJson = existsSync6(join10(cwd, "package.json"));
|
|
5854
|
+
const hasAnyLockfile = LOCKFILES.some(({ file }) => existsSync6(join10(cwd, file)));
|
|
5855
|
+
const hasViteConfig = VITE_CONFIG_NAMES.some((n) => existsSync6(join10(cwd, n)));
|
|
5825
5856
|
if (!hasPackageJson && !hasAnyLockfile && !hasViteConfig) {
|
|
5826
5857
|
return {
|
|
5827
5858
|
kind: "not-applicable",
|
|
@@ -5867,7 +5898,7 @@ var LOCKFILES = [
|
|
|
5867
5898
|
{ file: "yarn.lock", pm: "yarn" }
|
|
5868
5899
|
];
|
|
5869
5900
|
function detectPackageManager(cwd) {
|
|
5870
|
-
const present = LOCKFILES.filter(({ file }) =>
|
|
5901
|
+
const present = LOCKFILES.filter(({ file }) => existsSync6(join10(cwd, file)));
|
|
5871
5902
|
if (present.length === 0) {
|
|
5872
5903
|
return {
|
|
5873
5904
|
kind: "ambiguous",
|
|
@@ -5890,7 +5921,7 @@ function detectPackageManager(cwd) {
|
|
|
5890
5921
|
function detectVitePresence(cwd) {
|
|
5891
5922
|
for (const name of VITE_CONFIG_NAMES) {
|
|
5892
5923
|
const p = join10(cwd, name);
|
|
5893
|
-
if (
|
|
5924
|
+
if (existsSync6(p)) {
|
|
5894
5925
|
return { kind: "ok", value: { path: p } };
|
|
5895
5926
|
}
|
|
5896
5927
|
}
|
|
@@ -5902,7 +5933,7 @@ function detectVitePresence(cwd) {
|
|
|
5902
5933
|
function detectAppType(cwd) {
|
|
5903
5934
|
const fnDir = join10(cwd, "functions");
|
|
5904
5935
|
let hasFunctionsDir = false;
|
|
5905
|
-
if (
|
|
5936
|
+
if (existsSync6(fnDir)) {
|
|
5906
5937
|
try {
|
|
5907
5938
|
hasFunctionsDir = statSync(fnDir).isDirectory();
|
|
5908
5939
|
} catch {
|
|
@@ -5913,7 +5944,7 @@ function detectAppType(cwd) {
|
|
|
5913
5944
|
}
|
|
5914
5945
|
function detectBuildCommand(cwd, pm) {
|
|
5915
5946
|
const pkgJsonPath = join10(cwd, "package.json");
|
|
5916
|
-
if (!
|
|
5947
|
+
if (!existsSync6(pkgJsonPath)) {
|
|
5917
5948
|
return {
|
|
5918
5949
|
kind: "ambiguous",
|
|
5919
5950
|
reason: "no package.json at repo root. Run your package manager's `init` first."
|
|
@@ -5921,7 +5952,7 @@ function detectBuildCommand(cwd, pm) {
|
|
|
5921
5952
|
}
|
|
5922
5953
|
let pkgJson;
|
|
5923
5954
|
try {
|
|
5924
|
-
pkgJson = JSON.parse(
|
|
5955
|
+
pkgJson = JSON.parse(readFileSync10(pkgJsonPath, "utf8"));
|
|
5925
5956
|
} catch (e) {
|
|
5926
5957
|
return {
|
|
5927
5958
|
kind: "ambiguous",
|
|
@@ -5956,7 +5987,7 @@ var OUT_DIR_REGEX = /\bbuild\s*:\s*\{[^{}]*?\boutDir\s*:\s*['"]([^'"]+)['"]/s;
|
|
|
5956
5987
|
function detectDestinationDir(cwd, vite) {
|
|
5957
5988
|
let text;
|
|
5958
5989
|
try {
|
|
5959
|
-
text =
|
|
5990
|
+
text = readFileSync10(vite.path, "utf8");
|
|
5960
5991
|
} catch (e) {
|
|
5961
5992
|
return {
|
|
5962
5993
|
kind: "ambiguous",
|
|
@@ -5985,8 +6016,8 @@ async function runInit(args, io, prompt) {
|
|
|
5985
6016
|
return 64;
|
|
5986
6017
|
}
|
|
5987
6018
|
const { inputs, options } = parsed;
|
|
5988
|
-
const outPath =
|
|
5989
|
-
if (
|
|
6019
|
+
const outPath = resolve8(process.cwd(), options.out);
|
|
6020
|
+
if (existsSync7(outPath) && !options.force) {
|
|
5990
6021
|
io.err(`launchpad init: ${outPath} already exists`);
|
|
5991
6022
|
io.err("Pass --force to overwrite.");
|
|
5992
6023
|
return 64;
|
|
@@ -6033,7 +6064,7 @@ async function runInit(args, io, prompt) {
|
|
|
6033
6064
|
}
|
|
6034
6065
|
io.out(`✓ wrote ${outPath}`);
|
|
6035
6066
|
if (options.gitignore) {
|
|
6036
|
-
const gitignorePath =
|
|
6067
|
+
const gitignorePath = resolve8(process.cwd(), ".gitignore");
|
|
6037
6068
|
try {
|
|
6038
6069
|
const changed = ensureGitignoreEntries(gitignorePath, [".env", ".env.local"]);
|
|
6039
6070
|
if (changed.length > 0) {
|
|
@@ -6402,10 +6433,10 @@ function buildManifest(inputs, detected) {
|
|
|
6402
6433
|
function renderYaml(manifest) {
|
|
6403
6434
|
return yamlStringify(manifest, { lineWidth: 0 });
|
|
6404
6435
|
}
|
|
6405
|
-
function ensureGitignoreEntries(
|
|
6436
|
+
function ensureGitignoreEntries(path9, entries) {
|
|
6406
6437
|
let current = "";
|
|
6407
|
-
if (
|
|
6408
|
-
current =
|
|
6438
|
+
if (existsSync7(path9)) {
|
|
6439
|
+
current = readFileSync11(path9, "utf8");
|
|
6409
6440
|
}
|
|
6410
6441
|
const lines = current.split(/\r?\n/);
|
|
6411
6442
|
const present = new Set(lines.map((l) => l.trim()));
|
|
@@ -6423,7 +6454,7 @@ function ensureGitignoreEntries(path8, entries) {
|
|
|
6423
6454
|
}
|
|
6424
6455
|
}
|
|
6425
6456
|
if (added.length > 0) {
|
|
6426
|
-
writeFileSync3(
|
|
6457
|
+
writeFileSync3(path9, out, { encoding: "utf8" });
|
|
6427
6458
|
}
|
|
6428
6459
|
return added;
|
|
6429
6460
|
}
|
|
@@ -6495,14 +6526,13 @@ function describe19(e) {
|
|
|
6495
6526
|
}
|
|
6496
6527
|
|
|
6497
6528
|
// src/commands/logs.ts
|
|
6498
|
-
import * as
|
|
6529
|
+
import * as path9 from "node:path";
|
|
6499
6530
|
var logsCommand = {
|
|
6500
6531
|
name: "logs",
|
|
6501
6532
|
summary: "show recent Pages deployment history (slug-scoped)",
|
|
6502
6533
|
run: runLogs
|
|
6503
6534
|
};
|
|
6504
6535
|
var SLUG_RE6 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
6505
|
-
var DIRNAME_RE3 = /^launchpad-app-([a-z0-9][a-z0-9-]*[a-z0-9])$/;
|
|
6506
6536
|
var DEFAULT_LINES = 10;
|
|
6507
6537
|
var MAX_LINES = 25;
|
|
6508
6538
|
async function runLogs(args, io) {
|
|
@@ -6516,9 +6546,9 @@ async function runLogs(args, io) {
|
|
|
6516
6546
|
if (parsed.slug !== null) {
|
|
6517
6547
|
slug = parsed.slug;
|
|
6518
6548
|
} else {
|
|
6519
|
-
const inferred =
|
|
6549
|
+
const inferred = inferSlug({ cwd: process.cwd(), warn: (l) => io.err(l) });
|
|
6520
6550
|
if (inferred === null) {
|
|
6521
|
-
io.err(`launchpad logs: could not infer slug from cwd (${
|
|
6551
|
+
io.err(`launchpad logs: could not infer slug from cwd (${path9.basename(process.cwd())});
|
|
6522
6552
|
` + ` pass --slug <slug>, or cd into a directory named launchpad-app-<slug>.`);
|
|
6523
6553
|
return 64;
|
|
6524
6554
|
}
|
|
@@ -6596,11 +6626,6 @@ function parseArgs5(args) {
|
|
|
6596
6626
|
}
|
|
6597
6627
|
return { slug, lines };
|
|
6598
6628
|
}
|
|
6599
|
-
function inferSlugFromCwd3(cwd) {
|
|
6600
|
-
const base = path8.basename(cwd);
|
|
6601
|
-
const m = base.match(DIRNAME_RE3);
|
|
6602
|
-
return m === null ? null : m[1];
|
|
6603
|
-
}
|
|
6604
6629
|
function renderDeployments(deployments, io) {
|
|
6605
6630
|
if (deployments.length === 0) {
|
|
6606
6631
|
io.out("(no deployments yet)");
|
|
@@ -6651,14 +6676,13 @@ function describe20(e) {
|
|
|
6651
6676
|
}
|
|
6652
6677
|
|
|
6653
6678
|
// src/commands/merge.ts
|
|
6654
|
-
import * as
|
|
6679
|
+
import * as path10 from "node:path";
|
|
6655
6680
|
var mergeCommand = {
|
|
6656
6681
|
name: "merge",
|
|
6657
6682
|
summary: "squash-merge a review-passed PR (slug-scoped)",
|
|
6658
6683
|
run: runMerge
|
|
6659
6684
|
};
|
|
6660
6685
|
var SLUG_RE7 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
6661
|
-
var DIRNAME_RE4 = /^launchpad-app-([a-z0-9][a-z0-9-]*[a-z0-9])$/;
|
|
6662
6686
|
var HANDLED_STATUSES = [409, 422, 502, 503];
|
|
6663
6687
|
async function runMerge(args, io) {
|
|
6664
6688
|
const parsed = parseArgs6(args);
|
|
@@ -6671,9 +6695,9 @@ async function runMerge(args, io) {
|
|
|
6671
6695
|
if (parsed.slug !== null) {
|
|
6672
6696
|
slug = parsed.slug;
|
|
6673
6697
|
} else {
|
|
6674
|
-
const inferred =
|
|
6698
|
+
const inferred = inferSlug({ cwd: process.cwd(), warn: (l) => io.err(l) });
|
|
6675
6699
|
if (inferred === null) {
|
|
6676
|
-
io.err(`launchpad merge: could not infer slug from cwd (${
|
|
6700
|
+
io.err(`launchpad merge: could not infer slug from cwd (${path10.basename(process.cwd())});
|
|
6677
6701
|
` + ` pass --slug <slug>, or cd into a directory named launchpad-app-<slug>.`);
|
|
6678
6702
|
return 64;
|
|
6679
6703
|
}
|
|
@@ -6770,11 +6794,6 @@ function parseArgs6(args) {
|
|
|
6770
6794
|
return null;
|
|
6771
6795
|
return { slug, prNumber };
|
|
6772
6796
|
}
|
|
6773
|
-
function inferSlugFromCwd4(cwd) {
|
|
6774
|
-
const base = path9.basename(cwd);
|
|
6775
|
-
const m = base.match(DIRNAME_RE4);
|
|
6776
|
-
return m === null ? null : m[1];
|
|
6777
|
-
}
|
|
6778
6797
|
function renderBotError(status, env, io, prNumber = null) {
|
|
6779
6798
|
const code = env?.error ?? "unknown";
|
|
6780
6799
|
const detail = env?.message;
|
|
@@ -6830,7 +6849,7 @@ function describe21(e) {
|
|
|
6830
6849
|
}
|
|
6831
6850
|
|
|
6832
6851
|
// src/commands/plan.ts
|
|
6833
|
-
import { resolve as
|
|
6852
|
+
import { resolve as resolve9 } from "node:path";
|
|
6834
6853
|
var planCommand = {
|
|
6835
6854
|
name: "plan",
|
|
6836
6855
|
summary: "summarise what the manifest would deploy (offline)",
|
|
@@ -6843,7 +6862,7 @@ async function runPlan(args, io) {
|
|
|
6843
6862
|
io.err("Usage: launchpad plan [--file <path>] [--json]");
|
|
6844
6863
|
return 64;
|
|
6845
6864
|
}
|
|
6846
|
-
const manifestPath =
|
|
6865
|
+
const manifestPath = resolve9(process.cwd(), flags.file ?? "launchpad.yaml");
|
|
6847
6866
|
const result = loadManifest(manifestPath);
|
|
6848
6867
|
return flags.json ? renderJson(result, io) : renderHuman(result, io);
|
|
6849
6868
|
}
|
|
@@ -7026,7 +7045,7 @@ var SLUG_RE8 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
|
7026
7045
|
var SLUG_MIN_LENGTH = 3;
|
|
7027
7046
|
var SLUG_MAX_LENGTH = 58;
|
|
7028
7047
|
async function runDestroy(args, io, prompt, isTty) {
|
|
7029
|
-
const parsed = parseArgs7(args);
|
|
7048
|
+
const parsed = parseArgs7(args, process.cwd(), (l) => io.err(l));
|
|
7030
7049
|
if (typeof parsed === "string") {
|
|
7031
7050
|
io.err(`launchpad destroy: ${parsed}`);
|
|
7032
7051
|
printUsage3(io);
|
|
@@ -7081,7 +7100,7 @@ async function runDestroy(args, io, prompt, isTty) {
|
|
|
7081
7100
|
return 1;
|
|
7082
7101
|
}
|
|
7083
7102
|
}
|
|
7084
|
-
function parseArgs7(args, cwd = process.cwd()) {
|
|
7103
|
+
function parseArgs7(args, cwd = process.cwd(), warn) {
|
|
7085
7104
|
let slug = null;
|
|
7086
7105
|
let confirmSlug = null;
|
|
7087
7106
|
let yes = false;
|
|
@@ -7128,7 +7147,7 @@ function parseArgs7(args, cwd = process.cwd()) {
|
|
|
7128
7147
|
i += 1;
|
|
7129
7148
|
}
|
|
7130
7149
|
if (slug === null) {
|
|
7131
|
-
const inferred =
|
|
7150
|
+
const inferred = inferSlug({ cwd, warn });
|
|
7132
7151
|
if (inferred === null) {
|
|
7133
7152
|
return `slug not provided + cannot infer from cwd. ` + `Pass <slug> or --slug <slug>, or cd into a directory named launchpad-app-<slug>.`;
|
|
7134
7153
|
}
|
|
@@ -7288,8 +7307,8 @@ import { stringify as stringifyYaml } from "yaml";
|
|
|
7288
7307
|
|
|
7289
7308
|
// src/deploy/manifest-state.ts
|
|
7290
7309
|
async function fetchManifestState(cfg, slug, opts = {}, fetcher = fetch) {
|
|
7291
|
-
const
|
|
7292
|
-
const raw = await apiJson(cfg, { path:
|
|
7310
|
+
const path11 = opts.includeManifest === true ? `/apps/${encodeURIComponent(slug)}/manifest/state?include=manifest` : `/apps/${encodeURIComponent(slug)}/manifest/state`;
|
|
7311
|
+
const raw = await apiJson(cfg, { path: path11 }, fetcher);
|
|
7293
7312
|
return {
|
|
7294
7313
|
slug: raw.slug,
|
|
7295
7314
|
hasAppFile: raw.hasAppFile,
|
|
@@ -7302,8 +7321,8 @@ async function fetchManifestState(cfg, slug, opts = {}, fetcher = fetch) {
|
|
|
7302
7321
|
|
|
7303
7322
|
// src/deploy/manifest-status.ts
|
|
7304
7323
|
async function fetchManifestStatus(cfg, slug, fetcher = fetch) {
|
|
7305
|
-
const
|
|
7306
|
-
return apiJson(cfg, { path:
|
|
7324
|
+
const path11 = `/apps/${encodeURIComponent(slug)}/manifest/status`;
|
|
7325
|
+
return apiJson(cfg, { path: path11 }, fetcher);
|
|
7307
7326
|
}
|
|
7308
7327
|
|
|
7309
7328
|
// src/deploy/deployment-status.ts
|
|
@@ -7347,7 +7366,7 @@ var pullCommand = {
|
|
|
7347
7366
|
};
|
|
7348
7367
|
var SLUG_RE9 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
7349
7368
|
async function runPull(args, io) {
|
|
7350
|
-
const parsed = parseArgs8(args);
|
|
7369
|
+
const parsed = parseArgs8(args, process.cwd(), (l) => io.err(l));
|
|
7351
7370
|
if (typeof parsed === "string") {
|
|
7352
7371
|
io.err(`launchpad pull: ${parsed}`);
|
|
7353
7372
|
printUsage4(io);
|
|
@@ -7433,7 +7452,7 @@ async function fetchLiveDeploymentBestEffort(cfg, slug) {
|
|
|
7433
7452
|
return null;
|
|
7434
7453
|
}
|
|
7435
7454
|
}
|
|
7436
|
-
function parseArgs8(args, cwd = process.cwd()) {
|
|
7455
|
+
function parseArgs8(args, cwd = process.cwd(), warn) {
|
|
7437
7456
|
let slug = null;
|
|
7438
7457
|
let out = null;
|
|
7439
7458
|
let status = false;
|
|
@@ -7474,7 +7493,7 @@ function parseArgs8(args, cwd = process.cwd()) {
|
|
|
7474
7493
|
i += 1;
|
|
7475
7494
|
}
|
|
7476
7495
|
if (slug === null) {
|
|
7477
|
-
const inferred =
|
|
7496
|
+
const inferred = inferSlug({ cwd, warn });
|
|
7478
7497
|
if (inferred === null) {
|
|
7479
7498
|
return `slug not provided + cannot infer from cwd. ` + `Pass <slug> or --slug <slug>, or cd into a directory named launchpad-app-<slug>.`;
|
|
7480
7499
|
}
|
|
@@ -7492,8 +7511,9 @@ function printUsage4(io) {
|
|
|
7492
7511
|
" Reads the deployed launchpad.yaml for an app via the bot.",
|
|
7493
7512
|
" No local platform-repo or terraform required.",
|
|
7494
7513
|
"",
|
|
7495
|
-
"
|
|
7496
|
-
"
|
|
7514
|
+
" With no slug, it is inferred from the local launchpad.yaml's",
|
|
7515
|
+
" declared slug first, then from a launchpad-app-<slug>/",
|
|
7516
|
+
" directory name. Explicit --slug or positional override.",
|
|
7497
7517
|
"",
|
|
7498
7518
|
" --status read the role-redacted status block (what your",
|
|
7499
7519
|
" role may see) instead of the spec manifest.",
|
|
@@ -7506,14 +7526,184 @@ function describe23(e) {
|
|
|
7506
7526
|
return e instanceof Error ? e.message : String(e);
|
|
7507
7527
|
}
|
|
7508
7528
|
|
|
7529
|
+
// src/commands/recover.ts
|
|
7530
|
+
var recoverCommand = {
|
|
7531
|
+
name: "recover",
|
|
7532
|
+
summary: "repair a terminal-failed app record by reconciling against live state",
|
|
7533
|
+
run: runRecover
|
|
7534
|
+
};
|
|
7535
|
+
var SLUG_RE10 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
7536
|
+
async function runRecover(args, io) {
|
|
7537
|
+
const parsed = parseRecoverArgs(args, process.cwd(), (l) => io.err(l));
|
|
7538
|
+
if (typeof parsed === "string") {
|
|
7539
|
+
io.err(`launchpad recover: ${parsed}`);
|
|
7540
|
+
printUsage5(io);
|
|
7541
|
+
return 64;
|
|
7542
|
+
}
|
|
7543
|
+
const cfg = loadConfig();
|
|
7544
|
+
try {
|
|
7545
|
+
io.out(`Reconciling "${parsed.slug}" against live Cloudflare state …`);
|
|
7546
|
+
const res = await apiRaw(cfg, {
|
|
7547
|
+
method: "POST",
|
|
7548
|
+
path: `/apps/${parsed.slug}/recover`,
|
|
7549
|
+
jsonBody: undefined,
|
|
7550
|
+
nonThrowingStatuses: [409, 503]
|
|
7551
|
+
});
|
|
7552
|
+
const body = await res.json().catch(() => null);
|
|
7553
|
+
if (parsed.json) {
|
|
7554
|
+
io.out(JSON.stringify({ httpStatus: res.status, ...body ?? {} }, null, 2));
|
|
7555
|
+
return res.status === 200 ? 0 : 1;
|
|
7556
|
+
}
|
|
7557
|
+
if (res.status === 200 && body !== null && "outcome" in body) {
|
|
7558
|
+
renderSuccess2(body, io);
|
|
7559
|
+
return 0;
|
|
7560
|
+
}
|
|
7561
|
+
if (res.status === 503) {
|
|
7562
|
+
const msg = body !== null && "message" in body && typeof body.message === "string" ? body.message : "live state unavailable — nothing was changed; retry shortly.";
|
|
7563
|
+
io.err(`launchpad recover: ${msg}`);
|
|
7564
|
+
return 1;
|
|
7565
|
+
}
|
|
7566
|
+
if (res.status === 409 && body !== null && "error" in body) {
|
|
7567
|
+
renderRefusal(parsed.slug, body, io);
|
|
7568
|
+
return 1;
|
|
7569
|
+
}
|
|
7570
|
+
io.err(`launchpad recover: bot returned an unexpected HTTP ${res.status} response.`);
|
|
7571
|
+
return 1;
|
|
7572
|
+
} catch (e) {
|
|
7573
|
+
return mapError(e, parsed.slug, io);
|
|
7574
|
+
}
|
|
7575
|
+
}
|
|
7576
|
+
function renderSuccess2(body, io) {
|
|
7577
|
+
if (body.outcome === "noop_already_healthy") {
|
|
7578
|
+
io.out(`${body.slug}: already healthy — nothing to recover.`);
|
|
7579
|
+
io.out(` ${body.message}`);
|
|
7580
|
+
return;
|
|
7581
|
+
}
|
|
7582
|
+
io.out(`${body.slug}: REPAIRED — registry record reconciled to live.`);
|
|
7583
|
+
if (body.before !== undefined) {
|
|
7584
|
+
io.out(` before: ${body.before.lifecycle}` + (body.before.reason !== null ? ` (${body.before.reason})` : ""));
|
|
7585
|
+
}
|
|
7586
|
+
if (body.after !== undefined) {
|
|
7587
|
+
io.out(` after: ${body.after.lifecycle}`);
|
|
7588
|
+
}
|
|
7589
|
+
const checked = body.checked;
|
|
7590
|
+
if (checked !== undefined) {
|
|
7591
|
+
io.out(` verified: Pages project "${checked.pagesProject}" exists` + (checked.latestDeployment !== null ? `; latest production deployment ${checked.latestDeployment.id} ` + `(${checked.latestDeployment.buildStatus}, ${checked.latestDeployment.createdOn})` : ""));
|
|
7592
|
+
if (checked.olderContentServing) {
|
|
7593
|
+
io.out(" note: the LATEST build failed — an older successful deployment is what's serving.");
|
|
7594
|
+
}
|
|
7595
|
+
}
|
|
7596
|
+
io.out("");
|
|
7597
|
+
io.out(`Run \`launchpad status ${body.slug}\` — it now reports the live deployment truth.`);
|
|
7598
|
+
}
|
|
7599
|
+
function renderRefusal(slug, body, io) {
|
|
7600
|
+
io.err(`launchpad recover: refused — "${slug}" was NOT repaired.`);
|
|
7601
|
+
if (typeof body.message === "string") {
|
|
7602
|
+
io.err(` ${body.message}`);
|
|
7603
|
+
}
|
|
7604
|
+
const checked = body.checked;
|
|
7605
|
+
if (checked !== undefined) {
|
|
7606
|
+
io.err(" checked:");
|
|
7607
|
+
io.err(` Pages project "${checked.pagesProject}": ${checked.projectExists ? "exists" : "MISSING"}`);
|
|
7608
|
+
if (checked.latestDeployment !== null) {
|
|
7609
|
+
io.err(` latest production deployment: ${checked.latestDeployment.id} (${checked.latestDeployment.buildStatus})`);
|
|
7610
|
+
} else if (checked.projectExists) {
|
|
7611
|
+
io.err(" latest production deployment: none");
|
|
7612
|
+
}
|
|
7613
|
+
}
|
|
7614
|
+
}
|
|
7615
|
+
function mapError(e, slug, io) {
|
|
7616
|
+
if (e instanceof UnauthenticatedError) {
|
|
7617
|
+
io.err(`launchpad recover: ${e.message}`);
|
|
7618
|
+
io.err(" session expired, run `launchpad login`");
|
|
7619
|
+
return 1;
|
|
7620
|
+
}
|
|
7621
|
+
if (e instanceof ForbiddenError) {
|
|
7622
|
+
io.err(`launchpad recover: not authorised for app "${slug}" (you must be an owner or editor).`);
|
|
7623
|
+
return 1;
|
|
7624
|
+
}
|
|
7625
|
+
if (e instanceof NotFoundError) {
|
|
7626
|
+
io.err(`launchpad recover: app "${slug}" not found.`);
|
|
7627
|
+
return 1;
|
|
7628
|
+
}
|
|
7629
|
+
if (e instanceof ApiError || e instanceof TransportError) {
|
|
7630
|
+
io.err(`launchpad recover: ${e.message}`);
|
|
7631
|
+
return 1;
|
|
7632
|
+
}
|
|
7633
|
+
io.err(`launchpad recover failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
7634
|
+
return 1;
|
|
7635
|
+
}
|
|
7636
|
+
function parseRecoverArgs(args, cwd = process.cwd(), warn) {
|
|
7637
|
+
let slug = null;
|
|
7638
|
+
let json = false;
|
|
7639
|
+
let i = 0;
|
|
7640
|
+
while (i < args.length) {
|
|
7641
|
+
const a = args[i] ?? "";
|
|
7642
|
+
if (a === "--slug") {
|
|
7643
|
+
const v = args[i + 1];
|
|
7644
|
+
if (v === undefined)
|
|
7645
|
+
return "missing value for --slug";
|
|
7646
|
+
if (slug !== null)
|
|
7647
|
+
return "cannot mix --slug with positional slug (or pass --slug twice)";
|
|
7648
|
+
slug = v;
|
|
7649
|
+
i += 2;
|
|
7650
|
+
continue;
|
|
7651
|
+
}
|
|
7652
|
+
if (a === "--json") {
|
|
7653
|
+
json = true;
|
|
7654
|
+
i += 1;
|
|
7655
|
+
continue;
|
|
7656
|
+
}
|
|
7657
|
+
if (a.startsWith("--")) {
|
|
7658
|
+
return `unknown flag "${a}"`;
|
|
7659
|
+
}
|
|
7660
|
+
if (slug !== null) {
|
|
7661
|
+
return "cannot mix --slug with positional slug (or pass two positional slugs)";
|
|
7662
|
+
}
|
|
7663
|
+
slug = a;
|
|
7664
|
+
i += 1;
|
|
7665
|
+
}
|
|
7666
|
+
if (slug === null) {
|
|
7667
|
+
slug = inferSlug({ cwd, warn });
|
|
7668
|
+
}
|
|
7669
|
+
if (slug === null) {
|
|
7670
|
+
return "slug not provided + cannot infer from cwd. " + "Pass <slug> or --slug <slug>, cd into launchpad-app-<slug>/, or run from a directory with launchpad.yaml.";
|
|
7671
|
+
}
|
|
7672
|
+
if (!SLUG_RE10.test(slug)) {
|
|
7673
|
+
return `invalid slug "${slug}" — expected ${SLUG_RE10.source}`;
|
|
7674
|
+
}
|
|
7675
|
+
return { slug, json };
|
|
7676
|
+
}
|
|
7677
|
+
function printUsage5(io) {
|
|
7678
|
+
io.err([
|
|
7679
|
+
"usage: launchpad recover [<slug>] [--slug <slug>] [--json]",
|
|
7680
|
+
"",
|
|
7681
|
+
" Repair an app whose registry record is stuck at a terminal",
|
|
7682
|
+
" provisioning failure although the app is actually live (e.g. a",
|
|
7683
|
+
" since-fixed platform bug failed the record after content shipped).",
|
|
7684
|
+
"",
|
|
7685
|
+
" The bot verifies LIVE Cloudflare state first: the record is only",
|
|
7686
|
+
" repaired when the Pages project exists and a successful production",
|
|
7687
|
+
" deployment is serving. A not-live app is refused with what was",
|
|
7688
|
+
" checked — recover never fabricates a live state.",
|
|
7689
|
+
"",
|
|
7690
|
+
" Recovering an already-healthy app is a no-op success.",
|
|
7691
|
+
"",
|
|
7692
|
+
"Flags:",
|
|
7693
|
+
" --slug <slug> override cwd inference.",
|
|
7694
|
+
" --json emit machine-readable JSON to stdout."
|
|
7695
|
+
].join(`
|
|
7696
|
+
`));
|
|
7697
|
+
}
|
|
7698
|
+
|
|
7509
7699
|
// src/commands/status.ts
|
|
7510
|
-
import { readFileSync as
|
|
7700
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
7511
7701
|
var statusCommand = {
|
|
7512
7702
|
name: "status",
|
|
7513
7703
|
summary: "show drift between local launchpad.yaml and deployed state",
|
|
7514
7704
|
run: runStatus
|
|
7515
7705
|
};
|
|
7516
|
-
var
|
|
7706
|
+
var SLUG_RE11 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
7517
7707
|
async function fetchLifecycle(cfg, slug) {
|
|
7518
7708
|
try {
|
|
7519
7709
|
return await apiJson(cfg, { path: `/apps/${slug}/lifecycle` });
|
|
@@ -7545,10 +7735,10 @@ function mapBotError(e, slug, io) {
|
|
|
7545
7735
|
return 2;
|
|
7546
7736
|
}
|
|
7547
7737
|
async function runStatus(args, io) {
|
|
7548
|
-
const parsed = parseArgs9(args);
|
|
7738
|
+
const parsed = parseArgs9(args, process.cwd(), (l) => io.err(l));
|
|
7549
7739
|
if (typeof parsed === "string") {
|
|
7550
7740
|
io.err(`launchpad status: ${parsed}`);
|
|
7551
|
-
|
|
7741
|
+
printUsage6(io);
|
|
7552
7742
|
return 64;
|
|
7553
7743
|
}
|
|
7554
7744
|
const cfg = loadConfig();
|
|
@@ -7562,34 +7752,36 @@ async function runStatus(args, io) {
|
|
|
7562
7752
|
emit3(lifecycleOutput(parsed.slug, lifecycle), parsed.json, io);
|
|
7563
7753
|
return 0;
|
|
7564
7754
|
}
|
|
7565
|
-
let localYaml;
|
|
7755
|
+
let localYaml = null;
|
|
7566
7756
|
try {
|
|
7567
|
-
localYaml =
|
|
7757
|
+
localYaml = readFileSync12(parsed.file, "utf8");
|
|
7568
7758
|
} catch (e) {
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
7759
|
+
if (!isEnoent(e)) {
|
|
7760
|
+
io.err(`launchpad status: cannot read local manifest at ${parsed.file}: ${describe24(e)}`);
|
|
7761
|
+
return 2;
|
|
7572
7762
|
}
|
|
7573
|
-
return 2;
|
|
7574
|
-
}
|
|
7575
|
-
let localObj;
|
|
7576
|
-
try {
|
|
7577
|
-
const { parse: parseYaml5 } = await import("yaml");
|
|
7578
|
-
localObj = parseYaml5(localYaml);
|
|
7579
|
-
} catch (e) {
|
|
7580
|
-
io.err(`launchpad status: ${parsed.file} is not valid YAML: ${describe24(e)}`);
|
|
7581
|
-
return 2;
|
|
7582
7763
|
}
|
|
7583
|
-
|
|
7584
|
-
if (
|
|
7585
|
-
|
|
7586
|
-
|
|
7587
|
-
|
|
7764
|
+
let local = null;
|
|
7765
|
+
if (localYaml !== null) {
|
|
7766
|
+
let localObj;
|
|
7767
|
+
try {
|
|
7768
|
+
const { parse: parseYaml6 } = await import("yaml");
|
|
7769
|
+
localObj = parseYaml6(localYaml);
|
|
7770
|
+
} catch (e) {
|
|
7771
|
+
io.err(`launchpad status: ${parsed.file} is not valid YAML: ${describe24(e)}`);
|
|
7772
|
+
return 2;
|
|
7588
7773
|
}
|
|
7589
|
-
|
|
7774
|
+
const localParse = parseManifest(localObj);
|
|
7775
|
+
if (localParse.kind !== "ok") {
|
|
7776
|
+
io.err(`launchpad status: ${parsed.file} failed schema validation:`);
|
|
7777
|
+
for (const issue of localParse.issues) {
|
|
7778
|
+
io.err(` - ${issue.path}: ${issue.message}`);
|
|
7779
|
+
}
|
|
7780
|
+
return 2;
|
|
7781
|
+
}
|
|
7782
|
+
local = localParse.manifest;
|
|
7783
|
+
warnSecretShape(local.production_env, io);
|
|
7590
7784
|
}
|
|
7591
|
-
const local = localParse.manifest;
|
|
7592
|
-
warnSecretShape(local.production_env, io);
|
|
7593
7785
|
let state;
|
|
7594
7786
|
try {
|
|
7595
7787
|
state = await fetchManifestState(cfg, parsed.slug, { includeManifest: true });
|
|
@@ -7623,6 +7815,7 @@ async function runStatus(args, io) {
|
|
|
7623
7815
|
const out = {
|
|
7624
7816
|
state: contentIsLive ? "live_content_untracked" : liveButEmpty ? "live_no_content" : "no_deployed_manifest",
|
|
7625
7817
|
slug: parsed.slug,
|
|
7818
|
+
...local === null ? { drift: null } : {},
|
|
7626
7819
|
deployedSha: state.lastAppliedManifestSha,
|
|
7627
7820
|
headSha: state.appRepoHeadSha,
|
|
7628
7821
|
hasOpenPr: state.openPr !== null,
|
|
@@ -7635,10 +7828,34 @@ async function runStatus(args, io) {
|
|
|
7635
7828
|
emit3(out, parsed.json, io);
|
|
7636
7829
|
return 0;
|
|
7637
7830
|
}
|
|
7831
|
+
if (local === null) {
|
|
7832
|
+
const out = {
|
|
7833
|
+
state: "live_drift_unknown",
|
|
7834
|
+
slug: parsed.slug,
|
|
7835
|
+
drift: null,
|
|
7836
|
+
deployedSha: state.lastAppliedManifestSha,
|
|
7837
|
+
headSha: state.appRepoHeadSha,
|
|
7838
|
+
hasOpenPr: state.openPr !== null,
|
|
7839
|
+
openPrNumber: state.openPr?.number ?? null,
|
|
7840
|
+
driftFields: [],
|
|
7841
|
+
driftDetails: [],
|
|
7842
|
+
...deploymentKnown ? { deployment } : {},
|
|
7843
|
+
...standingExceptions !== null ? { standingExceptions } : {}
|
|
7844
|
+
};
|
|
7845
|
+
emit3(out, parsed.json, io);
|
|
7846
|
+
if (parsed.strict) {
|
|
7847
|
+
if (deployment?.liveDeployment?.buildStatus === "failure") {
|
|
7848
|
+
io.err(`launchpad status: --strict: live build FAILED ` + `(drift not evaluated — no local launchpad.yaml here).`);
|
|
7849
|
+
return 1;
|
|
7850
|
+
}
|
|
7851
|
+
io.err(`launchpad status: --strict: drift not evaluated — no local ` + `launchpad.yaml here; live state looks healthy, exiting 0.`);
|
|
7852
|
+
}
|
|
7853
|
+
return 0;
|
|
7854
|
+
}
|
|
7638
7855
|
let deployedObj;
|
|
7639
7856
|
try {
|
|
7640
|
-
const { parse:
|
|
7641
|
-
deployedObj =
|
|
7857
|
+
const { parse: parseYaml6 } = await import("yaml");
|
|
7858
|
+
deployedObj = parseYaml6(state.manifestYaml);
|
|
7642
7859
|
} catch (e) {
|
|
7643
7860
|
io.err(`launchpad status: deployed manifest at ${state.lastAppliedManifestSha} is not valid YAML: ${describe24(e)}`);
|
|
7644
7861
|
return 2;
|
|
@@ -7673,17 +7890,17 @@ async function runStatus(args, io) {
|
|
|
7673
7890
|
}
|
|
7674
7891
|
function computeDrift(local, deployed) {
|
|
7675
7892
|
const diffs = [];
|
|
7676
|
-
const cmp = (
|
|
7893
|
+
const cmp = (path11, l, d) => {
|
|
7677
7894
|
const li = l ?? null;
|
|
7678
7895
|
const di = d ?? null;
|
|
7679
7896
|
if (typeof li === "object" || typeof di === "object") {
|
|
7680
7897
|
if (JSON.stringify(li) !== JSON.stringify(di)) {
|
|
7681
|
-
diffs.push({ path:
|
|
7898
|
+
diffs.push({ path: path11, local: l, deployed: d });
|
|
7682
7899
|
}
|
|
7683
7900
|
return;
|
|
7684
7901
|
}
|
|
7685
7902
|
if (li !== di) {
|
|
7686
|
-
diffs.push({ path:
|
|
7903
|
+
diffs.push({ path: path11, local: l, deployed: d });
|
|
7687
7904
|
}
|
|
7688
7905
|
};
|
|
7689
7906
|
cmp("metadata.name", local.metadata.name, deployed.metadata.name);
|
|
@@ -7748,18 +7965,28 @@ function emit3(out, asJson, io) {
|
|
|
7748
7965
|
return;
|
|
7749
7966
|
case "live_no_content":
|
|
7750
7967
|
io.out(`${out.slug}: live — no content deployed yet. Run \`launchpad deploy\`.`);
|
|
7968
|
+
surfaceNoLocalManifestNote(out, io);
|
|
7751
7969
|
surfaceHeadVsDeployed(out, io);
|
|
7752
7970
|
surfaceDeployment(out, io);
|
|
7753
7971
|
surfaceExceptions(out, io);
|
|
7754
7972
|
return;
|
|
7755
7973
|
case "no_deployed_manifest":
|
|
7756
7974
|
io.out(`${out.slug}: no deployed manifest yet — run \`launchpad deploy\`.`);
|
|
7975
|
+
surfaceNoLocalManifestNote(out, io);
|
|
7757
7976
|
surfaceHeadVsDeployed(out, io);
|
|
7758
7977
|
surfaceDeployment(out, io);
|
|
7759
7978
|
surfaceExceptions(out, io);
|
|
7760
7979
|
return;
|
|
7761
7980
|
case "live_content_untracked":
|
|
7762
7981
|
io.out(`${out.slug}: live — content deployed via ` + `${triggerLabel(out.deployment?.liveDeployment?.trigger)} (no platform-tracked manifest; this app deploys outside \`launchpad deploy\`).`);
|
|
7982
|
+
surfaceNoLocalManifestNote(out, io);
|
|
7983
|
+
surfaceHeadVsDeployed(out, io);
|
|
7984
|
+
surfaceDeployment(out, io);
|
|
7985
|
+
surfaceExceptions(out, io);
|
|
7986
|
+
return;
|
|
7987
|
+
case "live_drift_unknown":
|
|
7988
|
+
io.out(`${out.slug}: live` + (out.deployedSha ? ` (content @ ${out.deployedSha.slice(0, 7)})` : ""));
|
|
7989
|
+
surfaceNoLocalManifestNote(out, io);
|
|
7763
7990
|
surfaceHeadVsDeployed(out, io);
|
|
7764
7991
|
surfaceDeployment(out, io);
|
|
7765
7992
|
surfaceExceptions(out, io);
|
|
@@ -7783,6 +8010,11 @@ function emit3(out, asJson, io) {
|
|
|
7783
8010
|
return;
|
|
7784
8011
|
}
|
|
7785
8012
|
}
|
|
8013
|
+
function surfaceNoLocalManifestNote(out, io) {
|
|
8014
|
+
if (out.drift !== null)
|
|
8015
|
+
return;
|
|
8016
|
+
io.out(" no local launchpad.yaml here — drift not checked " + "(cd into the app directory or pass --file to compare)");
|
|
8017
|
+
}
|
|
7786
8018
|
function triggerLabel(trigger) {
|
|
7787
8019
|
if (trigger === "git-push")
|
|
7788
8020
|
return "git push";
|
|
@@ -7870,7 +8102,7 @@ function warnSecretShape(env, io) {
|
|
|
7870
8102
|
}
|
|
7871
8103
|
}
|
|
7872
8104
|
}
|
|
7873
|
-
function parseArgs9(args, cwd = process.cwd()) {
|
|
8105
|
+
function parseArgs9(args, cwd = process.cwd(), warn) {
|
|
7874
8106
|
let slug = null;
|
|
7875
8107
|
let file = "./launchpad.yaml";
|
|
7876
8108
|
let json = false;
|
|
@@ -7917,54 +8149,68 @@ function parseArgs9(args, cwd = process.cwd()) {
|
|
|
7917
8149
|
i += 1;
|
|
7918
8150
|
}
|
|
7919
8151
|
if (slug === null) {
|
|
7920
|
-
const inferred =
|
|
8152
|
+
const inferred = inferSlug({ cwd, file, warn });
|
|
7921
8153
|
if (inferred === null) {
|
|
7922
8154
|
return `slug not provided + cannot infer from cwd. Pass <slug> or --slug <slug>, or cd into a directory named launchpad-app-<slug>.`;
|
|
7923
8155
|
}
|
|
7924
8156
|
slug = inferred;
|
|
7925
8157
|
}
|
|
7926
|
-
if (!
|
|
7927
|
-
return `invalid slug "${slug}" — expected ${
|
|
8158
|
+
if (!SLUG_RE11.test(slug)) {
|
|
8159
|
+
return `invalid slug "${slug}" — expected ${SLUG_RE11.source}`;
|
|
7928
8160
|
}
|
|
7929
8161
|
return { slug, file, json, strict };
|
|
7930
8162
|
}
|
|
7931
|
-
function
|
|
8163
|
+
function printUsage6(io) {
|
|
7932
8164
|
io.err([
|
|
7933
8165
|
"usage: launchpad status [<slug>] [--slug <slug>] [--file <path>] [--json] [--strict]",
|
|
7934
8166
|
"",
|
|
7935
8167
|
" Compare local launchpad.yaml against the deployed state.",
|
|
7936
8168
|
" No local platform-repo or terraform required.",
|
|
7937
8169
|
"",
|
|
7938
|
-
"
|
|
7939
|
-
"
|
|
8170
|
+
" With no explicit slug, it is inferred from (in order): the",
|
|
8171
|
+
" local manifest's declared slug (./launchpad.yaml or --file),",
|
|
8172
|
+
" then a launchpad-app-<slug>/ directory name. An explicit",
|
|
8173
|
+
" --slug or positional always overrides; when the manifest and",
|
|
8174
|
+
" the directory name disagree, the manifest wins (with a note).",
|
|
8175
|
+
"",
|
|
8176
|
+
" With a known slug but NO local manifest, status degrades to",
|
|
8177
|
+
" the live-truth-only view (lifecycle + deployment; drift not",
|
|
8178
|
+
" checked) and exits 0.",
|
|
7940
8179
|
"",
|
|
7941
8180
|
"Flags:",
|
|
7942
8181
|
" --file <path> local manifest path (default: ./launchpad.yaml).",
|
|
7943
|
-
"
|
|
7944
|
-
"
|
|
8182
|
+
" Also consulted for slug inference; --slug or a",
|
|
8183
|
+
" positional slug still overrides.",
|
|
8184
|
+
" --slug <slug> override inference.",
|
|
7945
8185
|
" --json emit machine-readable JSON to stdout.",
|
|
7946
8186
|
" --strict exit 1 on drift. Default is report-only (exit 0).",
|
|
8187
|
+
" With no local manifest, drift can't be evaluated:",
|
|
8188
|
+
" exit 0 unless the live build itself failed.",
|
|
7947
8189
|
"",
|
|
7948
8190
|
"Exit codes:",
|
|
7949
|
-
" 0 = in sync, OR drift in default (report-only) mode
|
|
7950
|
-
"
|
|
7951
|
-
"
|
|
8191
|
+
" 0 = in sync, OR drift in default (report-only) mode, OR the",
|
|
8192
|
+
" live-truth-only view (no local manifest).",
|
|
8193
|
+
" 1 = drift, when --strict is set (or a failed live build under",
|
|
8194
|
+
" --strict with no local manifest).",
|
|
8195
|
+
" 2 = error (network, auth, unreadable/invalid local manifest, etc.)."
|
|
7952
8196
|
].join(`
|
|
7953
8197
|
`));
|
|
7954
8198
|
}
|
|
7955
8199
|
function describe24(e) {
|
|
7956
8200
|
return e instanceof Error ? e.message : String(e);
|
|
7957
8201
|
}
|
|
8202
|
+
function isEnoent(e) {
|
|
8203
|
+
return typeof e === "object" && e !== null && e.code === "ENOENT";
|
|
8204
|
+
}
|
|
7958
8205
|
|
|
7959
8206
|
// src/commands/review.ts
|
|
7960
|
-
import * as
|
|
8207
|
+
import * as path11 from "node:path";
|
|
7961
8208
|
var reviewCommand = {
|
|
7962
8209
|
name: "review",
|
|
7963
8210
|
summary: "show the review state for a PR (slug-scoped)",
|
|
7964
8211
|
run: runReview
|
|
7965
8212
|
};
|
|
7966
|
-
var
|
|
7967
|
-
var DIRNAME_RE5 = /^launchpad-app-([a-z0-9][a-z0-9-]*[a-z0-9])$/;
|
|
8213
|
+
var SLUG_RE12 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
7968
8214
|
async function runReview(args, io) {
|
|
7969
8215
|
const parsed = parseArgs10(args);
|
|
7970
8216
|
if (parsed === null) {
|
|
@@ -7976,16 +8222,16 @@ async function runReview(args, io) {
|
|
|
7976
8222
|
if (parsed.slug !== null) {
|
|
7977
8223
|
slug = parsed.slug;
|
|
7978
8224
|
} else {
|
|
7979
|
-
const inferred =
|
|
8225
|
+
const inferred = inferSlug({ cwd: process.cwd(), warn: (l) => io.err(l) });
|
|
7980
8226
|
if (inferred === null) {
|
|
7981
|
-
io.err(`launchpad review: could not infer slug from cwd (${
|
|
8227
|
+
io.err(`launchpad review: could not infer slug from cwd (${path11.basename(process.cwd())});
|
|
7982
8228
|
` + ` pass --slug <slug>, or cd into a directory named launchpad-app-<slug>.`);
|
|
7983
8229
|
return 64;
|
|
7984
8230
|
}
|
|
7985
8231
|
slug = inferred;
|
|
7986
8232
|
}
|
|
7987
|
-
if (!
|
|
7988
|
-
io.err(`launchpad review: invalid slug "${slug}" — expected ${
|
|
8233
|
+
if (!SLUG_RE12.test(slug)) {
|
|
8234
|
+
io.err(`launchpad review: invalid slug "${slug}" — expected ${SLUG_RE12.source}`);
|
|
7989
8235
|
return 64;
|
|
7990
8236
|
}
|
|
7991
8237
|
try {
|
|
@@ -8060,11 +8306,6 @@ function parseArgs10(args) {
|
|
|
8060
8306
|
}
|
|
8061
8307
|
return { slug, prNumber };
|
|
8062
8308
|
}
|
|
8063
|
-
function inferSlugFromCwd5(cwd) {
|
|
8064
|
-
const base = path10.basename(cwd);
|
|
8065
|
-
const m = base.match(DIRNAME_RE5);
|
|
8066
|
-
return m === null ? null : m[1];
|
|
8067
|
-
}
|
|
8068
8309
|
function renderReview(r, io) {
|
|
8069
8310
|
const rv = r.review;
|
|
8070
8311
|
io.out(`Review of PR #${rv.prNumber} (${rv.slug})`);
|
|
@@ -8116,7 +8357,7 @@ function describe25(e) {
|
|
|
8116
8357
|
}
|
|
8117
8358
|
|
|
8118
8359
|
// src/deploy/rollback.ts
|
|
8119
|
-
import { existsSync as
|
|
8360
|
+
import { existsSync as existsSync8, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "node:fs";
|
|
8120
8361
|
import { resolve as resolvePath2 } from "node:path";
|
|
8121
8362
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
8122
8363
|
|
|
@@ -8240,16 +8481,16 @@ async function runRollback(opts, io, deps = {}) {
|
|
|
8240
8481
|
yes: true
|
|
8241
8482
|
}, io, applyDeps);
|
|
8242
8483
|
}
|
|
8243
|
-
function readCurrentManifest(
|
|
8244
|
-
if (!
|
|
8484
|
+
function readCurrentManifest(path12) {
|
|
8485
|
+
if (!existsSync8(path12))
|
|
8245
8486
|
return null;
|
|
8246
8487
|
let raw;
|
|
8247
8488
|
try {
|
|
8248
|
-
raw =
|
|
8489
|
+
raw = readFileSync13(path12, "utf8");
|
|
8249
8490
|
} catch {
|
|
8250
8491
|
return null;
|
|
8251
8492
|
}
|
|
8252
|
-
const parsed = parseManifest2(raw,
|
|
8493
|
+
const parsed = parseManifest2(raw, path12);
|
|
8253
8494
|
if (parsed.kind !== "ok")
|
|
8254
8495
|
return null;
|
|
8255
8496
|
return summarise(parsed.manifest);
|
|
@@ -8401,7 +8642,7 @@ function parseArgs11(args) {
|
|
|
8401
8642
|
}
|
|
8402
8643
|
|
|
8403
8644
|
// src/secrets/push.ts
|
|
8404
|
-
import { existsSync as
|
|
8645
|
+
import { existsSync as existsSync9, readFileSync as readFileSync14 } from "node:fs";
|
|
8405
8646
|
import { resolve as resolvePath3 } from "node:path";
|
|
8406
8647
|
|
|
8407
8648
|
// src/secrets/env-parse.ts
|
|
@@ -8456,14 +8697,14 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
8456
8697
|
return 0;
|
|
8457
8698
|
}
|
|
8458
8699
|
const envPath = resolvePath3(process.cwd(), opts.env ?? ".env");
|
|
8459
|
-
if (!
|
|
8700
|
+
if (!existsSync9(envPath)) {
|
|
8460
8701
|
io.err(`launchpad secrets push: ${envPath}`);
|
|
8461
8702
|
io.err(" .env file not found. Run `launchpad secrets template` to scaffold one.");
|
|
8462
8703
|
return 2;
|
|
8463
8704
|
}
|
|
8464
8705
|
let envText;
|
|
8465
8706
|
try {
|
|
8466
|
-
envText =
|
|
8707
|
+
envText = readFileSync14(envPath, "utf8");
|
|
8467
8708
|
} catch (e) {
|
|
8468
8709
|
io.err(`launchpad secrets push: failed to read ${envPath}: ${describe27(e)}`);
|
|
8469
8710
|
return 2;
|
|
@@ -8735,32 +8976,32 @@ function renderManifestError5(result, io) {
|
|
|
8735
8976
|
}
|
|
8736
8977
|
|
|
8737
8978
|
// src/secrets/set.ts
|
|
8738
|
-
import { existsSync as
|
|
8979
|
+
import { existsSync as existsSync10, readFileSync as readFileSync15 } from "node:fs";
|
|
8739
8980
|
import { resolve as resolvePath5 } from "node:path";
|
|
8740
|
-
import { parse as
|
|
8981
|
+
import { parse as parseYaml6 } from "yaml";
|
|
8741
8982
|
var CELL_LABEL2 = {
|
|
8742
8983
|
present: "PRESENT",
|
|
8743
8984
|
missing: "MISSING",
|
|
8744
8985
|
not_deployed: "NOT_DEPLOYED"
|
|
8745
8986
|
};
|
|
8746
8987
|
function loadSet(fleetFile, setName, io) {
|
|
8747
|
-
const
|
|
8748
|
-
if (!
|
|
8749
|
-
io.err(`✗ ${
|
|
8988
|
+
const path12 = resolvePath5(process.cwd(), fleetFile ?? "fleet-secret-sets.yaml");
|
|
8989
|
+
if (!existsSync10(path12)) {
|
|
8990
|
+
io.err(`✗ ${path12}`);
|
|
8750
8991
|
io.err(" fleet-secret-sets.yaml not found. Run from the platform repo root or pass --fleet-file.");
|
|
8751
8992
|
return 2;
|
|
8752
8993
|
}
|
|
8753
8994
|
let obj;
|
|
8754
8995
|
try {
|
|
8755
|
-
obj =
|
|
8996
|
+
obj = parseYaml6(readFileSync15(path12, "utf8"));
|
|
8756
8997
|
} catch (e) {
|
|
8757
|
-
io.err(`✗ ${
|
|
8998
|
+
io.err(`✗ ${path12}`);
|
|
8758
8999
|
io.err(` YAML parse error: ${e instanceof Error ? e.message : String(e)}`);
|
|
8759
9000
|
return 1;
|
|
8760
9001
|
}
|
|
8761
9002
|
const parsed = parseFleetSecretSets(obj);
|
|
8762
9003
|
if (!parsed.ok) {
|
|
8763
|
-
io.err(`✗ ${
|
|
9004
|
+
io.err(`✗ ${path12}`);
|
|
8764
9005
|
io.err(` ${parsed.issues.length} schema issue(s):`);
|
|
8765
9006
|
for (const i of parsed.issues)
|
|
8766
9007
|
io.err(` ${i.path}: ${i.message}`);
|
|
@@ -8769,7 +9010,7 @@ function loadSet(fleetFile, setName, io) {
|
|
|
8769
9010
|
const set = parsed.manifest.secretSets.find((s) => s.name === setName);
|
|
8770
9011
|
if (set === undefined) {
|
|
8771
9012
|
const names = parsed.manifest.secretSets.map((s) => s.name).join(", ");
|
|
8772
|
-
io.err(`✗ secret-set "${setName}" not found in ${
|
|
9013
|
+
io.err(`✗ secret-set "${setName}" not found in ${path12}`);
|
|
8773
9014
|
io.err(` declared sets: ${names}`);
|
|
8774
9015
|
return 1;
|
|
8775
9016
|
}
|
|
@@ -8837,14 +9078,14 @@ async function runSecretsPushSet(opts, io, deps = {}) {
|
|
|
8837
9078
|
if (typeof set === "number")
|
|
8838
9079
|
return set;
|
|
8839
9080
|
const envPath = resolvePath5(process.cwd(), opts.env ?? ".env");
|
|
8840
|
-
if (!
|
|
9081
|
+
if (!existsSync10(envPath)) {
|
|
8841
9082
|
io.err(`launchpad secrets push --set: ${envPath} not found.`);
|
|
8842
9083
|
io.err(` Create a .env carrying the set's secrets: ${set.secrets.join(", ")}`);
|
|
8843
9084
|
return 2;
|
|
8844
9085
|
}
|
|
8845
9086
|
let envText;
|
|
8846
9087
|
try {
|
|
8847
|
-
envText =
|
|
9088
|
+
envText = readFileSync15(envPath, "utf8");
|
|
8848
9089
|
} catch (e) {
|
|
8849
9090
|
io.err(`launchpad secrets push --set: failed to read ${envPath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
8850
9091
|
return 2;
|
|
@@ -8988,8 +9229,8 @@ function setPushExit(e) {
|
|
|
8988
9229
|
}
|
|
8989
9230
|
|
|
8990
9231
|
// src/commands/secrets-template.ts
|
|
8991
|
-
import { existsSync as
|
|
8992
|
-
import { resolve as
|
|
9232
|
+
import { existsSync as existsSync11, writeFileSync as writeFileSync6 } from "node:fs";
|
|
9233
|
+
import { resolve as resolve10 } from "node:path";
|
|
8993
9234
|
async function runSecretsTemplate(args, io) {
|
|
8994
9235
|
const flags = parseFlags4(args);
|
|
8995
9236
|
if (flags.kind === "usage-error") {
|
|
@@ -8997,7 +9238,7 @@ async function runSecretsTemplate(args, io) {
|
|
|
8997
9238
|
io.err("Usage: launchpad secrets template [--file <path>] [--out <path>] " + "[--stdout] [--force] [--include-platform-managed]");
|
|
8998
9239
|
return 64;
|
|
8999
9240
|
}
|
|
9000
|
-
const manifestPath =
|
|
9241
|
+
const manifestPath = resolve10(process.cwd(), flags.file ?? "launchpad.yaml");
|
|
9001
9242
|
const result = loadManifest(manifestPath);
|
|
9002
9243
|
const renderResult = renderManifest(result, io);
|
|
9003
9244
|
if (renderResult.kind !== "ok") {
|
|
@@ -9011,8 +9252,8 @@ async function runSecretsTemplate(args, io) {
|
|
|
9011
9252
|
}
|
|
9012
9253
|
return 0;
|
|
9013
9254
|
}
|
|
9014
|
-
const outPath =
|
|
9015
|
-
if (
|
|
9255
|
+
const outPath = resolve10(process.cwd(), flags.out);
|
|
9256
|
+
if (existsSync11(outPath) && !flags.force) {
|
|
9016
9257
|
io.err(`launchpad secrets template: ${outPath} already exists`);
|
|
9017
9258
|
io.err("Pass --force to overwrite, or --stdout to print without writing.");
|
|
9018
9259
|
return 64;
|
|
@@ -9300,8 +9541,8 @@ function printHelp2(io) {
|
|
|
9300
9541
|
|
|
9301
9542
|
// src/commands/skills.ts
|
|
9302
9543
|
import { fileURLToPath } from "node:url";
|
|
9303
|
-
import { dirname as dirname6, join as join11, resolve as
|
|
9304
|
-
import { promises as fs5, existsSync as
|
|
9544
|
+
import { dirname as dirname6, join as join11, resolve as resolve11 } from "node:path";
|
|
9545
|
+
import { promises as fs5, existsSync as existsSync12 } from "node:fs";
|
|
9305
9546
|
import { homedir as homedir2 } from "node:os";
|
|
9306
9547
|
var BUNDLE_PREFIX = "launchpad-";
|
|
9307
9548
|
var BUNDLED_SKILLS = [
|
|
@@ -9368,11 +9609,11 @@ function resolveInstallEnv() {
|
|
|
9368
9609
|
function defaultBundleDir() {
|
|
9369
9610
|
const here = dirname6(fileURLToPath(import.meta.url));
|
|
9370
9611
|
const candidates = [
|
|
9371
|
-
|
|
9372
|
-
|
|
9612
|
+
resolve11(here, "..", "skills"),
|
|
9613
|
+
resolve11(here, "..", "..", "skills")
|
|
9373
9614
|
];
|
|
9374
9615
|
for (const c of candidates) {
|
|
9375
|
-
if (
|
|
9616
|
+
if (existsSync12(join11(c, "launchpad-onboard", "SKILL.md"))) {
|
|
9376
9617
|
return c;
|
|
9377
9618
|
}
|
|
9378
9619
|
}
|
|
@@ -9452,10 +9693,10 @@ async function doList(io) {
|
|
|
9452
9693
|
}
|
|
9453
9694
|
return 0;
|
|
9454
9695
|
}
|
|
9455
|
-
async function readVersion(
|
|
9696
|
+
async function readVersion(path12) {
|
|
9456
9697
|
let text;
|
|
9457
9698
|
try {
|
|
9458
|
-
text = await fs5.readFile(
|
|
9699
|
+
text = await fs5.readFile(path12, "utf8");
|
|
9459
9700
|
} catch {
|
|
9460
9701
|
return null;
|
|
9461
9702
|
}
|
|
@@ -9465,9 +9706,9 @@ async function readVersion(path11) {
|
|
|
9465
9706
|
const m = /^version:\s*(.+?)\s*$/m.exec(front);
|
|
9466
9707
|
return m === null ? null : m[1] ?? null;
|
|
9467
9708
|
}
|
|
9468
|
-
async function isDir(
|
|
9709
|
+
async function isDir(path12) {
|
|
9469
9710
|
try {
|
|
9470
|
-
const stat = await fs5.stat(
|
|
9711
|
+
const stat = await fs5.stat(path12);
|
|
9471
9712
|
return stat.isDirectory();
|
|
9472
9713
|
} catch {
|
|
9473
9714
|
return false;
|
|
@@ -9481,9 +9722,9 @@ function describe28(e) {
|
|
|
9481
9722
|
import { execFile, spawn as spawn5 } from "node:child_process";
|
|
9482
9723
|
import { promisify } from "node:util";
|
|
9483
9724
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
9484
|
-
import { dirname as dirname7, resolve as
|
|
9725
|
+
import { dirname as dirname7, resolve as resolve12, relative as relative4, isAbsolute as isAbsolute2, join as join12 } from "node:path";
|
|
9485
9726
|
import { homedir as homedir3, tmpdir } from "node:os";
|
|
9486
|
-
import { readFileSync as
|
|
9727
|
+
import { readFileSync as readFileSync16, mkdtempSync, writeFileSync as writeFileSync7, rmSync as rmSync2 } from "node:fs";
|
|
9487
9728
|
|
|
9488
9729
|
// src/commands/channel-auth.ts
|
|
9489
9730
|
import { createServer as createServer2 } from "node:http";
|
|
@@ -9526,9 +9767,9 @@ async function startLoopback(state, timeoutMs) {
|
|
|
9526
9767
|
resolveCode(code);
|
|
9527
9768
|
}
|
|
9528
9769
|
});
|
|
9529
|
-
const bound = await new Promise((
|
|
9530
|
-
server.once("error", () =>
|
|
9531
|
-
server.listen(0, "127.0.0.1", () =>
|
|
9770
|
+
const bound = await new Promise((resolve12) => {
|
|
9771
|
+
server.once("error", () => resolve12(false));
|
|
9772
|
+
server.listen(0, "127.0.0.1", () => resolve12(true));
|
|
9532
9773
|
});
|
|
9533
9774
|
if (!bound)
|
|
9534
9775
|
return null;
|
|
@@ -9712,7 +9953,7 @@ function resolveLatestVersion() {
|
|
|
9712
9953
|
}
|
|
9713
9954
|
function detectInstallChannel() {
|
|
9714
9955
|
try {
|
|
9715
|
-
return
|
|
9956
|
+
return readFileSync16(CHANNEL_MARKER, "utf8").trim() === "platform" ? "platform" : "github";
|
|
9716
9957
|
} catch {
|
|
9717
9958
|
return "github";
|
|
9718
9959
|
}
|
|
@@ -9775,7 +10016,7 @@ function errStderr(e) {
|
|
|
9775
10016
|
return "";
|
|
9776
10017
|
}
|
|
9777
10018
|
async function detectPackageManager2() {
|
|
9778
|
-
const pkgRoot =
|
|
10019
|
+
const pkgRoot = resolve12(dirname7(fileURLToPath2(import.meta.url)), "..", "..");
|
|
9779
10020
|
const candidates = [];
|
|
9780
10021
|
const npmRoot = await pmRoot("npm");
|
|
9781
10022
|
if (npmRoot !== null)
|
|
@@ -9783,7 +10024,7 @@ async function detectPackageManager2() {
|
|
|
9783
10024
|
const pnpmRoot = await pmRoot("pnpm");
|
|
9784
10025
|
if (pnpmRoot !== null)
|
|
9785
10026
|
candidates.push(["pnpm", pnpmRoot]);
|
|
9786
|
-
candidates.push(["bun",
|
|
10027
|
+
candidates.push(["bun", resolve12(homedir3(), ".bun/install/global/node_modules")]);
|
|
9787
10028
|
const matches = candidates.filter(([, root]) => pathContains(root, pkgRoot));
|
|
9788
10029
|
return matches.length === 1 ? matches[0][0] : null;
|
|
9789
10030
|
}
|
|
@@ -9919,8 +10160,8 @@ function printHelp4(io) {
|
|
|
9919
10160
|
}
|
|
9920
10161
|
|
|
9921
10162
|
// src/commands/validate.ts
|
|
9922
|
-
import { readFileSync as
|
|
9923
|
-
import { dirname as dirname8, resolve as
|
|
10163
|
+
import { readFileSync as readFileSync17 } from "node:fs";
|
|
10164
|
+
import { dirname as dirname8, resolve as resolve13 } from "node:path";
|
|
9924
10165
|
var validateCommand = {
|
|
9925
10166
|
name: "validate",
|
|
9926
10167
|
summary: "validate launchpad.yaml against the v1alpha1 schema",
|
|
@@ -9934,12 +10175,12 @@ async function runValidate(args, io) {
|
|
|
9934
10175
|
return 64;
|
|
9935
10176
|
}
|
|
9936
10177
|
const { file, json, strictGroups } = parseResult;
|
|
9937
|
-
const
|
|
9938
|
-
const result = loadManifest(
|
|
10178
|
+
const path12 = resolve13(process.cwd(), file ?? "launchpad.yaml");
|
|
10179
|
+
const result = loadManifest(path12);
|
|
9939
10180
|
if (result.kind !== "ok") {
|
|
9940
10181
|
return json ? renderJsonError(result, io) : renderHumanError(result, io);
|
|
9941
10182
|
}
|
|
9942
|
-
const boundary = checkBoundary(
|
|
10183
|
+
const boundary = checkBoundary(path12, result.manifest.app !== undefined);
|
|
9943
10184
|
const groupCheck = strictGroups ? await checkGroups(allowedEntraGroups(result.manifest.access)) : { kind: "skipped" };
|
|
9944
10185
|
return json ? renderJsonOk(result, groupCheck, boundary, io) : renderHumanOk(result, groupCheck, boundary, io);
|
|
9945
10186
|
}
|
|
@@ -9953,7 +10194,7 @@ function checkBoundary(manifestPath, declared) {
|
|
|
9953
10194
|
}
|
|
9954
10195
|
let manifestYaml;
|
|
9955
10196
|
try {
|
|
9956
|
-
manifestYaml =
|
|
10197
|
+
manifestYaml = readFileSync17(manifestPath, "utf8");
|
|
9957
10198
|
} catch {
|
|
9958
10199
|
manifestYaml = null;
|
|
9959
10200
|
}
|
|
@@ -10268,14 +10509,14 @@ function describe30(e) {
|
|
|
10268
10509
|
import { spawn as spawn6 } from "node:child_process";
|
|
10269
10510
|
import { homedir as homedir4 } from "node:os";
|
|
10270
10511
|
import { join as join13 } from "node:path";
|
|
10271
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
10512
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync18, writeFileSync as writeFileSync8 } from "node:fs";
|
|
10272
10513
|
var INTERNAL_REFRESH_VERB = "__refresh-update-cache";
|
|
10273
10514
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
10274
10515
|
var OPT_OUT_ENV = "LAUNCHPAD_NO_UPDATE_NOTIFIER";
|
|
10275
10516
|
var CACHE_FILE = join13(homedir4(), ".launchpad", "update-check.json");
|
|
10276
10517
|
function readCache2() {
|
|
10277
10518
|
try {
|
|
10278
|
-
const raw = JSON.parse(
|
|
10519
|
+
const raw = JSON.parse(readFileSync18(CACHE_FILE, "utf8"));
|
|
10279
10520
|
if (typeof raw === "object" && raw !== null && typeof raw.checkedAt === "number") {
|
|
10280
10521
|
const latest = raw.latest;
|
|
10281
10522
|
return {
|
|
@@ -10391,6 +10632,7 @@ var COMMANDS = [
|
|
|
10391
10632
|
generateCommand,
|
|
10392
10633
|
pullCommand,
|
|
10393
10634
|
statusCommand,
|
|
10635
|
+
recoverCommand,
|
|
10394
10636
|
destroyCommand,
|
|
10395
10637
|
rollbackCommand,
|
|
10396
10638
|
groupsCommand,
|