@m-kopa/launchpad-cli 0.62.0 → 0.64.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 +50 -0
- package/dist/bundle/upload.d.ts +2 -0
- package/dist/bundle/upload.d.ts.map +1 -1
- package/dist/cli.js +684 -299
- package/dist/commands/deploy.d.ts +10 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/migrate.d.ts +30 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/secrets-template.d.ts.map +1 -1
- package/dist/manifest/migrate-dispatch.d.ts +13 -3
- package/dist/manifest/migrate-dispatch.d.ts.map +1 -1
- package/dist/manifest/wrangler-config.d.ts +3 -0
- package/dist/manifest/wrangler-config.d.ts.map +1 -0
- package/dist/secrets/declared.d.ts +12 -0
- package/dist/secrets/declared.d.ts.map +1 -0
- package/dist/secrets/push.d.ts +0 -8
- 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 +6 -5
- package/skills/launchpad-deploy/SKILL.md +41 -17
- 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 +5 -4
- package/skills/launchpad-report/SKILL.md +1 -1
- package/skills/launchpad-status/SKILL.md +1 -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.64.0";
|
|
23
23
|
|
|
24
24
|
// src/config.ts
|
|
25
25
|
import { readFileSync } from "node:fs";
|
|
@@ -1888,8 +1888,8 @@ function describe10(e) {
|
|
|
1888
1888
|
}
|
|
1889
1889
|
|
|
1890
1890
|
// src/commands/deploy.ts
|
|
1891
|
-
import { existsSync as
|
|
1892
|
-
import * as
|
|
1891
|
+
import { existsSync as existsSync8 } from "node:fs";
|
|
1892
|
+
import * as path9 from "node:path";
|
|
1893
1893
|
|
|
1894
1894
|
// src/bundle/orchestrate.ts
|
|
1895
1895
|
import { readFileSync as readFileSync5 } from "node:fs";
|
|
@@ -2206,8 +2206,14 @@ function deployBundleError(envelope, body) {
|
|
|
2206
2206
|
...typeof body.head_sha === "string" ? { head_sha: body.head_sha } : {},
|
|
2207
2207
|
...paths === undefined ? {} : { paths },
|
|
2208
2208
|
...typeof body.reason === "string" ? { reason: body.reason } : {},
|
|
2209
|
+
...readDeployGuidance(body)
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
2212
|
+
function readDeployGuidance(body) {
|
|
2213
|
+
return {
|
|
2209
2214
|
...typeof body.minimum === "string" ? { minimum: body.minimum } : {},
|
|
2210
2215
|
...typeof body.yours === "string" ? { yours: body.yours } : {},
|
|
2216
|
+
...typeof body.requires_admin_approval === "boolean" ? { requires_admin_approval: body.requires_admin_approval } : {},
|
|
2211
2217
|
...typeof body.statusPageUrl === "string" ? { statusPageUrl: body.statusPageUrl } : {}
|
|
2212
2218
|
};
|
|
2213
2219
|
}
|
|
@@ -3357,7 +3363,10 @@ var SpecSchema = z3.object({
|
|
|
3357
3363
|
env_vars: z3.record(z3.string().regex(ENV_NAME_REGEX, "env_vars keys must be UPPER_SNAKE_CASE"), EnvVarSchema).optional(),
|
|
3358
3364
|
containers: z3.array(ContainerSchema).min(1).optional(),
|
|
3359
3365
|
secrets: SecretsSchema.optional(),
|
|
3360
|
-
fronted_by_concierge: z3.boolean().optional()
|
|
3366
|
+
fronted_by_concierge: z3.boolean().optional(),
|
|
3367
|
+
bindings: z3.object({
|
|
3368
|
+
d1: z3.string().min(1).max(64).regex(ENV_NAME_REGEX, "bindings.d1 must be an UPPER_SNAKE_CASE binding name").optional()
|
|
3369
|
+
}).strict().optional()
|
|
3361
3370
|
}).strict().superRefine((s, ctx) => {
|
|
3362
3371
|
if (s.access.identity_types !== undefined && (s.auth ?? "access") !== "gateway") {
|
|
3363
3372
|
ctx.addIssue({
|
|
@@ -3426,14 +3435,27 @@ var SpecSchema = z3.object({
|
|
|
3426
3435
|
message: "confine_origin has no meaning with origin: dispatch — there is no public pages.dev apex to confine. Remove it."
|
|
3427
3436
|
});
|
|
3428
3437
|
}
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
}
|
|
3438
|
+
}
|
|
3439
|
+
if (s.bindings?.d1 !== undefined) {
|
|
3440
|
+
if (s.origin !== "dispatch") {
|
|
3441
|
+
ctx.addIssue({
|
|
3442
|
+
code: z3.ZodIssueCode.custom,
|
|
3443
|
+
path: ["bindings", "d1"],
|
|
3444
|
+
message: "bindings.d1 requires origin: dispatch. A pages-origin app declares its database with a v1 pages target d1_binding."
|
|
3445
|
+
});
|
|
3446
|
+
} else if (s.appType !== "react+api") {
|
|
3447
|
+
ctx.addIssue({
|
|
3448
|
+
code: z3.ZodIssueCode.custom,
|
|
3449
|
+
path: ["bindings", "d1"],
|
|
3450
|
+
message: `bindings.d1 requires appType 'react+api' (current appType: '${s.appType}'); only a react+api app ships the Worker code that can read a database.`
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
3453
|
+
if (s.env_vars?.[s.bindings.d1] !== undefined) {
|
|
3454
|
+
ctx.addIssue({
|
|
3455
|
+
code: z3.ZodIssueCode.custom,
|
|
3456
|
+
path: ["env_vars", s.bindings.d1],
|
|
3457
|
+
message: `env var '${s.bindings.d1}' has the same name as bindings.d1. Rename one of them.`
|
|
3458
|
+
});
|
|
3437
3459
|
}
|
|
3438
3460
|
}
|
|
3439
3461
|
if (!isContainer && s.containers !== undefined) {
|
|
@@ -4443,7 +4465,7 @@ function deploymentAuthIssue(appType, auth) {
|
|
|
4443
4465
|
function newAppOrigin(appType, requested) {
|
|
4444
4466
|
if (appType === "container")
|
|
4445
4467
|
return;
|
|
4446
|
-
return requested ??
|
|
4468
|
+
return requested ?? "dispatch";
|
|
4447
4469
|
}
|
|
4448
4470
|
// src/bundle/boundary.ts
|
|
4449
4471
|
var MAX_CONTENT_PROBE_BYTES = 1024 * 1024;
|
|
@@ -7005,7 +7027,7 @@ async function verifyFirstDeployServed(args, liveUrl, io, deps) {
|
|
|
7005
7027
|
|
|
7006
7028
|
// src/commands/deploy.ts
|
|
7007
7029
|
import { parse as parseYaml6 } from "yaml";
|
|
7008
|
-
import { readFileSync as
|
|
7030
|
+
import { readFileSync as readFileSync12 } from "node:fs";
|
|
7009
7031
|
|
|
7010
7032
|
// src/deploy/git-files.ts
|
|
7011
7033
|
import { spawn as spawn3 } from "node:child_process";
|
|
@@ -8613,6 +8635,494 @@ function rejectUnsupportedCliRuntime(args) {
|
|
|
8613
8635
|
return 1;
|
|
8614
8636
|
}
|
|
8615
8637
|
|
|
8638
|
+
// src/commands/migrate.ts
|
|
8639
|
+
import { closeSync, existsSync as existsSync7, lstatSync as lstatSync4, mkdirSync as mkdirSync2, mkdtempSync, openSync, readdirSync as readdirSync2, readFileSync as readFileSync11, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
8640
|
+
import path8 from "node:path";
|
|
8641
|
+
import { stringify } from "yaml";
|
|
8642
|
+
|
|
8643
|
+
// src/manifest/migrate-dispatch.ts
|
|
8644
|
+
var PLATFORM_ENV_NAMES = new Set([
|
|
8645
|
+
"GATEWAY_ISSUER",
|
|
8646
|
+
"GATEWAY_AUD",
|
|
8647
|
+
"GATEWAY_JWKS_URL",
|
|
8648
|
+
"CF_ACCESS_TEAM_DOMAIN",
|
|
8649
|
+
"CF_ACCESS_AUD",
|
|
8650
|
+
"JWKS_URL"
|
|
8651
|
+
]);
|
|
8652
|
+
function dispatchManifest(manifest, wranglerD1) {
|
|
8653
|
+
const v2 = manifest.apiVersion === "launchpad.m-kopa.us/v2";
|
|
8654
|
+
const source = v2 ? manifest.spec : manifest;
|
|
8655
|
+
const appType = v2 ? manifest.spec.appType : manifest.deployment.type;
|
|
8656
|
+
if (appType === "container" || source.containers !== undefined) {
|
|
8657
|
+
throw new Error("Containers use their own runtime and cannot convert to dispatch.");
|
|
8658
|
+
}
|
|
8659
|
+
if (!v2)
|
|
8660
|
+
assertConvertibleV1(manifest);
|
|
8661
|
+
const d1 = d1Binding(v2 ? manifest.spec.bindings?.d1 : pagesTargetD1(manifest), wranglerD1);
|
|
8662
|
+
const plainEnv = v2 ? manifest.spec.env_vars : plainTextEnv(manifest.production_env);
|
|
8663
|
+
const envVars = { ...plainEnv, ...secretEnv(source.secrets, plainEnv) };
|
|
8664
|
+
const spec = v2 ? { ...manifest.spec, secrets: undefined } : {
|
|
8665
|
+
appType,
|
|
8666
|
+
display: {
|
|
8667
|
+
name: manifest.metadata.name,
|
|
8668
|
+
team: manifest.metadata.team,
|
|
8669
|
+
...manifest.metadata.description === undefined ? {} : { description: manifest.metadata.description }
|
|
8670
|
+
},
|
|
8671
|
+
owner: { email: manifest.metadata.owner },
|
|
8672
|
+
access: {
|
|
8673
|
+
audience: manifest.access.audience,
|
|
8674
|
+
private_owner: manifest.access.private_owner,
|
|
8675
|
+
allowed_entra_groups: [...allowedEntraGroups(manifest.access)],
|
|
8676
|
+
identity_types: manifest.access.identity_types
|
|
8677
|
+
},
|
|
8678
|
+
build: manifest.build,
|
|
8679
|
+
app: manifest.app,
|
|
8680
|
+
verify: manifest.verify,
|
|
8681
|
+
sensitive: manifest.sensitive,
|
|
8682
|
+
catalogue: manifest.catalogue
|
|
8683
|
+
};
|
|
8684
|
+
const candidate = {
|
|
8685
|
+
apiVersion: "launchpad.m-kopa.us/v2",
|
|
8686
|
+
kind: "App",
|
|
8687
|
+
metadata: v2 ? manifest.metadata : { slug: manifest.metadata.name, schemaVersion: 2 },
|
|
8688
|
+
spec: {
|
|
8689
|
+
...spec,
|
|
8690
|
+
...d1 === undefined ? {} : { bindings: { d1 } },
|
|
8691
|
+
env_vars: Object.keys(envVars).length === 0 ? undefined : envVars,
|
|
8692
|
+
origin: "dispatch",
|
|
8693
|
+
auth: "gateway",
|
|
8694
|
+
confine_origin: undefined
|
|
8695
|
+
}
|
|
8696
|
+
};
|
|
8697
|
+
const parsed = parseManifestV2(candidate);
|
|
8698
|
+
if (parsed.kind !== "ok") {
|
|
8699
|
+
throw new Error(`Cannot convert manifest: ${parsed.issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`);
|
|
8700
|
+
}
|
|
8701
|
+
return parsed.manifest;
|
|
8702
|
+
}
|
|
8703
|
+
function assertConvertibleV1(manifest) {
|
|
8704
|
+
const unsupported2 = [
|
|
8705
|
+
manifest.hostnames !== undefined && "hostnames",
|
|
8706
|
+
manifest.targets !== undefined && pagesTargetD1(manifest) === undefined && "targets",
|
|
8707
|
+
manifest.access.session_duration !== undefined && "access.session_duration",
|
|
8708
|
+
manifest.deployment.runtime !== undefined && "deployment.runtime"
|
|
8709
|
+
].filter(Boolean);
|
|
8710
|
+
if (unsupported2.length > 0) {
|
|
8711
|
+
throw new Error(`Cannot preserve ${unsupported2.join(", ")} on dispatch. Review these settings with the platform team before migrating.`);
|
|
8712
|
+
}
|
|
8713
|
+
}
|
|
8714
|
+
function d1Binding(declared, wranglerD1) {
|
|
8715
|
+
if (declared !== undefined && wranglerD1 !== undefined && declared !== wranglerD1) {
|
|
8716
|
+
throw new Error(`The manifest binds D1 as ${declared} but the wrangler config binds ${wranglerD1}. Use one name for both.`);
|
|
8717
|
+
}
|
|
8718
|
+
return declared ?? wranglerD1;
|
|
8719
|
+
}
|
|
8720
|
+
function plainTextEnv(env) {
|
|
8721
|
+
if (env === undefined)
|
|
8722
|
+
return;
|
|
8723
|
+
return Object.fromEntries(Object.entries(env).map(([name, value]) => [name, { type: "plain_text", value }]));
|
|
8724
|
+
}
|
|
8725
|
+
function secretEnv(secrets, env) {
|
|
8726
|
+
const out = {};
|
|
8727
|
+
for (const binding of secrets?.bindings ?? []) {
|
|
8728
|
+
const refusal = secretRefusal(binding, env);
|
|
8729
|
+
if (refusal !== undefined)
|
|
8730
|
+
throw new Error(`Secret ${binding.name} ${refusal}`);
|
|
8731
|
+
out[binding.name] = { type: "secret_text", secret_ref: binding.name };
|
|
8732
|
+
}
|
|
8733
|
+
return out;
|
|
8734
|
+
}
|
|
8735
|
+
function secretRefusal(binding, env) {
|
|
8736
|
+
const targets = binding.targets === undefined || binding.targets === "all" ? [] : binding.targets;
|
|
8737
|
+
const checks = [
|
|
8738
|
+
[binding.source !== "env-file", "is platform-managed and cannot be converted automatically. Ask the platform team to migrate it."],
|
|
8739
|
+
[targets.some((target) => target !== "primary"), "targets another surface, which dispatch does not have."],
|
|
8740
|
+
[PLATFORM_ENV_NAMES.has(binding.name), "is set by the platform on dispatch apps. Rename it before converting."],
|
|
8741
|
+
[env?.[binding.name] !== undefined, "is also declared as an environment variable. Keep one."]
|
|
8742
|
+
];
|
|
8743
|
+
return checks.find(([refused]) => refused)?.[1];
|
|
8744
|
+
}
|
|
8745
|
+
function pagesTargetD1(manifest) {
|
|
8746
|
+
const [target, ...rest] = manifest.targets ?? [];
|
|
8747
|
+
if (rest.length > 0 || target?.kind !== "pages")
|
|
8748
|
+
return;
|
|
8749
|
+
return target.d1_binding;
|
|
8750
|
+
}
|
|
8751
|
+
|
|
8752
|
+
// src/manifest/wrangler-config.ts
|
|
8753
|
+
var ALLOWED_KEYS = new Set([
|
|
8754
|
+
"name",
|
|
8755
|
+
"compatibility_date",
|
|
8756
|
+
"compatibility_flags",
|
|
8757
|
+
"pages_build_output_dir",
|
|
8758
|
+
"d1_databases"
|
|
8759
|
+
]);
|
|
8760
|
+
var PLATFORM_FLAGS = new Set(["nodejs_compat"]);
|
|
8761
|
+
|
|
8762
|
+
class UnsupportedWranglerConfigError extends Error {
|
|
8763
|
+
}
|
|
8764
|
+
function wranglerD1Binding(file, text) {
|
|
8765
|
+
const toml = file.endsWith(".toml");
|
|
8766
|
+
const config = toml ? readToml(file, text) : readJsonc(file, text);
|
|
8767
|
+
for (const key of Object.keys(config)) {
|
|
8768
|
+
if (!ALLOWED_KEYS.has(key) && (toml || key !== "$schema"))
|
|
8769
|
+
throw unsupportedKey(file, key);
|
|
8770
|
+
}
|
|
8771
|
+
if (config.compatibility_flags !== undefined) {
|
|
8772
|
+
const extra = asArray(file, "compatibility_flags", config.compatibility_flags).find((flag) => !PLATFORM_FLAGS.has(String(flag)));
|
|
8773
|
+
if (extra !== undefined) {
|
|
8774
|
+
throw new UnsupportedWranglerConfigError(`${file} sets compatibility flag \`${String(extra)}\`; dispatch runs with nodejs_compat only`);
|
|
8775
|
+
}
|
|
8776
|
+
}
|
|
8777
|
+
if (config.d1_databases === undefined)
|
|
8778
|
+
return;
|
|
8779
|
+
const databases = asArray(file, "d1_databases", config.d1_databases);
|
|
8780
|
+
if (databases.length !== 1) {
|
|
8781
|
+
throw new UnsupportedWranglerConfigError(`${file} declares ${databases.length} \`d1_databases\` entries; dispatch binds exactly one D1 database`);
|
|
8782
|
+
}
|
|
8783
|
+
const binding = databases[0]?.binding;
|
|
8784
|
+
if (typeof binding !== "string" || binding === "") {
|
|
8785
|
+
throw new UnsupportedWranglerConfigError(`${file} has a \`d1_databases\` entry without a binding name`);
|
|
8786
|
+
}
|
|
8787
|
+
return binding;
|
|
8788
|
+
}
|
|
8789
|
+
function unsupportedKey(file, key) {
|
|
8790
|
+
return new UnsupportedWranglerConfigError(`${file} sets \`${key}\`, which dispatch cannot preserve`);
|
|
8791
|
+
}
|
|
8792
|
+
function asArray(file, key, value) {
|
|
8793
|
+
if (!Array.isArray(value))
|
|
8794
|
+
throw new UnsupportedWranglerConfigError(`${file} sets \`${key}\` to something other than a list`);
|
|
8795
|
+
return value;
|
|
8796
|
+
}
|
|
8797
|
+
function readJsonc(file, text) {
|
|
8798
|
+
const stripped = stripOutsideStrings(text, '"', (at) => {
|
|
8799
|
+
if (text.startsWith("//", at))
|
|
8800
|
+
return lineEnd(text, at) - at;
|
|
8801
|
+
if (!text.startsWith("/*", at))
|
|
8802
|
+
return 0;
|
|
8803
|
+
const end = text.indexOf("*/", at + 2);
|
|
8804
|
+
return (end === -1 ? text.length : end + 2) - at;
|
|
8805
|
+
}).replace(/,(\s*[}\]])/g, "$1");
|
|
8806
|
+
let parsed;
|
|
8807
|
+
try {
|
|
8808
|
+
parsed = JSON.parse(stripped);
|
|
8809
|
+
} catch {
|
|
8810
|
+
throw new UnsupportedWranglerConfigError(`${file} could not be read as JSON`);
|
|
8811
|
+
}
|
|
8812
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
8813
|
+
throw new UnsupportedWranglerConfigError(`${file} is not a JSON object`);
|
|
8814
|
+
}
|
|
8815
|
+
return parsed;
|
|
8816
|
+
}
|
|
8817
|
+
function readToml(file, text) {
|
|
8818
|
+
const config = {};
|
|
8819
|
+
const databases = [];
|
|
8820
|
+
let table = config;
|
|
8821
|
+
for (const line of logicalTomlLines(text)) {
|
|
8822
|
+
if (line.startsWith("[")) {
|
|
8823
|
+
table = d1Table(file, line);
|
|
8824
|
+
databases.push(table);
|
|
8825
|
+
} else {
|
|
8826
|
+
assign(file, table, line, table === config);
|
|
8827
|
+
}
|
|
8828
|
+
}
|
|
8829
|
+
if (databases.length === 0)
|
|
8830
|
+
return config;
|
|
8831
|
+
if (config.d1_databases !== undefined)
|
|
8832
|
+
throw new UnsupportedWranglerConfigError(`${file} declares d1_databases twice`);
|
|
8833
|
+
return { ...config, d1_databases: databases };
|
|
8834
|
+
}
|
|
8835
|
+
function d1Table(file, line) {
|
|
8836
|
+
const name = /^\[\[?\s*([^\]]*?)\s*\]\]?$/.exec(line)?.[1] ?? line;
|
|
8837
|
+
if (!line.startsWith("[[") || name !== "d1_databases")
|
|
8838
|
+
throw unsupportedKey(file, topKey(name));
|
|
8839
|
+
return {};
|
|
8840
|
+
}
|
|
8841
|
+
function assign(file, table, line, topLevel) {
|
|
8842
|
+
const assignment = /^([\w\-."]+)\s*=\s*(.*)$/.exec(line);
|
|
8843
|
+
if (assignment === null)
|
|
8844
|
+
throw new UnsupportedWranglerConfigError(`${file} could not be read as TOML near \`${line.slice(0, 40)}\``);
|
|
8845
|
+
const key = topKey(assignment[1]);
|
|
8846
|
+
if (topLevel && assignment[1].includes("."))
|
|
8847
|
+
throw unsupportedKey(file, key);
|
|
8848
|
+
table[key] = tomlValue(assignment[2].trim());
|
|
8849
|
+
}
|
|
8850
|
+
function topKey(dotted) {
|
|
8851
|
+
return dotted.split(".")[0].replace(/"/g, "").trim();
|
|
8852
|
+
}
|
|
8853
|
+
function tomlValue(raw) {
|
|
8854
|
+
const quoted = /^"((?:[^"\\]|\\.)*)"$|^'([^']*)'$/.exec(raw);
|
|
8855
|
+
if (quoted !== null)
|
|
8856
|
+
return quoted[1] ?? quoted[2];
|
|
8857
|
+
if (!raw.startsWith("["))
|
|
8858
|
+
return raw;
|
|
8859
|
+
const inner = raw.slice(1, -1);
|
|
8860
|
+
if (inner.includes("{")) {
|
|
8861
|
+
return [...inner.matchAll(/\{([^}]*)\}/g)].map((table) => ({
|
|
8862
|
+
binding: /(?:^|[\s,])binding\s*=\s*["']([^"']*)["']/.exec(table[1])?.[1]
|
|
8863
|
+
}));
|
|
8864
|
+
}
|
|
8865
|
+
return [...inner.matchAll(/"((?:[^"\\]|\\.)*)"|'([^']*)'/g)].map((item) => item[1] ?? item[2]);
|
|
8866
|
+
}
|
|
8867
|
+
function logicalTomlLines(text) {
|
|
8868
|
+
const stripped = stripOutsideStrings(text, `"'`, (at) => text[at] === "#" ? lineEnd(text, at) - at : 0);
|
|
8869
|
+
const lines = [];
|
|
8870
|
+
let pending = "";
|
|
8871
|
+
for (const raw of stripped.split(/\r?\n/)) {
|
|
8872
|
+
pending = `${pending} ${raw.trim()}`.trim();
|
|
8873
|
+
if (pending === "" || openBrackets(pending) > 0)
|
|
8874
|
+
continue;
|
|
8875
|
+
lines.push(pending);
|
|
8876
|
+
pending = "";
|
|
8877
|
+
}
|
|
8878
|
+
if (pending !== "")
|
|
8879
|
+
lines.push(pending);
|
|
8880
|
+
return lines;
|
|
8881
|
+
}
|
|
8882
|
+
function lineEnd(text, at) {
|
|
8883
|
+
const end = text.indexOf(`
|
|
8884
|
+
`, at);
|
|
8885
|
+
return end === -1 ? text.length : end;
|
|
8886
|
+
}
|
|
8887
|
+
function openBrackets(line) {
|
|
8888
|
+
if (/^\[\[?[^\]=]*\]\]?$/.test(line))
|
|
8889
|
+
return 0;
|
|
8890
|
+
let open = 0;
|
|
8891
|
+
stripOutsideStrings(line, `"'`, (at) => {
|
|
8892
|
+
if (line[at] === "[" || line[at] === "{")
|
|
8893
|
+
open++;
|
|
8894
|
+
if (line[at] === "]" || line[at] === "}")
|
|
8895
|
+
open--;
|
|
8896
|
+
return 0;
|
|
8897
|
+
});
|
|
8898
|
+
return open;
|
|
8899
|
+
}
|
|
8900
|
+
function stripOutsideStrings(text, quotes, skip) {
|
|
8901
|
+
let out = "";
|
|
8902
|
+
for (let i = 0;i < text.length; ) {
|
|
8903
|
+
if (quotes.includes(text[i])) {
|
|
8904
|
+
const end = stringEnd(text, i);
|
|
8905
|
+
out += text.slice(i, end);
|
|
8906
|
+
i = end;
|
|
8907
|
+
continue;
|
|
8908
|
+
}
|
|
8909
|
+
const skipped = skip(i);
|
|
8910
|
+
if (skipped === 0)
|
|
8911
|
+
out += text[i];
|
|
8912
|
+
i += Math.max(skipped, 1);
|
|
8913
|
+
}
|
|
8914
|
+
return out;
|
|
8915
|
+
}
|
|
8916
|
+
function stringEnd(text, start) {
|
|
8917
|
+
const quote = text[start];
|
|
8918
|
+
for (let i = start + 1;i < text.length; i++) {
|
|
8919
|
+
if (text[i] === "\\" && quote === '"')
|
|
8920
|
+
i++;
|
|
8921
|
+
else if (text[i] === quote)
|
|
8922
|
+
return i + 1;
|
|
8923
|
+
}
|
|
8924
|
+
return text.length;
|
|
8925
|
+
}
|
|
8926
|
+
|
|
8927
|
+
// src/commands/migrate.ts
|
|
8928
|
+
var USAGE = "usage: launchpad migrate --origin dispatch [--dry-run]";
|
|
8929
|
+
var migrateCommand = {
|
|
8930
|
+
name: "migrate",
|
|
8931
|
+
summary: "convert an unprovisioned static, React or React API app to Workers dispatch",
|
|
8932
|
+
run: runMigrate
|
|
8933
|
+
};
|
|
8934
|
+
async function runMigrate(args, io) {
|
|
8935
|
+
if (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) {
|
|
8936
|
+
io.out(USAGE);
|
|
8937
|
+
return 0;
|
|
8938
|
+
}
|
|
8939
|
+
const flags = args.filter((arg) => arg !== "--dry-run");
|
|
8940
|
+
if (flags.length !== 2 || flags[0] !== "--origin" || flags[1] !== "dispatch" || args.length > 3) {
|
|
8941
|
+
io.err(USAGE);
|
|
8942
|
+
return 64;
|
|
8943
|
+
}
|
|
8944
|
+
try {
|
|
8945
|
+
const result = await convertToDispatch(process.cwd(), args.includes("--dry-run"));
|
|
8946
|
+
if (result.kind === "already") {
|
|
8947
|
+
io.out("launchpad.yaml already selects Workers dispatch. Run `launchpad deploy`.");
|
|
8948
|
+
} else if (result.kind === "eligible") {
|
|
8949
|
+
io.out(`${result.slug}: eligible for v2 Workers dispatch with gateway authentication. No files changed.`);
|
|
8950
|
+
} else {
|
|
8951
|
+
io.out(`${result.slug}: launchpad.yaml now uses v2 Workers dispatch with gateway authentication.`);
|
|
8952
|
+
io.out(`Audience, owner and application settings are preserved. Original: ${BACKUP_PATH}`);
|
|
8953
|
+
io.out("Run `launchpad deploy`. No Pages project is needed.");
|
|
8954
|
+
const next = secretsNextStep(result.secrets);
|
|
8955
|
+
if (next !== null)
|
|
8956
|
+
io.out(next);
|
|
8957
|
+
}
|
|
8958
|
+
return 0;
|
|
8959
|
+
} catch (error) {
|
|
8960
|
+
io.err(`launchpad migrate: ${error instanceof Error ? error.message : String(error)}`);
|
|
8961
|
+
return 1;
|
|
8962
|
+
}
|
|
8963
|
+
}
|
|
8964
|
+
var BACKUP_PATH = ".launchpad/manifest-before-dispatch.yaml";
|
|
8965
|
+
function secretsNextStep(secrets) {
|
|
8966
|
+
if (secrets.length === 0)
|
|
8967
|
+
return null;
|
|
8968
|
+
return `Secrets (${secrets.join(", ")}) carry over as secret_text env vars. Once \`launchpad status\` shows the first deploy delivered, run \`launchpad secrets push\` to set them.`;
|
|
8969
|
+
}
|
|
8970
|
+
async function convertToDispatch(cwd, dryRun) {
|
|
8971
|
+
const file = path8.join(cwd, "launchpad.yaml");
|
|
8972
|
+
if (!lstatSync4(file).isFile())
|
|
8973
|
+
throw new Error("launchpad.yaml must be a regular file, not a symlink.");
|
|
8974
|
+
const original = readFileSync11(file, "utf8");
|
|
8975
|
+
const loaded = parseManifestAnyVersion(original, file);
|
|
8976
|
+
if (loaded.kind !== "ok")
|
|
8977
|
+
throw new Error("launchpad.yaml is invalid. Run `launchpad validate` and correct it first.");
|
|
8978
|
+
const manifest = loaded.manifest;
|
|
8979
|
+
if (manifest.apiVersion === "launchpad.m-kopa.us/v2" && manifest.spec.origin === "dispatch")
|
|
8980
|
+
return { kind: "already" };
|
|
8981
|
+
const slug = manifest.apiVersion === "launchpad.m-kopa.us/v2" ? manifest.metadata.slug : manifest.metadata.name;
|
|
8982
|
+
let converted;
|
|
8983
|
+
try {
|
|
8984
|
+
converted = convertLocally(cwd, manifest);
|
|
8985
|
+
} catch (error) {
|
|
8986
|
+
await refuseIfRegistered(slug);
|
|
8987
|
+
throw error;
|
|
8988
|
+
}
|
|
8989
|
+
await assertUnregistered(slug);
|
|
8990
|
+
const secrets = Object.entries(converted.spec.env_vars ?? {}).filter(([, value]) => value.type === "secret_text").map(([name]) => name);
|
|
8991
|
+
if (dryRun)
|
|
8992
|
+
return { kind: "eligible", slug, secrets };
|
|
8993
|
+
try {
|
|
8994
|
+
saveMigration(file, original, converted);
|
|
8995
|
+
} catch (error) {
|
|
8996
|
+
throw new MigrationSaveError(error);
|
|
8997
|
+
}
|
|
8998
|
+
return { kind: "converted", slug, secrets };
|
|
8999
|
+
}
|
|
9000
|
+
function convertLocally(cwd, manifest) {
|
|
9001
|
+
const v2 = manifest.apiVersion === "launchpad.m-kopa.us/v2";
|
|
9002
|
+
const spec = v2 ? manifest.spec : manifest;
|
|
9003
|
+
const appType = v2 ? manifest.spec.appType : manifest.deployment.type;
|
|
9004
|
+
return dispatchManifest(manifest, inspectRuntime(cwd, appType, spec.app?.root, spec.build));
|
|
9005
|
+
}
|
|
9006
|
+
async function refuseIfRegistered(slug) {
|
|
9007
|
+
try {
|
|
9008
|
+
await assertUnregistered(slug);
|
|
9009
|
+
} catch (error) {
|
|
9010
|
+
if (error instanceof RegisteredAppError)
|
|
9011
|
+
throw error;
|
|
9012
|
+
}
|
|
9013
|
+
}
|
|
9014
|
+
async function assertUnregistered(slug) {
|
|
9015
|
+
try {
|
|
9016
|
+
await apiRaw(loadConfig(), { path: `/apps/${encodeURIComponent(slug)}/manifest/state` });
|
|
9017
|
+
} catch (error) {
|
|
9018
|
+
if (error instanceof NotFoundError && error.serverCode === "not_found")
|
|
9019
|
+
return;
|
|
9020
|
+
if (error instanceof UpgradeRequiredError || error instanceof UnauthenticatedError)
|
|
9021
|
+
throw error;
|
|
9022
|
+
throw new RegistryUnavailableError(error);
|
|
9023
|
+
}
|
|
9024
|
+
throw new RegisteredAppError;
|
|
9025
|
+
}
|
|
9026
|
+
|
|
9027
|
+
class MigrationSaveError extends Error {
|
|
9028
|
+
constructor(cause) {
|
|
9029
|
+
super(`Could not save the converted manifest: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
9030
|
+
}
|
|
9031
|
+
}
|
|
9032
|
+
|
|
9033
|
+
class RegistryUnavailableError extends Error {
|
|
9034
|
+
constructor(cause) {
|
|
9035
|
+
super(`Could not confirm whether this app is registered: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
9036
|
+
}
|
|
9037
|
+
}
|
|
9038
|
+
|
|
9039
|
+
class RegisteredAppError extends Error {
|
|
9040
|
+
constructor() {
|
|
9041
|
+
super("This app is already registered. This command only converts apps that have never provisioned; ask the platform team to migrate an existing app.");
|
|
9042
|
+
}
|
|
9043
|
+
}
|
|
9044
|
+
var WRANGLER_FILES = ["wrangler.toml", "wrangler.json", "wrangler.jsonc"];
|
|
9045
|
+
function inspectRuntime(cwd, appType, appRootDir, build) {
|
|
9046
|
+
const d1 = new Set;
|
|
9047
|
+
for (const root of runtimeRoots(cwd, appRootDir, build)) {
|
|
9048
|
+
assertNoUncompiledCode(root, appType);
|
|
9049
|
+
for (const binding2 of wranglerD1Bindings(cwd, root))
|
|
9050
|
+
d1.add(binding2);
|
|
9051
|
+
}
|
|
9052
|
+
if (d1.size > 1)
|
|
9053
|
+
throw new Error(`The wrangler configs bind D1 under different names (${[...d1].join(", ")}). Use one name.`);
|
|
9054
|
+
const [binding] = d1;
|
|
9055
|
+
if (binding !== undefined && appType !== "react+api") {
|
|
9056
|
+
throw new Error("The wrangler config declares a D1 database, which only react+api apps can use on dispatch.");
|
|
9057
|
+
}
|
|
9058
|
+
return binding;
|
|
9059
|
+
}
|
|
9060
|
+
function runtimeRoots(cwd, appRootDir, build) {
|
|
9061
|
+
const appRoot = path8.resolve(cwd, appRootDir ?? ".");
|
|
9062
|
+
const buildRoot = path8.resolve(appRoot, build?.root_dir ?? ".");
|
|
9063
|
+
return new Set([
|
|
9064
|
+
cwd,
|
|
9065
|
+
appRoot,
|
|
9066
|
+
buildRoot,
|
|
9067
|
+
path8.resolve(cwd, build?.root_dir ?? "."),
|
|
9068
|
+
path8.resolve(buildRoot, build?.destination_dir ?? "dist")
|
|
9069
|
+
]);
|
|
9070
|
+
}
|
|
9071
|
+
function assertNoUncompiledCode(root, appType) {
|
|
9072
|
+
const api = appType === "react+api";
|
|
9073
|
+
for (const name of api ? ["_worker.js"] : ["_worker.js", "functions"]) {
|
|
9074
|
+
if (existsSync7(path8.join(root, name))) {
|
|
9075
|
+
throw new Error(`${name} needs a runtime review before conversion. Pages Functions and custom Worker code must not be dropped. Ask the platform team to review this app.`);
|
|
9076
|
+
}
|
|
9077
|
+
}
|
|
9078
|
+
if (api)
|
|
9079
|
+
assertWorkerRoutesFit(root);
|
|
9080
|
+
}
|
|
9081
|
+
function wranglerD1Bindings(cwd, root) {
|
|
9082
|
+
return WRANGLER_FILES.map((name) => path8.join(root, name)).filter((file) => existsSync7(file)).map((file) => wranglerD1Binding(path8.relative(cwd, file), readFileSync11(file, "utf8"))).filter((binding) => binding !== undefined);
|
|
9083
|
+
}
|
|
9084
|
+
function assertWorkerRoutesFit(root) {
|
|
9085
|
+
const functions = path8.join(root, "functions");
|
|
9086
|
+
if (!existsSync7(functions) || !lstatSync4(functions).isDirectory())
|
|
9087
|
+
return;
|
|
9088
|
+
const catchAll = readdirSync2(functions).find((name) => /^_middleware\.[cm]?[jt]sx?$/.test(name) || name.startsWith("["));
|
|
9089
|
+
if (catchAll !== undefined) {
|
|
9090
|
+
throw new Error(`functions/${catchAll} would run the Worker for every path, including static assets, which dispatch does not support. Move it under a prefix such as functions/api/.`);
|
|
9091
|
+
}
|
|
9092
|
+
}
|
|
9093
|
+
function saveMigration(file, original, converted) {
|
|
9094
|
+
if (readFileSync11(file, "utf8") !== original)
|
|
9095
|
+
throw new Error("launchpad.yaml changed during the check. Retry migration.");
|
|
9096
|
+
const backupDir = path8.join(path8.dirname(file), ".launchpad");
|
|
9097
|
+
if (existsSync7(backupDir) && !lstatSync4(backupDir).isDirectory()) {
|
|
9098
|
+
throw new Error(".launchpad must be a regular directory, not a symlink or file.");
|
|
9099
|
+
}
|
|
9100
|
+
mkdirSync2(backupDir, { recursive: true });
|
|
9101
|
+
const backup = path8.join(backupDir, "manifest-before-dispatch.yaml");
|
|
9102
|
+
const attemptDir = mkdtempSync(path8.join(backupDir, "migration-"));
|
|
9103
|
+
let backupCreated = false;
|
|
9104
|
+
try {
|
|
9105
|
+
const fd = openSync(backup, "wx", 384);
|
|
9106
|
+
backupCreated = true;
|
|
9107
|
+
try {
|
|
9108
|
+
writeFileSync2(fd, original);
|
|
9109
|
+
} finally {
|
|
9110
|
+
closeSync(fd);
|
|
9111
|
+
}
|
|
9112
|
+
const temporary = path8.join(attemptDir, "launchpad.yaml");
|
|
9113
|
+
writeFileSync2(temporary, stringify(converted), { flag: "wx", mode: 384 });
|
|
9114
|
+
if (readFileSync11(file, "utf8") !== original)
|
|
9115
|
+
throw new Error("launchpad.yaml changed during migration. Retry migration.");
|
|
9116
|
+
renameSync(temporary, file);
|
|
9117
|
+
} catch (error) {
|
|
9118
|
+
if (backupCreated)
|
|
9119
|
+
rmSync2(backup);
|
|
9120
|
+
throw error;
|
|
9121
|
+
} finally {
|
|
9122
|
+
rmSync2(attemptDir, { recursive: true, force: true });
|
|
9123
|
+
}
|
|
9124
|
+
}
|
|
9125
|
+
|
|
8616
9126
|
// src/commands/deploy.ts
|
|
8617
9127
|
var deployCommand = {
|
|
8618
9128
|
name: "deploy",
|
|
@@ -8665,8 +9175,8 @@ async function runDeploy(args, io) {
|
|
|
8665
9175
|
}, io);
|
|
8666
9176
|
}
|
|
8667
9177
|
const cwd = process.cwd();
|
|
8668
|
-
const manifestPath =
|
|
8669
|
-
if (
|
|
9178
|
+
const manifestPath = path9.join(cwd, "launchpad.yaml");
|
|
9179
|
+
if (existsSync8(manifestPath)) {
|
|
8670
9180
|
const contentMode = flags.mode.kind === "content" ? flags.mode : {
|
|
8671
9181
|
allowStale: false,
|
|
8672
9182
|
rebaseOntoHead: false,
|
|
@@ -8700,7 +9210,7 @@ async function runDeploy(args, io) {
|
|
|
8700
9210
|
} else {
|
|
8701
9211
|
const inferred = inferSlug({ cwd: process.cwd(), warn: (l) => io.err(l) });
|
|
8702
9212
|
if (inferred === null) {
|
|
8703
|
-
io.err(`launchpad deploy: could not infer slug from cwd (${
|
|
9213
|
+
io.err(`launchpad deploy: could not infer slug from cwd (${path9.basename(process.cwd())});
|
|
8704
9214
|
` + ` pass --slug <slug>, or cd into a directory named launchpad-app-<slug>.`);
|
|
8705
9215
|
return 64;
|
|
8706
9216
|
}
|
|
@@ -8720,10 +9230,10 @@ async function runDeploy(args, io) {
|
|
|
8720
9230
|
return 1;
|
|
8721
9231
|
}
|
|
8722
9232
|
let contentManifestYaml = null;
|
|
8723
|
-
const contentManifestPath =
|
|
8724
|
-
if (
|
|
9233
|
+
const contentManifestPath = path9.join(process.cwd(), "launchpad.yaml");
|
|
9234
|
+
if (existsSync8(contentManifestPath)) {
|
|
8725
9235
|
try {
|
|
8726
|
-
contentManifestYaml =
|
|
9236
|
+
contentManifestYaml = readFileSync12(contentManifestPath, "utf8");
|
|
8727
9237
|
} catch {
|
|
8728
9238
|
contentManifestYaml = null;
|
|
8729
9239
|
}
|
|
@@ -8933,8 +9443,8 @@ function surfaceStaleBlock(body, io) {
|
|
|
8933
9443
|
const head = body.head_sha ?? null;
|
|
8934
9444
|
if (body.error === "stale_overlap" && body.paths !== undefined) {
|
|
8935
9445
|
io.err(`launchpad deploy: BLOCKED — this deploy would roll back ${body.paths.length} file(s) a teammate changed on main since you cloned:`);
|
|
8936
|
-
for (const
|
|
8937
|
-
io.err(` - ${
|
|
9446
|
+
for (const path10 of body.paths.slice(0, 20)) {
|
|
9447
|
+
io.err(` - ${path10}`);
|
|
8938
9448
|
}
|
|
8939
9449
|
if (body.paths.length > 20) {
|
|
8940
9450
|
io.err(` … and ${body.paths.length - 20} more`);
|
|
@@ -8967,7 +9477,7 @@ function withTimeout(p, ms) {
|
|
|
8967
9477
|
async function surfaceManifestDiff(cfg, slug, manifestPath, io) {
|
|
8968
9478
|
let localYaml;
|
|
8969
9479
|
try {
|
|
8970
|
-
localYaml =
|
|
9480
|
+
localYaml = readFileSync12(manifestPath, "utf8");
|
|
8971
9481
|
} catch {
|
|
8972
9482
|
return;
|
|
8973
9483
|
}
|
|
@@ -9016,8 +9526,21 @@ async function runModelADeploy(args) {
|
|
|
9016
9526
|
let slug;
|
|
9017
9527
|
let appType = "static";
|
|
9018
9528
|
let declaredOrigin;
|
|
9529
|
+
let pagesNote;
|
|
9019
9530
|
try {
|
|
9020
|
-
|
|
9531
|
+
pagesNote = await defaultToDispatch(cwd, manifestPath, io);
|
|
9532
|
+
} catch (e) {
|
|
9533
|
+
if (e instanceof UpgradeRequiredError) {
|
|
9534
|
+
io.err(e.message);
|
|
9535
|
+
return EXIT_CLI_UPGRADE_REQUIRED;
|
|
9536
|
+
}
|
|
9537
|
+
io.err(`launchpad deploy: ${describe17(e)}. Nothing was deployed.`);
|
|
9538
|
+
if (e instanceof UnauthenticatedError)
|
|
9539
|
+
io.err(" run `launchpad login` to refresh your session.");
|
|
9540
|
+
return 1;
|
|
9541
|
+
}
|
|
9542
|
+
try {
|
|
9543
|
+
const manifestObj = parseYaml6(readFileSync12(manifestPath, "utf8"));
|
|
9021
9544
|
const metaSlug = resolveManifestSlug(manifestObj);
|
|
9022
9545
|
if (metaSlug === null) {
|
|
9023
9546
|
io.err(`launchpad deploy: launchpad.yaml is missing metadata.slug (v2) / metadata.name (v1). ` + `Run \`launchpad init\` again to regenerate the manifest.`);
|
|
@@ -9084,9 +9607,8 @@ async function runModelADeploy(args) {
|
|
|
9084
9607
|
io.out(`Bundling ${cwd} for ${slug} (Model A) …`);
|
|
9085
9608
|
if (declaredOrigin !== "dispatch" && appType !== "container") {
|
|
9086
9609
|
io.out("Deployment target: Cloudflare Pages, selected by launchpad.yaml.");
|
|
9087
|
-
if (
|
|
9088
|
-
io.out(
|
|
9089
|
-
}
|
|
9610
|
+
if (pagesNote !== null)
|
|
9611
|
+
io.out(pagesNote);
|
|
9090
9612
|
}
|
|
9091
9613
|
let cfg;
|
|
9092
9614
|
try {
|
|
@@ -9172,7 +9694,7 @@ async function runModelADeploy(args) {
|
|
|
9172
9694
|
const errorCode = body.error;
|
|
9173
9695
|
if (errorCode === "audience_confirmation_required") {
|
|
9174
9696
|
io.err(String(body.message));
|
|
9175
|
-
io.err("
|
|
9697
|
+
io.err(body.requires_admin_approval === true ? "This sensitive app needs builder confirmation and platform-admin approval. Retry after both are complete." : "The server still requires browser confirmation. Ask the platform team to update it, then retry.");
|
|
9176
9698
|
return 1;
|
|
9177
9699
|
}
|
|
9178
9700
|
if (result.status === 426 && typeof body.minimum === "string" && typeof body.yours === "string") {
|
|
@@ -9375,6 +9897,49 @@ async function runModelADeploy(args) {
|
|
|
9375
9897
|
function surfaceCommittedHandoff(dispatchOrigin, io, pagesStyle = "unicode") {
|
|
9376
9898
|
io.out(committedHandoffMessage(dispatchOrigin, pagesStyle));
|
|
9377
9899
|
}
|
|
9900
|
+
function isIneligible(e) {
|
|
9901
|
+
return !(e instanceof RegistryUnavailableError || e instanceof MigrationSaveError || e instanceof UpgradeRequiredError || e instanceof UnauthenticatedError);
|
|
9902
|
+
}
|
|
9903
|
+
async function defaultToDispatch(cwd, manifestPath, io) {
|
|
9904
|
+
if (!impliesPagesForGateway(manifestPath))
|
|
9905
|
+
return null;
|
|
9906
|
+
try {
|
|
9907
|
+
const result = await convertToDispatch(cwd, false);
|
|
9908
|
+
if (result.kind === "converted")
|
|
9909
|
+
reportConversion(io, result.secrets);
|
|
9910
|
+
return null;
|
|
9911
|
+
} catch (e) {
|
|
9912
|
+
if (e instanceof RegisteredAppError)
|
|
9913
|
+
return null;
|
|
9914
|
+
if (!(e instanceof Error) || !isIneligible(e))
|
|
9915
|
+
throw e;
|
|
9916
|
+
return `Not converted to Workers dispatch: ${describe17(e)}`;
|
|
9917
|
+
}
|
|
9918
|
+
}
|
|
9919
|
+
var CONVERTIBLE_APP_TYPES = new Set(["static", "react", "react+api"]);
|
|
9920
|
+
function impliesPagesForGateway(manifestPath) {
|
|
9921
|
+
const fields = originFields(manifestPath);
|
|
9922
|
+
return CONVERTIBLE_APP_TYPES.has(fields.appType) && fields.origin === undefined && fields.auth === "gateway";
|
|
9923
|
+
}
|
|
9924
|
+
function originFields(manifestPath) {
|
|
9925
|
+
let raw;
|
|
9926
|
+
try {
|
|
9927
|
+
raw = parseYaml6(readFileSync12(manifestPath, "utf8")) ?? {};
|
|
9928
|
+
} catch {
|
|
9929
|
+
return {};
|
|
9930
|
+
}
|
|
9931
|
+
if (raw.apiVersion === "launchpad.m-kopa.us/v2")
|
|
9932
|
+
return raw.spec ?? {};
|
|
9933
|
+
return { origin: raw.origin, auth: raw.auth, appType: raw.deployment?.type };
|
|
9934
|
+
}
|
|
9935
|
+
function reportConversion(io, secrets) {
|
|
9936
|
+
io.out("New apps deploy on Workers dispatch by default, so launchpad.yaml was converted (no Pages project needed).");
|
|
9937
|
+
io.out(` Audience, owner and build settings are preserved. Original: ${BACKUP_PATH}`);
|
|
9938
|
+
io.out(` To stay on Pages instead, restore ${BACKUP_PATH} as launchpad.yaml, set \`origin: pages\` (v1) or \`spec.origin: pages\` (v2) and deploy again.`);
|
|
9939
|
+
const next = secretsNextStep(secrets);
|
|
9940
|
+
if (next !== null)
|
|
9941
|
+
io.out(` ${next}`);
|
|
9942
|
+
}
|
|
9378
9943
|
function declaredOriginOf(isV22, origin) {
|
|
9379
9944
|
if (!isV22)
|
|
9380
9945
|
return "pages";
|
|
@@ -9409,7 +9974,7 @@ function pendingReconcileOutcome(body) {
|
|
|
9409
9974
|
}
|
|
9410
9975
|
|
|
9411
9976
|
// src/commands/redeploy.ts
|
|
9412
|
-
import { existsSync as
|
|
9977
|
+
import { existsSync as existsSync9, readFileSync as readFileSync13 } from "node:fs";
|
|
9413
9978
|
import { resolve as resolvePath2 } from "node:path";
|
|
9414
9979
|
import { parse as parseYaml7 } from "yaml";
|
|
9415
9980
|
var SLUG_RE6 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
|
|
@@ -9487,10 +10052,10 @@ async function runRedeploy(opts, io, deps = {}) {
|
|
|
9487
10052
|
}
|
|
9488
10053
|
function readLocalAppType(cwd, file) {
|
|
9489
10054
|
const manifestPath = resolvePath2(cwd, file ?? "launchpad.yaml");
|
|
9490
|
-
if (!
|
|
10055
|
+
if (!existsSync9(manifestPath))
|
|
9491
10056
|
return null;
|
|
9492
10057
|
try {
|
|
9493
|
-
const obj = parseYaml7(
|
|
10058
|
+
const obj = parseYaml7(readFileSync13(manifestPath, "utf8"));
|
|
9494
10059
|
return coerceAppType(obj?.deployment?.type);
|
|
9495
10060
|
} catch {
|
|
9496
10061
|
return null;
|
|
@@ -9584,7 +10149,7 @@ function printHelp(io) {
|
|
|
9584
10149
|
}
|
|
9585
10150
|
|
|
9586
10151
|
// src/commands/envvars.ts
|
|
9587
|
-
import * as
|
|
10152
|
+
import * as path10 from "node:path";
|
|
9588
10153
|
var envvarsCommand = {
|
|
9589
10154
|
name: "envvars",
|
|
9590
10155
|
summary: "list / set / remove production env vars (slug-scoped)",
|
|
@@ -9604,7 +10169,7 @@ async function runEnvvars(args, io) {
|
|
|
9604
10169
|
} else {
|
|
9605
10170
|
const inferred = inferSlug({ cwd: process.cwd(), warn: (l) => io.err(l) });
|
|
9606
10171
|
if (inferred === null) {
|
|
9607
|
-
io.err(`launchpad envvars: could not infer slug from cwd (${
|
|
10172
|
+
io.err(`launchpad envvars: could not infer slug from cwd (${path10.basename(process.cwd())});
|
|
9608
10173
|
` + ` pass --slug <slug>, or cd into a directory named launchpad-app-<slug>.`);
|
|
9609
10174
|
return 64;
|
|
9610
10175
|
}
|
|
@@ -9774,7 +10339,7 @@ function describe18(e) {
|
|
|
9774
10339
|
}
|
|
9775
10340
|
|
|
9776
10341
|
// src/commands/generate.ts
|
|
9777
|
-
import { mkdirSync as
|
|
10342
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync14, writeFileSync as writeFileSync3 } from "node:fs";
|
|
9778
10343
|
import { dirname as dirname7, resolve as resolve10, relative as relative4 } from "node:path";
|
|
9779
10344
|
var generateCommand = {
|
|
9780
10345
|
name: "generate",
|
|
@@ -9824,7 +10389,7 @@ async function runGenerate(args, io) {
|
|
|
9824
10389
|
];
|
|
9825
10390
|
return flags.json ? renderApplyJson(reports, appRoot, io) : renderApplyHuman(reports, appRoot, io);
|
|
9826
10391
|
}
|
|
9827
|
-
function applyOne(artefact,
|
|
10392
|
+
function applyOne(artefact, path11, out, force) {
|
|
9828
10393
|
if (out.kind === "not-applicable" || out.kind === "unsupported") {
|
|
9829
10394
|
return {
|
|
9830
10395
|
artefact,
|
|
@@ -9835,32 +10400,32 @@ function applyOne(artefact, path10, out, force) {
|
|
|
9835
10400
|
warnings: []
|
|
9836
10401
|
};
|
|
9837
10402
|
}
|
|
9838
|
-
const existing = readIfExists(
|
|
10403
|
+
const existing = readIfExists(path11);
|
|
9839
10404
|
if (existing.kind === "read-error") {
|
|
9840
10405
|
return {
|
|
9841
10406
|
artefact,
|
|
9842
|
-
action: { kind: "write-error", path:
|
|
10407
|
+
action: { kind: "write-error", path: path11, message: existing.message },
|
|
9843
10408
|
warnings: out.warnings
|
|
9844
10409
|
};
|
|
9845
10410
|
}
|
|
9846
10411
|
if (existing.kind === "ok" && existing.content === out.content) {
|
|
9847
|
-
return { artefact, action: { kind: "unchanged", path:
|
|
10412
|
+
return { artefact, action: { kind: "unchanged", path: path11 }, warnings: out.warnings };
|
|
9848
10413
|
}
|
|
9849
10414
|
if (existing.kind === "ok" && existing.content !== out.content && !force) {
|
|
9850
10415
|
return {
|
|
9851
10416
|
artefact,
|
|
9852
|
-
action: { kind: "would-overwrite", path:
|
|
10417
|
+
action: { kind: "would-overwrite", path: path11 },
|
|
9853
10418
|
warnings: out.warnings
|
|
9854
10419
|
};
|
|
9855
10420
|
}
|
|
9856
10421
|
try {
|
|
9857
|
-
|
|
9858
|
-
|
|
10422
|
+
mkdirSync3(dirname7(path11), { recursive: true });
|
|
10423
|
+
writeFileSync3(path11, out.content, "utf8");
|
|
9859
10424
|
return {
|
|
9860
10425
|
artefact,
|
|
9861
10426
|
action: {
|
|
9862
10427
|
kind: "written",
|
|
9863
|
-
path:
|
|
10428
|
+
path: path11,
|
|
9864
10429
|
bytes: Buffer.byteLength(out.content, "utf8")
|
|
9865
10430
|
},
|
|
9866
10431
|
warnings: out.warnings
|
|
@@ -9870,16 +10435,16 @@ function applyOne(artefact, path10, out, force) {
|
|
|
9870
10435
|
artefact,
|
|
9871
10436
|
action: {
|
|
9872
10437
|
kind: "write-error",
|
|
9873
|
-
path:
|
|
10438
|
+
path: path11,
|
|
9874
10439
|
message: err.message ?? String(err)
|
|
9875
10440
|
},
|
|
9876
10441
|
warnings: out.warnings
|
|
9877
10442
|
};
|
|
9878
10443
|
}
|
|
9879
10444
|
}
|
|
9880
|
-
function readIfExists(
|
|
10445
|
+
function readIfExists(path11) {
|
|
9881
10446
|
try {
|
|
9882
|
-
return { kind: "ok", content:
|
|
10447
|
+
return { kind: "ok", content: readFileSync14(path11, "utf8") };
|
|
9883
10448
|
} catch (err) {
|
|
9884
10449
|
const e = err;
|
|
9885
10450
|
if (e.code === "ENOENT")
|
|
@@ -10618,17 +11183,17 @@ function describe21(e) {
|
|
|
10618
11183
|
|
|
10619
11184
|
// src/commands/init.ts
|
|
10620
11185
|
import { createInterface } from "node:readline/promises";
|
|
10621
|
-
import { existsSync as
|
|
11186
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync16, writeFileSync as writeFileSync4 } from "node:fs";
|
|
10622
11187
|
import { dirname as dirname8, resolve as resolve11 } from "node:path";
|
|
10623
11188
|
import { stringify as yamlStringify } from "yaml";
|
|
10624
11189
|
|
|
10625
11190
|
// src/detect/index.ts
|
|
10626
|
-
import { existsSync as
|
|
11191
|
+
import { existsSync as existsSync10, readFileSync as readFileSync15, statSync } from "node:fs";
|
|
10627
11192
|
import { join as join13 } from "node:path";
|
|
10628
11193
|
function detectAppShape(cwd) {
|
|
10629
|
-
const hasPackageJson =
|
|
10630
|
-
const hasAnyLockfile = LOCKFILES.some(({ file }) =>
|
|
10631
|
-
const hasViteConfig = VITE_CONFIG_NAMES.some((n) =>
|
|
11194
|
+
const hasPackageJson = existsSync10(join13(cwd, "package.json"));
|
|
11195
|
+
const hasAnyLockfile = LOCKFILES.some(({ file }) => existsSync10(join13(cwd, file)));
|
|
11196
|
+
const hasViteConfig = VITE_CONFIG_NAMES.some((n) => existsSync10(join13(cwd, n)));
|
|
10632
11197
|
if (!hasPackageJson && !hasAnyLockfile && !hasViteConfig) {
|
|
10633
11198
|
return {
|
|
10634
11199
|
kind: "not-applicable",
|
|
@@ -10674,7 +11239,7 @@ var LOCKFILES = [
|
|
|
10674
11239
|
{ file: "yarn.lock", pm: "yarn" }
|
|
10675
11240
|
];
|
|
10676
11241
|
function detectPackageManager(cwd) {
|
|
10677
|
-
const present = LOCKFILES.filter(({ file }) =>
|
|
11242
|
+
const present = LOCKFILES.filter(({ file }) => existsSync10(join13(cwd, file)));
|
|
10678
11243
|
if (present.length === 0) {
|
|
10679
11244
|
return {
|
|
10680
11245
|
kind: "ambiguous",
|
|
@@ -10697,7 +11262,7 @@ function detectPackageManager(cwd) {
|
|
|
10697
11262
|
function detectVitePresence(cwd) {
|
|
10698
11263
|
for (const name of VITE_CONFIG_NAMES) {
|
|
10699
11264
|
const p = join13(cwd, name);
|
|
10700
|
-
if (
|
|
11265
|
+
if (existsSync10(p)) {
|
|
10701
11266
|
return { kind: "ok", value: { path: p } };
|
|
10702
11267
|
}
|
|
10703
11268
|
}
|
|
@@ -10709,7 +11274,7 @@ function detectVitePresence(cwd) {
|
|
|
10709
11274
|
function detectAppType(cwd) {
|
|
10710
11275
|
const fnDir = join13(cwd, "functions");
|
|
10711
11276
|
let hasFunctionsDir = false;
|
|
10712
|
-
if (
|
|
11277
|
+
if (existsSync10(fnDir)) {
|
|
10713
11278
|
try {
|
|
10714
11279
|
hasFunctionsDir = statSync(fnDir).isDirectory();
|
|
10715
11280
|
} catch {
|
|
@@ -10720,7 +11285,7 @@ function detectAppType(cwd) {
|
|
|
10720
11285
|
}
|
|
10721
11286
|
function detectBuildCommand(cwd, pm) {
|
|
10722
11287
|
const pkgJsonPath = join13(cwd, "package.json");
|
|
10723
|
-
if (!
|
|
11288
|
+
if (!existsSync10(pkgJsonPath)) {
|
|
10724
11289
|
return {
|
|
10725
11290
|
kind: "ambiguous",
|
|
10726
11291
|
reason: "no package.json at repo root. Run your package manager's `init` first."
|
|
@@ -10728,7 +11293,7 @@ function detectBuildCommand(cwd, pm) {
|
|
|
10728
11293
|
}
|
|
10729
11294
|
let pkgJson;
|
|
10730
11295
|
try {
|
|
10731
|
-
pkgJson = JSON.parse(
|
|
11296
|
+
pkgJson = JSON.parse(readFileSync15(pkgJsonPath, "utf8"));
|
|
10732
11297
|
} catch (e) {
|
|
10733
11298
|
return {
|
|
10734
11299
|
kind: "ambiguous",
|
|
@@ -10763,7 +11328,7 @@ var OUT_DIR_REGEX = /\bbuild\s*:\s*\{[^{}]*?\boutDir\s*:\s*['"]([^'"]+)['"]/s;
|
|
|
10763
11328
|
function detectDestinationDir(cwd, vite) {
|
|
10764
11329
|
let text;
|
|
10765
11330
|
try {
|
|
10766
|
-
text =
|
|
11331
|
+
text = readFileSync15(vite.path, "utf8");
|
|
10767
11332
|
} catch (e) {
|
|
10768
11333
|
return {
|
|
10769
11334
|
kind: "ambiguous",
|
|
@@ -10793,7 +11358,7 @@ async function runInit(args, io, prompt, identity = () => apiJson(loadConfig(),
|
|
|
10793
11358
|
}
|
|
10794
11359
|
const { inputs, options } = parsed;
|
|
10795
11360
|
const outPath = resolve11(process.cwd(), options.out);
|
|
10796
|
-
if (
|
|
11361
|
+
if (existsSync11(outPath) && !options.force) {
|
|
10797
11362
|
io.err(`launchpad init: ${outPath} already exists`);
|
|
10798
11363
|
io.err("Pass --force to overwrite.");
|
|
10799
11364
|
return 64;
|
|
@@ -10830,10 +11395,6 @@ async function runInit(args, io, prompt, identity = () => apiJson(loadConfig(),
|
|
|
10830
11395
|
return 64;
|
|
10831
11396
|
}
|
|
10832
11397
|
const dispatch = newAppOrigin(fillResult.inputs.type, inputs.origin) === "dispatch";
|
|
10833
|
-
if (dispatch && fillResult.inputs.type === "react+api") {
|
|
10834
|
-
io.err("launchpad init: API starter dispatch is not available yet; use --origin pages");
|
|
10835
|
-
return 64;
|
|
10836
|
-
}
|
|
10837
11398
|
if (dispatch && (inputs.hostnames.length > 0 || inputs.sessionDuration !== undefined)) {
|
|
10838
11399
|
io.err("launchpad init: dispatch does not support --hostname or --session-duration; use --origin pages for a Pages manifest");
|
|
10839
11400
|
return 64;
|
|
@@ -10849,7 +11410,7 @@ async function runInit(args, io, prompt, identity = () => apiJson(loadConfig(),
|
|
|
10849
11410
|
io.out(`Audience: private. Only you (${privateOwner.email}) and platform administrators can open this app.`);
|
|
10850
11411
|
} else {
|
|
10851
11412
|
io.out(`Audience: selected Entra groups (${fillResult.inputs.groups.join(", ")}), plus platform administrators.`);
|
|
10852
|
-
io.out("
|
|
11413
|
+
io.out("The deploy skill confirms company-wide grants with you. Sensitive apps also require browser confirmation and platform-admin approval.");
|
|
10853
11414
|
}
|
|
10854
11415
|
const manifest = buildManifest({ ...fillResult.inputs, ...privateOwner === undefined ? {} : { privateOwner } }, detected);
|
|
10855
11416
|
const validation = manifest.apiVersion === "launchpad.m-kopa.us/v2" ? ManifestV2Schema.safeParse(manifest) : ManifestSchema.safeParse(manifest);
|
|
@@ -10862,7 +11423,7 @@ async function runInit(args, io, prompt, identity = () => apiJson(loadConfig(),
|
|
|
10862
11423
|
}
|
|
10863
11424
|
const yamlText = renderYaml(manifest);
|
|
10864
11425
|
try {
|
|
10865
|
-
|
|
11426
|
+
writeFileSync4(outPath, yamlText, { encoding: "utf8" });
|
|
10866
11427
|
} catch (err) {
|
|
10867
11428
|
const e = err;
|
|
10868
11429
|
io.err(`launchpad init: failed to write ${outPath}`);
|
|
@@ -10888,10 +11449,10 @@ async function runInit(args, io, prompt, identity = () => apiJson(loadConfig(),
|
|
|
10888
11449
|
if (fillResult.inputs.type === "static") {
|
|
10889
11450
|
const serveDir = resolve11(dirname8(outPath), STATIC_SERVE_DIR);
|
|
10890
11451
|
const indexPath = resolve11(serveDir, "index.html");
|
|
10891
|
-
if (!
|
|
11452
|
+
if (!existsSync11(indexPath)) {
|
|
10892
11453
|
try {
|
|
10893
|
-
|
|
10894
|
-
|
|
11454
|
+
mkdirSync4(serveDir, { recursive: true });
|
|
11455
|
+
writeFileSync4(indexPath, starterIndexHtml(fillResult.inputs.name), {
|
|
10895
11456
|
encoding: "utf8"
|
|
10896
11457
|
});
|
|
10897
11458
|
io.out(`✓ scaffolded ${indexPath}`);
|
|
@@ -10905,11 +11466,10 @@ async function runInit(args, io, prompt, identity = () => apiJson(loadConfig(),
|
|
|
10905
11466
|
io.out("");
|
|
10906
11467
|
io.out("Next steps:");
|
|
10907
11468
|
io.out(" launchpad validate # confirm the manifest");
|
|
10908
|
-
|
|
10909
|
-
io.out(" launchpad secrets template # scaffold .env.example");
|
|
11469
|
+
io.out(" launchpad secrets template # scaffold .env.example");
|
|
10910
11470
|
io.out(" launchpad deploy # build and deploy the app");
|
|
10911
11471
|
if (dispatch)
|
|
10912
|
-
io.out("
|
|
11472
|
+
io.out(" Push secrets with `launchpad secrets push` once the first deploy has been delivered.");
|
|
10913
11473
|
return 0;
|
|
10914
11474
|
}
|
|
10915
11475
|
function parseArgs6(args) {
|
|
@@ -10961,11 +11521,11 @@ function parseArgs6(args) {
|
|
|
10961
11521
|
i++;
|
|
10962
11522
|
return next;
|
|
10963
11523
|
};
|
|
10964
|
-
const setFromNext = (
|
|
11524
|
+
const setFromNext = (assign2) => {
|
|
10965
11525
|
const got = needsValue();
|
|
10966
11526
|
if (typeof got !== "string")
|
|
10967
11527
|
return got;
|
|
10968
|
-
|
|
11528
|
+
assign2(got);
|
|
10969
11529
|
return;
|
|
10970
11530
|
};
|
|
10971
11531
|
if (a === "--name") {
|
|
@@ -11310,10 +11870,10 @@ function starterIndexHtml(slug) {
|
|
|
11310
11870
|
function renderYaml(manifest) {
|
|
11311
11871
|
return yamlStringify(manifest, { lineWidth: 0 });
|
|
11312
11872
|
}
|
|
11313
|
-
function ensureGitignoreEntries(
|
|
11873
|
+
function ensureGitignoreEntries(path11, entries) {
|
|
11314
11874
|
let current = "";
|
|
11315
|
-
if (
|
|
11316
|
-
current =
|
|
11875
|
+
if (existsSync11(path11)) {
|
|
11876
|
+
current = readFileSync16(path11, "utf8");
|
|
11317
11877
|
}
|
|
11318
11878
|
const lines = current.split(/\r?\n/);
|
|
11319
11879
|
const present = new Set(lines.map((l) => l.trim()));
|
|
@@ -11331,7 +11891,7 @@ function ensureGitignoreEntries(path10, entries) {
|
|
|
11331
11891
|
}
|
|
11332
11892
|
}
|
|
11333
11893
|
if (added.length > 0) {
|
|
11334
|
-
|
|
11894
|
+
writeFileSync4(path11, out, { encoding: "utf8" });
|
|
11335
11895
|
}
|
|
11336
11896
|
return added;
|
|
11337
11897
|
}
|
|
@@ -11503,7 +12063,7 @@ function describe23(e) {
|
|
|
11503
12063
|
}
|
|
11504
12064
|
|
|
11505
12065
|
// src/commands/logs.ts
|
|
11506
|
-
import * as
|
|
12066
|
+
import * as path11 from "node:path";
|
|
11507
12067
|
var logsCommand = {
|
|
11508
12068
|
name: "logs",
|
|
11509
12069
|
summary: "show recent Pages deployment history (slug-scoped)",
|
|
@@ -11525,7 +12085,7 @@ async function runLogs(args, io) {
|
|
|
11525
12085
|
} else {
|
|
11526
12086
|
const inferred = inferSlug({ cwd: process.cwd(), warn: (l) => io.err(l) });
|
|
11527
12087
|
if (inferred === null) {
|
|
11528
|
-
io.err(`launchpad logs: could not infer slug from cwd (${
|
|
12088
|
+
io.err(`launchpad logs: could not infer slug from cwd (${path11.basename(process.cwd())});
|
|
11529
12089
|
` + ` pass --slug <slug>, or cd into a directory named launchpad-app-<slug>.`);
|
|
11530
12090
|
return 64;
|
|
11531
12091
|
}
|
|
@@ -11653,7 +12213,7 @@ function describe24(e) {
|
|
|
11653
12213
|
}
|
|
11654
12214
|
|
|
11655
12215
|
// src/commands/merge.ts
|
|
11656
|
-
import * as
|
|
12216
|
+
import * as path12 from "node:path";
|
|
11657
12217
|
var mergeCommand = {
|
|
11658
12218
|
name: "merge",
|
|
11659
12219
|
summary: "squash-merge a review-passed PR (slug-scoped)",
|
|
@@ -11674,7 +12234,7 @@ async function runMerge(args, io) {
|
|
|
11674
12234
|
} else {
|
|
11675
12235
|
const inferred = inferSlug({ cwd: process.cwd(), warn: (l) => io.err(l) });
|
|
11676
12236
|
if (inferred === null) {
|
|
11677
|
-
io.err(`launchpad merge: could not infer slug from cwd (${
|
|
12237
|
+
io.err(`launchpad merge: could not infer slug from cwd (${path12.basename(process.cwd())});
|
|
11678
12238
|
` + ` pass --slug <slug>, or cd into a directory named launchpad-app-<slug>.`);
|
|
11679
12239
|
return 64;
|
|
11680
12240
|
}
|
|
@@ -11840,178 +12400,6 @@ function describe25(e) {
|
|
|
11840
12400
|
return e instanceof Error ? e.message : String(e);
|
|
11841
12401
|
}
|
|
11842
12402
|
|
|
11843
|
-
// src/commands/migrate.ts
|
|
11844
|
-
import { closeSync, existsSync as existsSync11, lstatSync as lstatSync4, mkdirSync as mkdirSync4, mkdtempSync, openSync, readFileSync as readFileSync16, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "node:fs";
|
|
11845
|
-
import path12 from "node:path";
|
|
11846
|
-
import { stringify } from "yaml";
|
|
11847
|
-
|
|
11848
|
-
// src/manifest/migrate-dispatch.ts
|
|
11849
|
-
function dispatchManifest(manifest) {
|
|
11850
|
-
const v2 = manifest.apiVersion === "launchpad.m-kopa.us/v2";
|
|
11851
|
-
const source = v2 ? manifest.spec : manifest;
|
|
11852
|
-
const appType = v2 ? manifest.spec.appType : manifest.deployment.type;
|
|
11853
|
-
if (appType !== "static" && appType !== "react") {
|
|
11854
|
-
throw new Error("Only static and React apps can use this converter. Pages Functions (react+api) need a compiled Worker build before switching to dispatch; containers use their own runtime.");
|
|
11855
|
-
}
|
|
11856
|
-
if (source.secrets !== undefined || source.containers !== undefined) {
|
|
11857
|
-
throw new Error("Secret and container bindings cannot be converted automatically.");
|
|
11858
|
-
}
|
|
11859
|
-
if (!v2)
|
|
11860
|
-
assertConvertibleV1(manifest);
|
|
11861
|
-
const spec = v2 ? { ...manifest.spec } : {
|
|
11862
|
-
appType,
|
|
11863
|
-
display: {
|
|
11864
|
-
name: manifest.metadata.name,
|
|
11865
|
-
team: manifest.metadata.team,
|
|
11866
|
-
...manifest.metadata.description === undefined ? {} : { description: manifest.metadata.description }
|
|
11867
|
-
},
|
|
11868
|
-
owner: { email: manifest.metadata.owner },
|
|
11869
|
-
access: {
|
|
11870
|
-
audience: manifest.access.audience,
|
|
11871
|
-
private_owner: manifest.access.private_owner,
|
|
11872
|
-
allowed_entra_groups: [...allowedEntraGroups(manifest.access)],
|
|
11873
|
-
identity_types: manifest.access.identity_types
|
|
11874
|
-
},
|
|
11875
|
-
build: manifest.build,
|
|
11876
|
-
app: manifest.app,
|
|
11877
|
-
verify: manifest.verify,
|
|
11878
|
-
sensitive: manifest.sensitive,
|
|
11879
|
-
catalogue: manifest.catalogue,
|
|
11880
|
-
...manifest.production_env === undefined ? {} : {
|
|
11881
|
-
env_vars: Object.fromEntries(Object.entries(manifest.production_env).map(([name, value]) => [name, { type: "plain_text", value }]))
|
|
11882
|
-
}
|
|
11883
|
-
};
|
|
11884
|
-
const candidate = {
|
|
11885
|
-
apiVersion: "launchpad.m-kopa.us/v2",
|
|
11886
|
-
kind: "App",
|
|
11887
|
-
metadata: v2 ? manifest.metadata : { slug: manifest.metadata.name, schemaVersion: 2 },
|
|
11888
|
-
spec: { ...spec, origin: "dispatch", auth: "gateway", confine_origin: undefined }
|
|
11889
|
-
};
|
|
11890
|
-
const parsed = parseManifestV2(candidate);
|
|
11891
|
-
if (parsed.kind !== "ok") {
|
|
11892
|
-
throw new Error(`Cannot convert manifest: ${parsed.issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`);
|
|
11893
|
-
}
|
|
11894
|
-
return parsed.manifest;
|
|
11895
|
-
}
|
|
11896
|
-
function assertConvertibleV1(manifest) {
|
|
11897
|
-
const unsupported2 = [
|
|
11898
|
-
manifest.hostnames !== undefined && "hostnames",
|
|
11899
|
-
manifest.targets !== undefined && "targets",
|
|
11900
|
-
manifest.access.session_duration !== undefined && "access.session_duration",
|
|
11901
|
-
manifest.deployment.runtime !== undefined && "deployment.runtime"
|
|
11902
|
-
].filter(Boolean);
|
|
11903
|
-
if (unsupported2.length > 0) {
|
|
11904
|
-
throw new Error(`Cannot preserve ${unsupported2.join(", ")} on dispatch. Review these settings with the platform team before migrating.`);
|
|
11905
|
-
}
|
|
11906
|
-
}
|
|
11907
|
-
|
|
11908
|
-
// src/commands/migrate.ts
|
|
11909
|
-
var USAGE = "usage: launchpad migrate --origin dispatch [--dry-run]";
|
|
11910
|
-
var migrateCommand = {
|
|
11911
|
-
name: "migrate",
|
|
11912
|
-
summary: "convert an unprovisioned static or React app to Workers dispatch",
|
|
11913
|
-
run: runMigrate
|
|
11914
|
-
};
|
|
11915
|
-
async function runMigrate(args, io) {
|
|
11916
|
-
if (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) {
|
|
11917
|
-
io.out(USAGE);
|
|
11918
|
-
return 0;
|
|
11919
|
-
}
|
|
11920
|
-
const flags = args.filter((arg) => arg !== "--dry-run");
|
|
11921
|
-
if (flags.length !== 2 || flags[0] !== "--origin" || flags[1] !== "dispatch" || args.length > 3) {
|
|
11922
|
-
io.err(USAGE);
|
|
11923
|
-
return 64;
|
|
11924
|
-
}
|
|
11925
|
-
const cwd = process.cwd();
|
|
11926
|
-
const file = path12.join(cwd, "launchpad.yaml");
|
|
11927
|
-
try {
|
|
11928
|
-
if (!lstatSync4(file).isFile())
|
|
11929
|
-
throw new Error("launchpad.yaml must be a regular file, not a symlink.");
|
|
11930
|
-
const original = readFileSync16(file, "utf8");
|
|
11931
|
-
const loaded = parseManifestAnyVersion(original, file);
|
|
11932
|
-
if (loaded.kind !== "ok")
|
|
11933
|
-
throw new Error("launchpad.yaml is invalid. Run `launchpad validate` and correct it first.");
|
|
11934
|
-
const manifest = loaded.manifest;
|
|
11935
|
-
if (manifest.apiVersion === "launchpad.m-kopa.us/v2" && manifest.spec.origin === "dispatch") {
|
|
11936
|
-
io.out("launchpad.yaml already selects Workers dispatch. Run `launchpad deploy`.");
|
|
11937
|
-
return 0;
|
|
11938
|
-
}
|
|
11939
|
-
const converted = dispatchManifest(manifest);
|
|
11940
|
-
assertNoCustomRuntime(cwd, converted);
|
|
11941
|
-
const slug = converted.metadata.slug;
|
|
11942
|
-
await assertUnregistered(slug);
|
|
11943
|
-
if (args.includes("--dry-run")) {
|
|
11944
|
-
io.out(`${slug}: eligible for v2 Workers dispatch with gateway authentication. No files changed.`);
|
|
11945
|
-
return 0;
|
|
11946
|
-
}
|
|
11947
|
-
saveMigration(file, original, converted);
|
|
11948
|
-
io.out(`${slug}: launchpad.yaml now uses v2 Workers dispatch with gateway authentication.`);
|
|
11949
|
-
io.out("Audience, owner and application settings are preserved. Original: .launchpad/manifest-before-dispatch.yaml");
|
|
11950
|
-
io.out("Run `launchpad deploy`. No Pages project is needed.");
|
|
11951
|
-
return 0;
|
|
11952
|
-
} catch (error) {
|
|
11953
|
-
io.err(`launchpad migrate: ${error instanceof Error ? error.message : String(error)}`);
|
|
11954
|
-
return 1;
|
|
11955
|
-
}
|
|
11956
|
-
}
|
|
11957
|
-
async function assertUnregistered(slug) {
|
|
11958
|
-
try {
|
|
11959
|
-
await apiRaw(loadConfig(), { path: `/apps/${encodeURIComponent(slug)}/manifest/state` });
|
|
11960
|
-
} catch (error) {
|
|
11961
|
-
if (error instanceof NotFoundError && error.serverCode === "not_found")
|
|
11962
|
-
return;
|
|
11963
|
-
throw error;
|
|
11964
|
-
}
|
|
11965
|
-
throw new Error("This app is already registered. This command only converts apps that have never provisioned; ask the platform team to migrate an existing app.");
|
|
11966
|
-
}
|
|
11967
|
-
function assertNoCustomRuntime(cwd, manifest) {
|
|
11968
|
-
const appRoot = path12.resolve(cwd, manifest.spec.app?.root ?? ".");
|
|
11969
|
-
const buildRoot = path12.resolve(appRoot, manifest.spec.build?.root_dir ?? ".");
|
|
11970
|
-
const roots = new Set([
|
|
11971
|
-
cwd,
|
|
11972
|
-
appRoot,
|
|
11973
|
-
buildRoot,
|
|
11974
|
-
path12.resolve(cwd, manifest.spec.build?.root_dir ?? "."),
|
|
11975
|
-
path12.resolve(buildRoot, manifest.spec.build?.destination_dir ?? "dist")
|
|
11976
|
-
]);
|
|
11977
|
-
for (const root of roots) {
|
|
11978
|
-
for (const name of ["functions", "_worker.js", "wrangler.toml", "wrangler.json", "wrangler.jsonc"]) {
|
|
11979
|
-
if (existsSync11(path12.join(root, name))) {
|
|
11980
|
-
throw new Error(`${name} needs a runtime review before conversion. Pages Functions and custom Worker bindings must not be dropped. Ask the platform team to review this app.`);
|
|
11981
|
-
}
|
|
11982
|
-
}
|
|
11983
|
-
}
|
|
11984
|
-
}
|
|
11985
|
-
function saveMigration(file, original, converted) {
|
|
11986
|
-
if (readFileSync16(file, "utf8") !== original)
|
|
11987
|
-
throw new Error("launchpad.yaml changed during the check. Retry migration.");
|
|
11988
|
-
const backupDir = path12.join(path12.dirname(file), ".launchpad");
|
|
11989
|
-
mkdirSync4(backupDir, { recursive: true });
|
|
11990
|
-
const backup = path12.join(backupDir, "manifest-before-dispatch.yaml");
|
|
11991
|
-
const attemptDir = mkdtempSync(path12.join(backupDir, "migration-"));
|
|
11992
|
-
let backupCreated = false;
|
|
11993
|
-
try {
|
|
11994
|
-
const fd = openSync(backup, "wx", 384);
|
|
11995
|
-
backupCreated = true;
|
|
11996
|
-
try {
|
|
11997
|
-
writeFileSync4(fd, original);
|
|
11998
|
-
} finally {
|
|
11999
|
-
closeSync(fd);
|
|
12000
|
-
}
|
|
12001
|
-
const temporary = path12.join(attemptDir, "launchpad.yaml");
|
|
12002
|
-
writeFileSync4(temporary, stringify(converted), { flag: "wx", mode: 384 });
|
|
12003
|
-
if (readFileSync16(file, "utf8") !== original)
|
|
12004
|
-
throw new Error("launchpad.yaml changed during migration. Retry migration.");
|
|
12005
|
-
renameSync(temporary, file);
|
|
12006
|
-
} catch (error) {
|
|
12007
|
-
if (backupCreated)
|
|
12008
|
-
rmSync2(backup);
|
|
12009
|
-
throw error;
|
|
12010
|
-
} finally {
|
|
12011
|
-
rmSync2(attemptDir, { recursive: true, force: true });
|
|
12012
|
-
}
|
|
12013
|
-
}
|
|
12014
|
-
|
|
12015
12403
|
// src/commands/plan.ts
|
|
12016
12404
|
import { resolve as resolve12 } from "node:path";
|
|
12017
12405
|
var planCommand = {
|
|
@@ -13892,6 +14280,22 @@ function parseEnv(text) {
|
|
|
13892
14280
|
return { entries, invalidNames, malformedLines };
|
|
13893
14281
|
}
|
|
13894
14282
|
|
|
14283
|
+
// src/secrets/declared.ts
|
|
14284
|
+
function declaredSecrets(loaded) {
|
|
14285
|
+
if (loaded.schema === "launchpad.m-kopa.us/v1alpha1") {
|
|
14286
|
+
const m2 = loaded.manifest;
|
|
14287
|
+
return { slug: m2.metadata.name, apiVersion: m2.apiVersion, bindings: m2.secrets?.bindings ?? [] };
|
|
14288
|
+
}
|
|
14289
|
+
const m = loaded.manifest;
|
|
14290
|
+
const envVars = m.spec.appType === "container" ? {} : m.spec.env_vars ?? {};
|
|
14291
|
+
const fromEnvVars = Object.entries(envVars).filter(([, v]) => v.type === "secret_text").map(([name]) => ({ name, source: "env-file" }));
|
|
14292
|
+
return {
|
|
14293
|
+
slug: m.metadata.slug,
|
|
14294
|
+
apiVersion: m.apiVersion,
|
|
14295
|
+
bindings: [...m.spec.secrets?.bindings ?? [], ...fromEnvVars]
|
|
14296
|
+
};
|
|
14297
|
+
}
|
|
14298
|
+
|
|
13895
14299
|
// src/secrets/push.ts
|
|
13896
14300
|
async function runSecretsPush(opts, io, deps = {}) {
|
|
13897
14301
|
const cfg = deps.config ?? loadConfig();
|
|
@@ -13901,14 +14305,10 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
13901
14305
|
if (loaded.kind !== "ok") {
|
|
13902
14306
|
return renderManifestError4(loaded, io);
|
|
13903
14307
|
}
|
|
13904
|
-
const
|
|
13905
|
-
if (v2Refusal !== null)
|
|
13906
|
-
return v2Refusal;
|
|
13907
|
-
const manifest = loaded.manifest;
|
|
13908
|
-
const bindings = manifest.secrets?.bindings ?? [];
|
|
14308
|
+
const { slug, bindings } = declaredSecrets(loaded);
|
|
13909
14309
|
const envFileBindings = bindings.filter((b) => b.source === "env-file");
|
|
13910
14310
|
if (envFileBindings.length === 0) {
|
|
13911
|
-
io.out("No `env-file`
|
|
14311
|
+
io.out("No secrets to push: the manifest declares no `env-file` bindings or `secret_text` env vars.");
|
|
13912
14312
|
io.out("(platform-managed bindings are pushed via the platform-team ops path.)");
|
|
13913
14313
|
return 0;
|
|
13914
14314
|
}
|
|
@@ -13987,11 +14387,11 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
13987
14387
|
io.out("Re-run without --dry-run to push.");
|
|
13988
14388
|
return 0;
|
|
13989
14389
|
}
|
|
13990
|
-
const slug = manifest.metadata.name;
|
|
13991
14390
|
let pushed = 0;
|
|
13992
14391
|
let partial = 0;
|
|
13993
14392
|
let anyPagesUpdated = false;
|
|
13994
14393
|
let anyWorkerUpdated = false;
|
|
14394
|
+
let anyDispatchUpdated = false;
|
|
13995
14395
|
for (const b of envFileBindings) {
|
|
13996
14396
|
const value = parsed.entries.get(b.name);
|
|
13997
14397
|
io.out(`Pushing ${b.name} …`);
|
|
@@ -14012,6 +14412,11 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
14012
14412
|
io.err(`✗ secrets push ${b.name}: invalid bot response (results must be an array)`);
|
|
14013
14413
|
return 2;
|
|
14014
14414
|
}
|
|
14415
|
+
if (resp.error === "dispatch_script_not_delivered") {
|
|
14416
|
+
io.err(`✗ secrets push ${b.name}: ${resp.message ?? "the dispatch script is not delivered yet"}`);
|
|
14417
|
+
io.err(`${pushed} of ${envFileBindings.length} pushed before failure.`);
|
|
14418
|
+
return 1;
|
|
14419
|
+
}
|
|
14015
14420
|
const results = resp.results ?? [];
|
|
14016
14421
|
for (const r of results) {
|
|
14017
14422
|
if (r.status !== "updated")
|
|
@@ -14020,6 +14425,8 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
14020
14425
|
anyPagesUpdated = true;
|
|
14021
14426
|
else if (r.kind === "worker")
|
|
14022
14427
|
anyWorkerUpdated = true;
|
|
14428
|
+
else if (r.kind === "dispatch")
|
|
14429
|
+
anyDispatchUpdated = true;
|
|
14023
14430
|
}
|
|
14024
14431
|
const notUpdated = results.filter((r) => r.status !== "updated");
|
|
14025
14432
|
if (resp.updated === true && notUpdated.length === 0) {
|
|
@@ -14043,7 +14450,9 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
14043
14450
|
if (anyPagesUpdated) {
|
|
14044
14451
|
return offerRedeploy(slug, opts, io, deps);
|
|
14045
14452
|
}
|
|
14046
|
-
if (
|
|
14453
|
+
if (anyDispatchUpdated) {
|
|
14454
|
+
io.out("Set on the app's dispatch Worker and live now - no redeploy needed.");
|
|
14455
|
+
} else if (anyWorkerUpdated) {
|
|
14047
14456
|
io.out("These secrets bind on the app's next invocation — no redeploy needed.");
|
|
14048
14457
|
}
|
|
14049
14458
|
return 0;
|
|
@@ -14112,20 +14521,6 @@ function mapPushError(e, slug, name, io) {
|
|
|
14112
14521
|
function describe30(e) {
|
|
14113
14522
|
return e instanceof Error ? e.message : String(e);
|
|
14114
14523
|
}
|
|
14115
|
-
function refuseV2Manifest(loaded, command, io) {
|
|
14116
|
-
if (loaded.schema !== "launchpad.m-kopa.us/v2")
|
|
14117
|
-
return null;
|
|
14118
|
-
const origin = loaded.manifest.spec?.origin;
|
|
14119
|
-
if (origin === "dispatch") {
|
|
14120
|
-
io.err(`${command}: this is a dispatch-origin (v2) app.`);
|
|
14121
|
-
const outcome = command.endsWith("push") ? "nothing was pushed" : "nothing was read";
|
|
14122
|
-
io.err(` Secrets are not delivered to the dispatch namespace yet (PS-2740); ${outcome}.`);
|
|
14123
|
-
return 1;
|
|
14124
|
-
}
|
|
14125
|
-
io.err(`${command}: this is a launchpad.m-kopa.us/v2 manifest.`);
|
|
14126
|
-
io.err(" This command reads v1alpha1 `secrets.bindings`; v2 declares secrets as `spec.env_vars` `secret_text` entries, which it does not push yet.");
|
|
14127
|
-
return 1;
|
|
14128
|
-
}
|
|
14129
14524
|
function renderManifestError4(result, io) {
|
|
14130
14525
|
switch (result.kind) {
|
|
14131
14526
|
case "ok":
|
|
@@ -14166,17 +14561,12 @@ async function runSecretsStatus(opts, io, deps = {}) {
|
|
|
14166
14561
|
const loaded = loadManifestAnyVersion(manifestPath);
|
|
14167
14562
|
if (loaded.kind !== "ok")
|
|
14168
14563
|
return renderManifestError5(loaded, io);
|
|
14169
|
-
const
|
|
14170
|
-
if (v2Refusal !== null)
|
|
14171
|
-
return v2Refusal;
|
|
14172
|
-
const manifest = loaded.manifest;
|
|
14173
|
-
const slug = manifest.metadata.name;
|
|
14564
|
+
const { slug, bindings } = declaredSecrets(loaded);
|
|
14174
14565
|
if (opts.slug != null && opts.slug !== slug) {
|
|
14175
14566
|
io.err(`launchpad secrets status: slug "${opts.slug}" does not match this directory's ` + `manifest (metadata.name: "${slug}").`);
|
|
14176
14567
|
io.err(" secrets status audits the manifest in the current directory — omit the slug, " + "or run from the app's directory.");
|
|
14177
14568
|
return 2;
|
|
14178
14569
|
}
|
|
14179
|
-
const bindings = manifest.secrets?.bindings ?? [];
|
|
14180
14570
|
if (bindings.length === 0) {
|
|
14181
14571
|
io.out("No secret bindings declared in the manifest.");
|
|
14182
14572
|
return 0;
|
|
@@ -14526,12 +14916,12 @@ async function runSecretsTemplate(args, io) {
|
|
|
14526
14916
|
return 64;
|
|
14527
14917
|
}
|
|
14528
14918
|
const manifestPath = resolve14(process.cwd(), flags.file ?? "launchpad.yaml");
|
|
14529
|
-
const
|
|
14530
|
-
const renderResult = renderManifest(result, io);
|
|
14919
|
+
const renderResult = renderManifest(loadManifestAnyVersion(manifestPath), io);
|
|
14531
14920
|
if (renderResult.kind !== "ok") {
|
|
14532
14921
|
return renderResult.code;
|
|
14533
14922
|
}
|
|
14534
|
-
const
|
|
14923
|
+
const declared = renderResult.declared;
|
|
14924
|
+
const body = buildTemplate(declared, flags.includePlatformManaged);
|
|
14535
14925
|
if (flags.toStdout) {
|
|
14536
14926
|
for (const line of body.split(`
|
|
14537
14927
|
`)) {
|
|
@@ -14554,14 +14944,14 @@ async function runSecretsTemplate(args, io) {
|
|
|
14554
14944
|
return 2;
|
|
14555
14945
|
}
|
|
14556
14946
|
io.out(`✓ wrote ${outPath}`);
|
|
14557
|
-
const
|
|
14558
|
-
io.out(` ${
|
|
14947
|
+
const written = declared.bindings.filter((b) => b.source === "env-file").length;
|
|
14948
|
+
io.out(` ${written} binding${written === 1 ? "" : "s"} scaffolded`);
|
|
14559
14949
|
return 0;
|
|
14560
14950
|
}
|
|
14561
14951
|
function renderManifest(result, io) {
|
|
14562
14952
|
switch (result.kind) {
|
|
14563
14953
|
case "ok":
|
|
14564
|
-
return { kind: "ok",
|
|
14954
|
+
return { kind: "ok", declared: declaredSecrets(result) };
|
|
14565
14955
|
case "file-not-found":
|
|
14566
14956
|
io.err(`launchpad secrets template: ${result.path}`);
|
|
14567
14957
|
io.err(" manifest not found");
|
|
@@ -14583,19 +14973,19 @@ function renderManifest(result, io) {
|
|
|
14583
14973
|
return { kind: "fail", code: 1 };
|
|
14584
14974
|
}
|
|
14585
14975
|
}
|
|
14586
|
-
function buildTemplate(
|
|
14587
|
-
const bindings =
|
|
14976
|
+
function buildTemplate(declared, includePlatformManaged) {
|
|
14977
|
+
const { bindings } = declared;
|
|
14588
14978
|
const lines = [];
|
|
14589
14979
|
lines.push("# Auto-generated by `launchpad secrets template` — do not commit values.");
|
|
14590
|
-
lines.push(`# Source: launchpad.yaml (apiVersion: ${
|
|
14591
|
-
lines.push(`# App: ${
|
|
14980
|
+
lines.push(`# Source: launchpad.yaml (apiVersion: ${declared.apiVersion})`);
|
|
14981
|
+
lines.push(`# App: ${declared.slug}`);
|
|
14592
14982
|
lines.push("#");
|
|
14593
14983
|
lines.push("# Fill in values, then run `launchpad secrets push`.");
|
|
14594
14984
|
lines.push("# This file is meant to be committed; the real `.env` must be gitignored.");
|
|
14595
14985
|
lines.push("");
|
|
14596
14986
|
if (bindings.length === 0) {
|
|
14597
14987
|
lines.push("# No secret bindings declared in launchpad.yaml.");
|
|
14598
|
-
lines.push("# Add entries under `secrets.bindings[]` and re-run this command.");
|
|
14988
|
+
lines.push("# Add entries under `secrets.bindings[]` (v1alpha1) or `spec.env_vars` with " + "`type: secret_text` (v2) and re-run this command.");
|
|
14599
14989
|
lines.push("");
|
|
14600
14990
|
return lines.join(`
|
|
14601
14991
|
`);
|
|
@@ -14629,11 +15019,6 @@ function buildTemplate(manifest, includePlatformManaged) {
|
|
|
14629
15019
|
return lines.join(`
|
|
14630
15020
|
`);
|
|
14631
15021
|
}
|
|
14632
|
-
function countBindings(manifest) {
|
|
14633
|
-
const bindings = manifest.secrets?.bindings ?? [];
|
|
14634
|
-
const written = bindings.filter((b) => b.source === "env-file").length;
|
|
14635
|
-
return { total: bindings.length, written };
|
|
14636
|
-
}
|
|
14637
15022
|
function parseFlags4(args) {
|
|
14638
15023
|
let file;
|
|
14639
15024
|
let out = ".env.example";
|
|
@@ -14842,7 +15227,7 @@ import { promises as fs7, existsSync as existsSync16 } from "node:fs";
|
|
|
14842
15227
|
// src/skills-bundle.ts
|
|
14843
15228
|
import { homedir as homedir2 } from "node:os";
|
|
14844
15229
|
import { join as join15 } from "node:path";
|
|
14845
|
-
import { readFileSync as readFileSync20, readdirSync as
|
|
15230
|
+
import { readFileSync as readFileSync20, readdirSync as readdirSync3 } from "node:fs";
|
|
14846
15231
|
var SKILL_PREFIX = "launchpad-";
|
|
14847
15232
|
function skillsTargetDir() {
|
|
14848
15233
|
return process.env.LAUNCHPAD_SKILLS_TARGET_DIR ?? join15(homedir2(), ".claude", "skills");
|
|
@@ -14850,7 +15235,7 @@ function skillsTargetDir() {
|
|
|
14850
15235
|
function readInstalledSkills() {
|
|
14851
15236
|
try {
|
|
14852
15237
|
const dir = skillsTargetDir();
|
|
14853
|
-
const bundle =
|
|
15238
|
+
const bundle = readdirSync3(dir, { withFileTypes: true }).find((e) => e.isDirectory() && e.name.startsWith(SKILL_PREFIX));
|
|
14854
15239
|
if (!bundle)
|
|
14855
15240
|
return { present: false, version: null };
|
|
14856
15241
|
return {
|
|
@@ -15540,7 +15925,7 @@ function printHelp5(io) {
|
|
|
15540
15925
|
}
|
|
15541
15926
|
|
|
15542
15927
|
// src/commands/validate.ts
|
|
15543
|
-
import { existsSync as existsSync17, readFileSync as readFileSync22, readdirSync as
|
|
15928
|
+
import { existsSync as existsSync17, readFileSync as readFileSync22, readdirSync as readdirSync4, statSync as statSync2 } from "node:fs";
|
|
15544
15929
|
import { dirname as dirname12, resolve as resolve17 } from "node:path";
|
|
15545
15930
|
var validateCommand = {
|
|
15546
15931
|
name: "validate",
|
|
@@ -15631,7 +16016,7 @@ function checkStaticServeDir(manifestPath, manifest) {
|
|
|
15631
16016
|
}
|
|
15632
16017
|
let entries;
|
|
15633
16018
|
try {
|
|
15634
|
-
entries =
|
|
16019
|
+
entries = readdirSync4(abs);
|
|
15635
16020
|
} catch (e) {
|
|
15636
16021
|
return [`failed to read static served directory '${serveDir}/': ${describe32(e)}`];
|
|
15637
16022
|
}
|