@rebasepro/cli 0.21.0 → 0.21.1-canary.g8c5a265

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/README.md CHANGED
@@ -9,8 +9,8 @@ pnpm add -g @rebasepro/cli
9
9
  ```
10
10
 
11
11
  ESM-only: `"type": "module"` with no CommonJS build, so it is loaded with
12
- `import`. `require()` of it resolves only on Node 22.12+, which supports
13
- `require(esm)`.
12
+ `import`. It needs Node `>=22.22.0` (its `engines` floor), where `require()`
13
+ of it resolves too: Node has supported `require(esm)` since 22.12.
14
14
 
15
15
  The CLI is also bundled with every Rebase project as a local dependency.
16
16
 
@@ -20,19 +20,29 @@ The CLI is also bundled with every Rebase project as a local dependency.
20
20
  |---------|-------------|
21
21
  | `rebase init` | Scaffold a new Rebase project |
22
22
  | `rebase dev` | Start the development server (backend + frontend) |
23
- | `rebase build` | Build all workspace packages |
23
+ | `rebase build` | Build the apps declared in `rebase.json` into a bundle |
24
+ | `rebase normalize-imports` | Complete compiled output's relative imports for Node ESM |
24
25
  | `rebase start` | Start the backend server (production) |
26
+ | `rebase apps list` | Show the apps this repository declares |
25
27
  | `rebase schema generate` | Generate Drizzle schema from collection definitions |
26
28
  | `rebase schema introspect` | Introspect an existing database → Rebase collections |
29
+ | `rebase schema stale` | Report generated schema files the collections have moved past |
27
30
  | `rebase db push` | Apply schema directly to database (dev). Previews the plan and refuses destructive changes (e.g. dropped columns) unless confirmed interactively or run with `--allow-destructive`. |
28
31
  | `rebase db generate` | Generate SQL migration files |
29
32
  | `rebase db migrate` | Run pending migrations |
33
+ | `rebase db pull` | Copy another database into local development |
34
+ | `rebase db branch` | Create, list, switch and delete database branches |
35
+ | `rebase db stop` | Stop the managed development database (data is kept) |
36
+ | `rebase db reset` | Delete the managed development database and start over |
30
37
  | `rebase generate-sdk` | Generate a typed TypeScript SDK from collections |
31
38
  | `rebase auth reset-password` | Reset a user's password |
39
+ | `rebase api-keys list \| create \| revoke` | Manage scoped service API keys |
32
40
  | `rebase doctor` | Detect schema drift between collections, Drizzle schema, and database |
33
41
  | `rebase status` | Show every resource this project declares and whether its variables are set |
34
42
  | `rebase resources` | List the databases, buckets and topics this project declares |
43
+ | `rebase eject` | Own the server process and the image (one-way) |
35
44
  | `rebase skills install` | Install Rebase agent skills for your AI coding assistant |
45
+ | `rebase telemetry` | Anonymous usage sharing (opt-in, off by default) |
36
46
  | `rebase cloud <command>` | Manage your apps on Rebase Cloud (auth, deploy, databases, …) |
37
47
 
38
48
  Run `rebase --help` or `rebase <command> --help` for detailed usage.
package/bin/rebase.js CHANGED
@@ -188,7 +188,10 @@ const { entry } = await import("../dist/index.es.js");
188
188
  * `utils/args.ts` marks those with `isUsageError` rather than a class, because
189
189
  * this file imports the bundle and `instanceof` cannot reach across it.
190
190
  */
191
- const wantsStack = process.argv.includes("--debug") || process.env.REBASE_DEBUG === "1";
191
+ // After the bundle, which already imports this package, and spelled as the
192
+ // commands spell it: `wantsRawError` in `commands/cloud/errors.ts`.
193
+ const { parseEnvBoolean } = await import("@rebasepro/types");
194
+ const wantsStack = process.argv.includes("--debug") || parseEnvBoolean(process.env.REBASE_DEBUG) === true;
192
195
 
193
196
  entry(process.argv).catch((error) => {
194
197
  const message = error instanceof Error ? error.message : String(error);
@@ -76,5 +76,10 @@ export declare function summarizeError(error: unknown, context: string): ErrorSu
76
76
  * `--debug` is already what `bin/rebase.js` prints after every failure as the
77
77
  * thing to add, so the raw payload hangs off the flag people are told to reach
78
78
  * for rather than off one invented here.
79
+ *
80
+ * The one reader of `REBASE_DEBUG` in the commands, spelled as the bin spells
81
+ * it. It used to be `=== "1"` here while the quote fallbacks in `resources.ts`
82
+ * tested the raw string for truthiness, so `=true` hid the body and `=0`
83
+ * printed the fallback.
79
84
  */
80
85
  export declare function wantsRawError(argv?: readonly string[]): boolean;
package/dist/index.es.js CHANGED
@@ -22,7 +22,7 @@ import { execFileSync, spawn, spawnSync } from "child_process";
22
22
  import os from "os";
23
23
  import { createRebaseClient } from "@rebasepro/client";
24
24
  import dotenv from "dotenv";
25
- import { BUNDLE_FORMAT_VERSION, DEFAULT_DATA_SOURCE_KEY, DEFAULT_RESOURCE_KEY, DEFAULT_STORAGE_SOURCE_KEY, RUNTIME_CONTRACT_VERSION, buildResourceGraph, computeSchemaVersion, declareFunction, declaredQueueConsumers, declaredResources, declaredSubscriptions, deserializeCollections, envBasesForResource, findEnvSuffixCollision, findStorageSuffixCollision, getDataSourceCapabilities, isResourceHandle, reservedPrefixFor, resetDeclaredQueueConsumers, resetDeclaredResources, resetDeclaredSubscriptions, resolveResourceRefs, resourceEnvSuffix, resourceId, resourceKeyOf, resourceToDataSource } from "@rebasepro/types";
25
+ import { BUNDLE_FORMAT_VERSION, DEFAULT_DATA_SOURCE_KEY, DEFAULT_RESOURCE_KEY, DEFAULT_STORAGE_SOURCE_KEY, RUNTIME_CONTRACT_VERSION, buildResourceGraph, computeSchemaVersion, declareFunction, declaredQueueConsumers, declaredResources, declaredSubscriptions, deserializeCollections, envBasesForResource, findEnvSuffixCollision, findStorageSuffixCollision, getDataSourceCapabilities, isResourceHandle, parseEnvBoolean, reservedPrefixFor, resetDeclaredQueueConsumers, resetDeclaredResources, resetDeclaredSubscriptions, resolveResourceRefs, resourceEnvSuffix, resourceId, resourceKeyOf, resourceToDataSource } from "@rebasepro/types";
26
26
  import { CodegenError, generateSDK, toSafeIdentifier } from "@rebasepro/codegen";
27
27
  import { createRequire } from "module";
28
28
  import { randomBytes as randomBytes$1 } from "node:crypto";
@@ -186,6 +186,29 @@ function parseCommandArgs({ spec, rawArgs, commandWords, command, maxPositionals
186
186
  //#endregion
187
187
  //#region src/commands/cloud/errors.ts
188
188
  /**
189
+ * Turning a control-plane failure into something the caller can act on.
190
+ *
191
+ * The control plane talks to Kubernetes, and when Kubernetes refuses it, the
192
+ * refusal travels back verbatim: a whole `Status` object, the request headers,
193
+ * an `audit-id`, an `x-kubernetes-pf-flowschema-uid`. That reached the user's
194
+ * terminal unedited. Two things are wrong with it beyond the noise.
195
+ *
196
+ * The first is that the one sentence that matters is buried in the middle of a
197
+ * JSON blob, so the remedy — if there is one — is the hardest part to find.
198
+ *
199
+ * The second is worse, and is the reason this file exists rather than a
200
+ * `slice(0, 200)`. A `403` naming a `system:serviceaccount:` is a statement
201
+ * about the PLATFORM's own credentials: some role the control plane runs as is
202
+ * missing a grant. Nothing in the user's project can change that — not the
203
+ * collections, not `rebase.json`, not the deploy flags — and the error, printed
204
+ * raw in the middle of `rebase cloud deploy`, reads exactly like a project
205
+ * fault. Someone acting on that reading deletes working code looking for the
206
+ * cause. (That is not hypothetical: three cron jobs were removed from a project
207
+ * to test whether they caused a `cronjobs.batch` 403. They did not.)
208
+ *
209
+ * So this classifies before it summarises, and says whose problem it is.
210
+ */
211
+ /**
189
212
  * The service accounts a Kubernetes 403 can name, and what each means.
190
213
  *
191
214
  * `system:serviceaccount:` is the platform's own identity — the control plane's
@@ -308,9 +331,14 @@ function truncate(text) {
308
331
  * `--debug` is already what `bin/rebase.js` prints after every failure as the
309
332
  * thing to add, so the raw payload hangs off the flag people are told to reach
310
333
  * for rather than off one invented here.
334
+ *
335
+ * The one reader of `REBASE_DEBUG` in the commands, spelled as the bin spells
336
+ * it. It used to be `=== "1"` here while the quote fallbacks in `resources.ts`
337
+ * tested the raw string for truthiness, so `=true` hid the body and `=0`
338
+ * printed the fallback.
311
339
  */
312
340
  function wantsRawError(argv = process.argv) {
313
- return argv.includes("--debug") || process.env.REBASE_DEBUG === "1";
341
+ return argv.includes("--debug") || parseEnvBoolean(process.env.REBASE_DEBUG) === true;
314
342
  }
315
343
  //#endregion
316
344
  //#region src/commands/cloud/context.ts
@@ -714,8 +742,7 @@ function initOutputMode(rawArgs) {
714
742
  argv: rawArgs.slice(2),
715
743
  permissive: true
716
744
  })["--json"]) JSON_MODE = true;
717
- else if (process.env.REBASE_JSON === "0") JSON_MODE = false;
718
- else JSON_MODE = process.env.REBASE_JSON === "1" || process.stdout.isTTY !== true;
745
+ else JSON_MODE = parseEnvBoolean(process.env.REBASE_JSON) ?? process.stdout.isTTY !== true;
719
746
  return JSON_MODE;
720
747
  }
721
748
  /** Whether the current invocation is emitting machine-readable JSON. */
@@ -1446,6 +1473,18 @@ function endpoint() {
1446
1473
  return process.env.REBASE_TELEMETRY_ENDPOINT?.trim() || "https://app.rebase.pro/api/functions/telemetry";
1447
1474
  }
1448
1475
  /**
1476
+ * Set to anything but a spelled no.
1477
+ *
1478
+ * The conventions behind these variables make presence the signal —
1479
+ * `CI=woodpecker` is a CI run, `DO_NOT_TRACK=please` is an answer — so a value
1480
+ * the parser does not recognise still counts, and the refusal holds. Only the
1481
+ * platform's spellings of no turn one off. It was `!== "0"` for two of them and
1482
+ * `!== "false"` for `CI`, so `CI=0` read as a runner.
1483
+ */
1484
+ function setAndNotNo(value) {
1485
+ return value !== void 0 && value.trim() !== "" && parseEnvBoolean(value) !== false;
1486
+ }
1487
+ /**
1449
1488
  * The one function that decides whether anything leaves the machine.
1450
1489
  *
1451
1490
  * Every path is a refusal except the last, which is the point: consent is
@@ -1465,9 +1504,9 @@ function endpoint() {
1465
1504
  * project.ts on why the reverse is refused.
1466
1505
  */
1467
1506
  function suppressionReason(env = process.env, cwd = process.cwd()) {
1468
- if (env.DO_NOT_TRACK && env.DO_NOT_TRACK !== "0") return "do_not_track";
1469
- if (env.REBASE_TELEMETRY_DISABLED && env.REBASE_TELEMETRY_DISABLED !== "0") return "rebase_telemetry_disabled";
1470
- if (env.CI && env.CI !== "false") return "ci";
1507
+ if (setAndNotNo(env.DO_NOT_TRACK)) return "do_not_track";
1508
+ if (setAndNotNo(env.REBASE_TELEMETRY_DISABLED)) return "rebase_telemetry_disabled";
1509
+ if (setAndNotNo(env.CI)) return "ci";
1471
1510
  if (readProjectPolicy(cwd) === "opt_out") return "project_opt_out";
1472
1511
  const config = readConfig();
1473
1512
  if (config.enabled === void 0) return "not_asked";
@@ -2343,7 +2382,7 @@ async function replacePlaceholders(options) {
2343
2382
  const matches = [...content.matchAll(/"(@rebasepro\/[^"]+)":\s*"workspace:\*"/g)];
2344
2383
  for (const match of matches) allPackages.add(match[1]);
2345
2384
  }
2346
- const probe = process.env.REBASE_E2E === "true" || version === "unknown" ? { kind: "published" } : await probeRelease(version);
2385
+ const probe = parseEnvBoolean(process.env.REBASE_E2E) === true || version === "unknown" ? { kind: "published" } : await probeRelease(version);
2347
2386
  if (probe.kind === "gap") throw new Error(`Rebase ${version} is not fully published to npm.\n\n${RELEASE_PROBE_PACKAGE} has no ${version} release — the registry has ${probe.found}.\nEvery @rebasepro package ships at one version, so scaffolding would pin ${allPackages.size}\ndependencies at a version that is not there, and the install would fail.\n\nThat is a release gap in Rebase itself — not a problem with your machine,
