@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
@@ -78,6 +78,72 @@ export declare function resolveTsx(projectRoot: string): string | null;
78
78
  * error description string when it appears corrupted.
79
79
  */
80
80
  export declare function validateTsxInstallation(tsxBinPath: string): string | null;
81
+ /**
82
+ * The one sentence a command says when the project's dependencies are missing.
83
+ *
84
+ * Six commands each had their own wording for the same state, and none of them
85
+ * named the remedy:
86
+ *
87
+ * ✗ Could not find CLI entry point for @rebasepro/server-postgres.
88
+ * ✗ Could not find tsx binary.
89
+ * ✗ Could not find tsx binary for backend.
90
+ *
91
+ * All three mean "you have not installed yet" — `getActiveBackendPlugin` has
92
+ * already read the driver out of `backend/package.json` by the time the first
93
+ * one fires, so the package is declared and simply not on disk, and `tsx` is a
94
+ * devDependency of every scaffold. But they read as *Rebase* being broken, and
95
+ * they name an internal path or a binary the developer never asked for rather
96
+ * than the command that fixes it. That is the whole of the failure someone sees
97
+ * on a fresh clone, where `node_modules/` is the one thing a checkout does not
98
+ * carry.
99
+ *
100
+ * So: one sentence, naming the package manager this project uses and the
101
+ * directory to run it in — the working directory is usually neither.
102
+ */
103
+ export declare function dependenciesNotInstalled(projectRoot: string): string;
104
+ /**
105
+ * Print {@link dependenciesNotInstalled} and exit 1.
106
+ *
107
+ * For the five commands that report and stop. `db.ts` throws instead, because
108
+ * its caller adds the `✗` and the exit itself.
109
+ */
110
+ export declare function exitDependenciesNotInstalled(projectRoot: string): never;
111
+ /** One problem inside a refusal, when the command found several. */
112
+ export interface JsonFailureIssue {
113
+ /** Where it is — a config path, a manifest key, a file. */
114
+ path?: string;
115
+ message: string;
116
+ }
117
+ /**
118
+ * The refusal envelope, for a caller that asked for JSON.
119
+ *
120
+ * Same shape as the cloud family's `fail()` — `{ error: { message, code, hint } }`
121
+ * — because there is one CLI and a caller should not have to know which half of
122
+ * it answered. `code` is what a caller branches on and is never absent; the
123
+ * cloud family's reasoning about that applies here unchanged. `issues` carries
124
+ * the per-problem detail a human reader gets as a bullet list, because a
125
+ * refusal that says "2 problem(s)" and does not say which two is not
126
+ * machine-readable in any useful sense.
127
+ *
128
+ * On **stdout**, like every other `--json` result: the contract those commands
129
+ * make is that stdout holds one JSON value, and a caller that pipes stdout to a
130
+ * parser must get a parseable refusal rather than an empty stream and a
131
+ * human sentence it never sees. That contract used to hold for exactly one
132
+ * failure of each command — the one that goes through `requireProjectRoot` —
133
+ * while every other exit of the *same* command wrote human text to stderr and
134
+ * left stdout empty. So `rebase status --json` was parseable outside a project
135
+ * and unparseable inside a broken one, which is the case a caller actually has
136
+ * to handle.
137
+ *
138
+ * The flag is read off `process.argv` because the failure happens inside a
139
+ * helper the command calls before it has parsed anything. Coarse — a literal
140
+ * `--json` as some other flag's value would count — and worth it: the failure
141
+ * mode of being coarse is a JSON error where a human one was wanted, and the
142
+ * failure mode of not doing it is an unparseable stream.
143
+ */
144
+ export declare function failAsJson(message: string, code: string, hint?: string, issues?: JsonFailureIssue[]): never;
145
+ /** Did the command line ask for machine-readable output? */
146
+ export declare function wantsJsonOutput(argv?: readonly string[]): boolean;
81
147
  /**
82
148
  * Require the project root or exit with a helpful error.
83
149
  */
