@m-kopa/launchpad-cli 0.63.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 +23 -0
- package/dist/cli.js +418 -115
- package/dist/commands/deploy.d.ts +1 -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 +4 -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 +1 -1
- package/skills/launchpad-deploy/SKILL.md +27 -7
- 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 -5
- package/skills/launchpad-report/SKILL.md +1 -1
- package/skills/launchpad-status/SKILL.md +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,29 @@ pre-1.0 minor bumps may carry breaking changes per ADR 0005.
|
|
|
8
8
|
|
|
9
9
|
## Unreleased
|
|
10
10
|
|
|
11
|
+
## 0.64.0 - 2026-09-25
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- New `react+api` apps default to Workers dispatch too.
|
|
16
|
+
`launchpad init --type react+api` writes a v2 dispatch manifest with `functions/**` in the bundle; `--origin pages` still opts out.
|
|
17
|
+
A first `launchpad deploy` (or `launchpad migrate --origin dispatch`) of an unregistered React API app with a Pages-implied manifest converts it the same way static and React apps already convert.
|
|
18
|
+
`functions/` is compiled by the dispatch build, a v1 pages-target `d1_binding` or a single wrangler `d1_databases` entry becomes `spec.bindings.d1`, `production_env` values become plain-text env vars, and declared `env-file` secrets become `secret_text` env vars with a reminder to run `launchpad secrets push` once the first deploy is delivered.
|
|
19
|
+
The app stays on Pages with the reason printed when its wrangler config sets anything else (KV, R2, services, vars, Durable Objects and so on, named in the message), its functions have a root `_middleware` or root-level dynamic route that would claim every path, it ships a hand-written `_worker.js`, or a secret is platform-managed, targets another surface, or uses a name the platform sets.
|
|
20
|
+
- `launchpad validate` refuses a v2 env var with the same name as `spec.bindings.d1`.
|
|
21
|
+
- `launchpad secrets push` works for dispatch-origin apps.
|
|
22
|
+
The value goes to the app's Worker script in the dispatch namespace and is live immediately, so there is no redeploy prompt.
|
|
23
|
+
Pushing before the first deploy has been delivered stops with `dispatch_script_not_delivered` and writes nothing.
|
|
24
|
+
Fleet secret-set pushes reach dispatch members the same way.
|
|
25
|
+
- `secrets template`, `push` and `status` read v2 manifests of either origin, taking each `spec.env_vars` entry of `type: secret_text` as a secret.
|
|
26
|
+
v2 dispatch manifests may now declare `secret_text` env vars.
|
|
27
|
+
- Dispatch `react+api` apps can declare a D1 database as `spec.bindings.d1`, and `launchpad validate` accepts it.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- Raise the production minimum CLI version to 0.63.0 through the governed policy workflow.
|
|
32
|
+
Older stable clients must run `launchpad update` before platform mutations.
|
|
33
|
+
|
|
11
34
|
## 0.63.0 - 2026-09-25
|
|
12
35
|
|
|
13
36
|
### Changed
|
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";
|
|
@@ -3363,7 +3363,10 @@ var SpecSchema = z3.object({
|
|
|
3363
3363
|
env_vars: z3.record(z3.string().regex(ENV_NAME_REGEX, "env_vars keys must be UPPER_SNAKE_CASE"), EnvVarSchema).optional(),
|
|
3364
3364
|
containers: z3.array(ContainerSchema).min(1).optional(),
|
|
3365
3365
|
secrets: SecretsSchema.optional(),
|
|
3366
|
-
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()
|
|
3367
3370
|
}).strict().superRefine((s, ctx) => {
|
|
3368
3371
|
if (s.access.identity_types !== undefined && (s.auth ?? "access") !== "gateway") {
|
|
3369
3372
|
ctx.addIssue({
|
|
@@ -3432,14 +3435,27 @@ var SpecSchema = z3.object({
|
|
|
3432
3435
|
message: "confine_origin has no meaning with origin: dispatch — there is no public pages.dev apex to confine. Remove it."
|
|
3433
3436
|
});
|
|
3434
3437
|
}
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
}
|
|
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
|
+
});
|
|
3443
3459
|
}
|
|
3444
3460
|
}
|
|
3445
3461
|
if (!isContainer && s.containers !== undefined) {
|
|
@@ -4449,7 +4465,7 @@ function deploymentAuthIssue(appType, auth) {
|
|
|
4449
4465
|
function newAppOrigin(appType, requested) {
|
|
4450
4466
|
if (appType === "container")
|
|
4451
4467
|
return;
|
|
4452
|
-
return requested ??
|
|
4468
|
+
return requested ?? "dispatch";
|
|
4453
4469
|
}
|
|
4454
4470
|
// src/bundle/boundary.ts
|
|
4455
4471
|
var MAX_CONTENT_PROBE_BYTES = 1024 * 1024;
|
|
@@ -8620,24 +8636,32 @@ function rejectUnsupportedCliRuntime(args) {
|
|
|
8620
8636
|
}
|
|
8621
8637
|
|
|
8622
8638
|
// src/commands/migrate.ts
|
|
8623
|
-
import { closeSync, existsSync as existsSync7, lstatSync as lstatSync4, mkdirSync as mkdirSync2, mkdtempSync, openSync, readFileSync as readFileSync11, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
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";
|
|
8624
8640
|
import path8 from "node:path";
|
|
8625
8641
|
import { stringify } from "yaml";
|
|
8626
8642
|
|
|
8627
8643
|
// src/manifest/migrate-dispatch.ts
|
|
8628
|
-
|
|
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) {
|
|
8629
8653
|
const v2 = manifest.apiVersion === "launchpad.m-kopa.us/v2";
|
|
8630
8654
|
const source = v2 ? manifest.spec : manifest;
|
|
8631
8655
|
const appType = v2 ? manifest.spec.appType : manifest.deployment.type;
|
|
8632
|
-
if (appType
|
|
8633
|
-
throw new Error("
|
|
8634
|
-
}
|
|
8635
|
-
if (source.secrets !== undefined || source.containers !== undefined) {
|
|
8636
|
-
throw new Error("Secret and container bindings cannot be converted automatically.");
|
|
8656
|
+
if (appType === "container" || source.containers !== undefined) {
|
|
8657
|
+
throw new Error("Containers use their own runtime and cannot convert to dispatch.");
|
|
8637
8658
|
}
|
|
8638
8659
|
if (!v2)
|
|
8639
8660
|
assertConvertibleV1(manifest);
|
|
8640
|
-
const
|
|
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 } : {
|
|
8641
8665
|
appType,
|
|
8642
8666
|
display: {
|
|
8643
8667
|
name: manifest.metadata.name,
|
|
@@ -8655,16 +8679,20 @@ function dispatchManifest(manifest) {
|
|
|
8655
8679
|
app: manifest.app,
|
|
8656
8680
|
verify: manifest.verify,
|
|
8657
8681
|
sensitive: manifest.sensitive,
|
|
8658
|
-
catalogue: manifest.catalogue
|
|
8659
|
-
...manifest.production_env === undefined ? {} : {
|
|
8660
|
-
env_vars: Object.fromEntries(Object.entries(manifest.production_env).map(([name, value]) => [name, { type: "plain_text", value }]))
|
|
8661
|
-
}
|
|
8682
|
+
catalogue: manifest.catalogue
|
|
8662
8683
|
};
|
|
8663
8684
|
const candidate = {
|
|
8664
8685
|
apiVersion: "launchpad.m-kopa.us/v2",
|
|
8665
8686
|
kind: "App",
|
|
8666
8687
|
metadata: v2 ? manifest.metadata : { slug: manifest.metadata.name, schemaVersion: 2 },
|
|
8667
|
-
spec: {
|
|
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
|
+
}
|
|
8668
8696
|
};
|
|
8669
8697
|
const parsed = parseManifestV2(candidate);
|
|
8670
8698
|
if (parsed.kind !== "ok") {
|
|
@@ -8675,7 +8703,7 @@ function dispatchManifest(manifest) {
|
|
|
8675
8703
|
function assertConvertibleV1(manifest) {
|
|
8676
8704
|
const unsupported2 = [
|
|
8677
8705
|
manifest.hostnames !== undefined && "hostnames",
|
|
8678
|
-
manifest.targets !== undefined && "targets",
|
|
8706
|
+
manifest.targets !== undefined && pagesTargetD1(manifest) === undefined && "targets",
|
|
8679
8707
|
manifest.access.session_duration !== undefined && "access.session_duration",
|
|
8680
8708
|
manifest.deployment.runtime !== undefined && "deployment.runtime"
|
|
8681
8709
|
].filter(Boolean);
|
|
@@ -8683,12 +8711,224 @@ function assertConvertibleV1(manifest) {
|
|
|
8683
8711
|
throw new Error(`Cannot preserve ${unsupported2.join(", ")} on dispatch. Review these settings with the platform team before migrating.`);
|
|
8684
8712
|
}
|
|
8685
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
|
+
}
|
|
8686
8926
|
|
|
8687
8927
|
// src/commands/migrate.ts
|
|
8688
8928
|
var USAGE = "usage: launchpad migrate --origin dispatch [--dry-run]";
|
|
8689
8929
|
var migrateCommand = {
|
|
8690
8930
|
name: "migrate",
|
|
8691
|
-
summary: "convert an unprovisioned static or React app to Workers dispatch",
|
|
8931
|
+
summary: "convert an unprovisioned static, React or React API app to Workers dispatch",
|
|
8692
8932
|
run: runMigrate
|
|
8693
8933
|
};
|
|
8694
8934
|
async function runMigrate(args, io) {
|
|
@@ -8711,6 +8951,9 @@ async function runMigrate(args, io) {
|
|
|
8711
8951
|
io.out(`${result.slug}: launchpad.yaml now uses v2 Workers dispatch with gateway authentication.`);
|
|
8712
8952
|
io.out(`Audience, owner and application settings are preserved. Original: ${BACKUP_PATH}`);
|
|
8713
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);
|
|
8714
8957
|
}
|
|
8715
8958
|
return 0;
|
|
8716
8959
|
} catch (error) {
|
|
@@ -8719,6 +8962,11 @@ async function runMigrate(args, io) {
|
|
|
8719
8962
|
}
|
|
8720
8963
|
}
|
|
8721
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
|
+
}
|
|
8722
8970
|
async function convertToDispatch(cwd, dryRun) {
|
|
8723
8971
|
const file = path8.join(cwd, "launchpad.yaml");
|
|
8724
8972
|
if (!lstatSync4(file).isFile())
|
|
@@ -8730,18 +8978,38 @@ async function convertToDispatch(cwd, dryRun) {
|
|
|
8730
8978
|
const manifest = loaded.manifest;
|
|
8731
8979
|
if (manifest.apiVersion === "launchpad.m-kopa.us/v2" && manifest.spec.origin === "dispatch")
|
|
8732
8980
|
return { kind: "already" };
|
|
8733
|
-
const
|
|
8734
|
-
|
|
8735
|
-
|
|
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
|
+
}
|
|
8736
8989
|
await assertUnregistered(slug);
|
|
8990
|
+
const secrets = Object.entries(converted.spec.env_vars ?? {}).filter(([, value]) => value.type === "secret_text").map(([name]) => name);
|
|
8737
8991
|
if (dryRun)
|
|
8738
|
-
return { kind: "eligible", slug };
|
|
8992
|
+
return { kind: "eligible", slug, secrets };
|
|
8739
8993
|
try {
|
|
8740
8994
|
saveMigration(file, original, converted);
|
|
8741
8995
|
} catch (error) {
|
|
8742
8996
|
throw new MigrationSaveError(error);
|
|
8743
8997
|
}
|
|
8744
|
-
return { kind: "converted", slug };
|
|
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
|
+
}
|
|
8745
9013
|
}
|
|
8746
9014
|
async function assertUnregistered(slug) {
|
|
8747
9015
|
try {
|
|
@@ -8773,23 +9041,54 @@ class RegisteredAppError extends Error {
|
|
|
8773
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.");
|
|
8774
9042
|
}
|
|
8775
9043
|
}
|
|
8776
|
-
|
|
8777
|
-
|
|
8778
|
-
const
|
|
8779
|
-
const
|
|
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([
|
|
8780
9064
|
cwd,
|
|
8781
9065
|
appRoot,
|
|
8782
9066
|
buildRoot,
|
|
8783
|
-
path8.resolve(cwd,
|
|
8784
|
-
path8.resolve(buildRoot,
|
|
9067
|
+
path8.resolve(cwd, build?.root_dir ?? "."),
|
|
9068
|
+
path8.resolve(buildRoot, build?.destination_dir ?? "dist")
|
|
8785
9069
|
]);
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
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.`);
|
|
8791
9076
|
}
|
|
8792
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
|
+
}
|
|
8793
9092
|
}
|
|
8794
9093
|
function saveMigration(file, original, converted) {
|
|
8795
9094
|
if (readFileSync11(file, "utf8") !== original)
|
|
@@ -9602,24 +9901,12 @@ function isIneligible(e) {
|
|
|
9602
9901
|
return !(e instanceof RegistryUnavailableError || e instanceof MigrationSaveError || e instanceof UpgradeRequiredError || e instanceof UnauthenticatedError);
|
|
9603
9902
|
}
|
|
9604
9903
|
async function defaultToDispatch(cwd, manifestPath, io) {
|
|
9605
|
-
|
|
9606
|
-
try {
|
|
9607
|
-
raw = parseYaml6(readFileSync12(manifestPath, "utf8"));
|
|
9608
|
-
} catch {
|
|
9609
|
-
return null;
|
|
9610
|
-
}
|
|
9611
|
-
const isV22 = raw?.apiVersion === "launchpad.m-kopa.us/v2";
|
|
9612
|
-
const appType = isV22 ? raw?.spec?.appType : raw?.deployment?.type;
|
|
9613
|
-
const origin = isV22 ? raw?.spec?.origin : raw?.origin;
|
|
9614
|
-
const auth = isV22 ? raw?.spec?.auth : raw?.auth;
|
|
9615
|
-
if (appType !== "static" && appType !== "react" || origin !== undefined || auth !== "gateway")
|
|
9904
|
+
if (!impliesPagesForGateway(manifestPath))
|
|
9616
9905
|
return null;
|
|
9617
9906
|
try {
|
|
9618
|
-
|
|
9619
|
-
|
|
9620
|
-
io
|
|
9621
|
-
io.out(" To stay on Pages instead, set `spec.origin: pages` and deploy again.");
|
|
9622
|
-
}
|
|
9907
|
+
const result = await convertToDispatch(cwd, false);
|
|
9908
|
+
if (result.kind === "converted")
|
|
9909
|
+
reportConversion(io, result.secrets);
|
|
9623
9910
|
return null;
|
|
9624
9911
|
} catch (e) {
|
|
9625
9912
|
if (e instanceof RegisteredAppError)
|
|
@@ -9629,6 +9916,30 @@ async function defaultToDispatch(cwd, manifestPath, io) {
|
|
|
9629
9916
|
return `Not converted to Workers dispatch: ${describe17(e)}`;
|
|
9630
9917
|
}
|
|
9631
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
|
+
}
|
|
9632
9943
|
function declaredOriginOf(isV22, origin) {
|
|
9633
9944
|
if (!isV22)
|
|
9634
9945
|
return "pages";
|
|
@@ -11084,10 +11395,6 @@ async function runInit(args, io, prompt, identity = () => apiJson(loadConfig(),
|
|
|
11084
11395
|
return 64;
|
|
11085
11396
|
}
|
|
11086
11397
|
const dispatch = newAppOrigin(fillResult.inputs.type, inputs.origin) === "dispatch";
|
|
11087
|
-
if (dispatch && fillResult.inputs.type === "react+api") {
|
|
11088
|
-
io.err("launchpad init: API starter dispatch is not available yet; use --origin pages");
|
|
11089
|
-
return 64;
|
|
11090
|
-
}
|
|
11091
11398
|
if (dispatch && (inputs.hostnames.length > 0 || inputs.sessionDuration !== undefined)) {
|
|
11092
11399
|
io.err("launchpad init: dispatch does not support --hostname or --session-duration; use --origin pages for a Pages manifest");
|
|
11093
11400
|
return 64;
|
|
@@ -11159,11 +11466,10 @@ async function runInit(args, io, prompt, identity = () => apiJson(loadConfig(),
|
|
|
11159
11466
|
io.out("");
|
|
11160
11467
|
io.out("Next steps:");
|
|
11161
11468
|
io.out(" launchpad validate # confirm the manifest");
|
|
11162
|
-
|
|
11163
|
-
io.out(" launchpad secrets template # scaffold .env.example");
|
|
11469
|
+
io.out(" launchpad secrets template # scaffold .env.example");
|
|
11164
11470
|
io.out(" launchpad deploy # build and deploy the app");
|
|
11165
11471
|
if (dispatch)
|
|
11166
|
-
io.out("
|
|
11472
|
+
io.out(" Push secrets with `launchpad secrets push` once the first deploy has been delivered.");
|
|
11167
11473
|
return 0;
|
|
11168
11474
|
}
|
|
11169
11475
|
function parseArgs6(args) {
|
|
@@ -11215,11 +11521,11 @@ function parseArgs6(args) {
|
|
|
11215
11521
|
i++;
|
|
11216
11522
|
return next;
|
|
11217
11523
|
};
|
|
11218
|
-
const setFromNext = (
|
|
11524
|
+
const setFromNext = (assign2) => {
|
|
11219
11525
|
const got = needsValue();
|
|
11220
11526
|
if (typeof got !== "string")
|
|
11221
11527
|
return got;
|
|
11222
|
-
|
|
11528
|
+
assign2(got);
|
|
11223
11529
|
return;
|
|
11224
11530
|
};
|
|
11225
11531
|
if (a === "--name") {
|
|
@@ -13974,6 +14280,22 @@ function parseEnv(text) {
|
|
|
13974
14280
|
return { entries, invalidNames, malformedLines };
|
|
13975
14281
|
}
|
|
13976
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
|
+
|
|
13977
14299
|
// src/secrets/push.ts
|
|
13978
14300
|
async function runSecretsPush(opts, io, deps = {}) {
|
|
13979
14301
|
const cfg = deps.config ?? loadConfig();
|
|
@@ -13983,14 +14305,10 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
13983
14305
|
if (loaded.kind !== "ok") {
|
|
13984
14306
|
return renderManifestError4(loaded, io);
|
|
13985
14307
|
}
|
|
13986
|
-
const
|
|
13987
|
-
if (v2Refusal !== null)
|
|
13988
|
-
return v2Refusal;
|
|
13989
|
-
const manifest = loaded.manifest;
|
|
13990
|
-
const bindings = manifest.secrets?.bindings ?? [];
|
|
14308
|
+
const { slug, bindings } = declaredSecrets(loaded);
|
|
13991
14309
|
const envFileBindings = bindings.filter((b) => b.source === "env-file");
|
|
13992
14310
|
if (envFileBindings.length === 0) {
|
|
13993
|
-
io.out("No `env-file`
|
|
14311
|
+
io.out("No secrets to push: the manifest declares no `env-file` bindings or `secret_text` env vars.");
|
|
13994
14312
|
io.out("(platform-managed bindings are pushed via the platform-team ops path.)");
|
|
13995
14313
|
return 0;
|
|
13996
14314
|
}
|
|
@@ -14069,11 +14387,11 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
14069
14387
|
io.out("Re-run without --dry-run to push.");
|
|
14070
14388
|
return 0;
|
|
14071
14389
|
}
|
|
14072
|
-
const slug = manifest.metadata.name;
|
|
14073
14390
|
let pushed = 0;
|
|
14074
14391
|
let partial = 0;
|
|
14075
14392
|
let anyPagesUpdated = false;
|
|
14076
14393
|
let anyWorkerUpdated = false;
|
|
14394
|
+
let anyDispatchUpdated = false;
|
|
14077
14395
|
for (const b of envFileBindings) {
|
|
14078
14396
|
const value = parsed.entries.get(b.name);
|
|
14079
14397
|
io.out(`Pushing ${b.name} …`);
|
|
@@ -14094,6 +14412,11 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
14094
14412
|
io.err(`✗ secrets push ${b.name}: invalid bot response (results must be an array)`);
|
|
14095
14413
|
return 2;
|
|
14096
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
|
+
}
|
|
14097
14420
|
const results = resp.results ?? [];
|
|
14098
14421
|
for (const r of results) {
|
|
14099
14422
|
if (r.status !== "updated")
|
|
@@ -14102,6 +14425,8 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
14102
14425
|
anyPagesUpdated = true;
|
|
14103
14426
|
else if (r.kind === "worker")
|
|
14104
14427
|
anyWorkerUpdated = true;
|
|
14428
|
+
else if (r.kind === "dispatch")
|
|
14429
|
+
anyDispatchUpdated = true;
|
|
14105
14430
|
}
|
|
14106
14431
|
const notUpdated = results.filter((r) => r.status !== "updated");
|
|
14107
14432
|
if (resp.updated === true && notUpdated.length === 0) {
|
|
@@ -14125,7 +14450,9 @@ async function runSecretsPush(opts, io, deps = {}) {
|
|
|
14125
14450
|
if (anyPagesUpdated) {
|
|
14126
14451
|
return offerRedeploy(slug, opts, io, deps);
|
|
14127
14452
|
}
|
|
14128
|
-
if (
|
|
14453
|
+
if (anyDispatchUpdated) {
|
|
14454
|
+
io.out("Set on the app's dispatch Worker and live now - no redeploy needed.");
|
|
14455
|
+
} else if (anyWorkerUpdated) {
|
|
14129
14456
|
io.out("These secrets bind on the app's next invocation — no redeploy needed.");
|
|
14130
14457
|
}
|
|
14131
14458
|
return 0;
|
|
@@ -14194,20 +14521,6 @@ function mapPushError(e, slug, name, io) {
|
|
|
14194
14521
|
function describe30(e) {
|
|
14195
14522
|
return e instanceof Error ? e.message : String(e);
|
|
14196
14523
|
}
|
|
14197
|
-
function refuseV2Manifest(loaded, command, io) {
|
|
14198
|
-
if (loaded.schema !== "launchpad.m-kopa.us/v2")
|
|
14199
|
-
return null;
|
|
14200
|
-
const origin = loaded.manifest.spec?.origin;
|
|
14201
|
-
if (origin === "dispatch") {
|
|
14202
|
-
io.err(`${command}: this is a dispatch-origin (v2) app.`);
|
|
14203
|
-
const outcome = command.endsWith("push") ? "nothing was pushed" : "nothing was read";
|
|
14204
|
-
io.err(` Secrets are not delivered to the dispatch namespace yet (PS-2740); ${outcome}.`);
|
|
14205
|
-
return 1;
|
|
14206
|
-
}
|
|
14207
|
-
io.err(`${command}: this is a launchpad.m-kopa.us/v2 manifest.`);
|
|
14208
|
-
io.err(" This command reads v1alpha1 `secrets.bindings`; v2 declares secrets as `spec.env_vars` `secret_text` entries, which it does not push yet.");
|
|
14209
|
-
return 1;
|
|
14210
|
-
}
|
|
14211
14524
|
function renderManifestError4(result, io) {
|
|
14212
14525
|
switch (result.kind) {
|
|
14213
14526
|
case "ok":
|
|
@@ -14248,17 +14561,12 @@ async function runSecretsStatus(opts, io, deps = {}) {
|
|
|
14248
14561
|
const loaded = loadManifestAnyVersion(manifestPath);
|
|
14249
14562
|
if (loaded.kind !== "ok")
|
|
14250
14563
|
return renderManifestError5(loaded, io);
|
|
14251
|
-
const
|
|
14252
|
-
if (v2Refusal !== null)
|
|
14253
|
-
return v2Refusal;
|
|
14254
|
-
const manifest = loaded.manifest;
|
|
14255
|
-
const slug = manifest.metadata.name;
|
|
14564
|
+
const { slug, bindings } = declaredSecrets(loaded);
|
|
14256
14565
|
if (opts.slug != null && opts.slug !== slug) {
|
|
14257
14566
|
io.err(`launchpad secrets status: slug "${opts.slug}" does not match this directory's ` + `manifest (metadata.name: "${slug}").`);
|
|
14258
14567
|
io.err(" secrets status audits the manifest in the current directory — omit the slug, " + "or run from the app's directory.");
|
|
14259
14568
|
return 2;
|
|
14260
14569
|
}
|
|
14261
|
-
const bindings = manifest.secrets?.bindings ?? [];
|
|
14262
14570
|
if (bindings.length === 0) {
|
|
14263
14571
|
io.out("No secret bindings declared in the manifest.");
|
|
14264
14572
|
return 0;
|
|
@@ -14608,12 +14916,12 @@ async function runSecretsTemplate(args, io) {
|
|
|
14608
14916
|
return 64;
|
|
14609
14917
|
}
|
|
14610
14918
|
const manifestPath = resolve14(process.cwd(), flags.file ?? "launchpad.yaml");
|
|
14611
|
-
const
|
|
14612
|
-
const renderResult = renderManifest(result, io);
|
|
14919
|
+
const renderResult = renderManifest(loadManifestAnyVersion(manifestPath), io);
|
|
14613
14920
|
if (renderResult.kind !== "ok") {
|
|
14614
14921
|
return renderResult.code;
|
|
14615
14922
|
}
|
|
14616
|
-
const
|
|
14923
|
+
const declared = renderResult.declared;
|
|
14924
|
+
const body = buildTemplate(declared, flags.includePlatformManaged);
|
|
14617
14925
|
if (flags.toStdout) {
|
|
14618
14926
|
for (const line of body.split(`
|
|
14619
14927
|
`)) {
|
|
@@ -14636,14 +14944,14 @@ async function runSecretsTemplate(args, io) {
|
|
|
14636
14944
|
return 2;
|
|
14637
14945
|
}
|
|
14638
14946
|
io.out(`✓ wrote ${outPath}`);
|
|
14639
|
-
const
|
|
14640
|
-
io.out(` ${
|
|
14947
|
+
const written = declared.bindings.filter((b) => b.source === "env-file").length;
|
|
14948
|
+
io.out(` ${written} binding${written === 1 ? "" : "s"} scaffolded`);
|
|
14641
14949
|
return 0;
|
|
14642
14950
|
}
|
|
14643
14951
|
function renderManifest(result, io) {
|
|
14644
14952
|
switch (result.kind) {
|
|
14645
14953
|
case "ok":
|
|
14646
|
-
return { kind: "ok",
|
|
14954
|
+
return { kind: "ok", declared: declaredSecrets(result) };
|
|
14647
14955
|
case "file-not-found":
|
|
14648
14956
|
io.err(`launchpad secrets template: ${result.path}`);
|
|
14649
14957
|
io.err(" manifest not found");
|
|
@@ -14665,19 +14973,19 @@ function renderManifest(result, io) {
|
|
|
14665
14973
|
return { kind: "fail", code: 1 };
|
|
14666
14974
|
}
|
|
14667
14975
|
}
|
|
14668
|
-
function buildTemplate(
|
|
14669
|
-
const bindings =
|
|
14976
|
+
function buildTemplate(declared, includePlatformManaged) {
|
|
14977
|
+
const { bindings } = declared;
|
|
14670
14978
|
const lines = [];
|
|
14671
14979
|
lines.push("# Auto-generated by `launchpad secrets template` — do not commit values.");
|
|
14672
|
-
lines.push(`# Source: launchpad.yaml (apiVersion: ${
|
|
14673
|
-
lines.push(`# App: ${
|
|
14980
|
+
lines.push(`# Source: launchpad.yaml (apiVersion: ${declared.apiVersion})`);
|
|
14981
|
+
lines.push(`# App: ${declared.slug}`);
|
|
14674
14982
|
lines.push("#");
|
|
14675
14983
|
lines.push("# Fill in values, then run `launchpad secrets push`.");
|
|
14676
14984
|
lines.push("# This file is meant to be committed; the real `.env` must be gitignored.");
|
|
14677
14985
|
lines.push("");
|
|
14678
14986
|
if (bindings.length === 0) {
|
|
14679
14987
|
lines.push("# No secret bindings declared in launchpad.yaml.");
|
|
14680
|
-
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.");
|
|
14681
14989
|
lines.push("");
|
|
14682
14990
|
return lines.join(`
|
|
14683
14991
|
`);
|
|
@@ -14711,11 +15019,6 @@ function buildTemplate(manifest, includePlatformManaged) {
|
|
|
14711
15019
|
return lines.join(`
|
|
14712
15020
|
`);
|
|
14713
15021
|
}
|
|
14714
|
-
function countBindings(manifest) {
|
|
14715
|
-
const bindings = manifest.secrets?.bindings ?? [];
|
|
14716
|
-
const written = bindings.filter((b) => b.source === "env-file").length;
|
|
14717
|
-
return { total: bindings.length, written };
|
|
14718
|
-
}
|
|
14719
15022
|
function parseFlags4(args) {
|
|
14720
15023
|
let file;
|
|
14721
15024
|
let out = ".env.example";
|
|
@@ -14924,7 +15227,7 @@ import { promises as fs7, existsSync as existsSync16 } from "node:fs";
|
|
|
14924
15227
|
// src/skills-bundle.ts
|
|
14925
15228
|
import { homedir as homedir2 } from "node:os";
|
|
14926
15229
|
import { join as join15 } from "node:path";
|
|
14927
|
-
import { readFileSync as readFileSync20, readdirSync as
|
|
15230
|
+
import { readFileSync as readFileSync20, readdirSync as readdirSync3 } from "node:fs";
|
|
14928
15231
|
var SKILL_PREFIX = "launchpad-";
|
|
14929
15232
|
function skillsTargetDir() {
|
|
14930
15233
|
return process.env.LAUNCHPAD_SKILLS_TARGET_DIR ?? join15(homedir2(), ".claude", "skills");
|
|
@@ -14932,7 +15235,7 @@ function skillsTargetDir() {
|
|
|
14932
15235
|
function readInstalledSkills() {
|
|
14933
15236
|
try {
|
|
14934
15237
|
const dir = skillsTargetDir();
|
|
14935
|
-
const bundle =
|
|
15238
|
+
const bundle = readdirSync3(dir, { withFileTypes: true }).find((e) => e.isDirectory() && e.name.startsWith(SKILL_PREFIX));
|
|
14936
15239
|
if (!bundle)
|
|
14937
15240
|
return { present: false, version: null };
|
|
14938
15241
|
return {
|
|
@@ -15622,7 +15925,7 @@ function printHelp5(io) {
|
|
|
15622
15925
|
}
|
|
15623
15926
|
|
|
15624
15927
|
// src/commands/validate.ts
|
|
15625
|
-
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";
|
|
15626
15929
|
import { dirname as dirname12, resolve as resolve17 } from "node:path";
|
|
15627
15930
|
var validateCommand = {
|
|
15628
15931
|
name: "validate",
|
|
@@ -15713,7 +16016,7 @@ function checkStaticServeDir(manifestPath, manifest) {
|
|
|
15713
16016
|
}
|
|
15714
16017
|
let entries;
|
|
15715
16018
|
try {
|
|
15716
|
-
entries =
|
|
16019
|
+
entries = readdirSync4(abs);
|
|
15717
16020
|
} catch (e) {
|
|
15718
16021
|
return [`failed to read static served directory '${serveDir}/': ${describe32(e)}`];
|
|
15719
16022
|
}
|
|
@@ -24,7 +24,7 @@ export declare function parseArgs(args: readonly string[]): DeployArgs | null;
|
|
|
24
24
|
/** Re-export for tests. */
|
|
25
25
|
export { MAX_COMPRESSED_TARBALL_BYTES };
|
|
26
26
|
/**
|
|
27
|
-
* Workers dispatch is the default for new static and React apps. Before a
|
|
27
|
+
* Workers dispatch is the default for new static, React and React API apps. Before a
|
|
28
28
|
* first deploy, convert a gateway manifest that only implies Pages (no
|
|
29
29
|
* `origin` in v1 or v2) to v2 dispatch. An explicit origin is an opt-out and
|
|
30
30
|
* registered apps keep their origin. Returns a note explaining why the deploy
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AA0EA,OAAO,EAGL,4BAA4B,EAC7B,MAAM,uBAAuB,CAAC;AAY/B,OAAO,EAAa,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAY,MAAM,kBAAkB,CAAC;AASjE,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;AAEjD,eAAO,MAAM,aAAa,EAAE,OAI3B,CAAC;AAEF,UAAU,UAAU;IAClB,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AA6RD;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAoBpC;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,GAAG,IAAI,CA4BpE;AAiMD,2BAA2B;AAC3B,OAAO,EAAE,4BAA4B,EAAE,CAAC;AA8uBxC;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AA0EA,OAAO,EAGL,4BAA4B,EAC7B,MAAM,uBAAuB,CAAC;AAY/B,OAAO,EAAa,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAY,MAAM,kBAAkB,CAAC;AASjE,OAAO,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,CAAC;AAEjD,eAAO,MAAM,aAAa,EAAE,OAI3B,CAAC;AAEF,UAAU,UAAU;IAClB,6BAA6B;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AA6RD;;;;;;GAMG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAoBpC;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,UAAU,GAAG,IAAI,CA4BpE;AAiMD,2BAA2B;AAC3B,OAAO,EAAE,4BAA4B,EAAE,CAAC;AA8uBxC;;;;;;;GAOG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAY5G;AAkCD,2EAA2E;AAC3E;+EAC+E;AAC/E,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;AAE9D,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAG,cAAc,CAG/E;AAED;;;4EAG4E;AAC5E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE;IACvC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;CACrD,GAAG,OAAO,CAIV;AAED,wBAAgB,uBAAuB,CACrC,cAAc,EAAE,OAAO,EACvB,UAAU,GAAE,OAAO,GAAG,SAAqB,GAC1C,MAAM,CAOR"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAIL,KAAK,OAAO,EACZ,KAAK,QAAQ,EACd,MAAM,uBAAuB,CAAC;AAI/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAK7D,sDAAsD;AACtD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B;;;;OAIG;IACH,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC3B,iFAAiF;IACjF,MAAM,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;IACzC;;;;OAIG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED,iEAAiE;AACjE,MAAM,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEhF,eAAO,MAAM,WAAW,EAAE,OAIzB,CAAC;AAEF,4FAA4F;AAC5F,wBAAsB,OAAO,CAC3B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,KAAK,EACT,MAAM,EAAE,QAAQ,EAChB,QAAQ,GAAE,MAAM,OAAO,CAAC,YAAY,CAAuE,GAC1G,OAAO,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AA4CA,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAIL,KAAK,OAAO,EACZ,KAAK,QAAQ,EACd,MAAM,uBAAuB,CAAC;AAI/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAK7D,sDAAsD;AACtD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IACzB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,WAAW,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,IAAI,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1B;;;;OAIG;IACH,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC3B,iFAAiF;IACjF,MAAM,EAAE,OAAO,GAAG,UAAU,GAAG,SAAS,CAAC;IACzC;;;;OAIG;IACH,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,cAAc,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAED,iEAAiE;AACjE,MAAM,MAAM,QAAQ,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;AAEhF,eAAO,MAAM,WAAW,EAAE,OAIzB,CAAC;AAEF,4FAA4F;AAC5F,wBAAsB,OAAO,CAC3B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,KAAK,EACT,MAAM,EAAE,QAAQ,EAChB,QAAQ,GAAE,MAAM,OAAO,CAAC,YAAY,CAAuE,GAC1G,OAAO,CAAC,QAAQ,CAAC,CA0LnB"}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import type { Command } from "../dispatcher.js";
|
|
2
2
|
export declare const migrateCommand: Command;
|
|
3
3
|
export declare const BACKUP_PATH = ".launchpad/manifest-before-dispatch.yaml";
|
|
4
|
+
/** Dispatch secrets land on the Worker script, which exists only after the first delivery. */
|
|
5
|
+
export declare function secretsNextStep(secrets: readonly string[]): string | null;
|
|
4
6
|
export type DispatchConversion = {
|
|
5
7
|
readonly kind: "already";
|
|
6
8
|
} | {
|
|
7
9
|
readonly kind: "eligible" | "converted";
|
|
8
10
|
readonly slug: string;
|
|
11
|
+
/** Secret names the converted manifest declares; set with `secrets push` after first delivery. */
|
|
12
|
+
readonly secrets: readonly string[];
|
|
9
13
|
};
|
|
10
14
|
/**
|
|
11
15
|
* Convert `<cwd>/launchpad.yaml` to a v2 dispatch manifest, or report that it
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAS,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAIvD,eAAO,MAAM,cAAc,EAAE,OAI5B,CAAC;AAiCF,eAAO,MAAM,WAAW,6CAA6C,CAAC;AAEtE,8FAA8F;AAC9F,wBAAgB,eAAe,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,IAAI,CAGzE;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IACE,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,CAAC;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kGAAkG;IAClG,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;CACrC,CAAC;AAEN;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA0BjG;AAkCD,yFAAyF;AACzF,qBAAa,kBAAmB,SAAQ,KAAK;gBAC/B,KAAK,EAAE,OAAO;CAG3B;AAED,yFAAyF;AACzF,qBAAa,wBAAyB,SAAQ,KAAK;gBACrC,KAAK,EAAE,OAAO;CAG3B;AAED,0GAA0G;AAC1G,qBAAa,kBAAmB,SAAQ,KAAK;;CAI5C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secrets-template.d.ts","sourceRoot":"","sources":["../../src/commands/secrets-template.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"secrets-template.d.ts","sourceRoot":"","sources":["../../src/commands/secrets-template.ts"],"names":[],"mappings":"AA6CA,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAgBxD,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,EAAE,EAAE,KAAK,GACR,OAAO,CAAC,QAAQ,CAAC,CAiDnB"}
|
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
import { type DeploymentManifest, type ManifestV2 } from "@m-kopa/launchpad-engine";
|
|
2
|
-
/**
|
|
3
|
-
|
|
1
|
+
import { type DeploymentManifest, type Manifest, type ManifestV2 } from "@m-kopa/launchpad-engine";
|
|
2
|
+
/**
|
|
3
|
+
* Convert a Pages-implied manifest to v2 dispatch, keeping only what the
|
|
4
|
+
* dispatch runtime preserves. `wranglerD1` is the D1 binding the app's
|
|
5
|
+
* wrangler config declares, already checked by the caller.
|
|
6
|
+
*/
|
|
7
|
+
export declare function dispatchManifest(manifest: DeploymentManifest, wranglerD1?: string): ManifestV2;
|
|
8
|
+
/**
|
|
9
|
+
* The D1 binding name when `targets` is exactly one pages target carrying
|
|
10
|
+
* `d1_binding`. Dispatch declares that database as `spec.bindings.d1`; every
|
|
11
|
+
* other target shape (cron Workers, secrets surfaces) has no dispatch form.
|
|
12
|
+
*/
|
|
13
|
+
export declare function pagesTargetD1(manifest: Pick<Manifest, "targets">): string | undefined;
|
|
4
14
|
//# sourceMappingURL=migrate-dispatch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-dispatch.d.ts","sourceRoot":"","sources":["../../src/manifest/migrate-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,kBAAkB,EAEvB,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"migrate-dispatch.d.ts","sourceRoot":"","sources":["../../src/manifest/migrate-dispatch.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,kBAAkB,EAEvB,KAAK,QAAQ,EACb,KAAK,UAAU,EAGhB,MAAM,0BAA0B,CAAC;AAOlC;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,kBAAkB,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,UAAU,CAgD9F;AAuDD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,MAAM,GAAG,SAAS,CAIrF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wrangler-config.d.ts","sourceRoot":"","sources":["../../src/manifest/wrangler-config.ts"],"names":[],"mappings":"AAoBA,kFAAkF;AAClF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAuBhF"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SecretBinding } from "@m-kopa/launchpad-engine";
|
|
2
|
+
import type { AnyVersionLoadResult } from "../manifest/load.js";
|
|
3
|
+
export interface DeclaredSecrets {
|
|
4
|
+
readonly slug: string;
|
|
5
|
+
readonly apiVersion: string;
|
|
6
|
+
readonly bindings: readonly SecretBinding[];
|
|
7
|
+
}
|
|
8
|
+
/** Normalise a loaded manifest's secret declarations to v1-shaped bindings. */
|
|
9
|
+
export declare function declaredSecrets(loaded: Extract<AnyVersionLoadResult, {
|
|
10
|
+
kind: "ok";
|
|
11
|
+
}>): DeclaredSecrets;
|
|
12
|
+
//# sourceMappingURL=declared.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"declared.d.ts","sourceRoot":"","sources":["../../src/secrets/declared.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAc,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,CAAC;CAC7C;AAED,+EAA+E;AAC/E,wBAAgB,eAAe,CAC7B,MAAM,EAAE,OAAO,CAAC,oBAAoB,EAAE;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,GACpD,eAAe,CAiBjB"}
|
package/dist/secrets/push.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { type CliConfig } from "../config.js";
|
|
2
|
-
import { type AnyVersionLoadResult } from "../manifest/load.js";
|
|
3
2
|
import { type RedeployOptions, type RedeployDeps } from "../commands/redeploy.js";
|
|
4
3
|
import type { CliIo, ExitCode } from "../dispatcher.js";
|
|
5
4
|
export interface PushOptions {
|
|
@@ -26,11 +25,4 @@ export interface PushDeps {
|
|
|
26
25
|
readonly runRedeploy?: (opts: RedeployOptions, io: CliIo, deps?: RedeployDeps) => Promise<ExitCode>;
|
|
27
26
|
}
|
|
28
27
|
export declare function runSecretsPush(opts: PushOptions, io: CliIo, deps?: PushDeps): Promise<ExitCode>;
|
|
29
|
-
/** v2 manifests carry no `secrets.bindings`. On `origin: dispatch` the bot
|
|
30
|
-
* refuses too (PS-2740), so say so here before any .env is read; on a v2
|
|
31
|
-
* pages app say what this command reads today instead of reporting
|
|
32
|
-
* "no bindings declared", which would be false. Returns null for v1alpha1. */
|
|
33
|
-
export declare function refuseV2Manifest(loaded: Extract<AnyVersionLoadResult, {
|
|
34
|
-
kind: "ok";
|
|
35
|
-
}>, command: string, io: CliIo): ExitCode | null;
|
|
36
28
|
//# sourceMappingURL=push.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../src/secrets/push.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"push.d.ts","sourceRoot":"","sources":["../../src/secrets/push.ts"],"names":[],"mappings":"AAoDA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAY1D,OAAO,EAAe,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAC/F,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IAChC,sEAAsE;IACtE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,OAAO,CAAC;IAC/B,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACxD,6CAA6C;IAC7C,QAAQ,CAAC,WAAW,CAAC,EAAE,CACrB,IAAI,EAAE,eAAe,EACrB,EAAE,EAAE,KAAK,EACT,IAAI,CAAC,EAAE,YAAY,KAChB,OAAO,CAAC,QAAQ,CAAC,CAAC;CACxB;AAiBD,wBAAsB,cAAc,CAClC,IAAI,EAAE,WAAW,EACjB,EAAE,EAAE,KAAK,EACT,IAAI,GAAE,QAAa,GAClB,OAAO,CAAC,QAAQ,CAAC,CAoNnB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/secrets/status.ts"],"names":[],"mappings":"AAaA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/secrets/status.ts"],"names":[],"mappings":"AAaA,OAAO,EAAc,KAAK,SAAS,EAAE,MAAM,cAAc,CAAC;AAY1D,OAAO,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAExD,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;;;OAMG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;CACjC;AAkBD,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,aAAa,EACnB,EAAE,EAAE,KAAK,EACT,IAAI,GAAE,UAAe,GACpB,OAAO,CAAC,QAAQ,CAAC,CAoEnB"}
|
package/dist/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "0.
|
|
1
|
+
export declare const CLI_VERSION = "0.64.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-content-pr
|
|
3
3
|
description: Push a content change to a Launchpad app via `launchpad deploy` and verify it shipped via `launchpad status`. Covers the post-first-deploy iteration loop (edit → deploy → verify) — subsequent deploys commit directly to the app repo's main and the Pages build runs asynchronously, so verification is its own step. Use when someone says "push a content change", "ship an update", "/launchpad-content-pr", "verify my deploy", or after `/launchpad-deploy` reports `done` and they want to follow up with an edit.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.64.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-deploy
|
|
3
3
|
description: Walk a Launchpad user through deploying an app from their local working directory (Model A — `launchpad init` + `launchpad deploy`). Wraps the CLI verbs end-to-end: detects the app shape, scaffolds `launchpad.yaml`, resolves the allowed Entra group via `launchpad groups`, bundles the CWD via `launchpad deploy`, and watches the rollout via `launchpad status`. Use when someone says "deploy a new app", "ship my app to Launchpad", "/launchpad-deploy", "I have an app locally — get it on Launchpad", or any variant. Resume/abandon for legacy in-flight provisioning is at the bottom.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.64.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -149,25 +149,29 @@ resolve it by passing `--type` explicitly or skipping detection with
|
|
|
149
149
|
launchpad init
|
|
150
150
|
```
|
|
151
151
|
|
|
152
|
-
New `static` and `react` apps default to Workers dispatch with gateway authentication.
|
|
152
|
+
New `static`, `react` and `react+api` apps default to Workers dispatch with gateway authentication.
|
|
153
153
|
The CLI writes a v2 manifest and keeps the detected build settings and access groups.
|
|
154
|
-
Gateway placement is automatic for new gateway-fronted apps, including
|
|
154
|
+
Gateway placement is automatic for new gateway-fronted apps, including API apps.
|
|
155
155
|
The platform selects the configured gateway with the most free hostname slots, counting pending deployments as reservations.
|
|
156
156
|
Do not ask the author to choose a gateway or add gateway project names to the manifest.
|
|
157
157
|
Existing apps and retries retain their assigned gateway and public URL.
|
|
158
|
-
An older manifest that only implies Pages (v1, or v2 without `spec.origin`) is converted to dispatch automatically on the first `launchpad deploy` of an unregistered static or React app.
|
|
158
|
+
An older manifest that only implies Pages (v1, or v2 without `spec.origin`) is converted to dispatch automatically on the first `launchpad deploy` of an unregistered static, React or React API app.
|
|
159
159
|
Tell the author when deploy reports the conversion, and point them at the backup in `.launchpad/manifest-before-dispatch.yaml`.
|
|
160
160
|
`launchpad migrate --origin dispatch --dry-run` runs the same check without deploying, if the author wants to see it first.
|
|
161
161
|
If deploy prints `Not converted to Workers dispatch`, the deploy continues on Pages; tell the author the reason, and do not delete files or change `appType` to force the conversion.
|
|
162
162
|
Do not regenerate the manifest with `init`, broaden its groups, or change private ownership to work around the quota.
|
|
163
163
|
If `launchpad migrate` refuses functions, custom runtime configuration or bindings, explain the named limitation; a normal `launchpad deploy` still works on Pages.
|
|
164
|
-
|
|
164
|
+
For a React API app, `functions/` converts as-is and the dispatch build compiles it; its D1 becomes `spec.bindings.d1` and declared secrets become `secret_text` env vars.
|
|
165
|
+
When deploy says secrets carried over, tell the author to run `launchpad secrets push` once `launchpad status` shows the first delivery; pushing earlier fails with `dispatch_script_not_delivered`.
|
|
166
|
+
Still refused on dispatch, with the reason printed: wrangler settings beyond name, compatibility date/flags, `pages_build_output_dir` and one D1 (so KV, R2, services, vars, Durable Objects, queues), a root `functions/_middleware` or root-level catch-all that would claim `/*`, a hand-written `_worker.js`, and functions that compile to more than one module.
|
|
167
|
+
A function that calls `next()` to fall through to a static asset does not work on dispatch, because the Worker has no asset fallback.
|
|
168
|
+
Changing `appType` to `react` would not convert the API; do not suggest it.
|
|
165
169
|
Do not use this command to migrate a registered app or delete its Pages resources.
|
|
166
170
|
If the pool is full or inventory cannot be read, report the capacity error; do not fall back to Pages or change authentication to bypass it.
|
|
167
171
|
Do not replace that manifest with a v1 example or add `--origin pages` just because an older app uses Pages.
|
|
168
172
|
Use `--origin pages` only when Pages is deliberately required.
|
|
169
|
-
|
|
170
|
-
Dispatch supports plain-text environment variables,
|
|
173
|
+
Containers keep their existing deployment path.
|
|
174
|
+
Dispatch supports plain-text environment variables and `secret_text` env vars; after the first deploy is delivered, `launchpad secrets push` sets the secret values, live immediately with no redeploy.
|
|
171
175
|
|
|
172
176
|
This is interactive by default. It will ask for:
|
|
173
177
|
|
|
@@ -500,6 +504,22 @@ targets:
|
|
|
500
504
|
On `launchpad deploy` the bot auto-provisions the shared D1 named after the slug (**create-or-adopt by slug during deploy** - a successful destroy exports and deletes the recorded UUID before repository archive, so platform recovery creates a fresh destination database) and binds it to the Pages app, so `env.DB` works with no manual `wrangler d1 create`.
|
|
501
505
|
The bot also pins the matching `[[d1_databases]]` block into the **committed** `wrangler.toml` - Pages git-source builds read bindings from that file, and a build without the block silently resets the binding to empty.
|
|
502
506
|
|
|
507
|
+
**Dispatch form.** A v2 `react+api` app on `origin: dispatch` has no `targets`; it declares the same database as `spec.bindings.d1`:
|
|
508
|
+
|
|
509
|
+
```yaml
|
|
510
|
+
spec:
|
|
511
|
+
appType: react+api
|
|
512
|
+
auth: gateway
|
|
513
|
+
origin: dispatch
|
|
514
|
+
bindings:
|
|
515
|
+
d1: DB # env binding name your Worker code reads
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
The bot create-or-adopts the slug-named D1, records its UUID, and binds it on every Worker upload (no `wrangler.toml` needed).
|
|
519
|
+
If the database cannot be resolved the delivery refuses with `d1-unresolved` and nothing uploads.
|
|
520
|
+
`bindings.d1` is refused on a v2 pages app and on any appType other than `react+api`.
|
|
521
|
+
`launchpad migrate --origin dispatch` and a first `launchpad deploy` write this field from a v1 pages target `d1_binding` or a single wrangler `d1_databases` entry.
|
|
522
|
+
|
|
503
523
|
**Empty-DB gotcha:** the platform provisions an **empty** database — schema
|
|
504
524
|
and migrations are the app's job. The usual pattern is idempotent
|
|
505
525
|
`CREATE TABLE IF NOT EXISTS …` at runtime (startup / first request); there
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-deploy-status
|
|
3
3
|
description: Show the current provisioning stage + failure reason for a Launchpad app via `launchpad status` (Model A drift + deployment_verified) and `launchpad apps` (lifecycle bucket), or watch provisioning live with `launchpad watch`. Renders the M-892 stage trace for in-flight provisioning, and is the canonical home for `launchpad recover` (repair a terminal-failed app record that is actually live). Use when someone says "what's the status of demo-X", "/launchpad-deploy-status", "is my deploy stuck", "watch my deploy go live", "watch provisioning", "my app says failed but it's serving", or after `/launchpad-deploy` reports a non-`done` terminal stage.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.64.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-destroy
|
|
3
3
|
description: Tear down a Launchpad app end-to-end via `launchpad destroy` — Cloudflare Pages project, edge-auth wiring (gateway KV/audience entries, or the Access app for `auth: access` apps), custom hostname, platform-repo TF, and the app repo (archive-renamed). Owner-only verb with a two-step destructive confirmation. Use when someone says "destroy this app", "/launchpad-destroy", "tear down `<slug>`", "delete the app", or asks to clean up a smoke-test / orphan / retired app.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.64.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-identity
|
|
3
3
|
description: Teach an app author how to use the signed-in user's identity inside a Launchpad app — read the gateway-forwarded X-Launchpad-User-Assertion in a Pages Function, VERIFY it with @m-kopa/platform-auth (fail-closed), and show who's logged in (sub/email/name). Use when someone says "who is logged in", "show the current user", "get the user's email in my app", "auth in my launchpad app", "read the user identity", "/launchpad-identity", or is wiring up an /api/me for a gateway-fronted app.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.64.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-onboard
|
|
3
3
|
description: One-time setup for the Launchpad CLI + Claude Code skill bundle. Verifies the `launchpad` CLI is installed and current, runs `launchpad whoami` to confirm the session is fresh, and checks the bundled skills are installed and in lock-step with the CLI. Idempotent — safe to re-run any time. Use when someone says "set me up for Launchpad", "I just got a new machine and want to use Launchpad", "/launchpad-onboard", or any of the other launchpad-* skills fails on a prereq check.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.64.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -215,12 +215,12 @@ Otherwise finish with:
|
|
|
215
215
|
|
|
216
216
|
## New app defaults
|
|
217
217
|
|
|
218
|
-
`launchpad init` creates new static and React apps on Workers dispatch with gateway authentication.
|
|
218
|
+
`launchpad init` creates new static, React and React API apps on Workers dispatch with gateway authentication.
|
|
219
219
|
The generated v2 manifest preserves the detected build settings and the chosen access groups.
|
|
220
220
|
Use the normal `launchpad deploy` flow; authors do not create Cloudflare projects or choose a gateway.
|
|
221
221
|
The platform assigns new gateway-fronted apps to the configured gateway with the most free hostname slots, including pending reservations.
|
|
222
222
|
Existing apps keep their assigned gateway, URL and access groups.
|
|
223
|
-
|
|
224
|
-
An older manifest for an unregistered static or React app is converted to dispatch on its first `launchpad deploy
|
|
223
|
+
Registered apps keep their current origin.
|
|
224
|
+
An older manifest for an unregistered static, React or React API app is converted to dispatch on its first `launchpad deploy`; a React API app with KV, R2 or other unsupported wrangler bindings stays on Pages with the reason printed.
|
|
225
225
|
An explicit `--origin pages` is available when required.
|
|
226
|
-
Dispatch
|
|
226
|
+
Dispatch apps declare secrets as `secret_text` env vars and set them with `launchpad secrets push` once the first deploy has been delivered.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-report
|
|
3
3
|
description: File a bug report or feature request to the Launchpad team's tracker from the CLI. Use when someone reports something broken, hits an error in a launchpad command, or wishes a feature existed — e.g. "this is broken", "report a bug", "can you file that", "I wish launchpad could…", "/launchpad-bug", "/launchpad-feature". Always confirm and show exactly what you'll send before filing; never file silently.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.64.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: launchpad-status
|
|
3
3
|
description: Safely refresh a Launchpad clone, show manifest drift, or read the deployed manifest. Wraps bare `launchpad pull` for source sync, `launchpad pull --manifest` for deployed YAML, and `launchpad status` for drift. Use for "sync my clone", "what's deployed", "show drift", "/launchpad-status", "/launchpad-pull", or post-deploy verification.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.64.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
<!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
|