@m-kopa/launchpad-cli 0.57.0 → 0.60.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +76 -0
- package/dist/bundle/upload.d.ts +2 -0
- package/dist/bundle/upload.d.ts.map +1 -1
- package/dist/cli.js +632 -145
- package/dist/commands/deploy.d.ts +15 -0
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/init.d.ts +3 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/status.d.ts +14 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/validate.d.ts.map +1 -1
- package/dist/deploy/apply.d.ts +6 -0
- package/dist/deploy/apply.d.ts.map +1 -1
- package/dist/deploy/commit-match-settle.d.ts +14 -1
- package/dist/deploy/commit-match-settle.d.ts.map +1 -1
- package/dist/deploy/deployment-status.d.ts +19 -0
- package/dist/deploy/deployment-status.d.ts.map +1 -1
- package/dist/deploy/manifest-diff.d.ts +2 -0
- package/dist/deploy/manifest-diff.d.ts.map +1 -1
- package/dist/deploy/verify-deploy.d.ts +2 -0
- package/dist/deploy/verify-deploy.d.ts.map +1 -1
- package/dist/manifest/load.d.ts +27 -0
- package/dist/manifest/load.d.ts.map +1 -1
- package/dist/secrets/push.d.ts +8 -0
- package/dist/secrets/push.d.ts.map +1 -1
- package/dist/secrets/status.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/package.json +1 -1
- package/skills/launchpad-content-pr/SKILL.md +1 -1
- package/skills/launchpad-deploy/SKILL.md +32 -10
- package/skills/launchpad-deploy-status/SKILL.md +10 -1
- package/skills/launchpad-destroy/SKILL.md +1 -1
- package/skills/launchpad-identity/SKILL.md +1 -1
- package/skills/launchpad-onboard/SKILL.md +13 -1
- package/skills/launchpad-report/SKILL.md +1 -1
- package/skills/launchpad-status/SKILL.md +10 -1
package/dist/cli.js
CHANGED
|
@@ -19,7 +19,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
19
19
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
20
20
|
|
|
21
21
|
// src/version.ts
|
|
22
|
-
var CLI_VERSION = "0.
|
|
22
|
+
var CLI_VERSION = "0.60.1";
|
|
23
23
|
|
|
24
24
|
// src/config.ts
|
|
25
25
|
import { readFileSync } from "node:fs";
|
|
@@ -2138,6 +2138,9 @@ function parse202(body, fallbackSlug) {
|
|
|
2138
2138
|
slug: typeof body.slug === "string" ? body.slug : fallbackSlug,
|
|
2139
2139
|
message: typeof body.message === "string" ? body.message : "bundle committed; deployment is pending"
|
|
2140
2140
|
};
|
|
2141
|
+
if (body.v2Delivery !== undefined && body.v2Delivery !== "pending" && body.v2Delivery !== "deferred") {
|
|
2142
|
+
return null;
|
|
2143
|
+
}
|
|
2141
2144
|
return reconcile === undefined ? accepted : { ...accepted, reconcile };
|
|
2142
2145
|
}
|
|
2143
2146
|
if (body.status === "provisioning_started") {
|
|
@@ -3399,19 +3402,14 @@ var SpecSchema = z2.object({
|
|
|
3399
3402
|
message: "confine_origin has no meaning with origin: dispatch — there is no public pages.dev apex to confine. Remove it."
|
|
3400
3403
|
});
|
|
3401
3404
|
}
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
ctx.addIssue({
|
|
3411
|
-
code: z2.ZodIssueCode.custom,
|
|
3412
|
-
path: ["build"],
|
|
3413
|
-
message: "build is not yet supported with origin: dispatch — a dispatch workspace has no Pages module to carry it, and the v2 deploy path that will consume it is not shipped yet. Accepting it here would silently drop it."
|
|
3414
|
-
});
|
|
3405
|
+
for (const [name, envVar] of Object.entries(s.env_vars ?? {})) {
|
|
3406
|
+
if (envVar.type === "secret_text") {
|
|
3407
|
+
ctx.addIssue({
|
|
3408
|
+
code: z2.ZodIssueCode.custom,
|
|
3409
|
+
path: ["env_vars", name],
|
|
3410
|
+
message: "secret_text env vars are not supported with origin: dispatch until PS-2740 (secrets on v2) provides their runtime consumer."
|
|
3411
|
+
});
|
|
3412
|
+
}
|
|
3415
3413
|
}
|
|
3416
3414
|
}
|
|
3417
3415
|
if (!isContainer && s.containers !== undefined) {
|
|
@@ -4403,6 +4401,21 @@ function membersForSecret(set, secret) {
|
|
|
4403
4401
|
}
|
|
4404
4402
|
// ../launchpad-engine/dist/redact-status.js
|
|
4405
4403
|
var UNSAFE_KEY = new Set(["__proto__", "prototype", "constructor"]);
|
|
4404
|
+
// ../launchpad-engine/dist/deployment-auth.js
|
|
4405
|
+
function deploymentAuthIssue(appType, auth) {
|
|
4406
|
+
if (appType === "container" || auth === "gateway")
|
|
4407
|
+
return null;
|
|
4408
|
+
return {
|
|
4409
|
+
error: "pages_access_retired",
|
|
4410
|
+
message: "Cloudflare Access login is retired for Pages apps. Set auth: gateway (spec.auth in v2). Containers are unchanged."
|
|
4411
|
+
};
|
|
4412
|
+
}
|
|
4413
|
+
// ../launchpad-engine/dist/new-app-origin.js
|
|
4414
|
+
function newAppOrigin(appType, requested) {
|
|
4415
|
+
if (appType === "container")
|
|
4416
|
+
return;
|
|
4417
|
+
return requested ?? (appType === "react+api" ? "pages" : "dispatch");
|
|
4418
|
+
}
|
|
4406
4419
|
// src/bundle/boundary.ts
|
|
4407
4420
|
var MAX_CONTENT_PROBE_BYTES = 1024 * 1024;
|
|
4408
4421
|
function applyBoundaryToFiles(args) {
|
|
@@ -4550,7 +4563,7 @@ async function bundleAndDeploy(args) {
|
|
|
4550
4563
|
}
|
|
4551
4564
|
|
|
4552
4565
|
// src/deploy/verify-deploy.ts
|
|
4553
|
-
import { readFileSync as
|
|
4566
|
+
import { readFileSync as readFileSync10, existsSync as existsSync4 } from "node:fs";
|
|
4554
4567
|
import { join as join9 } from "node:path";
|
|
4555
4568
|
|
|
4556
4569
|
// src/deploy/redact-diagnostic.ts
|
|
@@ -4596,11 +4609,67 @@ async function fetchDeploymentStatus(cfg, slug, fetcher = fetch) {
|
|
|
4596
4609
|
return {
|
|
4597
4610
|
slug: typeof raw.slug === "string" ? raw.slug : slug,
|
|
4598
4611
|
supported: raw.supported,
|
|
4612
|
+
...raw.origin === "pages" || raw.origin === "dispatch" ? { origin: raw.origin } : {},
|
|
4613
|
+
...projectV2(raw.v2),
|
|
4599
4614
|
liveDeployment: scrubLiveDeployment(raw.liveDeployment ?? null),
|
|
4600
4615
|
lastSuccessfulDeployment: projectLastSuccessful(raw.lastSuccessfulDeployment),
|
|
4601
4616
|
...raw.reconciledToLive === true ? { reconciledToLive: true } : {}
|
|
4602
4617
|
};
|
|
4603
4618
|
}
|
|
4619
|
+
function projectV2(value) {
|
|
4620
|
+
if (value === null || typeof value !== "object")
|
|
4621
|
+
return {};
|
|
4622
|
+
const raw = value;
|
|
4623
|
+
if (!isV2State(raw.state) || !isV2Stage(raw.stage) || typeof raw.commitSha !== "string" || typeof raw.attempts !== "number" || !Number.isFinite(raw.attempts) || typeof raw.updatedAt !== "string") {
|
|
4624
|
+
return {};
|
|
4625
|
+
}
|
|
4626
|
+
if (!isConsistentV2State(raw.state, raw.stage))
|
|
4627
|
+
return {};
|
|
4628
|
+
if (raw.failedAt !== undefined && !isV2FailedStage(raw.failedAt))
|
|
4629
|
+
return {};
|
|
4630
|
+
const v2 = {
|
|
4631
|
+
state: raw.state,
|
|
4632
|
+
stage: raw.stage,
|
|
4633
|
+
commitSha: raw.commitSha,
|
|
4634
|
+
attempts: raw.attempts,
|
|
4635
|
+
updatedAt: raw.updatedAt,
|
|
4636
|
+
...raw.failedAt !== undefined ? { failedAt: raw.failedAt } : {},
|
|
4637
|
+
...typeof raw.reason === "string" ? { reason: redactDiagnostic(raw.reason) } : {},
|
|
4638
|
+
...typeof raw.detail === "string" ? { detail: redactDiagnostic(raw.detail) } : {},
|
|
4639
|
+
...projectRunUrl(raw.runUrl),
|
|
4640
|
+
...typeof raw.deliveredAt === "string" ? { deliveredAt: raw.deliveredAt } : {}
|
|
4641
|
+
};
|
|
4642
|
+
return { v2 };
|
|
4643
|
+
}
|
|
4644
|
+
function projectRunUrl(value) {
|
|
4645
|
+
if (typeof value !== "string")
|
|
4646
|
+
return {};
|
|
4647
|
+
try {
|
|
4648
|
+
const url = new URL(value);
|
|
4649
|
+
return url.protocol === "https:" && url.hostname === "github.com" ? { runUrl: value } : {};
|
|
4650
|
+
} catch {
|
|
4651
|
+
return {};
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
function isV2State(value) {
|
|
4655
|
+
return value === "pending" || value === "delivered" || value === "refused";
|
|
4656
|
+
}
|
|
4657
|
+
function isV2Stage(value) {
|
|
4658
|
+
return ["committed", "building", "verified", "uploading", "live", "failed"].includes(String(value));
|
|
4659
|
+
}
|
|
4660
|
+
function isConsistentV2State(state, stage) {
|
|
4661
|
+
switch (state) {
|
|
4662
|
+
case "delivered":
|
|
4663
|
+
return stage === "live";
|
|
4664
|
+
case "refused":
|
|
4665
|
+
return stage === "failed";
|
|
4666
|
+
case "pending":
|
|
4667
|
+
return stage !== "live" && stage !== "failed";
|
|
4668
|
+
}
|
|
4669
|
+
}
|
|
4670
|
+
function isV2FailedStage(value) {
|
|
4671
|
+
return ["build", "verify", "download", "expand", "manifest", "plan", "upload"].includes(String(value));
|
|
4672
|
+
}
|
|
4604
4673
|
function projectLastSuccessful(value) {
|
|
4605
4674
|
if (value === null || typeof value !== "object")
|
|
4606
4675
|
return null;
|
|
@@ -4650,20 +4719,27 @@ function shaMatches(a, b) {
|
|
|
4650
4719
|
return x === y;
|
|
4651
4720
|
return x.slice(0, n) === y.slice(0, n);
|
|
4652
4721
|
}
|
|
4653
|
-
async function
|
|
4722
|
+
async function readDeploymentContext(deps, slug) {
|
|
4654
4723
|
let status;
|
|
4655
4724
|
try {
|
|
4656
4725
|
status = await deps.fetchStatus(slug);
|
|
4657
4726
|
} catch {
|
|
4658
|
-
return null;
|
|
4727
|
+
return { baseline: null };
|
|
4728
|
+
}
|
|
4729
|
+
if (status === null)
|
|
4730
|
+
return { baseline: null };
|
|
4731
|
+
const origin = status.origin;
|
|
4732
|
+
if (!status.supported || status.liveDeployment === null) {
|
|
4733
|
+
return { baseline: null, ...origin !== undefined ? { origin } : {} };
|
|
4659
4734
|
}
|
|
4660
|
-
if (status === null || !status.supported || status.liveDeployment === null)
|
|
4661
|
-
return null;
|
|
4662
4735
|
const live = status.liveDeployment;
|
|
4663
4736
|
return {
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4737
|
+
baseline: {
|
|
4738
|
+
id: live.id,
|
|
4739
|
+
commitHash: live.commit?.hash ?? null,
|
|
4740
|
+
createdOn: live.createdOn
|
|
4741
|
+
},
|
|
4742
|
+
...origin !== undefined ? { origin } : {}
|
|
4667
4743
|
};
|
|
4668
4744
|
}
|
|
4669
4745
|
async function waitForCommitDeployment(args, deps) {
|
|
@@ -4671,6 +4747,7 @@ async function waitForCommitDeployment(args, deps) {
|
|
|
4671
4747
|
const start = deps.now();
|
|
4672
4748
|
const baselineId = args.baseline?.id ?? null;
|
|
4673
4749
|
let matchedInProgress = false;
|
|
4750
|
+
let lastV2Stage = null;
|
|
4674
4751
|
for (;; ) {
|
|
4675
4752
|
let status;
|
|
4676
4753
|
try {
|
|
@@ -4684,6 +4761,38 @@ async function waitForCommitDeployment(args, deps) {
|
|
|
4684
4761
|
return { kind: "unsupported" };
|
|
4685
4762
|
const live = status.liveDeployment;
|
|
4686
4763
|
const elapsed = deps.now() - start;
|
|
4764
|
+
if (status.origin === "dispatch" && status.v2 !== undefined && shaMatches(status.v2.commitSha, args.shippedSha)) {
|
|
4765
|
+
const delivery = status.v2;
|
|
4766
|
+
if (delivery.stage !== lastV2Stage) {
|
|
4767
|
+
args.onV2Stage?.(delivery);
|
|
4768
|
+
lastV2Stage = delivery.stage;
|
|
4769
|
+
}
|
|
4770
|
+
if (delivery.state === "refused") {
|
|
4771
|
+
return {
|
|
4772
|
+
kind: "failure",
|
|
4773
|
+
dispatch: true,
|
|
4774
|
+
failedStage: delivery.failedAt ?? delivery.stage,
|
|
4775
|
+
reason: delivery.reason,
|
|
4776
|
+
detail: delivery.detail,
|
|
4777
|
+
runUrl: delivery.runUrl
|
|
4778
|
+
};
|
|
4779
|
+
}
|
|
4780
|
+
if (delivery.state === "delivered" || delivery.stage === "live") {
|
|
4781
|
+
return {
|
|
4782
|
+
kind: "success",
|
|
4783
|
+
deploymentUrl: live?.url ?? null,
|
|
4784
|
+
v2: {
|
|
4785
|
+
commitSha: delivery.commitSha,
|
|
4786
|
+
...delivery.deliveredAt !== undefined ? { deliveredAt: delivery.deliveredAt } : {}
|
|
4787
|
+
}
|
|
4788
|
+
};
|
|
4789
|
+
}
|
|
4790
|
+
matchedInProgress = true;
|
|
4791
|
+
if (elapsed >= args.settleTimeoutMs)
|
|
4792
|
+
return { kind: "pending" };
|
|
4793
|
+
await deps.sleep(pollIntervalMs);
|
|
4794
|
+
continue;
|
|
4795
|
+
}
|
|
4687
4796
|
if (live !== null && shaMatches(live.commit?.hash, args.shippedSha)) {
|
|
4688
4797
|
if (live.buildStatus === "success") {
|
|
4689
4798
|
return { kind: "success", deploymentUrl: live.url };
|
|
@@ -4795,7 +4904,7 @@ async function verifyServed(args, deps = { fetch }) {
|
|
|
4795
4904
|
}
|
|
4796
4905
|
|
|
4797
4906
|
// src/commands/status.ts
|
|
4798
|
-
import { readFileSync as
|
|
4907
|
+
import { readFileSync as readFileSync9 } from "node:fs";
|
|
4799
4908
|
|
|
4800
4909
|
// src/deploy/manifest-state.ts
|
|
4801
4910
|
async function fetchManifestState(cfg, slug, opts = {}, fetcher = fetch) {
|
|
@@ -5604,6 +5713,88 @@ still provisioning after the watch window — re-run \`launchpad watch ${slug}\`
|
|
|
5604
5713
|
}
|
|
5605
5714
|
}
|
|
5606
5715
|
|
|
5716
|
+
// src/manifest/load.ts
|
|
5717
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
5718
|
+
import { parse as parseYaml4, YAMLParseError } from "yaml";
|
|
5719
|
+
function loadManifest(path8) {
|
|
5720
|
+
let raw;
|
|
5721
|
+
try {
|
|
5722
|
+
raw = readFileSync8(path8, "utf8");
|
|
5723
|
+
} catch (err) {
|
|
5724
|
+
const e = err;
|
|
5725
|
+
if (e.code === "ENOENT") {
|
|
5726
|
+
return { kind: "file-not-found", path: path8 };
|
|
5727
|
+
}
|
|
5728
|
+
return { kind: "read-error", path: path8, message: e.message ?? String(err) };
|
|
5729
|
+
}
|
|
5730
|
+
return parseManifest2(raw, path8);
|
|
5731
|
+
}
|
|
5732
|
+
function loadManifestAnyVersion(path8) {
|
|
5733
|
+
let raw;
|
|
5734
|
+
try {
|
|
5735
|
+
raw = readFileSync8(path8, "utf8");
|
|
5736
|
+
} catch (err) {
|
|
5737
|
+
const e = err;
|
|
5738
|
+
if (e.code === "ENOENT")
|
|
5739
|
+
return { kind: "file-not-found", path: path8 };
|
|
5740
|
+
return { kind: "read-error", path: path8, message: e.message ?? String(err) };
|
|
5741
|
+
}
|
|
5742
|
+
return parseManifestAnyVersion(raw, path8);
|
|
5743
|
+
}
|
|
5744
|
+
function parseManifestAnyVersion(yamlText, path8) {
|
|
5745
|
+
let parsed;
|
|
5746
|
+
try {
|
|
5747
|
+
parsed = parseYaml4(yamlText);
|
|
5748
|
+
} catch (err) {
|
|
5749
|
+
const message = err instanceof YAMLParseError ? err.message : err.message ?? String(err);
|
|
5750
|
+
return { kind: "yaml-parse-error", path: path8, message };
|
|
5751
|
+
}
|
|
5752
|
+
const apiVersion = typeof parsed === "object" && parsed !== null ? parsed.apiVersion : undefined;
|
|
5753
|
+
if (apiVersion === "launchpad.m-kopa.us/v2") {
|
|
5754
|
+
const result = parseManifestV2(parsed);
|
|
5755
|
+
return result.kind === "ok" ? { kind: "ok", manifest: result.manifest, schema: apiVersion } : { kind: "schema-error", path: path8, schema: apiVersion, issues: [...result.issues] };
|
|
5756
|
+
}
|
|
5757
|
+
if (apiVersion === "launchpad.m-kopa.us/v1alpha1") {
|
|
5758
|
+
const result = parseManifest(parsed);
|
|
5759
|
+
return result.kind === "ok" ? { kind: "ok", manifest: result.manifest, schema: apiVersion } : { kind: "schema-error", path: path8, schema: apiVersion, issues: [...result.issues] };
|
|
5760
|
+
}
|
|
5761
|
+
return {
|
|
5762
|
+
kind: "schema-error",
|
|
5763
|
+
path: path8,
|
|
5764
|
+
schema: "launchpad.m-kopa.us/v1alpha1 or launchpad.m-kopa.us/v2",
|
|
5765
|
+
issues: [
|
|
5766
|
+
{
|
|
5767
|
+
path: "apiVersion",
|
|
5768
|
+
message: `unsupported apiVersion ${JSON.stringify(apiVersion)}; expected launchpad.m-kopa.us/v1alpha1 or launchpad.m-kopa.us/v2`
|
|
5769
|
+
}
|
|
5770
|
+
]
|
|
5771
|
+
};
|
|
5772
|
+
}
|
|
5773
|
+
function parseManifest2(yamlText, path8) {
|
|
5774
|
+
let parsed;
|
|
5775
|
+
try {
|
|
5776
|
+
parsed = parseYaml4(yamlText);
|
|
5777
|
+
} catch (err) {
|
|
5778
|
+
const message = err instanceof YAMLParseError ? err.message : err.message ?? String(err);
|
|
5779
|
+
return { kind: "yaml-parse-error", path: path8, message };
|
|
5780
|
+
}
|
|
5781
|
+
const result = ManifestSchema.safeParse(parsed);
|
|
5782
|
+
if (result.success) {
|
|
5783
|
+
return { kind: "ok", manifest: result.data };
|
|
5784
|
+
}
|
|
5785
|
+
return {
|
|
5786
|
+
kind: "schema-error",
|
|
5787
|
+
path: path8,
|
|
5788
|
+
issues: formatIssues(result.error)
|
|
5789
|
+
};
|
|
5790
|
+
}
|
|
5791
|
+
function formatIssues(error) {
|
|
5792
|
+
return error.issues.map((issue) => ({
|
|
5793
|
+
path: issue.path.map(String).join(".") || "(root)",
|
|
5794
|
+
message: issue.message
|
|
5795
|
+
}));
|
|
5796
|
+
}
|
|
5797
|
+
|
|
5607
5798
|
// src/commands/status.ts
|
|
5608
5799
|
var statusCommand = {
|
|
5609
5800
|
name: "status",
|
|
@@ -5699,7 +5890,7 @@ async function runStatus(args, io) {
|
|
|
5699
5890
|
}
|
|
5700
5891
|
let localYaml = null;
|
|
5701
5892
|
try {
|
|
5702
|
-
localYaml =
|
|
5893
|
+
localYaml = readFileSync9(parsed.file, "utf8");
|
|
5703
5894
|
} catch (e) {
|
|
5704
5895
|
if (!isEnoent(e)) {
|
|
5705
5896
|
io.err(`launchpad status: cannot read local manifest at ${parsed.file}: ${describe12(e)}`);
|
|
@@ -5708,24 +5899,24 @@ async function runStatus(args, io) {
|
|
|
5708
5899
|
}
|
|
5709
5900
|
let local = null;
|
|
5710
5901
|
if (localYaml !== null) {
|
|
5711
|
-
|
|
5712
|
-
try {
|
|
5713
|
-
const { parse: parseYaml4 } = await import("yaml");
|
|
5714
|
-
localObj = parseYaml4(localYaml);
|
|
5715
|
-
} catch (e) {
|
|
5716
|
-
io.err(`launchpad status: ${parsed.file} is not valid YAML: ${describe12(e)}`);
|
|
5717
|
-
return 2;
|
|
5718
|
-
}
|
|
5719
|
-
const localParse = parseManifest(localObj);
|
|
5902
|
+
const localParse = parseManifestAnyVersion(localYaml, parsed.file);
|
|
5720
5903
|
if (localParse.kind !== "ok") {
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5904
|
+
if (localParse.kind === "yaml-parse-error") {
|
|
5905
|
+
io.err(`launchpad status: ${parsed.file} is not valid YAML: ${localParse.message}`);
|
|
5906
|
+
} else if (localParse.kind === "schema-error") {
|
|
5907
|
+
io.err(`launchpad status: ${parsed.file} failed schema validation against ${localParse.schema}:`);
|
|
5908
|
+
for (const issue of localParse.issues) {
|
|
5909
|
+
io.err(` - ${issue.path}: ${issue.message}`);
|
|
5910
|
+
}
|
|
5911
|
+
} else {
|
|
5912
|
+
io.err(`launchpad status: cannot read local manifest at ${parsed.file}`);
|
|
5724
5913
|
}
|
|
5725
5914
|
return 2;
|
|
5726
5915
|
}
|
|
5727
5916
|
local = localParse.manifest;
|
|
5728
|
-
|
|
5917
|
+
if (local.apiVersion === "launchpad.m-kopa.us/v1alpha1") {
|
|
5918
|
+
warnSecretShape(local.production_env, io);
|
|
5919
|
+
}
|
|
5729
5920
|
}
|
|
5730
5921
|
let state;
|
|
5731
5922
|
try {
|
|
@@ -5755,9 +5946,9 @@ async function runStatus(args, io) {
|
|
|
5755
5946
|
}
|
|
5756
5947
|
if (state.manifestYaml === null || state.manifestYaml === undefined) {
|
|
5757
5948
|
const live = deployment?.liveDeployment ?? null;
|
|
5758
|
-
const contentIsLive = live !== null && (live.buildStatus === "success" || deployment?.lastSuccessfulDeployment != null);
|
|
5949
|
+
const contentIsLive = deployment?.v2?.state === "delivered" || deployment?.v2?.stage === "live" || live !== null && (live.buildStatus === "success" || deployment?.lastSuccessfulDeployment != null);
|
|
5759
5950
|
const liveButEmpty = lifecycle !== null && lifecycle.state === "live";
|
|
5760
|
-
const platformTracked = state.hasAppFile === true || live?.trigger === "bot";
|
|
5951
|
+
const platformTracked = state.hasAppFile === true || live?.trigger === "bot" || deployment?.origin === "dispatch";
|
|
5761
5952
|
const out = {
|
|
5762
5953
|
state: contentIsLive ? platformTracked ? "live_drift_unknown" : "live_content_untracked" : platformTracked && !deploymentKnown ? "live_drift_unknown" : liveButEmpty ? "live_no_content" : "no_deployed_manifest",
|
|
5763
5954
|
...local === null ? { drift: null } : {},
|
|
@@ -5790,7 +5981,7 @@ async function runStatus(args, io) {
|
|
|
5790
5981
|
};
|
|
5791
5982
|
emit4(out, parsed.json, io);
|
|
5792
5983
|
if (parsed.strict) {
|
|
5793
|
-
if (deployment?.liveDeployment?.buildStatus === "failure") {
|
|
5984
|
+
if (deployment?.liveDeployment?.buildStatus === "failure" || deployment?.v2?.state === "refused") {
|
|
5794
5985
|
io.err(`launchpad status: --strict: live build FAILED ` + `(drift not evaluated — no local launchpad.yaml here).`);
|
|
5795
5986
|
return 1;
|
|
5796
5987
|
}
|
|
@@ -5798,26 +5989,40 @@ async function runStatus(args, io) {
|
|
|
5798
5989
|
}
|
|
5799
5990
|
return 0;
|
|
5800
5991
|
}
|
|
5801
|
-
|
|
5802
|
-
try {
|
|
5803
|
-
const { parse: parseYaml4 } = await import("yaml");
|
|
5804
|
-
deployedObj = parseYaml4(state.manifestYaml);
|
|
5805
|
-
} catch (e) {
|
|
5806
|
-
io.err(`launchpad status: deployed manifest at ${state.deployedManifestSha} is not valid YAML: ${describe12(e)}`);
|
|
5807
|
-
return 2;
|
|
5808
|
-
}
|
|
5809
|
-
const deployedParse = parseManifest(deployedObj);
|
|
5992
|
+
const deployedParse = parseManifestAnyVersion(state.manifestYaml, `deployed manifest at ${state.deployedManifestSha}`);
|
|
5810
5993
|
if (deployedParse.kind !== "ok") {
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5994
|
+
if (deployedParse.kind === "yaml-parse-error") {
|
|
5995
|
+
io.err(`launchpad status: deployed manifest at ${state.deployedManifestSha} is not valid YAML: ${deployedParse.message}`);
|
|
5996
|
+
} else if (deployedParse.kind === "schema-error") {
|
|
5997
|
+
io.err(`launchpad status: deployed manifest at ${state.deployedManifestSha} failed schema validation against ${deployedParse.schema}:`);
|
|
5998
|
+
for (const issue of deployedParse.issues) {
|
|
5999
|
+
io.err(` - ${issue.path}: ${issue.message}`);
|
|
6000
|
+
}
|
|
5814
6001
|
}
|
|
5815
6002
|
return 2;
|
|
5816
6003
|
}
|
|
5817
6004
|
const deployed = deployedParse.manifest;
|
|
6005
|
+
if (local.apiVersion !== deployed.apiVersion) {
|
|
6006
|
+
const reason = `local manifest uses ${local.apiVersion}, while the deployed manifest uses ${deployed.apiVersion}; ` + "the manifest shapes differ, so drift was not evaluated";
|
|
6007
|
+
const out = {
|
|
6008
|
+
state: "live_drift_unknown",
|
|
6009
|
+
drift: null,
|
|
6010
|
+
driftUnavailableReason: reason,
|
|
6011
|
+
...statusTruthFields({
|
|
6012
|
+
slug: parsed.slug,
|
|
6013
|
+
deployedSha: state.deployedManifestSha,
|
|
6014
|
+
headSha: state.appRepoHeadSha,
|
|
6015
|
+
openPrNumber: state.openPr?.number ?? null,
|
|
6016
|
+
...deploymentKnown ? { deployment } : {},
|
|
6017
|
+
...standingExceptions !== null ? { standingExceptions } : {}
|
|
6018
|
+
})
|
|
6019
|
+
};
|
|
6020
|
+
emit4(out, parsed.json, io);
|
|
6021
|
+
return 0;
|
|
6022
|
+
}
|
|
5818
6023
|
const localGroups = await resolveStatusGroups({
|
|
5819
6024
|
cfg,
|
|
5820
|
-
refs: allowedEntraGroups(local
|
|
6025
|
+
refs: allowedEntraGroups(manifestAccess(local)),
|
|
5821
6026
|
source: "local manifest",
|
|
5822
6027
|
manifestSha: null
|
|
5823
6028
|
});
|
|
@@ -5826,14 +6031,17 @@ async function runStatus(args, io) {
|
|
|
5826
6031
|
}
|
|
5827
6032
|
const deployedGroups = await resolveStatusGroups({
|
|
5828
6033
|
cfg,
|
|
5829
|
-
refs: allowedEntraGroups(deployed
|
|
6034
|
+
refs: allowedEntraGroups(manifestAccess(deployed)),
|
|
5830
6035
|
source: "deployed manifest",
|
|
5831
6036
|
manifestSha: state.deployedManifestSha
|
|
5832
6037
|
});
|
|
5833
6038
|
if (deployedGroups.kind === "error") {
|
|
5834
6039
|
return renderStatusGroupError(deployedGroups.envelope, parsed.json, io);
|
|
5835
6040
|
}
|
|
5836
|
-
const drift =
|
|
6041
|
+
const drift = local.apiVersion === "launchpad.m-kopa.us/v2" && deployed.apiVersion === "launchpad.m-kopa.us/v2" ? computeV2SpecDrift(local, deployed, {
|
|
6042
|
+
local: localGroups.ids,
|
|
6043
|
+
deployed: deployedGroups.ids
|
|
6044
|
+
}) : computeDrift(local, deployed, {
|
|
5837
6045
|
local: localGroups.ids,
|
|
5838
6046
|
deployed: deployedGroups.ids
|
|
5839
6047
|
});
|
|
@@ -5850,8 +6058,8 @@ async function runStatus(args, io) {
|
|
|
5850
6058
|
}
|
|
5851
6059
|
accessDrift = detectCanonicalAccessDrift(localGroups.ids, enforcedGroups.ids);
|
|
5852
6060
|
}
|
|
5853
|
-
const localAssignmentGroups = (local
|
|
5854
|
-
const deployedAssignmentGroups = (deployed
|
|
6061
|
+
const localAssignmentGroups = manifestAuth(local) === "access" ? localGroups : null;
|
|
6062
|
+
const deployedAssignmentGroups = manifestAuth(deployed) === "access" ? deployedGroups : null;
|
|
5855
6063
|
const assignmentCheck = localAssignmentGroups === null && deployedAssignmentGroups === null ? { kind: "ok", issues: [] } : await findAssignmentIssues(cfg, localAssignmentGroups, deployedAssignmentGroups, state.deployedManifestSha);
|
|
5856
6064
|
if (assignmentCheck.kind === "error") {
|
|
5857
6065
|
return renderStatusGroupError(assignmentCheck.envelope, parsed.json, io);
|
|
@@ -6052,6 +6260,58 @@ function computeDrift(local, deployed, canonicalGroups) {
|
|
|
6052
6260
|
}
|
|
6053
6261
|
return diffs;
|
|
6054
6262
|
}
|
|
6263
|
+
function computeV2SpecDrift(local, deployed, canonicalGroups) {
|
|
6264
|
+
const localSpec = {
|
|
6265
|
+
...local.spec,
|
|
6266
|
+
access: {
|
|
6267
|
+
...local.spec.access,
|
|
6268
|
+
allowed_entra_groups: [
|
|
6269
|
+
...canonicalGroups?.local ?? local.spec.access.allowed_entra_groups
|
|
6270
|
+
].sort()
|
|
6271
|
+
}
|
|
6272
|
+
};
|
|
6273
|
+
const deployedSpec = {
|
|
6274
|
+
...deployed.spec,
|
|
6275
|
+
access: {
|
|
6276
|
+
...deployed.spec.access,
|
|
6277
|
+
allowed_entra_groups: [
|
|
6278
|
+
...canonicalGroups?.deployed ?? deployed.spec.access.allowed_entra_groups
|
|
6279
|
+
].sort()
|
|
6280
|
+
}
|
|
6281
|
+
};
|
|
6282
|
+
const diffs = [];
|
|
6283
|
+
compareV2Value("spec", localSpec, deployedSpec, diffs);
|
|
6284
|
+
return diffs;
|
|
6285
|
+
}
|
|
6286
|
+
function compareV2Value(path8, local, deployed, diffs) {
|
|
6287
|
+
if (Object.is(local, deployed))
|
|
6288
|
+
return;
|
|
6289
|
+
if (Array.isArray(local) || Array.isArray(deployed)) {
|
|
6290
|
+
if (JSON.stringify(local) !== JSON.stringify(deployed)) {
|
|
6291
|
+
diffs.push({ path: path8, local, deployed });
|
|
6292
|
+
}
|
|
6293
|
+
return;
|
|
6294
|
+
}
|
|
6295
|
+
if (typeof local === "object" && local !== null && typeof deployed === "object" && deployed !== null) {
|
|
6296
|
+
const localRecord = local;
|
|
6297
|
+
const deployedRecord = deployed;
|
|
6298
|
+
const keys = new Set([
|
|
6299
|
+
...Object.keys(localRecord),
|
|
6300
|
+
...Object.keys(deployedRecord)
|
|
6301
|
+
]);
|
|
6302
|
+
for (const key of [...keys].sort()) {
|
|
6303
|
+
compareV2Value(`${path8}.${key}`, localRecord[key], deployedRecord[key], diffs);
|
|
6304
|
+
}
|
|
6305
|
+
return;
|
|
6306
|
+
}
|
|
6307
|
+
diffs.push({ path: path8, local, deployed });
|
|
6308
|
+
}
|
|
6309
|
+
function manifestAccess(manifest) {
|
|
6310
|
+
return manifest.apiVersion === "launchpad.m-kopa.us/v2" ? manifest.spec.access : manifest.access;
|
|
6311
|
+
}
|
|
6312
|
+
function manifestAuth(manifest) {
|
|
6313
|
+
return manifest.apiVersion === "launchpad.m-kopa.us/v2" ? manifest.spec.auth ?? "access" : manifest.auth ?? "access";
|
|
6314
|
+
}
|
|
6055
6315
|
function lifecycleOutput(slug, lc) {
|
|
6056
6316
|
const state = lc.state === "failed" ? "provisioning_failed" : lc.state === "live" ? "in_sync" : lc.state;
|
|
6057
6317
|
return {
|
|
@@ -6119,7 +6379,7 @@ function emit4(out, asJson, io) {
|
|
|
6119
6379
|
surfaceExceptions(out, io);
|
|
6120
6380
|
return;
|
|
6121
6381
|
case "live_content_untracked":
|
|
6122
|
-
io.out(`${out.slug}: live — content deployed via ` + `${triggerLabel(out.deployment?.liveDeployment?.trigger)} (no platform-tracked manifest; this app deploys outside \`launchpad deploy\`).`);
|
|
6382
|
+
io.out(`${out.slug}: live — content deployed via ` + `${triggerLabel(out.deployment?.liveDeployment?.trigger)} ` + `(no platform-tracked manifest; this app deploys outside \`launchpad deploy\`).`);
|
|
6123
6383
|
surfaceNoLocalManifestNote(out, io);
|
|
6124
6384
|
surfaceHeadVsDeployed(out, io);
|
|
6125
6385
|
surfaceDeployment(out, io);
|
|
@@ -6181,6 +6441,10 @@ function surfaceGroupIssues(out, io) {
|
|
|
6181
6441
|
function surfaceNoLocalManifestNote(out, io) {
|
|
6182
6442
|
if (out.drift !== null)
|
|
6183
6443
|
return;
|
|
6444
|
+
if (out.driftUnavailableReason !== undefined) {
|
|
6445
|
+
io.out(` ${out.driftUnavailableReason}.`);
|
|
6446
|
+
return;
|
|
6447
|
+
}
|
|
6184
6448
|
io.out(" no local launchpad.yaml here — drift not checked " + "(cd into the app directory or pass --file to compare)");
|
|
6185
6449
|
}
|
|
6186
6450
|
function triggerLabel(trigger) {
|
|
@@ -6206,7 +6470,13 @@ function surfaceFailedLifecycleDeployment(out, io) {
|
|
|
6206
6470
|
}
|
|
6207
6471
|
function surfaceDeployment(out, io) {
|
|
6208
6472
|
const dep = out.deployment;
|
|
6209
|
-
if (dep === undefined || dep === null
|
|
6473
|
+
if (dep === undefined || dep === null)
|
|
6474
|
+
return;
|
|
6475
|
+
if (dep.origin === "dispatch") {
|
|
6476
|
+
surfaceV2Deployment(dep, io);
|
|
6477
|
+
return;
|
|
6478
|
+
}
|
|
6479
|
+
if (!dep.supported)
|
|
6210
6480
|
return;
|
|
6211
6481
|
if (dep.reconciledToLive === true) {
|
|
6212
6482
|
io.out(" ✓ recovered: this app was in a failed state; its verified-live build reconciled the record failed→live (no `launchpad recover` needed).");
|
|
@@ -6246,6 +6516,29 @@ function surfaceDeployment(out, io) {
|
|
|
6246
6516
|
}
|
|
6247
6517
|
}
|
|
6248
6518
|
}
|
|
6519
|
+
function surfaceV2Deployment(dep, io) {
|
|
6520
|
+
for (const line of renderV2DeploymentLines(dep))
|
|
6521
|
+
io.out(line);
|
|
6522
|
+
}
|
|
6523
|
+
function renderV2DeploymentLines(dep) {
|
|
6524
|
+
const delivery = dep.v2;
|
|
6525
|
+
if (delivery === undefined) {
|
|
6526
|
+
return [" v2 delivery: dispatch origin (delivery state unavailable)."];
|
|
6527
|
+
}
|
|
6528
|
+
const lines = [];
|
|
6529
|
+
const deliveredAt = delivery.state === "delivered" && delivery.deliveredAt !== undefined ? `, delivered ${delivery.deliveredAt}` : "";
|
|
6530
|
+
lines.push(` v2 delivery: state ${delivery.state}, stage ${delivery.stage}, commit ${delivery.commitSha.slice(0, 7)}${deliveredAt}`);
|
|
6531
|
+
if (delivery.state === "refused") {
|
|
6532
|
+
const failedAt = delivery.failedAt ?? delivery.stage;
|
|
6533
|
+
lines.push(` failed at ${failedAt}${delivery.reason ? `: ${delivery.reason}` : ""}`);
|
|
6534
|
+
if (delivery.runUrl)
|
|
6535
|
+
lines.push(` GitHub run: ${delivery.runUrl}`);
|
|
6536
|
+
}
|
|
6537
|
+
if (delivery.state !== "delivered" && dep.lastSuccessfulDeployment !== null) {
|
|
6538
|
+
lines.push(` serving: previous successful deployment from ${dep.lastSuccessfulDeployment.createdOn}`);
|
|
6539
|
+
}
|
|
6540
|
+
return lines;
|
|
6541
|
+
}
|
|
6249
6542
|
function surfaceExceptions(out, io) {
|
|
6250
6543
|
const exceptions = out.standingExceptions;
|
|
6251
6544
|
if (exceptions === undefined || exceptions.length === 0)
|
|
@@ -6262,7 +6555,7 @@ function surfaceHeadVsDeployed(out, io) {
|
|
|
6262
6555
|
if (out.hasOpenPr) {
|
|
6263
6556
|
io.out(` (deployed sha ${short(out.deployedSha)} is behind app-repo HEAD ${short(out.headSha)};` + ` an apply may be queued — see PR #${out.openPrNumber})`);
|
|
6264
6557
|
} else {
|
|
6265
|
-
io.out(` (deployed sha ${short(out.deployedSha)} is behind app-repo HEAD ${short(out.headSha)}
|
|
6558
|
+
io.out(` (deployed sha ${short(out.deployedSha)} is behind app-repo HEAD ${short(out.headSha)};` + ` main is ahead of deployed)`);
|
|
6266
6559
|
}
|
|
6267
6560
|
}
|
|
6268
6561
|
}
|
|
@@ -6283,7 +6576,7 @@ function warnSecretShape(env, io) {
|
|
|
6283
6576
|
const hasPrefix = prefixes.some((p) => v.startsWith(p));
|
|
6284
6577
|
const longBase64Shape = v.length >= 32 && /^[A-Za-z0-9+/=_-]+$/.test(v);
|
|
6285
6578
|
if (hasPrefix && longBase64Shape) {
|
|
6286
|
-
io.err(`launchpad status: production_env.${k} looks like a secret. production_env is non-secret by contract; move secrets to the secrets bindings path. (This is informational; status will continue.)`);
|
|
6579
|
+
io.err(`launchpad status: production_env.${k} looks like a secret. ` + `production_env is non-secret by contract; move secrets to the ` + `secrets bindings path. (This is informational; status will continue.)`);
|
|
6287
6580
|
}
|
|
6288
6581
|
}
|
|
6289
6582
|
}
|
|
@@ -6342,7 +6635,7 @@ function parseArgs2(args, cwd = process.cwd(), warn) {
|
|
|
6342
6635
|
if (slug === null) {
|
|
6343
6636
|
const inferred = inferSlug({ cwd, file, warn });
|
|
6344
6637
|
if (inferred === null) {
|
|
6345
|
-
return `slug not provided + cannot infer from cwd. Pass <slug> or --slug <slug>, or cd into a directory named launchpad-app-<slug>.`;
|
|
6638
|
+
return `slug not provided + cannot infer from cwd. ` + `Pass <slug> or --slug <slug>, or cd into a directory named launchpad-app-<slug>.`;
|
|
6346
6639
|
}
|
|
6347
6640
|
slug = inferred;
|
|
6348
6641
|
}
|
|
@@ -6401,7 +6694,7 @@ function readLocalEntrypoint(cwd) {
|
|
|
6401
6694
|
for (const rel of ["index.html", join9("static", "index.html")]) {
|
|
6402
6695
|
const p = join9(cwd, rel);
|
|
6403
6696
|
if (existsSync4(p))
|
|
6404
|
-
return new Uint8Array(
|
|
6697
|
+
return new Uint8Array(readFileSync10(p));
|
|
6405
6698
|
}
|
|
6406
6699
|
return null;
|
|
6407
6700
|
}
|
|
@@ -6416,13 +6709,25 @@ function realVerifyDeployDeps(cfg) {
|
|
|
6416
6709
|
}
|
|
6417
6710
|
async function runDeployVerification(args, io, deps) {
|
|
6418
6711
|
io.out("");
|
|
6419
|
-
|
|
6712
|
+
if (args.dispatchOrigin === true) {
|
|
6713
|
+
io.out("Waiting for your repo build and dispatch delivery …");
|
|
6714
|
+
} else {
|
|
6715
|
+
io.out("Verifying a build of your commit went live …");
|
|
6716
|
+
}
|
|
6717
|
+
const seenV2Stages = new Set;
|
|
6718
|
+
const onV2Stage = (delivery) => {
|
|
6719
|
+
if (seenV2Stages.has(delivery.stage))
|
|
6720
|
+
return;
|
|
6721
|
+
seenV2Stages.add(delivery.stage);
|
|
6722
|
+
io.out(` v2 stage: ${v2StageLabel(delivery.stage)}`);
|
|
6723
|
+
};
|
|
6420
6724
|
const settle = await deps.wait({
|
|
6421
6725
|
slug: args.slug,
|
|
6422
6726
|
shippedSha: args.shippedSha,
|
|
6423
6727
|
baseline: args.baseline,
|
|
6424
6728
|
registrationBoundMs: args.registrationBoundMs,
|
|
6425
|
-
settleTimeoutMs: args.timeoutMs
|
|
6729
|
+
settleTimeoutMs: args.timeoutMs,
|
|
6730
|
+
onV2Stage
|
|
6426
6731
|
}, deps.waitDeps);
|
|
6427
6732
|
switch (settle.kind) {
|
|
6428
6733
|
case "unsupported":
|
|
@@ -6451,10 +6756,25 @@ async function runDeployVerification(args, io, deps) {
|
|
|
6451
6756
|
io.out(` It may still be queued. Re-check the outcome: \`launchpad status ${args.slug}\``);
|
|
6452
6757
|
return 0;
|
|
6453
6758
|
case "pending":
|
|
6759
|
+
if (args.dispatchOrigin === true || seenV2Stages.size > 0) {
|
|
6760
|
+
io.out(`⏳ Dispatch delivery is still in progress after ${Math.round(args.timeoutMs / 1000)}s - couldn't verify yet.`);
|
|
6761
|
+
io.out(` Re-check the outcome: \`launchpad status ${args.slug}\``);
|
|
6762
|
+
return 0;
|
|
6763
|
+
}
|
|
6454
6764
|
io.out(`⏳ Your commit's build is still in progress after ${Math.round(args.timeoutMs / 1000)}s — couldn't verify yet.`);
|
|
6455
6765
|
io.out(` Re-check the outcome: \`launchpad status ${args.slug}\``);
|
|
6456
6766
|
return 0;
|
|
6457
6767
|
case "failure":
|
|
6768
|
+
if (settle.dispatch === true) {
|
|
6769
|
+
io.err(`✗ Dispatch delivery REFUSED at ${settle.failedStage ?? "unknown"} - your commit is NOT live.`);
|
|
6770
|
+
if (settle.reason)
|
|
6771
|
+
io.err(` reason: ${redactDiagnostic(settle.reason)}`);
|
|
6772
|
+
if (settle.detail)
|
|
6773
|
+
io.err(` detail: ${redactDiagnostic(settle.detail)}`);
|
|
6774
|
+
if (settle.runUrl)
|
|
6775
|
+
io.err(` GitHub run: ${settle.runUrl}`);
|
|
6776
|
+
return 1;
|
|
6777
|
+
}
|
|
6458
6778
|
io.err("✗ The Cloudflare Pages build for your commit FAILED — your content is NOT live.");
|
|
6459
6779
|
if (settle.failedStage)
|
|
6460
6780
|
io.err(` failed stage: ${redactDiagnostic(settle.failedStage)}`);
|
|
@@ -6466,9 +6786,30 @@ async function runDeployVerification(args, io, deps) {
|
|
|
6466
6786
|
io.err(` Full logs: \`launchpad status ${args.slug}\``);
|
|
6467
6787
|
return 1;
|
|
6468
6788
|
case "success":
|
|
6789
|
+
if (settle.v2 !== undefined) {
|
|
6790
|
+
const deliveredAt = settle.v2.deliveredAt ? ` at ${settle.v2.deliveredAt}` : "";
|
|
6791
|
+
io.out(`✓ Live in dispatch namespace - delivered commit ${settle.v2.commitSha}${deliveredAt}.`);
|
|
6792
|
+
return 0;
|
|
6793
|
+
}
|
|
6469
6794
|
return verifySuccess(args, settle.deploymentUrl, io, deps);
|
|
6470
6795
|
}
|
|
6471
6796
|
}
|
|
6797
|
+
function v2StageLabel(stage) {
|
|
6798
|
+
switch (stage) {
|
|
6799
|
+
case "committed":
|
|
6800
|
+
return "source committed";
|
|
6801
|
+
case "building":
|
|
6802
|
+
return "CI building";
|
|
6803
|
+
case "verified":
|
|
6804
|
+
return "run verified";
|
|
6805
|
+
case "uploading":
|
|
6806
|
+
return "artifact uploading";
|
|
6807
|
+
case "live":
|
|
6808
|
+
return "live in dispatch namespace";
|
|
6809
|
+
case "failed":
|
|
6810
|
+
return "failed";
|
|
6811
|
+
}
|
|
6812
|
+
}
|
|
6472
6813
|
function realRedeployVerifyDeps(cfg) {
|
|
6473
6814
|
return {
|
|
6474
6815
|
wait: waitForDeployment,
|
|
@@ -6695,7 +7036,7 @@ function runGit2(sp, cwd, args) {
|
|
|
6695
7036
|
}
|
|
6696
7037
|
|
|
6697
7038
|
// src/deploy/manifest-diff.ts
|
|
6698
|
-
import { parse as
|
|
7039
|
+
import { parse as parseYaml5 } from "yaml";
|
|
6699
7040
|
var MASK = "«redacted»";
|
|
6700
7041
|
var SECRET_KEY_SEGMENT = /(secret|token|api[_-]?key|access[_-]?key|private[_-]?key|secret[_-]?key|password|passwd|pwd|authorization|auth[_-]?token|bearer|client[_-]?secret|session|credential|value)/i;
|
|
6701
7042
|
function isSecretPath(path8) {
|
|
@@ -6786,7 +7127,7 @@ function diffManifests(localYaml, deployedYaml) {
|
|
|
6786
7127
|
let local;
|
|
6787
7128
|
let deployed;
|
|
6788
7129
|
try {
|
|
6789
|
-
local =
|
|
7130
|
+
local = parseYaml5(localYaml);
|
|
6790
7131
|
} catch (e) {
|
|
6791
7132
|
return {
|
|
6792
7133
|
inSync: false,
|
|
@@ -6795,7 +7136,7 @@ function diffManifests(localYaml, deployedYaml) {
|
|
|
6795
7136
|
};
|
|
6796
7137
|
}
|
|
6797
7138
|
try {
|
|
6798
|
-
deployed =
|
|
7139
|
+
deployed = parseYaml5(deployedYaml);
|
|
6799
7140
|
} catch (e) {
|
|
6800
7141
|
return {
|
|
6801
7142
|
inSync: false,
|
|
@@ -6803,6 +7144,22 @@ function diffManifests(localYaml, deployedYaml) {
|
|
|
6803
7144
|
parseError: `deployed manifest did not parse: ${e.message}`
|
|
6804
7145
|
};
|
|
6805
7146
|
}
|
|
7147
|
+
const localVersion = manifestApiVersion(local);
|
|
7148
|
+
const deployedVersion = manifestApiVersion(deployed);
|
|
7149
|
+
if (localVersion !== deployedVersion && (isV2(localVersion) || isV2(deployedVersion))) {
|
|
7150
|
+
return {
|
|
7151
|
+
inSync: false,
|
|
7152
|
+
changes: [],
|
|
7153
|
+
parseError: null,
|
|
7154
|
+
shapeMismatch: `local uses ${localVersion ?? "an unknown schema"}; deployed uses ${deployedVersion ?? "an unknown schema"}`
|
|
7155
|
+
};
|
|
7156
|
+
}
|
|
7157
|
+
if (isV2(localVersion) && isV2(deployedVersion)) {
|
|
7158
|
+
local = { spec: manifestSpec(local) };
|
|
7159
|
+
deployed = { spec: manifestSpec(deployed) };
|
|
7160
|
+
}
|
|
7161
|
+
local = normalize(local);
|
|
7162
|
+
deployed = normalize(deployed);
|
|
6806
7163
|
if (stableStringify(local) === stableStringify(deployed)) {
|
|
6807
7164
|
return { inSync: true, changes: [], parseError: null };
|
|
6808
7165
|
}
|
|
@@ -6849,6 +7206,18 @@ function diffManifests(localYaml, deployedYaml) {
|
|
|
6849
7206
|
}
|
|
6850
7207
|
return { inSync: false, changes, parseError: null };
|
|
6851
7208
|
}
|
|
7209
|
+
function manifestApiVersion(value) {
|
|
7210
|
+
if (typeof value !== "object" || value === null)
|
|
7211
|
+
return null;
|
|
7212
|
+
const apiVersion = value.apiVersion;
|
|
7213
|
+
return typeof apiVersion === "string" ? apiVersion : null;
|
|
7214
|
+
}
|
|
7215
|
+
function isV2(apiVersion) {
|
|
7216
|
+
return apiVersion === "launchpad.m-kopa.us/v2";
|
|
7217
|
+
}
|
|
7218
|
+
function manifestSpec(value) {
|
|
7219
|
+
return typeof value === "object" && value !== null ? value.spec : undefined;
|
|
7220
|
+
}
|
|
6852
7221
|
function renderManifestDiffLines(diff, max = 20) {
|
|
6853
7222
|
const lines = [];
|
|
6854
7223
|
for (const c of diff.changes.slice(0, max)) {
|
|
@@ -7277,47 +7646,6 @@ function parseDeployFlags(args) {
|
|
|
7277
7646
|
import { existsSync as existsSync6, rmSync } from "node:fs";
|
|
7278
7647
|
import { resolve as resolvePath, join as join10 } from "node:path";
|
|
7279
7648
|
|
|
7280
|
-
// src/manifest/load.ts
|
|
7281
|
-
import { readFileSync as readFileSync10 } from "node:fs";
|
|
7282
|
-
import { parse as parseYaml5, YAMLParseError } from "yaml";
|
|
7283
|
-
function loadManifest(path8) {
|
|
7284
|
-
let raw;
|
|
7285
|
-
try {
|
|
7286
|
-
raw = readFileSync10(path8, "utf8");
|
|
7287
|
-
} catch (err) {
|
|
7288
|
-
const e = err;
|
|
7289
|
-
if (e.code === "ENOENT") {
|
|
7290
|
-
return { kind: "file-not-found", path: path8 };
|
|
7291
|
-
}
|
|
7292
|
-
return { kind: "read-error", path: path8, message: e.message ?? String(err) };
|
|
7293
|
-
}
|
|
7294
|
-
return parseManifest2(raw, path8);
|
|
7295
|
-
}
|
|
7296
|
-
function parseManifest2(yamlText, path8) {
|
|
7297
|
-
let parsed;
|
|
7298
|
-
try {
|
|
7299
|
-
parsed = parseYaml5(yamlText);
|
|
7300
|
-
} catch (err) {
|
|
7301
|
-
const message = err instanceof YAMLParseError ? err.message : err.message ?? String(err);
|
|
7302
|
-
return { kind: "yaml-parse-error", path: path8, message };
|
|
7303
|
-
}
|
|
7304
|
-
const result = ManifestSchema.safeParse(parsed);
|
|
7305
|
-
if (result.success) {
|
|
7306
|
-
return { kind: "ok", manifest: result.data };
|
|
7307
|
-
}
|
|
7308
|
-
return {
|
|
7309
|
-
kind: "schema-error",
|
|
7310
|
-
path: path8,
|
|
7311
|
-
issues: formatIssues(result.error)
|
|
7312
|
-
};
|
|
7313
|
-
}
|
|
7314
|
-
function formatIssues(error) {
|
|
7315
|
-
return error.issues.map((issue) => ({
|
|
7316
|
-
path: issue.path.map(String).join(".") || "(root)",
|
|
7317
|
-
message: issue.message
|
|
7318
|
-
}));
|
|
7319
|
-
}
|
|
7320
|
-
|
|
7321
7649
|
// src/status-page-output.ts
|
|
7322
7650
|
async function surfaceStatusPageUrl(url, io, options) {
|
|
7323
7651
|
if (typeof url !== "string" || url.length === 0)
|
|
@@ -7392,13 +7720,13 @@ async function runDeployApply(opts, io, deps = {}) {
|
|
|
7392
7720
|
return 0;
|
|
7393
7721
|
}
|
|
7394
7722
|
const manifestPath = resolvePath(process.cwd(), opts.file ?? "launchpad.yaml");
|
|
7395
|
-
const loaded =
|
|
7723
|
+
const loaded = loadManifestAnyVersion(manifestPath);
|
|
7396
7724
|
if (loaded.kind !== "ok") {
|
|
7397
7725
|
return renderManifestError(loaded, io);
|
|
7398
7726
|
}
|
|
7399
|
-
const slug = loaded
|
|
7727
|
+
const slug = manifestSlug(loaded);
|
|
7400
7728
|
if (!SLUG_REGEX2.test(slug)) {
|
|
7401
|
-
io.err(`launchpad deploy --apply: manifest's metadata.name '${slug}' is not a valid slug`);
|
|
7729
|
+
io.err(`launchpad deploy --apply: manifest's ${loaded.schema === "launchpad.m-kopa.us/v2" ? "metadata.slug" : "metadata.name"} '${slug}' is not a valid slug`);
|
|
7402
7730
|
return 1;
|
|
7403
7731
|
}
|
|
7404
7732
|
if (opts.atSha !== undefined && !MANIFEST_SHA_REGEX.test(opts.atSha)) {
|
|
@@ -7689,12 +8017,12 @@ function deletePinIfPresent(cfg, slug, io) {
|
|
|
7689
8017
|
}
|
|
7690
8018
|
async function loadSlugForResume(opts, io) {
|
|
7691
8019
|
const manifestPath = resolvePath(process.cwd(), opts.file ?? "launchpad.yaml");
|
|
7692
|
-
const loaded =
|
|
8020
|
+
const loaded = loadManifestAnyVersion(manifestPath);
|
|
7693
8021
|
if (loaded.kind !== "ok") {
|
|
7694
8022
|
renderManifestError(loaded, io);
|
|
7695
8023
|
return null;
|
|
7696
8024
|
}
|
|
7697
|
-
return loaded
|
|
8025
|
+
return manifestSlug(loaded);
|
|
7698
8026
|
}
|
|
7699
8027
|
async function defaultPrompt(question) {
|
|
7700
8028
|
const { createInterface } = await import("node:readline/promises");
|
|
@@ -7735,6 +8063,10 @@ function mapHttpError(e, slug, io) {
|
|
|
7735
8063
|
io.err(`launchpad deploy --apply failed: ${describe14(e)}`);
|
|
7736
8064
|
return 2;
|
|
7737
8065
|
}
|
|
8066
|
+
function manifestSlug(loaded) {
|
|
8067
|
+
const { metadata } = loaded.manifest;
|
|
8068
|
+
return "slug" in metadata ? metadata.slug : metadata.name;
|
|
8069
|
+
}
|
|
7738
8070
|
function renderManifestError(loaded, io) {
|
|
7739
8071
|
switch (loaded.kind) {
|
|
7740
8072
|
case "ok":
|
|
@@ -8630,6 +8962,10 @@ async function surfaceManifestDiff(cfg, slug, manifestPath, io) {
|
|
|
8630
8962
|
io.out(` config diff: skipped (${diff.parseError}).`);
|
|
8631
8963
|
return;
|
|
8632
8964
|
}
|
|
8965
|
+
if (diff.shapeMismatch !== undefined) {
|
|
8966
|
+
io.out(` config diff: manifest shapes differ (${diff.shapeMismatch}); drift was not evaluated.`);
|
|
8967
|
+
return;
|
|
8968
|
+
}
|
|
8633
8969
|
if (diff.inSync) {
|
|
8634
8970
|
io.out(" config: launchpad.yaml matches the deployed manifest (in sync).");
|
|
8635
8971
|
return;
|
|
@@ -8653,6 +8989,7 @@ async function runModelADeploy(args) {
|
|
|
8653
8989
|
const { cwd, manifestPath, io } = args;
|
|
8654
8990
|
let slug;
|
|
8655
8991
|
let appType = "static";
|
|
8992
|
+
let declaredOrigin;
|
|
8656
8993
|
try {
|
|
8657
8994
|
const manifestObj = parseYaml6(readFileSync11(manifestPath, "utf8"));
|
|
8658
8995
|
const metaSlug = resolveManifestSlug(manifestObj);
|
|
@@ -8662,13 +8999,22 @@ async function runModelADeploy(args) {
|
|
|
8662
8999
|
}
|
|
8663
9000
|
slug = metaSlug;
|
|
8664
9001
|
const raw = manifestObj;
|
|
8665
|
-
const
|
|
8666
|
-
|
|
9002
|
+
const isV22 = raw?.apiVersion === "launchpad.m-kopa.us/v2";
|
|
9003
|
+
declaredOrigin = declaredOriginOf(isV22, raw?.spec?.origin);
|
|
9004
|
+
const deploymentType = isV22 ? raw?.spec?.appType : raw?.deployment?.type;
|
|
8667
9005
|
if (deploymentType === "static" || deploymentType === "react" || deploymentType === "react+api" || deploymentType === "container") {
|
|
8668
9006
|
appType = deploymentType;
|
|
8669
9007
|
}
|
|
9008
|
+
if (appType !== undefined) {
|
|
9009
|
+
const auth = isV22 ? raw?.spec?.auth : raw?.auth;
|
|
9010
|
+
const issue = deploymentAuthIssue(appType, auth === "gateway" ? "gateway" : auth === "access" ? "access" : undefined);
|
|
9011
|
+
if (issue !== null) {
|
|
9012
|
+
io.err(`launchpad deploy: ${issue.message}`);
|
|
9013
|
+
return 1;
|
|
9014
|
+
}
|
|
9015
|
+
}
|
|
8670
9016
|
if (deploymentType === "container") {
|
|
8671
|
-
const parsed =
|
|
9017
|
+
const parsed = isV22 ? parseManifestV2(manifestObj) : parseManifest(manifestObj);
|
|
8672
9018
|
if (parsed.kind !== "ok") {
|
|
8673
9019
|
io.err("launchpad deploy: manifest is structurally invalid:");
|
|
8674
9020
|
for (const issue of parsed.issues) {
|
|
@@ -8720,7 +9066,8 @@ async function runModelADeploy(args) {
|
|
|
8720
9066
|
try {
|
|
8721
9067
|
await surfaceManifestDiff(cfg, slug, manifestPath, io);
|
|
8722
9068
|
} catch {}
|
|
8723
|
-
const
|
|
9069
|
+
const deployContext = await readDeploymentContext(realCommitWaitDeps(cfg), slug);
|
|
9070
|
+
const deployBaseline = noVerify ? null : deployContext.baseline;
|
|
8724
9071
|
let result;
|
|
8725
9072
|
try {
|
|
8726
9073
|
if (args.allowUnguarded) {
|
|
@@ -8850,7 +9197,11 @@ async function runModelADeploy(args) {
|
|
|
8850
9197
|
if (accessExit !== 0)
|
|
8851
9198
|
return accessExit;
|
|
8852
9199
|
io.out("");
|
|
8853
|
-
|
|
9200
|
+
const dispatchOrigin = handoffIsDispatch({
|
|
9201
|
+
declared: declaredOrigin,
|
|
9202
|
+
baseline: deployContext.origin
|
|
9203
|
+
});
|
|
9204
|
+
surfaceCommittedHandoff(dispatchOrigin, io, "ascii");
|
|
8854
9205
|
if (noVerify)
|
|
8855
9206
|
return 0;
|
|
8856
9207
|
return runDeployVerification({
|
|
@@ -8860,7 +9211,8 @@ async function runModelADeploy(args) {
|
|
|
8860
9211
|
shippedSha: outcome.manifest_sha,
|
|
8861
9212
|
baseline: deployBaseline,
|
|
8862
9213
|
registrationBoundMs,
|
|
8863
|
-
timeoutMs: verifyTimeoutMs
|
|
9214
|
+
timeoutMs: verifyTimeoutMs,
|
|
9215
|
+
dispatchOrigin
|
|
8864
9216
|
}, io, realVerifyDeployDeps(cfg));
|
|
8865
9217
|
}
|
|
8866
9218
|
io.err(`launchpad deploy: ${formatErrorEnvelope(body)}`);
|
|
@@ -8956,9 +9308,18 @@ async function runModelADeploy(args) {
|
|
|
8956
9308
|
}
|
|
8957
9309
|
surfaceDeployExtras(success, io, slug, args.allowStale);
|
|
8958
9310
|
io.out("");
|
|
8959
|
-
|
|
9311
|
+
const dispatchOrigin = handoffIsDispatch({
|
|
9312
|
+
v2Delivery: success.v2Delivery,
|
|
9313
|
+
declared: declaredOrigin,
|
|
9314
|
+
baseline: deployContext.origin
|
|
9315
|
+
});
|
|
9316
|
+
surfaceCommittedHandoff(dispatchOrigin, io);
|
|
8960
9317
|
if (noVerify) {
|
|
8961
|
-
|
|
9318
|
+
if (dispatchOrigin) {
|
|
9319
|
+
io.out(`Run \`launchpad status ${slug}\` to confirm the delivery stage and outcome.`);
|
|
9320
|
+
} else {
|
|
9321
|
+
io.out(`Run \`launchpad status ${slug}\` to confirm the build outcome (success / failure + log excerpt).`);
|
|
9322
|
+
}
|
|
8962
9323
|
return 0;
|
|
8963
9324
|
}
|
|
8964
9325
|
return runDeployVerification({
|
|
@@ -8968,11 +9329,33 @@ async function runModelADeploy(args) {
|
|
|
8968
9329
|
shippedSha: success.commit_sha,
|
|
8969
9330
|
baseline: deployBaseline,
|
|
8970
9331
|
registrationBoundMs,
|
|
8971
|
-
timeoutMs: verifyTimeoutMs
|
|
9332
|
+
timeoutMs: verifyTimeoutMs,
|
|
9333
|
+
dispatchOrigin
|
|
8972
9334
|
}, io, realVerifyDeployDeps(cfg));
|
|
8973
9335
|
}
|
|
8974
9336
|
}
|
|
8975
9337
|
}
|
|
9338
|
+
function surfaceCommittedHandoff(dispatchOrigin, io, pagesStyle = "unicode") {
|
|
9339
|
+
io.out(committedHandoffMessage(dispatchOrigin, pagesStyle));
|
|
9340
|
+
}
|
|
9341
|
+
function declaredOriginOf(isV22, origin) {
|
|
9342
|
+
if (!isV22)
|
|
9343
|
+
return "pages";
|
|
9344
|
+
return origin === "dispatch" ? "dispatch" : "pages";
|
|
9345
|
+
}
|
|
9346
|
+
function handoffIsDispatch(input) {
|
|
9347
|
+
if (input.v2Delivery === "pending" || input.v2Delivery === "deferred")
|
|
9348
|
+
return true;
|
|
9349
|
+
if (input.declared !== undefined)
|
|
9350
|
+
return input.declared === "dispatch";
|
|
9351
|
+
return input.baseline === "dispatch";
|
|
9352
|
+
}
|
|
9353
|
+
function committedHandoffMessage(dispatchOrigin, pagesStyle = "unicode") {
|
|
9354
|
+
if (dispatchOrigin) {
|
|
9355
|
+
return "Committed; your repo's build workflow is building this commit, then the bot delivers it to the dispatch namespace.";
|
|
9356
|
+
}
|
|
9357
|
+
return pagesStyle === "ascii" ? "Committed; build pending - Cloudflare Pages is building this deploy now." : "Committed; build pending — Cloudflare Pages is building this deploy now.";
|
|
9358
|
+
}
|
|
8976
9359
|
function pendingReconcileOutcome(body) {
|
|
8977
9360
|
const manifestSha = body.manifestSha;
|
|
8978
9361
|
const id = body.reconcileId;
|
|
@@ -10400,8 +10783,26 @@ async function runInit(args, io, prompt) {
|
|
|
10400
10783
|
io.err(`launchpad init: ${fillResult.message}`);
|
|
10401
10784
|
return 64;
|
|
10402
10785
|
}
|
|
10786
|
+
const authIssue = deploymentAuthIssue(fillResult.inputs.type, fillResult.inputs.auth ?? "gateway");
|
|
10787
|
+
if (authIssue !== null) {
|
|
10788
|
+
io.err(`launchpad init: ${authIssue.message}`);
|
|
10789
|
+
return 64;
|
|
10790
|
+
}
|
|
10791
|
+
if (fillResult.inputs.type === "container" && inputs.origin !== undefined) {
|
|
10792
|
+
io.err("launchpad init: --origin is not supported for containers");
|
|
10793
|
+
return 64;
|
|
10794
|
+
}
|
|
10795
|
+
const dispatch = newAppOrigin(fillResult.inputs.type, inputs.origin) === "dispatch";
|
|
10796
|
+
if (dispatch && fillResult.inputs.type === "react+api") {
|
|
10797
|
+
io.err("launchpad init: API starter dispatch is not available yet; use --origin pages");
|
|
10798
|
+
return 64;
|
|
10799
|
+
}
|
|
10800
|
+
if (dispatch && (inputs.hostnames.length > 0 || inputs.sessionDuration !== undefined)) {
|
|
10801
|
+
io.err("launchpad init: dispatch does not support --hostname or --session-duration; use --origin pages for a Pages manifest");
|
|
10802
|
+
return 64;
|
|
10803
|
+
}
|
|
10403
10804
|
const manifest = buildManifest(fillResult.inputs, detected);
|
|
10404
|
-
const validation = ManifestSchema.safeParse(manifest);
|
|
10805
|
+
const validation = manifest.apiVersion === "launchpad.m-kopa.us/v2" ? ManifestV2Schema.safeParse(manifest) : ManifestSchema.safeParse(manifest);
|
|
10405
10806
|
if (!validation.success) {
|
|
10406
10807
|
io.err("launchpad init: invalid manifest input");
|
|
10407
10808
|
for (const issue of validation.error.issues) {
|
|
@@ -10454,8 +10855,11 @@ async function runInit(args, io, prompt) {
|
|
|
10454
10855
|
io.out("");
|
|
10455
10856
|
io.out("Next steps:");
|
|
10456
10857
|
io.out(" launchpad validate # confirm the manifest");
|
|
10457
|
-
|
|
10458
|
-
|
|
10858
|
+
if (!dispatch)
|
|
10859
|
+
io.out(" launchpad secrets template # scaffold .env.example");
|
|
10860
|
+
io.out(" launchpad deploy # build and deploy the app");
|
|
10861
|
+
if (dispatch)
|
|
10862
|
+
io.out(" Dispatch currently supports plain-text environment variables, but not secret bindings.");
|
|
10459
10863
|
return 0;
|
|
10460
10864
|
}
|
|
10461
10865
|
function parseArgs6(args) {
|
|
@@ -10466,6 +10870,7 @@ function parseArgs6(args) {
|
|
|
10466
10870
|
description: undefined,
|
|
10467
10871
|
type: undefined,
|
|
10468
10872
|
auth: undefined,
|
|
10873
|
+
origin: undefined,
|
|
10469
10874
|
groups: [],
|
|
10470
10875
|
sessionDuration: undefined,
|
|
10471
10876
|
hostnames: [],
|
|
@@ -10550,6 +10955,16 @@ function parseArgs6(args) {
|
|
|
10550
10955
|
inputs.type = got;
|
|
10551
10956
|
continue;
|
|
10552
10957
|
}
|
|
10958
|
+
if (a === "--origin") {
|
|
10959
|
+
const got = needsValue();
|
|
10960
|
+
if (typeof got !== "string")
|
|
10961
|
+
return got;
|
|
10962
|
+
if (got !== "pages" && got !== "dispatch") {
|
|
10963
|
+
return { kind: "usage-error", message: "--origin must be pages or dispatch" };
|
|
10964
|
+
}
|
|
10965
|
+
inputs.origin = got;
|
|
10966
|
+
continue;
|
|
10967
|
+
}
|
|
10553
10968
|
if (a === "--auth") {
|
|
10554
10969
|
const got = needsValue();
|
|
10555
10970
|
if (typeof got !== "string")
|
|
@@ -10744,6 +11159,7 @@ async function fillRequired(inputs, options, io, prompt) {
|
|
|
10744
11159
|
description: inputs.description,
|
|
10745
11160
|
type: typeR,
|
|
10746
11161
|
auth: inputs.auth,
|
|
11162
|
+
origin: inputs.origin,
|
|
10747
11163
|
groups: groupsR,
|
|
10748
11164
|
sessionDuration: inputs.sessionDuration,
|
|
10749
11165
|
hostnames: inputs.hostnames,
|
|
@@ -10803,6 +11219,29 @@ function buildManifest(inputs, detected) {
|
|
|
10803
11219
|
root: ".",
|
|
10804
11220
|
include: [...defaultIncludeForAppType(inputs.type)]
|
|
10805
11221
|
};
|
|
11222
|
+
if (newAppOrigin(inputs.type, inputs.origin) === "dispatch") {
|
|
11223
|
+
return {
|
|
11224
|
+
apiVersion: "launchpad.m-kopa.us/v2",
|
|
11225
|
+
kind: "App",
|
|
11226
|
+
metadata: { slug: inputs.name, schemaVersion: 2 },
|
|
11227
|
+
spec: {
|
|
11228
|
+
appType: inputs.type,
|
|
11229
|
+
origin: "dispatch",
|
|
11230
|
+
auth: resolvedAuth,
|
|
11231
|
+
display: {
|
|
11232
|
+
name: inputs.name,
|
|
11233
|
+
team: inputs.team,
|
|
11234
|
+
...inputs.description ? { description: inputs.description } : {}
|
|
11235
|
+
},
|
|
11236
|
+
owner: { email: inputs.owner },
|
|
11237
|
+
access: { allowed_entra_groups: [...inputs.groups] },
|
|
11238
|
+
app: manifest.app,
|
|
11239
|
+
...manifest.build === undefined ? {} : { build: manifest.build }
|
|
11240
|
+
}
|
|
11241
|
+
};
|
|
11242
|
+
}
|
|
11243
|
+
if (inputs.origin === "pages")
|
|
11244
|
+
manifest.origin = "pages";
|
|
10806
11245
|
return manifest;
|
|
10807
11246
|
}
|
|
10808
11247
|
function starterIndexHtml(slug) {
|
|
@@ -13242,11 +13681,15 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
13242
13681
|
const cfg = deps.config ?? loadConfig();
|
|
13243
13682
|
const fetcher = deps.fetcher ?? fetch;
|
|
13244
13683
|
const manifestPath = resolvePath4(process.cwd(), opts.file ?? "launchpad.yaml");
|
|
13245
|
-
const loaded =
|
|
13684
|
+
const loaded = loadManifestAnyVersion(manifestPath);
|
|
13246
13685
|
if (loaded.kind !== "ok") {
|
|
13247
13686
|
return renderManifestError4(loaded, io);
|
|
13248
13687
|
}
|
|
13249
|
-
const
|
|
13688
|
+
const v2Refusal = refuseV2Manifest(loaded, "launchpad secrets push", io);
|
|
13689
|
+
if (v2Refusal !== null)
|
|
13690
|
+
return v2Refusal;
|
|
13691
|
+
const manifest = loaded.manifest;
|
|
13692
|
+
const bindings = manifest.secrets?.bindings ?? [];
|
|
13250
13693
|
const envFileBindings = bindings.filter((b) => b.source === "env-file");
|
|
13251
13694
|
if (envFileBindings.length === 0) {
|
|
13252
13695
|
io.out("No `env-file` secret bindings declared in the manifest.");
|
|
@@ -13328,7 +13771,7 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
13328
13771
|
io.out("Re-run without --dry-run to push.");
|
|
13329
13772
|
return 0;
|
|
13330
13773
|
}
|
|
13331
|
-
const slug =
|
|
13774
|
+
const slug = manifest.metadata.name;
|
|
13332
13775
|
let pushed = 0;
|
|
13333
13776
|
let partial = 0;
|
|
13334
13777
|
let anyPagesUpdated = false;
|
|
@@ -13453,6 +13896,20 @@ function mapPushError(e, slug, name, io) {
|
|
|
13453
13896
|
function describe30(e) {
|
|
13454
13897
|
return e instanceof Error ? e.message : String(e);
|
|
13455
13898
|
}
|
|
13899
|
+
function refuseV2Manifest(loaded, command, io) {
|
|
13900
|
+
if (loaded.schema !== "launchpad.m-kopa.us/v2")
|
|
13901
|
+
return null;
|
|
13902
|
+
const origin = loaded.manifest.spec?.origin;
|
|
13903
|
+
if (origin === "dispatch") {
|
|
13904
|
+
io.err(`${command}: this is a dispatch-origin (v2) app.`);
|
|
13905
|
+
const outcome = command.endsWith("push") ? "nothing was pushed" : "nothing was read";
|
|
13906
|
+
io.err(` Secrets are not delivered to the dispatch namespace yet (PS-2740); ${outcome}.`);
|
|
13907
|
+
return 1;
|
|
13908
|
+
}
|
|
13909
|
+
io.err(`${command}: this is a launchpad.m-kopa.us/v2 manifest.`);
|
|
13910
|
+
io.err(" This command reads v1alpha1 `secrets.bindings`; v2 declares secrets as `spec.env_vars` `secret_text` entries, which it does not push yet.");
|
|
13911
|
+
return 1;
|
|
13912
|
+
}
|
|
13456
13913
|
function renderManifestError4(result, io) {
|
|
13457
13914
|
switch (result.kind) {
|
|
13458
13915
|
case "ok":
|
|
@@ -13490,16 +13947,20 @@ async function runSecretsStatus(opts, io, deps = {}) {
|
|
|
13490
13947
|
const cfg = deps.config ?? loadConfig();
|
|
13491
13948
|
const fetcher = deps.fetcher ?? fetch;
|
|
13492
13949
|
const manifestPath = resolvePath5(process.cwd(), opts.file ?? "launchpad.yaml");
|
|
13493
|
-
const loaded =
|
|
13950
|
+
const loaded = loadManifestAnyVersion(manifestPath);
|
|
13494
13951
|
if (loaded.kind !== "ok")
|
|
13495
13952
|
return renderManifestError5(loaded, io);
|
|
13496
|
-
const
|
|
13953
|
+
const v2Refusal = refuseV2Manifest(loaded, "launchpad secrets status", io);
|
|
13954
|
+
if (v2Refusal !== null)
|
|
13955
|
+
return v2Refusal;
|
|
13956
|
+
const manifest = loaded.manifest;
|
|
13957
|
+
const slug = manifest.metadata.name;
|
|
13497
13958
|
if (opts.slug != null && opts.slug !== slug) {
|
|
13498
13959
|
io.err(`launchpad secrets status: slug "${opts.slug}" does not match this directory's ` + `manifest (metadata.name: "${slug}").`);
|
|
13499
13960
|
io.err(" secrets status audits the manifest in the current directory — omit the slug, " + "or run from the app's directory.");
|
|
13500
13961
|
return 2;
|
|
13501
13962
|
}
|
|
13502
|
-
const bindings =
|
|
13963
|
+
const bindings = manifest.secrets?.bindings ?? [];
|
|
13503
13964
|
if (bindings.length === 0) {
|
|
13504
13965
|
io.out("No secret bindings declared in the manifest.");
|
|
13505
13966
|
return 0;
|
|
@@ -14867,7 +15328,7 @@ import { existsSync as existsSync16, readFileSync as readFileSync21, readdirSync
|
|
|
14867
15328
|
import { dirname as dirname12, resolve as resolve17 } from "node:path";
|
|
14868
15329
|
var validateCommand = {
|
|
14869
15330
|
name: "validate",
|
|
14870
|
-
summary: "validate launchpad.yaml against
|
|
15331
|
+
summary: "validate launchpad.yaml against its v1alpha1 or v2 schema",
|
|
14871
15332
|
run: runValidate
|
|
14872
15333
|
};
|
|
14873
15334
|
async function runValidate(args, io) {
|
|
@@ -14879,7 +15340,7 @@ async function runValidate(args, io) {
|
|
|
14879
15340
|
}
|
|
14880
15341
|
const { file, json, strictGroups } = parseResult;
|
|
14881
15342
|
const path14 = resolve17(process.cwd(), file ?? "launchpad.yaml");
|
|
14882
|
-
const result =
|
|
15343
|
+
const result = loadManifestAnyVersion(path14);
|
|
14883
15344
|
if (result.kind !== "ok") {
|
|
14884
15345
|
return json ? renderJsonError(result, io) : renderHumanError(result, io);
|
|
14885
15346
|
}
|
|
@@ -14889,16 +15350,17 @@ async function runValidate(args, io) {
|
|
|
14889
15350
|
manifest: result.manifest,
|
|
14890
15351
|
io,
|
|
14891
15352
|
json,
|
|
14892
|
-
structurallyValidName: result.manifest.
|
|
15353
|
+
structurallyValidName: manifestView(result.manifest).name
|
|
14893
15354
|
});
|
|
14894
15355
|
if (unsupported2 !== null)
|
|
14895
15356
|
return unsupported2;
|
|
14896
|
-
const
|
|
15357
|
+
const view = manifestView(result.manifest);
|
|
15358
|
+
const boundary = checkBoundary(path14, view.appDeclared);
|
|
14897
15359
|
const serveErrors = checkStaticServeDir(path14, result.manifest);
|
|
14898
15360
|
if (serveErrors.length > 0) {
|
|
14899
15361
|
boundary.errors = [...boundary.errors, ...serveErrors];
|
|
14900
15362
|
}
|
|
14901
|
-
const groupCheck = strictGroups ? await checkGroups(allowedEntraGroups(
|
|
15363
|
+
const groupCheck = strictGroups ? await checkGroups(allowedEntraGroups(view.access)) : { kind: "skipped" };
|
|
14902
15364
|
return json ? renderJsonOk(result, groupCheck, boundary, io) : renderHumanOk(result, groupCheck, boundary, io);
|
|
14903
15365
|
}
|
|
14904
15366
|
function checkBoundary(manifestPath, declared) {
|
|
@@ -14922,11 +15384,12 @@ function checkBoundary(manifestPath, declared) {
|
|
|
14922
15384
|
return { declared, errors: [], warnings: r.warnings };
|
|
14923
15385
|
}
|
|
14924
15386
|
function checkStaticServeDir(manifestPath, manifest) {
|
|
14925
|
-
|
|
15387
|
+
const view = manifestView(manifest);
|
|
15388
|
+
if (view.deploymentType !== "static")
|
|
14926
15389
|
return [];
|
|
14927
|
-
if (
|
|
15390
|
+
if (view.buildCommand !== undefined)
|
|
14928
15391
|
return [];
|
|
14929
|
-
const serveDir =
|
|
15392
|
+
const serveDir = view.destinationDir ?? STATIC_SERVE_DIR;
|
|
14930
15393
|
const normalized = serveDir.replace(/\\/g, "/");
|
|
14931
15394
|
const segments = normalized.split("/");
|
|
14932
15395
|
if (normalized === "" || normalized === "." || normalized === "./" || normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized) || segments.some((s) => s === "..")) {
|
|
@@ -15026,9 +15489,10 @@ async function checkGroups(groups) {
|
|
|
15026
15489
|
return ok ? { kind: "all-ok", perGroup } : { kind: "some-failed", perGroup };
|
|
15027
15490
|
}
|
|
15028
15491
|
function renderHumanOk(result, group, boundary, io) {
|
|
15029
|
-
|
|
15030
|
-
io.out(
|
|
15031
|
-
io.out(`
|
|
15492
|
+
const view = manifestView(result.manifest);
|
|
15493
|
+
io.out(`✓ ${view.name} — manifest is valid`);
|
|
15494
|
+
io.out(` apiVersion: ${result.schema}`);
|
|
15495
|
+
io.out(` type: ${view.deploymentType}`);
|
|
15032
15496
|
if (boundary.errors.length > 0) {
|
|
15033
15497
|
for (const e of boundary.errors) {
|
|
15034
15498
|
io.err(`✗ app boundary: ${e}`);
|
|
@@ -15097,6 +15561,7 @@ function renderHumanError(result, io) {
|
|
|
15097
15561
|
return 1;
|
|
15098
15562
|
case "schema-error":
|
|
15099
15563
|
io.err(`✗ ${result.path}`);
|
|
15564
|
+
io.err(` validated against schema: ${result.schema}`);
|
|
15100
15565
|
io.err(` ${result.issues.length} schema issue${result.issues.length === 1 ? "" : "s"}:`);
|
|
15101
15566
|
for (const issue of result.issues) {
|
|
15102
15567
|
io.err(` ${issue.path}: ${issue.message}`);
|
|
@@ -15105,11 +15570,12 @@ function renderHumanError(result, io) {
|
|
|
15105
15570
|
}
|
|
15106
15571
|
}
|
|
15107
15572
|
function renderJsonOk(result, group, boundary, io) {
|
|
15573
|
+
const view = manifestView(result.manifest);
|
|
15108
15574
|
const base = {
|
|
15109
15575
|
manifest_valid: true,
|
|
15110
15576
|
runtime_supported: true,
|
|
15111
|
-
name:
|
|
15112
|
-
deploymentType:
|
|
15577
|
+
name: view.name,
|
|
15578
|
+
deploymentType: view.deploymentType,
|
|
15113
15579
|
appBoundary: {
|
|
15114
15580
|
declared: boundary.declared,
|
|
15115
15581
|
errors: boundary.errors,
|
|
@@ -15189,11 +15655,32 @@ function renderJsonError(result, io) {
|
|
|
15189
15655
|
ok: false,
|
|
15190
15656
|
kind: "schema-error",
|
|
15191
15657
|
path: result.path,
|
|
15658
|
+
schema: result.schema,
|
|
15192
15659
|
issues: result.issues
|
|
15193
15660
|
}));
|
|
15194
15661
|
return 1;
|
|
15195
15662
|
}
|
|
15196
15663
|
}
|
|
15664
|
+
function manifestView(manifest) {
|
|
15665
|
+
if (manifest.apiVersion === "launchpad.m-kopa.us/v2") {
|
|
15666
|
+
return {
|
|
15667
|
+
name: manifest.metadata.slug,
|
|
15668
|
+
deploymentType: manifest.spec.appType,
|
|
15669
|
+
access: manifest.spec.access,
|
|
15670
|
+
buildCommand: manifest.spec.build?.command,
|
|
15671
|
+
destinationDir: manifest.spec.build?.destination_dir,
|
|
15672
|
+
appDeclared: manifest.spec.app !== undefined
|
|
15673
|
+
};
|
|
15674
|
+
}
|
|
15675
|
+
return {
|
|
15676
|
+
name: manifest.metadata.name,
|
|
15677
|
+
deploymentType: manifest.deployment.type,
|
|
15678
|
+
access: manifest.access,
|
|
15679
|
+
buildCommand: manifest.build?.command,
|
|
15680
|
+
destinationDir: manifest.build?.destination_dir,
|
|
15681
|
+
appDeclared: manifest.app !== undefined
|
|
15682
|
+
};
|
|
15683
|
+
}
|
|
15197
15684
|
function parseArgs13(args) {
|
|
15198
15685
|
let file;
|
|
15199
15686
|
let json = false;
|