@rebasepro/cli 0.17.3 → 0.18.1

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.
Files changed (103) hide show
  1. package/LICENSE +0 -1
  2. package/README.md +21 -3
  3. package/bin/rebase.js +93 -6
  4. package/dist/branch-pointer-dBiClJ0p.js +111 -0
  5. package/dist/branch-pointer-dBiClJ0p.js.map +1 -0
  6. package/dist/bundle.d.ts +80 -3
  7. package/dist/cli.d.ts +22 -0
  8. package/dist/commands/auth.d.ts +13 -0
  9. package/dist/commands/cloud/action-help.d.ts +0 -10
  10. package/dist/commands/cloud/auth.d.ts +37 -0
  11. package/dist/commands/cloud/context.d.ts +90 -2
  12. package/dist/commands/cloud/databases.d.ts +0 -2
  13. package/dist/commands/cloud/declared-resources.d.ts +3 -0
  14. package/dist/commands/cloud/deploy.d.ts +76 -6
  15. package/dist/commands/cloud/deployments.d.ts +9 -2
  16. package/dist/commands/cloud/index.d.ts +36 -0
  17. package/dist/commands/cloud/link.d.ts +1 -1
  18. package/dist/commands/cloud/orgs.d.ts +6 -0
  19. package/dist/commands/cloud/resources.d.ts +81 -3
  20. package/dist/commands/cloud/settings.d.ts +7 -0
  21. package/dist/commands/db.d.ts +119 -9
  22. package/dist/commands/dev.d.ts +216 -1
  23. package/dist/commands/doctor.d.ts +51 -0
  24. package/dist/commands/init.d.ts +1 -0
  25. package/dist/commands/skills.d.ts +82 -13
  26. package/dist/commands/status.d.ts +1 -0
  27. package/dist/commands/telemetry.d.ts +0 -8
  28. package/dist/{daemon-Bdl4lrdt.js → daemon-YDZK9NZ1.js} +88 -9
  29. package/dist/daemon-YDZK9NZ1.js.map +1 -0
  30. package/dist/{daemon-entry-CmJn83zu.js → daemon-entry-LTFKtpHy.js} +117 -44
  31. package/dist/daemon-entry-LTFKtpHy.js.map +1 -0
  32. package/dist/dev-db/branch-pointer.d.ts +71 -0
  33. package/dist/dev-db/daemon.d.ts +27 -0
  34. package/dist/dev-db/prepare.d.ts +62 -0
  35. package/dist/dev-db/pull.d.ts +28 -0
  36. package/dist/dev-db/resolve.d.ts +45 -6
  37. package/dist/dev-db/state.d.ts +21 -0
  38. package/dist/dev-preflight-CNLl4rdo.js +367 -0
  39. package/dist/dev-preflight-CNLl4rdo.js.map +1 -0
  40. package/dist/doctor-environment.d.ts +131 -0
  41. package/dist/function-portability.d.ts +7 -0
  42. package/dist/index.d.ts +25 -15
  43. package/dist/index.es.js +5594 -1741
  44. package/dist/index.es.js.map +1 -1
  45. package/dist/project-3WCbrrfW.js +499 -0
  46. package/dist/project-3WCbrrfW.js.map +1 -0
  47. package/dist/{pull-DqPRu1te.js → pull-Bj0XnH5s.js} +31 -2
  48. package/dist/pull-Bj0XnH5s.js.map +1 -0
  49. package/dist/resolve-Y56osuQH.js +74 -0
  50. package/dist/resolve-Y56osuQH.js.map +1 -0
  51. package/dist/resources/derive.d.ts +67 -1
  52. package/dist/resources/status.d.ts +128 -0
  53. package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
  54. package/dist/{state-c0CJ6Kwb.js → state-C59Elrnt.js} +24 -15
  55. package/dist/state-C59Elrnt.js.map +1 -0
  56. package/dist/utils/args.d.ts +18 -0
  57. package/dist/utils/command-words.d.ts +45 -0
  58. package/dist/utils/dev-preflight.d.ts +46 -0
  59. package/dist/utils/package-manager.d.ts +23 -0
  60. package/dist/utils/project.d.ts +66 -0
  61. package/dist/utils/spawn-error.d.ts +1 -0
  62. package/dist/utils/unknown-command.d.ts +26 -0
  63. package/dist/utils/version.d.ts +10 -0
  64. package/package.json +36 -28
  65. package/templates/eject/backend/src/env.ts +9 -0
  66. package/templates/eject/backend/src/index.ts +46 -19
  67. package/templates/eject/config/resources.ts +1 -1
  68. package/templates/overlays/baas/README.md +31 -11
  69. package/templates/overlays/baas/backend/package.json +2 -2
  70. package/templates/overlays/baas/backend/tsconfig.json +1 -1
  71. package/templates/overlays/baas/package.json +5 -2
  72. package/templates/overlays/baas/pnpm-workspace.yaml +13 -0
  73. package/templates/template/.cursorrules +1 -0
  74. package/templates/template/.env.example +91 -8
  75. package/templates/template/.github/copilot-instructions.md +1 -0
  76. package/templates/template/.mcp.json +11 -0
  77. package/templates/template/.windsurfrules +1 -0
  78. package/templates/template/AGENTS.md +1 -0
  79. package/templates/template/CLAUDE.md +2 -1
  80. package/templates/template/README.md +59 -28
  81. package/templates/template/ai-instructions.md +29 -12
  82. package/templates/template/backend/functions/hello.ts +5 -1
  83. package/templates/template/backend/package.json +2 -2
  84. package/templates/template/backend/src/schema.generated.ts +8 -3
  85. package/templates/template/backend/tsconfig.json +1 -1
  86. package/templates/template/config/cms.d.ts +6 -0
  87. package/templates/template/config/package.json +1 -1
  88. package/templates/template/config/resources.ts +42 -3
  89. package/templates/template/config/storage.ts +2 -1
  90. package/templates/template/docker-compose.yml +50 -9
  91. package/templates/template/frontend/index.html +0 -1
  92. package/templates/template/frontend/package.json +1 -2
  93. package/templates/template/frontend/src/App.tsx +8 -2
  94. package/templates/template/frontend/vite.config.ts +8 -0
  95. package/templates/template/gitignore +19 -1
  96. package/templates/template/npmrc +13 -4
  97. package/templates/template/package.json +10 -1
  98. package/templates/template/pnpm-workspace.yaml +17 -0
  99. package/templates/template/scripts/example.ts +4 -1
  100. package/dist/daemon-Bdl4lrdt.js.map +0 -1
  101. package/dist/daemon-entry-CmJn83zu.js.map +0 -1
  102. package/dist/pull-DqPRu1te.js.map +0 -1
  103. package/dist/state-c0CJ6Kwb.js.map +0 -1
