@m-kopa/launchpad-cli 0.62.0 → 0.63.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 CHANGED
@@ -8,6 +8,33 @@ pre-1.0 minor bumps may carry breaking changes per ADR 0005.
8
8
 
9
9
  ## Unreleased
10
10
 
11
+ ## 0.63.0 - 2026-09-25
12
+
13
+ ### Changed
14
+
15
+ - Workers dispatch is now the default for every new static and React app, not only ones created with `launchpad init`.
16
+ A first `launchpad deploy` converts a manifest that only implies Pages (v1, or v2 without `spec.origin`) to v2 dispatch before bundling, with the same checks and backup as `launchpad migrate --origin dispatch`.
17
+ An explicit `origin: pages` opts out, registered apps keep their origin, and apps that need a runtime review stay on Pages with the reason printed.
18
+ If the registry cannot confirm the app is new, deploy stops instead of falling back to Pages.
19
+ - The production minimum CLI version rises to 0.63.0 once this release is published.
20
+
21
+ ### Fixed
22
+
23
+ - Pages API deployments reject duplicate catch-all handlers before committing content.
24
+ First content uploads replace an unchanged bootstrap API starter when the app supplies its own API catch-all, and content PR deployments no longer recreate removed starters.
25
+ The authentication guide documents gateway JWKS configuration and recovery for older apps affected by AIO-440.
26
+ - Platform deployments find the existing audience policy for apps whose first deploy omitted their provisioning path.
27
+ New first-deploy registrations now retain that path.
28
+ Conflicting or unavailable policies still block publication.
29
+
30
+ ### Changed
31
+
32
+ - Ordinary all-employees grants are confirmed in the deploy skill conversation, without a browser page or expiring decision ID.
33
+ Sensitive apps retain builder confirmation and platform-admin approval.
34
+
35
+ - Raise the production minimum CLI version to 0.62.0 through the governed policy workflow.
36
+ Older stable clients must run `launchpad update` before platform mutations.
37
+
11
38
  ## 0.62.0 - 2026-09-17
12
39
 
13
40
  ### Added