@@ -0,0 +1 @@
1
+ export declare function reportSpawnFailure(error: unknown): void;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * The one command the user probably meant, or nothing.
3
+ *
4
+ * Tight on purpose. A suggestion that is wrong is worse than none: it sends
5
+ * someone to read the help for a command that was never the answer, and on a
6
+ * family like `db` — where `push` and `pull` are one edit apart — a loose
7
+ * threshold would confidently propose the destructive neighbour of what they
8
+ * typed. So: one edit for a short word, two for a long one, and a prefix match
9
+ * always counts (`rebase cloud dep` for `deploy`).
10
+ */
11
+ export declare function suggestCommand(typed: string, known: readonly string[]): string | undefined;
12
+ /**
13
+ * The line itself, without printing it — so callers that must answer in JSON
14
+ * (the cloud family, whose `fail()` writes a machine-readable envelope) use the
15
+ * same words as the ones that print.
16
+ *
17
+ * `family` is empty for the top level: `rebase --help`, not `rebase --help`.
18
+ */
19
+ export declare function unknownCommandMessage(typed: string | undefined, known: readonly string[], family?: string): string;
20
+ /**
21
+ * Print the line to stderr and exit 1.
22
+ *
23
+ * stderr, not stdout: a mistyped command is a failure, and the families with a
24
+ * `--json` mode guarantee that stdout holds only their envelope.
25
+ */
26
+ export declare function unknownCommand(typed: string | undefined, known: readonly string[], family?: string): never;
@@ -0,0 +1,10 @@
1
+ /** The CLI's version, or `"unknown"` when it cannot be read. Memoized. */
2
+ export declare function cliVersion(): string;
3
+ /**
4
+ * `rebase-cli/0.17.3` — sent on every control-plane request.
5
+ *
6
+ * The control plane needs it to answer `CLI_TOO_OLD` with a minimum version
7
+ * instead of failing somewhere further in with a shape error. A client that
8
+ * does not identify itself can only ever be refused generically.
9
+ */
10
+ export declare function cliUserAgent(): string;
package/package.json CHANGED
@@ -1,18 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/cli",
3
- "version": "0.17.3",
3
+ "version": "0.18.1",
4
4
  "description": "Developer tools for Rebase projects",
5
- "main": "./dist/index.es.js",
6
- "module": "./dist/index.es.js",
7
- "types": "./dist/index.d.ts",
8
- "type": "module",
9
- "source": "src/index.ts",
10
- "bin": {
11
- "rebase": "bin/rebase.js"
12
- },
13
- "publishConfig": {
14
- "access": "public"
15
- },
16
5
  "keywords": [
17
6
  "cli",
18
7
  "create-rebase-app",
@@ -22,8 +11,30 @@
22
11
  "typescript",
23
12
  "postgresql"
24
13
  ],
25
- "author": "rebase.pro",
14
+ "homepage": "https://rebase.pro",
15
+ "bugs": {
16
+ "url": "https://github.com/rebasepro/rebase/issues"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/rebasepro/rebase.git",
21
+ "directory": "packages/cli"
22
+ },
26
23
  "license": "MIT",
24
+ "engines": {
25
+ "node": ">=22.22.0"
26
+ },
27
+ "author": "rebase.pro",
28
+ "main": "./dist/index.es.js",
29
+ "module": "./dist/index.es.js",
30
+ "types": "./dist/index.d.ts",
31
+ "type": "module",
32
+ "bin": {
33
+ "rebase": "bin/rebase.js"
34
+ },
35
+ "publishConfig": {
36
+ "access": "public"
37
+ },
27
38
  "dependencies": {
28
39
  "arg": "^5.0.2",
29
40
  "chalk": "^5.6.2",
@@ -32,12 +43,12 @@
32
43
  "inquirer": "14.0.2",
33
44
  "jiti": "^2.7.0",
34
45
  "pg": "^8.22.0",
35
- "@rebasepro/agent-skills": "0.17.3",
36
- "@rebasepro/codegen": "0.17.3",
37
- "@rebasepro/server": "0.17.3",
38
- "@rebasepro/types": "0.17.3",
39
- "@rebasepro/client": "0.17.3",
40
- "@rebasepro/server-postgres": "0.17.3"
46
+ "@rebasepro/agent-skills": "0.18.1",
47
+ "@rebasepro/codegen": "0.18.1",
48
+ "@rebasepro/client": "0.18.1",
49
+ "@rebasepro/server-postgres": "0.18.1",
50
+ "@rebasepro/types": "0.18.1",
51
+ "@rebasepro/server": "0.18.1"
41
52
  },
