@rebasepro/cli 0.7.0 → 0.9.0

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.
@@ -35,6 +35,21 @@ export declare function resolveLocalBin(projectRoot: string, binName: string): s
35
35
  * Resolve the tsx binary. Checks backend node_modules first, then root.
36
36
  */
37
37
  export declare function resolveTsx(projectRoot: string): string | null;
38
+ /**
39
+ * Validate that a resolved tsx binary actually has an intact installation.
40
+ *
41
+ * `resolveLocalBin` only checks whether `node_modules/.bin/tsx` (a symlink)
42
+ * exists. If the pnpm content-addressable store was cleaned or a previous
43
+ * install was interrupted, the symlink can exist while critical files inside
44
+ * the tsx package (e.g. `dist/preflight.cjs`) are missing — causing a
45
+ * confusing MODULE_NOT_FOUND error at runtime.
46
+ *
47
+ * This function follows the symlink, walks up to find the tsx package root
48
+ * (`package.json` with `name: "tsx"`), and verifies that `dist/preflight.cjs`
49
+ * is present. Returns `null` when the installation looks healthy, or an
50
+ * error description string when it appears corrupted.
51
+ */
52
+ export declare function validateTsxInstallation(tsxBinPath: string): string | null;
38
53
  /**
39
54
  * Require the project root or exit with a helpful error.
40
55
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/cli",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "description": "Developer tools for Rebase projects",
5
5
  "main": "./dist/index.es.js",
6
6
  "module": "./dist/index.es.js",
@@ -13,12 +13,6 @@
13
13
  "publishConfig": {
14
14
  "access": "public"
15
15
  },
16
- "scripts": {
17
- "test": "vitest run",
18
- "test:e2e": "vitest run --config vitest.e2e.config.ts",
19
- "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.json",
20
- "clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
21
- },
22
16
  "keywords": [
23
17
  "cli",
24
18
  "create-rebase-app",
@@ -31,17 +25,17 @@
31
25
  "author": "rebase.pro",
32
26
  "license": "MIT",
33
27
  "dependencies": {
34
- "@rebasepro/agent-skills": "workspace:*",
35
- "@rebasepro/sdk-generator": "workspace:*",
36
- "@rebasepro/server-core": "workspace:*",
37
- "@rebasepro/server-postgresql": "workspace:*",
38
- "@rebasepro/types": "workspace:*",
39
28
  "arg": "^5.0.2",
40
29
  "chalk": "^5.6.2",
41
30
  "dotenv": "^17.4.2",
42
31
  "execa": "^9.6.1",
43
32
  "inquirer": "14.0.2",
44
- "jiti": "^2.7.0"
33
+ "jiti": "^2.7.0",
34
+ "@rebasepro/agent-skills": "0.9.0",
35
+ "@rebasepro/sdk-generator": "0.9.0",
36
+ "@rebasepro/server-core": "0.9.0",
37
+ "@rebasepro/types": "0.9.0",
38
+ "@rebasepro/server-postgresql": "0.9.0"
45
39
  },
46
40
  "devDependencies": {
47
41
  "@types/node": "^25.9.3",
@@ -67,10 +61,10 @@
67
61
  "url": "https://github.com/rebasepro/rebase.git",
68
62
  "directory": "packages/cli"
69
63
  },
70
- "pnpm": {
71
- "onlyBuiltDependencies": [
72
- "esbuild",
73
- "sharp"
74
- ]
64
+ "scripts": {
65
+ "test": "vitest run",
66
+ "test:e2e": "vitest run --config vitest.e2e.config.ts",
67
+ "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.json",
68
+ "clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
75
69
  }
76
- }
70
+ }
@@ -9,6 +9,10 @@
9
9
  # The backend will automatically detect the database type.
10
10
  DATABASE_URL=postgresql://rebase:changeme@localhost:5432/rebase?options=-c%20search_path=public
11
11
  # DATABASE_URL=mongodb://localhost:27017/rebase
12
+ #
13
+ # Local Postgres without SSL? If you see "SSL is not enabled on the server",
14
+ # append &sslmode=disable to the connection string, e.g.:
15
+ # DATABASE_URL=postgresql://rebase:changeme@localhost:5432/rebase?options=-c%20search_path=public&sslmode=disable
12
16
 
13
17
  # Separate admin connection string for migrations and schema operations (optional)
14
18
  # Falls back to DATABASE_URL if not set
@@ -14,12 +14,12 @@ ENV PATH="$PNPM_HOME:$PATH"
14
14
  RUN corepack enable
15
15
 
16
16
  # Native dependencies for bcrypt, pg, etc.
17
- RUN apk add --no-cache python3 make g++
17
+ RUN apk add --no-cache python3 make g++ curl ca-certificates
18
18
 
19
19
  WORKDIR /app
20
20
 
21
21
  # Copy workspace root files first (cache-friendly layer)
22
- COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
22
+ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
23
23
 
24
24
  # Copy workspace packages
25
25
  COPY backend ./backend
@@ -41,6 +41,7 @@ FROM node:22-alpine AS runtime
41
41
  ENV PNPM_HOME="/pnpm"
42
42
  ENV PATH="$PNPM_HOME:$PATH"
43
43
  ENV NODE_ENV=production
44
+ ENV CI=true
44
45
 
45
46
  RUN corepack enable
46
47
 
@@ -50,7 +51,7 @@ RUN addgroup -g 1001 rebase && adduser -u 1001 -G rebase -s /bin/sh -D rebase
50
51
  WORKDIR /app
51
52
 
52
53
  # Copy only production artifacts
53
- COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml ./
54
+ COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml /app/.npmrc ./
54
55
  COPY --from=builder /app/node_modules ./node_modules
55
56
  COPY --from=builder /app/backend ./backend
56
57
  COPY --from=builder /app/config ./config
@@ -1,6 +1,4 @@
1
- import { Hono } from "hono";
2
- import type { HonoEnv } from "@rebasepro/server-core";
3
- import { rebase } from "@rebasepro/server-core";
1
+ import { defineFunction } from "@rebasepro/server-core";
4
2
 
5
3
  /**
6
4
  * Example custom function route.
@@ -12,41 +10,47 @@ import { rebase } from "@rebasepro/server-core";
12
10
  * Call from the client SDK:
13
11
  * const result = await client.call("functions/hello", { name: "World" });
14
12
  *
15
- * This is a standard Hono app use any Hono middleware,
16
- * define any HTTP methods, access the request/response directly.
13
+ * Authored with `defineFunction`, which hands you a pre-typed Hono app
14
+ * (so `c.get("user")` / `c.get("driver")` are typed) and the `rebase`
15
+ * singleton via the injected context — use any Hono middleware, define any
16
+ * HTTP methods, access the request/response directly.
17
17
  *
18
- * The `rebase` singleton gives you admin-level access to all
19
- * app-scoped services (data, auth, storage, email) from anywhere.
20
- * For request-scoped / RLS-scoped access, use c.get("user") and c.get("driver").
18
+ * `rebase.dataAsAdmin` gives you admin-level access to your data and
19
+ * **bypasses RLS** use it only for trusted admin work. For request-scoped /
20
+ * RLS-scoped data access, use c.get("user") and c.get("driver"), which carry
21
+ * the caller's identity. (`rebase` also exposes auth, storage, email.)
21
22
  */
