@rebasepro/cli 0.9.1-canary.16c42e9 → 0.9.1-canary.26fe4b2

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/index.es.js CHANGED
@@ -875,7 +875,54 @@ function buildInitQuestions(params) {
875
875
  });
876
876
  return questions;
877
877
  }
878
+ /**
879
+ * The `cd` a user must type to enter the new project.
880
+ *
881
+ * Not the project's basename: `init apps/my-app` has to say `cd apps/my-app`,
882
+ * and `init .` returns "" because they are already in the project.
883
+ */
884
+ function formatCdTarget(cwd, targetDirectory) {
885
+ return path.relative(cwd, targetDirectory);
886
+ }
887
+ /** Help for `rebase init` — the flags were previously only discoverable by
888
+ * triggering the non-TTY error. */
889
+ function printInitHelp() {
890
+ console.log(`
891
+ ${chalk.bold("rebase init")} — Create a new Rebase project
892
+
893
+ ${chalk.bold("Usage")}
894
+ rebase init ${chalk.blue("[name]")} [options]
895
+
896
+ ${chalk.gray("The name may be a nested path (apps/my-app) or \".\" for the current directory.")}
897
+ ${chalk.gray("Defaults to \"my-rebase-app\" when omitted with --yes.")}
898
+
899
+ ${chalk.bold("Options")}
900
+ ${chalk.blue("-t, --template")} ${chalk.gray("<preset>")} blog | ecommerce | blank ${chalk.gray("(default: blog)")}
901
+ ${chalk.blue("-f, --flavor")} ${chalk.gray("<flavor>")} cms | baas ${chalk.gray("(default: cms)")}
902
+ ${chalk.blue("-y, --yes")} Accept defaults, never prompt ${chalk.gray("(required for CI / non-TTY)")}
903
+ ${chalk.blue("-i, --install")} Install dependencies after scaffolding
904
+ ${chalk.blue("-g, --git")} Initialize a git repository and make an initial commit
905
+ ${chalk.blue("--database-url")} ${chalk.gray("<url>")} Use an existing database instead of the generated one
906
+ ${chalk.blue("--introspect")} Generate collections from that database ${chalk.gray("(implies --template blank; needs --install)")}
907
+ ${chalk.blue("--project")} ${chalk.gray("<slug>")} Link the scaffold to a Rebase Cloud project
908
+ ${chalk.blue("--setup-key")} ${chalk.gray("<key>")} One-time key authenticating the cloud link ${chalk.gray("(use with --project)")}
909
+
910
+ ${chalk.bold("Flavors")}
911
+ ${chalk.blue("cms")} BaaS + admin UI, driven by collections you define ${chalk.gray("(like Payload/Directus)")}
912
+ ${chalk.blue("baas")} Headless API over your database — no collections, no UI ${chalk.gray("(like Supabase)")}
913
+ ${chalk.gray("--template has no effect on this flavor.")}
914
+
915
+ ${chalk.bold("Examples")}
916
+ ${chalk.gray("$")} rebase init my-shop --template ecommerce --install
917
+ ${chalk.gray("$")} rebase init my-api --flavor baas --yes
918
+ ${chalk.gray("$")} rebase init . --yes --git
919
+ `);
920
+ }
878
921
  async function createRebaseApp(rawArgs) {
922
+ if (rawArgs.includes("--help") || rawArgs.includes("-h")) {
923
+ printInitHelp();
924
+ return;
925
+ }
879
926
  console.log(`
880
927
  ${chalk.bold("Rebase")} — Create a new project 🚀
881
928
  `);
@@ -935,6 +982,7 @@ async function promptForOptions(rawArgs, pm) {
935
982
  databaseUrl: args["--database-url"] || void 0,
936
983
  introspect: args["--introspect"] || false,
937
984
  preset: templateArg || "blog",
985
+ explicitPreset: !!templateArg,
938
986
  flavor: flavorArg || "cms",
939
987
  pm,
940
988
  pmCommands,
@@ -972,6 +1020,7 @@ async function promptForOptions(rawArgs, pm) {
972
1020
  databaseUrl: answers.databaseUrl?.trim() || void 0,
973
1021
  introspect: answers.introspect || false,
974
1022
  preset: templateArg || answers.preset || "blog",
1023
+ explicitPreset: !!templateArg,
975
1024
  flavor: flavorArg || answers.flavor || "cms",
976
1025
  pm,
977
1026
  pmCommands,
@@ -1056,6 +1105,7 @@ async function createProject$1(options) {
1056
1105
  const shipped = path.join(options.targetDirectory, from);
1057
1106
  if (fs.existsSync(shipped)) fs.renameSync(shipped, path.join(options.targetDirectory, to));
1058
1107
  }
1108
+ if (options.flavor === "baas" && options.explicitPreset) console.log(chalk.yellow(` Ignoring --template ${options.preset}: the baas flavor has no collections.`));
1059
1109
  if (options.flavor !== "baas") {
1060
1110
  if (options.introspect && options.preset !== "blank") console.log(chalk.gray(" Using the blank template: collections will come from your database."));
1061
1111
  await applyPreset(options.targetDirectory, options.introspect ? "blank" : options.preset);
@@ -1067,6 +1117,33 @@ async function createProject$1(options) {
1067
1117
  console.log(chalk.gray(" Initializing git repository..."));
1068
1118
  try {
1069
1119
  await execa("git", ["init"], { cwd: options.targetDirectory });
1120
+ try {
1121
+ await execa("git", [
1122
+ "symbolic-ref",
1123
+ "HEAD",
1124
+ "refs/heads/main"
1125
+ ], { cwd: options.targetDirectory });
1126
+ } catch {}
1127
+ await execa("git", ["add", "-A"], { cwd: options.targetDirectory });
1128
+ let identity = {};
1129
+ try {
1130
+ await execa("git", ["config", "user.email"], { cwd: options.targetDirectory });
1131
+ } catch {
1132
+ identity = {
1133
+ GIT_AUTHOR_NAME: "Rebase",
1134
+ GIT_AUTHOR_EMAIL: "noreply@rebase.pro",
1135
+ GIT_COMMITTER_NAME: "Rebase",
1136
+ GIT_COMMITTER_EMAIL: "noreply@rebase.pro"
1137
+ };
1138
+ }
1139
+ await execa("git", [
1140
+ "commit",
1141
+ "-m",
1142
+ "Initial commit from Rebase"
1143
+ ], {
1144
+ cwd: options.targetDirectory,
1145
+ env: identity
1146
+ });
1070
1147
  } catch {
1071
1148
  console.warn(chalk.yellow(" Warning: Failed to initialize git repository"));
1072
1149
  }
@@ -1097,6 +1174,7 @@ async function createProject$1(options) {
1097
1174
  console.warn(chalk.yellow(` Warning: Failed to install dependencies. You may need to run \`${installCmd.join(" ")}\` manually.`));
1098
1175
  }
1099
1176
  }
1177
+ let introspected = false;
1100
1178
  if (options.introspect) {
1101
1179
  console.log("");
1102
1180
  if (options.installDeps) {
@@ -1112,6 +1190,7 @@ async function createProject$1(options) {
1112
1190
  stdio: "inherit"
1113
1191
  });
1114
1192
  console.log(chalk.green(" Database successfully introspected!"));
1193
+ introspected = true;
1115
1194
  } catch {
1116
1195
  console.warn(chalk.yellow(" Warning: Failed to introspect database automatically."));
1117
1196
  console.warn(chalk.yellow(` You can run \`${execCmd.join(" ")}\` then \`${generateCmd.join(" ")}\` manually after setup.`));
@@ -1130,13 +1209,21 @@ async function createProject$1(options) {
1130
1209
  const runDev = pmCommands.run("dev");
1131
1210
  const runDbPush = pmCommands.run("db:push");
1132
1211
  const isBaas = options.flavor === "baas";
1133
- console.log(` ${chalk.cyan("cd")} ${options.projectName}`);
1212
+ const cdTarget = formatCdTarget(process.cwd(), options.targetDirectory);
1213
+ if (cdTarget) console.log(` ${chalk.cyan("cd")} ${cdTarget}`);
1134
1214
  if (!options.installDeps) console.log(` ${chalk.cyan(installCmd.join(" "))}`);
1135
1215
  console.log("");
1136
- if (options.databaseUrl) if (options.introspect) {
1216
+ if (options.databaseUrl) if (introspected) {
1137
1217
  console.log(chalk.gray(" # Database has been introspected & collections generated!"));
1138
1218
  console.log(chalk.gray(" # Start the development server (frontend + backend):"));
1139
1219
  console.log(` ${chalk.cyan(runDev.join(" "))}`);
1220
+ } else if (options.introspect) {
1221
+ console.log(chalk.gray(" # Introspection did not run — finish it with:"));
1222
+ console.log(` ${chalk.cyan(execCmd.join(" "))}`);
1223
+ console.log(` ${chalk.cyan(generateCmd.join(" "))}`);
1224
+ console.log("");
1225
+ console.log(chalk.gray(" # Then start the development server:"));
1226
+ console.log(` ${chalk.cyan(runDev.join(" "))}`);
1140
1227
  } else {
1141
1228
  console.log(chalk.gray(" # Your custom database is configured in .env."));
1142
1229
  console.log(chalk.gray(" # If the database is empty, push the Rebase schema to initialize it:"));
@@ -2477,22 +2564,43 @@ function installForAgent(agentKey, skills, projectDir) {
2477
2564
  }
2478
2565
  return count;
2479
2566
  }
2480
- async function skillsCommand(subcommand, _args) {
2567
+ async function skillsCommand(subcommand, rawArgs) {
2481
2568
  switch (subcommand) {
2482
2569
  case "install":
2483
- await skillsInstall();
2570
+ await skillsInstall(rawArgs);
2484
2571
  break;
2485
2572
  case "--help":
2486
2573
  case void 0:
2487
2574
  printSkillsHelp();
2488
2575
  break;
2489
2576
  default:
2490
- console.log(chalk.red(`Unknown skills subcommand: ${subcommand}`));
2577
+ console.error(chalk.red(`Unknown skills subcommand: ${subcommand}`));
2491
2578
  console.log("");
2492
2579
  printSkillsHelp();
2580
+ process.exit(1);
2581
+ }
2582
+ }
2583
+ /**
2584
+ * Agents named explicitly on the command line, e.g. `--agent claude --agent cursor`
2585
+ * (also accepts a comma-separated list). Returns null when none were given.
2586
+ */
2587
+ function parseAgentFlags(rawArgs) {
2588
+ const requested = [];
2589
+ for (let i = 0; i < rawArgs.length; i++) {
2590
+ if (rawArgs[i] !== "--agent" && rawArgs[i] !== "-a") continue;
2591
+ const value = rawArgs[i + 1];
2592
+ if (value && !value.startsWith("-")) requested.push(...value.split(",").map((v) => v.trim()).filter(Boolean));
2593
+ }
2594
+ if (requested.length === 0) return null;
2595
+ const valid = Object.keys(AGENTS);
2596
+ const unknown = requested.filter((a) => !valid.includes(a));
2597
+ if (unknown.length > 0) {
2598
+ console.error(chalk.red(`Unknown agent(s): ${unknown.join(", ")}. Available: ${valid.join(", ")}`));
2599
+ process.exit(1);
2493
2600
  }
2601
+ return requested;
2494
2602
  }
2495
- async function skillsInstall() {
2603
+ async function skillsInstall(rawArgs = []) {
2496
2604
  const projectDir = process.cwd();
2497
2605
  let skillsDir;
2498
2606
  try {
@@ -2506,8 +2614,14 @@ async function skillsInstall() {
2506
2614
  console.error(`${chalk.red.bold("ERROR")} No skills found in ${skillsDir}`);
2507
2615
  process.exit(1);
2508
2616
  }
2509
- let agents = detectAgents(projectDir);
2617
+ let agents = parseAgentFlags(rawArgs) ?? detectAgents(projectDir);
2510
2618
  if (agents.length === 0) {
2619
+ if (!process.stdin.isTTY) {
2620
+ console.error(chalk.red("Cannot prompt: this is a non-interactive terminal (no TTY)."));
2621
+ console.error(chalk.yellow(` Name the agents explicitly, e.g. rebase skills install --agent ${Object.keys(AGENTS)[0]}`));
2622
+ console.error(chalk.gray(` Available: ${Object.keys(AGENTS).join(", ")}`));
2623
+ process.exit(1);
2624
+ }
2511
2625
  const choices = Object.entries(AGENTS).map(([key, agent]) => ({
2512
2626
  name: agent.label,
2513
2627
  value: key,
@@ -2549,8 +2663,15 @@ ${chalk.green.bold("Subcommands")}
2549
2663
  ${chalk.blue.bold("install")} Install Rebase agent skills for your AI coding assistant
2550
2664
  Supports: Cursor, Claude Code, Windsurf, Gemini CLI, Antigravity
2551
2665
 
2666
+ ${chalk.green.bold("Options")}
2667
+ ${chalk.blue("--agent, -a")} Agent(s) to install for, skipping detection and the prompt.
2668
+ Repeat the flag or pass a comma-separated list.
2669
+ Available: ${Object.keys(AGENTS).join(", ")}
2670
+
2552
2671
  ${chalk.green.bold("Examples")}
2553
2672
  ${chalk.cyan("rebase skills install")}
2673
+ ${chalk.cyan("rebase skills install --agent claude")}
2674
+ ${chalk.cyan("rebase skills install --agent claude,cursor")}
2554
2675
  `);
2555
2676
  }
2556
2677
  //#endregion
@@ -2582,9 +2703,16 @@ function loadEnv(projectRoot) {
2582
2703
  }
2583
2704
  return env;
2584
2705
  }
2585
- function resolveBaseUrl(env) {
2586
- const port = env.PORT || env.REBASE_PORT || "3001";
2587
- return env.REBASE_BASE_URL || `http://localhost:${port}`;
2706
+ function resolveBaseUrl(env, projectRoot) {
2707
+ if (env.REBASE_BASE_URL) return env.REBASE_BASE_URL;
2708
+ if (projectRoot) try {
2709
+ const urlFile = path.join(projectRoot, ".rebase-dev-url");
2710
+ if (fs.existsSync(urlFile)) {
2711
+ const devUrl = fs.readFileSync(urlFile, "utf-8").trim();
2712
+ if (devUrl) return devUrl;
2713
+ }
2714
+ } catch {}
2715
+ return `http://localhost:${env.PORT || env.REBASE_PORT || "3001"}`;
2588
2716
  }
2589
2717
  async function apiKeysCommand(subcommand, rawArgs) {
2590
2718
  if (!subcommand || subcommand === "--help") {
@@ -2609,8 +2737,9 @@ async function apiKeysCommand(subcommand, rawArgs) {
2609
2737
  }
2610
2738
  }
2611
2739
  async function listKeys(_rawArgs) {
2612
- const env = loadEnv(requireProjectRoot());
2613
- const baseUrl = resolveBaseUrl(env);
2740
+ const projectRoot = requireProjectRoot();
2741
+ const env = loadEnv(projectRoot);
2742
+ const baseUrl = resolveBaseUrl(env, projectRoot);
2614
2743
  const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;
2615
2744
  if (!serviceKey) {
2616
2745
  console.error(chalk.red("✗ SERVICE_KEY not found in .env — required for admin operations."));
@@ -2715,8 +2844,9 @@ async function createKey(rawArgs) {
2715
2844
  expires_at = parsed.toISOString();
2716
2845
  }
2717
2846
  }
2718
- const env = loadEnv(requireProjectRoot());
2719
- const baseUrl = resolveBaseUrl(env);
2847
+ const projectRoot = requireProjectRoot();
2848
+ const env = loadEnv(projectRoot);
2849
+ const baseUrl = resolveBaseUrl(env, projectRoot);
2720
2850
  const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;
2721
2851
  if (!serviceKey) {
2722
2852
  console.error(chalk.red("✗ SERVICE_KEY not found in .env — required for admin operations."));
@@ -2773,8 +2903,9 @@ async function revokeKey(rawArgs) {
2773
2903
  console.log(chalk.gray(" Usage: rebase api-keys revoke <key-id>"));
2774
2904
  process.exit(1);
2775
2905
  }
2776
- const env = loadEnv(requireProjectRoot());
2777
- const baseUrl = resolveBaseUrl(env);
2906
+ const projectRoot = requireProjectRoot();
2907
+ const env = loadEnv(projectRoot);
2908
+ const baseUrl = resolveBaseUrl(env, projectRoot);
2778
2909
  const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;
2779
2910
  if (!serviceKey) {
2780
2911
  console.error(chalk.red("✗ SERVICE_KEY not found in .env — required for admin operations."));
@@ -3243,13 +3374,18 @@ function fmtDate(value) {
3243
3374
  */
3244
3375
  var POLL_INTERVAL_MS = 1500;
3245
3376
  var POLL_TIMEOUT_MS = 900 * 1e3;
3377
+ var MAX_SOURCE_UPLOAD_BYTES = 100 * 1024 * 1024;
3246
3378
  function sleep(ms) {
3247
3379
  return new Promise((r) => setTimeout(r, ms));
3248
3380
  }
3249
- function run(cmd, cmdArgs, cwd) {
3381
+ function run(cmd, cmdArgs, cwd, env) {
3250
3382
  return new Promise((resolve, reject) => {
3251
3383
  const child = spawn(cmd, cmdArgs, {
3252
3384
  cwd,
3385
+ env: env ? {
3386
+ ...process.env,
3387
+ ...env
3388
+ } : void 0,
3253
3389
  stdio: [
3254
3390
  "ignore",
3255
3391
  "ignore",
@@ -3279,7 +3415,7 @@ async function createSourceTarball(sourceDir) {
3279
3415
  for (const ignore of [".gitignore", ".rebaseignore"]) if (fs.existsSync(path.join(dir, ignore))) tarArgs.push(`--exclude-from=${ignore}`);
3280
3416
  tarArgs.push(".");
3281
3417
  try {
3282
- await run("tar", tarArgs, dir);
3418
+ await run("tar", tarArgs, dir, { COPYFILE_DISABLE: "1" });
3283
3419
  } catch (e) {
3284
3420
  fail(`Failed to package source: ${e instanceof Error ? e.message : String(e)}`);
3285
3421
  }
@@ -3289,6 +3425,7 @@ async function createSourceTarball(sourceDir) {
3289
3425
  async function uploadSource(url, token, projectId, tarPath) {
3290
3426
  const bytes = fs.readFileSync(tarPath);
3291
3427
  const sizeMb = (bytes.length / 1024 / 1024).toFixed(1);
3428
+ if (bytes.length > MAX_SOURCE_UPLOAD_BYTES) fail(`Source context is ${sizeMb} MB — the upload cap is ${Math.round(MAX_SOURCE_UPLOAD_BYTES / 1024 / 1024)} MB.`, "Trim the build context: exclude sourcemaps (*.map), build output and large assets via .rebaseignore or .gitignore.");
3292
3429
  console.log(chalk.gray(` Uploading source (${sizeMb} MB)...`));
3293
3430
  const res = await fetch(`${url}/api/functions/deploy/upload?projectId=${encodeURIComponent(projectId)}`, {
3294
3431
  method: "POST",
@@ -4268,6 +4405,8 @@ ${chalk.green.bold("Options")}
4268
4405
  ${chalk.blue("--secret")} Mark a variable write-only ${chalk.gray("(set)")}
4269
4406
  ${chalk.blue("--json")} Machine-readable output
4270
4407
  ${chalk.blue("--project, -p")} Project slug ${chalk.gray("(defaults to the linked project)")}
4408
+
4409
+ ${chalk.gray("Values are encrypted at rest (AES-256-GCM) and only decrypted at deploy time.")}
4271
4410
  `);
4272
4411
  }
4273
4412
  function printEnvHelpJson() {
@@ -4819,7 +4958,7 @@ function deploymentDurationMs(dep) {
4819
4958
  const ms = b - a;
4820
4959
  return ms >= 0 ? ms : null;
4821
4960
  }
4822
- function formatDuration(ms) {
4961
+ function formatDuration$1(ms) {
4823
4962
  const totalSec = Math.max(0, Math.round(ms / 1e3));
4824
4963
  if (totalSec < 60) return `${totalSec}s`;
4825
4964
  const m = Math.floor(totalSec / 60);
@@ -4892,7 +5031,7 @@ async function deploymentsListCommand(rawArgs) {
4892
5031
  return;
4893
5032
  }
4894
5033
  for (const v of views) {
4895
- const dur = v.durationMs !== null ? formatDuration(v.durationMs) : chalk.gray("running");
5034
+ const dur = v.durationMs !== null ? formatDuration$1(v.durationMs) : chalk.gray("running");
4896
5035
  const trig = v.trigger.source;
4897
5036
  const roll = v.rollbackable ? chalk.green(" ↺ rollbackable") : "";
4898
5037
  console.log(` ${chalk.gray(`[${v.id}]`)} ${colorStatus(v.status)} ${chalk.gray(String(v.createdAt ?? "—"))} ${dur} ${chalk.gray(trig)}${roll}`);
@@ -5063,6 +5202,737 @@ async function powerCommand(action, rawArgs) {
5063
5202
  }
5064
5203
  }
5065
5204
  //#endregion
5205
+ //#region src/commands/cloud/debug.ts
5206
+ /**
5207
+ * `rebase cloud debug <subcommand>` — one entry point for "why is my deployed
5208
+ * app not behaving".
5209
+ *
5210
+ * ## Why this exists
5211
+ *
5212
+ * The useful signals for a deployed project live in four different places — the
5213
+ * control plane, the workload's pods, the tenant database, and the public URL —
5214
+ * and each is normally reached with a different tool and a different set of
5215
+ * flags. Getting to a log should not be a research task. This started life as a
5216
+ * hand-rolled `prod-debug.sh` for a single project, with the namespace and the
5217
+ * URL hardcoded at the top; it earned its place twice in one week, so it is
5218
+ * generalised here to any project the CLI can already resolve.
5219
+ *
5220
+ * ## Read-only by default
5221
+ *
5222
+ * Every subcommand here only reads. The two things the original script could do
5223
+ * that mutate are deliberately NOT reproduced as-is:
5224
+ *
5225
+ * - restarting the workload lives at `rebase cloud restart`, which already
5226
+ * gates downtime behind `--yes`; duplicating it here would give the same
5227
+ * destructive act a second, ungated spelling.
5228
+ * - `debug db` prints the port-forward recipe and the connection's *shape*.
5229
+ * It opens no session and prints no password — `rebase cloud db info
5230
+ * --reveal` is the explicit, auditable way to get one.
5231
+ *
5232
+ * ## The probes are the point
5233
+ *
5234
+ * `debug health` is the highest-value piece and the reason the script existed.
5235
+ * A bare status code is not a diagnosis: a 404 from a functions route means
5236
+ * something completely different from a 404 at the root, and a 200 on an
5237
+ * unauthenticated read is a finding rather than a success. So every probe ships
5238
+ * with the interpretation of what it got, not just the number — see
5239
+ * {@link PROBES}.
5240
+ */
5241
+ /** A response never arrived: DNS, TLS, ingress, or the pod. */
5242
+ var NO_RESPONSE = {
5243
+ verdict: "fail",
5244
+ meaning: "nothing answered — the hostname does not resolve, the ingress has no route, or no pod is running"
5245
+ };
5246
+ function serverError(what) {
5247
+ return {
5248
+ verdict: "fail",
5249
+ meaning: `the server is running but ${what}`
5250
+ };
5251
+ }
5252
+ /**
5253
+ * The probe set, in the order a failure cascades: if `health` is down, nothing
5254
+ * below it is meaningful, so it is checked first and reported first.
5255
+ */
5256
+ var PROBES = [
5257
+ {
5258
+ id: "health",
5259
+ label: "health",
5260
+ method: "GET",
5261
+ path: () => "/health",
5262
+ healthy: "200 — the backend is up",
5263
+ interpret: (status) => {
5264
+ if (status === null) return NO_RESPONSE;
5265
+ if (status === 200) return {
5266
+ verdict: "ok",
5267
+ meaning: "the backend is up and serving"
5268
+ };
5269
+ if (status === 404) return {
5270
+ verdict: "fail",
5271
+ meaning: "something answered but it is not a Rebase backend — the ingress is routing this host elsewhere"
5272
+ };
5273
+ if (status >= 500) return serverError("its health endpoint is failing");
5274
+ return {
5275
+ verdict: "unknown",
5276
+ meaning: "unexpected for a health endpoint"
5277
+ };
5278
+ }
5279
+ },
5280
+ {
5281
+ id: "spa",
5282
+ label: "spa",
5283
+ method: "GET",
5284
+ path: () => "/",
5285
+ healthy: "200 — the frontend is being served",
5286
+ interpret: (status) => {
5287
+ if (status === null) return NO_RESPONSE;
5288
+ if (status === 200) return {
5289
+ verdict: "ok",
5290
+ meaning: "the frontend bundle is being served"
5291
+ };
5292
+ if (status === 404) return {
5293
+ verdict: "warn",
5294
+ meaning: "no frontend at the root — expected for a backend-only project, otherwise the SPA assets were not bundled into the image"
5295
+ };
5296
+ if (status >= 500) return serverError("the root route throws");
5297
+ return {
5298
+ verdict: "unknown",
5299
+ meaning: "unexpected at the site root"
5300
+ };
5301
+ }
5302
+ },
5303
+ {
5304
+ id: "auth",
5305
+ label: "auth",
5306
+ method: "POST",
5307
+ path: () => "/api/auth/login",
5308
+ body: {},
5309
+ healthy: "400 — auth is mounted and rejects an empty body",
5310
+ interpret: (status) => {
5311
+ if (status === null) return NO_RESPONSE;
5312
+ if (status === 400 || status === 422) return {
5313
+ verdict: "ok",
5314
+ meaning: "auth is mounted and rejected the empty body, as it should"
5315
+ };
5316
+ if (status === 401 || status === 403) return {
5317
+ verdict: "ok",
5318
+ meaning: "auth is mounted and refused the credentials"
5319
+ };
5320
+ if (status === 404) return {
5321
+ verdict: "fail",
5322
+ meaning: "the auth routes are NOT mounted — this project cannot sign anyone in"
5323
+ };
5324
+ if (status === 200) return {
5325
+ verdict: "fail",
5326
+ meaning: "an EMPTY login body was accepted — a login with no credentials must never succeed"
5327
+ };
5328
+ if (status >= 500) return serverError("the login route throws — often a missing or unmigrated auth table");
5329
+ return {
5330
+ verdict: "unknown",
5331
+ meaning: "unexpected for a login route"
5332
+ };
5333
+ }
5334
+ },
5335
+ {
5336
+ id: "unauthRead",
5337
+ label: "unauth read",
5338
+ method: "GET",
5339
+ path: (t) => `/api/data/${encodeURIComponent(t.collection)}`,
5340
+ healthy: "401 — reads require authentication",
5341
+ interpret: (status) => {
5342
+ if (status === null) return NO_RESPONSE;
5343
+ if (status === 401 || status === 403) return {
5344
+ verdict: "ok",
5345
+ meaning: "unauthenticated reads are refused — row-level security is enforced"
5346
+ };
5347
+ if (status === 200) return {
5348
+ verdict: "warn",
5349
+ meaning: "this collection is readable with NO authentication — correct only if it is deliberately public"
5350
+ };
5351
+ if (status === 404) return {
5352
+ verdict: "warn",
5353
+ meaning: "no such collection on this deployment — check the name, or pass --collection"
5354
+ };
5355
+ if (status >= 500) return serverError("the read reached the database and failed — most often an RLS policy naming a column or table that is not there");
5356
+ return {
5357
+ verdict: "unknown",
5358
+ meaning: "unexpected for a data read"
5359
+ };
5360
+ }
5361
+ },
5362
+ {
5363
+ id: "functions",
5364
+ label: "functions",
5365
+ method: "GET",
5366
+ /**
5367
+ * The router's own listing endpoint, NOT a function's path.
5368
+ *
5369
+ * This matters, and it is the one place the original script got a wrong
5370
+ * answer. A function is a Hono sub-app mounted at `/<name>`, and it
5371
+ * usually defines only sub-routes (`/get`, `/list`) — so
5372
+ * `/api/functions/<name>` 404s **even when everything is mounted and
5373
+ * healthy**. Probing there cannot separate "the router is missing" from
5374
+ * "that function defines no root route", and reporting the first is how
5375
+ * you send someone to debug a deployment that was fine.
5376
+ *
5377
+ * `GET /api/functions` is unambiguous: the router registers a listing
5378
+ * route at its own root (see `createFunctionRoutes`), so a 200 proves
5379
+ * the mount *and* names every function that loaded.
5380
+ */
5381
+ path: () => "/api/functions",
5382
+ healthy: "200 — the functions router is mounted and lists its functions",
5383
+ interpret: (status) => {
5384
+ if (status === null) return NO_RESPONSE;
5385
+ if (status === 200) return {
5386
+ verdict: "ok",
5387
+ meaning: "the functions router is mounted"
5388
+ };
5389
+ if (status === 401 || status === 403) return {
5390
+ verdict: "ok",
5391
+ meaning: "the functions router is mounted (its listing requires auth)"
5392
+ };
5393
+ if (status === 404) return {
5394
+ verdict: "fail",
5395
+ meaning: "the functions router did not mount — no functions directory was found at build time, or it held no functions, so every function on this project is unreachable"
5396
+ };
5397
+ if (status >= 500) return serverError("the functions router throws");
5398
+ return {
5399
+ verdict: "unknown",
5400
+ meaning: "unexpected for the functions listing"
5401
+ };
5402
+ },
5403
+ needsBody: true,
5404
+ refine: (body, t) => {
5405
+ const names = functionNames(body);
5406
+ if (!names) return null;
5407
+ if (t.fn && !names.includes(t.fn)) return {
5408
+ verdict: "fail",
5409
+ meaning: `the router is mounted but no function is named "${t.fn}" — it loaded ${names.length}: ${names.join(", ")}`
5410
+ };
5411
+ const found = t.fn ? `, including ${t.fn}` : "";
5412
+ return {
5413
+ verdict: "ok",
5414
+ meaning: `the functions router is mounted and loaded ${names.length} function${names.length === 1 ? "" : "s"}${found}`
5415
+ };
5416
+ }
5417
+ }
5418
+ ];
5419
+ /** The function names out of a listing body, or null when it is not one. */
5420
+ function functionNames(body) {
5421
+ const list = body?.functions;
5422
+ if (!Array.isArray(list)) return null;
5423
+ const names = list.map((f) => f?.name).filter((n) => typeof n === "string");
5424
+ return names.length === list.length ? names : null;
5425
+ }
5426
+ /** Milliseconds before a probe is treated as unanswered. */
5427
+ var PROBE_TIMEOUT_MS = 1e4;
5428
+ /**
5429
+ * Ceiling on a probe body we will parse. The only body read is the function
5430
+ * listing; anything larger is a page we have no use for, and a debug command
5431
+ * must not be the thing that runs a machine out of memory.
5432
+ */
5433
+ var MAX_PROBE_BODY_BYTES = 256 * 1024;
5434
+ /**
5435
+ * Run one probe. A transport failure is a `null` status, never a thrown error:
5436
+ * "nothing answered" is a diagnosis in its own right and the other probes still
5437
+ * need to run.
5438
+ */
5439
+ async function runProbe(origin, spec, targets) {
5440
+ const url = `${origin}${spec.path(targets)}`;
5441
+ const started = Date.now();
5442
+ let status = null;
5443
+ let body;
5444
+ try {
5445
+ const res = await fetch(url, {
5446
+ method: spec.method,
5447
+ headers: spec.body ? { "Content-Type": "application/json" } : void 0,
5448
+ body: spec.body ? JSON.stringify(spec.body) : void 0,
5449
+ redirect: "manual",
5450
+ signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
5451
+ });
5452
+ status = res.status;
5453
+ if (spec.needsBody && res.ok) {
5454
+ const text = await res.text();
5455
+ if (text.length <= MAX_PROBE_BODY_BYTES) try {
5456
+ body = JSON.parse(text);
5457
+ } catch {}
5458
+ }
5459
+ } catch {
5460
+ status = null;
5461
+ }
5462
+ const statusReading = spec.interpret(status);
5463
+ const reading = status !== null && spec.refine?.(body, targets) || statusReading;
5464
+ return {
5465
+ statusOk: statusReading.verdict === "ok",
5466
+ id: spec.id,
5467
+ label: spec.label,
5468
+ method: spec.method,
5469
+ url,
5470
+ status,
5471
+ ms: Date.now() - started,
5472
+ verdict: reading.verdict,
5473
+ meaning: reading.meaning,
5474
+ healthy: spec.healthy
5475
+ };
5476
+ }
5477
+ /** The worst verdict across probes — what the command's exit code keys off. */
5478
+ function overallVerdict(results) {
5479
+ if (results.some((r) => r.verdict === "fail")) return "fail";
5480
+ if (results.some((r) => r.verdict === "unknown")) return "unknown";
5481
+ if (results.some((r) => r.verdict === "warn")) return "warn";
5482
+ return "ok";
5483
+ }
5484
+ function verdictMark(v) {
5485
+ switch (v) {
5486
+ case "ok": return chalk.green("✓");
5487
+ case "warn": return chalk.yellow("!");
5488
+ case "fail": return chalk.red("✗");
5489
+ default: return chalk.gray("?");
5490
+ }
5491
+ }
5492
+ var LOG_PREFIX_RE = /^(?:(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s+)?(?:\[([^\]]+)\]\s+)?([\s\S]*)$/;
5493
+ function parseLogLine(line) {
5494
+ const m = LOG_PREFIX_RE.exec(line);
5495
+ if (!m) return {
5496
+ ts: null,
5497
+ pod: null,
5498
+ text: line
5499
+ };
5500
+ return {
5501
+ ts: m[1] ?? null,
5502
+ pod: m[2] ?? null,
5503
+ text: m[3] ?? ""
5504
+ };
5505
+ }
5506
+ /**
5507
+ * Lines an operator scanning for a fault wants to see.
5508
+ *
5509
+ * Matches the structured `severity` field first, then the shapes that show up
5510
+ * in unstructured output. `refus`/`denied` are in the list because a permission
5511
+ * failure often logs at info level and is exactly what one is hunting for.
5512
+ */
5513
+ function isErrorLine(text) {
5514
+ return /"(?:severity|level)":\s*"(?:ERROR|WARN(?:ING)?|error|warn)"|\bError:|\bERR!|refus|denied|EACCES|ECONNREFUSED/i.test(text);
5515
+ }
5516
+ /**
5517
+ * Pull an HTTP request record out of a log line, or null when it is not one.
5518
+ *
5519
+ * Only structured request lines are recognised. Guessing at prose would produce
5520
+ * a table with invented columns, which is worse than a short one.
5521
+ */
5522
+ function parseRequestLine(text) {
5523
+ const start = text.indexOf("{");
5524
+ if (start === -1) return null;
5525
+ let parsed;
5526
+ try {
5527
+ parsed = JSON.parse(text.slice(start));
5528
+ } catch {
5529
+ return null;
5530
+ }
5531
+ if (parsed.message !== "request" && parsed.msg !== "request") return null;
5532
+ const num = (v) => {
5533
+ const n = typeof v === "string" ? Number(v) : typeof v === "number" ? v : NaN;
5534
+ return Number.isFinite(n) ? n : null;
5535
+ };
5536
+ return {
5537
+ status: num(parsed.status),
5538
+ method: typeof parsed.method === "string" ? parsed.method : "",
5539
+ path: typeof parsed.path === "string" ? parsed.path : "",
5540
+ latencyMs: num(parsed.latencyMs ?? parsed.durationMs)
5541
+ };
5542
+ }
5543
+ /**
5544
+ * Startup lines: what the server *decided* it was going to do. Which storage
5545
+ * backend it bound, which functions it loaded, whether auth tables were found.
5546
+ * This is the fastest way to tell a misconfiguration from a runtime fault.
5547
+ */
5548
+ function isBootLine(text) {
5549
+ return /storage|Loaded function|Mounted|Auth tables|Server running|listening|Refusing|migrat/i.test(text);
5550
+ }
5551
+ /** Render a number of seconds back as the compact duration a user would type. */
5552
+ function formatDuration(seconds) {
5553
+ if (seconds % 86400 === 0 && seconds >= 86400) return `${seconds / 86400}d`;
5554
+ if (seconds % 3600 === 0 && seconds >= 3600) return `${seconds / 3600}h`;
5555
+ if (seconds % 60 === 0 && seconds >= 60) return `${seconds / 60}m`;
5556
+ return `${seconds}s`;
5557
+ }
5558
+ /**
5559
+ * Parse a duration like `15m`, `2h`, `90s`, `1d` (or a bare number of seconds)
5560
+ * into seconds. Returns null when it is not a duration.
5561
+ */
5562
+ function parseSince(input) {
5563
+ if (!input) return null;
5564
+ const m = /^(\d+(?:\.\d+)?)\s*([smhd])?$/i.exec(input.trim());
5565
+ if (!m) return null;
5566
+ const value = parseFloat(m[1]);
5567
+ const unit = (m[2] || "s").toLowerCase();
5568
+ return Math.round(value * (unit === "s" ? 1 : unit === "m" ? 60 : unit === "h" ? 3600 : 86400));
5569
+ }
5570
+ async function fetchRuntimeLogs(client, projectId, opts) {
5571
+ const params = new URLSearchParams();
5572
+ if (opts.sinceSeconds !== void 0) params.set("sinceSeconds", String(opts.sinceSeconds));
5573
+ if (opts.tailLines !== void 0) params.set("tailLines", String(opts.tailLines));
5574
+ if (opts.previous) params.set("previous", "true");
5575
+ params.set("timestamps", "true");
5576
+ const qs = params.toString();
5577
+ return client.functions.invoke("runtime-logs", void 0, {
5578
+ method: "GET",
5579
+ path: `${projectId}${qs ? `?${qs}` : ""}`
5580
+ });
5581
+ }
5582
+ /** Print the per-pod states runtime-logs reports, including its hints. */
5583
+ function printPodStates(res) {
5584
+ if (res.state === "no_pods") {
5585
+ console.log(chalk.yellow(` ${res.message ?? "No pods are running for this project."}`));
5586
+ console.log("");
5587
+ return;
5588
+ }
5589
+ for (const p of res.pods ?? []) {
5590
+ if (p.state === "ok") continue;
5591
+ console.log(` ${chalk.yellow(p.pod)} ${chalk.gray(`(${p.state})`)}`);
5592
+ if (p.message) console.log(chalk.gray(` ${p.message}`));
5593
+ if (p.hint) console.log(chalk.cyan(` → ${p.hint}`));
5594
+ }
5595
+ if (res.truncated) console.log(chalk.gray(" (output truncated — narrow the window with --since)"));
5596
+ }
5597
+ /** Resolve the public origin a project is served at. */
5598
+ async function resolveOrigin(rawArgs, client, url, projectId) {
5599
+ const parsed = arg({ "--host": String }, {
5600
+ argv: rawArgs.slice(3),
5601
+ permissive: true
5602
+ });
5603
+ if (parsed["--host"]) {
5604
+ const h = parsed["--host"].trim().replace(/\/+$/, "");
5605
+ return /^https?:\/\//.test(h) ? h : `https://${h}`;
5606
+ }
5607
+ const [project, baseDomain] = await Promise.all([client.data.collection("projects").findById(projectId), fetchTenantBaseDomain(client, url)]);
5608
+ if (!project) fail(`Project ${displayProjectRef(rawArgs)} not found.`);
5609
+ const host = projectHost(project, baseDomain);
5610
+ if (!host) fail("Could not determine the public URL for this project.", "It may never have been deployed. Pass --host <hostname> to probe an address directly.");
5611
+ return `https://${host}`;
5612
+ }
5613
+ async function healthCommand(rawArgs) {
5614
+ const parsed = arg({
5615
+ "--collection": String,
5616
+ "--function": String
5617
+ }, {
5618
+ argv: rawArgs.slice(3),
5619
+ permissive: true
5620
+ });
5621
+ const targets = {
5622
+ collection: parsed["--collection"] || "users",
5623
+ fn: parsed["--function"]
5624
+ };
5625
+ const { client, url } = await requireClient(rawArgs);
5626
+ const origin = await resolveOrigin(rawArgs, client, url, await requireProject(rawArgs, client));
5627
+ const results = [];
5628
+ for (const spec of PROBES) results.push(await runProbe(origin, spec, targets));
5629
+ const overall = overallVerdict(results);
5630
+ emit(() => {
5631
+ console.log("");
5632
+ console.log(chalk.bold(` 🩺 Health — ${displayProjectRef(rawArgs)}`) + chalk.gray(` ${origin}`));
5633
+ console.log("");
5634
+ const width = Math.max(...results.map((r) => r.label.length));
5635
+ for (const r of results) {
5636
+ const code = r.status === null ? chalk.red("---") : String(r.status);
5637
+ console.log(` ${verdictMark(r.verdict)} ${chalk.bold(r.label.padEnd(width))} ${code.padStart(3)} ${chalk.gray(`${r.ms}ms`)}`);
5638
+ console.log(` ${chalk.gray(r.meaning)}`);
5639
+ }
5640
+ console.log("");
5641
+ if (overall === "ok") console.log(chalk.green(" Everything reachable and wired as expected."));
5642
+ else {
5643
+ const bad = results.filter((r) => r.verdict === "fail" || r.verdict === "warn");
5644
+ console.log(chalk.gray(` ${bad.length} of ${results.length} check${bad.length === 1 ? "" : "s"} need attention.`));
5645
+ const wrongStatus = bad.filter((r) => !r.statusOk);
5646
+ if (wrongStatus.length > 0) {
5647
+ console.log(chalk.gray(" A healthy deployment answers:"));
5648
+ for (const r of wrongStatus) console.log(chalk.gray(` ${r.label.padEnd(width)} ${r.healthy}`));
5649
+ }
5650
+ }
5651
+ console.log("");
5652
+ }, {
5653
+ origin,
5654
+ overall,
5655
+ probes: results
5656
+ });
5657
+ if (overall === "fail") process.exit(1);
5658
+ }
5659
+ async function logView(rawArgs, view) {
5660
+ const parsed = arg({
5661
+ "--since": String,
5662
+ "--tail": Number,
5663
+ "--previous": Boolean
5664
+ }, {
5665
+ argv: rawArgs.slice(3),
5666
+ permissive: true
5667
+ });
5668
+ const sinceArg = parsed["--since"];
5669
+ if (sinceArg !== void 0 && parseSince(sinceArg) === null) fail(`--since must be a duration like 15m, 2h or 90s; received "${sinceArg}".`);
5670
+ const sinceSeconds = parseSince(sinceArg) ?? view.defaultSinceSeconds;
5671
+ const { client } = await requireClient(rawArgs);
5672
+ const projectId = await requireProject(rawArgs, client);
5673
+ let res;
5674
+ try {
5675
+ res = await fetchRuntimeLogs(client, projectId, {
5676
+ sinceSeconds,
5677
+ tailLines: parsed["--tail"] ?? 500,
5678
+ previous: Boolean(parsed["--previous"])
5679
+ });
5680
+ } catch (e) {
5681
+ reportError(e, "Failed to fetch runtime logs");
5682
+ }
5683
+ const parsedLines = (res.logs ?? "").split("\n").filter((l) => l !== "").map(parseLogLine);
5684
+ const kept = view.filter ? parsedLines.filter((l) => view.filter(l.text)) : parsedLines;
5685
+ const shown = kept.slice(-view.limit);
5686
+ emit(() => {
5687
+ console.log("");
5688
+ console.log(chalk.bold(` ${view.title} — ${displayProjectRef(rawArgs)}`) + chalk.gray(` last ${formatDuration(sinceSeconds)}`));
5689
+ console.log("");
5690
+ printPodStates(res);
5691
+ if (shown.length === 0) {
5692
+ console.log(chalk.gray(" (nothing matched in this window)"));
5693
+ console.log("");
5694
+ return;
5695
+ }
5696
+ for (const l of shown) console.log(` ${l.pod ? chalk.gray(`[${l.pod}] `) : ""}${l.text}`);
5697
+ console.log("");
5698
+ if (kept.length > shown.length) {
5699
+ console.log(chalk.gray(` (showing the last ${shown.length} of ${kept.length} matching lines)`));
5700
+ console.log("");
5701
+ }
5702
+ }, {
5703
+ sinceSeconds,
5704
+ state: res.state ?? null,
5705
+ pods: res.pods ?? [],
5706
+ truncated: Boolean(res.truncated),
5707
+ matched: kept.length,
5708
+ lines: shown
5709
+ });
5710
+ }
5711
+ async function requestsCommand(rawArgs) {
5712
+ const parsed = arg({
5713
+ "--since": String,
5714
+ "--tail": Number
5715
+ }, {
5716
+ argv: rawArgs.slice(3),
5717
+ permissive: true
5718
+ });
5719
+ const sinceArg = parsed["--since"];
5720
+ if (sinceArg !== void 0 && parseSince(sinceArg) === null) fail(`--since must be a duration like 15m, 2h or 90s; received "${sinceArg}".`);
5721
+ const sinceSeconds = parseSince(sinceArg) ?? 900;
5722
+ const { client } = await requireClient(rawArgs);
5723
+ const projectId = await requireProject(rawArgs, client);
5724
+ let res;
5725
+ try {
5726
+ res = await fetchRuntimeLogs(client, projectId, {
5727
+ sinceSeconds,
5728
+ tailLines: parsed["--tail"] ?? 1e3
5729
+ });
5730
+ } catch (e) {
5731
+ reportError(e, "Failed to fetch runtime logs");
5732
+ }
5733
+ const shown = (res.logs ?? "").split("\n").filter((l) => l !== "").map((l) => parseRequestLine(parseLogLine(l).text)).filter((e) => e !== null).slice(-40);
5734
+ emit(() => {
5735
+ console.log("");
5736
+ console.log(chalk.bold(` 🌐 Requests — ${displayProjectRef(rawArgs)}`) + chalk.gray(` last ${formatDuration(sinceSeconds)}`));
5737
+ console.log("");
5738
+ printPodStates(res);
5739
+ if (shown.length === 0) {
5740
+ console.log(chalk.gray(" No structured request lines in this window."));
5741
+ console.log(chalk.gray(" (this view needs the server's request logging; try `debug logs`)"));
5742
+ console.log("");
5743
+ return;
5744
+ }
5745
+ for (const e of shown) {
5746
+ const status = e.status ?? 0;
5747
+ const color = status >= 500 ? chalk.red : status >= 400 ? chalk.yellow : chalk.green;
5748
+ console.log(` ${color(String(e.status ?? "---").padStart(3))} ${e.method.padEnd(6)} ${e.path.slice(0, 70).padEnd(70)} ${chalk.gray(e.latencyMs === null ? "" : `${e.latencyMs}ms`)}`);
5749
+ }
5750
+ console.log("");
5751
+ }, {
5752
+ sinceSeconds,
5753
+ requests: shown
5754
+ });
5755
+ }
5756
+ async function podCommand(rawArgs) {
5757
+ const { client } = await requireClient(rawArgs);
5758
+ const projectId = await requireProject(rawArgs, client);
5759
+ let m;
5760
+ try {
5761
+ m = await client.functions.invoke("metrics", void 0, {
5762
+ method: "GET",
5763
+ path: projectId
5764
+ });
5765
+ } catch (e) {
5766
+ reportError(e, "Failed to read workload placement");
5767
+ }
5768
+ const p = m.placement ?? {};
5769
+ const replicas = p.replicas ?? {};
5770
+ emit(() => {
5771
+ console.log("");
5772
+ console.log(chalk.bold(` ☸ Workload — ${displayProjectRef(rawArgs)}`));
5773
+ console.log("");
5774
+ keyValues([
5775
+ ["Status", m.status ? colorStatus(m.status === "running" ? "active" : m.status) : void 0],
5776
+ ["Replicas", replicas.desired === void 0 ? void 0 : `${replicas.available ?? 0} / ${replicas.desired} available`],
5777
+ ["Namespace", p.namespace],
5778
+ ["Cluster", p.cluster],
5779
+ ["Region", [p.provider, p.region].filter(Boolean).join(" · ") || void 0],
5780
+ ["Host", p.host],
5781
+ ["Image", p.image],
5782
+ ["CPU", m.cpu ?? void 0],
5783
+ ["Memory", m.memory ?? void 0]
5784
+ ]);
5785
+ console.log("");
5786
+ if ((replicas.available ?? 0) === 0 && (replicas.desired ?? 0) > 0) {
5787
+ console.log(chalk.yellow(" No replica is available — the pod is not passing its readiness check."));
5788
+ console.log(chalk.gray(" See why with: ") + chalk.bold("rebase cloud debug logs --previous"));
5789
+ console.log("");
5790
+ }
5791
+ }, {
5792
+ status: m.status ?? null,
5793
+ placement: p
5794
+ });
5795
+ }
5796
+ async function dbDebugCommand(rawArgs) {
5797
+ const { client } = await requireClient(rawArgs);
5798
+ const projectId = await requireProject(rawArgs, client);
5799
+ let info;
5800
+ try {
5801
+ info = await client.functions.invoke("db-info", void 0, {
5802
+ method: "GET",
5803
+ path: projectId
5804
+ });
5805
+ } catch (e) {
5806
+ reportError(e, "Failed to read database connection info");
5807
+ }
5808
+ const pf = info.portForward;
5809
+ const forwardCmd = pf ? `kubectl port-forward -n ${pf.namespace} svc/${pf.service} ${pf.localPort}:${pf.remotePort}` : null;
5810
+ const psqlCmd = pf && info.username && info.database ? `psql -h 127.0.0.1 -p ${pf.localPort} -U ${info.username} -d ${info.database}` : null;
5811
+ emit(() => {
5812
+ console.log("");
5813
+ console.log(chalk.bold(` 🐘 Database — ${displayProjectRef(rawArgs)}`));
5814
+ console.log("");
5815
+ if (info.unavailableReason) {
5816
+ console.log(chalk.yellow(` ${info.unavailableReason}`));
5817
+ console.log("");
5818
+ return;
5819
+ }
5820
+ keyValues([
5821
+ ["Type", info.type],
5822
+ ["Host", info.host],
5823
+ ["Port", info.port],
5824
+ ["Database", info.database],
5825
+ ["Username", info.username],
5826
+ ["Password", info.passwordAvailable ? chalk.gray("stored — not shown here") : chalk.yellow("none stored")]
5827
+ ]);
5828
+ console.log("");
5829
+ if (forwardCmd) {
5830
+ console.log(chalk.gray(" A managed database is only reachable inside its cluster. To connect:"));
5831
+ console.log("");
5832
+ console.log(` ${forwardCmd}`);
5833
+ if (psqlCmd) console.log(` ${psqlCmd}`);
5834
+ console.log("");
5835
+ if (info.passwordAvailable) {
5836
+ console.log(chalk.gray(" Get the password with: ") + chalk.bold("rebase cloud db info --reveal"));
5837
+ console.log("");
5838
+ }
5839
+ }
5840
+ }, {
5841
+ type: info.type ?? null,
5842
+ host: info.host ?? null,
5843
+ port: info.port ?? null,
5844
+ database: info.database ?? null,
5845
+ username: info.username ?? null,
5846
+ passwordAvailable: Boolean(info.passwordAvailable),
5847
+ portForwardCommand: forwardCmd,
5848
+ psqlCommand: psqlCmd
5849
+ });
5850
+ }
5851
+ async function debugCommand(action, rawArgs) {
5852
+ switch (action) {
5853
+ case void 0:
5854
+ case "health":
5855
+ await healthCommand(rawArgs);
5856
+ break;
5857
+ case "logs":
5858
+ await logView(rawArgs, {
5859
+ defaultSinceSeconds: 900,
5860
+ limit: 200,
5861
+ title: "📄 Logs"
5862
+ });
5863
+ break;
5864
+ case "errors":
5865
+ await logView(rawArgs, {
5866
+ filter: isErrorLine,
5867
+ defaultSinceSeconds: 3600,
5868
+ limit: 40,
5869
+ title: "🔥 Errors"
5870
+ });
5871
+ break;
5872
+ case "boot":
5873
+ await logView(rawArgs, {
5874
+ filter: isBootLine,
5875
+ defaultSinceSeconds: 168 * 3600,
5876
+ limit: 25,
5877
+ title: "🚀 Boot"
5878
+ });
5879
+ break;
5880
+ case "requests":
5881
+ await requestsCommand(rawArgs);
5882
+ break;
5883
+ case "pod":
5884
+ case "workload":
5885
+ await podCommand(rawArgs);
5886
+ break;
5887
+ case "db":
5888
+ await dbDebugCommand(rawArgs);
5889
+ break;
5890
+ case "help":
5891
+ case "--help":
5892
+ printDebugHelp();
5893
+ break;
5894
+ default:
5895
+ if (isJsonMode()) fail(`Unknown debug command: ${action}`, void 0, "unknown_command");
5896
+ console.error(chalk.red(`Unknown debug command: ${action}`));
5897
+ console.log("");
5898
+ printDebugHelp();
5899
+ process.exit(1);
5900
+ }
5901
+ }
5902
+ function printDebugHelp() {
5903
+ console.log(`
5904
+ ${chalk.bold("rebase cloud debug")} — Find out why a deployed project is misbehaving
5905
+
5906
+ ${chalk.green.bold("Usage")}
5907
+ rebase cloud debug ${chalk.blue("<subcommand>")} [options]
5908
+
5909
+ ${chalk.green.bold("End-to-end")}
5910
+ ${chalk.blue.bold("health")} Probe the live URL and explain every status code ${chalk.gray("(default)")}
5911
+
5912
+ ${chalk.green.bold("Runtime")}
5913
+ ${chalk.blue.bold("logs")} ${chalk.gray("[--since 15m]")} Recent application logs
5914
+ ${chalk.blue.bold("errors")} ${chalk.gray("[--since 1h]")} Error and warning lines only
5915
+ ${chalk.blue.bold("requests")} ${chalk.gray("[--since 15m]")} HTTP requests the server logged ${chalk.gray("(status, path, latency)")}
5916
+ ${chalk.blue.bold("boot")} What the server decided at startup ${chalk.gray("(storage, functions, auth)")}
5917
+ ${chalk.blue.bold("pod")} Replicas, image, namespace, cluster placement
5918
+
5919
+ ${chalk.green.bold("Data")}
5920
+ ${chalk.blue.bold("db")} Connection shape + the port-forward recipe ${chalk.gray("(no password)")}
5921
+
5922
+ ${chalk.green.bold("Options")}
5923
+ ${chalk.blue("--since <dur>")} Lookback window: ${chalk.gray("90s, 15m, 2h, 1d")}
5924
+ ${chalk.blue("--tail <n>")} Lines to read per pod ${chalk.gray("(default 500)")}
5925
+ ${chalk.blue("--previous")} Read the CRASHED container instance ${chalk.gray("(where the reason lives)")}
5926
+ ${chalk.blue("--host <hostname>")} Probe this address instead of the project's own
5927
+ ${chalk.blue("--collection <name>")} Collection for the unauth-read probe ${chalk.gray("(default: users)")}
5928
+ ${chalk.blue("--function <name>")} Also assert this function loaded ${chalk.gray("(checked against the listing)")}
5929
+ ${chalk.blue("--project, -p <slug>")} Operate on a project without linking
5930
+
5931
+ ${chalk.gray("Everything here is read-only. `health` exits non-zero when a check fails,")}
5932
+ ${chalk.gray("so it works in a deploy script. To restart a workload, use `rebase cloud restart`.")}
5933
+ `);
5934
+ }
5935
+ //#endregion
5066
5936
  //#region src/commands/cloud/resources.ts
5067
5937
  /**
5068
5938
  * `rebase cloud` resource subcommands: status, metrics, webhooks, storage,
@@ -5175,7 +6045,10 @@ async function webhooksCommand(subcommand, rawArgs) {
5175
6045
  reportError(e, "Webhook operation failed");
5176
6046
  }
5177
6047
  }
5178
- async function storageCommand(rawArgs) {
6048
+ async function storageCommand(action, rawArgs) {
6049
+ if (action === "create") return storageCreateCommand(rawArgs);
6050
+ if (action === "attach") return storageAttachCommand(rawArgs);
6051
+ if (action === "help") return printStorageHelp();
5179
6052
  const { client } = await requireClient(rawArgs);
5180
6053
  const projectId = await requireProject(rawArgs, client);
5181
6054
  try {
@@ -5200,6 +6073,107 @@ async function storageCommand(rawArgs) {
5200
6073
  reportError(e, "Failed to list storage");
5201
6074
  }
5202
6075
  }
6076
+ function printStorageHelp() {
6077
+ console.log("");
6078
+ console.log(chalk.bold(" rebase cloud storage"));
6079
+ console.log("");
6080
+ console.log(" " + chalk.blue.bold("storage") + " List this project's storage");
6081
+ console.log(" " + chalk.blue.bold("storage create") + " Provision platform-managed storage");
6082
+ console.log(" " + chalk.blue.bold("storage attach") + " Attach your own S3-compatible bucket");
6083
+ console.log("");
6084
+ console.log(chalk.gray(" attach options:"));
6085
+ console.log(chalk.gray(" --bucket <name> Bucket name (required)"));
6086
+ console.log(chalk.gray(" --access-key-id <id> Access key ID (required)"));
6087
+ console.log(chalk.gray(" --secret-access-key <s> Secret access key (required)"));
6088
+ console.log(chalk.gray(" --endpoint <url> S3 endpoint; omit for AWS"));
6089
+ console.log(chalk.gray(" --region <region> Region"));
6090
+ console.log(chalk.gray(" --force-path-style Required by MinIO and some gateways"));
6091
+ console.log("");
6092
+ console.log(chalk.gray(" Without either, a tenant falls back to the container filesystem and"));
6093
+ console.log(chalk.gray(" loses uploaded files on its next restart."));
6094
+ console.log("");
6095
+ }
6096
+ async function storageCreateCommand(rawArgs) {
6097
+ const { client } = await requireClient(rawArgs);
6098
+ const projectId = await requireProject(rawArgs, client);
6099
+ try {
6100
+ console.log("");
6101
+ console.log(chalk.gray(" Provisioning managed storage — this creates a bucket and its credentials..."));
6102
+ const res = await client.functions.invoke(`storage-provision/${encodeURIComponent(projectId)}`, void 0, { method: "POST" });
6103
+ const info = res.data ?? res.data;
6104
+ success(`Managed storage provisioned for ${displayProjectRef(rawArgs)}.`);
6105
+ keyValues([
6106
+ ["Bucket", info.bucketName],
6107
+ ["Region", info.region],
6108
+ ["Endpoint", info.endpoint],
6109
+ ["Access key", info.accessKeyId]
6110
+ ]);
6111
+ console.log("");
6112
+ console.log(chalk.gray(" The secret key is stored encrypted and injected at deploy time; it is not displayed."));
6113
+ console.log(chalk.gray(" Redeploy for the tenant to pick it up: ") + chalk.bold("rebase cloud deploy"));
6114
+ console.log("");
6115
+ } catch (e) {
6116
+ reportError(e, "Failed to provision managed storage");
6117
+ }
6118
+ }
6119
+ async function storageAttachCommand(rawArgs) {
6120
+ const parsed = arg({
6121
+ "--bucket": String,
6122
+ "--access-key-id": String,
6123
+ "--secret-access-key": String,
6124
+ "--endpoint": String,
6125
+ "--region": String,
6126
+ "--force-path-style": Boolean
6127
+ }, {
6128
+ argv: rawArgs.slice(3),
6129
+ permissive: true
6130
+ });
6131
+ const bucket = parsed["--bucket"];
6132
+ const accessKeyId = parsed["--access-key-id"];
6133
+ const secretAccessKey = parsed["--secret-access-key"];
6134
+ const missing = [
6135
+ !bucket && "--bucket",
6136
+ !accessKeyId && "--access-key-id",
6137
+ !secretAccessKey && "--secret-access-key"
6138
+ ].filter(Boolean);
6139
+ if (missing.length > 0) fail(`Missing ${missing.join(", ")}.`, "A bucket without credentials cannot be used, and would be stored as though it could. Run `rebase cloud storage --help` for the full list.");
6140
+ const { client } = await requireClient(rawArgs);
6141
+ const projectId = await requireProject(rawArgs, client);
6142
+ try {
6143
+ const existing = (await client.data.collection("storages").find({
6144
+ where: { project: ["==", projectId] },
6145
+ limit: 1
6146
+ })).data[0];
6147
+ const row = {
6148
+ project: projectId,
6149
+ type: "byos",
6150
+ status: "active",
6151
+ s3Bucket: bucket,
6152
+ s3AccessKeyId: accessKeyId,
6153
+ s3SecretAccessKey: secretAccessKey,
6154
+ bucketName: bucket
6155
+ };
6156
+ if (parsed["--endpoint"]) row.s3Endpoint = parsed["--endpoint"];
6157
+ if (parsed["--region"]) {
6158
+ row.s3Region = parsed["--region"];
6159
+ row.region = parsed["--region"];
6160
+ }
6161
+ if (parsed["--force-path-style"]) row.s3ForcePathStyle = true;
6162
+ if (existing?.id) await client.data.collection("storages").update(String(existing.id), row);
6163
+ else await client.data.collection("storages").create(row);
6164
+ success(`Storage attached to ${displayProjectRef(rawArgs)}.`);
6165
+ keyValues([
6166
+ ["Bucket", bucket],
6167
+ ["Endpoint", parsed["--endpoint"] ?? "AWS S3"],
6168
+ ["Region", parsed["--region"] ?? "(default)"]
6169
+ ]);
6170
+ console.log("");
6171
+ console.log(chalk.gray(" Redeploy for the tenant to pick it up: ") + chalk.bold("rebase cloud deploy"));
6172
+ console.log("");
6173
+ } catch (e) {
6174
+ reportError(e, "Failed to attach storage");
6175
+ }
6176
+ }
5203
6177
  async function clustersCommand(rawArgs) {
5204
6178
  const { client } = await requireClient(rawArgs);
5205
6179
  try {
@@ -5391,6 +6365,9 @@ async function cloudCommand(subcommand, rawArgs) {
5391
6365
  case "metrics":
5392
6366
  await metricsCommand(rawArgs);
5393
6367
  break;
6368
+ case "debug":
6369
+ await debugCommand(action, rawArgs);
6370
+ break;
5394
6371
  case "env":
5395
6372
  await envCommand(action, rawArgs);
5396
6373
  break;
@@ -5417,7 +6394,7 @@ async function cloudCommand(subcommand, rawArgs) {
5417
6394
  await webhooksCommand(action, rawArgs);
5418
6395
  break;
5419
6396
  case "storage":
5420
- await storageCommand(rawArgs);
6397
+ await storageCommand(action, rawArgs);
5421
6398
  break;
5422
6399
  case "clusters":
5423
6400
  await clustersCommand(rawArgs);
@@ -5502,6 +6479,7 @@ ${chalk.green.bold("Deploy & observe")}
5502
6479
  ${chalk.blue.bold("start|stop|restart")} ${chalk.gray("[-y]")} Power ops ${chalk.gray("(stop/restart need -y)")}
5503
6480
  ${chalk.blue.bold("status")} One-glance project status
5504
6481
  ${chalk.blue.bold("metrics")} Live CPU / memory / disk
6482
+ ${chalk.blue.bold("debug")} ${chalk.gray("[health|logs|…]")} Diagnose a misbehaving deployment ${chalk.gray("(read-only)")}
5505
6483
 
5506
6484
  ${chalk.green.bold("Config")}
5507
6485
  ${chalk.blue.bold("env list|set|unset|reveal|pull")}
@@ -5520,6 +6498,8 @@ ${chalk.green.bold("Databases")}
5520
6498
  ${chalk.green.bold("Other resources")}
5521
6499
  ${chalk.blue.bold("webhooks list|create|delete")}
5522
6500
  ${chalk.blue.bold("storage")} List storage buckets
6501
+ ${chalk.blue.bold("storage create")} Provision platform-managed storage
6502
+ ${chalk.blue.bold("storage attach")} Attach your own S3-compatible bucket
5523
6503
  ${chalk.blue.bold("clusters")} List compute clusters
5524
6504
  ${chalk.blue.bold("billing setup")} Attach a card to the org ${chalk.gray("(one-time, opens browser)")}
5525
6505
  ${chalk.blue.bold("billing")} Show billing account + card on file
@@ -5561,6 +6541,7 @@ async function entry(args) {
5561
6541
  const command = parsedArgs._[0];
5562
6542
  const subcommand = parsedArgs._[1];
5563
6543
  if (!command || parsedArgs["--help"] && ![
6544
+ "init",
5564
6545
  "schema",
5565
6546
  "db",
5566
6547
  "dev",
@@ -5628,9 +6609,10 @@ async function entry(args) {
5628
6609
  await cloudCommand(effectiveSubcommand, args);
5629
6610
  break;
5630
6611
  default:
5631
- console.log(chalk.red(`Unknown command: ${command}`));
6612
+ console.error(chalk.red(`Unknown command: ${command}`));
5632
6613
  console.log("");
5633
6614
  printHelp();
6615
+ process.exit(1);
5634
6616
  }
5635
6617
  }
5636
6618
  function printHelp() {
@@ -5690,6 +6672,6 @@ ${chalk.gray("Documentation: https://rebase.pro/docs")}
5690
6672
  `);
5691
6673
  }
5692
6674
  //#endregion
5693
- export { authCommand, buildCommand, buildInitQuestions, cloudCommand, configureEnvFile, createRebaseApp, dbCommand, detectPackageManager, devCommand, doctorCommand, entry, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, generateSdkCommand, getActiveBackendPlugin, getPMCommands, isPnpmAvailable, requireBackendDir, requireProjectRoot, resolveLocalBin, resolvePluginCliScript, resolveTsx, schemaCommand, startCommand, validateProjectName, validateTsxInstallation };
6675
+ export { authCommand, buildCommand, buildInitQuestions, cloudCommand, configureEnvFile, createRebaseApp, dbCommand, detectPackageManager, devCommand, doctorCommand, entry, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, formatCdTarget, generateSdkCommand, getActiveBackendPlugin, getPMCommands, isPnpmAvailable, printInitHelp, requireBackendDir, requireProjectRoot, resolveLocalBin, resolvePluginCliScript, resolveTsx, schemaCommand, startCommand, validateProjectName, validateTsxInstallation };
5694
6676
 
5695
6677
  //# sourceMappingURL=index.es.js.map