@@ -97,6 +97,8 @@ export interface DeployBundleProvisioningStarted {
97
97
  export type DeployBundleSuccess = DeployBundleAccepted | DeployBundleProvisioningStarted | DeployBundleNothingToDeploy;
98
98
  /** Structured error shape from the bot on validation failure. */
99
99
  export interface DeployBundleError extends StructuredErrorEnvelope {
100
+ /** Sensitive grants still require the authenticated approval flow. */
101
+ readonly requires_admin_approval?: boolean;
100
102
  readonly error: string;
101
103
  readonly violations?: ReadonlyArray<{
102
104
  path: string;
@@ -1 +1 @@
1
- {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/bundle/upload.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../src/bundle/upload.ts"],"names":[],"mappings":"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"}
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.62.0";
22
+ var CLI_VERSION = "0.63.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 existsSync7 } from "node:fs";
1892
- import * as path8 from "node:path";
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
  }
@@ -7005,7 +7011,7 @@ async function verifyFirstDeployServed(args, liveUrl, io, deps) {
7005
7011
 
7006
7012
  // src/commands/deploy.ts
7007
7013
  import { parse as parseYaml6 } from "yaml";
7008
- import { readFileSync as readFileSync11 } from "node:fs";
7014
+ import { readFileSync as readFileSync12 } from "node:fs";
7009
7015
 
7010
7016
  // src/deploy/git-files.ts
7011
7017
  import { spawn as spawn3 } from "node:child_process";
@@ -8613,6 +8619,211 @@ function rejectUnsupportedCliRuntime(args) {
8613
8619
  return 1;
8614
8620
  }
8615
8621
 
8622
+ // 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";
8624
+ import path8 from "node:path";
8625
+ import { stringify } from "yaml";
8626
+
8627
+ // src/manifest/migrate-dispatch.ts
8628
+ function dispatchManifest(manifest) {
8629
+ const v2 = manifest.apiVersion === "launchpad.m-kopa.us/v2";
8630
+ const source = v2 ? manifest.spec : manifest;
8631
+ const appType = v2 ? manifest.spec.appType : manifest.deployment.type;
8632
+ if (appType !== "static" && appType !== "react") {
8633
+ 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.");
8634
+ }
8635
+ if (source.secrets !== undefined || source.containers !== undefined) {
8636
+ throw new Error("Secret and container bindings cannot be converted automatically.");
8637
+ }
8638
+ if (!v2)
8639
+ assertConvertibleV1(manifest);
8640
+ const spec = v2 ? { ...manifest.spec } : {
8641
+ appType,
8642
+ display: {
8643
+ name: manifest.metadata.name,
8644
+ team: manifest.metadata.team,
8645
+ ...manifest.metadata.description === undefined ? {} : { description: manifest.metadata.description }
8646
+ },
8647
+ owner: { email: manifest.metadata.owner },
8648
+ access: {
8649
+ audience: manifest.access.audience,
8650
+ private_owner: manifest.access.private_owner,
8651
+ allowed_entra_groups: [...allowedEntraGroups(manifest.access)],
8652
+ identity_types: manifest.access.identity_types
8653
+ },
8654
+ build: manifest.build,
8655
+ app: manifest.app,
8656
+ verify: manifest.verify,
8657
+ 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
+ }
8662
+ };
8663
+ const candidate = {
8664
+ apiVersion: "launchpad.m-kopa.us/v2",
8665
+ kind: "App",
8666
+ metadata: v2 ? manifest.metadata : { slug: manifest.metadata.name, schemaVersion: 2 },
8667
+ spec: { ...spec, origin: "dispatch", auth: "gateway", confine_origin: undefined }
8668
+ };
8669
+ const parsed = parseManifestV2(candidate);
8670
+ if (parsed.kind !== "ok") {
8671
+ throw new Error(`Cannot convert manifest: ${parsed.issues.map((issue) => `${issue.path}: ${issue.message}`).join("; ")}`);
8672
+ }
8673
+ return parsed.manifest;
8674
+ }
8675
+ function assertConvertibleV1(manifest) {
8676
+ const unsupported2 = [
8677
+ manifest.hostnames !== undefined && "hostnames",
8678
+ manifest.targets !== undefined && "targets",
8679
+ manifest.access.session_duration !== undefined && "access.session_duration",
8680
+ manifest.deployment.runtime !== undefined && "deployment.runtime"
8681
+ ].filter(Boolean);
8682
+ if (unsupported2.length > 0) {
8683
+ throw new Error(`Cannot preserve ${unsupported2.join(", ")} on dispatch. Review these settings with the platform team before migrating.`);
8684
+ }
8685
+ }
8686
+
8687
+ // src/commands/migrate.ts
8688
+ var USAGE = "usage: launchpad migrate --origin dispatch [--dry-run]";
8689
+ var migrateCommand = {
8690
+ name: "migrate",
8691
+ summary: "convert an unprovisioned static or React app to Workers dispatch",
8692
+ run: runMigrate
8693
+ };
8694
+ async function runMigrate(args, io) {
8695
+ if (args.length === 1 && (args[0] === "--help" || args[0] === "-h")) {
8696
+ io.out(USAGE);
8697
+ return 0;
8698
+ }
8699
+ const flags = args.filter((arg) => arg !== "--dry-run");
8700
+ if (flags.length !== 2 || flags[0] !== "--origin" || flags[1] !== "dispatch" || args.length > 3) {
8701
+ io.err(USAGE);
8702
+ return 64;
8703
+ }
8704
+ try {
8705
+ const result = await convertToDispatch(process.cwd(), args.includes("--dry-run"));
8706
+ if (result.kind === "already") {
8707
+ io.out("launchpad.yaml already selects Workers dispatch. Run `launchpad deploy`.");
8708
+ } else if (result.kind === "eligible") {
8709
+ io.out(`${result.slug}: eligible for v2 Workers dispatch with gateway authentication. No files changed.`);
8710
+ } else {
8711
+ io.out(`${result.slug}: launchpad.yaml now uses v2 Workers dispatch with gateway authentication.`);
8712
+ io.out(`Audience, owner and application settings are preserved. Original: ${BACKUP_PATH}`);
8713
+ io.out("Run `launchpad deploy`. No Pages project is needed.");
8714
+ }
8715
+ return 0;
8716
+ } catch (error) {
8717
+ io.err(`launchpad migrate: ${error instanceof Error ? error.message : String(error)}`);
8718
+ return 1;
8719
+ }
8720
+ }
8721
+ var BACKUP_PATH = ".launchpad/manifest-before-dispatch.yaml";
8722
+ async function convertToDispatch(cwd, dryRun) {
8723
+ const file = path8.join(cwd, "launchpad.yaml");
8724
+ if (!lstatSync4(file).isFile())
8725
+ throw new Error("launchpad.yaml must be a regular file, not a symlink.");
8726
+ const original = readFileSync11(file, "utf8");
8727
+ const loaded = parseManifestAnyVersion(original, file);
8728
+ if (loaded.kind !== "ok")
8729
+ throw new Error("launchpad.yaml is invalid. Run `launchpad validate` and correct it first.");
8730
+ const manifest = loaded.manifest;
8731
+ if (manifest.apiVersion === "launchpad.m-kopa.us/v2" && manifest.spec.origin === "dispatch")
8732
+ return { kind: "already" };
8733
+ const converted = dispatchManifest(manifest);
8734
+ assertNoCustomRuntime(cwd, converted);
8735
+ const slug = converted.metadata.slug;
8736
+ await assertUnregistered(slug);
8737
+ if (dryRun)
8738
+ return { kind: "eligible", slug };
8739
+ try {
8740
+ saveMigration(file, original, converted);
8741
+ } catch (error) {
8742
+ throw new MigrationSaveError(error);
8743
+ }
8744
+ return { kind: "converted", slug };
8745
+ }
8746
+ async function assertUnregistered(slug) {
8747
+ try {
8748
+ await apiRaw(loadConfig(), { path: `/apps/${encodeURIComponent(slug)}/manifest/state` });
8749
+ } catch (error) {
8750
+ if (error instanceof NotFoundError && error.serverCode === "not_found")
8751
+ return;
8752
+ if (error instanceof UpgradeRequiredError || error instanceof UnauthenticatedError)
8753
+ throw error;
8754
+ throw new RegistryUnavailableError(error);
8755
+ }
8756
+ throw new RegisteredAppError;
8757
+ }
8758
+
8759
+ class MigrationSaveError extends Error {
8760
+ constructor(cause) {
8761
+ super(`Could not save the converted manifest: ${cause instanceof Error ? cause.message : String(cause)}`);
8762
+ }
8763
+ }
8764
+
8765
+ class RegistryUnavailableError extends Error {
8766
+ constructor(cause) {
8767
+ super(`Could not confirm whether this app is registered: ${cause instanceof Error ? cause.message : String(cause)}`);
8768
+ }
8769
+ }
8770
+
8771
+ class RegisteredAppError extends Error {
8772
+ constructor() {
8773
+ 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
+ }
8775
+ }
8776
+ function assertNoCustomRuntime(cwd, manifest) {
8777
+ const appRoot = path8.resolve(cwd, manifest.spec.app?.root ?? ".");
8778
+ const buildRoot = path8.resolve(appRoot, manifest.spec.build?.root_dir ?? ".");
8779
+ const roots = new Set([
8780
+ cwd,
8781
+ appRoot,
8782
+ buildRoot,
8783
+ path8.resolve(cwd, manifest.spec.build?.root_dir ?? "."),
8784
+ path8.resolve(buildRoot, manifest.spec.build?.destination_dir ?? "dist")
8785
+ ]);
8786
+ for (const root of roots) {
8787
+ for (const name of ["functions", "_worker.js", "wrangler.toml", "wrangler.json", "wrangler.jsonc"]) {
8788
+ if (existsSync7(path8.join(root, name))) {
8789
+ 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.`);
8790
+ }
8791
+ }
8792
+ }
8793
+ }
8794
+ function saveMigration(file, original, converted) {
8795
+ if (readFileSync11(file, "utf8") !== original)
8796
+ throw new Error("launchpad.yaml changed during the check. Retry migration.");
8797
+ const backupDir = path8.join(path8.dirname(file), ".launchpad");
8798
+ if (existsSync7(backupDir) && !lstatSync4(backupDir).isDirectory()) {
8799
+ throw new Error(".launchpad must be a regular directory, not a symlink or file.");
8800
+ }
8801
+ mkdirSync2(backupDir, { recursive: true });
8802
+ const backup = path8.join(backupDir, "manifest-before-dispatch.yaml");
8803
+ const attemptDir = mkdtempSync(path8.join(backupDir, "migration-"));
8804
+ let backupCreated = false;
8805
+ try {
8806
+ const fd = openSync(backup, "wx", 384);
8807
+ backupCreated = true;
8808
+ try {
8809
+ writeFileSync2(fd, original);
8810
+ } finally {
8811
+ closeSync(fd);
8812
+ }
8813
+ const temporary = path8.join(attemptDir, "launchpad.yaml");
8814
+ writeFileSync2(temporary, stringify(converted), { flag: "wx", mode: 384 });
8815
+ if (readFileSync11(file, "utf8") !== original)
8816
+ throw new Error("launchpad.yaml changed during migration. Retry migration.");
8817
+ renameSync(temporary, file);
8818
+ } catch (error) {
8819
+ if (backupCreated)
8820
+ rmSync2(backup);
8821
+ throw error;
8822
+ } finally {
8823
+ rmSync2(attemptDir, { recursive: true, force: true });
8824
+ }
8825
+ }
8826
+
8616
8827
  // src/commands/deploy.ts
8617
8828
  var deployCommand = {
8618
8829
  name: "deploy",
@@ -8665,8 +8876,8 @@ async function runDeploy(args, io) {
8665
8876
  }, io);
8666
8877
  }
8667
8878
  const cwd = process.cwd();
8668
- const manifestPath = path8.join(cwd, "launchpad.yaml");
8669
- if (existsSync7(manifestPath)) {
8879
+ const manifestPath = path9.join(cwd, "launchpad.yaml");
8880
+ if (existsSync8(manifestPath)) {
8670
8881
  const contentMode = flags.mode.kind === "content" ? flags.mode : {
8671
8882
  allowStale: false,
8672
8883
  rebaseOntoHead: false,
@@ -8700,7 +8911,7 @@ async function runDeploy(args, io) {
8700
8911
  } else {
8701
8912
  const inferred = inferSlug({ cwd: process.cwd(), warn: (l) => io.err(l) });
8702
8913
  if (inferred === null) {
8703
- io.err(`launchpad deploy: could not infer slug from cwd (${path8.basename(process.cwd())});
8914
+ io.err(`launchpad deploy: could not infer slug from cwd (${path9.basename(process.cwd())});
8704
8915
  ` + ` pass --slug <slug>, or cd into a directory named launchpad-app-<slug>.`);
8705
8916
  return 64;
8706
8917
  }
@@ -8720,10 +8931,10 @@ async function runDeploy(args, io) {
8720
8931
  return 1;
8721
8932
  }
8722
8933
  let contentManifestYaml = null;
8723
- const contentManifestPath = path8.join(process.cwd(), "launchpad.yaml");
8724
- if (existsSync7(contentManifestPath)) {
8934
+ const contentManifestPath = path9.join(process.cwd(), "launchpad.yaml");
8935
+ if (existsSync8(contentManifestPath)) {
8725
8936
  try {
8726
- contentManifestYaml = readFileSync11(contentManifestPath, "utf8");
8937
+ contentManifestYaml = readFileSync12(contentManifestPath, "utf8");
8727
8938
  } catch {
8728
8939
  contentManifestYaml = null;
8729
8940
  }
@@ -8933,8 +9144,8 @@ function surfaceStaleBlock(body, io) {
8933
9144
  const head = body.head_sha ?? null;
8934
9145
  if (body.error === "stale_overlap" && body.paths !== undefined) {
8935
9146
  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 path9 of body.paths.slice(0, 20)) {
8937
- io.err(` - ${path9}`);
9147
+ for (const path10 of body.paths.slice(0, 20)) {
9148
+ io.err(` - ${path10}`);
8938
9149
  }
8939
9150
  if (body.paths.length > 20) {
8940
9151
  io.err(` … and ${body.paths.length - 20} more`);
@@ -8967,7 +9178,7 @@ function withTimeout(p, ms) {
8967
9178
  async function surfaceManifestDiff(cfg, slug, manifestPath, io) {
8968
9179
  let localYaml;
8969
9180
  try {
8970
- localYaml = readFileSync11(manifestPath, "utf8");
9181
+ localYaml = readFileSync12(manifestPath, "utf8");
8971
9182
  } catch {
8972
9183
  return;
8973
9184
  }
@@ -9016,8 +9227,21 @@ async function runModelADeploy(args) {
9016
9227
  let slug;
9017
9228
  let appType = "static";
9018
9229
  let declaredOrigin;
9230
+ let pagesNote;
9231
+ try {
9232
+ pagesNote = await defaultToDispatch(cwd, manifestPath, io);
9233
+ } catch (e) {
9234
+ if (e instanceof UpgradeRequiredError) {
9235
+ io.err(e.message);
9236
+ return EXIT_CLI_UPGRADE_REQUIRED;
9237
+ }
9238
+ io.err(`launchpad deploy: ${describe17(e)}. Nothing was deployed.`);
9239
+ if (e instanceof UnauthenticatedError)
9240
+ io.err(" run `launchpad login` to refresh your session.");
9241
+ return 1;
9242
+ }
9019
9243
  try {
9020
- const manifestObj = parseYaml6(readFileSync11(manifestPath, "utf8"));
9244
+ const manifestObj = parseYaml6(readFileSync12(manifestPath, "utf8"));
9021
9245
  const metaSlug = resolveManifestSlug(manifestObj);
9022
9246
  if (metaSlug === null) {
9023
9247
  io.err(`launchpad deploy: launchpad.yaml is missing metadata.slug (v2) / metadata.name (v1). ` + `Run \`launchpad init\` again to regenerate the manifest.`);
@@ -9084,9 +9308,8 @@ async function runModelADeploy(args) {
9084
9308
  io.out(`Bundling ${cwd} for ${slug} (Model A) …`);
9085
9309
  if (declaredOrigin !== "dispatch" && appType !== "container") {
9086
9310
  io.out("Deployment target: Cloudflare Pages, selected by launchpad.yaml.");
9087
- if (appType === "static" || appType === "react") {
9088
- io.out("For an app that has never provisioned, `launchpad migrate --origin dispatch` checks whether it can use Workers without a Pages project.");
9089
- }
9311
+ if (pagesNote !== null)
9312
+ io.out(pagesNote);
9090
9313
  }
9091
9314
  let cfg;
9092
9315
  try {
@@ -9172,7 +9395,7 @@ async function runModelADeploy(args) {
9172
9395
  const errorCode = body.error;
9173
9396
  if (errorCode === "audience_confirmation_required") {
9174
9397
  io.err(String(body.message));
9175
- io.err("Only the human builder may confirm. Retry this deployment after confirmation.");
9398
+ 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
9399
  return 1;
9177
9400
  }
9178
9401
  if (result.status === 426 && typeof body.minimum === "string" && typeof body.yours === "string") {
@@ -9375,6 +9598,37 @@ async function runModelADeploy(args) {
9375
9598
  function surfaceCommittedHandoff(dispatchOrigin, io, pagesStyle = "unicode") {
9376
9599
  io.out(committedHandoffMessage(dispatchOrigin, pagesStyle));
9377
9600
  }
9601
+ function isIneligible(e) {
9602
+ return !(e instanceof RegistryUnavailableError || e instanceof MigrationSaveError || e instanceof UpgradeRequiredError || e instanceof UnauthenticatedError);
9603
+ }
9604
+ async function defaultToDispatch(cwd, manifestPath, io) {
9605
+ let raw;
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")
9616
+ return null;
9617
+ try {
9618
+ if ((await convertToDispatch(cwd, false)).kind === "converted") {
9619
+ io.out("New apps deploy on Workers dispatch by default, so launchpad.yaml was converted (no Pages project needed).");
9620
+ io.out(` Audience, owner and build settings are preserved. Original: ${BACKUP_PATH}`);
9621
+ io.out(" To stay on Pages instead, set `spec.origin: pages` and deploy again.");
9622
+ }
9623
+ return null;
9624
+ } catch (e) {
9625
+ if (e instanceof RegisteredAppError)
9626
+ return null;
9627
+ if (!(e instanceof Error) || !isIneligible(e))
9628
+ throw e;
9629
+ return `Not converted to Workers dispatch: ${describe17(e)}`;
9630
+ }
9631
+ }
9378
9632
  function declaredOriginOf(isV22, origin) {
9379
9633
  if (!isV22)
9380
9634
  return "pages";
@@ -9409,7 +9663,7 @@ function pendingReconcileOutcome(body) {
9409
9663
  }
9410
9664
 
9411
9665
  // src/commands/redeploy.ts
9412
- import { existsSync as existsSync8, readFileSync as readFileSync12 } from "node:fs";
9666
+ import { existsSync as existsSync9, readFileSync as readFileSync13 } from "node:fs";
9413
9667
  import { resolve as resolvePath2 } from "node:path";
9414
9668
  import { parse as parseYaml7 } from "yaml";
9415
9669
  var SLUG_RE6 = /^[a-z0-9][a-z0-9-]*[a-z0-9]$/;
@@ -9487,10 +9741,10 @@ async function runRedeploy(opts, io, deps = {}) {
9487
9741
  }
9488
9742
  function readLocalAppType(cwd, file) {
9489
9743
  const manifestPath = resolvePath2(cwd, file ?? "launchpad.yaml");
9490
- if (!existsSync8(manifestPath))
9744
+ if (!existsSync9(manifestPath))
9491
9745
  return null;
9492
9746
  try {
9493
- const obj = parseYaml7(readFileSync12(manifestPath, "utf8"));
9747
+ const obj = parseYaml7(readFileSync13(manifestPath, "utf8"));
9494
9748
  return coerceAppType(obj?.deployment?.type);
9495
9749
  } catch {
9496
9750
  return null;
@@ -9584,7 +9838,7 @@ function printHelp(io) {
9584
9838
  }
9585
9839
 
9586
9840
  // src/commands/envvars.ts
9587
- import * as path9 from "node:path";
9841
+ import * as path10 from "node:path";
9588
9842
  var envvarsCommand = {
9589
9843
  name: "envvars",
9590
9844
  summary: "list / set / remove production env vars (slug-scoped)",
@@ -9604,7 +9858,7 @@ async function runEnvvars(args, io) {
9604
9858
  } else {
9605
9859
  const inferred = inferSlug({ cwd: process.cwd(), warn: (l) => io.err(l) });
9606
9860
  if (inferred === null) {
9607
- io.err(`launchpad envvars: could not infer slug from cwd (${path9.basename(process.cwd())});
9861
+ io.err(`launchpad envvars: could not infer slug from cwd (${path10.basename(process.cwd())});
9608
9862
  ` + ` pass --slug <slug>, or cd into a directory named launchpad-app-<slug>.`);
9609
9863
  return 64;
9610
9864
  }
@@ -9774,7 +10028,7 @@ function describe18(e) {
9774
10028
  }
9775
10029
 
9776
10030
  // src/commands/generate.ts
9777
- import { mkdirSync as mkdirSync2, readFileSync as readFileSync13, writeFileSync as writeFileSync2 } from "node:fs";
10031
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync14, writeFileSync as writeFileSync3 } from "node:fs";
9778
10032
  import { dirname as dirname7, resolve as resolve10, relative as relative4 } from "node:path";
9779
10033
  var generateCommand = {
9780
10034
  name: "generate",
@@ -9824,7 +10078,7 @@ async function runGenerate(args, io) {
9824
10078
  ];
9825
10079
  return flags.json ? renderApplyJson(reports, appRoot, io) : renderApplyHuman(reports, appRoot, io);
9826
10080
  }
9827
- function applyOne(artefact, path10, out, force) {
10081
+ function applyOne(artefact, path11, out, force) {
9828
10082
  if (out.kind === "not-applicable" || out.kind === "unsupported") {
9829
10083
  return {
9830
10084
  artefact,
@@ -9835,32 +10089,32 @@ function applyOne(artefact, path10, out, force) {
9835
10089
  warnings: []
9836
10090
  };
9837
10091
  }
9838
- const existing = readIfExists(path10);
10092
+ const existing = readIfExists(path11);
9839
10093
  if (existing.kind === "read-error") {
9840
10094
  return {
9841
10095
  artefact,
9842
- action: { kind: "write-error", path: path10, message: existing.message },
10096
+ action: { kind: "write-error", path: path11, message: existing.message },
9843
10097
  warnings: out.warnings
9844
10098
  };
9845
10099
  }
9846
10100
  if (existing.kind === "ok" && existing.content === out.content) {
9847
- return { artefact, action: { kind: "unchanged", path: path10 }, warnings: out.warnings };
10101
+ return { artefact, action: { kind: "unchanged", path: path11 }, warnings: out.warnings };
9848
10102
  }
9849
10103
  if (existing.kind === "ok" && existing.content !== out.content && !force) {
9850
10104
  return {
9851
10105
  artefact,
9852
- action: { kind: "would-overwrite", path: path10 },
10106
+ action: { kind: "would-overwrite", path: path11 },
9853
10107
  warnings: out.warnings
9854
10108
  };
9855
10109
  }
9856
10110
  try {
9857
- mkdirSync2(dirname7(path10), { recursive: true });
9858
- writeFileSync2(path10, out.content, "utf8");
10111
+ mkdirSync3(dirname7(path11), { recursive: true });
10112
+ writeFileSync3(path11, out.content, "utf8");
9859
10113
  return {
9860
10114
  artefact,
9861
10115
  action: {
9862
10116
  kind: "written",
9863
- path: path10,
10117
+ path: path11,
9864
10118
  bytes: Buffer.byteLength(out.content, "utf8")
9865
10119
  },
9866
10120
  warnings: out.warnings
@@ -9870,16 +10124,16 @@ function applyOne(artefact, path10, out, force) {
9870
10124
  artefact,
9871
10125
  action: {
9872
10126
  kind: "write-error",
9873
- path: path10,
10127
+ path: path11,
9874
10128
  message: err.message ?? String(err)
9875
10129
  },
9876
10130
  warnings: out.warnings
9877
10131
  };
9878
10132
  }
9879
10133
  }
9880
- function readIfExists(path10) {
10134
+ function readIfExists(path11) {
9881
10135
  try {
9882
- return { kind: "ok", content: readFileSync13(path10, "utf8") };
10136
+ return { kind: "ok", content: readFileSync14(path11, "utf8") };
9883
10137
  } catch (err) {
9884
10138
  const e = err;
9885
10139
  if (e.code === "ENOENT")
@@ -10618,17 +10872,17 @@ function describe21(e) {
10618
10872
 
10619
10873
  // src/commands/init.ts
10620
10874
  import { createInterface } from "node:readline/promises";
10621
- import { existsSync as existsSync10, mkdirSync as mkdirSync3, readFileSync as readFileSync15, writeFileSync as writeFileSync3 } from "node:fs";
10875
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync16, writeFileSync as writeFileSync4 } from "node:fs";
10622
10876
  import { dirname as dirname8, resolve as resolve11 } from "node:path";
10623
10877
  import { stringify as yamlStringify } from "yaml";
10624
10878
 
10625
10879
  // src/detect/index.ts
10626
- import { existsSync as existsSync9, readFileSync as readFileSync14, statSync } from "node:fs";
10880
+ import { existsSync as existsSync10, readFileSync as readFileSync15, statSync } from "node:fs";
10627
10881
  import { join as join13 } from "node:path";
10628
10882
  function detectAppShape(cwd) {
10629
- const hasPackageJson = existsSync9(join13(cwd, "package.json"));
10630
- const hasAnyLockfile = LOCKFILES.some(({ file }) => existsSync9(join13(cwd, file)));
10631
- const hasViteConfig = VITE_CONFIG_NAMES.some((n) => existsSync9(join13(cwd, n)));
10883
+ const hasPackageJson = existsSync10(join13(cwd, "package.json"));
10884
+ const hasAnyLockfile = LOCKFILES.some(({ file }) => existsSync10(join13(cwd, file)));
10885
+ const hasViteConfig = VITE_CONFIG_NAMES.some((n) => existsSync10(join13(cwd, n)));
10632
10886
  if (!hasPackageJson && !hasAnyLockfile && !hasViteConfig) {
10633
10887
  return {
10634
10888
  kind: "not-applicable",
@@ -10674,7 +10928,7 @@ var LOCKFILES = [
10674
10928
  { file: "yarn.lock", pm: "yarn" }
10675
10929
  ];
10676
10930
  function detectPackageManager(cwd) {
10677
- const present = LOCKFILES.filter(({ file }) => existsSync9(join13(cwd, file)));
10931
+ const present = LOCKFILES.filter(({ file }) => existsSync10(join13(cwd, file)));
10678
10932
  if (present.length === 0) {
10679
10933
  return {
10680
10934
  kind: "ambiguous",
@@ -10697,7 +10951,7 @@ function detectPackageManager(cwd) {
10697
10951
  function detectVitePresence(cwd) {
10698
10952
  for (const name of VITE_CONFIG_NAMES) {
10699
10953
  const p = join13(cwd, name);
10700
- if (existsSync9(p)) {
10954
+ if (existsSync10(p)) {
10701
10955
  return { kind: "ok", value: { path: p } };
10702
10956
  }
10703
10957
  }
@@ -10709,7 +10963,7 @@ function detectVitePresence(cwd) {
10709
10963
  function detectAppType(cwd) {
10710
10964
  const fnDir = join13(cwd, "functions");
10711
10965
  let hasFunctionsDir = false;
10712
- if (existsSync9(fnDir)) {
10966
+ if (existsSync10(fnDir)) {
10713
10967
  try {
10714
10968
  hasFunctionsDir = statSync(fnDir).isDirectory();
10715
10969
  } catch {
@@ -10720,7 +10974,7 @@ function detectAppType(cwd) {
10720
10974
  }
10721
10975
  function detectBuildCommand(cwd, pm) {
10722
10976
  const pkgJsonPath = join13(cwd, "package.json");
10723
- if (!existsSync9(pkgJsonPath)) {
10977
+ if (!existsSync10(pkgJsonPath)) {
10724
10978
  return {
10725
10979
  kind: "ambiguous",
10726
10980
  reason: "no package.json at repo root. Run your package manager's `init` first."
@@ -10728,7 +10982,7 @@ function detectBuildCommand(cwd, pm) {
10728
10982
  }
10729
10983
  let pkgJson;
10730
10984
  try {
10731
- pkgJson = JSON.parse(readFileSync14(pkgJsonPath, "utf8"));
10985
+ pkgJson = JSON.parse(readFileSync15(pkgJsonPath, "utf8"));
10732
10986
  } catch (e) {
10733
10987
  return {
10734
10988
  kind: "ambiguous",
@@ -10763,7 +11017,7 @@ var OUT_DIR_REGEX = /\bbuild\s*:\s*\{[^{}]*?\boutDir\s*:\s*['"]([^'"]+)['"]/s;
10763
11017
  function detectDestinationDir(cwd, vite) {
10764
11018
  let text;
10765
11019
  try {
10766
- text = readFileSync14(vite.path, "utf8");
11020
+ text = readFileSync15(vite.path, "utf8");
10767
11021
  } catch (e) {
10768
11022
  return {
10769
11023
  kind: "ambiguous",
@@ -10793,7 +11047,7 @@ async function runInit(args, io, prompt, identity = () => apiJson(loadConfig(),
10793
11047
  }
10794
11048
  const { inputs, options } = parsed;
10795
11049
  const outPath = resolve11(process.cwd(), options.out);
10796
- if (existsSync10(outPath) && !options.force) {
11050
+ if (existsSync11(outPath) && !options.force) {
10797
11051
  io.err(`launchpad init: ${outPath} already exists`);
10798
11052
  io.err("Pass --force to overwrite.");
10799
11053
  return 64;
@@ -10849,7 +11103,7 @@ async function runInit(args, io, prompt, identity = () => apiJson(loadConfig(),
10849
11103
  io.out(`Audience: private. Only you (${privateOwner.email}) and platform administrators can open this app.`);
10850
11104
  } else {
10851
11105
  io.out(`Audience: selected Entra groups (${fillResult.inputs.groups.join(", ")}), plus platform administrators.`);
10852
- io.out("Company-wide grants require your explicit browser confirmation before publishing.");
11106
+ io.out("The deploy skill confirms company-wide grants with you. Sensitive apps also require browser confirmation and platform-admin approval.");
10853
11107
  }
10854
11108
  const manifest = buildManifest({ ...fillResult.inputs, ...privateOwner === undefined ? {} : { privateOwner } }, detected);
10855
11109
  const validation = manifest.apiVersion === "launchpad.m-kopa.us/v2" ? ManifestV2Schema.safeParse(manifest) : ManifestSchema.safeParse(manifest);
@@ -10862,7 +11116,7 @@ async function runInit(args, io, prompt, identity = () => apiJson(loadConfig(),
10862
11116
  }
10863
11117
  const yamlText = renderYaml(manifest);
10864
11118
  try {
10865
- writeFileSync3(outPath, yamlText, { encoding: "utf8" });
11119
+ writeFileSync4(outPath, yamlText, { encoding: "utf8" });
10866
11120
  } catch (err) {
10867
11121
  const e = err;
10868
11122
  io.err(`launchpad init: failed to write ${outPath}`);
@@ -10888,10 +11142,10 @@ async function runInit(args, io, prompt, identity = () => apiJson(loadConfig(),
10888
11142
  if (fillResult.inputs.type === "static") {
10889
11143
  const serveDir = resolve11(dirname8(outPath), STATIC_SERVE_DIR);
10890
11144
  const indexPath = resolve11(serveDir, "index.html");
10891
- if (!existsSync10(indexPath)) {
11145
+ if (!existsSync11(indexPath)) {
10892
11146
  try {
10893
- mkdirSync3(serveDir, { recursive: true });
10894
- writeFileSync3(indexPath, starterIndexHtml(fillResult.inputs.name), {
11147
+ mkdirSync4(serveDir, { recursive: true });
11148
+ writeFileSync4(indexPath, starterIndexHtml(fillResult.inputs.name), {
10895
11149
  encoding: "utf8"
10896
11150
  });
10897
11151
  io.out(`✓ scaffolded ${indexPath}`);
@@ -11310,10 +11564,10 @@ function starterIndexHtml(slug) {
11310
11564
  function renderYaml(manifest) {
11311
11565
  return yamlStringify(manifest, { lineWidth: 0 });
11312
11566
  }
11313
- function ensureGitignoreEntries(path10, entries) {
11567
+ function ensureGitignoreEntries(path11, entries) {
11314
11568
  let current = "";
11315
- if (existsSync10(path10)) {
11316
- current = readFileSync15(path10, "utf8");
11569
+ if (existsSync11(path11)) {
11570
+ current = readFileSync16(path11, "utf8");
11317
11571
  }
11318
11572
  const lines = current.split(/\r?\n/);
11319
11573
  const present = new Set(lines.map((l) => l.trim()));
@@ -11331,7 +11585,7 @@ function ensureGitignoreEntries(path10, entries) {
11331
11585
  }
11332
11586
  }
11333
11587
  if (added.length > 0) {
11334
- writeFileSync3(path10, out, { encoding: "utf8" });
11588
+ writeFileSync4(path11, out, { encoding: "utf8" });
11335
11589
  }
11336
11590
  return added;
11337
11591
  }
@@ -11503,7 +11757,7 @@ function describe23(e) {
11503
11757
  }
11504
11758
 
11505
11759
  // src/commands/logs.ts
11506
- import * as path10 from "node:path";
11760
+ import * as path11 from "node:path";
11507
11761
  var logsCommand = {
11508
11762
  name: "logs",
11509
11763
  summary: "show recent Pages deployment history (slug-scoped)",
@@ -11525,7 +11779,7 @@ async function runLogs(args, io) {
11525
11779
  } else {
11526
11780
  const inferred = inferSlug({ cwd: process.cwd(), warn: (l) => io.err(l) });
11527
11781
  if (inferred === null) {
11528
- io.err(`launchpad logs: could not infer slug from cwd (${path10.basename(process.cwd())});
11782
+ io.err(`launchpad logs: could not infer slug from cwd (${path11.basename(process.cwd())});
11529
11783
  ` + ` pass --slug <slug>, or cd into a directory named launchpad-app-<slug>.`);
11530
11784
  return 64;
11531
11785
  }
@@ -11653,7 +11907,7 @@ function describe24(e) {
11653
11907
  }
11654
11908
 
11655
11909
  // src/commands/merge.ts
11656
- import * as path11 from "node:path";
11910
+ import * as path12 from "node:path";
11657
11911
  var mergeCommand = {
11658
11912
  name: "merge",
11659
11913
  summary: "squash-merge a review-passed PR (slug-scoped)",
@@ -11674,7 +11928,7 @@ async function runMerge(args, io) {
11674
11928
  } else {
11675
11929
  const inferred = inferSlug({ cwd: process.cwd(), warn: (l) => io.err(l) });
11676
11930
  if (inferred === null) {
11677
- io.err(`launchpad merge: could not infer slug from cwd (${path11.basename(process.cwd())});
11931
+ io.err(`launchpad merge: could not infer slug from cwd (${path12.basename(process.cwd())});
11678
11932
  ` + ` pass --slug <slug>, or cd into a directory named launchpad-app-<slug>.`);
11679
11933
  return 64;
11680
11934
  }
@@ -11840,178 +12094,6 @@ function describe25(e) {
11840
12094
  return e instanceof Error ? e.message : String(e);
11841
12095
  }
11842
12096
 
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
12097
  // src/commands/plan.ts
12016
12098
  import { resolve as resolve12 } from "node:path";
12017
12099
  var planCommand = {
@@ -1,6 +1,6 @@
1
1
  import { MAX_COMPRESSED_TARBALL_BYTES } from "../deploy/tar-pack.js";
2
2
  import { inferSlugFromCwd, resolveManifestSlug } from "./infer-slug.js";
3
- import type { Command } from "../dispatcher.js";
3
+ import type { CliIo, Command } from "../dispatcher.js";
4
4
  export { inferSlugFromCwd, resolveManifestSlug };
5
5
  export declare const deployCommand: Command;
6
6
  interface DeployArgs {
@@ -23,6 +23,15 @@ export declare function deployUsage(): string;
23
23
  export declare function parseArgs(args: readonly string[]): DeployArgs | null;
24
24
  /** Re-export for tests. */
25
25
  export { MAX_COMPRESSED_TARBALL_BYTES };
26
+ /**
27
+ * Workers dispatch is the default for new static and React apps. Before a
28
+ * first deploy, convert a gateway manifest that only implies Pages (no
29
+ * `origin` in v1 or v2) to v2 dispatch. An explicit origin is an opt-out and
30
+ * registered apps keep their origin. Returns a note explaining why the deploy
31
+ * stays on Pages, or null when there is nothing to say. Throws when
32
+ * registration cannot be confirmed or the converted manifest cannot be saved.
33
+ */
34
+ export declare function defaultToDispatch(cwd: string, manifestPath: string, io: CliIo): Promise<string | null>;
26
35
  /** Pure wording branch kept exported for the v1 byte-identity contract. */
27
36
  /** What the local manifest says the origin is: v1 manifests are always pages;
28
37
  * a v2 manifest defaults to pages; `undefined` means it could not be read. */
@@ -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,EAAS,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAQjE,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;AA4tBxC,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
+ {"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,CA6B5G;AAED,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,3 +1,29 @@
1
1
  import type { Command } from "../dispatcher.js";
2
2
  export declare const migrateCommand: Command;
3
+ export declare const BACKUP_PATH = ".launchpad/manifest-before-dispatch.yaml";
4
+ export type DispatchConversion = {
5
+ readonly kind: "already";
6
+ } | {
7
+ readonly kind: "eligible" | "converted";
8
+ readonly slug: string;
9
+ };
10
+ /**
11
+ * Convert `<cwd>/launchpad.yaml` to a v2 dispatch manifest, or report that it
12
+ * would convert (`dryRun`). Throws with a user-facing reason when the app is
13
+ * registered, the registry cannot be read, or the app has a runtime dispatch
14
+ * cannot preserve. Shared by `migrate` and first-deploy auto-conversion.
15
+ */
16
+ export declare function convertToDispatch(cwd: string, dryRun: boolean): Promise<DispatchConversion>;
17
+ /** The app is eligible but the converted manifest or its backup could not be written. */
18
+ export declare class MigrationSaveError extends Error {
19
+ constructor(cause: unknown);
20
+ }
21
+ /** The registry could not confirm the slug is unregistered, so nothing was converted. */
22
+ export declare class RegistryUnavailableError extends Error {
23
+ constructor(cause: unknown);
24
+ }
25
+ /** The slug already has an app record; its origin changes only via a planned infrastructure migration. */
26
+ export declare class RegisteredAppError extends Error {
27
+ constructor();
28
+ }
3
29
  //# sourceMappingURL=migrate.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAS,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAIvD,eAAO,MAAM,cAAc,EAAE,OAI5B,CAAC"}
1
+ {"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../src/commands/migrate.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAS,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAIvD,eAAO,MAAM,cAAc,EAAE,OAI5B,CAAC;AA+BF,eAAO,MAAM,WAAW,6CAA6C,CAAC;AAEtE,MAAM,MAAM,kBAAkB,GAC1B;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAA;CAAE,GAC5B;IAAE,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,WAAW,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvE;;;;;GAKG;AACH,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAmBjG;AAcD,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"}
package/dist/version.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- export declare const CLI_VERSION = "0.62.0";
1
+ export declare const CLI_VERSION = "0.63.0";
2
2
  //# sourceMappingURL=version.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@m-kopa/launchpad-cli",
3
- "version": "0.62.0",
3
+ "version": "0.63.0",
4
4
  "description": "Launchpad CLI — clone / deploy / review / merge against Launchpad-managed apps. Talks to the portal-bot endpoints (SCOPE-M-760 / T4).",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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.62.0
4
+ version: 0.63.0
5
5
  ---
6
6
 
7
7
  <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
@@ -56,10 +56,11 @@ change — and how do you know it's actually serving.
56
56
  Keep the existing audience for ordinary content updates.
57
57
  Do not broaden groups to bypass a validation or deployment failure.
58
58
  If this update newly grants a company-wide audience, ask: **"Are you sure you want every employee to be able to open this app?"**
59
- Name the app and groups, explain that any selected group grants access, and give the human the server's confirmation URL.
60
- Never submit the browser decision on their behalf.
61
- Wait for their confirmation, then retry the unchanged deployment.
62
- Sensitive apps also require platform-admin approval.
59
+ Name the app and groups, explain that any selected group grants access, and wait for their explicit answer in this conversation.
60
+ If they decline, stop the grant and keep the existing audience.
61
+ For ordinary apps, continue after they agree; no browser confirmation, decision ID or extra acknowledgement flag is required.
62
+ Sensitive apps retain builder confirmation and platform-admin approval through the server's confirmation URL.
63
+ Never submit that browser decision on their behalf; retry only after the required people complete it.
63
64
  Do not review or recertify unchanged existing audiences as part of a routine update.
64
65
  Catalogue visibility controls discovery, not access; platform administrators retain entry independently of app groups.
65
66
 
@@ -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.62.0
4
+ version: 0.63.0
5
5
  ---
6
6
 
7
7
  <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
@@ -48,12 +48,16 @@ Never select `S_ALL_Employees`, `S_ALL`, or another broad group to fix a missing
48
48
 
49
49
  Before requesting a new company-wide grant, ask the human exactly: **"Are you sure you want every employee to be able to open this app?"**
50
50
  Name the app and the selected groups, and explain that membership in any selected group grants access.
51
- A general instruction to deploy, a generic yes flag, and a previous approval for another app or revision do not answer this question.
52
- When the server returns `audience_confirmation_required`, give the user its confirmation URL and wait for them to complete the browser decision.
53
- Never open the decision endpoint with automation to approve it, submit its form, or manufacture an acknowledgement for the user.
54
- Retry the same deployment only after the human confirms completion.
55
- Sensitive apps also require a platform administrator's approval.
56
- A changed audience or revision needs a new decision; unchanged existing audiences do not require recertification.
51
+ A general instruction to deploy, a generic yes flag, and a previous approval for another app or audience do not answer this question.
52
+ Wait for the human's explicit answer in this conversation before deploying the company-wide grant.
53
+ If they decline, stop the grant and keep the existing audience.
54
+ For ordinary apps, their answer is the confirmation: continue the deployment without a browser, confirmation ID, token or extra acknowledgement flag.
55
+ The server does not attest to this conversation; do not claim that it enforces the human decision.
56
+ Sensitive apps retain the separate builder-confirmation and platform-admin approval requirement.
57
+ If the server returns `audience_confirmation_required` with `requires_admin_approval: true`, give the human its URL and retry only after the required people complete approval.
58
+ Never submit that browser decision on their behalf.
59
+ If the error does not require admin approval, report that the server needs updating rather than telling the user a browser visit is part of the ordinary flow.
60
+ A changed audience needs an explicit answer for that audience; unchanged existing audiences do not require recertification.
57
61
 
58
62
  Catalogue listing controls discovery only.
59
63
  It neither grants access nor makes an app private.
@@ -151,12 +155,12 @@ Gateway placement is automatic for new gateway-fronted apps, including Pages API
151
155
  The platform selects the configured gateway with the most free hostname slots, counting pending deployments as reservations.
152
156
  Do not ask the author to choose a gateway or add gateway project names to the manifest.
153
157
  Existing apps and retries retain their assigned gateway and public URL.
154
- Updating the CLI does not rewrite an existing Pages manifest.
155
- If an unprovisioned static or React app hits the Pages project limit, run `launchpad update`, then `launchpad migrate --origin dispatch --dry-run`.
156
- When eligible, run `launchpad migrate --origin dispatch`, review the preserved audience and build settings, then run `launchpad deploy`.
157
- The command checks the live registry and saves the original YAML under `.launchpad/manifest-before-dispatch.yaml`.
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.
159
+ Tell the author when deploy reports the conversion, and point them at the backup in `.launchpad/manifest-before-dispatch.yaml`.
160
+ `launchpad migrate --origin dispatch --dry-run` runs the same check without deploying, if the author wants to see it first.
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.
158
162
  Do not regenerate the manifest with `init`, broaden its groups, or change private ownership to work around the quota.
159
- If migration refuses functions, custom runtime configuration or bindings, stop and explain the named limitation.
163
+ If `launchpad migrate` refuses functions, custom runtime configuration or bindings, explain the named limitation; a normal `launchpad deploy` still works on Pages.
160
164
  Pages Functions apps need a compiled Worker build; changing `appType` to `react` would not convert their API.
161
165
  Do not use this command to migrate a registered app or delete its Pages resources.
162
166
  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.
@@ -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.62.0
4
+ version: 0.63.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.62.0
4
+ version: 0.63.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.62.0
4
+ version: 0.63.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.62.0
4
+ version: 0.63.0
5
5
  ---
6
6
 
7
7
  <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->
@@ -220,6 +220,7 @@ The generated v2 manifest preserves the detected build settings and the chosen a
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
- React API starters remain on Pages, and existing manifests keep their current origin.
223
+ React API starters remain on Pages and registered apps keep their current origin.
224
+ An older manifest for an unregistered static or React app is converted to dispatch on its first `launchpad deploy`.
224
225
  An explicit `--origin pages` is available when required.
225
226
  Dispatch secret bindings are not supported yet, so do not promise that `launchpad secrets push` will work for those apps.
@@ -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.62.0
4
+ version: 0.63.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.62.0
4
+ version: 0.63.0
5
5
  ---
6
6
 
7
7
  <!-- BEGIN shell-contract (managed by scripts/sync-skill-contract.sh — edit skills/_partials/shell-contract.md) -->