package/LICENSE CHANGED
@@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
20
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
22
-
package/README.md CHANGED
@@ -8,6 +8,10 @@ Developer CLI for scaffolding, running, and managing Rebase projects.
8
8
  pnpm add -g @rebasepro/cli
9
9
  ```
10
10
 
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)`.
14
+
11
15
  The CLI is also bundled with every Rebase project as a local dependency.
12
16
 
13
17
  ## Commands
@@ -26,6 +30,9 @@ The CLI is also bundled with every Rebase project as a local dependency.
26
30
  | `rebase generate-sdk` | Generate a typed TypeScript SDK from collections |
27
31
  | `rebase auth reset-password` | Reset a user's password |
28
32
  | `rebase doctor` | Detect schema drift between collections, Drizzle schema, and database |
33
+ | `rebase status` | Show every resource this project declares and whether its variables are set |
34
+ | `rebase resources` | List the databases, buckets and topics this project declares |
35
+ | `rebase skills install` | Install Rebase agent skills for your AI coding assistant |
29
36
  | `rebase cloud <command>` | Manage your apps on Rebase Cloud (auth, deploy, databases, …) |
30
37
 
31
38
  Run `rebase --help` or `rebase <command> --help` for detailed usage.
@@ -61,13 +68,24 @@ Run `rebase cloud --help` for the full list.
61
68
  ## Quick Start
62
69
 
63
70
  ```bash
64
- rebase init my-app
71
+ pnpm dlx @rebasepro/cli init my-app
65
72
  cd my-app
66
- docker compose up -d db
67
- pnpm run db:push
73
+ pnpm install
68
74
  pnpm run dev
69
75
  ```
70
76
 
77
+ That is the whole first run. With no `DATABASE_URL` set, `rebase dev` starts a
78
+ managed PostgreSQL (PGlite) in the project directory, generates the Drizzle
79
+ schema from your collections, and creates the tables at boot — no database to
80
+ install and no schema step.
81
+
82
+ Two commands worth knowing straight after:
83
+
84
+ ```bash
85
+ rebase status # every resource, and whether its variables are set
86
+ rebase skills install # Rebase skills for your AI coding assistant
87
+ ```
88
+
71
89
  ## Related Packages
72
90
 
73
91
  | Package | Role |
