@rebasepro/cli 0.19.2-canary.gef769df → 0.20.1-canary.g4d882ca

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/dist/bundle.d.ts CHANGED
@@ -398,6 +398,8 @@ export declare function foldStaticIntoBundle(options: {
398
398
  path: string;
399
399
  /** Serve `index.html` for unmatched paths under `path`. */
400
400
  spa: boolean;
401
+ /** Where this app mounts the Rebase CMS, if it does. */
402
+ cms?: string;
401
403
  }): {
402
404
  fileCount: number;
403
405
  dir: string;
@@ -412,6 +414,8 @@ export declare function buildStaticBundle(options: {
412
414
  path?: string;
413
415
  /** Serve `index.html` for unmatched paths. Default `true`. */
414
416
  spa?: boolean;
417
+ /** Where this app mounts the Rebase CMS, if it does. */
418
+ cms?: string;
415
419
  }): {
416
420
  outDir: string;
417
421
  manifest: RebaseBundleManifest;
@@ -6,6 +6,7 @@ export interface FoldableManifest {
6
6
  output?: string;
7
7
  path?: string;
8
8
  spa?: boolean;
9
+ cms?: string;
9
10
  }>;
10
11
  }
11
12
  export interface FoldOptions {
@@ -32,6 +33,8 @@ export interface FoldableApp {
32
33
  path: string;
33
34
  /** SPA fallback, defaulted to `true`. */
34
35
  spa: boolean;
36
+ /** Where this app mounts the Rebase CMS, if it does. */
37
+ cms?: string;
35
38
  }
36
39
  /**
37
40
  * Every static app in the manifest, in mount order.
package/dist/index.es.js CHANGED
@@ -1474,33 +1474,12 @@ async function recordEvent(event, properties = {}, options = {}) {
1474
1474
  }
1475
1475
  //#endregion
1476
1476
  //#region src/telemetry/consent.ts
1477
- /**
1478
- * Asking, and what the question looks like.
1479
- *
1480
- * ## Why the prompt comes *after* the work
1481
- *
1482
- * The first `rebase init` on a machine is the event most worth having, and it
1483
- * is the one where no consent exists yet. Asking before scaffolding puts a
1484
- * privacy negotiation in front of someone who has not yet seen the tool do
1485
- * anything — the worst possible moment, and a reliable way to get a reflexive
1486
- * no.
1487
- *
1488
- * So the question is asked once the project exists and the user has seen it
1489
- * work. The event's data is still in memory at that point, so nothing is lost
1490
- * by waiting, and — this is the part that matters — **nothing has been
1491
- * transmitted or written**. Declining leaves no id, no file, no record.
1492
- *
1493
- * ## Why the payload is shown rather than described
1494
- *
1495
- * "Anonymous usage data" is a phrase that has been used to mean almost
1496
- * anything. Printing the exact JSON costs four lines of output and replaces a
1497
- * claim the user has to take on faith with something they can read. It is also
1498
- * the same builder the sender uses, so it cannot drift into a comfortable
1499
- * fiction.
1500
- */
1501
- /** True when we may ask: no decision recorded, and nothing else forbids it. */
1502
- function shouldPrompt(env = process.env) {
1503
- return suppressionReason(env) === "not_asked" && Boolean(process.stdin.isTTY);
1477
+ /** True when we may ask: nothing forbids it, and there is a question to ask. */
1478
+ function shouldPrompt(env = process.env, { reAskDeclined = false } = {}) {
1479
+ if (!process.stdin.isTTY) return false;
1480
+ const reason = suppressionReason(env);
1481
+ if (reason === "not_asked") return true;
1482
+ return reAskDeclined && reason === "declined";
1504
1483
  }
1505
1484
  function renderPreview(event, properties) {
1506
1485
  const preview = buildEvent(event, properties, {
@@ -1516,30 +1495,33 @@ function renderPreview(event, properties) {
1516
1495
  * CI must behave exactly as it does today, which means not asking and not
1517
1496
  * sending.
1518
1497
  */
1519
- async function promptForConsent(event, properties) {
1520
- if (!shouldPrompt()) return false;
1498
+ async function promptForConsent(options = {}) {
1499
+ if (!shouldPrompt(process.env, options)) return false;
1500
+ const askedBefore = suppressionReason(process.env) === "declined";
1521
1501
  try {
1522
1502
  console.log("");
1523
1503
  console.log(chalk.bold("Help improve Rebase?"));
1524
1504
  console.log("");
1525
- console.log(chalk.gray(" Rebase is self-hosted, so we have no idea what works and what does not"));
1526
- console.log(chalk.gray(" unless you tell us. Sharing is entirely optional and off by default."));
1505
+ console.log(chalk.gray(" Rebase is self-hosted, so we only learn what works if you tell us."));
1506
+ console.log(chalk.gray(" Anonymous: random ids, the CLI version, your OS, and which template and"));
1507
+ console.log(chalk.gray(" package manager you used. Never project names, paths, schemas, URLs or"));
1508
+ console.log(chalk.gray(" error messages."));
1527
1509
  console.log("");
1528
- console.log(chalk.gray(" This is exactly what would be sent nothing more, ever:"));
1529
- console.log("");
1530
- console.log(renderPreview(event, properties).split("\n").map((line) => chalk.gray(" " + line)).join("\n"));
1531
- console.log("");
1532
- console.log(chalk.gray(" No project names, paths, schemas, URLs or error messages. Change your"));
1533
- console.log(chalk.gray(` mind any time with ${chalk.cyan("rebase telemetry disable")}.`));
1510
+ console.log(chalk.gray(` Print the exact payload with ${chalk.cyan("rebase telemetry show")}; change your mind`));
1511
+ console.log(chalk.gray(` any time with ${chalk.cyan("rebase telemetry disable")}.`));
1512
+ if (askedBefore) {
1513
+ console.log("");
1514
+ console.log(chalk.gray(" You declined before; that stands unless you change it here."));
1515
+ }
1534
1516
  console.log("");
1535
1517
  const { accepted } = await inquirer.prompt([{
1536
1518
  type: "confirm",
1537
1519
  name: "accepted",
1538
1520
  message: "Share anonymous usage data?",
1539
- default: false
1521
+ default: true
1540
1522
  }]);
1541
1523
  setConsent(Boolean(accepted));
1542
- console.log(accepted ? chalk.green(" Thank you — sharing enabled.") : chalk.gray(" Nothing will be sent. You will not be asked again."));
1524
+ console.log(accepted ? chalk.green(" Thank you — sharing enabled.") : chalk.gray(" Nothing will be sent." + (options.reAskDeclined ? " You will be asked again the next time you scaffold a project." : " You will not be asked again.")));
1543
1525
  console.log("");
1544
1526
  return Boolean(accepted);
1545
1527
  } catch {
@@ -1653,7 +1635,7 @@ function buildInitQuestions(params) {
1653
1635
  type: "input",
1654
1636
  name: "projectName",
1655
1637
  message: "Project name:",
1656
- default: "my-rebase-app",
1638
+ default: "my-app",
1657
1639
  validate: (input) => validateProjectName(input) ?? true
1658
1640
  });
1659
1641
  if (headlessArg === void 0) questions.push({
@@ -1721,7 +1703,7 @@ ${chalk.bold("Usage")}
1721
1703
  rebase init ${chalk.blue("[name]")} [options]
1722
1704
 
1723
1705
  ${chalk.gray("The name may be a nested path (apps/my-app) or \".\" for the current directory.")}
1724
- ${chalk.gray("Defaults to \"my-rebase-app\" when omitted with --yes.")}
1706
+ ${chalk.gray("Asks when omitted; defaults to \"my-app\", and takes it without asking under --yes.")}
1725
1707
 
1726
1708
  ${chalk.bold("Options")}
1727
1709
  ${chalk.blue("-t, --template")} ${chalk.gray("<preset>")} blog | ecommerce | blank ${chalk.gray("(default: blog)")}
@@ -1803,7 +1785,7 @@ async function promptForOptions(rawArgs, pm) {
1803
1785
  const installFlag = noInstall ? false : args["--install"] ?? false;
1804
1786
  const hasInstallFlag = noInstall || args["--install"] === true;
1805
1787
  if (isNonInteractive) {
1806
- const projectName = nameArg || "my-rebase-app";
1788
+ const projectName = nameArg || "my-app";
1807
1789
  const targetDirectory = path.resolve(process.cwd(), projectName);
1808
1790
  const templateDirectory = path.resolve(cliRoot, "templates", "template");
1809
1791
  const pmCommands = getPMCommands(pm);
@@ -2139,7 +2121,7 @@ async function createProject$1(options) {
2139
2121
  git: Boolean(options.git),
2140
2122
  duration: durationBucket(Date.now() - startedAt)
2141
2123
  };
2142
- if (await promptForConsent("cli.init", initProperties)) await recordEvent("cli.init", initProperties, { projectRoot: options.targetDirectory });
2124
+ if (await promptForConsent({ reAskDeclined: true })) await recordEvent("cli.init", initProperties, { projectRoot: options.targetDirectory });
2143
2125
  }
2144
2126
  /**
2145
2127
  * Apply a template preset by replacing the default collection files.
@@ -4418,6 +4400,38 @@ function checkAppPath(value, fieldPath, issues) {
4418
4400
  }
4419
4401
  return value;
4420
4402
  }
4403
+ /**
4404
+ * Is `candidate` the path `parent`, or something beneath it?
4405
+ *
4406
+ * Segment-aware for the same reason `isForbiddenStaticPath` is: a plain
4407
+ * `startsWith` reads `/admin` as living under `/adm`.
4408
+ */
4409
+ function isUnderPath(candidate, parent) {
4410
+ if (parent === "/") return true;
4411
+ return candidate === parent || candidate.startsWith(`${parent}/`);
4412
+ }
4413
+ /**
4414
+ * Validate where an app says it mounts the Rebase CMS.
4415
+ *
4416
+ * The same shape as {@link checkAppPath} — it is a URL path and ends up in the
4417
+ * same places — plus one rule of its own: it has to be inside the app declaring
4418
+ * it. The app's SPA fallback is what answers that URL, so a `cms` outside its
4419
+ * `path` names an address this app will never serve. That is a link the console
4420
+ * would then offer to a 404, which is worse than the missing link it replaces.
4421
+ */
4422
+ function checkCmsPath(value, appPath, fieldPath, issues) {
4423
+ if (value === void 0) return void 0;
4424
+ const cms = checkAppPath(value, fieldPath, issues);
4425
+ if (cms === void 0) return void 0;
4426
+ if (!isUnderPath(cms, appPath)) {
4427
+ issues.push({
4428
+ path: fieldPath,
4429
+ message: `must be inside this app's path — it is at "${appPath}", so it cannot serve "${cms}". The CMS is a route of this app, not a separate deployment; if it really lives elsewhere, declare that app and put \`cms\` on it instead`
4430
+ });
4431
+ return;
4432
+ }
4433
+ return cms;
4434
+ }
4421
4435
  function isRecord(value) {
4422
4436
  return typeof value === "object" && value !== null && !Array.isArray(value);
4423
4437
  }
@@ -4480,7 +4494,8 @@ var KNOWN_APP_FIELDS = {
4480
4494
  "build",
4481
4495
  "output",
4482
4496
  "path",
4483
- "spa"
4497
+ "spa",
4498
+ "cms"
4484
4499
  ]
4485
4500
  };
4486
4501
  /**
@@ -4592,7 +4607,7 @@ function validateApp(name, raw, issues) {
4592
4607
  });
4593
4608
  return raw;
4594
4609
  }
4595
- case "static":
4610
+ case "static": {
4596
4611
  checkRelativePath(raw.root, `${base}.root`, issues, { required: true });
4597
4612
  checkRelativePath(raw.output, `${base}.output`, issues, { required: true });
4598
4613
  if (raw.build !== void 0 && typeof raw.build !== "string") issues.push({
@@ -4603,8 +4618,10 @@ function validateApp(name, raw, issues) {
4603
4618
  path: `${base}.spa`,
4604
4619
  message: "must be a boolean"
4605
4620
  });
4606
- checkAppPath(raw.path, `${base}.path`, issues);
4621
+ const appPath = checkAppPath(raw.path, `${base}.path`, issues);
4622
+ checkCmsPath(raw.cms, appPath ?? "/", `${base}.cms`, issues);
4607
4623
  return raw;
4624
+ }
4608
4625
  default: return;
4609
4626
  }
4610
4627
  }
@@ -4671,6 +4688,11 @@ function validateManifest(raw) {
4671
4688
  }
4672
4689
  byPath.set(at, name);
4673
4690
  }
4691
+ const withCms = Object.entries(apps).filter(([, app]) => app.type === "static" && typeof app.cms === "string");
4692
+ if (withCms.length > 1) for (const [name] of withCms.slice(1)) issues.push({
4693
+ path: `apps.${name}.cms`,
4694
+ message: `a project has one CMS — "${withCms[0][0]}" already declares one`
4695
+ });
4674
4696
  refuseStorageBlock(raw.storage, issues);
4675
4697
  let telemetry;
4676
4698
  if (raw.telemetry !== void 0) if (typeof raw.telemetry === "boolean") telemetry = raw.telemetry;
@@ -4853,6 +4875,30 @@ function findBackendApp(manifest) {
4853
4875
  };
4854
4876
  }
4855
4877
  /**
4878
+ * Where this project mounts the Rebase CMS, if it says.
4879
+ *
4880
+ * The CMS is a component inside a developer's own app, so its address is a
4881
+ * client-side route: no build artifact, no running server and no control plane
4882
+ * can observe it. {@link RebaseStaticAppConfig.cms} is the only place that fact
4883
+ * is ever written down, and this is the one reader of it — so that "the
4884
+ * project's CMS" means the same thing to `rebase dev`, `rebase apps list`, the
4885
+ * bundle it builds and the console that shows it.
4886
+ *
4887
+ * Returns the *serving* app alongside the path, because a caller with a base URL
4888
+ * needs to know which app answers there — and because the path alone cannot say
4889
+ * whether the CMS is the whole of an app or one route of it.
4890
+ */
4891
+ function cmsMountOf(manifest) {
4892
+ for (const [name, app] of Object.entries(manifest.apps)) {
4893
+ if (app.type !== "static" || typeof app.cms !== "string") continue;
4894
+ return {
4895
+ appName: name,
4896
+ app,
4897
+ path: app.cms
4898
+ };
4899
+ }
4900
+ }
4901
+ /**
4856
4902
  * The app a deploy targets: the one named, or the obvious one.
4857
4903
  *
4858
4904
  * A repository declares apps; a project owns them. So "which app" is a question
@@ -5649,14 +5695,21 @@ async function devCommand(rawArgs) {
5649
5695
  * a shape that has no such directory reads as a broken scaffold on the
5650
5696
  * very first run of the very command the headless quickstart names.
5651
5697
  */
5652
- const declaresStaticApp = (() => {
5698
+ const staticShape = (() => {
5653
5699
  try {
5654
5700
  const { manifest } = loadManifest(projectRoot);
5655
- return Object.values(manifest.apps ?? {}).some((app) => app.type === "static");
5701
+ return {
5702
+ declaresStaticApp: Object.values(manifest.apps ?? {}).some((app) => app.type === "static"),
5703
+ cmsPath: cmsMountOf(manifest)?.path
5704
+ };
5656
5705
  } catch {
5657
- return Boolean(frontendDir);
5706
+ return {
5707
+ declaresStaticApp: Boolean(frontendDir),
5708
+ cmsPath: void 0
5709
+ };
5658
5710
  }
5659
5711
  })();
5712
+ const declaresStaticApp = staticShape.declaresStaticApp;
5660
5713
  let frontendUrl = "";
5661
5714
  let backendUrl = "";
5662
5715
  let debounceSummary = null;
@@ -5696,6 +5749,7 @@ async function devCommand(rawArgs) {
5696
5749
  ["✦ Rebase API is ready!", ""],
5697
5750
  ["➜ API: ", api]
5698
5751
  ];
5752
+ if (declaresStaticApp && staticShape.cmsPath && staticShape.cmsPath !== "/") lines.push(["➜ CMS: ", `${stripAnsi(frontendUrl).replace(/\/$/, "")}${staticShape.cmsPath}`]);
5699
5753
  if (!declaresStaticApp) {
5700
5754
  if (swaggerPath) lines.push(["➜ Swagger: ", `${api}${swaggerPath}`]);
5701
5755
  else lines.push([" ", "(no tables served yet, so no data API and no docs)"]);
@@ -7812,7 +7866,7 @@ function vendorDependencies(options) {
7812
7866
  * in one image already, that is exactly what it had.
7813
7867
  */
7814
7868
  function foldStaticIntoBundle(options) {
7815
- const { bundleDir, assetsDir, appName, path: basePath, spa } = options;
7869
+ const { bundleDir, assetsDir, appName, path: basePath, spa, cms } = options;
7816
7870
  const manifestPath = path.join(bundleDir, "manifest.json");
7817
7871
  if (!fs.existsSync(manifestPath)) throw new Error(`No manifest at ${manifestPath} — build the backend bundle first.`);
7818
7872
  if (!fs.existsSync(assetsDir)) throw new Error(`No built assets at ${assetsDir}.`);
@@ -7837,7 +7891,9 @@ function foldStaticIntoBundle(options) {
7837
7891
  static: [...existing, {
7838
7892
  path: basePath,
7839
7893
  dir,
7840
- spa
7894
+ spa,
7895
+ name: appName,
7896
+ ...cms ? { cms } : {}
7841
7897
  }]
7842
7898
  };
7843
7899
  fs.writeFileSync(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
@@ -7872,7 +7928,9 @@ function buildStaticBundle(options) {
7872
7928
  entry: { static: [{
7873
7929
  path: basePath,
7874
7930
  dir: "static",
7875
- spa: options.spa ?? true
7931
+ spa: options.spa ?? true,
7932
+ name: appName,
7933
+ ...options.cms ? { cms: options.cms } : {}
7876
7934
  }] },
7877
7935
  hooks: { native: false },
7878
7936
  deps: { declared: {} },
@@ -8004,7 +8062,8 @@ function foldableApps(manifest) {
8004
8062
  build: app.build,
8005
8063
  output: app.output,
8006
8064
  path: app.path ?? "/",
8007
- spa: app.spa ?? true
8065
+ spa: app.spa ?? true,
8066
+ cms: app.cms
8008
8067
  });
8009
8068
  }
8010
8069
  apps.sort((a, b) => b.path.length - a.path.length);
@@ -8124,7 +8183,8 @@ async function foldFrontendIntoBundle(options) {
8124
8183
  assetsDir,
8125
8184
  appName: app.name,
8126
8185
  path: app.path,
8127
- spa: app.spa
8186
+ spa: app.spa,
8187
+ cms: app.cms
8128
8188
  });
8129
8189
  outcomes.push({
8130
8190
  appName: app.name,
@@ -8836,7 +8896,8 @@ async function buildAssetApp(projectRoot, name, app, runtimeRange, outOverride)
8836
8896
  outDir: outOverride ? path.resolve(process.cwd(), outOverride) : path.join(projectRoot, `dist-bundle-${name}`),
8837
8897
  runtimeRange,
8838
8898
  path: basePath,
8839
- spa: asset.spa ?? true
8899
+ spa: asset.spa ?? true,
8900
+ cms: asset.cms
8840
8901
  });
8841
8902
  const rel = path.relative(projectRoot, result.outDir);
8842
8903
  console.log(chalk.green(` ✓ static bundle → ${rel}/`) + chalk.dim(` (${result.fileCount} file(s) → served at ${basePath})`));
@@ -9019,13 +9080,6 @@ async function ejectCommand(rawArgs = []) {
9019
9080
  const dryRun = Boolean(args["--dry-run"]);
9020
9081
  const force = Boolean(args["--force"]);
9021
9082
  const requested = positionals[0];
9022
- if (requested === "infra") {
9023
- console.error(chalk.red(" ✗ `rebase eject infra` has been removed."));
9024
- console.error(chalk.gray(" It wrote rebase.infra.json, which nothing ever read. Resources bind"));
9025
- console.error(chalk.gray(" from the environment on the <BASE>__<KEY> convention; declare them in"));
9026
- console.error(chalk.gray(" config/resources.ts and see `rebase resources` for the names."));
9027
- process.exit(1);
9028
- }
9029
9083
  let loaded;
9030
9084
  try {
9031
9085
  loaded = loadManifest(projectRoot);
@@ -11750,8 +11804,11 @@ function printPayload() {
11750
11804
  console.log("");
11751
11805
  console.log(` ${describeState()}`);
11752
11806
  console.log("");
11753
- console.log(chalk.gray(" Nothing is being sent, so there is no payload to show."));
11754
- console.log(chalk.gray(` Run ${chalk.cyan("rebase telemetry enable")} first if you want to inspect one.`));
11807
+ console.log(chalk.gray(" Nothing is being sent. This is what WOULD be sent if you said yes:"));
11808
+ console.log("");
11809
+ console.log(renderPreview("cli.dev", { first_run: false }).split("\n").map((l) => " " + l).join("\n"));
11810
+ console.log("");
11811
+ console.log(chalk.gray(` Start sharing with ${chalk.cyan("rebase telemetry enable")}.`));
11755
11812
  console.log("");
11756
11813
  return;
11757
11814
  }
@@ -11767,7 +11824,7 @@ function printPayload() {
11767
11824
  }
11768
11825
  function printHelp$2() {
11769
11826
  console.log(`
11770
- ${chalk.bold("rebase telemetry")} — anonymous usage sharing (opt-in, off by default)
11827
+ ${chalk.bold("rebase telemetry")} — anonymous usage sharing (asked once per project)
11771
11828
 
11772
11829
  ${chalk.bold("Commands")}
11773
11830
  ${chalk.blue("status")} Whether anything is being shared, and why ${chalk.gray("(default)")}
@@ -15174,7 +15231,31 @@ function resolveDeployArgs(rawArgs) {
15174
15231
  appName: positionals[0]
15175
15232
  };
15176
15233
  }
15234
+ /**
15235
+ * `cli.deploy` — the event that says whether anyone ever ships.
15236
+ *
15237
+ * Declared in `TelemetryEventName` from the start and recorded nowhere, so a
15238
+ * consenting user reported scaffolding, dev servers and schema pushes and never
15239
+ * the one act the product exists for.
15240
+ *
15241
+ * What it carries is deliberately thin. No project id, no app name, no
15242
+ * deployment id, no URL: those are the "project names" and "URLs" the consent
15243
+ * screen promises are never sent, and a deployment id is a durable handle on
15244
+ * somebody's infrastructure. `frameworkVersion` is *our* version, not the
15245
+ * user's data, and it is the one field that makes the rest legible — a failure
15246
+ * rate means nothing without knowing which release it is a rate for.
15247
+ */
15248
+ async function recordDeploy(o) {
15249
+ await recordEvent("cli.deploy", {
15250
+ followed: o.followed,
15251
+ status: o.status,
15252
+ deduplicated: o.deduplicated,
15253
+ framework_version: o.frameworkVersion ?? null,
15254
+ duration: durationBucket(Date.now() - o.startedAt)
15255
+ }, { projectRoot: process.cwd() });
15256
+ }
15177
15257
  async function deployCommand(rawArgs, projectRef) {
15258
+ const startedAt = Date.now();
15178
15259
  const { flags: args, appName } = resolveDeployArgs(rawArgs);
15179
15260
  if (args["--wait"] && args["--no-follow"]) fail("--wait and --no-follow ask for opposite things.", "`deploy` follows by default — pass neither, or `--no-follow` to return as soon as the build is triggered.", "usage");
15180
15261
  const { client, url } = await requireClient(rawArgs);
@@ -15258,6 +15339,13 @@ async function deployCommand(rawArgs, projectRef) {
15258
15339
  following: false,
15259
15340
  ...warningPayload(warnings)
15260
15341
  });
15342
+ await recordDeploy({
15343
+ followed: false,
15344
+ status: "not_followed",
15345
+ deduplicated,
15346
+ frameworkVersion,
15347
+ startedAt
15348
+ });
15261
15349
  return;
15262
15350
  }
15263
15351
  if (!isJsonMode()) {
@@ -15270,6 +15358,13 @@ async function deployCommand(rawArgs, projectRef) {
15270
15358
  projectId,
15271
15359
  url
15272
15360
  });
15361
+ await recordDeploy({
15362
+ followed: true,
15363
+ status,
15364
+ deduplicated,
15365
+ frameworkVersion,
15366
+ startedAt
15367
+ });
15273
15368
  emit(() => {}, {
15274
15369
  deploymentId,
15275
15370
  deduplicated,
@@ -18871,7 +18966,7 @@ async function appsCommand(subcommand, rawArgs = []) {
18871
18966
  function describeApp(app) {
18872
18967
  switch (app.type) {
18873
18968
  case "backend": return app.runtime === "custom" ? `custom runtime — ${app.dockerfile ?? "Dockerfile"}` : `managed runtime, config: ${app.config ?? "config"}`;
18874
- case "static": return `${app.root} → ${app.output} @ ${app.path ?? "/"}`;
18969
+ case "static": return `${app.root} → ${app.output} @ ${app.path ?? "/"}` + (app.cms ? ` ${chalk.magenta(`CMS at ${app.cms}`)}` : "");
18875
18970
  default: return "";
18876
18971
  }
18877
18972
  }
@@ -19079,6 +19174,24 @@ async function entry(args) {
19079
19174
  return;
19080
19175
  }
19081
19176
  const effectiveSubcommand = parsedArgs["--help"] && !subcommand ? "--help" : subcommand;
19177
+ try {
19178
+ await dispatch(command, effectiveSubcommand, args, parsedArgs, namespacedCommands);
19179
+ } catch (error) {
19180
+ await recordEvent("cli.error", {
19181
+ command: command ?? "none",
19182
+ subcommand: effectiveSubcommand ?? "none",
19183
+ error_type: error instanceof Error ? error.constructor.name : "unknown",
19184
+ usage: Boolean(error && typeof error === "object" && error.isUsageError)
19185
+ }, { projectRoot: process.cwd() });
19186
+ throw error;
19187
+ }
19188
+ }
19189
+ /**
19190
+ * The dispatch, lifted out of {@link entry} so the whole of it sits inside one
19191
+ * `try`. Inlining the switch there would have put the `catch` around the flag
19192
+ * parsing too, which throws its own usage errors before a command is even named.
19193
+ */
19194
+ async function dispatch(command, effectiveSubcommand, args, parsedArgs, namespacedCommands) {
19082
19195
  switch (command) {
19083
19196
  case "__dev-db-daemon": {
19084
19197
  const { parseDaemonArgs, runDaemon } = await import("./daemon-entry-LTFKtpHy.js");
@@ -19256,6 +19369,6 @@ function telemetryNotice() {
19256
19369
  return chalk.gray(`Usage sharing: ${sharing ? "on" : "off"} — ${chalk.cyan("rebase telemetry")} to inspect or change\n`);
19257
19370
  }
19258
19371
  //#endregion
19259
- export { CURRENT_RUNTIME_RANGE, DEFAULT_BUNDLE_DIR, DEFAULT_CONFIG_DIR, DEFAULT_CRONS_DIR, DEFAULT_FUNCTIONS_DIR, DEFAULT_SCHEMA_FILE, ManifestError, VENDOR_SIZE_MAX_BYTES, VENDOR_SIZE_WARN_BYTES, VENDOR_TARGET_CPU, VENDOR_TARGET_OS, assessManagedCompatibility, buildBundle, buildStaticBundle, buildableApps, collectDeclaredDependencies, commentSpans, composeBundleManifest, detectDeclaredDepConflicts, detectFrameworkDepDrift, detectNativeDependencies, detectStorageAuthorize, entry, findBackendApp, findUnusedServerEntry, foldStaticIntoBundle, loadManifest, manifestExists, manifestPath, normalizeEsmSpecifiers, resolveBackendPaths, selectDeployApp, synthesizeManifest, validateManifest, vendorDependencies, writeManifest };
19372
+ export { CURRENT_RUNTIME_RANGE, DEFAULT_BUNDLE_DIR, DEFAULT_CONFIG_DIR, DEFAULT_CRONS_DIR, DEFAULT_FUNCTIONS_DIR, DEFAULT_SCHEMA_FILE, ManifestError, VENDOR_SIZE_MAX_BYTES, VENDOR_SIZE_WARN_BYTES, VENDOR_TARGET_CPU, VENDOR_TARGET_OS, assessManagedCompatibility, buildBundle, buildStaticBundle, buildableApps, cmsMountOf, collectDeclaredDependencies, commentSpans, composeBundleManifest, detectDeclaredDepConflicts, detectFrameworkDepDrift, detectNativeDependencies, detectStorageAuthorize, entry, findBackendApp, findUnusedServerEntry, foldStaticIntoBundle, loadManifest, manifestExists, manifestPath, normalizeEsmSpecifiers, resolveBackendPaths, selectDeployApp, synthesizeManifest, validateManifest, vendorDependencies, writeManifest };
19260
19373
 
19261
19374
  //# sourceMappingURL=index.es.js.map