42
53
  "devDependencies": {
43
54
  "@types/node": "^26.1.2",
@@ -55,21 +66,18 @@
55
66
  "exports": {
56
67
  ".": {
57
68
  "types": "./dist/index.d.ts",
58
- "import": "./dist/index.es.js"
69
+ "import": "./dist/index.es.js",
70
+ "default": "./dist/index.es.js"
59
71
  }
60
72
  },
61
- "repository": {
62
- "type": "git",
63
- "url": "https://github.com/rebasepro/rebase.git",
64
- "directory": "packages/cli"
65
- },
66
73
  "optionalDependencies": {
67
- "@electric-sql/pglite": "^0.5.6",
68
- "@electric-sql/pglite-pgvector": "^0.0.7",
69
- "@electric-sql/pglite-socket": "^0.2.9"
74
+ "@electric-sql/pglite": "0.5.6",
75
+ "@electric-sql/pglite-pgvector": "0.0.7",
76
+ "@electric-sql/pglite-socket": "0.2.9"
70
77
  },
71
78
  "scripts": {
72
79
  "test": "vitest run",
80
+ "test:watch": "vitest",
73
81
  "test:integration": "vitest run --config vitest.integration.config.ts",
74
82
  "test:e2e": "vitest run --config vitest.e2e.config.ts",
75
83
  "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.json && node ../../tooling/scripts/add-dts-extensions.mjs dist && node ../../tooling/scripts/assert-build-output.mjs",
@@ -3,6 +3,15 @@ import fs from "fs";
3
3
  import path from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import { loadEnv } from "@rebasepro/server";
6
+ // This should be `import { loadEnv, z } from "@rebasepro/server"`, and will be
7
+ // in the first release that publishes that `z`. `loadEnv({ extend })` composes
8
+ // the schema below with the framework's, and a schema built against a second
9
+ // copy of zod fails the framework's instanceof checks and is silently dropped —
10
+ // so taking `z` from the runtime is the only form that is reliably correct.
11
+ // The `@rebasepro/server` version an ejected project pins does not export it
12
+ // yet, and a template may only import what the version it pins publishes
13
+ // (`pnpm check:templates`); an import of a symbol that is not there fails at
14
+ // boot rather than at build. Switch both lines back when the version bumps.
6
15
  import { z } from "zod";
7
16
 
8
17
  const __filename = fileURLToPath(import.meta.url);
@@ -15,11 +15,12 @@ import {
15
15
  HonoEnv,
16
16
  listenWithPortRetry,
17
17
  cleanupDevPortFile,
18
+ initializeDataSources,
19
+ resolveDataSources,
18
20
  resolveStorageSources,
19
21
  logger
20
22
  } from "@rebasepro/server";
21
- import { createPostgresDatabaseConnection, createPostgresAdapter } from "@rebasepro/server-postgres";
22
- import { declaredStorageSources } from "@rebasepro/types";
23
+ import { declaredDataSources, declaredStorageSources } from "@rebasepro/types";
23
24
  // Side-effect import: declaring is what registers, so anything that dropped
24
25
  // this as "unused" would leave the backend with no buckets — silently.
25
26
  import "../../config/resources.js";
@@ -35,10 +36,11 @@ import usersCollection from "../../config/collections/users.js";
35
36
  const __filename = fileURLToPath(import.meta.url);
36
37
  const __dirname = path.dirname(__filename);
37
38
 
38
- // Which buckets this project has, read from the declarations in
39
+ // Which databases and buckets this project has, read from the declarations in
39
40
  // `config/resources.ts`. One declaration site, so this process, the platform
40
41
  // and the frontend all read the same list. A project that declares none gets
41
- // one default source from the plain, unsuffixed S3_*/GCS_* variables.
42
+ // one default source of each, from the plain unsuffixed variables.
43
+ const dataSources = declaredDataSources();
42
44
  const storageSources = declaredStorageSources();
43
45
 
44
46
  // ─── App ─────────────────────────────────────────────────────────────
@@ -84,10 +86,21 @@ app.use("/*", cors({
84
86
 
85
87
  app.use("/*", secureHeaders());
86
88
 
87
- // ─── Database ────────────────────────────────────────────────────────
88
- const databaseUrl = env.DATABASE_URL;
89
-
90
- const { db, pool, connectionString } = createPostgresDatabaseConnection(databaseUrl);
89
+ // ─── Databases ───────────────────────────────────────────────────────
90
+ // One connection per declared database, resolved from `DATABASE_URL` for the
91
+ // default one and `DATABASE_URL__<KEY>` for every other — the same resolver the
92
+ // managed runtime uses, so this entrypoint cannot drift from it.
93
+ //
94
+ // It reads `process.env` rather than the typed `env` above on purpose: the
95
+ // suffixed names belong to sources this project declared, so no fixed schema
96
+ // can list them.
97
+ //
98
+ // Doing this by hand — one `createPostgresDatabaseConnection(env.DATABASE_URL)`
99
+ // — is what this file used to do, and it quietly broke the moment anyone
100
+ // declared a second database: collections routed to it fell back to the default
101
+ // driver and their rows landed in the wrong database, behind a server that
102
+ // looked healthy.
103
+ const resolvedDataSources = resolveDataSources(process.env, dataSources);
91
104
 
92
105
  // ─── Start ───────────────────────────────────────────────────────────
93
106
  async function startServer() {
@@ -102,6 +115,21 @@ async function startServer() {
102
115
  // the same path is right from source and from `backend/dist/backend/src`.
103
116
  const cronsDir = path.resolve(__dirname, "../crons");
104
117
 
118
+ // Open a connection per declared database. Sequential, and a failure on the
119
+ // second closes whatever the first opened rather than leaking it against a
120
+ // server this process is about to abandon.
121
+ const drivers = await initializeDataSources(
122
+ resolvedDataSources,
123
+ // {{#collections}}
124
+ { tables, enums, relations },
125
+ // {{/collections}}
126
+ // {{^collections}}
127
+ // No generated schema: this project declares no collections in code.
128
+ undefined,
129
+ // {{/collections}}
130
+ [__dirname]
131
+ );
132
+
105
133
  const backend = await initializeRebaseBackend({
106
134
  // {{#collections}}
107
135
  collectionsDir: path.resolve(__dirname, "../../config/collections"),
@@ -115,16 +143,11 @@ async function startServer() {
115
143
  cronsDir: fs.existsSync(cronsDir) ? cronsDir : undefined,
116
144
  server,
117
145
  app,
118
- database: createPostgresAdapter({
119
- connection: db,
120
- // {{#collections}}
121
- schema: { tables,
122
- enums,
123
- relations },
124
- // {{/collections}}
125
- adminConnectionString: env.ADMIN_CONNECTION_STRING || databaseUrl,
126
- connectionString
127
- }),
146
+ // One bootstrapper per data source, each registered under its own key —
147
+ // the key is exactly what `collection.dataSource` routes against. Only
148
+ // the default source is handed the generated schema, which describes
149
+ // this project's tables and they live in the default database.
150
+ bootstrappers: drivers.map(driver => driver.bootstrapper),
128
151
  auth: {
129
152
  // {{#collections}}
130
153
  collection: usersCollection,
@@ -275,7 +298,11 @@ pass: env.SMTP_PASS! }
275
298
  // Drains HTTP, stops crons, tears down realtime, then closes the pool.
276
299
  // Guards against double signals and force-exits if shutdown hangs.
277
300
  installShutdownHandlers(backend, {
278
- onCleanup: () => pool.end()
301
+ // Every pool, not just the first: a second database left open holds a
302
+ // connection against the server while this process is meant to be gone.
303
+ onCleanup: async () => {
304
+ await Promise.all(drivers.map(driver => driver.connection.pool?.end()));
305
+ }
279
306
  });
280
307
  }
281
308
 
@@ -16,7 +16,7 @@ export const main = database();
16
16
 
17
17
  /**
18
18
  * The default bucket, bound from the plain unsuffixed storage variables
19
- * (`S3_BUCKET`, `GCS_BUCKET`, `STORAGE_BUCKET`).
19
+ * (`S3_BUCKET`, `GCS_BUCKET`).
20
20
  *
21
21
  * Name more of them to get more — `bucket("media")` reads `S3_BUCKET__MEDIA`,
22
22
  * and so on for every variable that kind uses.
@@ -22,8 +22,12 @@ CREATE POLICY your_table_owner ON your_table
22
22
  `rebase.uid()`, `rebase.roles()` and `rebase.jwt()` are provided by Rebase and read the
23
23
  identity of the authenticated request.
24
24
 
25
- To serve unprotected tables anyway (development only), set
26
- `baas: { unprotectedTables: "serve" }` in `backend/src/index.ts`.
25
+ Serving one anyway is `baas: { unprotectedTables: "serve" }`, and it is passed
26
+ to `initializeRebaseBackend` so it needs a project that owns its entrypoint.
27
+ This scaffold has none: the published runtime boots it. Run `rebase eject` if
28
+ you want that file, and read the flag for what it is — every authenticated
29
+ request runs as one database role, so a table with no policy is handed whole to
30
+ every signed-in caller.
27
31
 
28
32
  ## Run it
29
33
 
@@ -32,24 +36,40 @@ pnpm install
32
36
  pnpm dev
33
37
  ```
34
38
 
35
- - API: `http://localhost:3001/api/data/<table>`
36
- - Docs: `http://localhost:3001/api/swagger`
37
- - Health: `http://localhost:3001/health`
39
+ `rebase dev` prints the URL it bound, and a box with the API, the Swagger path
40
+ and the database it is using. **Read that box**: the port is derived from this
41
+ project's path rather than fixed, so several Rebase projects can run at once,
42
+ and `PORT` in `.env` applies to `rebase start`, not here.
38
43
 
39
- Set `DATABASE_URL` in `.env` to point at your database.
44
+ - API: `<the printed URL>/api/data/<table>` once a table is served
45
+ - Docs: `<the printed URL>/api/swagger` — likewise
46
+ - Health: `<the printed URL>/health`
47
+
48
+ A headless project starts with no tables, so the first two answer
49
+ `404 NO_COLLECTIONS` until you create one and restart — the box `rebase dev`
50
+ prints says the same thing. See [Serving a table](#serving-a-table).
51
+
52
+ There is no database to install: with no `DATABASE_URL` set, `rebase dev` runs a
53
+ managed PostgreSQL for this project, with its data under `.rebase/`. Set
54
+ `DATABASE_URL` in `.env` to point at a database of your own — it always wins.
55
+ `rebase db url` prints whichever one is in use, so it pipes straight into psql.
40
56
 
41
57
  ## Use it from an app
42
58
 
43
59
  ```ts
44
60
  import { createRebaseClient } from "@rebasepro/client";
45
61
 
46
- const rebase = createRebaseClient({ baseUrl: "http://localhost:3001" });
62
+ // The URL `rebase dev` printed. `pnpm example` reads it from .rebase-dev-url
63
+ // for you rather than hardcoding a port.
64
+ const rebase = createRebaseClient({ baseUrl: process.env.REBASE_URL! });
47
65
  const posts = await rebase.data.collection("posts").find();
48
66
  ```
49
67
 
50
68
  ## Adding an admin UI later
51
69
 
52
- Nothing here locks you out of it. Switch `mode` to `"cms"` in
53
- `backend/src/index.ts`, add a `config/collections` directory, and add a frontend
54
- that renders them. See MODULAR-ARCHITECTURE.md in the Rebase repo for the three
55
- adoption modes.
70
+ Nothing here locks you out of it. Add a `config/collections/` directory and a
71
+ frontend that renders them: the backend serves an admin panel once collections
72
+ exist, because the mode is derived from whether any are declared rather than
73
+ set anywhere. There is no `backend/src/index.ts` to edit — the published
74
+ runtime boots this project — and `rebase eject` writes one if you want to own
75
+ the entrypoint.
@@ -15,12 +15,12 @@
15
15
  "@hono/node-server": "^2.0.12",
16
16
  "pg": "^8.11.3",
17
17
  "ws": "^8.16.0",
18
- "dotenv": "^16.0.0",
18
+ "dotenv": "^17.4.2",
19
19
  "zod": "^4.4.3"
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/pg": "^8.6.5",
23
- "@types/node": "^20.10.5",
23
+ "@types/node": "^22.19.4",
24
24
  "@types/ws": "^8.5.10",
25
25
  "tsx": "^4.20.6",
26
26
  "typescript": "^5.9.2"
@@ -2,7 +2,7 @@
2
2
  "compilerOptions": {
3
3
  "target": "ES2022",
4
4
  "module": "ESNext",
5
- "moduleResolution": "node",
5
+ "moduleResolution": "bundler",
6
6
  "lib": ["ES2022"],
7
7
  "outDir": "./dist",
8
8
  "strict": true,
@@ -27,10 +27,10 @@
27
27
  },
28
28
  "dependencies": {
29
29
  "@rebasepro/client": "workspace:*",
30
- "dotenv": "^16.0.0"
30
+ "dotenv": "^17.4.2"
31
31
  },
32
32
  "engines": {
33
- "node": ">=18.0.0"
33
+ "node": ">=22.22.0"
34
34
  },
35
35
  "pnpm": {
36
36
  "onlyBuiltDependencies": [
@@ -38,5 +38,8 @@
38
38
  "sharp",
39
39
  "@ariga/atlas"
40
40
  ]
41
+ },
42
+ "allowScripts": {
43
+ "@ariga/atlas": true
41
44
  }
42
45
  }
@@ -4,6 +4,19 @@ packages:
4
4
  linkWorkspacePackages: true
5
5
  blockExoticSubdeps: false
6
6
  minimumReleaseAge: 0
7
+ # See the note in templates/template/pnpm-workspace.yaml: pnpm 11 reads these
8
+ # here and no longer from `.npmrc`, where npm 12 also warns about them.
9
+ verifyDepsBeforeRun: false
10
+ # Refuse to install on a Node older than `engines.node` in package.json.
11
+ #
12
+ # Off by default, in every package manager, which is why the floor this
13
+ # scaffold declares was enforced by nothing: pnpm 11 installs a project whose
14
+ # `engines.node` is `>=99.0.0` and exits 0, and npm prints EBADENGINE and also
15
+ # exits 0. So a first run on Node 20 said nothing at all, and the first sign was
16
+ # a syntax error from inside a dependency. `engine-strict=true` in npmrc is the
17
+ # same setting for npm.
18
+ engineStrict: true
19
+ confirmModulesPurge: false
7
20
  allowBuilds:
8
21
  esbuild: true
9
22
  sharp: true
@@ -1,2 +1,3 @@
1
1
  # Rebase AI Rules
2
2
  Please refer to and follow the instructions defined in [ai-instructions.md](./ai-instructions.md).
3
+ Install the Rebase skills for this assistant: `rebase skills install --agent cursor`.
@@ -4,9 +4,17 @@
4
4
  # ╚══════════════════════════════════════════════════════════════════════════════╝
5
5
 
6
6
  # ── Database ──────────────────────────────────────────────────────────────────
7
- # Database connection string (required)
8
- # You can use PostgreSQL (postgresql://) or MongoDB (mongodb:// or mongodb+srv://).
9
- # The backend will automatically detect the database type.
7
+ # DATABASE_URL: unset means the managed database; set means yours.
8
+ #
9
+ # Leave it unset and `rebase dev` starts a managed PostgreSQL for this project,
10
+ # with its data under .rebase/ — no Docker, nothing to install. Set it and that
11
+ # database wins over the managed one, always: PostgreSQL (postgresql://) or
12
+ # MongoDB (mongodb:// or mongodb+srv://), detected from the scheme.
13
+ #
14
+ # `rebase init` writes your project's `.env` with this line commented out, which
15
+ # is what puts a new project on the managed database. `rebase db url` prints
16
+ # whichever one is in use.
17
+ #
10
18
  # sslmode=disable matches the local docker-compose database, which has no TLS;
11
19
  # schema tooling (atlas) would otherwise default to requiring SSL. Remove it
12
20
  # when pointing at a managed/cloud database.
@@ -24,6 +32,15 @@ DATABASE_URL=postgresql://rebase_app:changeme@localhost:5432/rebase?options=-c%2
24
32
  # explicitly for a local database, say so:
25
33
  # DATABASE_URL=postgresql://rebase_app:changeme@localhost:5432/rebase?options=-c%20search_path%3Dpublic&sslmode=disable
26
34
 
35
+ # The password for the compose database, and the one inside the DATABASE_URL
36
+ # above. `rebase init` generates it and writes both into `.env`, so they cannot
37
+ # disagree; docker-compose.yml falls back to `changeme` when it is unset.
38
+ #
39
+ # Commented out on purpose: `init` writes its own line above, and a second
40
+ # assignment here would win — dotenv and compose both take the last one, so an
41
+ # uncommented default would silently replace the generated password.
42
+ # DATABASE_PASSWORD=changeme
43
+
27
44
  # Separate admin connection string for migrations and schema operations (optional)
28
45
  # Falls back to DATABASE_URL if not set
29
46
  # ADMIN_CONNECTION_STRING=postgresql://postgres:your-password@localhost:5432/rebase
@@ -53,9 +70,42 @@ JWT_SECRET=
53
70
  JWT_ACCESS_EXPIRES_IN=1h
54
71
  JWT_REFRESH_EXPIRES_IN=30d
55
72
 
73
+ # Does the data API demand a token before it looks at anything? Defaults to
74
+ # `true`. Set `false` for a backend a public website reads — row-level security
75
+ # still decides which rows come back, this only removes the gate in front of it,
76
+ # so a collection with no `select` rule still returns nothing.
77
+ # AUTH_REQUIRE=false
78
+
56
79
  # ── Registration ──────────────────────────────────────────────────────────────
57
80
  ALLOW_REGISTRATION=true
58
81
 
82
+ # The hard kill switch. `ALLOW_REGISTRATION=false` still admits the first
83
+ # account into an empty table — it has to, or a fresh database can never
84
+ # produce the signed-in caller that bootstrapping an admin needs. This closes
85
+ # that window too, so pair it with the first-admin variables below or the
86
+ # deployment has no way in at all. docker-compose.yml defaults it to `true`.
87
+ # DISABLE_SELF_REGISTRATION=true
88
+
89
+ # ── The first admin account ───────────────────────────────────────────────────
90
+ # Outside production the first account to register is promoted to admin, which
91
+ # is what makes `rebase dev` one command. In production that window is closed:
92
+ # a deployment is answering on a hostname before you have typed anything, so a
93
+ # first-come-first-admin form is a race the operator can lose.
94
+ #
95
+ # So production names its first admin instead. The runtime creates the account
96
+ # once, while the user table is empty, and does nothing on every boot after
97
+ # that. docker-compose.yml requires both variables, and `rebase init` wrote
98
+ # them here with a generated password.
99
+ #
100
+ # `rebase dev` ignores both and says so at boot: locally the first account to
101
+ # register is still the way in, and seeding here would spend that window before
102
+ # you opened the app.
103
+ #
104
+ # Change the email to yours. Sign in, then change the password — this file is
105
+ # plain text on disk.
106
+ REBASE_ADMIN_EMAIL=
107
+ REBASE_ADMIN_PASSWORD=
108
+
59
109
  # ── Email (optional — required for password reset / verification) ─────────────
60
110
  # SMTP_HOST=smtp.example.com
61
111
  # SMTP_PORT=587
@@ -70,7 +120,14 @@ ALLOW_REGISTRATION=true
70
120
  # EMAIL_LOGO_URL=https://yourapp.com/logo.png
71
121
 
72
122
  # ── URLs ──────────────────────────────────────────────────────────────────────
73
- # Canonical frontend URL (used in password-reset / verification emails)
123
+ # Canonical frontend URL. It is the base of every emailed link (welcome,
124
+ # password reset, verification) and one of the two things CORS is derived from.
125
+ #
126
+ # `rebase dev` OVERRIDES this with the port it actually starts the frontend on,
127
+ # so you do not need to keep it in step during development — the value below is
128
+ # only a sensible default for the tools that read the file directly. Set it to
129
+ # your real site for a deployment; in production the server refuses to boot with
130
+ # neither this nor CORS_ORIGINS.
74
131
  FRONTEND_URL=http://localhost:5173
75
132
 
76
133
  # Allowed CORS origins in production (comma-separated)
@@ -138,11 +195,15 @@ VITE_API_URL=
138
195
  # interface and pass it directly in your backend config.
139
196
 
140
197
  # --- Several buckets ------------------------------------------------------
141
- # Declare them in rebase.json, then configure each from the SAME variable names
142
- # carrying its own suffix. The default source takes no suffix, so everything
143
- # above keeps working and a single-bucket project declares nothing.
198
+ # Declare them in config/resources.ts, then configure each from the SAME
199
+ # variable names carrying its own suffix. The default source takes no suffix, so
200
+ # everything above keeps working and a single-bucket project declares nothing.
144
201
  #
145
- # rebase.json: "storage": { "media": { "engine": "s3" } }
202
+ # config/resources.ts: export const media = bucket("media", { engine: "s3" });
203
+ #
204
+ # Then `rebase resources --write` regenerates rebase.resources.json, which is
205
+ # what readers that cannot run your code — the build, the control plane — see.
206
+ # `rebase status` prints each bucket with the variables it is actually reading.
146
207
  #
147
208
  # STORAGE_TYPE__MEDIA=s3
148
209
  # S3_BUCKET__MEDIA=my-app-media
@@ -173,3 +234,25 @@ VITE_API_URL=
173
234
  # Falls back to false. Useful for databases with application-only roles or managed setups.
174
235
  # DISABLE_DB_ROLE_SWITCHING=true
175
236
 
237
+ # ── Collection config strictness (optional) ───────────────────────────────────
238
+ # What boot does with a key in your collections that this version does not read.
239
+ # A key it knows *moved* is always fatal; this governs the ones it simply does
240
+ # not recognise, which are usually a typo and occasionally deliberate metadata.
241
+ # warn log it and boot (the default)
242
+ # error refuse to boot — worth turning on in CI
243
+ # off say nothing
244
+ # REBASE_STRICT_COLLECTION_CONFIG=error
245
+
246
+ # ── Self-hosting (docker compose) ─────────────────────────────────────────────
247
+ # The runtime image tag the compose file runs: `rebasepro/server:${REBASE_VERSION}`.
248
+ # `rebase init` pins it to the version it scaffolded against, so a project does
249
+ # not silently move to a new runtime; `latest` is only the fallback when it is
250
+ # unset. To upgrade, change this and restart — your bundle is untouched.
251
+ REBASE_VERSION=latest
252
+
253
+ # What boot does about versioned migrations in `backend/migrations`.
254
+ # ensure create missing tables and columns from your collections (the default)
255
+ # migrate run the migration files, then ensure
256
+ # off neither
257
+ # REBASE_MIGRATE_ON_BOOT=ensure
258
+
@@ -1,2 +1,3 @@
1
1
  # Rebase AI Rules
2
2
  Please refer to and follow the instructions defined in [ai-instructions.md](../ai-instructions.md).
3
+ Install the Rebase skills for this assistant: `rebase skills install --agent copilot`.
@@ -0,0 +1,11 @@
1
+ {
2
+ "mcpServers": {
3
+ "rebase": {
4
+ "command": "npx",
5
+ "args": ["-y", "@rebasepro/mcp"],
6
+ "env": {
7
+ "REBASE_PROJECT_DIR": "."
8
+ }
9
+ }
10
+ }
11
+ }