package/bin/rebase.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readdirSync, statSync } from "node:fs";
2
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
3
3
  import { dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
@@ -7,6 +7,85 @@ const here = dirname(fileURLToPath(import.meta.url));
7
7
  const distEntry = join(here, "..", "dist", "index.es.js");
8
8
  const srcDir = join(here, "..", "src");
9
9
 
10
+ /**
11
+ * Colour, but only for a terminal.
12
+ *
13
+ * Every other line the CLI prints goes through chalk, which checks this for
14
+ * itself. These three did not — they are written before the bundle is even
15
+ * imported, so they hard-coded `\x1b[31m` — and stderr is exactly where that
16
+ * costs something: `rebase status extra 2>err.txt` wrote the escapes into the
17
+ * file, and CI logs, `2>&1 | grep`, and every agent reading a failed command's
18
+ * output got them too.
19
+ *
20
+ * `NO_COLOR` and `FORCE_COLOR` are the two conventions chalk honours, so
21
+ * honouring the same two keeps one CLI rather than two.
22
+ */
23
+ const useColor = process.env.FORCE_COLOR !== undefined && process.env.FORCE_COLOR !== "0"
24
+ ? true
25
+ : Boolean(process.stderr.isTTY) && !process.env.NO_COLOR && process.env.TERM !== "dumb";
26
+
27
+ const paint = (code, text) => (useColor ? `\x1b[${code}m${text}\x1b[0m` : text);
28
+ const red = (text) => paint(31, text);
29
+ const yellow = (text) => paint(33, text);
30
+ const dim = (text) => paint(90, text);
31
+
32
+ /**
33
+ * Refuse to run on a Node older than this CLI's own floor, before anything else.
34
+ *
35
+ * `engines.node` is declared on all 22 packages and `check:floors` keeps the
36
+ * numbers in step — on the reasoning that "npm and pnpm check `engines` on
37
+ * install, so the number is load-bearing". pnpm 11 does not: a project whose
38
+ * `engines.node` is `>=99.0.0` installs silently and exits 0, and so does a
39
+ * *dependency* declaring it. npm prints `EBADENGINE` and also exits 0. So the
40
+ * declared floor was enforced by nothing at all, and `rebase init` on Node 20
41
+ * said nothing until a syntax error surfaced from inside a dependency —
42
+ * `checkNodeVersion` existed but only `rebase doctor` ever called it.
43
+ *
44
+ * First, and before `await import("../dist/…")`, because the bundle is the
45
+ * thing most likely to fail incomprehensibly on an old runtime: the point is to
46
+ * name the version rather than let the reader debug a stack trace in minified
47
+ * output.
48
+ *
49
+ * The floor is read from this package's own `package.json` rather than written
50
+ * here, so it cannot drift from what `check:floors` gates and from what
51
+ * `rebase doctor` reports. The comparison duplicates
52
+ * `doctor-environment.ts`'s — importing it would mean importing the bundle,
53
+ * which is the thing being guarded — and `bin-floor.test.ts` asserts the two
54
+ * still agree.
55
+ */
56
+ function assertNodeFloor() {
57
+ let required;
58
+ try {
59
+ const manifest = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8"));
60
+ required = manifest?.engines?.node;
61
+ } catch {
62
+ return; // No manifest, no floor to enforce. Never a reason to refuse.
63
+ }
64
+ if (typeof required !== "string") return;
65
+
66
+ // `>=x[.y[.z]]`, the only form any engines.node in this repo takes. Anything
67
+ // else parses to nothing and the guard stands down rather than guessing.
68
+ const declared = required.match(/>=\s*v?(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
69
+ if (!declared) return;
70
+ const running = process.versions.node.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/);
71
+ if (!running) return;
72
+
73
+ for (let i = 1; i <= 3; i++) {
74
+ const min = Number(declared[i] ?? 0);
75
+ const now = Number(running[i] ?? 0);
76
+ if (now > min) return;
77
+ if (now < min) {
78
+ process.stderr.write(
79
+ `${red(`✗ rebase CLI: Node ${process.versions.node} is running, and Rebase needs ${required}.`)}\n` +
80
+ ` Install Node ${declared[1]} or newer (nvm install ${declared[1]}), then reinstall dependencies.\n`
81
+ );
82
+ process.exit(1);
83
+ }
84
+ }
85
+ }
86
+
87
+ assertNodeFloor();
88
+
10
89
  /**
11
90
  * Warn when the built CLI is older than the source it was built from.
12
91
  *
@@ -53,7 +132,7 @@ function warnIfStale() {
53
132
  ? `${Math.round(seconds / 3600)}h`
54
133
  : seconds >= 60 ? `${Math.round(seconds / 60)}m` : `${seconds}s`;
55
134
  process.stderr.write(
56
- `⚠ rebase CLI: dist/ is ${ago} older than src/ — you are running a stale build.\n` +
135
+ `${yellow(`⚠ rebase CLI: dist/ is ${ago} older than src/ — you are running a stale build.`)}\n` +
57
136
  ` Rebuild with: (cd ${join(here, "..")} && npm run build)\n`
58
137
  );
59
138
  }
@@ -77,7 +156,7 @@ try {
77
156
  if (!existsSync(distEntry)) {
78
157
  const dev = existsSync(srcDir);
79
158
  process.stderr.write(
80
- `\x1b[31m✗ rebase CLI: not built yet — ${distEntry} is missing.\x1b[0m\n` +
159
+ `${red(`✗ rebase CLI: not built yet — ${distEntry} is missing.`)}\n` +
81
160
  (dev
82
161
  ? " Build it with: pnpm --filter @rebasepro/cli build\n" +
83
162
  " (or `pnpm build` from the repo root to build every package)\n"
@@ -102,16 +181,24 @@ const { entry } = await import("../dist/index.es.js");
102
181
  * friendly and exit never reach here. The stack is available behind
103
182
  * `--debug`/`REBASE_DEBUG`, because when the message is *not* enough that is
104
183
  * the only thing that helps.
184
+ *
185
+ * With one exception: a *usage* error. `rebase status extra` has no stack worth
186
+ * reading — it points at `arg` and `utils/args.ts` — and the hint suggests
187
+ * re-running with another flag, when the flags are precisely what went wrong.
188
+ * `utils/args.ts` marks those with `isUsageError` rather than a class, because
189
+ * this file imports the bundle and `instanceof` cannot reach across it.
105
190
  */
106
191
  const wantsStack = process.argv.includes("--debug") || process.env.REBASE_DEBUG === "1";
107
192
 
108
193
  entry(process.argv).catch((error) => {
109
194
  const message = error instanceof Error ? error.message : String(error);
110
- process.stderr.write(`\x1b[31m✗ ${message}\x1b[0m\n`);
195
+ const isUsage = Boolean(error && typeof error === "object" && error.isUsageError);
196
+
197
+ process.stderr.write(`${red(`✗ ${message}`)}\n`);
111
198
  if (wantsStack && error instanceof Error && error.stack) {
112
199
  process.stderr.write(`\n${error.stack}\n`);
113
- } else {
114
- process.stderr.write("\x1b[90m Re-run with --debug for the stack trace.\x1b[0m\n");
200
+ } else if (!isUsage) {
201
+ process.stderr.write(`${dim(" Re-run with --debug for the stack trace.")}\n`);
115
202
  }
116
203
  process.exit(1);
117
204
  });
@@ -0,0 +1,111 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import path from "path";
3
+ import fs from "fs";
4
+ //#region src/dev-db/branch-pointer.ts
5
+ /**
6
+ * Which database branch this checkout is working on.
7
+ *
8
+ * `rebase db branch create` made a full copy of the database in about a second
9
+ * and then stopped one step short of being a feature: there was no `switch`, no
10
+ * `--branch` on `rebase dev`, and nothing wrote the branch anywhere a command
11
+ * would read it. The only way to *use* a branch was to hand-edit
12
+ * `DATABASE_URL`, and the documentation claimed otherwise — "the CLI updates
13
+ * your local development configuration" — which it did not; the `.env` was
14
+ * byte-identical afterwards.
15
+ *
16
+ * This is that missing step, and the shape of it is chosen to avoid three
17
+ * things:
18
+ *
19
+ * 1. **It does not touch `.env`.** That file holds secrets, comments and the
20
+ * developer's own ordering, and a command that rewrites it in place will
21
+ * eventually lose something that was not its business. The branch is
22
+ * recorded beside the other per-checkout state in `.rebase/`, which is
23
+ * already gitignored — a branch is a fact about one machine, never about
24
+ * the project.
25
+ *
26
+ * 2. **It stores a name, not a connection string.** The credentials stay in
27
+ * exactly one place; the URL is derived by swapping the database name on
28
+ * the base one at resolution time. A stored URL would be a second copy of
29
+ * the password, and would go stale the moment `.env` changed.
30
+ *
31
+ * 3. **It does not outrank an explicit instruction.** `--database-url` and a
32
+ * `DATABASE_URL` in the shell are more immediate and more specific than a
33
+ * switch made yesterday, so they still win. The pointer sits directly above
34
+ * the project's `.env`: it is a deliberate, persistent choice, and `.env` is
35
+ * the project default it is meant to override. Anything less and `switch`
36
+ * would silently do nothing on every project that sets `DATABASE_URL` —
37
+ * which is every project not using the managed database.
38
+ */
39
+ var branch_pointer_exports = /* @__PURE__ */ __exportAll({
40
+ BRANCH_DB_PREFIX: () => "rb_",
41
+ branchDatabaseName: () => branchDatabaseName,
42
+ branchPointerPath: () => branchPointerPath,
43
+ branchUrl: () => branchUrl,
44
+ clearActiveBranch: () => clearActiveBranch,
45
+ databaseNameOf: () => databaseNameOf,
46
+ readActiveBranch: () => readActiveBranch,
47
+ writeActiveBranch: () => writeActiveBranch
48
+ });
49
+ /** Where the pointer lives, next to the rest of the per-checkout state. */
50
+ function branchPointerPath(projectRoot) {
51
+ return path.join(projectRoot, ".rebase", "branch.json");
52
+ }
53
+ function branchDatabaseName(name) {
54
+ return `rb_${name}`;
55
+ }
56
+ /** Read the active branch, or null when the checkout is on the main database. */
57
+ function readActiveBranch(projectRoot) {
58
+ try {
59
+ const raw = fs.readFileSync(branchPointerPath(projectRoot), "utf-8");
60
+ const parsed = JSON.parse(raw);
61
+ if (typeof parsed.name !== "string" || typeof parsed.database !== "string") return null;
62
+ if (!parsed.name || !parsed.database) return null;
63
+ return {
64
+ name: parsed.name,
65
+ database: parsed.database
66
+ };
67
+ } catch {
68
+ return null;
69
+ }
70
+ }
71
+ function writeActiveBranch(projectRoot, branch) {
72
+ const file = branchPointerPath(projectRoot);
73
+ fs.mkdirSync(path.dirname(file), { recursive: true });
74
+ fs.writeFileSync(file, `${JSON.stringify(branch, null, 2)}\n`, "utf-8");
75
+ }
76
+ /** Return to the main database. Absent is success — this is idempotent. */
77
+ function clearActiveBranch(projectRoot) {
78
+ fs.rmSync(branchPointerPath(projectRoot), { force: true });
79
+ }
80
+ /**
81
+ * The base connection string with its database swapped for the branch's.
82
+ *
83
+ * Everything else is carried across untouched — credentials, host, port, and
84
+ * the query string, which is load-bearing here: `?sslmode=disable` is what a
85
+ * local Docker Postgres needs, and dropping it turns a switch into a TLS error
86
+ * that says nothing about branches.
87
+ *
88
+ * Returns null for a connection string that cannot be parsed, so the caller can
89
+ * say so plainly rather than emitting a mangled URL that fails later.
90
+ */
91
+ function branchUrl(baseUrl, database) {
92
+ try {
93
+ const url = new URL(baseUrl);
94
+ url.pathname = `/${database}`;
95
+ return url.toString();
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
100
+ /** The database name in a connection string, for reporting what you are on. */
101
+ function databaseNameOf(connectionString) {
102
+ try {
103
+ return new URL(connectionString).pathname.replace(/^\//, "") || null;
104
+ } catch {
105
+ return null;
106
+ }
107
+ }
108
+ //#endregion
109
+ export { branch_pointer_exports as n, readActiveBranch as r, branchUrl as t };
110
+
111
+ //# sourceMappingURL=branch-pointer-dBiClJ0p.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"branch-pointer-dBiClJ0p.js","names":[],"sources":["../src/dev-db/branch-pointer.ts"],"sourcesContent":["/**\n * Which database branch this checkout is working on.\n *\n * `rebase db branch create` made a full copy of the database in about a second\n * and then stopped one step short of being a feature: there was no `switch`, no\n * `--branch` on `rebase dev`, and nothing wrote the branch anywhere a command\n * would read it. The only way to *use* a branch was to hand-edit\n * `DATABASE_URL`, and the documentation claimed otherwise — \"the CLI updates\n * your local development configuration\" — which it did not; the `.env` was\n * byte-identical afterwards.\n *\n * This is that missing step, and the shape of it is chosen to avoid three\n * things:\n *\n * 1. **It does not touch `.env`.** That file holds secrets, comments and the\n * developer's own ordering, and a command that rewrites it in place will\n * eventually lose something that was not its business. The branch is\n * recorded beside the other per-checkout state in `.rebase/`, which is\n * already gitignored — a branch is a fact about one machine, never about\n * the project.\n *\n * 2. **It stores a name, not a connection string.** The credentials stay in\n * exactly one place; the URL is derived by swapping the database name on\n * the base one at resolution time. A stored URL would be a second copy of\n * the password, and would go stale the moment `.env` changed.\n *\n * 3. **It does not outrank an explicit instruction.** `--database-url` and a\n * `DATABASE_URL` in the shell are more immediate and more specific than a\n * switch made yesterday, so they still win. The pointer sits directly above\n * the project's `.env`: it is a deliberate, persistent choice, and `.env` is\n * the project default it is meant to override. Anything less and `switch`\n * would silently do nothing on every project that sets `DATABASE_URL` —\n * which is every project not using the managed database.\n */\n\nimport fs from \"fs\";\nimport path from \"path\";\n\n/** Where the pointer lives, next to the rest of the per-checkout state. */\nexport function branchPointerPath(projectRoot: string): string {\n return path.join(projectRoot, \".rebase\", \"branch.json\");\n}\n\nexport interface ActiveBranch {\n /** The name the developer typed. */\n name: string;\n /** The PostgreSQL database that name refers to. */\n database: string;\n}\n\n/**\n * The prefix `BranchService` puts on every branch database.\n *\n * Duplicated from the driver rather than imported because the CLI must answer\n * \"which database\" without loading a database driver — `rebase dev` reads this\n * before it knows which plugin the project uses. It is covered by a test that\n * fails if the driver's prefix moves.\n */\nexport const BRANCH_DB_PREFIX = \"rb_\";\n\nexport function branchDatabaseName(name: string): string {\n return `${BRANCH_DB_PREFIX}${name}`;\n}\n\n/** Read the active branch, or null when the checkout is on the main database. */\nexport function readActiveBranch(projectRoot: string): ActiveBranch | null {\n try {\n const raw = fs.readFileSync(branchPointerPath(projectRoot), \"utf-8\");\n const parsed = JSON.parse(raw) as Partial<ActiveBranch>;\n if (typeof parsed.name !== \"string\" || typeof parsed.database !== \"string\") return null;\n if (!parsed.name || !parsed.database) return null;\n\n return { name: parsed.name, database: parsed.database };\n } catch {\n // Absent is the common case and is not an error. A corrupt file is\n // treated the same way: the answer is \"no branch\", which is the state\n // the developer can always get back to with `branch switch --off`.\n return null;\n }\n}\n\nexport function writeActiveBranch(projectRoot: string, branch: ActiveBranch): void {\n const file = branchPointerPath(projectRoot);\n fs.mkdirSync(path.dirname(file), { recursive: true });\n fs.writeFileSync(file, `${JSON.stringify(branch, null, 2)}\\n`, \"utf-8\");\n}\n\n/** Return to the main database. Absent is success — this is idempotent. */\nexport function clearActiveBranch(projectRoot: string): void {\n fs.rmSync(branchPointerPath(projectRoot), { force: true });\n}\n\n/**\n * The base connection string with its database swapped for the branch's.\n *\n * Everything else is carried across untouched — credentials, host, port, and\n * the query string, which is load-bearing here: `?sslmode=disable` is what a\n * local Docker Postgres needs, and dropping it turns a switch into a TLS error\n * that says nothing about branches.\n *\n * Returns null for a connection string that cannot be parsed, so the caller can\n * say so plainly rather than emitting a mangled URL that fails later.\n */\nexport function branchUrl(baseUrl: string, database: string): string | null {\n try {\n const url = new URL(baseUrl);\n // `pathname` keeps its leading slash; a URL with no database at all\n // still gets one, which is what connecting to a named branch means.\n url.pathname = `/${database}`;\n\n return url.toString();\n } catch {\n return null;\n }\n}\n\n/** The database name in a connection string, for reporting what you are on. */\nexport function databaseNameOf(connectionString: string): string | null {\n try {\n const name = new URL(connectionString).pathname.replace(/^\\//, \"\");\n\n return name || null;\n } catch {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,kBAAkB,aAA6B;CAC3D,OAAO,KAAK,KAAK,aAAa,WAAW,aAAa;AAC1D;AAmBA,SAAgB,mBAAmB,MAAsB;CACrD,OAAO,MAAsB;AACjC;;AAGA,SAAgB,iBAAiB,aAA0C;CACvE,IAAI;EACA,MAAM,MAAM,GAAG,aAAa,kBAAkB,WAAW,GAAG,OAAO;EACnE,MAAM,SAAS,KAAK,MAAM,GAAG;EAC7B,IAAI,OAAO,OAAO,SAAS,YAAY,OAAO,OAAO,aAAa,UAAU,OAAO;EACnF,IAAI,CAAC,OAAO,QAAQ,CAAC,OAAO,UAAU,OAAO;EAE7C,OAAO;GAAE,MAAM,OAAO;GAAM,UAAU,OAAO;EAAS;CAC1D,QAAQ;EAIJ,OAAO;CACX;AACJ;AAEA,SAAgB,kBAAkB,aAAqB,QAA4B;CAC/E,MAAM,OAAO,kBAAkB,WAAW;CAC1C,GAAG,UAAU,KAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;CACpD,GAAG,cAAc,MAAM,GAAG,KAAK,UAAU,QAAQ,MAAM,CAAC,EAAE,KAAK,OAAO;AAC1E;;AAGA,SAAgB,kBAAkB,aAA2B;CACzD,GAAG,OAAO,kBAAkB,WAAW,GAAG,EAAE,OAAO,KAAK,CAAC;AAC7D;;;;;;;;;;;;AAaA,SAAgB,UAAU,SAAiB,UAAiC;CACxE,IAAI;EACA,MAAM,MAAM,IAAI,IAAI,OAAO;EAG3B,IAAI,WAAW,IAAI;EAEnB,OAAO,IAAI,SAAS;CACxB,QAAQ;EACJ,OAAO;CACX;AACJ;;AAGA,SAAgB,eAAe,kBAAyC;CACpE,IAAI;EAGA,OAFa,IAAI,IAAI,gBAAgB,CAAC,CAAC,SAAS,QAAQ,OAAO,EAExD,KAAQ;CACnB,QAAQ;EACJ,OAAO;CACX;AACJ"}
package/dist/bundle.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type ResourceGraph, type NativeDependency, type RebaseBundleManifest, type RebaseBackendAppConfig } from "@rebasepro/types";
1
+ import { type ResourceGraph, type NativeDependency, type RebaseBundleManifest, type RebaseBundleFunction, type RebaseBackendAppConfig } from "@rebasepro/types";
2
2
  export declare const DEFAULT_BUNDLE_DIR = "dist-bundle";
3
3
  export interface BuildBundleOptions {
4
4
  projectRoot: string;
@@ -89,15 +89,28 @@ export declare function detectNativeDependencies(projectRoot: string, declared:
89
89
  * wasted space and at worst a version conflict. Workspace packages are excluded
90
90
  * too: they are not on the registry the runtime installs from, and the project's
91
91
  * own config package already travels inside the bundle.
92
+ *
93
+ * Throws when a declared range is not something the registry can resolve, or
94
+ * when two manifests declare a name at ranges nothing satisfies. The
95
+ * alternative is a bundle that builds green and fails at the deploy's install
96
+ * step, minutes later and on a machine the developer cannot see. `rebase build`
97
+ * reports both in its own words first; this is the backstop for every other
98
+ * caller, `rebase cloud deploy` included.
92
99
  */
93
100
  export declare function collectDeclaredDependencies(projectRoot: string): Record<string, string>;
94
- /** One `@rebasepro/*` dependency as some package.json in the project declares it. */
101
+ /** One dependency as some package.json in the project declares it. */
95
102
  export interface DeclaredFrameworkDep {
96
103
  name: string;
97
104
  range: string;
98
105
  /** Project-relative package.json it was declared in. */
99
106
  file: string;
100
107
  }
108
+ /** One name two of the bundle's manifests declare at ranges nothing satisfies. */
109
+ export interface DeclaredDepConflict {
110
+ name: string;
111
+ /** The two declarations, in manifest order. */
112
+ declarations: [DeclaredFrameworkDep, DeclaredFrameworkDep];
113
+ }
101
114
  export interface FrameworkDepDrift {
102
115
  /** Declared at a version that can never reach the CLI's own. */
103
116
  behind: DeclaredFrameworkDep[];
@@ -107,7 +120,27 @@ export interface FrameworkDepDrift {
107
120
  * packages against each other.
108
121
  */
109
122
  disagreeing: string[];
123
+ /**
124
+ * Third-party names two bundle manifests declare at ranges no single version
125
+ * satisfies. One of the two wins in `deps.declared` and the loser's code was
126
+ * typechecked against a version the runtime will never install.
127
+ */
128
+ conflicting: DeclaredDepConflict[];
110
129
  }
130
+ /**
131
+ * Names two of the bundle's manifests declare at ranges nothing satisfies.
132
+ *
133
+ * `collectDeclaredDependencies` has to pick one — the app's — and the loser is
134
+ * silent: the bundle tells the managed runtime to install a version the other
135
+ * half of the project was never compiled against. The stock scaffold shipped
136
+ * exactly that (`dotenv ^16` at the root, `^17.4.2` in `backend/`), so every
137
+ * bundle built from a default project asked for dotenv 16.
138
+ *
139
+ * Scoped to the three manifests that feed `deps.declared`, and to their
140
+ * `dependencies`: a `frontend/` that wants a different major of a build tool is
141
+ * not this problem, and `devDependencies` never reach the bundle.
142
+ */
143
+ export declare function detectDeclaredDepConflicts(projectRoot: string): DeclaredDepConflict[];
111
144
  /**
112
145
  * Find `@rebasepro/*` dependencies pinned to a version older than this CLI.
113
146
  *
@@ -126,6 +159,24 @@ export interface FrameworkDepDrift {
126
159
  * one nobody looks at.
127
160
  */
128
161
  export declare function detectFrameworkDepDrift(projectRoot: string, cliVersion: string): FrameworkDepDrift;
162
+ /**
163
+ * The `[start, end)` ranges of `//` and block comments in JavaScript source.
164
+ *
165
+ * `normalizeEsmSpecifiers` scans emitted output with a regular expression, and a
166
+ * regular expression cannot tell a specifier from a specifier somebody wrote
167
+ * about. The stock template ships one: `config/resources.ts` documents
168
+ * `import { media } from "../resources";` inside a docblock, tsc preserves the
169
+ * comment, and every `rebase build` of an untouched scaffold printed
170
+ * `⚠ 1 import(s) could not be resolved`. A warning that is wrong on the default
171
+ * project teaches people to ignore the warning that predicts a boot failure.
172
+ *
173
+ * Strings are tracked only so a `//` inside one — every `http://` in the tree —
174
+ * does not open a comment that swallows the rest of the line. A `'` or `"` span
175
+ * ends at the newline as well as at its closing quote: a JavaScript string
176
+ * cannot contain a raw newline, so a quote that reaches one was never a string
177
+ * (a regex literal's, most likely) and the mistake must not run on.
178
+ */
179
+ export declare function commentSpans(source: string): Array<[number, number]>;
129
180
  /**
130
181
  * Rewrite relative import specifiers in emitted JavaScript so Node can resolve them.
131
182
  *
@@ -169,6 +220,33 @@ export declare function findUnusedServerEntry(projectRoot: string, functionsDir:
169
220
  /**
170
221
  * Compile and assemble a bundle.
171
222
  */
223
+ /** What `composeBundleManifest` needs: every input, already resolved. */
224
+ export interface ComposeManifestInput {
225
+ runtimeRange: string;
226
+ builtAgainst: string;
227
+ schemaVersion: string;
228
+ appName: string;
229
+ entry: RebaseBundleManifest["entry"];
230
+ collectionSlugs: string[];
231
+ functions: RebaseBundleFunction[];
232
+ nativeModules: NativeDependency[];
233
+ declaresStorageAuthorize: boolean;
234
+ resources: ResourceGraph | undefined;
235
+ declaredDeps: Record<string, string>;
236
+ build: RebaseBundleManifest["build"];
237
+ }
238
+ /**
239
+ * The bundle manifest, as a pure function of its inputs.
240
+ *
241
+ * Separated from `buildBundle` so the SHAPE a host reads can be pinned by a
242
+ * test without compiling a project: `tooling/contracts/bundle-manifest.json`
243
+ * is written from this with fixed inputs, and the control plane's own tests
244
+ * read that file to prove they consume what the CLI emits. That contract is
245
+ * what was missing when buckets moved from `storage.sources` into `resources`
246
+ * and the control plane went on reading the old field for two weeks — every
247
+ * declared bucket arriving as nothing, with both suites green.
248
+ */
249
+ export declare function composeBundleManifest(input: ComposeManifestInput): RebaseBundleManifest;
172
250
  export declare function buildBundle(options: BuildBundleOptions): Promise<BuildBundleResult>;
173
251
  /** Default install target: what the published runtime image runs. */
174
252
  export declare const VENDOR_TARGET_OS = "linux";
@@ -339,4 +417,3 @@ export declare function buildStaticBundle(options: {
339
417
  manifest: RebaseBundleManifest;
340
418
  fileCount: number;
341
419
  };
342
- export declare function resolveCliVersion(): string;
package/dist/cli.d.ts CHANGED
@@ -1 +1,23 @@
1
+ /**
2
+ * The flags that mean the same thing wherever they appear.
3
+ *
4
+ * `--debug` is in here even though this file does not act on it: it is
5
+ * implemented one level up, in `bin/rebase.js`, which prints the stack of a
6
+ * failed command instead of the one-line message. That is exactly why it went
7
+ * missing from the help for as long as it did — the flag lived in the
8
+ * launcher, the Options block lived here, and neither knew about the other.
9
+ * Declaring it here puts both under one spec, which `cli.test.ts` holds
10
+ * against the printed help.
11
+ *
12
+ * Exported for that test.
13
+ */
14
+ export declare const ROOT_FLAGS: {
15
+ readonly "--version": BooleanConstructor;
16
+ readonly "--help": BooleanConstructor;
17
+ readonly "--debug": BooleanConstructor;
18
+ readonly "-v": "--version";
19
+ readonly "-h": "--help";
20
+ };
1
21
  export declare function entry(args: string[]): Promise<void>;
22
+ /** The global help. Exported so its Options block can be held against {@link ROOT_FLAGS}. */
23
+ export declare function printHelp(): void;
@@ -61,3 +61,16 @@ export declare function resolveResetPasswordArgs(rawArgs: string[]): {
61
61
  email?: string;
62
62
  password?: string;
63
63
  };
64
+ /**
65
+ * A password for a reset that was not given one.
66
+ *
67
+ * This used to be a constant, and `--help` printed it as the default. Reset is
68
+ * the documented way back into an account nobody can sign in to — an admin,
69
+ * usually — so the recovery path set every such account to a fixed string that
70
+ * ships inside a public repository and a published npm package, and left it
71
+ * there until somebody remembered to change it.
72
+ *
73
+ * base64url of 18 random bytes: 24 characters, ~144 bits, no shell-quoting
74
+ * hazard, and nothing that reads like a placeholder somebody might keep.
75
+ */
76
+ export declare function generatePassword(): string;
@@ -11,16 +11,6 @@ export interface ActionHelp {
11
11
  /** Anything a caller gets wrong more than once. */
12
12
  notes?: string[];
13
13
  }
14
- /**
15
- * Flags every cloud command accepts, documented once.
16
- *
17
- * Excluded from the spec comparison below — they are merged in by
18
- * `parseCloudArgs` for every command in the family, so repeating them per entry
19
- * would be nine copies of the same four lines.
20
- */
21
- export declare const GLOBAL_HELP_FLAGS: Array<[string, string]>;
22
- /** Flag names that `parseCloudArgs` adds to every command in the family. */
23
- export declare const GLOBAL_SPEC_KEYS: Set<string>;
24
14
  export declare const ACTION_HELP: Record<string, ActionHelp>;
25
15
  /** Print one action's page — human, or its JSON description when piped. */
26
16
  export declare function printActionHelp(entry: ActionHelp): void;
@@ -1,3 +1,40 @@
1
+ /**
2
+ * Every flag `rebase cloud login` accepts.
3
+ *
4
+ * Hoisted out of the `arg` call so one declaration serves the parser,
5
+ * `action-help.ts`'s page for this command, and the test that holds the two to
6
+ * each other — the same arrangement `DEPLOY_FLAGS` and `CREATE_PROJECT_FLAGS`
7
+ * already use.
8
+ */
9
+ export declare const LOGIN_FLAGS: {
10
+ readonly "--email": StringConstructor;
11
+ readonly "--password": StringConstructor;
12
+ readonly "-e": "--email";
13
+ };
14
+ /**
15
+ * Where credentials may come from, in the order this command prefers them.
16
+ *
17
+ * The environment is the non-interactive route, and it exists so `--password`
18
+ * does not have to be. A password written as an argument is in the shell's
19
+ * history file and in the process table for as long as the command runs, and
20
+ * neither is something this CLI can redact after the fact — the same reasoning
21
+ * `rls-check` states for its connection string, which carries one too.
22
+ *
23
+ * There is no machine token yet, so CI genuinely does need a human's password;
24
+ * `REBASE_CLOUD_PASSWORD` is how a secret store hands it over without it
25
+ * appearing on a command line.
26
+ */
27
+ export declare const PASSWORD_ENV = "REBASE_CLOUD_PASSWORD";
28
+ export declare const EMAIL_ENV = "REBASE_CLOUD_EMAIL";
29
+ /**
30
+ * Whether this line put a password in the shell's history.
31
+ *
32
+ * Exported for its test: the warning is the whole feature, so "it warns exactly
33
+ * when the flag was used" is the thing worth pinning.
34
+ */
35
+ export declare function passwordOnTheCommandLine(args: {
36
+ "--password"?: string;
37
+ }): boolean;
1
38
  export declare function loginCommand(rawArgs: string[]): Promise<void>;
2
39
  export declare function logoutCommand(rawArgs: string[]): Promise<void>;
3
40
  export declare function whoamiCommand(rawArgs: string[]): Promise<void>;
@@ -8,6 +8,19 @@ export declare function getContextOrg(url: string): string | undefined;
8
8
  /** Mark a host as the active context (called on login). */
9
9
  export declare function setCurrentContext(url: string): void;
10
10
  export declare function resolveCloudUrl(rawArgs: string[]): string;
11
+ /**
12
+ * Refuse to run a control-plane command in a directory linked straight at a
13
+ * backend.
14
+ *
15
+ * The alternative — quietly using the default control plane — would be worse
16
+ * than the bug it replaces: the user asked for `https://api.example.com` and
17
+ * would get an answer about `app.rebase.pro` with nothing saying so. Naming the
18
+ * link, and both ways out of it, is the whole message.
19
+ *
20
+ * `--project` and `--url` are the ways out, so a line carrying either passes
21
+ * through: both say, explicitly, which control-plane subject the caller means.
22
+ */
23
+ export declare function refuseDirectLink(rawArgs: string[]): void;
11
24
  export type CloudClient = ReturnType<typeof createRebaseClient>;
12
25
  /**
13
26
  * Build an SDK client bound to a control-plane host, backed by the on-disk
@@ -200,7 +213,7 @@ export declare function emit<T>(human: () => void, json: JsonArg<T>): void;
200
213
  * `json: unknown` accepted a function without complaint, and four call sites in
201
214
  * `resources.ts` passed `() => ({ … })` — mirroring the human argument beside
202
215
  * it, which *is* a thunk. In JSON mode `printJson` then stringified a function,
203
- * which is `undefined`. So `rebase cloud resources`, `resources set` and two
216
+ * which is `undefined`. So `rebase cloud compute`, `compute set` and two
204
217
  * cluster commands printed the single word `undefined` on every piped or
205
218
  * `--json` run — and since this family forces JSON mode off a TTY, that is
206
219
  * every scripted use of them.
@@ -224,7 +237,64 @@ export type JsonArg<T> = T extends (...args: never[]) => unknown ? never : T;
224
237
  * `env` shipped this shape first, alone; this generalises it so every group
225
238
  * answers the same way.
226
239
  */
227
- export declare function emitHelp(command: string, actions: string[], human: () => void, extra?: Record<string, unknown>): void;
240
+ export declare function emitHelp(command: string, actions: Array<string | HelpAction>, human: () => void, extra?: Record<string, unknown>): void;
241
+ /**
242
+ * One action on a group's help page.
243
+ *
244
+ * The page used to exist twice: a hand-formatted template literal for a
245
+ * terminal, and a bare list of action WORDS for everything else. So the two
246
+ * answers to `--help` were not the same answer — piped, `rebase cloud env
247
+ * --help` said `["list","set","unset","reveal","pull"]` and not one description,
248
+ * not one flag, and not the sentence about build-time variables that is the
249
+ * whole reason the page exists. This family forces JSON mode off a TTY, so that
250
+ * was every scripted and every agent-driven read of it.
251
+ *
252
+ * One description, rendered twice.
253
+ */
254
+ export interface HelpAction {
255
+ /** The action word: `set`, `backup restore`. */
256
+ action: string;
257
+ /** The usage tail after the word: `KEY=VALUE`, `<domain>`, `[-y]`. */
258
+ args?: string;
259
+ /** One line: what it does. */
260
+ description: string;
261
+ /** Flags this action takes of its own, `[flag, description]`. */
262
+ flags?: Array<[string, string]>;
263
+ /** The heading it sits under, on a page that groups its actions. */
264
+ section?: string;
265
+ }
266
+ /** A whole group page — `rebase cloud env --help`. */
267
+ export interface GroupHelp {
268
+ /** The command words, no leading `rebase`: `cloud env`, or `cloud` itself. */
269
+ command: string;
270
+ /** The title's tail: "Environment variables". */
271
+ title: string;
272
+ actions: HelpAction[];
273
+ /** Options that belong to the group rather than to one action. */
274
+ options?: Array<[string, string]>;
275
+ /** Closing paragraphs — the things a reader gets wrong more than once. */
276
+ notes?: string[];
277
+ }
278
+ /**
279
+ * Flags every cloud command accepts, documented once.
280
+ *
281
+ * Lives here rather than in `action-help.ts` because both help renderers print
282
+ * it and `action-help` imports this module, not the other way round.
283
+ */
284
+ export declare const GLOBAL_HELP_FLAGS: Array<[string, string]>;
285
+ /**
286
+ * Flag names `parseCloudArgs` adds to every command in the family.
287
+ *
288
+ * Derived from `GLOBAL_CLOUD_FLAGS` rather than listed again — a second copy is
289
+ * how a global gains a spelling that the collision sweep does not know about.
290
+ * `--debug` is the one addition: `bin/rebase.js` takes it off `process.argv`
291
+ * itself, so no cloud spec declares it and every command still accepts it.
292
+ */
293
+ export declare const GLOBAL_SPEC_KEYS: Set<string>;
294
+ /**
295
+ * Print a group's page — the human one, and the same content as JSON when piped.
296
+ */
297
+ export declare function printGroupHelp(page: GroupHelp): void;
228
298
  /**
229
299
  * Print a warning (+ optional hint) — in every output mode, always to stderr.
230
300
  *
@@ -347,6 +417,24 @@ export declare function parseCloudArgs<S extends arg.Spec>(opts: {
347
417
  flags: arg.Result<S & typeof GLOBAL_CLOUD_FLAGS>;
348
418
  positionals: string[];
349
419
  };
420
+ /**
421
+ * Refuse an action word its group does not dispatch. Returns for `undefined`,
422
+ * which is every group's default action.
423
+ *
424
+ * The groups that switch on their action already do this in a `default:` case.
425
+ * The groups written as a chain of `if (action === "x") return …` did not: a
426
+ * word that matched nothing fell out of the chain into the *default* action, so
427
+ * `rebase cloud storage creat` listed the buckets and exited 0, and
428
+ * `rebase cloud billing usage` printed the account. Reporting a typo as a
429
+ * successful run of a different command is the failure mode this family exists
430
+ * to not have — an agent branching on the exit code learns nothing, and a person
431
+ * reads the output of a command they did not ask for.
432
+ *
433
+ * One spelling, so the code is `unknown_command` everywhere rather than the
434
+ * default `"error"` half of them used, and the hint always names the group's own
435
+ * `--help` rather than the index page.
436
+ */
437
+ export declare function requireKnownAction(group: string, action: string | undefined, known: readonly string[]): void;
350
438
  /**
351
439
  * `--timeout <seconds>` as milliseconds, or `fallbackMs` when it was not given.
352
440
  *