@rebasepro/cli 0.13.0 → 0.13.1-canary.g06dbe5b

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 (36) hide show
  1. package/dist/bundle.d.ts +4 -3
  2. package/dist/commands/api-keys.d.ts +51 -0
  3. package/dist/commands/auth.d.ts +62 -0
  4. package/dist/commands/cloud/context.d.ts +56 -7
  5. package/dist/commands/cloud/databases.d.ts +39 -0
  6. package/dist/commands/cloud/debug.d.ts +1 -0
  7. package/dist/commands/cloud/deployments.d.ts +22 -0
  8. package/dist/commands/cloud/domains.d.ts +10 -0
  9. package/dist/commands/cloud/env.d.ts +51 -0
  10. package/dist/commands/cloud/extensions.d.ts +21 -0
  11. package/dist/commands/cloud/orgs.d.ts +1 -0
  12. package/dist/commands/cloud/projects.d.ts +29 -0
  13. package/dist/commands/cloud/resources.d.ts +14 -0
  14. package/dist/commands/cloud/settings.d.ts +1 -0
  15. package/dist/commands/dev.d.ts +17 -6
  16. package/dist/commands/eject.d.ts +42 -0
  17. package/dist/commands/init.d.ts +67 -0
  18. package/dist/commands/skills.d.ts +81 -0
  19. package/dist/fold-static.d.ts +47 -0
  20. package/dist/index.es.js +1440 -415
  21. package/dist/index.es.js.map +1 -1
  22. package/dist/manifest.d.ts +16 -1
  23. package/dist/telemetry/payload.d.ts +1 -1
  24. package/dist/utils/args.d.ts +76 -0
  25. package/dist/utils/collection-drift.d.ts +27 -0
  26. package/dist/utils/project.d.ts +20 -0
  27. package/package.json +7 -7
  28. package/templates/eject/Dockerfile +29 -4
  29. package/templates/eject/backend/src/index.ts +49 -5
  30. package/templates/eject/docker-compose.custom.yml +13 -5
  31. package/templates/overlays/baas/backend/tsconfig.json +6 -1
  32. package/templates/template/.env.example +13 -4
  33. package/templates/template/backend/functions/hello.ts +8 -4
  34. package/templates/template/backend/tsconfig.json +6 -1
  35. package/templates/template/config/package.json +1 -0
  36. package/templates/template/docker-compose.yml +4 -4
@@ -54,7 +54,22 @@ export declare function manifestExists(projectRoot: string): boolean;
54
54
  * available behaviour.
55
55
  */
56
56
  export declare function loadManifest(projectRoot: string): LoadedManifest;
57
- /** Write a manifest, with a trailing newline so it plays well with other tools. */
57
+ /**
58
+ * Write a manifest, with a trailing newline so it plays well with other tools.
59
+ *
60
+ * **Every key on disk survives.** This used to emit exactly `$schema`, `rebase`
61
+ * and `apps`, so a rewrite deleted the rest of the file — and two commands with
62
+ * no visible relationship to either key rewrite it: `rebase eject` and
63
+ * `rebase apps init --force`. A repository that had committed
64
+ * `"telemetry": false` lost its opt-out, and a multi-bucket project lost its
65
+ * whole `storage` block, in a commit whose stated change was `runtime: custom`.
66
+ *
67
+ * So: the caller's manifest wins for what it models, the file supplies the rest.
68
+ * `storage` and `telemetry` fall back to the file because the callers that
69
+ * synthesize a manifest (`apps init --force`) cannot know them — they are
70
+ * authored, not inferred — and unknown top-level keys are copied verbatim
71
+ * rather than listed, since a hand-listed set loses the next key too.
72
+ */
58
73
  export declare function writeManifest(projectRoot: string, manifest: RebaseProjectManifest): string;
59
74
  /** Find the single backend app, if this repository declares one. */