2348
2387
  your network, or your package manager.
2349
2388
 
@@ -4856,7 +4895,7 @@ function refuseStorageBlock(raw, issues) {
4856
4895
  * else. It used to be inferred from the presence of `backend/src/index.ts`,
4857
4896
  * which every scaffolded project had whether or not it wanted its own server, so
4858
4897
  * projects predating the manifest silently landed on the custom runtime and paid
4859
- * for it (see `docs/plans/cloud-deploy-workspace-vendoring.md`).
4898
+ * for it.
4860
4899
  */
4861
4900
  function synthesizeManifest(projectRoot) {
4862
4901
  const exists = (relative) => fs.existsSync(path.join(projectRoot, relative));
@@ -5774,8 +5813,8 @@ async function devCommand(rawArgs) {
5774
5813
  * gating only the preflight left the managed PGlite starting anyway, which
5775
5814
  * is the one database a scaffolded project would otherwise get.
5776
5815
  */
5777
- const noDb = Boolean(args["--no-db"]) || process.env.REBASE_DEV_NO_DB === "1";
5778
- const shouldGenerate = args["--generate"] || process.env.REBASE_AUTO_GENERATE === "true" || process.env.REBASE_GENERATE === "true";
5816
+ const noDb = Boolean(args["--no-db"]) || parseEnvBoolean(process.env.REBASE_DEV_NO_DB) === true;
5817
+ const shouldGenerate = args["--generate"] || parseEnvBoolean(process.env.REBASE_AUTO_GENERATE) === true || parseEnvBoolean(process.env.REBASE_GENERATE) === true;
5779
5818
  const { port: startPort, source: startPortSource } = resolveStartPort(projectRoot, args["--port"]);
5780
5819
  const portIsPinned = args["--port"] !== void 0;
5781
5820
  const pinnedFrontendPort = process.env.REBASE_FRONTEND_PORT;
@@ -7184,7 +7223,7 @@ function collectDeclaredDependencies(projectRoot) {
7184
7223
  if (typeof version === "string" && version.startsWith("workspace:")) continue;
7185
7224
  if (resolvesToWorkspacePackage(projectRoot, name)) continue;
7186
7225
  const protocol = typeof version === "string" ? nonRegistrySpecifier(version) : null;
7187
- if (protocol && process.env.REBASE_E2E === "true" && protocol === "file:") {
7226
+ if (protocol && parseEnvBoolean(process.env.REBASE_E2E) === true && protocol === "file:") {
7188
7227
  declared[name] = version;
7189
7228
  continue;
7190
7229
  }
@@ -13351,7 +13390,7 @@ async function billingCommand(rawArgs) {
13351
13390
  if (hasCluster) monthly = `${monthly} — platform fee, your own cluster`;
13352
13391
  }
13353
13392
  } catch (err) {
13354
- if (process.env.REBASE_DEBUG) console.error(` (no price: ${err instanceof Error ? err.message : String(err)})`);
13393
+ if (wantsRawError()) console.error(` (no price: ${err instanceof Error ? err.message : String(err)})`);
13355
13394
  }
13356
13395
  emit(() => {
13357
13396
  console.log("");
@@ -13470,7 +13509,7 @@ async function computeCommand(action, rawArgs) {
13470
13509
  path: "quote"
13471
13510
  });
13472
13511
  } catch (err) {
13473
- if (process.env.REBASE_DEBUG) console.error(` (no price: ${err instanceof Error ? err.message : String(err)})`);
13512
+ if (wantsRawError()) console.error(` (no price: ${err instanceof Error ? err.message : String(err)})`);
13474
13513
  }
13475
13514
  emit(() => {
13476
13515
  console.log("");