22
- const app = new Hono<HonoEnv>();
23
+ export default defineFunction((app, { rebase }) => {
24
+ void rebase; // available for dataAsAdmin/auth/storage/email — see commented usage below
23
25
 
24
- app.post("/", async (c) => {
25
- const body = await c.req.json().catch(() => ({}));
26
- const user = c.get("user");
26
+ app.post("/", async (c) => {
27
+ const body = await c.req.json().catch(() => ({}));
28
+ const user = c.get("user");
27
29
 
28
- const userId = (user && typeof user === "object" && "userId" in user)
29
- ? user.userId
30
- : "anonymous";
30
+ const userId = (user && typeof user === "object" && "userId" in user)
31
+ ? user.userId
32
+ : "anonymous";
31
33
 
32
- // Access any Rebase service just import and use:
33
- // await rebase.email?.send({
34
- // to: "admin@example.com",
35
- // subject: "Function called",
36
- // html: `<p>Hello from ${userId}!</p>`,
37
- // });
38
- //
39
- // const authors = await rebase.data.authors.find({ limit: 5 });
34
+ // Access any Rebase service via the injected `rebase`:
35
+ // await rebase.email.send({
36
+ // to: "admin@example.com",
37
+ // subject: "Function called",
38
+ // html: `<p>Hello from ${userId}!</p>`,
39
+ // });
40
+ //
41
+ // Admin-scoped data (bypasses RLS trusted work only):
42
+ // const authors = await rebase.dataAsAdmin.authors.find({ limit: 5 });
43
+ // For user-scoped data (RLS applies), use the request-scoped driver
44
+ // (c.get("driver")), which carries the caller's identity.
40
45
 
41
- return c.json({
42
- message: `Hello, ${body.name || "World"}!`,
43
- user: userId
46
+ return c.json({
47
+ message: `Hello, ${body.name || "World"}!`,
48
+ user: userId
49
+ });
44
50
  });
45
- });
46
51
 
47
- app.get("/", (c) => {
48
- return c.json({ status: "ok",
49
- endpoint: "hello" });
52
+ app.get("/", (c) => {
53
+ return c.json({ status: "ok",
54
+ endpoint: "hello" });
55
+ });
50
56
  });
51
-
52
- export default app;
@@ -14,13 +14,15 @@
14
14
  "@rebasepro/server-core": "workspace:*",
15
15
  "@rebasepro/server-postgresql": "workspace:*",
16
16
  "@rebasepro/admin": "workspace:*",
17
+ "@rebasepro/types": "workspace:*",
17
18
  "drizzle-orm": "^0.45.2",
18
19
  "hono": "^4.12.10",
19
20
  "@hono/node-server": "^1.19.12",
20
21
  "pg": "^8.11.3",
21
22
  "ws": "^8.16.0",
22
23
  "react-compiler-runtime": "^1.0.0",
23
- "dotenv": "^16.0.0"
24
+ "dotenv": "^16.0.0",
25
+ "zod": "^4.4.3"
24
26
  },
25
27
  "devDependencies": {
26
28
  "@types/pg": "^8.6.5",
@@ -1,7 +1,8 @@
1
1
  import * as dotenv from "dotenv";
2
2
  import path from "path";
3
3
  import { fileURLToPath } from "url";
4
- import { loadEnv, z } from "@rebasepro/server-core";
4
+ import { loadEnv } from "@rebasepro/server-core";
5
+ import { z } from "zod";
5
6
 
6
7
  const __filename = fileURLToPath(import.meta.url);
7
8
  const __dirname = path.dirname(__filename);
@@ -7,6 +7,7 @@ import path from "path";
7
7
  import { fileURLToPath } from "url";
8
8
  import {
9
9
  initializeRebaseBackend,
10
+ installShutdownHandlers,
10
11
  serveSPA,
11
12
  HonoEnv,
12
13
  listenWithPortRetry,
@@ -88,10 +89,15 @@ relations },
88
89
  accessExpiresIn: env.JWT_ACCESS_EXPIRES_IN,
89
90
  refreshExpiresIn: env.JWT_REFRESH_EXPIRES_IN,
90
91
  serviceKey: env.REBASE_SERVICE_KEY,
92
+ // Cookie-based auth: the refresh token is stored in an httpOnly
93
+ // cookie (not readable by JS) instead of localStorage, so it is
94
+ // not exposed to XSS. The frontend opts in via
95
+ // `authFlowMode: "cookie"` on createRebaseClient. Requires CORS
96
+ // `credentials: true` (set above).
97
+ cookieAuth: { sameSite: "Lax" },
91
98
  google: env.GOOGLE_CLIENT_ID
92
99
  ? { clientId: env.GOOGLE_CLIENT_ID }
93
100
  : undefined,
94
- seedDefaultRoles: true,
95
101
  allowRegistration: env.ALLOW_REGISTRATION,
96
102
  email: env.SMTP_HOST
97
103
  ? {
@@ -164,17 +170,11 @@ pass: env.SMTP_PASS! }
164
170
  }
165
171
 
166
172
  // ─── Graceful Shutdown ───────────────────────────────────────────────
167
- // Uses the framework's built-in shutdown() which drains connections,
168
- // stops the cron scheduler, and force-exits after 15s timeout.
169
- const gracefulShutdown = async (signal: string) => {
170
- logger.info(`Received ${signal}, shutting down...`);
171
- await backend.shutdown();
172
- await pool.end();
173
- logger.info("Database pool closed");
174
- process.exit(0);
175
- };
176
- process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
177
- process.on("SIGINT", () => gracefulShutdown("SIGINT"));
173
+ // Drains HTTP, stops crons, tears down realtime, then closes the pool.
174
+ // Guards against double signals and force-exits if shutdown hangs.
175
+ installShutdownHandlers(backend, {
176
+ onCleanup: () => pool.end()
177
+ });
178
178
  }
179
179
 
180
180
  startServer().catch(err => {
@@ -1,6 +1,6 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
2
 
3
- const authorsCollection: EntityCollection = {
3
+ const authorsCollection: CollectionConfig = {
4
4
  name: "Authors",
5
5
  singularName: "Author",
6
6
  slug: "authors",
@@ -1,8 +1,8 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
2
  import authorsCollection from "./authors.js";
3
3
  import tagsCollection from "./tags.js";
4
4
 
5
- const postsCollection: EntityCollection = {
5
+ const postsCollection: CollectionConfig = {
6
6
  name: "Posts",
7
7
  singularName: "Post",
8
8
  slug: "posts",
@@ -24,7 +24,9 @@ const postsCollection: EntityCollection = {
24
24
  content: {
25
25
  name: "Content",
26
26
  type: "string",
27
- markdown: true
27
+ ui: {
28
+ markdown: true
29
+ }
28
30
  },
29
31
  status: {
30
32
  name: "Status",
@@ -1,6 +1,6 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
2
 
3
- const categoriesCollection: EntityCollection = {
3
+ const categoriesCollection: CollectionConfig = {
4
4
  name: "Categories",
5
5
  singularName: "Category",
6
6
  slug: "categories",
@@ -25,7 +25,9 @@ const categoriesCollection: EntityCollection = {
25
25
  description: {
26
26
  name: "Description",
27
27
  type: "string",
28
- multiline: true
28
+ ui: {
29
+ multiline: true
30
+ }
29
31
  },
30
32
  icon: {
31
33
  name: "Icon",
@@ -1,6 +1,6 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
2
 
3
- const ordersCollection: EntityCollection = {
3
+ const ordersCollection: CollectionConfig = {
4
4
  name: "Orders",
5
5
  singularName: "Order",
6
6
  slug: "orders",
@@ -47,7 +47,9 @@ color: "red" }
47
47
  notes: {
48
48
  name: "Notes",
49
49
  type: "string",
50
- multiline: true
50
+ ui: {
51
+ multiline: true
52
+ }
51
53
  },
52
54
  createdAt: {
53
55
  name: "Created At",
@@ -1,7 +1,7 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
2
  import categoriesCollection from "./categories.js";
3
3
 
4
- const productsCollection: EntityCollection = {
4
+ const productsCollection: CollectionConfig = {
5
5
  name: "Products",
6
6
  singularName: "Product",
7
7
  slug: "products",
@@ -21,7 +21,9 @@ const productsCollection: EntityCollection = {
21
21
  description: {
22
22
  name: "Description",
23
23
  type: "string",
24
- markdown: true
24
+ ui: {
25
+ markdown: true
26
+ }
25
27
  },
26
28
  price: {
27
29
  name: "Price",
@@ -1,6 +1,6 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
2
 
3
- const tagsCollection: EntityCollection = {
3
+ const tagsCollection: CollectionConfig = {
4
4
  name: "Tags",
5
5
  singularName: "Tag",
6
6
  slug: "tags",
@@ -1,6 +1,6 @@
1
- import type { EntityCollection } from "@rebasepro/types";
1
+ import type { CollectionConfig } from "@rebasepro/types";
2
2
 
3
- const usersCollection: EntityCollection = {
3
+ const usersCollection: CollectionConfig = {
4
4
  name: "Users",
5
5
  singularName: "User",
6
6
  slug: "users",
@@ -44,7 +44,9 @@ roles: ["admin"] }
44
44
  name: "Photo URL",
45
45
  type: "string",
46
46
  columnName: "photo_url",
47
- url: "image"
47
+ ui: {
48
+ url: "image"
49
+ }
48
50
  },
49
51
  roles: {
50
52
  name: "Roles",
@@ -17,14 +17,16 @@ RUN apk add --no-cache python3 make g++
17
17
  WORKDIR /app
18
18
 
19
19
  # Copy workspace root files
20
- COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
20
+ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
21
21
 
22
22
  # Copy workspace packages
23
23
  COPY frontend ./frontend
24
24
  COPY config ./config
25
25
 
26
- # Install dependencies
27
- RUN pnpm install --frozen-lockfile
26
+ # Install dependencies (skip scripts to avoid @ariga/atlas binary download,
27
+ # which is a backend-only dependency that may fail on certain platforms)
28
+ RUN pnpm install --frozen-lockfile --ignore-scripts
29
+ RUN pnpm rebuild esbuild
28
30
 
29
31
  # Build config first, then frontend
30
32
  RUN pnpm --filter "*-config" run build
@@ -17,7 +17,10 @@ const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID;
17
17
 
18
18
  export function App() {
19
19
  const rebaseClient = React.useMemo(() => createRebaseClient({
20
- baseUrl: API_URL
20
+ baseUrl: API_URL,
21
+ // Store the refresh token in an httpOnly cookie (XSS-safe) rather than
22
+ // localStorage. The backend issues it via `auth.cookieAuth`.
23
+ auth: { authFlowMode: "cookie" }
21
24
  }), []);
22
25
 
23
26
  const authController = useRebaseAuthController({
@@ -7,6 +7,20 @@ import { rebaseCollectionsPlugin } from "@rebasepro/core/vitePlugin";
7
7
 
8
8
  export default defineConfig({
9
9
  envDir: path.resolve(__dirname, ".."),
10
+ // Force a single copy of React and React Router across the app and all
11
+ // @rebasepro/* packages. Without this, a locally `link:`ed Rebase checkout
12
+ // resolves its own copies of react-router, producing "multiple copies of
13
+ // React" and "useBlocker must be used within a data router" errors in the
14
+ // admin. Safe to keep for npm-installed setups too.
15
+ resolve: {
16
+ dedupe: [
17
+ "react",
18
+ "react-dom",
19
+ "react-router",
20
+ "react-router-dom",
21
+ "@remix-run/router"
22
+ ]
23
+ },
10
24
  esbuild: {
11
25
  logOverride: { "this-is-undefined-in-esm": "silent" }
12
26
  },
@@ -24,6 +24,7 @@
24
24
  },
25
25
  "devDependencies": {
26
26
  "@rebasepro/cli": "workspace:*",
27
+ "@rebasepro/types": "workspace:*",
27
28
  "concurrently": "^8.2.2",
28
29
  "typescript": "^5.9.2"
29
30
  },
@@ -33,7 +34,8 @@
33
34
  "pnpm": {
34
35
  "onlyBuiltDependencies": [
35
36
  "esbuild",
36
- "sharp"
37
+ "sharp",
38
+ "@ariga/atlas"
37
39
  ]
38
40
  }
39
41
  }
@@ -7,5 +7,7 @@ blockExoticSubdeps: false
7
7
  minimumReleaseAge: 0
8
8
  allowBuilds:
9
9
  esbuild: true
10
+ sharp: true
11
+ "@ariga/atlas": true
10
12
  "{{PROJECT_NAME}}-config": true
11
13
 
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Scripts run OUTSIDE the server and need explicit authentication.
5
5
  * Use a Service Key (set in .env as REBASE_SERVICE_KEY) to get admin
6
- * access — similar to a Firebase Service Account credential.
6
+ * access — similar to a Service Account credential.
7
7
  *
8
8
  * Usage:
9
9
  * # With local dev server running (`pnpm dev` in another terminal):