60
75
  export declare function findBackendApp(manifest: RebaseProjectManifest): {
@@ -31,7 +31,7 @@
31
31
  */
32
32
  export declare const TELEMETRY_SCHEMA_VERSION = 1;
33
33
  /** The events the CLI reports. Closed set — a name not listed cannot be sent. */
34
- export type TelemetryEventName = "cli.init" | "cli.dev" | "cli.deploy" | "cli.schema_generate" | "cli.db_push" | "cli.error";
34
+ export type TelemetryEventName = "cli.init" | "cli.dev" | "cli.deploy" | "cli.schema" | "cli.db" | "cli.error";
35
35
  /** A single non-free-text value. */
36
36
  export type TelemetryValue = string | number | boolean;
37
37
  export type TelemetryEvent = {
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Argument parsing for the commands that take positional arguments.
3
+ *
4
+ * Every command in the small group used to parse its own line the same way:
5
+ * `arg(spec, { argv: rawArgs.slice(4), permissive: true })`, then read `_[0]`
6
+ * and `_[1]`. Both halves of that are wrong in a way that costs data.
7
+ *
8
+ * - **`permissive: true` turns an unknown flag into a positional.** `arg`
9
+ * pushes an undeclared flag into `_` as a bare token, so `_[1]` is whatever
10
+ * came second on the line, flag or not. `rebase auth reset-password
11
+ * bob@example.com --debug` set Bob's password to the literal `--debug` — and
12
+ * `--debug` is what `bin/rebase.js` prints after *every* failure as the thing
13
+ * to re-run with, so the single most likely next keystroke after a failed
14
+ * reset was the one that reset the account to a two-word string.
15
+ * - **`slice(4)` assumes the command words are at fixed indices.** They are
16
+ * not: a flag before the command shifts everything, so `rebase --debug auth
17
+ * reset-password bob@example.com NewPass1!` read the email as
18
+ * `reset-password` and the password as `bob@example.com`.
19
+ *
20
+ * So: parse the *whole* line — `rawArgs` is `process.argv` — against a spec
21
+ * strictly, with no permissive mode. `arg` then consumes every flag wherever it
22
+ * appears and rejects the ones nobody declared, which leaves `_` holding the
23
+ * command words followed by the real positionals, in order and at a known
24
+ * offset. An unrecognised flag becomes an error naming the command's help,
25
+ * which is the only safe answer: the alternative is guessing that it was meant
26
+ * as a value.
27
+ *
28
+ * `commands/cloud/index.ts` resolves its positionals against its own spec for
29
+ * the same reason; this is that idea for the commands whose positionals are
30
+ * credentials rather than resource names.
31
+ */
32
+ import arg from "arg";
33
+ /**
34
+ * Flags accepted on top of whatever a command declares.
35
+ *
36
+ * `--debug` is read by `bin/rebase.js` off `process.argv` and never by a
37
+ * command, but it has to be *declared* somewhere or strict parsing rejects the
38
+ * exact flag the CLI tells people to add. `--help`/`-h` are answered by each
39
+ * command's dispatcher before any work happens.
40
+ */
41
+ export declare const GLOBAL_COMMAND_FLAGS: {
42
+ readonly "--debug": BooleanConstructor;
43
+ readonly "--help": BooleanConstructor;
44
+ readonly "-h": "--help";
45
+ };
46
+ export interface ParsedCommand<S extends arg.Spec> {
47
+ /** The declared flags, as `arg` resolved them. */
48
+ flags: arg.Result<S>;
49
+ /** What is left after the command words — never a flag. */
50
+ positionals: string[];
51
+ /** `--help` or `-h` appeared anywhere on the line. */
52
+ help: boolean;
53
+ }
54
+ /** Did the line ask for help? Answered before dispatch, never by a handler. */
55
+ export declare function wantsHelp(rawArgs: string[]): boolean;
56
+ /**
57
+ * Resolve a command's flags and positionals from the full `process.argv`.
58
+ *
59
+ * `commandWords` is how many words name the command itself — 2 for
60
+ * `auth reset-password`, 1 for `start` — and is applied to the *parsed*
61
+ * positionals rather than to `argv`, so a flag placed before the command no
62
+ * longer shifts them.
63
+ *
64
+ * `command` names the command in error messages, e.g. `auth reset-password`.
65
+ *
66
+ * Throws on an unknown flag, on a positional that looks like a flag, and on
67
+ * more positionals than the command takes. `bin/rebase.js` turns each into a
68
+ * one-line `✗ …` and exit 1.
69
+ */
70
+ export declare function parseCommandArgs<S extends arg.Spec>({ spec, rawArgs, commandWords, command, maxPositionals }: {
71
+ spec: S;
72
+ rawArgs: string[];
73
+ commandWords: number;
74
+ command: string;
75
+ maxPositionals?: number;
76
+ }): ParsedCommand<S>;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Would `loadCollectionsFromDirectory` load this file?
3
+ *
4
+ * Mirrors that loader's own `isCollectionFile` plus its flat (non-recursive)
5
+ * scan. `relativePath` is relative to the collections directory, as `fs.watch`
6
+ * reports it.
7
+ */
8
+ export declare function isLoadedCollectionFile(relativePath: string): boolean;
9
+ /**
10
+ * Does this collection source declare anything a SQL toolchain would own?
11
+ *
12
+ * The fallback order matches `resolveDataSource`: an explicit `engine` wins,
13
+ * and a collection that only names a `dataSource` is resolved as if the key
14
+ * were the engine — which is what that function does when no registry is
15
+ * available, and the CLI has none. An engine nobody recognises counts as
16
+ * relational, for the reason `isRelationalCollection` gives.
17
+ *
18
+ * A file declaring several collections is SQL-affecting if *any* of them is.
19
+ */
20
+ export declare function declaresRelationalCollection(rawSource: string): boolean;
21
+ /**
22
+ * Can this edit have changed the generated SQL schema?
23
+ *
24
+ * Answers `true` when it cannot tell — an unreadable file is a reason to warn,
25
+ * not a reason to go quiet.
26
+ */
27
+ export declare function affectsSqlSchema(collectionsDir: string, relativePath: string): boolean;
@@ -34,6 +34,26 @@ export declare function findFrontendDir(projectRoot: string): string | null;
34
34
  * Find the .env file. Checks the project root first, then backend.
35
35
  */
36
36
  export declare function findEnvFile(projectRoot: string): string | null;
37
+ /**
38
+ * Read the project's `.env` into a plain object.
39
+ *
40
+ * One reader, because there were four: `dotenv` in `start`, a hand-rolled
41
+ * `indexOf("=")` loop in `api-keys`, a single-key regex in `auth`, and its own
42
+ * splitting in `cloud env`. `dotenv` is a declared dependency of this package,
43
+ * so the other three existed for no reason and disagreed with the correct one
44
+ * on the two things people actually write in a `.env`:
45
+ *
46
+ * - `export KEY=value`, which the hand-rolled parser keyed as
47
+ * `export KEY` — so the command reported the key as unset while it was
48
+ * right there in the file;
49
+ * - `KEY=value # comment`, whose comment travelled into the value and then
50
+ * into an `Authorization` header, coming back as a 401 with nothing
51
+ * pointing at the cause.
52
+ *
53
+ * Returns `{}` when the project has no `.env`, so callers can treat "absent"
54
+ * and "empty" alike.
55
+ */
56
+ export declare function readEnvFile(projectRoot: string): Record<string, string>;
37
57
  /**
38
58
  * Resolve a binary from the project's node_modules/.bin.
39
59
  * Checks backend, root, parent monorepo root, then falls back to PATH.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/cli",
3
- "version": "0.13.0",
3
+ "version": "0.13.1-canary.g06dbe5b",
4
4
  "description": "Developer tools for Rebase projects",
5
5
  "main": "./dist/index.es.js",
6
6
  "module": "./dist/index.es.js",
@@ -31,12 +31,12 @@
31
31
  "execa": "^9.6.1",
32
32
  "inquirer": "14.0.2",
33
33
  "jiti": "^2.7.0",
34
- "@rebasepro/agent-skills": "0.13.0",
35
- "@rebasepro/client": "0.13.0",
36
- "@rebasepro/codegen": "0.13.0",
37
- "@rebasepro/types": "0.13.0",
38
- "@rebasepro/server": "0.13.0",
39
- "@rebasepro/server-postgres": "0.13.0"
34
+ "@rebasepro/agent-skills": "0.13.1-canary.g06dbe5b",
35
+ "@rebasepro/client": "0.13.1-canary.g06dbe5b",
36
+ "@rebasepro/codegen": "0.13.1-canary.g06dbe5b",
37
+ "@rebasepro/server-postgres": "0.13.1-canary.g06dbe5b",
38
+ "@rebasepro/server": "0.13.1-canary.g06dbe5b",
39
+ "@rebasepro/types": "0.13.1-canary.g06dbe5b"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "^26.1.2",
@@ -7,7 +7,12 @@
7
7
  # not reach this project.
8
8
  #
9
9
  # Assumes pnpm, which is what `rebase init` scaffolds a workspace for. On npm,
10
- # swap the three pnpm lines below for `npm ci` and `npm run build --workspace`.
10
+ # every line below that mentions pnpm has to change — the `pnpm install` and
11
+ # `pnpm --filter … run build` lines become `npm ci` and
12
+ # `npm run build --workspace <package>`, `CMD ["pnpm", "start"]` becomes
13
+ # `CMD ["npm", "start"]`, and the two `COPY` lines that name `pnpm-lock.yaml`
14
+ # and `pnpm-workspace.yaml` have to name `package-lock.json` instead. Those
15
+ # COPYs fail first, before any of the build lines runs.
11
16
  #
12
17
  # Build context: the project root (where pnpm-workspace.yaml lives)
13
18
  # Usage:
@@ -33,15 +38,31 @@ WORKDIR /app
33
38
  # Copy workspace root files first (cache-friendly layer)
34
39
  COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
35
40
 
36
- # Copy workspace packages
41
+ # Copy workspace packages. Every workspace the lockfile has an importer for,
42
+ # so `--frozen-lockfile` below sees the same set of packages it was written from.
37
43
  COPY backend ./backend
38
44
  COPY config ./config
45
+ # {{#frontend}}
46
+ COPY frontend ./frontend
47
+ # {{/frontend}}
48
+
49
+ # The entrypoint reads rebase.json at boot — it is where this project declares
50
+ # its storage buckets, so that the platform, the console and this process all
51
+ # read one list. An image without it boots believing nothing was declared, and
52
+ # every upload lands in the wrong bucket or 501s.
53
+ COPY rebase.json ./
39
54
 
40
55
  # Install all deps (including devDependencies for build)
41
56
  RUN pnpm install --frozen-lockfile
42
57
 
43
- # Build config first, then backend
58
+ # Build config first, then the rest: both the frontend and the backend import it.
44
59
  RUN pnpm --filter "*-config" run build
60
+ # {{#frontend}}
61
+ # The image serves the built site itself (see the `serveSPA` call in
62
+ # backend/src/index.ts), so the frontend is built here rather than deployed
63
+ # separately. Vite reads no API URL: same origin, same container.
64
+ RUN pnpm --filter "*-frontend" run build
65
+ # {{/frontend}}
45
66
  RUN pnpm --filter "*-backend" run build
46
67
 
47
68
  # Prune dev dependencies for a smaller runtime
@@ -63,10 +84,14 @@ RUN addgroup -g 1001 rebase && adduser -u 1001 -G rebase -s /bin/sh -D rebase
63
84
  WORKDIR /app
64
85
 
65
86
  # Copy only production artifacts
66
- COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml /app/.npmrc ./
87
+ COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml /app/.npmrc /app/rebase.json ./
67
88
  COPY --from=builder /app/node_modules ./node_modules
68
89
  COPY --from=builder /app/backend ./backend
69
90
  COPY --from=builder /app/config ./config
91
+ # {{#frontend}}
92
+ # The built site only — its sources and devDependencies stay in the builder.
93
+ COPY --from=builder /app/frontend/dist ./frontend/dist
94
+ # {{/frontend}}
70
95
 
71
96
  # Create uploads directory
72
97
  RUN mkdir -p /app/backend/uploads && chown -R rebase:rebase /app
@@ -3,12 +3,15 @@ import { cors } from "hono/cors";
3
3
  import { secureHeaders } from "hono/secure-headers";
4
4
  import { getRequestListener } from "@hono/node-server";
5
5
  import { createServer } from "http";
6
+ import fs from "fs";
6
7
  import path from "path";
7
8
  import { fileURLToPath } from "url";
8
9
  import {
9
10
  initializeRebaseBackend,
10
11
  installShutdownHandlers,
12
+ // {{#frontend}}
11
13
  serveSPA,
14
+ // {{/frontend}}
12
15
  HonoEnv,
13
16
  listenWithPortRetry,
14
17
  cleanupDevPortFile,
@@ -17,10 +20,14 @@ import {
17
20
  logger
18
21
  } from "@rebasepro/server";
19
22
  import { createPostgresDatabaseConnection, createPostgresAdapter } from "@rebasepro/server-postgres";
23
+ // {{#collections}}
20
24
  import { enums, relations, tables } from "./schema.generated.js";
25
+ // {{/collections}}
21
26
  import { storageAuthorize } from "../../config/storage.js";
22
27
  import { env } from "./env.js";
28
+ // {{#collections}}
23
29
  import usersCollection from "../../config/collections/users.js";
30
+ // {{/collections}}
24
31
 
25
32
  const __filename = fileURLToPath(import.meta.url);
26
33
  const __dirname = path.dirname(__filename);
@@ -86,21 +93,44 @@ async function startServer() {
86
93
  const PORT = env.PORT;
87
94
  const server = createServer(getRequestListener(app.fetch));
88
95
 
96
+ // `backend/crons` holds the scheduled jobs — nightly backups among them.
97
+ // Passed only when the directory exists, because a configured `cronsDir`
98
+ // with nothing in it mounts the cron routes and warns at every boot.
99
+ // Sibling-relative like `functionsDir`: both compile alongside this file, so
100
+ // the same path is right from source and from `backend/dist/backend/src`.
101
+ const cronsDir = path.resolve(__dirname, "../crons");
102
+
89
103
  const backend = await initializeRebaseBackend({
104
+ // {{#collections}}
90
105
  collectionsDir: path.resolve(__dirname, "../../config/collections"),
106
+ // {{/collections}}
107
+ // {{^collections}}
108
+ // No `collectionsDir`: this project declares no collections in code, so
109
+ // the server derives them from the live database schema at boot —
110
+ // exactly what the managed runtime did for it.
111
+ // {{/collections}}
91
112
  functionsDir: path.resolve(__dirname, "../functions"),
113
+ cronsDir: fs.existsSync(cronsDir) ? cronsDir : undefined,
92
114
  server,
93
115
  app,
94
116
  database: createPostgresAdapter({
95
117
  connection: db,
118
+ // {{#collections}}
96
119
  schema: { tables,
97
120
  enums,
98
121
  relations },
122
+ // {{/collections}}
99
123
  adminConnectionString: env.ADMIN_CONNECTION_STRING || databaseUrl,
100
124
  connectionString
101
125
  }),
102
126
  auth: {
127
+ // {{#collections}}
103
128
  collection: usersCollection,
129
+ // {{/collections}}
130
+ // {{^collections}}
131
+ // No users collection is declared here, so auth falls back to its
132
+ // own default users table — the same fallback a headless bundle gets.
133
+ // {{/collections}}
104
134
  jwtSecret,
105
135
  accessExpiresIn: env.JWT_ACCESS_EXPIRES_IN,
106
136
  refreshExpiresIn: env.JWT_REFRESH_EXPIRES_IN,
@@ -173,9 +203,13 @@ pass: env.SMTP_PASS! }
173
203
  //
174
204
  // `requireAuth` answers 401 without a valid token; `requireAdmin` answers
175
205
  // 403 without the `admin` role and must follow `requireAuth`. Note that
176
- // `c.get("driver")` — the RLS-scoped driver is only set inside the Rebase
177
- // routers, so out here reach for `rebase.dataAsAdmin`, which **bypasses
178
- // RLS** and therefore belongs behind one of those guards.
206
+ // `c.get("driver")` — the driver carrying the caller's identity is only
207
+ // set inside the Rebase routers, so out here reach for
208
+ // `rebase.dataAsAdmin`. That one is **admin-scoped, not an RLS bypass**: it
209
+ // runs as `{ uid: "service", roles: ["admin"] }` and your policies still
210
+ // apply, evaluated against that identity — which is exactly an admin's
211
+ // reach, and therefore belongs behind one of those guards. (`rebase.sql()`
212
+ // is the real bypass: owner connection, no policies.)
179
213
 
180
214
  // ─── Health check ─────────────────────────────────────────────
181
215
  // Deliberately public: an orchestrator's probe has no token to send.
@@ -189,10 +223,20 @@ pass: env.SMTP_PASS! }
189
223
  }, status);
190
224
  });
191
225
 
192
- // Serve the frontend in production
226
+ // {{#frontend}}
227
+ // Serve the frontend in production.
228
+ //
229
+ // Four levels up, not two: in production this file runs compiled, from
230
+ // `backend/dist/backend/src`. The paths above are the same in both modes
231
+ // because `config/` is compiled alongside this file; `frontend/dist` is not
232
+ // in the compiled tree at all, so it stays where the repository put it.
233
+ // `serveSPA` only warns when the path is wrong, which is why the Dockerfile
234
+ // that builds this image also copies `frontend` — verify a mount by
235
+ // fetching `/`, never by reading the log.
193
236
  if (isProduction) {
194
- serveSPA(app, { frontendPath: path.join(__dirname, "../../frontend/dist") });
237
+ serveSPA(app, { frontendPath: path.resolve(__dirname, "../../../../frontend/dist") });
195
238
  }
239
+ // {{/frontend}}
196
240
 
197
241
  if (!isProduction) {
198
242
  // Dev mode: retry the next port if the current one is in use
@@ -25,7 +25,7 @@ services:
25
25
  image: postgres:18-alpine
26
26
  restart: unless-stopped
27
27
  environment:
28
- POSTGRES_USER: rebase
28
+ POSTGRES_USER: rebase_app
29
29
  POSTGRES_PASSWORD: ${DATABASE_PASSWORD:-changeme}
30
30
  POSTGRES_DB: rebase
31
31
  ports:
@@ -33,7 +33,7 @@ services:
33
33
  volumes:
34
34
  - postgres_data:/var/lib/postgresql
35
35
  healthcheck:
36
- test: ["CMD-SHELL", "pg_isready -U rebase -d rebase"]
36
+ test: ["CMD-SHELL", "pg_isready -U rebase_app -d rebase"]
37
37
  interval: 5s
38
38
  timeout: 5s
39
39
  retries: 10
@@ -48,12 +48,20 @@ services:
48
48
  - "${PORT:-3001}:3001"
49
49
  env_file: .env
50
50
  environment:
51
- DATABASE_URL: postgresql://rebase:${DATABASE_PASSWORD:-changeme}@db:5432/rebase?options=-c%20search_path=public
52
- ADMIN_CONNECTION_STRING: postgresql://rebase:${DATABASE_PASSWORD:-changeme}@db:5432/rebase?options=-c%20search_path=public
51
+ DATABASE_URL: postgresql://rebase_app:${DATABASE_PASSWORD:-changeme}@db:5432/rebase?options=-c%20search_path=public
52
+ ADMIN_CONNECTION_STRING: postgresql://rebase_app:${DATABASE_PASSWORD:-changeme}@db:5432/rebase?options=-c%20search_path=public
53
53
  NODE_ENV: production
54
54
  PORT: "3001"
55
+ # {{#frontend}}
55
56
  # Your entrypoint serves the built frontend itself (see the `serveSPA`
56
- # call in backend/src/index.ts), so this is one container, same origin.
57
+ # call in backend/src/index.ts, and the frontend build stage in the
58
+ # Dockerfile), so this is one container, same origin.
59
+ # {{/frontend}}
60
+ # {{^frontend}}
61
+ # This image serves the API only — there is no frontend workspace in this
62
+ # project — so CORS_ORIGINS has to list the origins your clients are
63
+ # served from.
64
+ # {{/frontend}}
57
65
  CORS_ORIGINS: ${CORS_ORIGINS:?set CORS_ORIGINS to the origin you browse to}
58
66
  # A durable named volume, which is the case this flag acknowledges.
59
67
  STORAGE_PATH: /uploads
@@ -21,5 +21,10 @@
21
21
  //
22
22
  // No `rootDir`: it would have to be `.` for both, and letting tsc infer the
23
23
  // common root gets there without a second place to keep in sync.
24
- "include": ["src/**/*", "functions/**/*"]
24
+ //
25
+ // `crons/` too: `rebase build` compiles it through a generated tsconfig, but
26
+ // an ejected project builds with *this* file, so leaving it out meant an
27
+ // ejected server had no compiled jobs to load and silently stopped running
28
+ // every schedule.
29
+ "include": ["src/**/*", "functions/**/*", "crons/**/*"]
25
30
  }
@@ -10,7 +10,7 @@
10
10
  # sslmode=disable matches the local docker-compose database, which has no TLS;
11
11
  # schema tooling (atlas) would otherwise default to requiring SSL. Remove it
12
12
  # when pointing at a managed/cloud database.
13
- DATABASE_URL=postgresql://rebase:changeme@localhost:5432/rebase?options=-c%20search_path=public&sslmode=disable
13
+ DATABASE_URL=postgresql://rebase_app:changeme@localhost:5432/rebase?options=-c%20search_path=public&sslmode=disable
14
14
  # DATABASE_URL=mongodb://localhost:27017/rebase
15
15
  #
16
16
  # Seeing "SSL is not enabled on the server"? Something in your environment
@@ -22,7 +22,7 @@ DATABASE_URL=postgresql://rebase:changeme@localhost:5432/rebase?options=-c%20sea
22
22
  # downgrade SSL for you when you have asked for it, not even on localhost:
23
23
  # quietly ignoring a security setting is worse than a clear failure. To opt out
24
24
  # explicitly for a local database, say so:
25
- # DATABASE_URL=postgresql://rebase:changeme@localhost:5432/rebase?options=-c%20search_path=public&sslmode=disable
25
+ # DATABASE_URL=postgresql://rebase_app:changeme@localhost:5432/rebase?options=-c%20search_path=public&sslmode=disable
26
26
 
27
27
  # Separate admin connection string for migrations and schema operations (optional)
28
28
  # Falls back to DATABASE_URL if not set
@@ -77,9 +77,18 @@ FRONTEND_URL=http://localhost:5173
77
77
  # GOOGLE_CLIENT_ID=
78
78
 
79
79
  # ── Frontend (Vite) ──────────────────────────────────────────────────────────
80
- # For production builds. In dev, `rebase dev` injects the backend port it
80
+ # Leave this EMPTY unless your API lives on a different origin than the site.
81
+ #
82
+ # Empty means "same origin as the page", which is what a deployed build wants:
83
+ # the frontend is served by the backend it talks to, and a same-origin bundle
84
+ # keeps working when you add a custom domain. A value here is baked in at BUILD
85
+ # time — `vite build` reads this file (frontend/vite.config.ts sets
86
+ # `envDir: ".."`), so a stray `http://localhost:3001` follows the bundle into
87
+ # production and points every request at the machine that ran the build.
88
+ #
89
+ # In dev you need nothing here either: `rebase dev` injects the backend port it
81
90
  # actually bound, overriding this — see the PORT note above.
82
- VITE_API_URL=http://localhost:3001
91
+ VITE_API_URL=
83
92
  # VITE_GOOGLE_CLIENT_ID=
84
93
 
85
94
  # ── Storage ───────────────────────────────────────────────────────────────────
@@ -30,10 +30,14 @@ import { defineFunction, requireAuth, requireAdmin } from "@rebasepro/server";
30
30
  * slot over `app.use("/*", requireAuth)`: `use()` only covers routes declared
31
31
  * *below* it, so a route appended later above it is silently unprotected.
32
32
  *
33
- * `rebase.dataAsAdmin` gives you admin-level access to your data and
34
- * **bypasses RLS** use it only for trusted admin work. For request-scoped /
35
- * RLS-scoped data access, use c.get("user") and c.get("driver"), which carry
36
- * the caller's identity. (`rebase` also exposes auth, storage, email.)
33
+ * `rebase.dataAsAdmin` gives you admin-level access to your data: it runs as
34
+ * `{ uid: "service", roles: ["admin"] }`, which is **admin-scoped, not an RLS
35
+ * bypass** your policies are still evaluated, just against that identity. So
36
+ * `policy.serverContext()` is false for it (that arm means "no uid at all"),
37
+ * and anything an `admin` user can reach, it can reach too. For request-scoped
38
+ * data access use c.get("user") and c.get("driver"), which carry the caller's
39
+ * identity. `rebase.sql()` is the real bypass — owner connection, no policies.
40
+ * (`rebase` also exposes auth, storage, email.)
37
41
  */
38
42
  export default defineFunction((app, { rebase }) => {
39
43
  void rebase; // available for dataAsAdmin/auth/storage/email — see commented usage below
@@ -36,5 +36,10 @@
36
36
  // unnoticed until runtime. The inferred common root is already the project
37
37
  // root because of `../config`, so this adds no `rootDir` and moves nothing
38
38
  // that dist/ already contains.
39
- "include": ["src/**/*", "functions/**/*", "../config/**/*", "drizzle.config.ts"]
39
+ //
40
+ // `crons/` for the same reason, and for one more: `rebase build` compiles it
41
+ // through a generated tsconfig, but an ejected project builds with *this*
42
+ // file — so leaving crons out meant an ejected server had no compiled jobs to
43
+ // load and stopped running every schedule, nightly backups included.
44
+ "include": ["src/**/*", "functions/**/*", "crons/**/*", "../config/**/*", "drizzle.config.ts"]
40
45
  }
@@ -16,6 +16,7 @@
16
16
  "@rebasepro/types": "workspace:*"
17
17
  },
18
18
  "devDependencies": {
19
+ "@types/node": "^20.19.41",
19
20
  "typescript": "^5.9.2"
20
21
  },
21
22
  "exports": {
@@ -33,7 +33,7 @@ services:
33
33
  image: postgres:18-alpine
34
34
  restart: unless-stopped
35
35
  environment:
36
- POSTGRES_USER: rebase
36
+ POSTGRES_USER: rebase_app
37
37
  POSTGRES_PASSWORD: ${DATABASE_PASSWORD:-changeme}
38
38
  POSTGRES_DB: rebase
39
39
  # Published so `rebase db push` can reach it from the host. Remove this
@@ -47,7 +47,7 @@ services:
47
47
  # The runtime must not start before the database can answer, or its first
48
48
  # boot fails on a connection refused and the container restarts for no
49
49
  # reason a reader would understand.
50
- test: ["CMD-SHELL", "pg_isready -U rebase -d rebase"]
50
+ test: ["CMD-SHELL", "pg_isready -U rebase_app -d rebase"]
51
51
  interval: 5s
52
52
  timeout: 5s
53
53
  retries: 10
@@ -75,8 +75,8 @@ services:
75
75
  ports:
76
76
  - "${PORT:-3001}:3001"
77
77
  environment:
78
- DATABASE_URL: postgresql://rebase:${DATABASE_PASSWORD:-changeme}@db:5432/rebase?options=-c%20search_path=public
79
- ADMIN_CONNECTION_STRING: postgresql://rebase:${DATABASE_PASSWORD:-changeme}@db:5432/rebase?options=-c%20search_path=public
78
+ DATABASE_URL: postgresql://rebase_app:${DATABASE_PASSWORD:-changeme}@db:5432/rebase?options=-c%20search_path=public
79
+ ADMIN_CONNECTION_STRING: postgresql://rebase_app:${DATABASE_PASSWORD:-changeme}@db:5432/rebase?options=-c%20search_path=public
80
80
  JWT_SECRET: ${JWT_SECRET:?set JWT_SECRET in .env — `rebase init` generates one}
81
81
  REBASE_SERVICE_KEY: ${REBASE_SERVICE_KEY:?set REBASE_SERVICE_KEY in .env — `rebase init` generates one}
82
82
  NODE_ENV: production