@m-kopa/launchpad-cli 0.54.1 → 0.56.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 +33 -0
- package/dist/bundle/upload.d.ts +4 -0
- package/dist/bundle/upload.d.ts.map +1 -1
- package/dist/cli.js +1272 -591
- package/dist/commands/admin.d.ts +10 -0
- package/dist/commands/admin.d.ts.map +1 -0
- package/dist/commands/deploy-flags.d.ts +2 -0
- package/dist/commands/deploy-flags.d.ts.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/generate.d.ts.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/status.d.ts +5 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/deploy/access-drift-offer.d.ts +2 -0
- package/dist/deploy/access-drift-offer.d.ts.map +1 -1
- package/dist/deploy/apply.d.ts +2 -0
- package/dist/deploy/apply.d.ts.map +1 -1
- package/dist/deploy/deployment-status.d.ts.map +1 -1
- package/dist/deploy/manifest-state.d.ts +2 -1
- package/dist/deploy/manifest-state.d.ts.map +1 -1
- package/dist/deploy/redact-diagnostic.d.ts.map +1 -1
- package/dist/dispatcher.d.ts.map +1 -1
- package/dist/manifest/container-filesystem.d.ts +19 -0
- package/dist/manifest/container-filesystem.d.ts.map +1 -0
- package/dist/manifest/runtime-capability.d.ts +22 -0
- package/dist/manifest/runtime-capability.d.ts.map +1 -0
- package/dist/status-page-output.d.ts +12 -0
- package/dist/status-page-output.d.ts.map +1 -0
- package/dist/version.d.ts +1 -1
- package/dist/watch/index.d.ts +3 -0
- package/dist/watch/index.d.ts.map +1 -1
- package/dist/watch/model.d.ts +1 -0
- package/dist/watch/model.d.ts.map +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 +2 -2
package/dist/cli.js
CHANGED
|
@@ -19,34 +19,49 @@ 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.56.0";
|
|
23
23
|
|
|
24
24
|
// src/config.ts
|
|
25
|
+
import { readFileSync } from "node:fs";
|
|
25
26
|
import * as os from "node:os";
|
|
26
27
|
import * as path from "node:path";
|
|
27
28
|
var DEFAULT_BOT_URL = "https://launchpad-portal-bot.mkopa-launchpad.workers.dev";
|
|
28
29
|
var DEFAULT_AUTH_GATEWAY_URL = "https://auth.launchpad.m-kopa.us";
|
|
30
|
+
var DEFAULT_STATUS_PAGE_HOST = "status.launchpad.m-kopa.us";
|
|
29
31
|
function loadConfig(env = process.env) {
|
|
30
32
|
const rawBotUrl = env.LAUNCHPAD_BOT_URL ?? DEFAULT_BOT_URL;
|
|
31
33
|
const botUrl = rawBotUrl.replace(/\/+$/, "");
|
|
32
34
|
const rawGatewayUrl = env.LAUNCHPAD_AUTH_GATEWAY_URL ?? DEFAULT_AUTH_GATEWAY_URL;
|
|
33
35
|
const authGatewayUrl = rawGatewayUrl.replace(/\/+$/, "");
|
|
36
|
+
const statusPageHost = (env.LAUNCHPAD_STATUS_PAGE_HOST ?? DEFAULT_STATUS_PAGE_HOST).trim().toLowerCase();
|
|
34
37
|
const authLegacy = env.LAUNCHPAD_AUTH_LEGACY === "1";
|
|
35
38
|
const sessionPath = env.LAUNCHPAD_SESSION_PATH ?? path.join(os.homedir(), ".launchpad", "session.json");
|
|
36
39
|
const cacheDir = env.LAUNCHPAD_CACHE_DIR ?? path.join(os.homedir(), ".launchpad", "cache");
|
|
37
40
|
const stateDir = env.LAUNCHPAD_STATE_DIR ?? path.join(os.homedir(), ".launchpad", "state");
|
|
38
41
|
const rawPlatformRepo = env.LAUNCHPAD_PLATFORM_REPO;
|
|
39
42
|
const platformRepoPath = rawPlatformRepo !== undefined && rawPlatformRepo.length > 0 ? path.resolve(rawPlatformRepo) : null;
|
|
43
|
+
const configPath = env.LAUNCHPAD_CONFIG_PATH ?? path.join(os.homedir(), ".launchpad", "config.json");
|
|
44
|
+
const statusPageAutoOpen = readStatusPageAutoOpen(configPath);
|
|
40
45
|
return {
|
|
41
46
|
botUrl,
|
|
42
47
|
authGatewayUrl,
|
|
48
|
+
statusPageHost,
|
|
43
49
|
authLegacy,
|
|
44
50
|
sessionPath,
|
|
45
51
|
cacheDir,
|
|
46
52
|
stateDir,
|
|
47
|
-
platformRepoPath
|
|
53
|
+
platformRepoPath,
|
|
54
|
+
statusPageAutoOpen
|
|
48
55
|
};
|
|
49
56
|
}
|
|
57
|
+
function readStatusPageAutoOpen(configPath) {
|
|
58
|
+
try {
|
|
59
|
+
const value = JSON.parse(readFileSync(configPath, "utf8"));
|
|
60
|
+
return !(typeof value === "object" && value !== null && value.statusPageAutoOpen === false);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return error.code === "ENOENT";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
50
65
|
|
|
51
66
|
// src/http/error-envelope.ts
|
|
52
67
|
var MAX_TEXT_CHARS = 16384;
|
|
@@ -1871,7 +1886,7 @@ import { existsSync as existsSync7 } from "node:fs";
|
|
|
1871
1886
|
import * as path8 from "node:path";
|
|
1872
1887
|
|
|
1873
1888
|
// src/bundle/orchestrate.ts
|
|
1874
|
-
import { readFileSync as
|
|
1889
|
+
import { readFileSync as readFileSync5 } from "node:fs";
|
|
1875
1890
|
import { join as join7 } from "node:path";
|
|
1876
1891
|
|
|
1877
1892
|
// src/deploy/tar-pack.ts
|
|
@@ -2183,7 +2198,8 @@ function deployBundleError(envelope, body) {
|
|
|
2183
2198
|
...paths === undefined ? {} : { paths },
|
|
2184
2199
|
...typeof body.reason === "string" ? { reason: body.reason } : {},
|
|
2185
2200
|
...typeof body.minimum === "string" ? { minimum: body.minimum } : {},
|
|
2186
|
-
...typeof body.yours === "string" ? { yours: body.yours } : {}
|
|
2201
|
+
...typeof body.yours === "string" ? { yours: body.yours } : {},
|
|
2202
|
+
...typeof body.statusPageUrl === "string" ? { statusPageUrl: body.statusPageUrl } : {}
|
|
2187
2203
|
};
|
|
2188
2204
|
}
|
|
2189
2205
|
function readStringArray(value) {
|
|
@@ -2207,7 +2223,7 @@ function isRecord2(value) {
|
|
|
2207
2223
|
}
|
|
2208
2224
|
|
|
2209
2225
|
// src/bundle/cwd-walker.ts
|
|
2210
|
-
import { existsSync, lstatSync, readFileSync, readdirSync } from "node:fs";
|
|
2226
|
+
import { existsSync, lstatSync, readFileSync as readFileSync2, readdirSync } from "node:fs";
|
|
2211
2227
|
import { join as join4, relative as relative2, sep } from "node:path";
|
|
2212
2228
|
var DEFAULT_IGNORE = [
|
|
2213
2229
|
".git",
|
|
@@ -2313,7 +2329,7 @@ function loadIgnore(cwd) {
|
|
|
2313
2329
|
}
|
|
2314
2330
|
let content;
|
|
2315
2331
|
try {
|
|
2316
|
-
content =
|
|
2332
|
+
content = readFileSync2(file, "utf8");
|
|
2317
2333
|
} catch {
|
|
2318
2334
|
return { matches: () => false };
|
|
2319
2335
|
}
|
|
@@ -2387,7 +2403,7 @@ function globToRegExp(glob) {
|
|
|
2387
2403
|
}
|
|
2388
2404
|
|
|
2389
2405
|
// src/bundle/cron-bundle.ts
|
|
2390
|
-
import { readFileSync as
|
|
2406
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
2391
2407
|
import { dirname as dirname4, join as join5, resolve as resolve5 } from "node:path";
|
|
2392
2408
|
import { parse as parseYaml } from "yaml";
|
|
2393
2409
|
import { build as esbuild } from "esbuild";
|
|
@@ -2467,7 +2483,7 @@ function locateWorkerConfig(cwd, walkFiles, script) {
|
|
|
2467
2483
|
const abs = resolve5(cwd, rel);
|
|
2468
2484
|
let text;
|
|
2469
2485
|
try {
|
|
2470
|
-
text =
|
|
2486
|
+
text = readFileSync3(abs, "utf8");
|
|
2471
2487
|
} catch {
|
|
2472
2488
|
continue;
|
|
2473
2489
|
}
|
|
@@ -2546,7 +2562,7 @@ async function buildWorkerArtifact(cwd, manifestYaml, walkFiles) {
|
|
|
2546
2562
|
}
|
|
2547
2563
|
|
|
2548
2564
|
// src/bundle/boundary.ts
|
|
2549
|
-
import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as
|
|
2565
|
+
import { existsSync as existsSync2, lstatSync as lstatSync2, readFileSync as readFileSync4 } from "node:fs";
|
|
2550
2566
|
import { join as join6 } from "node:path";
|
|
2551
2567
|
import { parse as parseYaml2 } from "yaml";
|
|
2552
2568
|
|
|
@@ -3469,6 +3485,19 @@ var ManifestV2Schema = z2.object({
|
|
|
3469
3485
|
spec: SpecSchema,
|
|
3470
3486
|
status: StatusSchema.optional()
|
|
3471
3487
|
}).strict();
|
|
3488
|
+
function parseManifestV2(input) {
|
|
3489
|
+
const result = ManifestV2Schema.safeParse(input);
|
|
3490
|
+
if (result.success) {
|
|
3491
|
+
return { kind: "ok", manifest: result.data };
|
|
3492
|
+
}
|
|
3493
|
+
return {
|
|
3494
|
+
kind: "schema-error",
|
|
3495
|
+
issues: result.error.issues.map((issue) => ({
|
|
3496
|
+
path: issue.path.map(String).join(".") || "(root)",
|
|
3497
|
+
message: issue.message
|
|
3498
|
+
}))
|
|
3499
|
+
};
|
|
3500
|
+
}
|
|
3472
3501
|
// ../launchpad-engine/dist/secrets-audit.js
|
|
3473
3502
|
function auditSecretPresence(bindings, surfaces) {
|
|
3474
3503
|
const byId = new Map(surfaces.map((s) => [s.id, s]));
|
|
@@ -3588,298 +3617,569 @@ function deriveResources(m) {
|
|
|
3588
3617
|
}
|
|
3589
3618
|
return items;
|
|
3590
3619
|
}
|
|
3591
|
-
// ../launchpad-engine/dist/
|
|
3592
|
-
var
|
|
3593
|
-
var
|
|
3594
|
-
|
|
3595
|
-
|
|
3620
|
+
// ../launchpad-engine/dist/container-runtime-capability.js
|
|
3621
|
+
var MAX_PATH_BYTES = 256;
|
|
3622
|
+
var MAX_ENV_NAMES = 64;
|
|
3623
|
+
var MAX_ENV_NAME_BYTES = 64;
|
|
3624
|
+
var CONTAINER_NAME = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
3625
|
+
var ENV_NAME = /^[A-Z][A-Z0-9_]*$/;
|
|
3626
|
+
var IMAGE_SEGMENT = /^[A-Za-z0-9._-]+$/;
|
|
3627
|
+
var CONTROL_CHARACTER = /[\u0000-\u001f\u007f]/;
|
|
3628
|
+
var RESERVED_ENV_PREFIXES = ["LAUNCHPAD_", "CF_ACCESS_", "CLOUDFLARE_"];
|
|
3629
|
+
var RESERVED_ENV_NAMES = new Set([
|
|
3630
|
+
"BINDINGS_PROXY_SECRET",
|
|
3631
|
+
"BINDINGS_PROXY_URL"
|
|
3632
|
+
]);
|
|
3633
|
+
function validateDeploymentCapability(manifest) {
|
|
3634
|
+
const view = deploymentView(manifest);
|
|
3635
|
+
if (view.type !== "container") {
|
|
3596
3636
|
return {
|
|
3597
|
-
kind: "
|
|
3598
|
-
|
|
3637
|
+
kind: "supported",
|
|
3638
|
+
manifestValid: true,
|
|
3639
|
+
runtimeSupported: true,
|
|
3640
|
+
runtime: "pages"
|
|
3599
3641
|
};
|
|
3600
3642
|
}
|
|
3601
|
-
|
|
3602
|
-
|
|
3643
|
+
if (view.containers.length !== 1) {
|
|
3644
|
+
return unsupported("unsupported_container_topology", "containers", `the container runtime supports exactly one container; found ${view.containers.length}`);
|
|
3645
|
+
}
|
|
3646
|
+
const container = view.containers[0];
|
|
3647
|
+
if (byteLength(container.name) > 32 || container.name.length < 2 || !CONTAINER_NAME.test(container.name)) {
|
|
3648
|
+
return unsupported("invalid_container_name", "containers.0.name", "container name must be a 2-32 character lowercase slug");
|
|
3649
|
+
}
|
|
3650
|
+
if (container.port < 1024 || container.port > 65535) {
|
|
3651
|
+
return unsupported("unsupported_container_port", "containers.0.port", "container port must be between 1024 and 65535");
|
|
3652
|
+
}
|
|
3653
|
+
if (!validImagePath(container.image)) {
|
|
3654
|
+
return unsupported("invalid_container_image", "containers.0.image", "container image must be a contained repository-relative Dockerfile path beginning ./");
|
|
3655
|
+
}
|
|
3656
|
+
if (!validHealthcheck(container.healthcheck)) {
|
|
3657
|
+
return unsupported("invalid_container_healthcheck", "containers.0.healthcheck", "container healthcheck must be a simple absolute path without traversal, query, or fragment");
|
|
3658
|
+
}
|
|
3659
|
+
const environmentNames = container.env ?? [];
|
|
3660
|
+
if (environmentNames.length > MAX_ENV_NAMES) {
|
|
3661
|
+
return unsupported("too_many_container_environment_names", "containers.0.env", `container env may contain at most ${MAX_ENV_NAMES} names`);
|
|
3662
|
+
}
|
|
3663
|
+
const seen = new Set;
|
|
3664
|
+
const bindingName = containerBindingName(container.name);
|
|
3665
|
+
for (let index = 0;index < environmentNames.length; index += 1) {
|
|
3666
|
+
const name = environmentNames[index];
|
|
3667
|
+
const path7 = `containers.0.env.${index}`;
|
|
3668
|
+
if (byteLength(name) > MAX_ENV_NAME_BYTES || !ENV_NAME.test(name)) {
|
|
3669
|
+
return unsupported("invalid_container_environment_name", path7, "container environment names must be at most 64 bytes and use UPPER_SNAKE_CASE");
|
|
3670
|
+
}
|
|
3671
|
+
if (seen.has(name)) {
|
|
3672
|
+
return unsupported("duplicate_container_environment_name", path7, `container environment name '${name}' is duplicated`);
|
|
3673
|
+
}
|
|
3674
|
+
seen.add(name);
|
|
3675
|
+
if (RESERVED_ENV_NAMES.has(name) || RESERVED_ENV_PREFIXES.some((prefix) => name.startsWith(prefix)) || name === bindingName) {
|
|
3676
|
+
return unsupported("reserved_container_environment_name", path7, `container environment name '${name}' is owned by the Launchpad runtime`);
|
|
3677
|
+
}
|
|
3678
|
+
}
|
|
3679
|
+
return {
|
|
3680
|
+
kind: "supported",
|
|
3681
|
+
manifestValid: true,
|
|
3682
|
+
runtimeSupported: true,
|
|
3683
|
+
runtime: "container",
|
|
3684
|
+
capability: {
|
|
3685
|
+
appName: view.appName,
|
|
3686
|
+
apiVersion: manifest.apiVersion,
|
|
3687
|
+
container,
|
|
3688
|
+
className: containerClassName(view.appName),
|
|
3689
|
+
bindingName,
|
|
3690
|
+
dockerfilePathFromContainerDirectory: `../${container.image.slice(2)}`,
|
|
3691
|
+
environmentNames
|
|
3692
|
+
}
|
|
3693
|
+
};
|
|
3694
|
+
}
|
|
3695
|
+
function containerClassName(containerName) {
|
|
3696
|
+
return `AppContainer${containerName.split("-").map(capitalise).join("")}`;
|
|
3697
|
+
}
|
|
3698
|
+
function containerBindingName(containerName) {
|
|
3699
|
+
return `APP_CONTAINER_${containerName.split("-").map((part) => part.toUpperCase()).join("_")}`;
|
|
3700
|
+
}
|
|
3701
|
+
function deploymentView(manifest) {
|
|
3702
|
+
if (manifest.apiVersion === "launchpad.m-kopa.us/v2") {
|
|
3603
3703
|
return {
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3704
|
+
type: manifest.spec.appType,
|
|
3705
|
+
appName: manifest.metadata.slug,
|
|
3706
|
+
containers: manifest.spec.containers ?? []
|
|
3607
3707
|
};
|
|
3608
3708
|
}
|
|
3609
|
-
return {
|
|
3709
|
+
return {
|
|
3710
|
+
type: manifest.deployment.type,
|
|
3711
|
+
appName: manifest.metadata.name,
|
|
3712
|
+
containers: manifest.containers ?? []
|
|
3713
|
+
};
|
|
3610
3714
|
}
|
|
3611
|
-
function
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
# Source: launchpad.yaml (apiVersion: ${manifest.apiVersion})
|
|
3615
|
-
# App: ${manifest.metadata.name}
|
|
3616
|
-
#
|
|
3617
|
-
# Regenerate with \`launchpad generate\` (\`launchpad deploy\` also
|
|
3618
|
-
# re-emits before applying). Manual edits are overwritten on the
|
|
3619
|
-
# next regeneration. Manifest is the source of truth — change
|
|
3620
|
-
# ${manifest.metadata.name}/launchpad.yaml instead.`;
|
|
3621
|
-
lines.push(header);
|
|
3622
|
-
lines.push("");
|
|
3623
|
-
lines.push(`name = "${escapeTomlString(manifest.metadata.name)}"`);
|
|
3624
|
-
lines.push(`main = "worker/index.ts"`);
|
|
3625
|
-
lines.push(`compatibility_date = "${DEFAULT_COMPATIBILITY_DATE}"`);
|
|
3626
|
-
lines.push(`compatibility_flags = [${DEFAULT_COMPATIBILITY_FLAGS.map((f) => `"${escapeTomlString(f)}"`).join(", ")}]`);
|
|
3627
|
-
lines.push("");
|
|
3628
|
-
lines.push("# Workers Logs (PATTERNS.md: non-optional for container apps).");
|
|
3629
|
-
lines.push("[observability]");
|
|
3630
|
-
lines.push("enabled = true");
|
|
3631
|
-
lines.push("head_sampling_rate = 1.0");
|
|
3632
|
-
lines.push("");
|
|
3633
|
-
lines.push("# Platform-managed runtime config. Populated by the deploy");
|
|
3634
|
-
lines.push("# workflow via `wrangler deploy --var KEY:VALUE`. Secrets");
|
|
3635
|
-
lines.push("# (declared in launchpad.yaml secrets.bindings[]) go through");
|
|
3636
|
-
lines.push("# `wrangler secret put` and are NOT here.");
|
|
3637
|
-
lines.push("[vars]");
|
|
3638
|
-
lines.push(`CF_ACCESS_AUD = ""`);
|
|
3639
|
-
lines.push(`CF_ACCESS_TEAM_DOMAIN = ""`);
|
|
3640
|
-
lines.push("");
|
|
3641
|
-
for (const c of containers) {
|
|
3642
|
-
const className = containerClassName(c.name);
|
|
3643
|
-
const maxInstances = c.max_instances ?? 1;
|
|
3644
|
-
lines.push("[[containers]]");
|
|
3645
|
-
lines.push(`class_name = "${className}"`);
|
|
3646
|
-
lines.push(`image = "${escapeTomlString(c.image)}"`);
|
|
3647
|
-
lines.push(`max_instances = ${maxInstances}`);
|
|
3648
|
-
lines.push("");
|
|
3715
|
+
function validImagePath(path7) {
|
|
3716
|
+
if (byteLength(path7) > MAX_PATH_BYTES || !path7.startsWith("./") || path7.includes("\\") || CONTROL_CHARACTER.test(path7)) {
|
|
3717
|
+
return false;
|
|
3649
3718
|
}
|
|
3650
|
-
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
|
|
3655
|
-
|
|
3656
|
-
lines.push("");
|
|
3719
|
+
const segments = path7.slice(2).split("/");
|
|
3720
|
+
return segments.length > 0 && segments.every((segment) => segment.length > 0 && segment !== "." && segment !== ".." && IMAGE_SEGMENT.test(segment));
|
|
3721
|
+
}
|
|
3722
|
+
function validHealthcheck(path7) {
|
|
3723
|
+
if (byteLength(path7) === 0 || byteLength(path7) > MAX_PATH_BYTES || !path7.startsWith("/") || path7.includes("\\") || path7.includes("?") || path7.includes("#") || CONTROL_CHARACTER.test(path7)) {
|
|
3724
|
+
return false;
|
|
3657
3725
|
}
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3726
|
+
const segments = path7.slice(1).split("/");
|
|
3727
|
+
return segments.length > 0 && segments.every((segment) => segment.length > 0 && segment !== "." && segment !== "..");
|
|
3728
|
+
}
|
|
3729
|
+
function byteLength(value) {
|
|
3730
|
+
return new TextEncoder().encode(value).byteLength;
|
|
3731
|
+
}
|
|
3732
|
+
function capitalise(value) {
|
|
3733
|
+
if (value.length === 0)
|
|
3734
|
+
return value;
|
|
3735
|
+
return value[0].toUpperCase() + value.slice(1);
|
|
3736
|
+
}
|
|
3737
|
+
function unsupported(reason, path7, message) {
|
|
3738
|
+
return {
|
|
3739
|
+
kind: "unsupported",
|
|
3740
|
+
manifestValid: true,
|
|
3741
|
+
runtimeSupported: false,
|
|
3742
|
+
reason,
|
|
3743
|
+
path: path7,
|
|
3744
|
+
message
|
|
3745
|
+
};
|
|
3746
|
+
}
|
|
3747
|
+
// ../launchpad-engine/dist/container-worker.js
|
|
3748
|
+
function generateContainerWorker(manifest) {
|
|
3749
|
+
const result = validateDeploymentCapability(manifest);
|
|
3750
|
+
if (result.kind === "unsupported") {
|
|
3751
|
+
return {
|
|
3752
|
+
kind: "unsupported",
|
|
3753
|
+
reason: result.reason,
|
|
3754
|
+
message: result.message
|
|
3755
|
+
};
|
|
3756
|
+
}
|
|
3757
|
+
if (result.runtime !== "container") {
|
|
3758
|
+
return {
|
|
3759
|
+
kind: "not-applicable",
|
|
3760
|
+
reason: "pages runtimes do not use the generated container Worker"
|
|
3761
|
+
};
|
|
3762
|
+
}
|
|
3763
|
+
return { kind: "ok", content: emit(result.capability) };
|
|
3764
|
+
}
|
|
3765
|
+
function emit(capability) {
|
|
3766
|
+
const app = JSON.stringify(capability.appName);
|
|
3767
|
+
const className = capability.className;
|
|
3768
|
+
const bindingName = capability.bindingName;
|
|
3769
|
+
const environmentFields = capability.environmentNames.map((name) => ` ${name}: string;`);
|
|
3770
|
+
const environmentValues = capability.environmentNames.map((name) => ` ${name}: this.env.${name} ?? "",`);
|
|
3771
|
+
const lines = [
|
|
3772
|
+
"/**",
|
|
3773
|
+
" * Auto-generated by @m-kopa/launchpad-engine.",
|
|
3774
|
+
" * Source: launchpad.yaml. Do not edit by hand.",
|
|
3775
|
+
" */",
|
|
3776
|
+
'import { Container } from "@cloudflare/containers";',
|
|
3777
|
+
"import {",
|
|
3778
|
+
" AccessJwtVerificationError,",
|
|
3779
|
+
" createJwksCache,",
|
|
3780
|
+
" parseAccessJwtFromHeaders,",
|
|
3781
|
+
" verifyAccessJwt,",
|
|
3782
|
+
" type User,",
|
|
3783
|
+
'} from "@m-kopa/platform-auth";',
|
|
3784
|
+
"",
|
|
3785
|
+
"type Env = {",
|
|
3786
|
+
` ${bindingName}: DurableObjectNamespace<${className}>;`,
|
|
3787
|
+
" BINDINGS_PROXY_SECRET: string;",
|
|
3788
|
+
" CF_ACCESS_AUD: string;",
|
|
3789
|
+
" CF_ACCESS_TEAM_DOMAIN: string;",
|
|
3790
|
+
" LAUNCHPAD_DEPLOYMENT_SHA: string;",
|
|
3791
|
+
...environmentFields,
|
|
3792
|
+
"};",
|
|
3793
|
+
"",
|
|
3794
|
+
`export class ${className} extends Container<Env> {`,
|
|
3795
|
+
` defaultPort = ${capability.container.port};`,
|
|
3796
|
+
' sleepAfter = "5m";',
|
|
3797
|
+
...environmentValues.length === 0 ? [] : ["", " envVars = {", ...environmentValues, " };"],
|
|
3798
|
+
"}",
|
|
3799
|
+
"",
|
|
3800
|
+
"let jwksCache: ReturnType<typeof createJwksCache> | null = null;",
|
|
3801
|
+
"",
|
|
3802
|
+
"function jwks(env: Env): ReturnType<typeof createJwksCache> {",
|
|
3803
|
+
" if (jwksCache === null) {",
|
|
3804
|
+
" jwksCache = createJwksCache(",
|
|
3805
|
+
" `https://${env.CF_ACCESS_TEAM_DOMAIN}/cdn-cgi/access/certs`,",
|
|
3806
|
+
" );",
|
|
3807
|
+
" }",
|
|
3808
|
+
" return jwksCache;",
|
|
3809
|
+
"}",
|
|
3810
|
+
"",
|
|
3811
|
+
"function unauthenticated(code: string): Response {",
|
|
3812
|
+
" return Response.json(",
|
|
3813
|
+
' { error: "unauthenticated", code },',
|
|
3814
|
+
' { status: 401, headers: { "cache-control": "no-store" } },',
|
|
3815
|
+
" );",
|
|
3816
|
+
"}",
|
|
3817
|
+
"",
|
|
3818
|
+
"async function secretsEqual(left: string, right: string): Promise<boolean> {",
|
|
3819
|
+
" const encoder = new TextEncoder();",
|
|
3820
|
+
" const [leftDigest, rightDigest] = await Promise.all([",
|
|
3821
|
+
' crypto.subtle.digest("SHA-256", encoder.encode(left)),',
|
|
3822
|
+
' crypto.subtle.digest("SHA-256", encoder.encode(right)),',
|
|
3823
|
+
" ]);",
|
|
3824
|
+
" const leftBytes = new Uint8Array(leftDigest);",
|
|
3825
|
+
" const rightBytes = new Uint8Array(rightDigest);",
|
|
3826
|
+
" let difference = 0;",
|
|
3827
|
+
" for (let index = 0; index < leftBytes.length; index += 1) {",
|
|
3828
|
+
" difference |= leftBytes[index]! ^ rightBytes[index]!;",
|
|
3829
|
+
" }",
|
|
3830
|
+
" return difference === 0;",
|
|
3831
|
+
"}",
|
|
3832
|
+
"",
|
|
3833
|
+
"async function authenticate(request: Request, env: Env): Promise<User | Response> {",
|
|
3834
|
+
" if (!env.CF_ACCESS_TEAM_DOMAIN || !env.CF_ACCESS_AUD) {",
|
|
3835
|
+
' return unauthenticated("auth_unconfigured");',
|
|
3836
|
+
" }",
|
|
3837
|
+
" const token = parseAccessJwtFromHeaders(request.headers);",
|
|
3838
|
+
" if (token === null) {",
|
|
3839
|
+
' return unauthenticated("access_jwt_missing");',
|
|
3840
|
+
" }",
|
|
3841
|
+
" try {",
|
|
3842
|
+
" return await verifyAccessJwt(token, {",
|
|
3843
|
+
" teamDomain: `https://${env.CF_ACCESS_TEAM_DOMAIN}`,",
|
|
3844
|
+
" audience: env.CF_ACCESS_AUD,",
|
|
3845
|
+
" jwks: jwks(env),",
|
|
3846
|
+
" clockTolerance: 30,",
|
|
3847
|
+
" });",
|
|
3848
|
+
" } catch (error) {",
|
|
3849
|
+
" if (error instanceof AccessJwtVerificationError) {",
|
|
3850
|
+
" return unauthenticated(error.code);",
|
|
3851
|
+
" }",
|
|
3852
|
+
' console.error(JSON.stringify({ event: "container_runtime_error", kind: "access_jwt_verification_failed" }));',
|
|
3853
|
+
' return unauthenticated("access_jwt_verification_failed");',
|
|
3854
|
+
" }",
|
|
3855
|
+
"}",
|
|
3856
|
+
"",
|
|
3857
|
+
"function forwardedRequest(",
|
|
3858
|
+
" request: Request,",
|
|
3859
|
+
" user: User,",
|
|
3860
|
+
" env: Env,",
|
|
3861
|
+
" requestId: string,",
|
|
3862
|
+
"): Request {",
|
|
3863
|
+
" const url = new URL(request.url);",
|
|
3864
|
+
" const headers = new Headers(request.headers);",
|
|
3865
|
+
" for (const name of Array.from(headers.keys())) {",
|
|
3866
|
+
" const lower = name.toLowerCase();",
|
|
3867
|
+
" if (",
|
|
3868
|
+
' lower.startsWith("cf-access-") ||',
|
|
3869
|
+
' lower.startsWith("x-user-") ||',
|
|
3870
|
+
' lower.startsWith("x-bindings-proxy-") ||',
|
|
3871
|
+
' lower === "x-request-id"',
|
|
3872
|
+
" ) {",
|
|
3873
|
+
" headers.delete(name);",
|
|
3874
|
+
" }",
|
|
3875
|
+
" }",
|
|
3876
|
+
' headers.set("X-User-Sub", user.sub);',
|
|
3877
|
+
' headers.set("X-User-Email", user.preferredUsername);',
|
|
3878
|
+
" if (user.groups.length > 0) {",
|
|
3879
|
+
' headers.set("X-User-Groups", user.groups.join(","));',
|
|
3880
|
+
" }",
|
|
3881
|
+
' headers.set("X-Request-ID", requestId);',
|
|
3882
|
+
' headers.set("X-Bindings-Proxy-Secret", env.BINDINGS_PROXY_SECRET);',
|
|
3883
|
+
' headers.set("X-Bindings-Proxy-Url", `${url.origin}/__platform/bindings`);',
|
|
3884
|
+
" return new Request(url.toString(), {",
|
|
3885
|
+
" method: request.method,",
|
|
3886
|
+
" headers,",
|
|
3887
|
+
' body: request.method === "GET" || request.method === "HEAD" ? undefined : request.body,',
|
|
3888
|
+
" redirect: request.redirect,",
|
|
3889
|
+
" });",
|
|
3890
|
+
"}",
|
|
3891
|
+
"",
|
|
3892
|
+
"function runtimeEvent(",
|
|
3893
|
+
" env: Env,",
|
|
3894
|
+
" requestId: string,",
|
|
3895
|
+
" status: number,",
|
|
3896
|
+
" kind: string,",
|
|
3897
|
+
"): void {",
|
|
3898
|
+
" console.log(JSON.stringify({",
|
|
3899
|
+
' event: "container_runtime_dispatch",',
|
|
3900
|
+
` slug: ${app},`,
|
|
3901
|
+
" deployment_sha: env.LAUNCHPAD_DEPLOYMENT_SHA,",
|
|
3902
|
+
" request_id: requestId,",
|
|
3903
|
+
` class_name: ${JSON.stringify(className)},`,
|
|
3904
|
+
` binding_name: ${JSON.stringify(bindingName)},`,
|
|
3905
|
+
" status,",
|
|
3906
|
+
" kind,",
|
|
3907
|
+
" }));",
|
|
3908
|
+
"}",
|
|
3909
|
+
"",
|
|
3910
|
+
"export default {",
|
|
3911
|
+
" async fetch(request: Request, env: Env): Promise<Response> {",
|
|
3912
|
+
" const url = new URL(request.url);",
|
|
3913
|
+
" const requestId = crypto.randomUUID();",
|
|
3914
|
+
' if (request.method === "GET" && url.pathname === "/health") {',
|
|
3915
|
+
' const response = Response.json({ ok: true, role: "worker" });',
|
|
3916
|
+
' runtimeEvent(env, requestId, response.status, "worker_health");',
|
|
3917
|
+
" return response;",
|
|
3918
|
+
" }",
|
|
3919
|
+
' if (url.pathname === "/__platform/bindings" || url.pathname.startsWith("/__platform/bindings/")) {',
|
|
3920
|
+
' const presented = request.headers.get("x-bindings-proxy-secret");',
|
|
3921
|
+
" if (!env.BINDINGS_PROXY_SECRET || !presented || !(await secretsEqual(presented, env.BINDINGS_PROXY_SECRET))) {",
|
|
3922
|
+
' const response = Response.json({ error: "forbidden" }, { status: 403 });',
|
|
3923
|
+
' runtimeEvent(env, requestId, response.status, "bindings_forbidden");',
|
|
3924
|
+
" return response;",
|
|
3925
|
+
" }",
|
|
3926
|
+
' const response = Response.json({ error: "unknown_binding_endpoint" }, { status: 404 });',
|
|
3927
|
+
' runtimeEvent(env, requestId, response.status, "bindings_unknown");',
|
|
3928
|
+
" return response;",
|
|
3929
|
+
" }",
|
|
3930
|
+
" const identity = await authenticate(request, env);",
|
|
3931
|
+
" if (identity instanceof Response) {",
|
|
3932
|
+
' runtimeEvent(env, requestId, identity.status, "authentication_failed");',
|
|
3933
|
+
" return identity;",
|
|
3934
|
+
" }",
|
|
3935
|
+
` const id = env.${bindingName}.idFromName("singleton");`,
|
|
3936
|
+
` const response = await env.${bindingName}.get(id).fetch(`,
|
|
3937
|
+
" forwardedRequest(request, identity, env, requestId),",
|
|
3938
|
+
" );",
|
|
3939
|
+
' runtimeEvent(env, requestId, response.status, "container_response");',
|
|
3940
|
+
" return response;",
|
|
3941
|
+
" },",
|
|
3942
|
+
"};",
|
|
3943
|
+
""
|
|
3944
|
+
];
|
|
3668
3945
|
return lines.join(`
|
|
3669
3946
|
`);
|
|
3670
3947
|
}
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
function
|
|
3675
|
-
|
|
3948
|
+
// ../launchpad-engine/dist/wrangler.js
|
|
3949
|
+
var DEFAULT_COMPATIBILITY_DATE = "2026-05-01";
|
|
3950
|
+
var DEFAULT_COMPATIBILITY_FLAGS = ["nodejs_compat"];
|
|
3951
|
+
function generateWranglerToml(manifest) {
|
|
3952
|
+
const result = validateDeploymentCapability(manifest);
|
|
3953
|
+
if (result.kind === "unsupported") {
|
|
3954
|
+
return {
|
|
3955
|
+
kind: "unsupported",
|
|
3956
|
+
reason: result.reason,
|
|
3957
|
+
message: result.message
|
|
3958
|
+
};
|
|
3959
|
+
}
|
|
3960
|
+
if (result.runtime !== "container") {
|
|
3961
|
+
return {
|
|
3962
|
+
kind: "not-applicable",
|
|
3963
|
+
reason: "pages runtimes do not use container/wrangler.toml"
|
|
3964
|
+
};
|
|
3965
|
+
}
|
|
3966
|
+
return { kind: "ok", content: emit2(result.capability), warnings: [] };
|
|
3676
3967
|
}
|
|
3677
|
-
function
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3968
|
+
function emit2(capability) {
|
|
3969
|
+
const maxInstances = capability.container.max_instances ?? 1;
|
|
3970
|
+
return [
|
|
3971
|
+
"# Auto-generated by @m-kopa/launchpad-engine. Do not edit by hand.",
|
|
3972
|
+
`# Source: launchpad.yaml (apiVersion: ${capability.apiVersion})`,
|
|
3973
|
+
`# App: ${capability.appName}`,
|
|
3974
|
+
"",
|
|
3975
|
+
`name = "${escapeTomlString(capability.appName)}"`,
|
|
3976
|
+
'main = "worker/index.ts"',
|
|
3977
|
+
`compatibility_date = "${DEFAULT_COMPATIBILITY_DATE}"`,
|
|
3978
|
+
`compatibility_flags = [${DEFAULT_COMPATIBILITY_FLAGS.map((flag) => `"${flag}"`).join(", ")}]`,
|
|
3979
|
+
"",
|
|
3980
|
+
"[observability]",
|
|
3981
|
+
"enabled = true",
|
|
3982
|
+
"head_sampling_rate = 1.0",
|
|
3983
|
+
"",
|
|
3984
|
+
"[vars]",
|
|
3985
|
+
'CF_ACCESS_AUD = ""',
|
|
3986
|
+
'CF_ACCESS_TEAM_DOMAIN = ""',
|
|
3987
|
+
'LAUNCHPAD_DEPLOYMENT_SHA = ""',
|
|
3988
|
+
"",
|
|
3989
|
+
"[[containers]]",
|
|
3990
|
+
`class_name = "${capability.className}"`,
|
|
3991
|
+
`image = "${escapeTomlString(capability.dockerfilePathFromContainerDirectory)}"`,
|
|
3992
|
+
`max_instances = ${maxInstances}`,
|
|
3993
|
+
"",
|
|
3994
|
+
"[[durable_objects.bindings]]",
|
|
3995
|
+
`name = "${capability.bindingName}"`,
|
|
3996
|
+
`class_name = "${capability.className}"`,
|
|
3997
|
+
"",
|
|
3998
|
+
"[[migrations]]",
|
|
3999
|
+
'tag = "v1"',
|
|
4000
|
+
`new_sqlite_classes = ["${capability.className}"]`,
|
|
4001
|
+
""
|
|
4002
|
+
].join(`
|
|
4003
|
+
`);
|
|
3681
4004
|
}
|
|
3682
|
-
function escapeTomlString(
|
|
3683
|
-
return
|
|
4005
|
+
function escapeTomlString(value) {
|
|
4006
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
|
|
3684
4007
|
}
|
|
3685
4008
|
// ../launchpad-engine/dist/github-workflow.js
|
|
3686
|
-
var
|
|
3687
|
-
var DEFAULT_BUN_VERSION = "1.3.4";
|
|
4009
|
+
var DEFAULT_NODE_VERSION = "22";
|
|
3688
4010
|
var ACTIONS_CHECKOUT_SHA = "de0fac2e4500dabe0009e67214ff5f5447ce83dd";
|
|
3689
|
-
var
|
|
4011
|
+
var ACTIONS_SETUP_NODE_SHA = "48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e";
|
|
3690
4012
|
function generateGithubDeployWorkflow(manifest) {
|
|
3691
|
-
|
|
4013
|
+
const result = validateDeploymentCapability(manifest);
|
|
4014
|
+
if (result.kind === "unsupported") {
|
|
3692
4015
|
return {
|
|
3693
|
-
kind: "
|
|
3694
|
-
reason:
|
|
4016
|
+
kind: "unsupported",
|
|
4017
|
+
reason: result.reason,
|
|
4018
|
+
message: result.message
|
|
3695
4019
|
};
|
|
3696
4020
|
}
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
4021
|
+
if (result.runtime !== "container") {
|
|
4022
|
+
return {
|
|
4023
|
+
kind: "not-applicable",
|
|
4024
|
+
reason: "pages runtimes do not use the container deploy workflow"
|
|
4025
|
+
};
|
|
3701
4026
|
}
|
|
3702
|
-
return {
|
|
3703
|
-
kind: "ok",
|
|
3704
|
-
content: emit2(manifest, bindings),
|
|
3705
|
-
warnings
|
|
3706
|
-
};
|
|
4027
|
+
return { kind: "ok", content: emit3(result.capability), warnings: [] };
|
|
3707
4028
|
}
|
|
3708
|
-
function
|
|
3709
|
-
const
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
lines.push(` set -euo pipefail`);
|
|
3848
|
-
lines.push(` URL="${workerUrl}/api/health"`);
|
|
3849
|
-
lines.push(` code=$(curl -sS -o /dev/null -w "%{http_code}" "$URL")`);
|
|
3850
|
-
lines.push(` case "$code" in`);
|
|
3851
|
-
lines.push(` 302|401)`);
|
|
3852
|
-
lines.push(` echo "backend auth-gate ok: HTTP $code on unauthenticated request to /api/health."`);
|
|
3853
|
-
lines.push(` ;;`);
|
|
3854
|
-
lines.push(` *)`);
|
|
3855
|
-
lines.push(` echo "backend auth-gate regression: expected 302 or 401 on /api/health, got $code" >&2`);
|
|
3856
|
-
lines.push(` curl -sSI "$URL" >&2 || true`);
|
|
3857
|
-
lines.push(` exit 1`);
|
|
3858
|
-
lines.push(` ;;`);
|
|
3859
|
-
lines.push(` esac`);
|
|
3860
|
-
lines.push("");
|
|
4029
|
+
function emit3(capability) {
|
|
4030
|
+
const lines = [
|
|
4031
|
+
"# Auto-generated by @m-kopa/launchpad-engine. Do not edit by hand.",
|
|
4032
|
+
`# Source: launchpad.yaml (apiVersion: ${capability.apiVersion})`,
|
|
4033
|
+
`# App: ${capability.appName}`,
|
|
4034
|
+
"",
|
|
4035
|
+
"name: deploy",
|
|
4036
|
+
"",
|
|
4037
|
+
"on:",
|
|
4038
|
+
" push:",
|
|
4039
|
+
" branches:",
|
|
4040
|
+
" - main",
|
|
4041
|
+
" workflow_dispatch:",
|
|
4042
|
+
"",
|
|
4043
|
+
"permissions:",
|
|
4044
|
+
" contents: read",
|
|
4045
|
+
"",
|
|
4046
|
+
"concurrency:",
|
|
4047
|
+
" group: deploy-${{ github.workflow }}-${{ github.sha }}",
|
|
4048
|
+
" cancel-in-progress: false",
|
|
4049
|
+
"",
|
|
4050
|
+
"jobs:",
|
|
4051
|
+
" authorize:",
|
|
4052
|
+
" name: authorize exact run attempt",
|
|
4053
|
+
" runs-on: ubuntu-latest",
|
|
4054
|
+
" steps:",
|
|
4055
|
+
` - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6.0.2`,
|
|
4056
|
+
" with:",
|
|
4057
|
+
" persist-credentials: false",
|
|
4058
|
+
"",
|
|
4059
|
+
" - name: Verify protected runtime authorization",
|
|
4060
|
+
" env:",
|
|
4061
|
+
" GH_TOKEN: ${{ github.token }}",
|
|
4062
|
+
" run: |",
|
|
4063
|
+
" set -euo pipefail",
|
|
4064
|
+
" record=$(mktemp)",
|
|
4065
|
+
` trap 'rm -f "$record"' EXIT`,
|
|
4066
|
+
' api="${{ github.api_url }}/repos/${{ github.repository }}/contents/.launchpad/runtime-control/${GITHUB_SHA}.json?ref=launchpad-runtime-control"',
|
|
4067
|
+
" for attempt in $(seq 1 60); do",
|
|
4068
|
+
" if curl --fail --silent --show-error \\",
|
|
4069
|
+
' -H "Authorization: Bearer ${GH_TOKEN}" \\',
|
|
4070
|
+
' -H "Accept: application/vnd.github.raw+json" \\',
|
|
4071
|
+
' -H "X-GitHub-Api-Version: 2022-11-28" \\',
|
|
4072
|
+
' "$api" > "$record"; then',
|
|
4073
|
+
" break",
|
|
4074
|
+
" fi",
|
|
4075
|
+
' if [ "$attempt" -eq 60 ]; then',
|
|
4076
|
+
' echo "No exact-run authorization was published" >&2',
|
|
4077
|
+
" exit 1",
|
|
4078
|
+
" fi",
|
|
4079
|
+
" sleep 5",
|
|
4080
|
+
" done",
|
|
4081
|
+
" jq -e '",
|
|
4082
|
+
' (.schema == "launchpad-runtime-control/v1") and',
|
|
4083
|
+
' (.attempt_id | type == "string" and length > 0) and',
|
|
4084
|
+
' (.lease_digest | test("^[0-9a-f]{64}$")) and',
|
|
4085
|
+
" (.commit_sha == env.GITHUB_SHA) and",
|
|
4086
|
+
" (.run_id == env.GITHUB_RUN_ID) and",
|
|
4087
|
+
" (.run_attempt == env.GITHUB_RUN_ATTEMPT) and",
|
|
4088
|
+
' (.expires_at_epoch | type == "number" and . > now)',
|
|
4089
|
+
` ' "$record" >/dev/null`,
|
|
4090
|
+
" wrangler_sha=$(sha256sum container/wrangler.toml | cut -d' ' -f1)",
|
|
4091
|
+
" worker_sha=$(sha256sum container/worker/index.ts | cut -d' ' -f1)",
|
|
4092
|
+
" workflow_sha=$(sha256sum .github/workflows/deploy.yml | cut -d' ' -f1)",
|
|
4093
|
+
" jq -e \\",
|
|
4094
|
+
' --arg wrangler "$wrangler_sha" \\',
|
|
4095
|
+
' --arg worker "$worker_sha" \\',
|
|
4096
|
+
' --arg workflow "$workflow_sha" \\',
|
|
4097
|
+
` '.artifact_digests["container/wrangler.toml"] == $wrangler and`,
|
|
4098
|
+
' .artifact_digests["container/worker/index.ts"] == $worker and',
|
|
4099
|
+
` .artifact_digests[".github/workflows/deploy.yml"] == $workflow' \\`,
|
|
4100
|
+
' "$record" >/dev/null',
|
|
4101
|
+
"",
|
|
4102
|
+
" deploy:",
|
|
4103
|
+
" name: deploy admitted runtime",
|
|
4104
|
+
" needs: authorize",
|
|
4105
|
+
" runs-on: ubuntu-latest",
|
|
4106
|
+
" environment: ${{ vars.LAUNCHPAD_APP_RUNTIME || 'production' }}",
|
|
4107
|
+
" steps:",
|
|
4108
|
+
` - uses: actions/checkout@${ACTIONS_CHECKOUT_SHA} # v6.0.2`,
|
|
4109
|
+
" with:",
|
|
4110
|
+
" persist-credentials: false",
|
|
4111
|
+
"",
|
|
4112
|
+
` - uses: actions/setup-node@${ACTIONS_SETUP_NODE_SHA} # v6.4.0`,
|
|
4113
|
+
" with:",
|
|
4114
|
+
` node-version: "${DEFAULT_NODE_VERSION}"`,
|
|
4115
|
+
" cache: npm",
|
|
4116
|
+
" cache-dependency-path: |",
|
|
4117
|
+
" container/package-lock.json",
|
|
4118
|
+
" container/backend/package-lock.json",
|
|
4119
|
+
"",
|
|
4120
|
+
" - name: Install frozen Worker dependencies",
|
|
4121
|
+
" working-directory: container",
|
|
4122
|
+
" run: npm ci --ignore-scripts",
|
|
4123
|
+
"",
|
|
4124
|
+
" - name: Install frozen workload dependencies",
|
|
4125
|
+
" working-directory: container/backend",
|
|
4126
|
+
" run: npm ci --ignore-scripts",
|
|
4127
|
+
"",
|
|
4128
|
+
" - name: Verify required deployment inputs",
|
|
4129
|
+
" env:",
|
|
4130
|
+
" CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}",
|
|
4131
|
+
" CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}",
|
|
4132
|
+
" CF_ACCESS_AUD: ${{ vars.CF_ACCESS_AUD }}",
|
|
4133
|
+
" CF_ACCESS_TEAM_DOMAIN: ${{ vars.CF_ACCESS_TEAM_DOMAIN }}",
|
|
4134
|
+
" BINDINGS_PROXY_SECRET: ${{ secrets.BINDINGS_PROXY_SECRET }}",
|
|
4135
|
+
" CF_ACCESS_AUD: ${{ vars.CF_ACCESS_AUD }}",
|
|
4136
|
+
" CF_ACCESS_TEAM_DOMAIN: ${{ vars.CF_ACCESS_TEAM_DOMAIN }}",
|
|
4137
|
+
...capability.environmentNames.map((name) => ` ${name}: \${{ secrets.${name} }}`),
|
|
4138
|
+
" run: |",
|
|
4139
|
+
" set -euo pipefail",
|
|
4140
|
+
" for name in CLOUDFLARE_API_TOKEN CLOUDFLARE_ACCOUNT_ID BINDINGS_PROXY_SECRET CF_ACCESS_AUD CF_ACCESS_TEAM_DOMAIN; do",
|
|
4141
|
+
' if [ -z "${!name:-}" ]; then',
|
|
4142
|
+
' echo "Required deployment input $name is empty" >&2',
|
|
4143
|
+
" exit 1",
|
|
4144
|
+
" fi",
|
|
4145
|
+
" done",
|
|
4146
|
+
...capability.environmentNames.flatMap((name) => [
|
|
4147
|
+
` if [ -z "\${${name}:-}" ]; then`,
|
|
4148
|
+
` echo "Required container secret ${name} is empty" >&2`,
|
|
4149
|
+
" exit 1",
|
|
4150
|
+
" fi"
|
|
4151
|
+
]),
|
|
4152
|
+
"",
|
|
4153
|
+
" - name: Deploy Worker and container image",
|
|
4154
|
+
" working-directory: container",
|
|
4155
|
+
" env:",
|
|
4156
|
+
" CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}",
|
|
4157
|
+
" CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}",
|
|
4158
|
+
" run: |",
|
|
4159
|
+
" npx --no-install wrangler deploy \\",
|
|
4160
|
+
' --message "launchpad:${GITHUB_SHA}" \\',
|
|
4161
|
+
' --tag "launchpad-deployment-sha:${GITHUB_SHA}" \\',
|
|
4162
|
+
' --var "CF_ACCESS_AUD:${CF_ACCESS_AUD}" \\',
|
|
4163
|
+
' --var "CF_ACCESS_TEAM_DOMAIN:${CF_ACCESS_TEAM_DOMAIN}" \\',
|
|
4164
|
+
' --var "LAUNCHPAD_DEPLOYMENT_SHA:${GITHUB_SHA}"',
|
|
4165
|
+
"",
|
|
4166
|
+
...secretSteps(capability)
|
|
4167
|
+
];
|
|
3861
4168
|
return lines.join(`
|
|
3862
4169
|
`);
|
|
3863
4170
|
}
|
|
3864
|
-
function
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
lines.push(` run: |`);
|
|
3877
|
-
lines.push(` if [ -z "\${${name}:-}" ]; then`);
|
|
3878
|
-
lines.push(` echo "::warning::${name} GH Actions secret is unset — Worker will be missing this binding until the secret is provisioned + this workflow is rerun."`);
|
|
3879
|
-
lines.push(` exit 0`);
|
|
3880
|
-
lines.push(` fi`);
|
|
3881
|
-
lines.push(` printf '%s' "$${name}" | bunx wrangler secret put ${name}`);
|
|
3882
|
-
lines.push("");
|
|
4171
|
+
function secretSteps(capability) {
|
|
4172
|
+
return ["BINDINGS_PROXY_SECRET", ...capability.environmentNames].flatMap((name) => [
|
|
4173
|
+
` - name: Push ${name}`,
|
|
4174
|
+
" working-directory: container",
|
|
4175
|
+
" env:",
|
|
4176
|
+
" CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}",
|
|
4177
|
+
" CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}",
|
|
4178
|
+
` ${name}: \${{ secrets.${name} }}`,
|
|
4179
|
+
" run: |",
|
|
4180
|
+
` printf '%s' "$${name}" | npx --no-install wrangler secret put ${name}`,
|
|
4181
|
+
""
|
|
4182
|
+
]);
|
|
3883
4183
|
}
|
|
3884
4184
|
// ../launchpad-engine/dist/per-app-workspace.js
|
|
3885
4185
|
var CF_ACCOUNT_ID = "c07cb17c9f742e8144c4828b3693ca65";
|
|
@@ -4103,7 +4403,7 @@ function applyBoundaryToFiles(args) {
|
|
|
4103
4403
|
const ignorePath = join6(cwd, ".launchpadignore");
|
|
4104
4404
|
if (existsSync2(ignorePath)) {
|
|
4105
4405
|
try {
|
|
4106
|
-
launchpadIgnore = parseLaunchpadIgnore(
|
|
4406
|
+
launchpadIgnore = parseLaunchpadIgnore(readFileSync4(ignorePath, "utf8"));
|
|
4107
4407
|
} catch {}
|
|
4108
4408
|
}
|
|
4109
4409
|
const result = applyAppBoundary(contract, files, {
|
|
@@ -4114,7 +4414,7 @@ function applyBoundaryToFiles(args) {
|
|
|
4114
4414
|
const stat = lstatSync2(abs);
|
|
4115
4415
|
if (!stat.isFile() || stat.size > MAX_CONTENT_PROBE_BYTES)
|
|
4116
4416
|
return;
|
|
4117
|
-
return
|
|
4417
|
+
return readFileSync4(abs, "utf8");
|
|
4118
4418
|
} catch {
|
|
4119
4419
|
return;
|
|
4120
4420
|
}
|
|
@@ -4166,7 +4466,7 @@ async function bundleAndDeploy(args) {
|
|
|
4166
4466
|
const manifestPath = join7(cwd, "launchpad.yaml");
|
|
4167
4467
|
let manifestYaml;
|
|
4168
4468
|
try {
|
|
4169
|
-
manifestYaml =
|
|
4469
|
+
manifestYaml = readFileSync5(manifestPath, "utf8");
|
|
4170
4470
|
} catch (e) {
|
|
4171
4471
|
return {
|
|
4172
4472
|
kind: "no-manifest",
|
|
@@ -4228,9 +4528,24 @@ async function bundleAndDeploy(args) {
|
|
|
4228
4528
|
}
|
|
4229
4529
|
|
|
4230
4530
|
// src/deploy/verify-deploy.ts
|
|
4231
|
-
import { readFileSync as
|
|
4531
|
+
import { readFileSync as readFileSync9, existsSync as existsSync4 } from "node:fs";
|
|
4232
4532
|
import { join as join9 } from "node:path";
|
|
4233
4533
|
|
|
4534
|
+
// src/deploy/redact-diagnostic.ts
|
|
4535
|
+
var MAX_DIAGNOSTIC_LEN = 600;
|
|
4536
|
+
var SECRET_KV = /(["']?)(\b[A-Za-z0-9_-]*(?:secrets?|token|api[_-]?key|access[_-]?key|secret[_-]?key|private[_-]?key|password|passwd|pwd|authorization|auth[_-]?token|bearer|client[_-]?secret|session)\b)\1(\s*[=:]\s*|\s+)(?:"[^"]*"|'[^']*'|\S+)/gi;
|
|
4537
|
+
var LONG_TOKEN = /[A-Za-z0-9_\-+/.]{28,}={0,2}/g;
|
|
4538
|
+
function redactDiagnostic(text, maxLen = MAX_DIAGNOSTIC_LEN) {
|
|
4539
|
+
if (text === undefined || text === null)
|
|
4540
|
+
return "";
|
|
4541
|
+
let t = text.replace(SECRET_KV, (_match, quote, key, separator) => `${quote}${key}${quote}${separator}***`);
|
|
4542
|
+
t = t.replace(LONG_TOKEN, "***");
|
|
4543
|
+
t = t.trim();
|
|
4544
|
+
if (t.length > maxLen)
|
|
4545
|
+
t = `${t.slice(0, maxLen - 1)}…`;
|
|
4546
|
+
return t;
|
|
4547
|
+
}
|
|
4548
|
+
|
|
4234
4549
|
// src/deploy/deployment-status.ts
|
|
4235
4550
|
async function fetchStandingExceptions(cfg, slug, fetcher = fetch) {
|
|
4236
4551
|
let raw;
|
|
@@ -4259,11 +4574,39 @@ async function fetchDeploymentStatus(cfg, slug, fetcher = fetch) {
|
|
|
4259
4574
|
return {
|
|
4260
4575
|
slug: typeof raw.slug === "string" ? raw.slug : slug,
|
|
4261
4576
|
supported: raw.supported,
|
|
4262
|
-
liveDeployment: raw.liveDeployment ?? null,
|
|
4263
|
-
lastSuccessfulDeployment: raw.lastSuccessfulDeployment
|
|
4577
|
+
liveDeployment: scrubLiveDeployment(raw.liveDeployment ?? null),
|
|
4578
|
+
lastSuccessfulDeployment: projectLastSuccessful(raw.lastSuccessfulDeployment),
|
|
4264
4579
|
...raw.reconciledToLive === true ? { reconciledToLive: true } : {}
|
|
4265
4580
|
};
|
|
4266
4581
|
}
|
|
4582
|
+
function projectLastSuccessful(value) {
|
|
4583
|
+
if (value === null || typeof value !== "object")
|
|
4584
|
+
return null;
|
|
4585
|
+
const raw = value;
|
|
4586
|
+
if (typeof raw.id !== "string" || typeof raw.createdOn !== "string") {
|
|
4587
|
+
return null;
|
|
4588
|
+
}
|
|
4589
|
+
return {
|
|
4590
|
+
id: raw.id,
|
|
4591
|
+
createdOn: raw.createdOn,
|
|
4592
|
+
url: typeof raw.url === "string" ? raw.url : null
|
|
4593
|
+
};
|
|
4594
|
+
}
|
|
4595
|
+
function scrubLiveDeployment(deployment) {
|
|
4596
|
+
if (deployment === null)
|
|
4597
|
+
return null;
|
|
4598
|
+
return {
|
|
4599
|
+
...deployment,
|
|
4600
|
+
...deployment.commit === undefined ? {} : {
|
|
4601
|
+
commit: {
|
|
4602
|
+
...deployment.commit,
|
|
4603
|
+
message: redactDiagnostic(deployment.commit.message)
|
|
4604
|
+
}
|
|
4605
|
+
},
|
|
4606
|
+
...deployment.failedStage === undefined ? {} : { failedStage: redactDiagnostic(deployment.failedStage) },
|
|
4607
|
+
...deployment.logExcerpt === undefined ? {} : { logExcerpt: redactDiagnostic(deployment.logExcerpt) }
|
|
4608
|
+
};
|
|
4609
|
+
}
|
|
4267
4610
|
|
|
4268
4611
|
// src/deploy/commit-match-settle.ts
|
|
4269
4612
|
var DEFAULT_REGISTRATION_BOUND_MS = 120000;
|
|
@@ -4429,23 +4772,8 @@ async function verifyServed(args, deps = { fetch }) {
|
|
|
4429
4772
|
};
|
|
4430
4773
|
}
|
|
4431
4774
|
|
|
4432
|
-
// src/deploy/redact-diagnostic.ts
|
|
4433
|
-
var MAX_DIAGNOSTIC_LEN = 600;
|
|
4434
|
-
var SECRET_KV = /\b(secret|secrets?|token|api[_-]?key|access[_-]?key|secret[_-]?key|private[_-]?key|password|passwd|pwd|authorization|auth[_-]?token|bearer|client[_-]?secret|session)\b(\s*[=:]\s*|\s+)(\S+)/gi;
|
|
4435
|
-
var LONG_TOKEN = /[A-Za-z0-9_\-+/.]{28,}={0,2}/g;
|
|
4436
|
-
function redactDiagnostic(text, maxLen = MAX_DIAGNOSTIC_LEN) {
|
|
4437
|
-
if (text === undefined || text === null)
|
|
4438
|
-
return "";
|
|
4439
|
-
let t = text.replace(SECRET_KV, (_m, key, sep2) => `${key}${sep2}***`);
|
|
4440
|
-
t = t.replace(LONG_TOKEN, "***");
|
|
4441
|
-
t = t.trim();
|
|
4442
|
-
if (t.length > maxLen)
|
|
4443
|
-
t = `${t.slice(0, maxLen - 1)}…`;
|
|
4444
|
-
return t;
|
|
4445
|
-
}
|
|
4446
|
-
|
|
4447
4775
|
// src/commands/status.ts
|
|
4448
|
-
import { readFileSync as
|
|
4776
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
4449
4777
|
|
|
4450
4778
|
// src/deploy/manifest-state.ts
|
|
4451
4779
|
async function fetchManifestState(cfg, slug, opts = {}, fetcher = fetch) {
|
|
@@ -4455,6 +4783,7 @@ async function fetchManifestState(cfg, slug, opts = {}, fetcher = fetch) {
|
|
|
4455
4783
|
slug: raw.slug,
|
|
4456
4784
|
hasAppFile: raw.hasAppFile,
|
|
4457
4785
|
lastAppliedManifestSha: raw.lastAppliedManifestSha,
|
|
4786
|
+
deployedManifestSha: raw.deployedManifestSha ?? raw.lastAppliedManifestSha,
|
|
4458
4787
|
appRepoHeadSha: raw.appRepoHeadSha ?? null,
|
|
4459
4788
|
openPr: raw.openPr,
|
|
4460
4789
|
enforcedGroups: raw.enforcedGroups ?? null,
|
|
@@ -4464,7 +4793,7 @@ async function fetchManifestState(cfg, slug, opts = {}, fetcher = fetch) {
|
|
|
4464
4793
|
|
|
4465
4794
|
// src/commands/infer-slug.ts
|
|
4466
4795
|
import * as path7 from "node:path";
|
|
4467
|
-
import { existsSync as existsSync3, readFileSync as
|
|
4796
|
+
import { existsSync as existsSync3, readFileSync as readFileSync6 } from "node:fs";
|
|
4468
4797
|
import { parse as parseYaml3 } from "yaml";
|
|
4469
4798
|
var DIRNAME_RE = /^launchpad-app-([a-z0-9][a-z0-9-]*[a-z0-9])$/;
|
|
4470
4799
|
function inferSlugFromCwd(cwd) {
|
|
@@ -4487,7 +4816,7 @@ function inferSlugFromManifestFile(manifestPath) {
|
|
|
4487
4816
|
if (!existsSync3(manifestPath))
|
|
4488
4817
|
return null;
|
|
4489
4818
|
try {
|
|
4490
|
-
return resolveManifestSlug(parseYaml3(
|
|
4819
|
+
return resolveManifestSlug(parseYaml3(readFileSync6(manifestPath, "utf8")));
|
|
4491
4820
|
} catch {
|
|
4492
4821
|
return null;
|
|
4493
4822
|
}
|
|
@@ -4503,7 +4832,7 @@ function inferSlug(opts) {
|
|
|
4503
4832
|
}
|
|
4504
4833
|
|
|
4505
4834
|
// src/groups/client.ts
|
|
4506
|
-
import { mkdirSync, readFileSync as
|
|
4835
|
+
import { mkdirSync, readFileSync as readFileSync7, writeFileSync } from "node:fs";
|
|
4507
4836
|
import { dirname as dirname5, join as join8 } from "node:path";
|
|
4508
4837
|
var CACHE_TTL_MS = 60 * 60 * 1000;
|
|
4509
4838
|
var CACHE_FILENAME = "groups.json";
|
|
@@ -4547,7 +4876,7 @@ async function fetchGroups(cfg, opts = {}) {
|
|
|
4547
4876
|
function readCache(path8) {
|
|
4548
4877
|
let raw;
|
|
4549
4878
|
try {
|
|
4550
|
-
raw =
|
|
4879
|
+
raw = readFileSync7(path8, "utf8");
|
|
4551
4880
|
} catch {
|
|
4552
4881
|
return null;
|
|
4553
4882
|
}
|
|
@@ -5183,9 +5512,10 @@ async function runWatch(slug, io, deps = {}) {
|
|
|
5183
5512
|
const cfg = loadConfig();
|
|
5184
5513
|
const now = deps.now ?? (() => Date.now());
|
|
5185
5514
|
const stdout = deps.stdout ?? process.stdout;
|
|
5515
|
+
const readLifecycle = deps.fetchLifecycle ?? (() => fetchLifecycleView(cfg, slug));
|
|
5186
5516
|
let first;
|
|
5187
5517
|
try {
|
|
5188
|
-
first = await
|
|
5518
|
+
first = await readLifecycle();
|
|
5189
5519
|
} catch (e) {
|
|
5190
5520
|
return mapWatchError(e, slug, io);
|
|
5191
5521
|
}
|
|
@@ -5193,6 +5523,9 @@ async function runWatch(slug, io, deps = {}) {
|
|
|
5193
5523
|
io.err(`launchpad watch: no app "${slug}" to watch (unknown slug, or first deploy not started)`);
|
|
5194
5524
|
return 1;
|
|
5195
5525
|
}
|
|
5526
|
+
if (first.statusPageUrl !== undefined) {
|
|
5527
|
+
io.out(`Status page: ${first.statusPageUrl}`);
|
|
5528
|
+
}
|
|
5196
5529
|
if (first.state === "destroyed" || first.state === "destroying" || first.state === "destroy_failed") {
|
|
5197
5530
|
io.out(`${slug}: ${first.state} — nothing to watch.`);
|
|
5198
5531
|
return 0;
|
|
@@ -5219,7 +5552,7 @@ async function runWatch(slug, io, deps = {}) {
|
|
|
5219
5552
|
};
|
|
5220
5553
|
try {
|
|
5221
5554
|
const res = await runWatchLoop(slug, {
|
|
5222
|
-
fetchLifecycle:
|
|
5555
|
+
fetchLifecycle: readLifecycle,
|
|
5223
5556
|
now,
|
|
5224
5557
|
sleep: makeRealSleep(),
|
|
5225
5558
|
signal: controller.signal,
|
|
@@ -5256,6 +5589,19 @@ var statusCommand = {
|
|
|
5256
5589
|
run: runStatus
|
|
5257
5590
|
};
|
|
5258
5591
|
var SLUG_RE3 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
5592
|
+
function statusTruthFields(args) {
|
|
5593
|
+
return {
|
|
5594
|
+
slug: args.slug,
|
|
5595
|
+
deployedSha: args.deployedSha,
|
|
5596
|
+
headSha: args.headSha,
|
|
5597
|
+
hasOpenPr: args.openPrNumber !== null,
|
|
5598
|
+
openPrNumber: args.openPrNumber,
|
|
5599
|
+
driftFields: [],
|
|
5600
|
+
driftDetails: [],
|
|
5601
|
+
...args.deployment !== undefined ? { deployment: args.deployment } : {},
|
|
5602
|
+
...args.standingExceptions !== undefined ? { standingExceptions: args.standingExceptions } : {}
|
|
5603
|
+
};
|
|
5604
|
+
}
|
|
5259
5605
|
async function fetchLifecycle(cfg, slug) {
|
|
5260
5606
|
try {
|
|
5261
5607
|
return await apiJson(cfg, { path: `/apps/${slug}/lifecycle` });
|
|
@@ -5304,12 +5650,34 @@ async function runStatus(args, io) {
|
|
|
5304
5650
|
return mapBotError(e, parsed.slug, io);
|
|
5305
5651
|
}
|
|
5306
5652
|
if (lifecycle !== null && lifecycle.state !== "live") {
|
|
5307
|
-
|
|
5653
|
+
let output = lifecycleOutput(parsed.slug, lifecycle);
|
|
5654
|
+
if (lifecycle.state === "failed") {
|
|
5655
|
+
try {
|
|
5656
|
+
const deployment2 = await fetchDeploymentStatus(cfg, parsed.slug);
|
|
5657
|
+
output = {
|
|
5658
|
+
...output,
|
|
5659
|
+
deployment: deployment2,
|
|
5660
|
+
...deployment2 === null ? {
|
|
5661
|
+
deploymentUnavailableReason: "the deployment-status endpoint is unavailable or returned an unsupported response"
|
|
5662
|
+
} : {}
|
|
5663
|
+
};
|
|
5664
|
+
} catch (e) {
|
|
5665
|
+
if (e instanceof UnauthenticatedError || e instanceof ForbiddenError) {
|
|
5666
|
+
return mapBotError(e, parsed.slug, io);
|
|
5667
|
+
}
|
|
5668
|
+
output = {
|
|
5669
|
+
...output,
|
|
5670
|
+
deployment: null,
|
|
5671
|
+
deploymentUnavailableReason: redactDiagnostic(describe12(e))
|
|
5672
|
+
};
|
|
5673
|
+
}
|
|
5674
|
+
}
|
|
5675
|
+
emit4(output, parsed.json, io);
|
|
5308
5676
|
return 0;
|
|
5309
5677
|
}
|
|
5310
5678
|
let localYaml = null;
|
|
5311
5679
|
try {
|
|
5312
|
-
localYaml =
|
|
5680
|
+
localYaml = readFileSync8(parsed.file, "utf8");
|
|
5313
5681
|
} catch (e) {
|
|
5314
5682
|
if (!isEnoent(e)) {
|
|
5315
5683
|
io.err(`launchpad status: cannot read local manifest at ${parsed.file}: ${describe12(e)}`);
|
|
@@ -5352,7 +5720,7 @@ async function runStatus(args, io) {
|
|
|
5352
5720
|
if (e instanceof UnauthenticatedError || e instanceof ForbiddenError) {
|
|
5353
5721
|
return mapBotError(e, parsed.slug, io);
|
|
5354
5722
|
}
|
|
5355
|
-
io.err(`launchpad status: live deployment state unavailable (${describe12(e)}) — ` + `the report below is from the platform manifest view only.`);
|
|
5723
|
+
io.err(`launchpad status: live deployment state unavailable (${redactDiagnostic(describe12(e))}) — ` + `the report below is from the platform manifest view only.`);
|
|
5356
5724
|
}
|
|
5357
5725
|
let standingExceptions = null;
|
|
5358
5726
|
try {
|
|
@@ -5370,35 +5738,35 @@ async function runStatus(args, io) {
|
|
|
5370
5738
|
const platformTracked = state.hasAppFile === true || live?.trigger === "bot";
|
|
5371
5739
|
const out = {
|
|
5372
5740
|
state: contentIsLive ? platformTracked ? "live_drift_unknown" : "live_content_untracked" : platformTracked && !deploymentKnown ? "live_drift_unknown" : liveButEmpty ? "live_no_content" : "no_deployed_manifest",
|
|
5373
|
-
slug: parsed.slug,
|
|
5374
5741
|
...local === null ? { drift: null } : {},
|
|
5375
|
-
|
|
5376
|
-
|
|
5377
|
-
|
|
5378
|
-
|
|
5379
|
-
|
|
5380
|
-
|
|
5381
|
-
|
|
5382
|
-
|
|
5742
|
+
...statusTruthFields({
|
|
5743
|
+
slug: parsed.slug,
|
|
5744
|
+
deployedSha: state.deployedManifestSha,
|
|
5745
|
+
headSha: state.appRepoHeadSha,
|
|
5746
|
+
openPrNumber: state.openPr?.number ?? null,
|
|
5747
|
+
...deploymentKnown ? { deployment } : {},
|
|
5748
|
+
...standingExceptions !== null ? { standingExceptions } : {}
|
|
5749
|
+
}),
|
|
5750
|
+
...lifecycle?.statusPageUrl !== undefined ? { statusPageUrl: lifecycle.statusPageUrl } : {}
|
|
5383
5751
|
};
|
|
5384
|
-
|
|
5752
|
+
emit4(out, parsed.json, io);
|
|
5385
5753
|
return 0;
|
|
5386
5754
|
}
|
|
5387
5755
|
if (local === null) {
|
|
5388
5756
|
const out = {
|
|
5389
5757
|
state: "live_drift_unknown",
|
|
5390
|
-
slug: parsed.slug,
|
|
5391
5758
|
drift: null,
|
|
5392
|
-
|
|
5393
|
-
|
|
5394
|
-
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5759
|
+
...statusTruthFields({
|
|
5760
|
+
slug: parsed.slug,
|
|
5761
|
+
deployedSha: state.deployedManifestSha,
|
|
5762
|
+
headSha: state.appRepoHeadSha,
|
|
5763
|
+
openPrNumber: state.openPr?.number ?? null,
|
|
5764
|
+
...deploymentKnown ? { deployment } : {},
|
|
5765
|
+
...standingExceptions !== null ? { standingExceptions } : {}
|
|
5766
|
+
}),
|
|
5767
|
+
...lifecycle?.statusPageUrl !== undefined ? { statusPageUrl: lifecycle.statusPageUrl } : {}
|
|
5400
5768
|
};
|
|
5401
|
-
|
|
5769
|
+
emit4(out, parsed.json, io);
|
|
5402
5770
|
if (parsed.strict) {
|
|
5403
5771
|
if (deployment?.liveDeployment?.buildStatus === "failure") {
|
|
5404
5772
|
io.err(`launchpad status: --strict: live build FAILED ` + `(drift not evaluated — no local launchpad.yaml here).`);
|
|
@@ -5413,12 +5781,12 @@ async function runStatus(args, io) {
|
|
|
5413
5781
|
const { parse: parseYaml4 } = await import("yaml");
|
|
5414
5782
|
deployedObj = parseYaml4(state.manifestYaml);
|
|
5415
5783
|
} catch (e) {
|
|
5416
|
-
io.err(`launchpad status: deployed manifest at ${state.
|
|
5784
|
+
io.err(`launchpad status: deployed manifest at ${state.deployedManifestSha} is not valid YAML: ${describe12(e)}`);
|
|
5417
5785
|
return 2;
|
|
5418
5786
|
}
|
|
5419
5787
|
const deployedParse = parseManifest(deployedObj);
|
|
5420
5788
|
if (deployedParse.kind !== "ok") {
|
|
5421
|
-
io.err(`launchpad status: deployed manifest at ${state.
|
|
5789
|
+
io.err(`launchpad status: deployed manifest at ${state.deployedManifestSha} failed schema validation:`);
|
|
5422
5790
|
for (const issue of deployedParse.issues) {
|
|
5423
5791
|
io.err(` - ${issue.path}: ${issue.message}`);
|
|
5424
5792
|
}
|
|
@@ -5438,7 +5806,7 @@ async function runStatus(args, io) {
|
|
|
5438
5806
|
cfg,
|
|
5439
5807
|
refs: allowedEntraGroups(deployed.access),
|
|
5440
5808
|
source: "deployed manifest",
|
|
5441
|
-
manifestSha: state.
|
|
5809
|
+
manifestSha: state.deployedManifestSha
|
|
5442
5810
|
});
|
|
5443
5811
|
if (deployedGroups.kind === "error") {
|
|
5444
5812
|
return renderStatusGroupError(deployedGroups.envelope, parsed.json, io);
|
|
@@ -5453,7 +5821,7 @@ async function runStatus(args, io) {
|
|
|
5453
5821
|
cfg,
|
|
5454
5822
|
refs: state.enforcedGroups,
|
|
5455
5823
|
source: "enforced access",
|
|
5456
|
-
manifestSha: state.
|
|
5824
|
+
manifestSha: state.deployedManifestSha
|
|
5457
5825
|
});
|
|
5458
5826
|
if (enforcedGroups.kind === "error") {
|
|
5459
5827
|
return renderStatusGroupError(enforcedGroups.envelope, parsed.json, io);
|
|
@@ -5462,7 +5830,7 @@ async function runStatus(args, io) {
|
|
|
5462
5830
|
}
|
|
5463
5831
|
const localAssignmentGroups = (local.auth ?? "access") === "access" ? localGroups : null;
|
|
5464
5832
|
const deployedAssignmentGroups = (deployed.auth ?? "access") === "access" ? deployedGroups : null;
|
|
5465
|
-
const assignmentCheck = localAssignmentGroups === null && deployedAssignmentGroups === null ? { kind: "ok", issues: [] } : await findAssignmentIssues(cfg, localAssignmentGroups, deployedAssignmentGroups, state.
|
|
5833
|
+
const assignmentCheck = localAssignmentGroups === null && deployedAssignmentGroups === null ? { kind: "ok", issues: [] } : await findAssignmentIssues(cfg, localAssignmentGroups, deployedAssignmentGroups, state.deployedManifestSha);
|
|
5466
5834
|
if (assignmentCheck.kind === "error") {
|
|
5467
5835
|
return renderStatusGroupError(assignmentCheck.envelope, parsed.json, io);
|
|
5468
5836
|
}
|
|
@@ -5470,7 +5838,7 @@ async function runStatus(args, io) {
|
|
|
5470
5838
|
const result = {
|
|
5471
5839
|
state: drift.length === 0 ? "in_sync" : "drift",
|
|
5472
5840
|
slug: parsed.slug,
|
|
5473
|
-
deployedSha: state.
|
|
5841
|
+
deployedSha: state.deployedManifestSha,
|
|
5474
5842
|
headSha: state.appRepoHeadSha,
|
|
5475
5843
|
hasOpenPr: state.openPr !== null,
|
|
5476
5844
|
openPrNumber: state.openPr?.number ?? null,
|
|
@@ -5479,9 +5847,10 @@ async function runStatus(args, io) {
|
|
|
5479
5847
|
...deploymentKnown ? { deployment } : {},
|
|
5480
5848
|
...standingExceptions !== null ? { standingExceptions } : {},
|
|
5481
5849
|
...accessDrift !== undefined ? { accessDrift } : {},
|
|
5482
|
-
...groupIssues.length === 0 ? {} : { groupIssues }
|
|
5850
|
+
...groupIssues.length === 0 ? {} : { groupIssues },
|
|
5851
|
+
...lifecycle?.statusPageUrl !== undefined ? { statusPageUrl: lifecycle.statusPageUrl } : {}
|
|
5483
5852
|
};
|
|
5484
|
-
|
|
5853
|
+
emit4(result, parsed.json, io);
|
|
5485
5854
|
if (parsed.strict && (result.state === "drift" || accessDrift?.inSync === false || groupIssues.length > 0)) {
|
|
5486
5855
|
return 1;
|
|
5487
5856
|
}
|
|
@@ -5674,14 +6043,18 @@ function lifecycleOutput(slug, lc) {
|
|
|
5674
6043
|
hasOpenPr: false,
|
|
5675
6044
|
openPrNumber: null,
|
|
5676
6045
|
driftFields: [],
|
|
5677
|
-
driftDetails: []
|
|
6046
|
+
driftDetails: [],
|
|
6047
|
+
...lc.statusPageUrl !== undefined ? { statusPageUrl: lc.statusPageUrl } : {}
|
|
5678
6048
|
};
|
|
5679
6049
|
}
|
|
5680
|
-
function
|
|
6050
|
+
function emit4(out, asJson, io) {
|
|
5681
6051
|
if (asJson) {
|
|
5682
6052
|
io.out(JSON.stringify(out, null, 2));
|
|
5683
6053
|
return;
|
|
5684
6054
|
}
|
|
6055
|
+
if (out.statusPageUrl !== undefined) {
|
|
6056
|
+
io.out(`Status page: ${out.statusPageUrl}`);
|
|
6057
|
+
}
|
|
5685
6058
|
switch (out.state) {
|
|
5686
6059
|
case "provisioning":
|
|
5687
6060
|
io.out(`${out.slug}: provisioning (stage: ${out.stage ?? "starting"})` + (out.submissionId ? ` — submission ${out.submissionId}` : ""));
|
|
@@ -5689,12 +6062,16 @@ function emit3(out, asJson, io) {
|
|
|
5689
6062
|
return;
|
|
5690
6063
|
case "provisioning_failed":
|
|
5691
6064
|
io.out(`${out.slug}: provisioning FAILED at stage ${out.stage ?? "unknown"}` + (out.failedReason ? `: ${out.failedReason}` : ""));
|
|
5692
|
-
if (
|
|
6065
|
+
if (out.deployment?.reconciledToLive === true) {
|
|
6066
|
+
io.out(` that lifecycle state is stale: this read verified the live build and reconciled the record failed→live.`);
|
|
6067
|
+
io.out(` no action needed — re-run \`launchpad status ${out.slug}\` to see the reconciled state.`);
|
|
6068
|
+
} else if (isLikelyLiveFailureStage(out.stage)) {
|
|
5693
6069
|
io.out(` the app may already be live — run \`launchpad recover ${out.slug}\` to reconcile the record;`);
|
|
5694
6070
|
io.out(` if it is not serving, the same command starts the stage-dependent recovery.`);
|
|
5695
6071
|
} else {
|
|
5696
6072
|
io.out(` run \`launchpad recover ${out.slug}\` to start the stage-dependent recovery.`);
|
|
5697
6073
|
}
|
|
6074
|
+
surfaceFailedLifecycleDeployment(out, io);
|
|
5698
6075
|
return;
|
|
5699
6076
|
case "destroying":
|
|
5700
6077
|
io.out(`${out.slug}: destroying…`);
|
|
@@ -5791,6 +6168,20 @@ function triggerLabel(trigger) {
|
|
|
5791
6168
|
return "launchpad deploy (bot)";
|
|
5792
6169
|
return "an unknown mechanism";
|
|
5793
6170
|
}
|
|
6171
|
+
function surfaceFailedLifecycleDeployment(out, io) {
|
|
6172
|
+
if (out.deploymentUnavailableReason !== undefined) {
|
|
6173
|
+
io.out(` latest deployment evidence unavailable: ${out.deploymentUnavailableReason}`);
|
|
6174
|
+
return;
|
|
6175
|
+
}
|
|
6176
|
+
if (out.deployment === undefined || out.deployment === null)
|
|
6177
|
+
return;
|
|
6178
|
+
if (!out.deployment.supported) {
|
|
6179
|
+
io.out(" latest deployment evidence: not applicable because this app has no Pages deployment surface.");
|
|
6180
|
+
return;
|
|
6181
|
+
}
|
|
6182
|
+
io.out(" latest deployment evidence (may not be the cause of this lifecycle failure):");
|
|
6183
|
+
surfaceDeployment(out, io);
|
|
6184
|
+
}
|
|
5794
6185
|
function surfaceDeployment(out, io) {
|
|
5795
6186
|
const dep = out.deployment;
|
|
5796
6187
|
if (dep === undefined || dep === null || !dep.supported)
|
|
@@ -5807,15 +6198,15 @@ function surfaceDeployment(out, io) {
|
|
|
5807
6198
|
const via = triggerLabel(live.trigger);
|
|
5808
6199
|
switch (live.buildStatus) {
|
|
5809
6200
|
case "success":
|
|
5810
|
-
io.out(` last deployment: build success (${via}${commit}) at ${live.createdOn}`);
|
|
6201
|
+
io.out(` last deployment: build success (${via}${commit}) at ${live.createdOn} [deployment ${live.id}]`);
|
|
5811
6202
|
return;
|
|
5812
6203
|
case "in_progress":
|
|
5813
|
-
io.out(` last deployment: build IN PROGRESS (${via}${commit}, started ${live.createdOn})`);
|
|
6204
|
+
io.out(` last deployment: build IN PROGRESS (${via}${commit}, started ${live.createdOn}) [deployment ${live.id}]`);
|
|
5814
6205
|
io.out(" re-run `launchpad status` shortly to confirm the build outcome.");
|
|
5815
6206
|
return;
|
|
5816
6207
|
case "failure": {
|
|
5817
6208
|
const stage = live.failedStage !== undefined ? ` at stage "${live.failedStage}"` : "";
|
|
5818
|
-
io.out(` last deployment: build FAILED${stage} (${via}${commit}) at ${live.createdOn}`);
|
|
6209
|
+
io.out(` last deployment: build FAILED${stage} (${via}${commit}) at ${live.createdOn} [deployment ${live.id}]`);
|
|
5819
6210
|
if (live.logExcerpt !== undefined && live.logExcerpt.length > 0) {
|
|
5820
6211
|
io.out(" build log (excerpt):");
|
|
5821
6212
|
for (const line of live.logExcerpt.split(`
|
|
@@ -5988,7 +6379,7 @@ function readLocalEntrypoint(cwd) {
|
|
|
5988
6379
|
for (const rel of ["index.html", join9("static", "index.html")]) {
|
|
5989
6380
|
const p = join9(cwd, rel);
|
|
5990
6381
|
if (existsSync4(p))
|
|
5991
|
-
return new Uint8Array(
|
|
6382
|
+
return new Uint8Array(readFileSync9(p));
|
|
5992
6383
|
}
|
|
5993
6384
|
return null;
|
|
5994
6385
|
}
|
|
@@ -6222,7 +6613,7 @@ async function verifyFirstDeployServed(args, liveUrl, io, deps) {
|
|
|
6222
6613
|
|
|
6223
6614
|
// src/commands/deploy.ts
|
|
6224
6615
|
import { parse as parseYaml6 } from "yaml";
|
|
6225
|
-
import { readFileSync as
|
|
6616
|
+
import { readFileSync as readFileSync11 } from "node:fs";
|
|
6226
6617
|
|
|
6227
6618
|
// src/deploy/git-files.ts
|
|
6228
6619
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -6479,6 +6870,7 @@ function parseDeployFlags(args) {
|
|
|
6479
6870
|
let rebaseOntoHead = false;
|
|
6480
6871
|
let overrideStale = null;
|
|
6481
6872
|
let allowUnguarded = false;
|
|
6873
|
+
let noOpen = false;
|
|
6482
6874
|
let i = 0;
|
|
6483
6875
|
while (i < args.length) {
|
|
6484
6876
|
const a = args[i] ?? "";
|
|
@@ -6683,6 +7075,11 @@ function parseDeployFlags(args) {
|
|
|
6683
7075
|
i += 1;
|
|
6684
7076
|
continue;
|
|
6685
7077
|
}
|
|
7078
|
+
if (a === "--no-open") {
|
|
7079
|
+
noOpen = true;
|
|
7080
|
+
i += 1;
|
|
7081
|
+
continue;
|
|
7082
|
+
}
|
|
6686
7083
|
if (a.startsWith("--verify-timeout=")) {
|
|
6687
7084
|
const v = a.slice("--verify-timeout=".length);
|
|
6688
7085
|
const n = Number(v);
|
|
@@ -6703,6 +7100,9 @@ function parseDeployFlags(args) {
|
|
|
6703
7100
|
if (rebaseOntoHead && overrideStale !== null) {
|
|
6704
7101
|
return "--rebase-onto-head and --override-stale are mutually exclusive — re-stamp to head, or override in place, not both";
|
|
6705
7102
|
}
|
|
7103
|
+
if (noOpen && mode !== "content" && mode !== "apply") {
|
|
7104
|
+
return "--no-open is only valid for a content deploy or --apply";
|
|
7105
|
+
}
|
|
6706
7106
|
if (mode === "dry-run") {
|
|
6707
7107
|
if (slug !== null)
|
|
6708
7108
|
return "--dry-run does not accept --slug (slug is read from launchpad.yaml)";
|
|
@@ -6759,7 +7159,8 @@ function parseDeployFlags(args) {
|
|
|
6759
7159
|
timeoutMinutes
|
|
6760
7160
|
},
|
|
6761
7161
|
message: null,
|
|
6762
|
-
timeoutSeconds: null
|
|
7162
|
+
timeoutSeconds: null,
|
|
7163
|
+
...noOpen ? { noOpen: true } : {}
|
|
6763
7164
|
};
|
|
6764
7165
|
}
|
|
6765
7166
|
if (resumePrNumber !== null) {
|
|
@@ -6800,7 +7201,8 @@ function parseDeployFlags(args) {
|
|
|
6800
7201
|
allowUnguarded
|
|
6801
7202
|
},
|
|
6802
7203
|
message,
|
|
6803
|
-
timeoutSeconds
|
|
7204
|
+
timeoutSeconds,
|
|
7205
|
+
...noOpen ? { noOpen: true } : {}
|
|
6804
7206
|
};
|
|
6805
7207
|
}
|
|
6806
7208
|
if (mode === "resume" || mode === "abandon") {
|
|
@@ -6854,12 +7256,12 @@ import { existsSync as existsSync6, rmSync } from "node:fs";
|
|
|
6854
7256
|
import { resolve as resolvePath, join as join10 } from "node:path";
|
|
6855
7257
|
|
|
6856
7258
|
// src/manifest/load.ts
|
|
6857
|
-
import { readFileSync as
|
|
7259
|
+
import { readFileSync as readFileSync10 } from "node:fs";
|
|
6858
7260
|
import { parse as parseYaml5, YAMLParseError } from "yaml";
|
|
6859
7261
|
function loadManifest(path8) {
|
|
6860
7262
|
let raw;
|
|
6861
7263
|
try {
|
|
6862
|
-
raw =
|
|
7264
|
+
raw = readFileSync10(path8, "utf8");
|
|
6863
7265
|
} catch (err) {
|
|
6864
7266
|
const e = err;
|
|
6865
7267
|
if (e.code === "ENOENT") {
|
|
@@ -6894,6 +7296,38 @@ function formatIssues(error) {
|
|
|
6894
7296
|
}));
|
|
6895
7297
|
}
|
|
6896
7298
|
|
|
7299
|
+
// src/status-page-output.ts
|
|
7300
|
+
async function surfaceStatusPageUrl(url, io, options) {
|
|
7301
|
+
if (typeof url !== "string" || url.length === 0)
|
|
7302
|
+
return;
|
|
7303
|
+
io.out(`Status page: ${url}`);
|
|
7304
|
+
const env = options.env ?? process.env;
|
|
7305
|
+
const isTty = options.isTty ?? process.stdout.isTTY === true;
|
|
7306
|
+
if (options.noOpen || !options.autoOpenEnabled || !isTty || env.CI !== undefined) {
|
|
7307
|
+
return;
|
|
7308
|
+
}
|
|
7309
|
+
if (!isAllowedStatusPageUrl(url, options.allowedHost)) {
|
|
7310
|
+
io.err("warning: refused to open an untrusted status page URL");
|
|
7311
|
+
return;
|
|
7312
|
+
}
|
|
7313
|
+
try {
|
|
7314
|
+
await (options.open ?? openBrowser)(url);
|
|
7315
|
+
} catch (error) {
|
|
7316
|
+
io.err(`warning: couldn't open the status page automatically: ${describe13(error)}`);
|
|
7317
|
+
}
|
|
7318
|
+
}
|
|
7319
|
+
function isAllowedStatusPageUrl(url, allowedHost) {
|
|
7320
|
+
try {
|
|
7321
|
+
const parsed = new URL(url);
|
|
7322
|
+
return parsed.protocol === "https:" && (allowedHost === undefined || parsed.hostname === allowedHost);
|
|
7323
|
+
} catch {
|
|
7324
|
+
return false;
|
|
7325
|
+
}
|
|
7326
|
+
}
|
|
7327
|
+
function describe13(error) {
|
|
7328
|
+
return error instanceof Error ? error.message : String(error);
|
|
7329
|
+
}
|
|
7330
|
+
|
|
6897
7331
|
// src/deploy/apply.ts
|
|
6898
7332
|
var DEFAULT_TIMEOUT_MINUTES = 90;
|
|
6899
7333
|
var DEFAULT_POLL_INTERVAL_SECONDS = 10;
|
|
@@ -7028,6 +7462,11 @@ async function runDeployApply(opts, io, deps = {}) {
|
|
|
7028
7462
|
io.err(`launchpad deploy --apply: protocol failure from /apps/${slug}/manifest/apply: ` + `requested ${manifestSha}, received ${String(apply.manifestSha)}`);
|
|
7029
7463
|
return 2;
|
|
7030
7464
|
}
|
|
7465
|
+
await surfaceStatusPageUrl(apply.statusPageUrl, io, {
|
|
7466
|
+
noOpen: opts.noOpen === true,
|
|
7467
|
+
autoOpenEnabled: cfg.statusPageAutoOpen,
|
|
7468
|
+
allowedHost: cfg.statusPageHost
|
|
7469
|
+
});
|
|
7031
7470
|
io.out("");
|
|
7032
7471
|
io.out(`PR opened: ${apply.prUrl}`);
|
|
7033
7472
|
io.out(`Branch: ${apply.branch}`);
|
|
@@ -7102,7 +7541,7 @@ async function pollUntilApplied(args) {
|
|
|
7102
7541
|
breakDots();
|
|
7103
7542
|
return { kind: "failed", reason: e.message };
|
|
7104
7543
|
}
|
|
7105
|
-
args.io.err(`! apply-status fetch failed (will retry): ${
|
|
7544
|
+
args.io.err(`! apply-status fetch failed (will retry): ${describe14(e)}`);
|
|
7106
7545
|
await sleep(args.pollIntervalSec * 1000);
|
|
7107
7546
|
continue;
|
|
7108
7547
|
}
|
|
@@ -7149,7 +7588,7 @@ async function pollUntilApplied(args) {
|
|
|
7149
7588
|
path: `/apps/${args.slug}/manifest/state`
|
|
7150
7589
|
}, args.fetcher);
|
|
7151
7590
|
} catch (e) {
|
|
7152
|
-
args.io.err(`! state fetch failed (will retry): ${
|
|
7591
|
+
args.io.err(`! state fetch failed (will retry): ${describe14(e)}`);
|
|
7153
7592
|
await sleep(args.pollIntervalSec * 1000);
|
|
7154
7593
|
continue;
|
|
7155
7594
|
}
|
|
@@ -7223,7 +7662,7 @@ function deletePinIfPresent(cfg, slug, io) {
|
|
|
7223
7662
|
rmSync(pinPath, { force: true });
|
|
7224
7663
|
io.out(` (cleaned up obsolete pin file ${pinPath})`);
|
|
7225
7664
|
} catch (e) {
|
|
7226
|
-
io.err(`! warning: failed to delete obsolete pin file ${pinPath}: ${
|
|
7665
|
+
io.err(`! warning: failed to delete obsolete pin file ${pinPath}: ${describe14(e)}`);
|
|
7227
7666
|
}
|
|
7228
7667
|
}
|
|
7229
7668
|
async function loadSlugForResume(opts, io) {
|
|
@@ -7247,7 +7686,7 @@ async function defaultPrompt(question) {
|
|
|
7247
7686
|
rl.close();
|
|
7248
7687
|
}
|
|
7249
7688
|
}
|
|
7250
|
-
function
|
|
7689
|
+
function describe14(e) {
|
|
7251
7690
|
return e instanceof Error ? e.message : String(e);
|
|
7252
7691
|
}
|
|
7253
7692
|
function mapHttpError(e, slug, io) {
|
|
@@ -7271,7 +7710,7 @@ function mapHttpError(e, slug, io) {
|
|
|
7271
7710
|
io.err(`launchpad deploy --apply: ${e.message}`);
|
|
7272
7711
|
return 1;
|
|
7273
7712
|
}
|
|
7274
|
-
io.err(`launchpad deploy --apply failed: ${
|
|
7713
|
+
io.err(`launchpad deploy --apply failed: ${describe14(e)}`);
|
|
7275
7714
|
return 2;
|
|
7276
7715
|
}
|
|
7277
7716
|
function renderManifestError(loaded, io) {
|
|
@@ -7314,7 +7753,7 @@ function renderAccessDriftSummary(slug, body) {
|
|
|
7314
7753
|
return lines;
|
|
7315
7754
|
}
|
|
7316
7755
|
async function handleAccessDrift(params) {
|
|
7317
|
-
const { slug, manifestPath, body, io, deps, outcome } = params;
|
|
7756
|
+
const { slug, manifestPath, body, io, deps, noOpen, outcome } = params;
|
|
7318
7757
|
if (outcome === undefined) {
|
|
7319
7758
|
for (const line of renderAccessDriftSummary(slug, body))
|
|
7320
7759
|
io.err(line);
|
|
@@ -7330,6 +7769,7 @@ async function handleAccessDrift(params) {
|
|
|
7330
7769
|
platformRepo: null,
|
|
7331
7770
|
rePin: false,
|
|
7332
7771
|
yes: true,
|
|
7772
|
+
noOpen,
|
|
7333
7773
|
...outcome === undefined ? {} : { atSha: outcome.manifest_sha },
|
|
7334
7774
|
...outcome?.pr === undefined ? {} : {
|
|
7335
7775
|
preopenedPr: {
|
|
@@ -7370,7 +7810,7 @@ async function runDeployDryRun(opts, io, deps = {}) {
|
|
|
7370
7810
|
try {
|
|
7371
7811
|
manifestSha = (deps.gitHeadSha ?? defaultGitHeadSha)(process.cwd());
|
|
7372
7812
|
} catch (e) {
|
|
7373
|
-
const msg = `failed to resolve git HEAD in ${process.cwd()}: ${
|
|
7813
|
+
const msg = `failed to resolve git HEAD in ${process.cwd()}: ${describe15(e)}`;
|
|
7374
7814
|
if (opts.json) {
|
|
7375
7815
|
io.out(JSON.stringify({ ok: false, kind: "git-error", message: msg }));
|
|
7376
7816
|
} else {
|
|
@@ -7419,7 +7859,7 @@ function defaultGitHeadSha(cwd) {
|
|
|
7419
7859
|
});
|
|
7420
7860
|
return out.trim();
|
|
7421
7861
|
}
|
|
7422
|
-
function
|
|
7862
|
+
function describe15(e) {
|
|
7423
7863
|
return e instanceof Error ? e.message : String(e);
|
|
7424
7864
|
}
|
|
7425
7865
|
function mapHttpError2(e, slug, json, io) {
|
|
@@ -7718,6 +8158,81 @@ function handleNetworkError(e, io, slug, verb) {
|
|
|
7718
8158
|
return 1;
|
|
7719
8159
|
}
|
|
7720
8160
|
|
|
8161
|
+
// src/manifest/container-filesystem.ts
|
|
8162
|
+
import { lstatSync as lstatSync3, realpathSync } from "node:fs";
|
|
8163
|
+
import { dirname as dirname6, join as join11, relative as relative3, resolve as resolve9, sep as sep2 } from "node:path";
|
|
8164
|
+
function validateContainerFilesystem(manifestPath, capability) {
|
|
8165
|
+
const root = realpathSync(dirname6(manifestPath));
|
|
8166
|
+
const segments = capability.container.image.slice(2).split("/");
|
|
8167
|
+
let current = root;
|
|
8168
|
+
for (const segment of segments) {
|
|
8169
|
+
current = join11(current, segment);
|
|
8170
|
+
let stat;
|
|
8171
|
+
try {
|
|
8172
|
+
stat = lstatSync3(current);
|
|
8173
|
+
} catch (error) {
|
|
8174
|
+
return failure("container_image_unreadable", relative3(root, current), `cannot inspect container image path: ${describe16(error)}`);
|
|
8175
|
+
}
|
|
8176
|
+
if (stat.isSymbolicLink()) {
|
|
8177
|
+
return failure("container_image_symlink", relative3(root, current), "container image path must not contain symbolic links");
|
|
8178
|
+
}
|
|
8179
|
+
}
|
|
8180
|
+
let finalPath;
|
|
8181
|
+
try {
|
|
8182
|
+
finalPath = realpathSync(current);
|
|
8183
|
+
} catch (error) {
|
|
8184
|
+
return failure("container_image_unreadable", relative3(root, current), `cannot resolve container image path: ${describe16(error)}`);
|
|
8185
|
+
}
|
|
8186
|
+
const rootPrefix = resolve9(root) + sep2;
|
|
8187
|
+
if (!resolve9(finalPath).startsWith(rootPrefix)) {
|
|
8188
|
+
return failure("container_image_outside_repository", capability.container.image, "container image resolves outside the app repository");
|
|
8189
|
+
}
|
|
8190
|
+
if (!lstatSync3(finalPath).isFile()) {
|
|
8191
|
+
return failure("container_image_not_regular_file", capability.container.image, "container image must resolve to a regular Dockerfile");
|
|
8192
|
+
}
|
|
8193
|
+
return { kind: "ok", dockerfilePath: finalPath };
|
|
8194
|
+
}
|
|
8195
|
+
function failure(reason, path8, message) {
|
|
8196
|
+
return { kind: "error", reason, path: path8, message };
|
|
8197
|
+
}
|
|
8198
|
+
function describe16(error) {
|
|
8199
|
+
return error instanceof Error ? error.message : String(error);
|
|
8200
|
+
}
|
|
8201
|
+
|
|
8202
|
+
// src/manifest/runtime-capability.ts
|
|
8203
|
+
function validateCliRuntimeCapability(manifestPath, manifest) {
|
|
8204
|
+
const result = validateDeploymentCapability(manifest);
|
|
8205
|
+
if (result.kind === "unsupported" || result.runtime === "pages") {
|
|
8206
|
+
return result;
|
|
8207
|
+
}
|
|
8208
|
+
const filesystem = validateContainerFilesystem(manifestPath, result.capability);
|
|
8209
|
+
return filesystem.kind === "error" ? filesystem : result;
|
|
8210
|
+
}
|
|
8211
|
+
function rejectUnsupportedCliRuntime(args) {
|
|
8212
|
+
const result = validateCliRuntimeCapability(args.manifestPath, args.manifest);
|
|
8213
|
+
if (result.kind !== "unsupported" && result.kind !== "error")
|
|
8214
|
+
return null;
|
|
8215
|
+
if (args.json) {
|
|
8216
|
+
args.io.out(JSON.stringify({
|
|
8217
|
+
ok: false,
|
|
8218
|
+
manifest_valid: true,
|
|
8219
|
+
runtime_supported: false,
|
|
8220
|
+
reason: result.reason,
|
|
8221
|
+
path: result.path,
|
|
8222
|
+
message: result.message
|
|
8223
|
+
}));
|
|
8224
|
+
return 1;
|
|
8225
|
+
}
|
|
8226
|
+
if (args.structurallyValidName !== undefined) {
|
|
8227
|
+
args.io.out(`✓ ${args.structurallyValidName} - manifest is structurally valid`);
|
|
8228
|
+
args.io.err(`✗ runtime unsupported: ${result.reason}`);
|
|
8229
|
+
} else {
|
|
8230
|
+
args.io.err(`launchpad ${args.command}: runtime unsupported (${result.reason})`);
|
|
8231
|
+
}
|
|
8232
|
+
args.io.err(` ${result.path}: ${result.message}`);
|
|
8233
|
+
return 1;
|
|
8234
|
+
}
|
|
8235
|
+
|
|
7721
8236
|
// src/commands/deploy.ts
|
|
7722
8237
|
var deployCommand = {
|
|
7723
8238
|
name: "deploy",
|
|
@@ -7749,7 +8264,8 @@ async function runDeploy(args, io) {
|
|
|
7749
8264
|
yes: flags.mode.yes,
|
|
7750
8265
|
...flags.mode.atSha !== null ? { atSha: flags.mode.atSha } : {},
|
|
7751
8266
|
...flags.mode.resumePrNumber !== null ? { resumePrNumber: flags.mode.resumePrNumber } : {},
|
|
7752
|
-
...flags.mode.timeoutMinutes !== null ? { timeoutMinutes: flags.mode.timeoutMinutes } : {}
|
|
8267
|
+
...flags.mode.timeoutMinutes !== null ? { timeoutMinutes: flags.mode.timeoutMinutes } : {},
|
|
8268
|
+
noOpen: flags.noOpen === true
|
|
7753
8269
|
}, io);
|
|
7754
8270
|
}
|
|
7755
8271
|
if (flags.mode.kind === "abandon") {
|
|
@@ -7785,7 +8301,8 @@ async function runDeploy(args, io) {
|
|
|
7785
8301
|
allowStale: contentMode.allowStale,
|
|
7786
8302
|
rebaseOntoHead: contentMode.rebaseOntoHead,
|
|
7787
8303
|
overrideStale: contentMode.overrideStale,
|
|
7788
|
-
allowUnguarded: contentMode.allowUnguarded
|
|
8304
|
+
allowUnguarded: contentMode.allowUnguarded,
|
|
8305
|
+
noOpen: flags.noOpen === true
|
|
7789
8306
|
});
|
|
7790
8307
|
}
|
|
7791
8308
|
if (flags.mode.kind === "content" && flags.mode.allowUnguarded) {
|
|
@@ -7826,7 +8343,7 @@ async function runDeploy(args, io) {
|
|
|
7826
8343
|
const contentManifestPath = path8.join(process.cwd(), "launchpad.yaml");
|
|
7827
8344
|
if (existsSync7(contentManifestPath)) {
|
|
7828
8345
|
try {
|
|
7829
|
-
contentManifestYaml =
|
|
8346
|
+
contentManifestYaml = readFileSync11(contentManifestPath, "utf8");
|
|
7830
8347
|
} catch {
|
|
7831
8348
|
contentManifestYaml = null;
|
|
7832
8349
|
}
|
|
@@ -7859,6 +8376,11 @@ async function runDeploy(args, io) {
|
|
|
7859
8376
|
headers,
|
|
7860
8377
|
jsonBody: undefined
|
|
7861
8378
|
}, makeBytesFetcher(packed.bytes, `/apps/${slug}/deploy-pr`));
|
|
8379
|
+
await surfaceStatusPageUrl(response.statusPageUrl, io, {
|
|
8380
|
+
noOpen: flags.noOpen === true,
|
|
8381
|
+
autoOpenEnabled: cfg.statusPageAutoOpen,
|
|
8382
|
+
allowedHost: cfg.statusPageHost
|
|
8383
|
+
});
|
|
7862
8384
|
io.out("");
|
|
7863
8385
|
io.out(`PR opened: ${response.reviewUrl}`);
|
|
7864
8386
|
io.out(`Branch: ${response.branch}`);
|
|
@@ -7904,12 +8426,12 @@ async function runDeploy(args, io) {
|
|
|
7904
8426
|
io.err(`launchpad deploy: ${e.message}`);
|
|
7905
8427
|
return 1;
|
|
7906
8428
|
}
|
|
7907
|
-
io.err(`launchpad deploy failed: ${
|
|
8429
|
+
io.err(`launchpad deploy failed: ${describe17(e)}`);
|
|
7908
8430
|
return 1;
|
|
7909
8431
|
}
|
|
7910
8432
|
}
|
|
7911
8433
|
function deployUsage() {
|
|
7912
|
-
return `usage: launchpad deploy [--message <text>] [--slug <slug>] [--allow-unguarded]
|
|
8434
|
+
return `usage: launchpad deploy [--message <text>] [--slug <slug>] [--allow-unguarded] [--no-open]
|
|
7913
8435
|
` + ` (slug comes from launchpad.yaml at cwd when present — Model A —
|
|
7914
8436
|
` + " else from the current directory's `launchpad-app-<slug>` suffix)\n" + `
|
|
7915
8437
|
` + `provisioning modes:
|
|
@@ -7947,6 +8469,10 @@ function parseArgs3(args) {
|
|
|
7947
8469
|
i += 2;
|
|
7948
8470
|
continue;
|
|
7949
8471
|
}
|
|
8472
|
+
if (a === "--no-open") {
|
|
8473
|
+
i += 1;
|
|
8474
|
+
continue;
|
|
8475
|
+
}
|
|
7950
8476
|
return null;
|
|
7951
8477
|
}
|
|
7952
8478
|
return { slug, message };
|
|
@@ -7975,7 +8501,7 @@ function formatBytes2(n) {
|
|
|
7975
8501
|
return `${(n / 1024).toFixed(1)}KB`;
|
|
7976
8502
|
return `${(n / (1024 * 1024)).toFixed(1)}MB`;
|
|
7977
8503
|
}
|
|
7978
|
-
function
|
|
8504
|
+
function describe17(e) {
|
|
7979
8505
|
return e instanceof Error ? e.message : String(e);
|
|
7980
8506
|
}
|
|
7981
8507
|
function surfaceDeployExtras(body, io, slug, allowStale = false) {
|
|
@@ -8047,11 +8573,11 @@ function surfaceStaleBlock(body, io) {
|
|
|
8047
8573
|
}
|
|
8048
8574
|
var MANIFEST_DIFF_READ_TIMEOUT_MS = 5000;
|
|
8049
8575
|
function withTimeout(p, ms) {
|
|
8050
|
-
return new Promise((
|
|
8576
|
+
return new Promise((resolve10, reject) => {
|
|
8051
8577
|
const timer = setTimeout(() => reject(new Error(`manifest read timed out after ${ms}ms`)), ms);
|
|
8052
8578
|
p.then((v) => {
|
|
8053
8579
|
clearTimeout(timer);
|
|
8054
|
-
|
|
8580
|
+
resolve10(v);
|
|
8055
8581
|
}, (e) => {
|
|
8056
8582
|
clearTimeout(timer);
|
|
8057
8583
|
reject(e instanceof Error ? e : new Error(String(e)));
|
|
@@ -8061,7 +8587,7 @@ function withTimeout(p, ms) {
|
|
|
8061
8587
|
async function surfaceManifestDiff(cfg, slug, manifestPath, io) {
|
|
8062
8588
|
let localYaml;
|
|
8063
8589
|
try {
|
|
8064
|
-
localYaml =
|
|
8590
|
+
localYaml = readFileSync11(manifestPath, "utf8");
|
|
8065
8591
|
} catch {
|
|
8066
8592
|
return;
|
|
8067
8593
|
}
|
|
@@ -8098,7 +8624,7 @@ async function advanceBaseShaMarker(cwd, sha, io) {
|
|
|
8098
8624
|
return;
|
|
8099
8625
|
await writeBaseShaMarker(cwd, sha);
|
|
8100
8626
|
} catch (e) {
|
|
8101
|
-
io.err(`warning: couldn't update the local base-sha marker (${
|
|
8627
|
+
io.err(`warning: couldn't update the local base-sha marker (${describe17(e)}); ` + `a future deploy may re-warn about staleness. Harmless — your deploy landed.`);
|
|
8102
8628
|
}
|
|
8103
8629
|
}
|
|
8104
8630
|
async function runModelADeploy(args) {
|
|
@@ -8106,19 +8632,37 @@ async function runModelADeploy(args) {
|
|
|
8106
8632
|
let slug;
|
|
8107
8633
|
let appType = "static";
|
|
8108
8634
|
try {
|
|
8109
|
-
const manifestObj = parseYaml6(
|
|
8635
|
+
const manifestObj = parseYaml6(readFileSync11(manifestPath, "utf8"));
|
|
8110
8636
|
const metaSlug = resolveManifestSlug(manifestObj);
|
|
8111
8637
|
if (metaSlug === null) {
|
|
8112
8638
|
io.err(`launchpad deploy: launchpad.yaml is missing metadata.slug (v2) / metadata.name (v1). ` + `Run \`launchpad init\` again to regenerate the manifest.`);
|
|
8113
8639
|
return 64;
|
|
8114
8640
|
}
|
|
8115
8641
|
slug = metaSlug;
|
|
8116
|
-
const
|
|
8117
|
-
|
|
8118
|
-
|
|
8642
|
+
const raw = manifestObj;
|
|
8643
|
+
const isV2 = raw?.apiVersion === "launchpad.m-kopa.us/v2";
|
|
8644
|
+
const deploymentType = isV2 ? raw?.spec?.appType : raw?.deployment?.type;
|
|
8645
|
+
if (deploymentType === "static" || deploymentType === "react" || deploymentType === "react+api" || deploymentType === "container") {
|
|
8646
|
+
appType = deploymentType;
|
|
8647
|
+
}
|
|
8648
|
+
if (deploymentType === "container") {
|
|
8649
|
+
const parsed = isV2 ? parseManifestV2(manifestObj) : parseManifest(manifestObj);
|
|
8650
|
+
if (parsed.kind !== "ok") {
|
|
8651
|
+
io.err("launchpad deploy: manifest is structurally invalid:");
|
|
8652
|
+
for (const issue of parsed.issues) {
|
|
8653
|
+
io.err(` ${issue.path}: ${issue.message}`);
|
|
8654
|
+
}
|
|
8655
|
+
return 1;
|
|
8656
|
+
}
|
|
8657
|
+
const capability = validateCliRuntimeCapability(manifestPath, parsed.manifest);
|
|
8658
|
+
if (capability.kind === "unsupported" || capability.kind === "error") {
|
|
8659
|
+
io.err(`launchpad deploy: runtime unsupported (${capability.reason})`);
|
|
8660
|
+
io.err(` ${capability.path}: ${capability.message}`);
|
|
8661
|
+
return 1;
|
|
8662
|
+
}
|
|
8119
8663
|
}
|
|
8120
8664
|
} catch (e) {
|
|
8121
|
-
io.err(`launchpad deploy: failed to read ${manifestPath}: ${
|
|
8665
|
+
io.err(`launchpad deploy: failed to read ${manifestPath}: ${describe17(e)}`);
|
|
8122
8666
|
return 1;
|
|
8123
8667
|
}
|
|
8124
8668
|
const noVerify = args.argv.includes("--no-verify");
|
|
@@ -8148,7 +8692,7 @@ async function runModelADeploy(args) {
|
|
|
8148
8692
|
try {
|
|
8149
8693
|
cfg = loadConfig();
|
|
8150
8694
|
} catch (e) {
|
|
8151
|
-
io.err(`launchpad deploy: ${
|
|
8695
|
+
io.err(`launchpad deploy: ${describe17(e)}`);
|
|
8152
8696
|
return 1;
|
|
8153
8697
|
}
|
|
8154
8698
|
try {
|
|
@@ -8198,7 +8742,7 @@ async function runModelADeploy(args) {
|
|
|
8198
8742
|
io.err(" run `launchpad login` to refresh your session.");
|
|
8199
8743
|
return 1;
|
|
8200
8744
|
}
|
|
8201
|
-
io.err(`launchpad deploy: unexpected error: ${
|
|
8745
|
+
io.err(`launchpad deploy: unexpected error: ${describe17(e)}`);
|
|
8202
8746
|
return 1;
|
|
8203
8747
|
}
|
|
8204
8748
|
switch (result.kind) {
|
|
@@ -8219,6 +8763,11 @@ async function runModelADeploy(args) {
|
|
|
8219
8763
|
return 1;
|
|
8220
8764
|
case "upload-error": {
|
|
8221
8765
|
const body = result.body;
|
|
8766
|
+
await surfaceStatusPageUrl(body.statusPageUrl, io, {
|
|
8767
|
+
noOpen: args.noOpen,
|
|
8768
|
+
autoOpenEnabled: cfg.statusPageAutoOpen,
|
|
8769
|
+
allowedHost: cfg.statusPageHost
|
|
8770
|
+
});
|
|
8222
8771
|
const errorCode = body.error;
|
|
8223
8772
|
if (result.status === 426 && typeof body.minimum === "string" && typeof body.yours === "string") {
|
|
8224
8773
|
io.err(upgradeRequiredMessage(body.minimum, body.yours));
|
|
@@ -8255,6 +8804,7 @@ async function runModelADeploy(args) {
|
|
|
8255
8804
|
manifestPath,
|
|
8256
8805
|
body,
|
|
8257
8806
|
io,
|
|
8807
|
+
noOpen: args.noOpen,
|
|
8258
8808
|
deps: { runApply: runDeployApply }
|
|
8259
8809
|
});
|
|
8260
8810
|
}
|
|
@@ -8272,6 +8822,7 @@ async function runModelADeploy(args) {
|
|
|
8272
8822
|
body,
|
|
8273
8823
|
outcome,
|
|
8274
8824
|
io,
|
|
8825
|
+
noOpen: args.noOpen,
|
|
8275
8826
|
deps: { runApply: runDeployApply }
|
|
8276
8827
|
});
|
|
8277
8828
|
if (accessExit !== 0)
|
|
@@ -8315,6 +8866,11 @@ async function runModelADeploy(args) {
|
|
|
8315
8866
|
io.err(`warning: ${w}`);
|
|
8316
8867
|
}
|
|
8317
8868
|
const success = result.result;
|
|
8869
|
+
await surfaceStatusPageUrl(success.statusPageUrl, io, {
|
|
8870
|
+
noOpen: args.noOpen,
|
|
8871
|
+
autoOpenEnabled: cfg.statusPageAutoOpen,
|
|
8872
|
+
allowedHost: cfg.statusPageHost
|
|
8873
|
+
});
|
|
8318
8874
|
if ("outcome" in success) {
|
|
8319
8875
|
io.out("Nothing to deploy — your app is already live at this content.");
|
|
8320
8876
|
if (typeof success.head_sha === "string") {
|
|
@@ -8359,6 +8915,7 @@ async function runModelADeploy(args) {
|
|
|
8359
8915
|
body: {},
|
|
8360
8916
|
outcome: success.reconcile,
|
|
8361
8917
|
io,
|
|
8918
|
+
noOpen: args.noOpen,
|
|
8362
8919
|
deps: { runApply: runDeployApply }
|
|
8363
8920
|
});
|
|
8364
8921
|
if (accessExit !== 0)
|
|
@@ -8410,7 +8967,7 @@ function pendingReconcileOutcome(body) {
|
|
|
8410
8967
|
}
|
|
8411
8968
|
|
|
8412
8969
|
// src/commands/redeploy.ts
|
|
8413
|
-
import { existsSync as existsSync8, readFileSync as
|
|
8970
|
+
import { existsSync as existsSync8, readFileSync as readFileSync12 } from "node:fs";
|
|
8414
8971
|
import { resolve as resolvePath2 } from "node:path";
|
|
8415
8972
|
import { parse as parseYaml7 } from "yaml";
|
|
8416
8973
|
var SLUG_RE6 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
@@ -8491,7 +9048,7 @@ function readLocalAppType(cwd, file) {
|
|
|
8491
9048
|
if (!existsSync8(manifestPath))
|
|
8492
9049
|
return null;
|
|
8493
9050
|
try {
|
|
8494
|
-
const obj = parseYaml7(
|
|
9051
|
+
const obj = parseYaml7(readFileSync12(manifestPath, "utf8"));
|
|
8495
9052
|
return coerceAppType(obj?.deployment?.type);
|
|
8496
9053
|
} catch {
|
|
8497
9054
|
return null;
|
|
@@ -8673,7 +9230,7 @@ async function runEnvvars(args, io) {
|
|
|
8673
9230
|
io.err(`launchpad envvars: ${e.message}`);
|
|
8674
9231
|
return 1;
|
|
8675
9232
|
}
|
|
8676
|
-
io.err(`launchpad envvars failed: ${
|
|
9233
|
+
io.err(`launchpad envvars failed: ${describe18(e)}`);
|
|
8677
9234
|
return 1;
|
|
8678
9235
|
}
|
|
8679
9236
|
}
|
|
@@ -8770,16 +9327,16 @@ function renderList(envVars, io) {
|
|
|
8770
9327
|
io.out(fmt(row));
|
|
8771
9328
|
}
|
|
8772
9329
|
}
|
|
8773
|
-
function
|
|
9330
|
+
function describe18(e) {
|
|
8774
9331
|
return e instanceof Error ? e.message : String(e);
|
|
8775
9332
|
}
|
|
8776
9333
|
|
|
8777
9334
|
// src/commands/generate.ts
|
|
8778
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
8779
|
-
import { dirname as
|
|
9335
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync13, writeFileSync as writeFileSync2 } from "node:fs";
|
|
9336
|
+
import { dirname as dirname7, resolve as resolve10, relative as relative4 } from "node:path";
|
|
8780
9337
|
var generateCommand = {
|
|
8781
9338
|
name: "generate",
|
|
8782
|
-
summary: "emit derived artefacts
|
|
9339
|
+
summary: "emit derived runtime artefacts from launchpad.yaml",
|
|
8783
9340
|
run: runGenerate
|
|
8784
9341
|
};
|
|
8785
9342
|
async function runGenerate(args, io) {
|
|
@@ -8789,33 +9346,50 @@ async function runGenerate(args, io) {
|
|
|
8789
9346
|
io.err("Usage: launchpad generate [--file <path>] [--dry-run] [--force] [--json]");
|
|
8790
9347
|
return 64;
|
|
8791
9348
|
}
|
|
8792
|
-
const manifestPath =
|
|
9349
|
+
const manifestPath = resolve10(process.cwd(), flags.file ?? "launchpad.yaml");
|
|
8793
9350
|
const result = loadManifest(manifestPath);
|
|
8794
9351
|
if (result.kind !== "ok") {
|
|
8795
9352
|
return flags.json ? renderManifestErrorJson(result, io) : renderManifestErrorHuman(result, io);
|
|
8796
9353
|
}
|
|
8797
|
-
const
|
|
8798
|
-
|
|
8799
|
-
|
|
9354
|
+
const unsupported2 = rejectUnsupportedCliRuntime({
|
|
9355
|
+
command: "generate",
|
|
9356
|
+
manifestPath,
|
|
9357
|
+
manifest: result.manifest,
|
|
9358
|
+
io,
|
|
9359
|
+
json: flags.json
|
|
9360
|
+
});
|
|
9361
|
+
if (unsupported2 !== null)
|
|
9362
|
+
return unsupported2;
|
|
9363
|
+
const appRoot = dirname7(manifestPath);
|
|
9364
|
+
const wranglerPath = resolve10(appRoot, "container", "wrangler.toml");
|
|
9365
|
+
const workerPath = resolve10(appRoot, "container", "worker", "index.ts");
|
|
9366
|
+
const workflowPath = resolve10(appRoot, ".github", "workflows", "deploy.yml");
|
|
8800
9367
|
const wranglerOut = generateWranglerToml(result.manifest);
|
|
9368
|
+
const workerRaw = generateContainerWorker(result.manifest);
|
|
9369
|
+
const workerOut = workerRaw.kind === "ok" ? { kind: "ok", content: workerRaw.content, warnings: [] } : workerRaw;
|
|
8801
9370
|
const workflowOut = generateGithubDeployWorkflow(result.manifest);
|
|
8802
9371
|
if (flags.dryRun) {
|
|
8803
9372
|
return renderDryRun([
|
|
8804
9373
|
{ artefact: "wrangler.toml", path: wranglerPath, result: wranglerOut },
|
|
9374
|
+
{ artefact: "worker/index.ts", path: workerPath, result: workerOut },
|
|
8805
9375
|
{ artefact: "deploy.yml", path: workflowPath, result: workflowOut }
|
|
8806
9376
|
], appRoot, flags.json, io);
|
|
8807
9377
|
}
|
|
8808
9378
|
const reports = [
|
|
8809
9379
|
applyOne("wrangler.toml", wranglerPath, wranglerOut, flags.force),
|
|
9380
|
+
applyOne("worker/index.ts", workerPath, workerOut, flags.force),
|
|
8810
9381
|
applyOne("deploy.yml", workflowPath, workflowOut, flags.force)
|
|
8811
9382
|
];
|
|
8812
9383
|
return flags.json ? renderApplyJson(reports, appRoot, io) : renderApplyHuman(reports, appRoot, io);
|
|
8813
9384
|
}
|
|
8814
9385
|
function applyOne(artefact, path10, out, force) {
|
|
8815
|
-
if (out.kind === "not-applicable") {
|
|
9386
|
+
if (out.kind === "not-applicable" || out.kind === "unsupported") {
|
|
8816
9387
|
return {
|
|
8817
9388
|
artefact,
|
|
8818
|
-
action: {
|
|
9389
|
+
action: {
|
|
9390
|
+
kind: "not-applicable",
|
|
9391
|
+
reason: out.kind === "unsupported" ? `${out.reason}: ${out.message}` : out.reason
|
|
9392
|
+
},
|
|
8819
9393
|
warnings: []
|
|
8820
9394
|
};
|
|
8821
9395
|
}
|
|
@@ -8838,7 +9412,7 @@ function applyOne(artefact, path10, out, force) {
|
|
|
8838
9412
|
};
|
|
8839
9413
|
}
|
|
8840
9414
|
try {
|
|
8841
|
-
mkdirSync2(
|
|
9415
|
+
mkdirSync2(dirname7(path10), { recursive: true });
|
|
8842
9416
|
writeFileSync2(path10, out.content, "utf8");
|
|
8843
9417
|
return {
|
|
8844
9418
|
artefact,
|
|
@@ -8863,7 +9437,7 @@ function applyOne(artefact, path10, out, force) {
|
|
|
8863
9437
|
}
|
|
8864
9438
|
function readIfExists(path10) {
|
|
8865
9439
|
try {
|
|
8866
|
-
return { kind: "ok", content:
|
|
9440
|
+
return { kind: "ok", content: readFileSync13(path10, "utf8") };
|
|
8867
9441
|
} catch (err) {
|
|
8868
9442
|
const e = err;
|
|
8869
9443
|
if (e.code === "ENOENT")
|
|
@@ -8874,12 +9448,12 @@ function readIfExists(path10) {
|
|
|
8874
9448
|
function renderDryRun(inputs, appRoot, json, io) {
|
|
8875
9449
|
if (json) {
|
|
8876
9450
|
const files = inputs.map((i) => {
|
|
8877
|
-
const previewPath = i.preview === undefined ?
|
|
8878
|
-
if (i.result.kind === "not-applicable") {
|
|
9451
|
+
const previewPath = i.preview === undefined ? relative4(appRoot, i.path) : i.path;
|
|
9452
|
+
if (i.result.kind === "not-applicable" || i.result.kind === "unsupported") {
|
|
8879
9453
|
return {
|
|
8880
9454
|
artefact: i.artefact,
|
|
8881
9455
|
kind: "not-applicable",
|
|
8882
|
-
reason: i.result.reason,
|
|
9456
|
+
reason: i.result.kind === "unsupported" ? `${i.result.reason}: ${i.result.message}` : i.result.reason,
|
|
8883
9457
|
...i.preview ? { preview: true, destination: i.preview.destination } : {}
|
|
8884
9458
|
};
|
|
8885
9459
|
}
|
|
@@ -8896,12 +9470,13 @@ function renderDryRun(inputs, appRoot, json, io) {
|
|
|
8896
9470
|
return 0;
|
|
8897
9471
|
}
|
|
8898
9472
|
for (const i of inputs) {
|
|
8899
|
-
if (i.result.kind === "not-applicable") {
|
|
8900
|
-
|
|
9473
|
+
if (i.result.kind === "not-applicable" || i.result.kind === "unsupported") {
|
|
9474
|
+
const reason = i.result.kind === "unsupported" ? `${i.result.reason}: ${i.result.message}` : i.result.reason;
|
|
9475
|
+
io.out(`# ${i.artefact}: skipped - ${reason}`);
|
|
8901
9476
|
io.out("");
|
|
8902
9477
|
continue;
|
|
8903
9478
|
}
|
|
8904
|
-
const headerPath = i.preview === undefined ?
|
|
9479
|
+
const headerPath = i.preview === undefined ? relative4(appRoot, i.path) : i.path;
|
|
8905
9480
|
const header = i.preview === undefined ? `--- ${headerPath} ---` : `--- [preview, destination = ${i.preview.destination}] ${headerPath} ---`;
|
|
8906
9481
|
io.out(header);
|
|
8907
9482
|
io.out(i.result.content);
|
|
@@ -8920,20 +9495,20 @@ function renderApplyHuman(reports, appRoot, io) {
|
|
|
8920
9495
|
const a = r.action;
|
|
8921
9496
|
switch (a.kind) {
|
|
8922
9497
|
case "written":
|
|
8923
|
-
io.out(`✓ ${r.artefact} — wrote ${a.bytes} bytes to ${
|
|
9498
|
+
io.out(`✓ ${r.artefact} — wrote ${a.bytes} bytes to ${relative4(appRoot, a.path)}`);
|
|
8924
9499
|
break;
|
|
8925
9500
|
case "unchanged":
|
|
8926
|
-
io.out(`= ${r.artefact} — unchanged (${
|
|
9501
|
+
io.out(`= ${r.artefact} — unchanged (${relative4(appRoot, a.path)})`);
|
|
8927
9502
|
break;
|
|
8928
9503
|
case "would-overwrite":
|
|
8929
|
-
io.err(`! ${r.artefact} — exists with different content (${
|
|
9504
|
+
io.err(`! ${r.artefact} — exists with different content (${relative4(appRoot, a.path)}); re-run with --force to overwrite`);
|
|
8930
9505
|
wouldOverwrite++;
|
|
8931
9506
|
break;
|
|
8932
9507
|
case "not-applicable":
|
|
8933
9508
|
io.out(`- ${r.artefact} — skipped (${a.reason})`);
|
|
8934
9509
|
break;
|
|
8935
9510
|
case "write-error":
|
|
8936
|
-
io.err(`✗ ${r.artefact} — ${a.message} (${
|
|
9511
|
+
io.err(`✗ ${r.artefact} — ${a.message} (${relative4(appRoot, a.path)})`);
|
|
8937
9512
|
writeError++;
|
|
8938
9513
|
break;
|
|
8939
9514
|
}
|
|
@@ -8952,17 +9527,17 @@ function renderApplyJson(reports, appRoot, io) {
|
|
|
8952
9527
|
const base = { artefact: r.artefact, warnings: r.warnings };
|
|
8953
9528
|
switch (a.kind) {
|
|
8954
9529
|
case "written":
|
|
8955
|
-
return { ...base, kind: a.kind, path:
|
|
9530
|
+
return { ...base, kind: a.kind, path: relative4(appRoot, a.path), bytes: a.bytes };
|
|
8956
9531
|
case "unchanged":
|
|
8957
9532
|
case "would-overwrite":
|
|
8958
|
-
return { ...base, kind: a.kind, path:
|
|
9533
|
+
return { ...base, kind: a.kind, path: relative4(appRoot, a.path) };
|
|
8959
9534
|
case "not-applicable":
|
|
8960
9535
|
return { ...base, kind: a.kind, reason: a.reason };
|
|
8961
9536
|
case "write-error":
|
|
8962
9537
|
return {
|
|
8963
9538
|
...base,
|
|
8964
9539
|
kind: a.kind,
|
|
8965
|
-
path:
|
|
9540
|
+
path: relative4(appRoot, a.path),
|
|
8966
9541
|
message: a.message
|
|
8967
9542
|
};
|
|
8968
9543
|
}
|
|
@@ -9086,13 +9661,13 @@ function decodeJwtPayload(token) {
|
|
|
9086
9661
|
try {
|
|
9087
9662
|
json = Buffer.from(b64UrlToB64(payload), "base64").toString("utf8");
|
|
9088
9663
|
} catch (e) {
|
|
9089
|
-
throw new JwtParseError(`could not base64-decode JWT payload: ${
|
|
9664
|
+
throw new JwtParseError(`could not base64-decode JWT payload: ${describe19(e)}`);
|
|
9090
9665
|
}
|
|
9091
9666
|
let parsed;
|
|
9092
9667
|
try {
|
|
9093
9668
|
parsed = JSON.parse(json);
|
|
9094
9669
|
} catch (e) {
|
|
9095
|
-
throw new JwtParseError(`JWT payload is not JSON: ${
|
|
9670
|
+
throw new JwtParseError(`JWT payload is not JSON: ${describe19(e)}`);
|
|
9096
9671
|
}
|
|
9097
9672
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
9098
9673
|
throw new JwtParseError(`JWT payload is not an object`);
|
|
@@ -9103,7 +9678,7 @@ function b64UrlToB64(s) {
|
|
|
9103
9678
|
const padded = s.padEnd(s.length + (4 - s.length % 4) % 4, "=");
|
|
9104
9679
|
return padded.replace(/-/g, "+").replace(/_/g, "/");
|
|
9105
9680
|
}
|
|
9106
|
-
function
|
|
9681
|
+
function describe19(e) {
|
|
9107
9682
|
return e instanceof Error ? e.message : String(e);
|
|
9108
9683
|
}
|
|
9109
9684
|
|
|
@@ -9130,7 +9705,7 @@ async function runGroupsWhoami(args, io) {
|
|
|
9130
9705
|
try {
|
|
9131
9706
|
payload = decodeJwtPayload(session.accessToken);
|
|
9132
9707
|
} catch (e) {
|
|
9133
|
-
const message = e instanceof JwtParseError ? e.message :
|
|
9708
|
+
const message = e instanceof JwtParseError ? e.message : describe20(e);
|
|
9134
9709
|
if (json) {
|
|
9135
9710
|
io.out(JSON.stringify({ ok: false, kind: "jwt-parse-error", message }));
|
|
9136
9711
|
} else {
|
|
@@ -9211,7 +9786,7 @@ function parseFlags2(args) {
|
|
|
9211
9786
|
}
|
|
9212
9787
|
return { kind: "ok", json };
|
|
9213
9788
|
}
|
|
9214
|
-
function
|
|
9789
|
+
function describe20(e) {
|
|
9215
9790
|
return e instanceof Error ? e.message : String(e);
|
|
9216
9791
|
}
|
|
9217
9792
|
|
|
@@ -9314,7 +9889,7 @@ async function loadGroups(io, refresh, json) {
|
|
|
9314
9889
|
}
|
|
9315
9890
|
return { kind: "error", code: 3 };
|
|
9316
9891
|
}
|
|
9317
|
-
renderFetchError(io,
|
|
9892
|
+
renderFetchError(io, describe21(e), json);
|
|
9318
9893
|
return { kind: "error", code: 2 };
|
|
9319
9894
|
}
|
|
9320
9895
|
}
|
|
@@ -9351,7 +9926,7 @@ async function lookupTenantWide(key) {
|
|
|
9351
9926
|
return { kind: "unauthenticated", message: e.message };
|
|
9352
9927
|
if (e instanceof ForbiddenError)
|
|
9353
9928
|
return { kind: "forbidden", message: e.message };
|
|
9354
|
-
return { kind: "error", message:
|
|
9929
|
+
return { kind: "error", message: describe21(e) };
|
|
9355
9930
|
}
|
|
9356
9931
|
}
|
|
9357
9932
|
function renderLookupFailure(io, json, hit) {
|
|
@@ -9497,9 +10072,9 @@ async function runShow(args, io) {
|
|
|
9497
10072
|
}
|
|
9498
10073
|
const key = parsed.flags.positional[0];
|
|
9499
10074
|
const hit = await lookupTenantWide(key);
|
|
9500
|
-
const
|
|
9501
|
-
if (
|
|
9502
|
-
return
|
|
10075
|
+
const failure2 = renderLookupFailure(io, parsed.flags.json, hit);
|
|
10076
|
+
if (failure2 !== null)
|
|
10077
|
+
return failure2;
|
|
9503
10078
|
if (hit.kind !== "found" && hit.kind !== "ambiguous" && hit.kind !== "not-found") {
|
|
9504
10079
|
return 2;
|
|
9505
10080
|
}
|
|
@@ -9546,9 +10121,9 @@ async function runResolve(args, io) {
|
|
|
9546
10121
|
}
|
|
9547
10122
|
const key = parsed.flags.positional[0];
|
|
9548
10123
|
const hit = await lookupTenantWide(key);
|
|
9549
|
-
const
|
|
9550
|
-
if (
|
|
9551
|
-
return
|
|
10124
|
+
const failure2 = renderLookupFailure(io, parsed.flags.json, hit);
|
|
10125
|
+
if (failure2 !== null)
|
|
10126
|
+
return failure2;
|
|
9552
10127
|
if (hit.kind !== "found" && hit.kind !== "ambiguous" && hit.kind !== "not-found") {
|
|
9553
10128
|
return 2;
|
|
9554
10129
|
}
|
|
@@ -9595,23 +10170,23 @@ function renderTable2(io, groups) {
|
|
|
9595
10170
|
for (const r of rows)
|
|
9596
10171
|
io.out(fmt(r));
|
|
9597
10172
|
}
|
|
9598
|
-
function
|
|
10173
|
+
function describe21(e) {
|
|
9599
10174
|
return e instanceof Error ? e.message : String(e);
|
|
9600
10175
|
}
|
|
9601
10176
|
|
|
9602
10177
|
// src/commands/init.ts
|
|
9603
10178
|
import { createInterface } from "node:readline/promises";
|
|
9604
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as
|
|
9605
|
-
import { dirname as
|
|
10179
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync15, writeFileSync as writeFileSync3 } from "node:fs";
|
|
10180
|
+
import { dirname as dirname8, resolve as resolve11 } from "node:path";
|
|
9606
10181
|
import { stringify as yamlStringify } from "yaml";
|
|
9607
10182
|
|
|
9608
10183
|
// src/detect/index.ts
|
|
9609
|
-
import { existsSync as existsSync9, readFileSync as
|
|
9610
|
-
import { join as
|
|
10184
|
+
import { existsSync as existsSync9, readFileSync as readFileSync14, statSync } from "node:fs";
|
|
10185
|
+
import { join as join13 } from "node:path";
|
|
9611
10186
|
function detectAppShape(cwd) {
|
|
9612
|
-
const hasPackageJson = existsSync9(
|
|
9613
|
-
const hasAnyLockfile = LOCKFILES.some(({ file }) => existsSync9(
|
|
9614
|
-
const hasViteConfig = VITE_CONFIG_NAMES.some((n) => existsSync9(
|
|
10187
|
+
const hasPackageJson = existsSync9(join13(cwd, "package.json"));
|
|
10188
|
+
const hasAnyLockfile = LOCKFILES.some(({ file }) => existsSync9(join13(cwd, file)));
|
|
10189
|
+
const hasViteConfig = VITE_CONFIG_NAMES.some((n) => existsSync9(join13(cwd, n)));
|
|
9615
10190
|
if (!hasPackageJson && !hasAnyLockfile && !hasViteConfig) {
|
|
9616
10191
|
return {
|
|
9617
10192
|
kind: "not-applicable",
|
|
@@ -9657,7 +10232,7 @@ var LOCKFILES = [
|
|
|
9657
10232
|
{ file: "yarn.lock", pm: "yarn" }
|
|
9658
10233
|
];
|
|
9659
10234
|
function detectPackageManager(cwd) {
|
|
9660
|
-
const present = LOCKFILES.filter(({ file }) => existsSync9(
|
|
10235
|
+
const present = LOCKFILES.filter(({ file }) => existsSync9(join13(cwd, file)));
|
|
9661
10236
|
if (present.length === 0) {
|
|
9662
10237
|
return {
|
|
9663
10238
|
kind: "ambiguous",
|
|
@@ -9679,7 +10254,7 @@ function detectPackageManager(cwd) {
|
|
|
9679
10254
|
}
|
|
9680
10255
|
function detectVitePresence(cwd) {
|
|
9681
10256
|
for (const name of VITE_CONFIG_NAMES) {
|
|
9682
|
-
const p =
|
|
10257
|
+
const p = join13(cwd, name);
|
|
9683
10258
|
if (existsSync9(p)) {
|
|
9684
10259
|
return { kind: "ok", value: { path: p } };
|
|
9685
10260
|
}
|
|
@@ -9690,7 +10265,7 @@ function detectVitePresence(cwd) {
|
|
|
9690
10265
|
};
|
|
9691
10266
|
}
|
|
9692
10267
|
function detectAppType(cwd) {
|
|
9693
|
-
const fnDir =
|
|
10268
|
+
const fnDir = join13(cwd, "functions");
|
|
9694
10269
|
let hasFunctionsDir = false;
|
|
9695
10270
|
if (existsSync9(fnDir)) {
|
|
9696
10271
|
try {
|
|
@@ -9702,7 +10277,7 @@ function detectAppType(cwd) {
|
|
|
9702
10277
|
return { kind: "ok", value: hasFunctionsDir ? "react+api" : "react" };
|
|
9703
10278
|
}
|
|
9704
10279
|
function detectBuildCommand(cwd, pm) {
|
|
9705
|
-
const pkgJsonPath =
|
|
10280
|
+
const pkgJsonPath = join13(cwd, "package.json");
|
|
9706
10281
|
if (!existsSync9(pkgJsonPath)) {
|
|
9707
10282
|
return {
|
|
9708
10283
|
kind: "ambiguous",
|
|
@@ -9711,7 +10286,7 @@ function detectBuildCommand(cwd, pm) {
|
|
|
9711
10286
|
}
|
|
9712
10287
|
let pkgJson;
|
|
9713
10288
|
try {
|
|
9714
|
-
pkgJson = JSON.parse(
|
|
10289
|
+
pkgJson = JSON.parse(readFileSync14(pkgJsonPath, "utf8"));
|
|
9715
10290
|
} catch (e) {
|
|
9716
10291
|
return {
|
|
9717
10292
|
kind: "ambiguous",
|
|
@@ -9746,7 +10321,7 @@ var OUT_DIR_REGEX = /\bbuild\s*:\s*\{[^{}]*?\boutDir\s*:\s*['"]([^'"]+)['"]/s;
|
|
|
9746
10321
|
function detectDestinationDir(cwd, vite) {
|
|
9747
10322
|
let text;
|
|
9748
10323
|
try {
|
|
9749
|
-
text =
|
|
10324
|
+
text = readFileSync14(vite.path, "utf8");
|
|
9750
10325
|
} catch (e) {
|
|
9751
10326
|
return {
|
|
9752
10327
|
kind: "ambiguous",
|
|
@@ -9775,7 +10350,7 @@ async function runInit(args, io, prompt) {
|
|
|
9775
10350
|
return 64;
|
|
9776
10351
|
}
|
|
9777
10352
|
const { inputs, options } = parsed;
|
|
9778
|
-
const outPath =
|
|
10353
|
+
const outPath = resolve11(process.cwd(), options.out);
|
|
9779
10354
|
if (existsSync10(outPath) && !options.force) {
|
|
9780
10355
|
io.err(`launchpad init: ${outPath} already exists`);
|
|
9781
10356
|
io.err("Pass --force to overwrite.");
|
|
@@ -9823,7 +10398,7 @@ async function runInit(args, io, prompt) {
|
|
|
9823
10398
|
}
|
|
9824
10399
|
io.out(`✓ wrote ${outPath}`);
|
|
9825
10400
|
if (options.gitignore) {
|
|
9826
|
-
const gitignorePath =
|
|
10401
|
+
const gitignorePath = resolve11(process.cwd(), ".gitignore");
|
|
9827
10402
|
try {
|
|
9828
10403
|
const changed = ensureGitignoreEntries(gitignorePath, [".env", ".env.local"]);
|
|
9829
10404
|
if (changed.length > 0) {
|
|
@@ -9838,8 +10413,8 @@ async function runInit(args, io, prompt) {
|
|
|
9838
10413
|
}
|
|
9839
10414
|
}
|
|
9840
10415
|
if (fillResult.inputs.type === "static") {
|
|
9841
|
-
const serveDir =
|
|
9842
|
-
const indexPath =
|
|
10416
|
+
const serveDir = resolve11(dirname8(outPath), STATIC_SERVE_DIR);
|
|
10417
|
+
const indexPath = resolve11(serveDir, "index.html");
|
|
9843
10418
|
if (!existsSync10(indexPath)) {
|
|
9844
10419
|
try {
|
|
9845
10420
|
mkdirSync3(serveDir, { recursive: true });
|
|
@@ -10233,7 +10808,7 @@ function renderYaml(manifest) {
|
|
|
10233
10808
|
function ensureGitignoreEntries(path10, entries) {
|
|
10234
10809
|
let current = "";
|
|
10235
10810
|
if (existsSync10(path10)) {
|
|
10236
|
-
current =
|
|
10811
|
+
current = readFileSync15(path10, "utf8");
|
|
10237
10812
|
}
|
|
10238
10813
|
const lines = current.split(/\r?\n/);
|
|
10239
10814
|
const present = new Set(lines.map((l) => l.trim()));
|
|
@@ -10306,13 +10881,13 @@ async function runLogin(args, io, deps) {
|
|
|
10306
10881
|
io.out(`Access token expires in ~${expiresIn}s; refreshes silently.`);
|
|
10307
10882
|
return 0;
|
|
10308
10883
|
} catch (e) {
|
|
10309
|
-
io.err(`Gateway login failed: ${
|
|
10884
|
+
io.err(`Gateway login failed: ${describe22(e)}`);
|
|
10310
10885
|
io.err("Falling back to the legacy Cloudflare Access flow (DEPRECATED — this fallback will be removed when the dual-auth window closes).");
|
|
10311
10886
|
io.err("");
|
|
10312
10887
|
return await runLegacyLogin(io, cfg.botUrl, cfg.sessionPath, deps);
|
|
10313
10888
|
}
|
|
10314
10889
|
} catch (e) {
|
|
10315
|
-
io.err(`launchpad login failed: ${
|
|
10890
|
+
io.err(`launchpad login failed: ${describe22(e)}`);
|
|
10316
10891
|
return 1;
|
|
10317
10892
|
}
|
|
10318
10893
|
}
|
|
@@ -10354,7 +10929,7 @@ function printLoginHelp(io) {
|
|
|
10354
10929
|
io.out(" 0 signed in / session stored");
|
|
10355
10930
|
io.out(" 1 login failed (network, browser, or gateway error)");
|
|
10356
10931
|
}
|
|
10357
|
-
function
|
|
10932
|
+
function describe22(e) {
|
|
10358
10933
|
return e instanceof Error ? e.message : String(e);
|
|
10359
10934
|
}
|
|
10360
10935
|
|
|
@@ -10391,14 +10966,14 @@ async function runLogout(args, io, deps) {
|
|
|
10391
10966
|
});
|
|
10392
10967
|
io.out("Server-side session revoked.");
|
|
10393
10968
|
} catch (e) {
|
|
10394
|
-
io.err(`warning: could not revoke the session server-side (${
|
|
10969
|
+
io.err(`warning: could not revoke the session server-side (${describe23(e)}) — clearing the local session anyway. Any in-flight access token expires within 15 minutes.`);
|
|
10395
10970
|
}
|
|
10396
10971
|
}
|
|
10397
10972
|
const had = await clearSession(cfg.sessionPath);
|
|
10398
10973
|
io.out(had ? `Logged out. Session cleared at ${cfg.sessionPath}` : "Already logged out.");
|
|
10399
10974
|
return 0;
|
|
10400
10975
|
} catch (e) {
|
|
10401
|
-
io.err(`launchpad logout failed: ${
|
|
10976
|
+
io.err(`launchpad logout failed: ${describe23(e)}`);
|
|
10402
10977
|
return 1;
|
|
10403
10978
|
}
|
|
10404
10979
|
}
|
|
@@ -10418,7 +10993,7 @@ function printLogoutHelp(io) {
|
|
|
10418
10993
|
io.out(" 0 logged out (or already logged out)");
|
|
10419
10994
|
io.out(" 1 could not clear the local session (file IO / config error)");
|
|
10420
10995
|
}
|
|
10421
|
-
function
|
|
10996
|
+
function describe23(e) {
|
|
10422
10997
|
return e instanceof Error ? e.message : String(e);
|
|
10423
10998
|
}
|
|
10424
10999
|
|
|
@@ -10488,7 +11063,7 @@ async function runLogs(args, io) {
|
|
|
10488
11063
|
io.err(`launchpad logs: ${e.message}`);
|
|
10489
11064
|
return 1;
|
|
10490
11065
|
}
|
|
10491
|
-
io.err(`launchpad logs failed: ${
|
|
11066
|
+
io.err(`launchpad logs failed: ${describe24(e)}`);
|
|
10492
11067
|
return 1;
|
|
10493
11068
|
}
|
|
10494
11069
|
}
|
|
@@ -10568,7 +11143,7 @@ function formatRelative2(iso) {
|
|
|
10568
11143
|
return `${hr}h ago`;
|
|
10569
11144
|
return `${Math.floor(hr / 24)}d ago`;
|
|
10570
11145
|
}
|
|
10571
|
-
function
|
|
11146
|
+
function describe24(e) {
|
|
10572
11147
|
return e instanceof Error ? e.message : String(e);
|
|
10573
11148
|
}
|
|
10574
11149
|
|
|
@@ -10617,7 +11192,7 @@ async function runMerge(args, io) {
|
|
|
10617
11192
|
try {
|
|
10618
11193
|
body = await res.json();
|
|
10619
11194
|
} catch (e) {
|
|
10620
|
-
io.err(`launchpad merge: bot returned 2xx with malformed body: ${
|
|
11195
|
+
io.err(`launchpad merge: bot returned 2xx with malformed body: ${describe25(e)}`);
|
|
10621
11196
|
return 1;
|
|
10622
11197
|
}
|
|
10623
11198
|
io.out("");
|
|
@@ -10657,7 +11232,7 @@ async function runMerge(args, io) {
|
|
|
10657
11232
|
io.err(`launchpad merge: ${e.message}`);
|
|
10658
11233
|
return 1;
|
|
10659
11234
|
}
|
|
10660
|
-
io.err(`launchpad merge failed: ${
|
|
11235
|
+
io.err(`launchpad merge failed: ${describe25(e)}`);
|
|
10661
11236
|
return 1;
|
|
10662
11237
|
}
|
|
10663
11238
|
}
|
|
@@ -10756,12 +11331,12 @@ function renderBotError(status, env, io, prNumber = null) {
|
|
|
10756
11331
|
io.err(`launchpad merge: bot returned ${status} ${code}${detail !== undefined ? `: ${detail}` : ""}`);
|
|
10757
11332
|
}
|
|
10758
11333
|
}
|
|
10759
|
-
function
|
|
11334
|
+
function describe25(e) {
|
|
10760
11335
|
return e instanceof Error ? e.message : String(e);
|
|
10761
11336
|
}
|
|
10762
11337
|
|
|
10763
11338
|
// src/commands/plan.ts
|
|
10764
|
-
import { resolve as
|
|
11339
|
+
import { resolve as resolve12 } from "node:path";
|
|
10765
11340
|
var planCommand = {
|
|
10766
11341
|
name: "plan",
|
|
10767
11342
|
summary: "summarise what the manifest would deploy (offline)",
|
|
@@ -10774,8 +11349,19 @@ async function runPlan(args, io) {
|
|
|
10774
11349
|
io.err("Usage: launchpad plan [--file <path>] [--json]");
|
|
10775
11350
|
return 64;
|
|
10776
11351
|
}
|
|
10777
|
-
const manifestPath =
|
|
11352
|
+
const manifestPath = resolve12(process.cwd(), flags.file ?? "launchpad.yaml");
|
|
10778
11353
|
const result = loadManifest(manifestPath);
|
|
11354
|
+
if (result.kind === "ok") {
|
|
11355
|
+
const unsupported2 = rejectUnsupportedCliRuntime({
|
|
11356
|
+
command: "plan",
|
|
11357
|
+
manifestPath,
|
|
11358
|
+
manifest: result.manifest,
|
|
11359
|
+
io,
|
|
11360
|
+
json: flags.json
|
|
11361
|
+
});
|
|
11362
|
+
if (unsupported2 !== null)
|
|
11363
|
+
return unsupported2;
|
|
11364
|
+
}
|
|
10779
11365
|
return flags.json ? renderJson(result, io) : renderHuman(result, io);
|
|
10780
11366
|
}
|
|
10781
11367
|
function renderJson(result, io) {
|
|
@@ -11008,7 +11594,7 @@ async function runDestroy(args, io, prompt, isTty) {
|
|
|
11008
11594
|
io.err(`launchpad destroy: ${e.message}`);
|
|
11009
11595
|
return 1;
|
|
11010
11596
|
}
|
|
11011
|
-
io.err(`launchpad destroy failed: ${
|
|
11597
|
+
io.err(`launchpad destroy failed: ${describe26(e)}`);
|
|
11012
11598
|
return 1;
|
|
11013
11599
|
}
|
|
11014
11600
|
}
|
|
@@ -11208,7 +11794,7 @@ function printUsage4(io) {
|
|
|
11208
11794
|
].join(`
|
|
11209
11795
|
`));
|
|
11210
11796
|
}
|
|
11211
|
-
function
|
|
11797
|
+
function describe26(e) {
|
|
11212
11798
|
return e instanceof Error ? e.message : String(e);
|
|
11213
11799
|
}
|
|
11214
11800
|
|
|
@@ -11340,22 +11926,22 @@ async function treesEqual(cwd, tracked, headDir, headPaths) {
|
|
|
11340
11926
|
if (tracked.length !== headPaths.length)
|
|
11341
11927
|
return false;
|
|
11342
11928
|
const expected = new Set(headPaths);
|
|
11343
|
-
for (const
|
|
11344
|
-
if (!expected.has(
|
|
11929
|
+
for (const relative6 of tracked) {
|
|
11930
|
+
if (!expected.has(relative6))
|
|
11345
11931
|
return false;
|
|
11346
|
-
if (!equal(await readEntry(path12.resolve(cwd,
|
|
11932
|
+
if (!equal(await readEntry(path12.resolve(cwd, relative6)), await readEntry(path12.resolve(headDir, relative6)))) {
|
|
11347
11933
|
return false;
|
|
11348
11934
|
}
|
|
11349
11935
|
}
|
|
11350
11936
|
return true;
|
|
11351
11937
|
}
|
|
11352
|
-
async function listFiles(root,
|
|
11938
|
+
async function listFiles(root, relative6 = "") {
|
|
11353
11939
|
const files = [];
|
|
11354
|
-
const entries = await fs6.readdir(path12.resolve(root,
|
|
11940
|
+
const entries = await fs6.readdir(path12.resolve(root, relative6), {
|
|
11355
11941
|
withFileTypes: true
|
|
11356
11942
|
});
|
|
11357
11943
|
for (const entry of entries) {
|
|
11358
|
-
const child =
|
|
11944
|
+
const child = relative6.length === 0 ? entry.name : `${relative6}/${entry.name}`;
|
|
11359
11945
|
if (entry.isDirectory())
|
|
11360
11946
|
files.push(...await listFiles(root, child));
|
|
11361
11947
|
else if (entry.isFile())
|
|
@@ -11364,12 +11950,12 @@ async function listFiles(root, relative5 = "") {
|
|
|
11364
11950
|
return files.sort();
|
|
11365
11951
|
}
|
|
11366
11952
|
function gitOutput(cwd, args) {
|
|
11367
|
-
return new Promise((
|
|
11953
|
+
return new Promise((resolve14, reject) => {
|
|
11368
11954
|
execFile("git", [...args], { cwd, encoding: "utf8" }, (error, stdout) => {
|
|
11369
11955
|
if (error !== null)
|
|
11370
11956
|
reject(error);
|
|
11371
11957
|
else
|
|
11372
|
-
|
|
11958
|
+
resolve14(stdout);
|
|
11373
11959
|
});
|
|
11374
11960
|
});
|
|
11375
11961
|
}
|
|
@@ -11378,7 +11964,7 @@ function splitNull(value) {
|
|
|
11378
11964
|
}
|
|
11379
11965
|
async function reconcile(args) {
|
|
11380
11966
|
const removals = await cleanAncestorRemovals(args);
|
|
11381
|
-
const planned = await Promise.all(args.paths.map((
|
|
11967
|
+
const planned = await Promise.all(args.paths.map((relative6) => planPath(args, relative6, removals)));
|
|
11382
11968
|
const conflicts = planned.flatMap((entry) => entry.conflict === undefined ? [] : [entry.conflict]);
|
|
11383
11969
|
if (conflicts.length > 0) {
|
|
11384
11970
|
const report = {
|
|
@@ -11422,55 +12008,55 @@ async function reconcile(args) {
|
|
|
11422
12008
|
}
|
|
11423
12009
|
async function cleanAncestorRemovals(args) {
|
|
11424
12010
|
const removals = new Set;
|
|
11425
|
-
for (const
|
|
11426
|
-
assertSafePath(
|
|
11427
|
-
const checked = await checkParents(args.cwd,
|
|
12011
|
+
for (const relative6 of args.compare.deleted) {
|
|
12012
|
+
assertSafePath(relative6);
|
|
12013
|
+
const checked = await checkParents(args.cwd, relative6, removals);
|
|
11428
12014
|
if (checked.kind === "blocked")
|
|
11429
12015
|
continue;
|
|
11430
|
-
const base = await readEntry(path12.resolve(args.baseDir,
|
|
12016
|
+
const base = await readEntry(path12.resolve(args.baseDir, relative6));
|
|
11431
12017
|
const local = checked.projectedMissing ? { kind: "missing" } : await readEntry(checked.target);
|
|
11432
12018
|
if (local.kind !== "missing" && cleanLocal(base, local))
|
|
11433
|
-
removals.add(
|
|
12019
|
+
removals.add(relative6);
|
|
11434
12020
|
}
|
|
11435
12021
|
return removals;
|
|
11436
12022
|
}
|
|
11437
|
-
async function planPath(args,
|
|
11438
|
-
assertSafePath(
|
|
11439
|
-
const checked = await checkParents(args.cwd,
|
|
11440
|
-
const base = await readEntry(path12.resolve(args.baseDir,
|
|
11441
|
-
const upstreamDeleted = args.compare.deleted.includes(
|
|
11442
|
-
const head = upstreamDeleted ? { kind: "missing" } : await readEntry(path12.resolve(args.headDir,
|
|
11443
|
-
const
|
|
12023
|
+
async function planPath(args, relative6, removals) {
|
|
12024
|
+
assertSafePath(relative6);
|
|
12025
|
+
const checked = await checkParents(args.cwd, relative6, removals);
|
|
12026
|
+
const base = await readEntry(path12.resolve(args.baseDir, relative6));
|
|
12027
|
+
const upstreamDeleted = args.compare.deleted.includes(relative6);
|
|
12028
|
+
const head = upstreamDeleted ? { kind: "missing" } : await readEntry(path12.resolve(args.headDir, relative6));
|
|
12029
|
+
const unsupported2 = (args.compare.unsupported ?? []).includes(relative6);
|
|
11444
12030
|
if (checked.kind === "blocked") {
|
|
11445
|
-
return conflictPlan(
|
|
12031
|
+
return conflictPlan(relative6, checked.target, { kind: "missing" }, head, upstreamDeleted, checked.reason);
|
|
11446
12032
|
}
|
|
11447
12033
|
const local = checked.projectedMissing ? { kind: "missing" } : await readEntry(checked.target);
|
|
11448
|
-
if (
|
|
11449
|
-
return conflictPlan(
|
|
12034
|
+
if (unsupported2) {
|
|
12035
|
+
return conflictPlan(relative6, checked.target, local, head, false, "upstream or baseline path uses an unsupported Git entry type");
|
|
11450
12036
|
}
|
|
11451
12037
|
if (alreadyResolved(local, head, upstreamDeleted)) {
|
|
11452
|
-
return { relative:
|
|
12038
|
+
return { relative: relative6, target: checked.target, local, head, upstreamDeleted, apply: false };
|
|
11453
12039
|
}
|
|
11454
12040
|
if (unsupportedHead(head, upstreamDeleted)) {
|
|
11455
|
-
return conflictPlan(
|
|
12041
|
+
return conflictPlan(relative6, checked.target, local, head, true, "unsupported upstream file type");
|
|
11456
12042
|
}
|
|
11457
12043
|
if (cleanLocal(base, local)) {
|
|
11458
|
-
return { relative:
|
|
12044
|
+
return { relative: relative6, target: checked.target, local, head, upstreamDeleted, apply: true };
|
|
11459
12045
|
}
|
|
11460
|
-
return conflictPlan(
|
|
12046
|
+
return conflictPlan(relative6, checked.target, local, head, upstreamDeleted, conflictReason(base, local, upstreamDeleted));
|
|
11461
12047
|
}
|
|
11462
|
-
function conflictPlan(
|
|
12048
|
+
function conflictPlan(relative6, target, local, head, upstreamDeleted, reason) {
|
|
11463
12049
|
const suffix = upstreamDeleted ? ".upstream-deleted" : ".upstream";
|
|
11464
|
-
const digest = createHash3("sha256").update(
|
|
12050
|
+
const digest = createHash3("sha256").update(relative6).digest("hex");
|
|
11465
12051
|
return {
|
|
11466
|
-
relative:
|
|
12052
|
+
relative: relative6,
|
|
11467
12053
|
target,
|
|
11468
12054
|
local,
|
|
11469
12055
|
head,
|
|
11470
12056
|
upstreamDeleted,
|
|
11471
12057
|
apply: false,
|
|
11472
12058
|
conflict: {
|
|
11473
|
-
path:
|
|
12059
|
+
path: relative6,
|
|
11474
12060
|
reason,
|
|
11475
12061
|
artifact: `${CONFLICT_DIR}/${digest}${suffix}`,
|
|
11476
12062
|
upstreamDeleted
|
|
@@ -11571,13 +12157,13 @@ function equal(left, right) {
|
|
|
11571
12157
|
return false;
|
|
11572
12158
|
return left.bytes.every((byte, index) => byte === right.bytes[index]);
|
|
11573
12159
|
}
|
|
11574
|
-
async function writeAtomic(cwd,
|
|
11575
|
-
const checked = await checkParents(cwd,
|
|
12160
|
+
async function writeAtomic(cwd, relative6, bytes, mode, allowMetadata = false) {
|
|
12161
|
+
const checked = await checkParents(cwd, relative6, new Set, allowMetadata);
|
|
11576
12162
|
if (checked.kind === "blocked")
|
|
11577
12163
|
throw new Error(checked.reason);
|
|
11578
12164
|
const file = checked.target;
|
|
11579
12165
|
await fs6.mkdir(path12.dirname(file), { recursive: true });
|
|
11580
|
-
const rechecked = await checkParents(cwd,
|
|
12166
|
+
const rechecked = await checkParents(cwd, relative6, new Set, allowMetadata);
|
|
11581
12167
|
if (rechecked.kind === "blocked")
|
|
11582
12168
|
throw new Error(rechecked.reason);
|
|
11583
12169
|
const temporary = `${file}.launchpad-tmp-${process.pid}-${crypto.randomUUID()}`;
|
|
@@ -11592,8 +12178,8 @@ async function writeAtomic(cwd, relative5, bytes, mode, allowMetadata = false) {
|
|
|
11592
12178
|
throw error;
|
|
11593
12179
|
}
|
|
11594
12180
|
}
|
|
11595
|
-
async function writeJsonAtomic(cwd,
|
|
11596
|
-
await writeAtomic(cwd,
|
|
12181
|
+
async function writeJsonAtomic(cwd, relative6, value) {
|
|
12182
|
+
await writeAtomic(cwd, relative6, new TextEncoder().encode(`${JSON.stringify(value, null, 2)}
|
|
11597
12183
|
`), 384, true);
|
|
11598
12184
|
}
|
|
11599
12185
|
async function resetConflictArtifacts(cwd) {
|
|
@@ -11609,37 +12195,37 @@ async function cleanupConflictState(cwd) {
|
|
|
11609
12195
|
throw new Error(report.reason);
|
|
11610
12196
|
await fs6.rm(report.target, { force: true });
|
|
11611
12197
|
}
|
|
11612
|
-
function assertSafePath(
|
|
11613
|
-
if (!isSafeSourcePath(
|
|
11614
|
-
throw new Error(`unsafe source path returned by bot: ${
|
|
12198
|
+
function assertSafePath(relative6) {
|
|
12199
|
+
if (!isSafeSourcePath(relative6)) {
|
|
12200
|
+
throw new Error(`unsafe source path returned by bot: ${relative6}`);
|
|
11615
12201
|
}
|
|
11616
12202
|
}
|
|
11617
|
-
function isSafeSourcePath(
|
|
11618
|
-
return isSafeRelativePath(
|
|
12203
|
+
function isSafeSourcePath(relative6) {
|
|
12204
|
+
return isSafeRelativePath(relative6) && !relative6.startsWith(`${MARKER_DIR}/`);
|
|
11619
12205
|
}
|
|
11620
|
-
function isSafeRelativePath(
|
|
11621
|
-
const normalized = path12.posix.normalize(
|
|
11622
|
-
return
|
|
12206
|
+
function isSafeRelativePath(relative6) {
|
|
12207
|
+
const normalized = path12.posix.normalize(relative6);
|
|
12208
|
+
return relative6.length > 0 && !relative6.includes("\\") && !path12.isAbsolute(relative6) && normalized === relative6 && normalized !== ".." && !normalized.startsWith("../");
|
|
11623
12209
|
}
|
|
11624
|
-
function resolveInsideClone(cwd,
|
|
12210
|
+
function resolveInsideClone(cwd, relative6) {
|
|
11625
12211
|
const root = path12.resolve(cwd);
|
|
11626
|
-
const target = path12.resolve(root,
|
|
12212
|
+
const target = path12.resolve(root, relative6);
|
|
11627
12213
|
const fromRoot = path12.relative(root, target);
|
|
11628
12214
|
if (fromRoot.length === 0 || path12.isAbsolute(fromRoot) || fromRoot === ".." || fromRoot.startsWith(`..${path12.sep}`)) {
|
|
11629
|
-
throw new Error(`unsafe source path returned by bot: ${
|
|
12215
|
+
throw new Error(`unsafe source path returned by bot: ${relative6}`);
|
|
11630
12216
|
}
|
|
11631
12217
|
return target;
|
|
11632
12218
|
}
|
|
11633
|
-
async function checkParents(cwd,
|
|
12219
|
+
async function checkParents(cwd, relative6, projectedRemovals, allowMetadata = false) {
|
|
11634
12220
|
if (allowMetadata) {
|
|
11635
|
-
if (!isSafeRelativePath(
|
|
11636
|
-
throw new Error(`unsafe metadata path: ${
|
|
12221
|
+
if (!isSafeRelativePath(relative6)) {
|
|
12222
|
+
throw new Error(`unsafe metadata path: ${relative6}`);
|
|
11637
12223
|
}
|
|
11638
12224
|
} else {
|
|
11639
|
-
assertSafePath(
|
|
12225
|
+
assertSafePath(relative6);
|
|
11640
12226
|
}
|
|
11641
|
-
const target = resolveInsideClone(cwd,
|
|
11642
|
-
const components =
|
|
12227
|
+
const target = resolveInsideClone(cwd, relative6);
|
|
12228
|
+
const components = relative6.split("/");
|
|
11643
12229
|
let current = path12.resolve(cwd);
|
|
11644
12230
|
for (let index = 0;index < components.length - 1; index += 1) {
|
|
11645
12231
|
current = path12.join(current, components[index]);
|
|
@@ -11778,7 +12364,7 @@ async function runPull(args, io) {
|
|
|
11778
12364
|
io.err(`launchpad pull: ${e.message}`);
|
|
11779
12365
|
return 1;
|
|
11780
12366
|
}
|
|
11781
|
-
io.err(`launchpad pull failed: ${
|
|
12367
|
+
io.err(`launchpad pull failed: ${describe27(e)}`);
|
|
11782
12368
|
return 1;
|
|
11783
12369
|
}
|
|
11784
12370
|
}
|
|
@@ -11886,7 +12472,7 @@ function printUsage5(io) {
|
|
|
11886
12472
|
].join(`
|
|
11887
12473
|
`));
|
|
11888
12474
|
}
|
|
11889
|
-
function
|
|
12475
|
+
function describe27(e) {
|
|
11890
12476
|
return e instanceof Error ? e.message : String(e);
|
|
11891
12477
|
}
|
|
11892
12478
|
|
|
@@ -12221,7 +12807,7 @@ async function runReview(args, io) {
|
|
|
12221
12807
|
io.err(`launchpad review: ${e.message}`);
|
|
12222
12808
|
return 1;
|
|
12223
12809
|
}
|
|
12224
|
-
io.err(`launchpad review failed: ${
|
|
12810
|
+
io.err(`launchpad review failed: ${describe28(e)}`);
|
|
12225
12811
|
return 1;
|
|
12226
12812
|
}
|
|
12227
12813
|
}
|
|
@@ -12299,12 +12885,12 @@ function severityRank(s) {
|
|
|
12299
12885
|
return 3;
|
|
12300
12886
|
}
|
|
12301
12887
|
}
|
|
12302
|
-
function
|
|
12888
|
+
function describe28(e) {
|
|
12303
12889
|
return e instanceof Error ? e.message : String(e);
|
|
12304
12890
|
}
|
|
12305
12891
|
|
|
12306
12892
|
// src/deploy/rollback.ts
|
|
12307
|
-
import { existsSync as existsSync11, readFileSync as
|
|
12893
|
+
import { existsSync as existsSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync5 } from "node:fs";
|
|
12308
12894
|
import { resolve as resolvePath3 } from "node:path";
|
|
12309
12895
|
import { createInterface as createInterface3 } from "node:readline/promises";
|
|
12310
12896
|
|
|
@@ -12359,7 +12945,7 @@ async function runRollback(opts, io, deps = {}) {
|
|
|
12359
12945
|
cwd: process.cwd()
|
|
12360
12946
|
});
|
|
12361
12947
|
} catch (e) {
|
|
12362
|
-
io.err(`launchpad rollback: git rev-parse failed to start: ${
|
|
12948
|
+
io.err(`launchpad rollback: git rev-parse failed to start: ${describe29(e)}`);
|
|
12363
12949
|
return 2;
|
|
12364
12950
|
}
|
|
12365
12951
|
if (revParse.exitCode !== 0) {
|
|
@@ -12374,7 +12960,7 @@ async function runRollback(opts, io, deps = {}) {
|
|
|
12374
12960
|
try {
|
|
12375
12961
|
show = await runner.run("git", ["show", `${verifiedSha}:${manifestRelpath}`], { cwd: process.cwd() });
|
|
12376
12962
|
} catch (e) {
|
|
12377
|
-
io.err(`launchpad rollback: git show failed to start: ${
|
|
12963
|
+
io.err(`launchpad rollback: git show failed to start: ${describe29(e)}`);
|
|
12378
12964
|
return 2;
|
|
12379
12965
|
}
|
|
12380
12966
|
if (show.exitCode !== 0) {
|
|
@@ -12408,7 +12994,7 @@ async function runRollback(opts, io, deps = {}) {
|
|
|
12408
12994
|
try {
|
|
12409
12995
|
writeFileSync5(manifestPath, historicalYaml, "utf8");
|
|
12410
12996
|
} catch (e) {
|
|
12411
|
-
io.err(`launchpad rollback: failed to write ${manifestPath}: ${
|
|
12997
|
+
io.err(`launchpad rollback: failed to write ${manifestPath}: ${describe29(e)}`);
|
|
12412
12998
|
return 2;
|
|
12413
12999
|
}
|
|
12414
13000
|
io.out(`Restored ${manifestPath} from ${verifiedSha.slice(0, 12)}.`);
|
|
@@ -12433,7 +13019,7 @@ function readCurrentManifest(path14) {
|
|
|
12433
13019
|
return null;
|
|
12434
13020
|
let raw;
|
|
12435
13021
|
try {
|
|
12436
|
-
raw =
|
|
13022
|
+
raw = readFileSync16(path14, "utf8");
|
|
12437
13023
|
} catch {
|
|
12438
13024
|
return null;
|
|
12439
13025
|
}
|
|
@@ -12490,7 +13076,7 @@ async function defaultPrompt4(question) {
|
|
|
12490
13076
|
rl.close();
|
|
12491
13077
|
}
|
|
12492
13078
|
}
|
|
12493
|
-
function
|
|
13079
|
+
function describe29(e) {
|
|
12494
13080
|
return e instanceof Error ? e.message : String(e);
|
|
12495
13081
|
}
|
|
12496
13082
|
function renderManifestError3(result, io) {
|
|
@@ -12589,13 +13175,13 @@ function parseArgs12(args) {
|
|
|
12589
13175
|
}
|
|
12590
13176
|
|
|
12591
13177
|
// src/secrets/push.ts
|
|
12592
|
-
import { existsSync as existsSync12, readFileSync as
|
|
13178
|
+
import { existsSync as existsSync12, readFileSync as readFileSync17 } from "node:fs";
|
|
12593
13179
|
import { resolve as resolvePath4 } from "node:path";
|
|
12594
13180
|
import { createInterface as createInterface4 } from "node:readline/promises";
|
|
12595
13181
|
import { stdin as input2, stdout as output2 } from "node:process";
|
|
12596
13182
|
|
|
12597
13183
|
// src/secrets/env-parse.ts
|
|
12598
|
-
var
|
|
13184
|
+
var ENV_NAME2 = /^[A-Z][A-Z0-9_]*$/;
|
|
12599
13185
|
var LINE = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/;
|
|
12600
13186
|
function parseEnv(text) {
|
|
12601
13187
|
const entries = new Map;
|
|
@@ -12620,7 +13206,7 @@ function parseEnv(text) {
|
|
|
12620
13206
|
if (value.startsWith('"') && value.endsWith('"') && value.length >= 2 || value.startsWith("'") && value.endsWith("'") && value.length >= 2) {
|
|
12621
13207
|
value = value.slice(1, -1);
|
|
12622
13208
|
}
|
|
12623
|
-
if (!
|
|
13209
|
+
if (!ENV_NAME2.test(key)) {
|
|
12624
13210
|
invalidNames.push(key);
|
|
12625
13211
|
continue;
|
|
12626
13212
|
}
|
|
@@ -12653,9 +13239,9 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
12653
13239
|
}
|
|
12654
13240
|
let envText;
|
|
12655
13241
|
try {
|
|
12656
|
-
envText =
|
|
13242
|
+
envText = readFileSync17(envPath, "utf8");
|
|
12657
13243
|
} catch (e) {
|
|
12658
|
-
io.err(`launchpad secrets push: failed to read ${envPath}: ${
|
|
13244
|
+
io.err(`launchpad secrets push: failed to read ${envPath}: ${describe30(e)}`);
|
|
12659
13245
|
return 2;
|
|
12660
13246
|
}
|
|
12661
13247
|
const parsed = parseEnv(envText);
|
|
@@ -12839,10 +13425,10 @@ function mapPushError(e, slug, name, io) {
|
|
|
12839
13425
|
io.err(`✗ secrets push ${name}: ${e.message}`);
|
|
12840
13426
|
return 1;
|
|
12841
13427
|
}
|
|
12842
|
-
io.err(`✗ secrets push ${name} failed: ${
|
|
13428
|
+
io.err(`✗ secrets push ${name} failed: ${describe30(e)}`);
|
|
12843
13429
|
return 2;
|
|
12844
13430
|
}
|
|
12845
|
-
function
|
|
13431
|
+
function describe30(e) {
|
|
12846
13432
|
return e instanceof Error ? e.message : String(e);
|
|
12847
13433
|
}
|
|
12848
13434
|
function renderManifestError4(result, io) {
|
|
@@ -12978,7 +13564,7 @@ function renderManifestError5(result, io) {
|
|
|
12978
13564
|
}
|
|
12979
13565
|
|
|
12980
13566
|
// src/secrets/set.ts
|
|
12981
|
-
import { existsSync as existsSync13, readFileSync as
|
|
13567
|
+
import { existsSync as existsSync13, readFileSync as readFileSync18 } from "node:fs";
|
|
12982
13568
|
import { resolve as resolvePath6 } from "node:path";
|
|
12983
13569
|
import { parse as parseYaml8 } from "yaml";
|
|
12984
13570
|
var CELL_LABEL2 = {
|
|
@@ -12995,7 +13581,7 @@ function loadSet(fleetFile, setName, io) {
|
|
|
12995
13581
|
}
|
|
12996
13582
|
let obj;
|
|
12997
13583
|
try {
|
|
12998
|
-
obj = parseYaml8(
|
|
13584
|
+
obj = parseYaml8(readFileSync18(path14, "utf8"));
|
|
12999
13585
|
} catch (e) {
|
|
13000
13586
|
io.err(`✗ ${path14}`);
|
|
13001
13587
|
io.err(` YAML parse error: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -13087,7 +13673,7 @@ async function runSecretsPushSet(opts, io, deps = {}) {
|
|
|
13087
13673
|
}
|
|
13088
13674
|
let envText;
|
|
13089
13675
|
try {
|
|
13090
|
-
envText =
|
|
13676
|
+
envText = readFileSync18(envPath, "utf8");
|
|
13091
13677
|
} catch (e) {
|
|
13092
13678
|
io.err(`launchpad secrets push --set: failed to read ${envPath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
13093
13679
|
return 2;
|
|
@@ -13232,7 +13818,7 @@ function setPushExit(e) {
|
|
|
13232
13818
|
|
|
13233
13819
|
// src/commands/secrets-template.ts
|
|
13234
13820
|
import { existsSync as existsSync14, writeFileSync as writeFileSync6 } from "node:fs";
|
|
13235
|
-
import { resolve as
|
|
13821
|
+
import { resolve as resolve14 } from "node:path";
|
|
13236
13822
|
async function runSecretsTemplate(args, io) {
|
|
13237
13823
|
const flags = parseFlags4(args);
|
|
13238
13824
|
if (flags.kind === "usage-error") {
|
|
@@ -13240,7 +13826,7 @@ async function runSecretsTemplate(args, io) {
|
|
|
13240
13826
|
io.err("Usage: launchpad secrets template [--file <path>] [--out <path>] " + "[--stdout] [--force] [--include-platform-managed]");
|
|
13241
13827
|
return 64;
|
|
13242
13828
|
}
|
|
13243
|
-
const manifestPath =
|
|
13829
|
+
const manifestPath = resolve14(process.cwd(), flags.file ?? "launchpad.yaml");
|
|
13244
13830
|
const result = loadManifest(manifestPath);
|
|
13245
13831
|
const renderResult = renderManifest(result, io);
|
|
13246
13832
|
if (renderResult.kind !== "ok") {
|
|
@@ -13254,7 +13840,7 @@ async function runSecretsTemplate(args, io) {
|
|
|
13254
13840
|
}
|
|
13255
13841
|
return 0;
|
|
13256
13842
|
}
|
|
13257
|
-
const outPath =
|
|
13843
|
+
const outPath = resolve14(process.cwd(), flags.out);
|
|
13258
13844
|
if (existsSync14(outPath) && !flags.force) {
|
|
13259
13845
|
io.err(`launchpad secrets template: ${outPath} already exists`);
|
|
13260
13846
|
io.err("Pass --force to overwrite, or --stdout to print without writing.");
|
|
@@ -13551,16 +14137,16 @@ function printHelp3(io) {
|
|
|
13551
14137
|
|
|
13552
14138
|
// src/commands/skills.ts
|
|
13553
14139
|
import { fileURLToPath } from "node:url";
|
|
13554
|
-
import { dirname as
|
|
14140
|
+
import { dirname as dirname10, join as join16, resolve as resolve15 } from "node:path";
|
|
13555
14141
|
import { promises as fs7, existsSync as existsSync15 } from "node:fs";
|
|
13556
14142
|
|
|
13557
14143
|
// src/skills-bundle.ts
|
|
13558
14144
|
import { homedir as homedir2 } from "node:os";
|
|
13559
|
-
import { join as
|
|
13560
|
-
import { readFileSync as
|
|
14145
|
+
import { join as join15 } from "node:path";
|
|
14146
|
+
import { readFileSync as readFileSync19, readdirSync as readdirSync2 } from "node:fs";
|
|
13561
14147
|
var SKILL_PREFIX = "launchpad-";
|
|
13562
14148
|
function skillsTargetDir() {
|
|
13563
|
-
return process.env.LAUNCHPAD_SKILLS_TARGET_DIR ??
|
|
14149
|
+
return process.env.LAUNCHPAD_SKILLS_TARGET_DIR ?? join15(homedir2(), ".claude", "skills");
|
|
13564
14150
|
}
|
|
13565
14151
|
function readInstalledSkills() {
|
|
13566
14152
|
try {
|
|
@@ -13570,7 +14156,7 @@ function readInstalledSkills() {
|
|
|
13570
14156
|
return { present: false, version: null };
|
|
13571
14157
|
return {
|
|
13572
14158
|
present: true,
|
|
13573
|
-
version: readSkillVersion(
|
|
14159
|
+
version: readSkillVersion(join15(dir, bundle.name, "SKILL.md"))
|
|
13574
14160
|
};
|
|
13575
14161
|
} catch {
|
|
13576
14162
|
return { present: false, version: null };
|
|
@@ -13578,7 +14164,7 @@ function readInstalledSkills() {
|
|
|
13578
14164
|
}
|
|
13579
14165
|
function readSkillVersion(path14) {
|
|
13580
14166
|
try {
|
|
13581
|
-
const text =
|
|
14167
|
+
const text = readFileSync19(path14, "utf8");
|
|
13582
14168
|
const fenceEnd = text.indexOf(`
|
|
13583
14169
|
---`, 4);
|
|
13584
14170
|
const front = fenceEnd === -1 ? text.slice(0, 1024) : text.slice(0, fenceEnd);
|
|
@@ -13632,7 +14218,7 @@ async function runSkills(args, io) {
|
|
|
13632
14218
|
return 64;
|
|
13633
14219
|
}
|
|
13634
14220
|
} catch (e) {
|
|
13635
|
-
io.err(`launchpad skills ${action}: ${
|
|
14221
|
+
io.err(`launchpad skills ${action}: ${describe31(e)}`);
|
|
13636
14222
|
return 1;
|
|
13637
14223
|
}
|
|
13638
14224
|
}
|
|
@@ -13655,13 +14241,13 @@ function resolveInstallEnv() {
|
|
|
13655
14241
|
return { bundleDir, userSkillsDir };
|
|
13656
14242
|
}
|
|
13657
14243
|
function defaultBundleDir() {
|
|
13658
|
-
const here =
|
|
14244
|
+
const here = dirname10(fileURLToPath(import.meta.url));
|
|
13659
14245
|
const candidates = [
|
|
13660
|
-
|
|
13661
|
-
|
|
14246
|
+
resolve15(here, "..", "skills"),
|
|
14247
|
+
resolve15(here, "..", "..", "skills")
|
|
13662
14248
|
];
|
|
13663
14249
|
for (const c of candidates) {
|
|
13664
|
-
if (existsSync15(
|
|
14250
|
+
if (existsSync15(join16(c, "launchpad-onboard", "SKILL.md"))) {
|
|
13665
14251
|
return c;
|
|
13666
14252
|
}
|
|
13667
14253
|
}
|
|
@@ -13682,12 +14268,12 @@ async function doInstall(io) {
|
|
|
13682
14268
|
}
|
|
13683
14269
|
let installed = 0;
|
|
13684
14270
|
for (const skill of BUNDLED_SKILLS) {
|
|
13685
|
-
const src =
|
|
14271
|
+
const src = join16(env.bundleDir, skill);
|
|
13686
14272
|
if (!await isDir(src)) {
|
|
13687
14273
|
io.err(`launchpad skills install: bundled skill "${skill}" missing from ${env.bundleDir} — package is incomplete.`);
|
|
13688
14274
|
return 1;
|
|
13689
14275
|
}
|
|
13690
|
-
const dest =
|
|
14276
|
+
const dest = join16(env.userSkillsDir, skill);
|
|
13691
14277
|
await fs7.rm(dest, { recursive: true, force: true });
|
|
13692
14278
|
await fs7.cp(src, dest, { recursive: true });
|
|
13693
14279
|
installed++;
|
|
@@ -13712,7 +14298,7 @@ async function doUninstall(io) {
|
|
|
13712
14298
|
continue;
|
|
13713
14299
|
if (!isBundleManaged(entry.name))
|
|
13714
14300
|
continue;
|
|
13715
|
-
const target =
|
|
14301
|
+
const target = join16(env.userSkillsDir, entry.name);
|
|
13716
14302
|
await fs7.rm(target, { recursive: true, force: true });
|
|
13717
14303
|
removed++;
|
|
13718
14304
|
io.out(`✗ removed ${target}`);
|
|
@@ -13735,7 +14321,7 @@ async function doList(io) {
|
|
|
13735
14321
|
}
|
|
13736
14322
|
const width = managedDirs.reduce((n, s) => Math.max(n, s.length), 0);
|
|
13737
14323
|
for (const name of managedDirs) {
|
|
13738
|
-
const skillFile =
|
|
14324
|
+
const skillFile = join16(env.userSkillsDir, name, "SKILL.md");
|
|
13739
14325
|
const version = await readVersion(skillFile);
|
|
13740
14326
|
io.out(` ${name.padEnd(width + 2)}${version ?? "(no version)"}`);
|
|
13741
14327
|
}
|
|
@@ -13762,7 +14348,7 @@ async function isDir(path14) {
|
|
|
13762
14348
|
return false;
|
|
13763
14349
|
}
|
|
13764
14350
|
}
|
|
13765
|
-
function
|
|
14351
|
+
function describe31(e) {
|
|
13766
14352
|
return e instanceof Error ? e.message : String(e);
|
|
13767
14353
|
}
|
|
13768
14354
|
|
|
@@ -13770,9 +14356,9 @@ function describe29(e) {
|
|
|
13770
14356
|
import { execFile as execFile2, spawn as spawn5 } from "node:child_process";
|
|
13771
14357
|
import { promisify } from "node:util";
|
|
13772
14358
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
13773
|
-
import { dirname as
|
|
14359
|
+
import { dirname as dirname11, resolve as resolve16, relative as relative6, isAbsolute as isAbsolute3, join as join17 } from "node:path";
|
|
13774
14360
|
import { homedir as homedir3, tmpdir } from "node:os";
|
|
13775
|
-
import { readFileSync as
|
|
14361
|
+
import { readFileSync as readFileSync20, mkdtempSync, writeFileSync as writeFileSync7, rmSync as rmSync2 } from "node:fs";
|
|
13776
14362
|
|
|
13777
14363
|
// src/commands/channel-auth.ts
|
|
13778
14364
|
import { createServer as createServer2 } from "node:http";
|
|
@@ -13815,9 +14401,9 @@ async function startLoopback(state, timeoutMs) {
|
|
|
13815
14401
|
resolveCode(code);
|
|
13816
14402
|
}
|
|
13817
14403
|
});
|
|
13818
|
-
const bound = await new Promise((
|
|
13819
|
-
server.once("error", () =>
|
|
13820
|
-
server.listen(0, "127.0.0.1", () =>
|
|
14404
|
+
const bound = await new Promise((resolve16) => {
|
|
14405
|
+
server.once("error", () => resolve16(false));
|
|
14406
|
+
server.listen(0, "127.0.0.1", () => resolve16(true));
|
|
13821
14407
|
});
|
|
13822
14408
|
if (!bound)
|
|
13823
14409
|
return null;
|
|
@@ -13893,7 +14479,7 @@ var PKG = "@m-kopa/launchpad-cli";
|
|
|
13893
14479
|
var REGISTRY = "https://registry.npmjs.org";
|
|
13894
14480
|
var CHANNEL_VERSION_URL = "https://get.launchpad.m-kopa.us/version.json";
|
|
13895
14481
|
var CHANNEL_INSTALL_URL = "https://get.launchpad.m-kopa.us";
|
|
13896
|
-
var CHANNEL_MARKER =
|
|
14482
|
+
var CHANNEL_MARKER = join17(homedir3(), ".launchpad", "channel");
|
|
13897
14483
|
var EXIT_UPDATE_AVAILABLE = 10;
|
|
13898
14484
|
var UPGRADE_ARGS = {
|
|
13899
14485
|
npm: ["install", "-g", `${PKG}@latest`],
|
|
@@ -14022,8 +14608,8 @@ async function openSystemBrowser(url) {
|
|
|
14022
14608
|
await execFileAsync(opener, [url]);
|
|
14023
14609
|
}
|
|
14024
14610
|
async function runInstallerScript(script) {
|
|
14025
|
-
const dir = mkdtempSync(
|
|
14026
|
-
const file =
|
|
14611
|
+
const dir = mkdtempSync(join17(tmpdir(), "launchpad-update-"));
|
|
14612
|
+
const file = join17(dir, "install.sh");
|
|
14027
14613
|
try {
|
|
14028
14614
|
writeFileSync7(file, script, { mode: 448 });
|
|
14029
14615
|
return await new Promise((resolvePromise) => {
|
|
@@ -14047,7 +14633,7 @@ function resolveLatestVersion() {
|
|
|
14047
14633
|
}
|
|
14048
14634
|
function detectInstallChannel() {
|
|
14049
14635
|
try {
|
|
14050
|
-
return
|
|
14636
|
+
return readFileSync20(CHANNEL_MARKER, "utf8").trim() === "platform" ? "platform" : "github";
|
|
14051
14637
|
} catch {
|
|
14052
14638
|
return "github";
|
|
14053
14639
|
}
|
|
@@ -14110,7 +14696,7 @@ function errStderr(e) {
|
|
|
14110
14696
|
return "";
|
|
14111
14697
|
}
|
|
14112
14698
|
async function detectPackageManager2() {
|
|
14113
|
-
const pkgRoot =
|
|
14699
|
+
const pkgRoot = resolve16(dirname11(fileURLToPath2(import.meta.url)), "..", "..");
|
|
14114
14700
|
const candidates = [];
|
|
14115
14701
|
const npmRoot = await pmRoot("npm");
|
|
14116
14702
|
if (npmRoot !== null)
|
|
@@ -14118,7 +14704,7 @@ async function detectPackageManager2() {
|
|
|
14118
14704
|
const pnpmRoot = await pmRoot("pnpm");
|
|
14119
14705
|
if (pnpmRoot !== null)
|
|
14120
14706
|
candidates.push(["pnpm", pnpmRoot]);
|
|
14121
|
-
candidates.push(["bun",
|
|
14707
|
+
candidates.push(["bun", resolve16(homedir3(), ".bun/install/global/node_modules")]);
|
|
14122
14708
|
const matches = candidates.filter(([, root]) => pathContains(root, pkgRoot));
|
|
14123
14709
|
return matches.length === 1 ? matches[0][0] : null;
|
|
14124
14710
|
}
|
|
@@ -14132,7 +14718,7 @@ async function pmRoot(pm) {
|
|
|
14132
14718
|
}
|
|
14133
14719
|
}
|
|
14134
14720
|
function pathContains(root, child) {
|
|
14135
|
-
const rel =
|
|
14721
|
+
const rel = relative6(root, child);
|
|
14136
14722
|
return rel === "" || !rel.startsWith("..") && !isAbsolute3(rel);
|
|
14137
14723
|
}
|
|
14138
14724
|
function runUpgrade(pm) {
|
|
@@ -14255,8 +14841,8 @@ function printHelp5(io) {
|
|
|
14255
14841
|
}
|
|
14256
14842
|
|
|
14257
14843
|
// src/commands/validate.ts
|
|
14258
|
-
import { existsSync as existsSync16, readFileSync as
|
|
14259
|
-
import { dirname as
|
|
14844
|
+
import { existsSync as existsSync16, readFileSync as readFileSync21, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
|
|
14845
|
+
import { dirname as dirname12, resolve as resolve17 } from "node:path";
|
|
14260
14846
|
var validateCommand = {
|
|
14261
14847
|
name: "validate",
|
|
14262
14848
|
summary: "validate launchpad.yaml against the v1alpha1 schema",
|
|
@@ -14270,11 +14856,21 @@ async function runValidate(args, io) {
|
|
|
14270
14856
|
return 64;
|
|
14271
14857
|
}
|
|
14272
14858
|
const { file, json, strictGroups } = parseResult;
|
|
14273
|
-
const path14 =
|
|
14859
|
+
const path14 = resolve17(process.cwd(), file ?? "launchpad.yaml");
|
|
14274
14860
|
const result = loadManifest(path14);
|
|
14275
14861
|
if (result.kind !== "ok") {
|
|
14276
14862
|
return json ? renderJsonError(result, io) : renderHumanError(result, io);
|
|
14277
14863
|
}
|
|
14864
|
+
const unsupported2 = rejectUnsupportedCliRuntime({
|
|
14865
|
+
command: "validate",
|
|
14866
|
+
manifestPath: path14,
|
|
14867
|
+
manifest: result.manifest,
|
|
14868
|
+
io,
|
|
14869
|
+
json,
|
|
14870
|
+
structurallyValidName: result.manifest.metadata.name
|
|
14871
|
+
});
|
|
14872
|
+
if (unsupported2 !== null)
|
|
14873
|
+
return unsupported2;
|
|
14278
14874
|
const boundary = checkBoundary(path14, result.manifest.app !== undefined);
|
|
14279
14875
|
const serveErrors = checkStaticServeDir(path14, result.manifest);
|
|
14280
14876
|
if (serveErrors.length > 0) {
|
|
@@ -14284,7 +14880,7 @@ async function runValidate(args, io) {
|
|
|
14284
14880
|
return json ? renderJsonOk(result, groupCheck, boundary, io) : renderHumanOk(result, groupCheck, boundary, io);
|
|
14285
14881
|
}
|
|
14286
14882
|
function checkBoundary(manifestPath, declared) {
|
|
14287
|
-
const dir =
|
|
14883
|
+
const dir = dirname12(manifestPath);
|
|
14288
14884
|
let files;
|
|
14289
14885
|
try {
|
|
14290
14886
|
files = walkCwd(dir).files;
|
|
@@ -14293,7 +14889,7 @@ function checkBoundary(manifestPath, declared) {
|
|
|
14293
14889
|
}
|
|
14294
14890
|
let manifestYaml;
|
|
14295
14891
|
try {
|
|
14296
|
-
manifestYaml =
|
|
14892
|
+
manifestYaml = readFileSync21(manifestPath, "utf8");
|
|
14297
14893
|
} catch {
|
|
14298
14894
|
manifestYaml = null;
|
|
14299
14895
|
}
|
|
@@ -14316,8 +14912,8 @@ function checkStaticServeDir(manifestPath, manifest) {
|
|
|
14316
14912
|
`static app destination_dir '${serveDir}' is unsafe — use a confined relative subdirectory (e.g. '${STATIC_SERVE_DIR}'). ` + `Serving '.' or an absolute/parent path would publish files outside your app's content.`
|
|
14317
14913
|
];
|
|
14318
14914
|
}
|
|
14319
|
-
const dir =
|
|
14320
|
-
const abs =
|
|
14915
|
+
const dir = dirname12(manifestPath);
|
|
14916
|
+
const abs = resolve17(dir, serveDir);
|
|
14321
14917
|
if (!existsSync16(abs)) {
|
|
14322
14918
|
return [
|
|
14323
14919
|
`static app serves '${serveDir}/' but that directory does not exist — nothing would be served. ` + `Create ${serveDir}/index.html (or run \`launchpad init\` to scaffold it).`
|
|
@@ -14327,7 +14923,7 @@ function checkStaticServeDir(manifestPath, manifest) {
|
|
|
14327
14923
|
try {
|
|
14328
14924
|
isDir2 = statSync2(abs).isDirectory();
|
|
14329
14925
|
} catch (e) {
|
|
14330
|
-
return [`failed to inspect static served directory '${serveDir}/': ${
|
|
14926
|
+
return [`failed to inspect static served directory '${serveDir}/': ${describe32(e)}`];
|
|
14331
14927
|
}
|
|
14332
14928
|
if (!isDir2) {
|
|
14333
14929
|
return [`static app serves '${serveDir}/' but that path is not a directory.`];
|
|
@@ -14336,14 +14932,14 @@ function checkStaticServeDir(manifestPath, manifest) {
|
|
|
14336
14932
|
try {
|
|
14337
14933
|
entries = readdirSync3(abs);
|
|
14338
14934
|
} catch (e) {
|
|
14339
|
-
return [`failed to read static served directory '${serveDir}/': ${
|
|
14935
|
+
return [`failed to read static served directory '${serveDir}/': ${describe32(e)}`];
|
|
14340
14936
|
}
|
|
14341
14937
|
if (entries.length === 0) {
|
|
14342
14938
|
return [
|
|
14343
14939
|
`static app's served directory '${serveDir}/' is empty — the deploy would serve an empty site.`
|
|
14344
14940
|
];
|
|
14345
14941
|
}
|
|
14346
|
-
if (!existsSync16(
|
|
14942
|
+
if (!existsSync16(resolve17(abs, "index.html"))) {
|
|
14347
14943
|
return [
|
|
14348
14944
|
`static app's served directory '${serveDir}/' has no index.html — the deploy would serve a directory with no entrypoint.`
|
|
14349
14945
|
];
|
|
@@ -14391,7 +14987,7 @@ async function checkGroup(allowedGroup, cfg) {
|
|
|
14391
14987
|
if (e instanceof ForbiddenError) {
|
|
14392
14988
|
return { kind: "forbidden", message: e.message };
|
|
14393
14989
|
}
|
|
14394
|
-
return { kind: "network", message:
|
|
14990
|
+
return { kind: "network", message: describe32(e) };
|
|
14395
14991
|
}
|
|
14396
14992
|
}
|
|
14397
14993
|
async function checkGroups(groups) {
|
|
@@ -14488,6 +15084,8 @@ function renderHumanError(result, io) {
|
|
|
14488
15084
|
}
|
|
14489
15085
|
function renderJsonOk(result, group, boundary, io) {
|
|
14490
15086
|
const base = {
|
|
15087
|
+
manifest_valid: true,
|
|
15088
|
+
runtime_supported: true,
|
|
14491
15089
|
name: result.manifest.metadata.name,
|
|
14492
15090
|
deploymentType: result.manifest.deployment.type,
|
|
14493
15091
|
appBoundary: {
|
|
@@ -14604,7 +15202,7 @@ function parseArgs13(args) {
|
|
|
14604
15202
|
}
|
|
14605
15203
|
return { kind: "ok", file, json, strictGroups };
|
|
14606
15204
|
}
|
|
14607
|
-
function
|
|
15205
|
+
function describe32(e) {
|
|
14608
15206
|
return e instanceof Error ? e.message : String(e);
|
|
14609
15207
|
}
|
|
14610
15208
|
|
|
@@ -14657,7 +15255,7 @@ async function runWhoami(args, io) {
|
|
|
14657
15255
|
io.err(e.message);
|
|
14658
15256
|
return 1;
|
|
14659
15257
|
}
|
|
14660
|
-
io.err(`launchpad whoami failed: ${
|
|
15258
|
+
io.err(`launchpad whoami failed: ${describe33(e)}`);
|
|
14661
15259
|
return 1;
|
|
14662
15260
|
}
|
|
14663
15261
|
}
|
|
@@ -14687,11 +15285,11 @@ async function runShare(args, io) {
|
|
|
14687
15285
|
io.err(e.message);
|
|
14688
15286
|
return 1;
|
|
14689
15287
|
}
|
|
14690
|
-
io.err(`launchpad whoami --share failed: ${
|
|
15288
|
+
io.err(`launchpad whoami --share failed: ${describe33(e)}`);
|
|
14691
15289
|
return 1;
|
|
14692
15290
|
}
|
|
14693
15291
|
}
|
|
14694
|
-
function
|
|
15292
|
+
function describe33(e) {
|
|
14695
15293
|
return e instanceof Error ? e.message : String(e);
|
|
14696
15294
|
}
|
|
14697
15295
|
|
|
@@ -14857,7 +15455,7 @@ async function runEditor(action, args, io, deps) {
|
|
|
14857
15455
|
io.err(`launchpad ${verb}: no app "${slug}" — check the slug with \`launchpad apps\`.`);
|
|
14858
15456
|
return 1;
|
|
14859
15457
|
}
|
|
14860
|
-
io.err(`launchpad ${verb} failed: ${
|
|
15458
|
+
io.err(`launchpad ${verb} failed: ${describe34(e)}`);
|
|
14861
15459
|
return 1;
|
|
14862
15460
|
}
|
|
14863
15461
|
}
|
|
@@ -14891,7 +15489,7 @@ async function sendMutation(action, slug, target, version, cfg, deps) {
|
|
|
14891
15489
|
nonThrowingStatuses: [409]
|
|
14892
15490
|
});
|
|
14893
15491
|
}
|
|
14894
|
-
function
|
|
15492
|
+
function describe34(e) {
|
|
14895
15493
|
return e instanceof Error ? e.message : String(e);
|
|
14896
15494
|
}
|
|
14897
15495
|
var grantEditorCommand = makeEditorCommand("grant");
|
|
@@ -15097,7 +15695,7 @@ async function runOwner(action, args, io, deps) {
|
|
|
15097
15695
|
io.err(`launchpad ${verb}: no app "${slug}" — check the slug with \`launchpad apps\`.`);
|
|
15098
15696
|
return 1;
|
|
15099
15697
|
}
|
|
15100
|
-
io.err(`launchpad ${verb} failed: ${
|
|
15698
|
+
io.err(`launchpad ${verb} failed: ${describe35(e)}`);
|
|
15101
15699
|
return 1;
|
|
15102
15700
|
}
|
|
15103
15701
|
}
|
|
@@ -15131,12 +15729,92 @@ async function sendMutation2(action, slug, target, version, cfg, deps) {
|
|
|
15131
15729
|
nonThrowingStatuses: [409]
|
|
15132
15730
|
});
|
|
15133
15731
|
}
|
|
15134
|
-
function
|
|
15732
|
+
function describe35(e) {
|
|
15135
15733
|
return e instanceof Error ? e.message : String(e);
|
|
15136
15734
|
}
|
|
15137
15735
|
var addOwnerCommand = makeOwnerCommand("add");
|
|
15138
15736
|
var removeOwnerCommand = makeOwnerCommand("remove");
|
|
15139
15737
|
|
|
15738
|
+
// src/commands/admin.ts
|
|
15739
|
+
var REAL_DEPS3 = { loadConfig, apiJson };
|
|
15740
|
+
var adminCommand = makeAdminCommand();
|
|
15741
|
+
function makeAdminCommand(deps = REAL_DEPS3) {
|
|
15742
|
+
return {
|
|
15743
|
+
name: "admin",
|
|
15744
|
+
summary: "run authenticated platform-operator actions",
|
|
15745
|
+
run: (args, io) => runAdmin(args, io, deps)
|
|
15746
|
+
};
|
|
15747
|
+
}
|
|
15748
|
+
function statusPageUrl(value, allowedHost) {
|
|
15749
|
+
try {
|
|
15750
|
+
const url = new URL(value);
|
|
15751
|
+
if (url.protocol !== "https:")
|
|
15752
|
+
return null;
|
|
15753
|
+
if (url.hostname !== allowedHost)
|
|
15754
|
+
return null;
|
|
15755
|
+
if (!/^\/d\/[^/]+$/u.test(url.pathname))
|
|
15756
|
+
return null;
|
|
15757
|
+
if (url.username !== "" || url.password !== "")
|
|
15758
|
+
return null;
|
|
15759
|
+
if (url.search !== "" || url.hash !== "")
|
|
15760
|
+
return null;
|
|
15761
|
+
return url.toString();
|
|
15762
|
+
} catch {
|
|
15763
|
+
return null;
|
|
15764
|
+
}
|
|
15765
|
+
}
|
|
15766
|
+
async function runAdmin(args, io, deps) {
|
|
15767
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
15768
|
+
printHelp6(io);
|
|
15769
|
+
return 0;
|
|
15770
|
+
}
|
|
15771
|
+
const config = deps.loadConfig();
|
|
15772
|
+
const parsed = parseStatusRevokeArgs(args, config.statusPageHost ?? DEFAULT_STATUS_PAGE_HOST);
|
|
15773
|
+
if (parsed.kind === "unknown") {
|
|
15774
|
+
io.err(`launchpad admin: unknown subcommand '${parsed.subcommand}'`);
|
|
15775
|
+
printHelp6(io);
|
|
15776
|
+
return 64;
|
|
15777
|
+
}
|
|
15778
|
+
if (parsed.kind === "invalid") {
|
|
15779
|
+
io.err("launchpad admin: expected `status-revoke <status-url>`");
|
|
15780
|
+
printHelp6(io);
|
|
15781
|
+
return 64;
|
|
15782
|
+
}
|
|
15783
|
+
return revokeStatusPage(parsed.url, config, io, deps);
|
|
15784
|
+
}
|
|
15785
|
+
function parseStatusRevokeArgs(args, allowedHost) {
|
|
15786
|
+
if (args[0] !== undefined && args[0] !== "status-revoke") {
|
|
15787
|
+
return { kind: "unknown", subcommand: args[0] };
|
|
15788
|
+
}
|
|
15789
|
+
if (args.length !== 2)
|
|
15790
|
+
return { kind: "invalid" };
|
|
15791
|
+
const url = statusPageUrl(args[1], allowedHost);
|
|
15792
|
+
return url === null ? { kind: "invalid" } : { kind: "ok", url };
|
|
15793
|
+
}
|
|
15794
|
+
async function revokeStatusPage(url, config, io, deps) {
|
|
15795
|
+
try {
|
|
15796
|
+
const response = await deps.apiJson(config, {
|
|
15797
|
+
method: "POST",
|
|
15798
|
+
path: "/status-page/revoke",
|
|
15799
|
+
jsonBody: { url }
|
|
15800
|
+
});
|
|
15801
|
+
io.out(statusRevokeMessage(response));
|
|
15802
|
+
return 0;
|
|
15803
|
+
} catch (error) {
|
|
15804
|
+
io.err(`launchpad admin status-revoke: ${error instanceof Error ? error.message : String(error)}`);
|
|
15805
|
+
return 1;
|
|
15806
|
+
}
|
|
15807
|
+
}
|
|
15808
|
+
function statusRevokeMessage(response) {
|
|
15809
|
+
return response.revoked ? "Status page revoked." : "Status page was already absent.";
|
|
15810
|
+
}
|
|
15811
|
+
function printHelp6(io) {
|
|
15812
|
+
io.out("launchpad admin - authenticated platform-operator actions.");
|
|
15813
|
+
io.out("");
|
|
15814
|
+
io.out("Usage:");
|
|
15815
|
+
io.out(" launchpad admin status-revoke <status-url>");
|
|
15816
|
+
}
|
|
15817
|
+
|
|
15140
15818
|
// src/commands/list-owners.ts
|
|
15141
15819
|
function realDeps4() {
|
|
15142
15820
|
return { loadConfig, apiJson };
|
|
@@ -15212,20 +15890,20 @@ var listOwnersCommand = makeListOwnersCommand();
|
|
|
15212
15890
|
// src/update-notifier.ts
|
|
15213
15891
|
import { spawn as spawn6 } from "node:child_process";
|
|
15214
15892
|
import { homedir as homedir4 } from "node:os";
|
|
15215
|
-
import { join as
|
|
15893
|
+
import { join as join18 } from "node:path";
|
|
15216
15894
|
import {
|
|
15217
15895
|
existsSync as existsSync17,
|
|
15218
15896
|
mkdirSync as mkdirSync4,
|
|
15219
|
-
readFileSync as
|
|
15897
|
+
readFileSync as readFileSync22,
|
|
15220
15898
|
writeFileSync as writeFileSync8
|
|
15221
15899
|
} from "node:fs";
|
|
15222
15900
|
var INTERNAL_REFRESH_VERB = "__refresh-update-cache";
|
|
15223
15901
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
15224
15902
|
var OPT_OUT_ENV = "LAUNCHPAD_NO_UPDATE_NOTIFIER";
|
|
15225
|
-
var CACHE_FILE =
|
|
15903
|
+
var CACHE_FILE = join18(homedir4(), ".launchpad", "update-check.json");
|
|
15226
15904
|
function readCache2() {
|
|
15227
15905
|
try {
|
|
15228
|
-
const raw = JSON.parse(
|
|
15906
|
+
const raw = JSON.parse(readFileSync22(CACHE_FILE, "utf8"));
|
|
15229
15907
|
if (typeof raw === "object" && raw !== null && typeof raw.checkedAt === "number") {
|
|
15230
15908
|
const latest = raw.latest;
|
|
15231
15909
|
return {
|
|
@@ -15238,12 +15916,12 @@ function readCache2() {
|
|
|
15238
15916
|
}
|
|
15239
15917
|
function writeCache2(state) {
|
|
15240
15918
|
try {
|
|
15241
|
-
mkdirSync4(
|
|
15919
|
+
mkdirSync4(join18(homedir4(), ".launchpad"), { recursive: true });
|
|
15242
15920
|
writeFileSync8(CACHE_FILE, `${JSON.stringify(state)}
|
|
15243
15921
|
`, { mode: 384 });
|
|
15244
15922
|
} catch {}
|
|
15245
15923
|
}
|
|
15246
|
-
var SKILLS_HINT_MARKER =
|
|
15924
|
+
var SKILLS_HINT_MARKER = join18(homedir4(), ".launchpad", "skills-hint-shown");
|
|
15247
15925
|
function absentHintShown() {
|
|
15248
15926
|
try {
|
|
15249
15927
|
return existsSync17(SKILLS_HINT_MARKER);
|
|
@@ -15253,7 +15931,7 @@ function absentHintShown() {
|
|
|
15253
15931
|
}
|
|
15254
15932
|
function markAbsentHintShown() {
|
|
15255
15933
|
try {
|
|
15256
|
-
mkdirSync4(
|
|
15934
|
+
mkdirSync4(join18(homedir4(), ".launchpad"), { recursive: true });
|
|
15257
15935
|
writeFileSync8(SKILLS_HINT_MARKER, `${Date.now()}
|
|
15258
15936
|
`, { mode: 384 });
|
|
15259
15937
|
} catch {}
|
|
@@ -15356,8 +16034,8 @@ var refreshUpdateCacheCommand = {
|
|
|
15356
16034
|
// src/telemetry.ts
|
|
15357
16035
|
import { spawn as spawn7 } from "node:child_process";
|
|
15358
16036
|
import { homedir as homedir5 } from "node:os";
|
|
15359
|
-
import { join as
|
|
15360
|
-
import { existsSync as existsSync18, mkdirSync as mkdirSync5, readFileSync as
|
|
16037
|
+
import { join as join19 } from "node:path";
|
|
16038
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync5, readFileSync as readFileSync23, writeFileSync as writeFileSync9 } from "node:fs";
|
|
15361
16039
|
import { randomUUID } from "node:crypto";
|
|
15362
16040
|
var INTERNAL_EMIT_VERB = "__emit-telemetry";
|
|
15363
16041
|
var POSTHOG_HOST = "https://us.i.posthog.com";
|
|
@@ -15365,8 +16043,8 @@ var POSTHOG_PROJECT_KEY = "phc_CYaCuETanWc36TMTiB7cdkdnmPPhFZCkDGmWGLaNkXnb";
|
|
|
15365
16043
|
var CAPTURE_PATH = "/i/v0/e/";
|
|
15366
16044
|
var SEND_TIMEOUT_MS = 3000;
|
|
15367
16045
|
var APP = "launchpad-cli";
|
|
15368
|
-
var DEVICE_ID_FILE =
|
|
15369
|
-
var FIRST_RUN_MARKER =
|
|
16046
|
+
var DEVICE_ID_FILE = join19(homedir5(), ".launchpad", "telemetry-id.json");
|
|
16047
|
+
var FIRST_RUN_MARKER = join19(homedir5(), ".launchpad", "telemetry-notice-shown");
|
|
15370
16048
|
function buildEvent(ctx, identity, nowMs) {
|
|
15371
16049
|
const properties = {
|
|
15372
16050
|
app: APP,
|
|
@@ -15433,14 +16111,14 @@ async function resolveIdentityReal() {
|
|
|
15433
16111
|
}
|
|
15434
16112
|
function getOrCreateDeviceId() {
|
|
15435
16113
|
try {
|
|
15436
|
-
const raw = JSON.parse(
|
|
16114
|
+
const raw = JSON.parse(readFileSync23(DEVICE_ID_FILE, "utf8"));
|
|
15437
16115
|
const id2 = raw?.deviceId;
|
|
15438
16116
|
if (typeof id2 === "string" && id2)
|
|
15439
16117
|
return id2;
|
|
15440
16118
|
} catch {}
|
|
15441
16119
|
const id = randomUUID();
|
|
15442
16120
|
try {
|
|
15443
|
-
mkdirSync5(
|
|
16121
|
+
mkdirSync5(join19(homedir5(), ".launchpad"), { recursive: true });
|
|
15444
16122
|
writeFileSync9(DEVICE_ID_FILE, `${JSON.stringify({ deviceId: id })}
|
|
15445
16123
|
`, {
|
|
15446
16124
|
mode: 384
|
|
@@ -15518,7 +16196,7 @@ function firstRunNoticeShown() {
|
|
|
15518
16196
|
}
|
|
15519
16197
|
function markFirstRunNotice() {
|
|
15520
16198
|
try {
|
|
15521
|
-
mkdirSync5(
|
|
16199
|
+
mkdirSync5(join19(homedir5(), ".launchpad"), { recursive: true });
|
|
15522
16200
|
writeFileSync9(FIRST_RUN_MARKER, `${Date.now()}
|
|
15523
16201
|
`, { mode: 384 });
|
|
15524
16202
|
} catch {}
|
|
@@ -15584,22 +16262,22 @@ import { createInterface as createInterface7 } from "node:readline/promises";
|
|
|
15584
16262
|
import { platform } from "node:os";
|
|
15585
16263
|
|
|
15586
16264
|
// src/report/breadcrumb.ts
|
|
15587
|
-
import { writeFileSync as writeFileSync10, readFileSync as
|
|
16265
|
+
import { writeFileSync as writeFileSync10, readFileSync as readFileSync24, mkdirSync as mkdirSync6 } from "node:fs";
|
|
15588
16266
|
import { homedir as homedir6 } from "node:os";
|
|
15589
|
-
import { join as
|
|
16267
|
+
import { join as join20, dirname as dirname13 } from "node:path";
|
|
15590
16268
|
function breadcrumbPath() {
|
|
15591
|
-
return
|
|
16269
|
+
return join20(homedir6(), ".launchpad", "last-run.json");
|
|
15592
16270
|
}
|
|
15593
16271
|
function writeBreadcrumb(verb, exit) {
|
|
15594
16272
|
try {
|
|
15595
16273
|
const p = breadcrumbPath();
|
|
15596
|
-
mkdirSync6(
|
|
16274
|
+
mkdirSync6(dirname13(p), { recursive: true });
|
|
15597
16275
|
writeFileSync10(p, JSON.stringify({ verb, exit, ts: Date.now() }), "utf8");
|
|
15598
16276
|
} catch {}
|
|
15599
16277
|
}
|
|
15600
16278
|
function readBreadcrumb() {
|
|
15601
16279
|
try {
|
|
15602
|
-
const raw = JSON.parse(
|
|
16280
|
+
const raw = JSON.parse(readFileSync24(breadcrumbPath(), "utf8"));
|
|
15603
16281
|
if (typeof raw !== "object" || raw === null)
|
|
15604
16282
|
return null;
|
|
15605
16283
|
const r = raw;
|
|
@@ -15793,6 +16471,7 @@ var COMMANDS = [
|
|
|
15793
16471
|
addOwnerCommand,
|
|
15794
16472
|
removeOwnerCommand,
|
|
15795
16473
|
listOwnersCommand,
|
|
16474
|
+
adminCommand,
|
|
15796
16475
|
makeReportCommand("bug"),
|
|
15797
16476
|
makeReportCommand("feature"),
|
|
15798
16477
|
refreshUpdateCacheCommand,
|
|
@@ -15800,7 +16479,7 @@ var COMMANDS = [
|
|
|
15800
16479
|
];
|
|
15801
16480
|
async function dispatch(argv, io, commands = COMMANDS, deps = {}) {
|
|
15802
16481
|
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
15803
|
-
|
|
16482
|
+
printHelp7(io, commands);
|
|
15804
16483
|
return 0;
|
|
15805
16484
|
}
|
|
15806
16485
|
if (argv[0] === "--version" || argv[0] === "-v") {
|
|
@@ -15863,9 +16542,11 @@ function requiresCliFloorPreflight(verb, args) {
|
|
|
15863
16542
|
return args[0] === "set" || args[0] === "rm";
|
|
15864
16543
|
if (verb === "secrets")
|
|
15865
16544
|
return args[0] === "push";
|
|
16545
|
+
if (verb === "admin")
|
|
16546
|
+
return args[0] === "status-revoke";
|
|
15866
16547
|
return false;
|
|
15867
16548
|
}
|
|
15868
|
-
function
|
|
16549
|
+
function printHelp7(io, commands) {
|
|
15869
16550
|
io.out("launchpad — manage Launchpad-deployed apps from the command line.");
|
|
15870
16551
|
io.out("");
|
|
15871
16552
|
io.out("Usage:");
|