@rebasepro/cli 0.0.1-canary.f81da60 → 0.0.1-canary.fc811d7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +17 -196
- package/README.md +57 -33
- package/dist/commands/api-keys.d.ts +1 -0
- package/dist/commands/build.d.ts +1 -0
- package/dist/commands/cloud/auth.d.ts +3 -0
- package/dist/commands/cloud/context.d.ts +61 -0
- package/dist/commands/cloud/databases.d.ts +1 -0
- package/dist/commands/cloud/deploy.d.ts +2 -0
- package/dist/commands/cloud/index.d.ts +1 -0
- package/dist/commands/cloud/link.d.ts +5 -0
- package/dist/commands/cloud/orgs.d.ts +1 -0
- package/dist/commands/cloud/projects.d.ts +13 -0
- package/dist/commands/cloud/resources.d.ts +6 -0
- package/dist/commands/generate_sdk.d.ts +1 -1
- package/dist/commands/init.d.ts +37 -1
- package/dist/commands/skills.d.ts +1 -0
- package/dist/commands/start.d.ts +1 -0
- package/dist/index.d.ts +6 -1
- package/dist/index.es.js +3781 -1089
- package/dist/index.es.js.map +1 -1
- package/dist/utils/package-manager.d.ts +33 -0
- package/dist/utils/project.d.ts +16 -1
- package/package.json +26 -22
- package/templates/overlays/baas/README.md +37 -0
- package/templates/overlays/baas/backend/package.json +31 -0
- package/templates/overlays/baas/backend/src/index.ts +172 -0
- package/templates/overlays/baas/backend/tsconfig.json +18 -0
- package/templates/overlays/baas/package.json +42 -0
- package/templates/overlays/baas/pnpm-workspace.yaml +9 -0
- package/templates/template/.cursorrules +2 -0
- package/templates/template/.env.example +25 -1
- package/templates/template/.github/copilot-instructions.md +2 -0
- package/templates/template/.windsurfrules +2 -0
- package/templates/template/AGENTS.md +2 -0
- package/templates/template/CLAUDE.md +2 -0
- package/templates/template/README.md +6 -6
- package/templates/template/ai-instructions.md +17 -0
- package/templates/template/backend/Dockerfile +5 -4
- package/templates/template/backend/functions/hello.ts +36 -32
- package/templates/template/backend/package.json +9 -9
- package/templates/template/backend/src/env.ts +13 -42
- package/templates/template/backend/src/index.ts +54 -28
- package/templates/template/config/collections/authors.ts +2 -2
- package/templates/template/config/collections/index.ts +25 -4
- package/templates/template/config/collections/posts.ts +12 -32
- package/templates/template/config/collections/presets/blank/index.ts +3 -0
- package/templates/template/config/collections/presets/ecommerce/categories.ts +40 -0
- package/templates/template/config/collections/presets/ecommerce/index.ts +6 -0
- package/templates/template/config/collections/presets/ecommerce/orders.ts +66 -0
- package/templates/template/config/collections/presets/ecommerce/products.ts +64 -0
- package/templates/template/config/collections/tags.ts +3 -5
- package/templates/template/config/collections/users.ts +142 -0
- package/templates/template/config/index.ts +1 -1
- package/templates/template/docker-compose.yml +4 -4
- package/templates/template/frontend/Dockerfile +5 -3
- package/templates/template/frontend/package.json +8 -7
- package/templates/template/frontend/src/App.tsx +22 -22
- package/templates/template/frontend/src/main.tsx +4 -0
- package/templates/template/frontend/src/virtual.d.ts +6 -0
- package/templates/template/frontend/tsconfig.json +3 -2
- package/templates/template/frontend/vite.config.ts +16 -3
- package/templates/template/package.json +18 -5
- package/templates/template/pnpm-workspace.yaml +9 -0
- package/templates/template/scripts/example.ts +5 -2
- package/dist/auth.d.ts +0 -5
- package/dist/commands/cli.test.d.ts +0 -1
- package/dist/commands/dev.test.d.ts +0 -1
- package/dist/commands/init.test.d.ts +0 -1
- package/dist/index.cjs +0 -1306
- package/dist/index.cjs.map +0 -1
- package/dist/utils/project.test.d.ts +0 -1
- package/templates/template/backend/drizzle.config.ts +0 -42
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export type PackageManager = "pnpm" | "npm";
|
|
2
|
+
export interface PMCommands {
|
|
3
|
+
/** The binary name ("pnpm" | "npm"). */
|
|
4
|
+
name: PackageManager;
|
|
5
|
+
/** Install all dependencies — e.g. `pnpm install` / `npm install`. */
|
|
6
|
+
install: string[];
|
|
7
|
+
/** Run a script — e.g. `pnpm run dev` / `npm run dev`. */
|
|
8
|
+
run: (script: string) => string[];
|
|
9
|
+
/** Execute a local bin — e.g. `pnpm exec rebase ...` / `npx rebase ...`. */
|
|
10
|
+
exec: (bin: string, args: string[]) => string[];
|
|
11
|
+
/** Query the registry — e.g. `pnpm view <pkg> version` / `npm view <pkg> version`. */
|
|
12
|
+
view: (pkg: string, field: string) => string[];
|
|
13
|
+
/** Run all workspace scripts — e.g. `pnpm -r run build` / `npm run build --workspaces`. */
|
|
14
|
+
runAll: (script: string) => string[];
|
|
15
|
+
/** Run a script in a specific workspace — e.g. `pnpm --filter "*-backend" start` / `npm run start -w backend`. */
|
|
16
|
+
runWorkspace: (workspace: string, script: string) => string[];
|
|
17
|
+
/** Execute a one-off package — e.g. `pnpm dlx skills ...` / `npx -y skills ...`. */
|
|
18
|
+
dlx: (pkg: string, args: string[]) => string[];
|
|
19
|
+
/** The workspace dependency protocol: `"workspace:*"` for pnpm, `"*"` for npm. */
|
|
20
|
+
workspaceProtocol: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Detect the package manager from the environment or the target directory.
|
|
24
|
+
*
|
|
25
|
+
* Detection order:
|
|
26
|
+
* 1. Explicit override (if provided)
|
|
27
|
+
* 2. `npm_config_user_agent` env var (set by npm/pnpm when running via `npx`/`pnpm dlx`)
|
|
28
|
+
* 3. Lock-file presence in the target directory
|
|
29
|
+
* 4. Default to pnpm (Rebase's recommended PM)
|
|
30
|
+
*/
|
|
31
|
+
export declare function detectPackageManager(targetDir?: string): PackageManager;
|
|
32
|
+
/** Build the command helpers for a given package manager. */
|
|
33
|
+
export declare function getPMCommands(pm: PackageManager): PMCommands;
|
package/dist/utils/project.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare function findProjectRoot(startDir?: string): string | null;
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function findBackendDir(projectRoot: string): string | null;
|
|
13
13
|
/**
|
|
14
|
-
* Detect the active backend plugin (e.g. @rebasepro/server-
|
|
14
|
+
* Detect the active backend plugin (e.g. @rebasepro/server-postgres) from the backend's package.json.
|
|
15
15
|
*/
|
|
16
16
|
export declare function getActiveBackendPlugin(backendDir: string): string | null;
|
|
17
17
|
/**
|
|
@@ -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,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/cli",
|
|
3
|
-
"version": "0.0.1-canary.
|
|
3
|
+
"version": "0.0.1-canary.fc811d7",
|
|
4
4
|
"description": "Developer tools for Rebase projects",
|
|
5
|
-
"main": "./dist/index.
|
|
5
|
+
"main": "./dist/index.es.js",
|
|
6
6
|
"module": "./dist/index.es.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"type": "module",
|
|
@@ -26,21 +26,25 @@
|
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"arg": "^5.0.2",
|
|
29
|
-
"chalk": "^
|
|
30
|
-
"dotenv": "^
|
|
31
|
-
"execa": "^
|
|
32
|
-
"inquirer": "
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"@rebasepro/
|
|
36
|
-
"@rebasepro/
|
|
37
|
-
"@rebasepro/
|
|
29
|
+
"chalk": "^5.6.2",
|
|
30
|
+
"dotenv": "^17.4.2",
|
|
31
|
+
"execa": "^9.6.1",
|
|
32
|
+
"inquirer": "14.0.2",
|
|
33
|
+
"jiti": "^2.7.0",
|
|
34
|
+
"@rebasepro/agent-skills": "0.0.1-canary.fc811d7",
|
|
35
|
+
"@rebasepro/codegen": "0.0.1-canary.fc811d7",
|
|
36
|
+
"@rebasepro/client": "0.0.1-canary.fc811d7",
|
|
37
|
+
"@rebasepro/server": "0.0.1-canary.fc811d7",
|
|
38
|
+
"@rebasepro/types": "0.0.1-canary.fc811d7",
|
|
39
|
+
"@rebasepro/server-postgres": "0.0.1-canary.fc811d7"
|
|
38
40
|
},
|
|
39
41
|
"devDependencies": {
|
|
40
|
-
"@types/node": "^
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
42
|
+
"@types/node": "^25.9.3",
|
|
43
|
+
"@types/pg": "^8.20.0",
|
|
44
|
+
"pg": "^8.21.0",
|
|
45
|
+
"typescript": "^6.0.3",
|
|
46
|
+
"vite": "^8.0.16",
|
|
47
|
+
"vitest": "4.1.8"
|
|
44
48
|
},
|
|
45
49
|
"files": [
|
|
46
50
|
"bin/",
|
|
@@ -50,17 +54,17 @@
|
|
|
50
54
|
"exports": {
|
|
51
55
|
".": {
|
|
52
56
|
"types": "./dist/index.d.ts",
|
|
53
|
-
"import": "./dist/index.es.js"
|
|
54
|
-
"require": "./dist/index.cjs"
|
|
57
|
+
"import": "./dist/index.es.js"
|
|
55
58
|
}
|
|
56
59
|
},
|
|
57
|
-
"
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "https://github.com/rebasepro/rebase.git",
|
|
63
|
+
"directory": "packages/cli"
|
|
64
|
+
},
|
|
62
65
|
"scripts": {
|
|
63
66
|
"test": "vitest run",
|
|
67
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
64
68
|
"build": "vite build && tsc --emitDeclarationOnly -p tsconfig.json",
|
|
65
69
|
"clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
|
|
66
70
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# {{PROJECT_NAME}}
|
|
2
|
+
|
|
3
|
+
A headless Rebase backend: a REST API, auth, storage, realtime and backups over
|
|
4
|
+
your PostgreSQL database.
|
|
5
|
+
|
|
6
|
+
There are no collection files. The server reads your database schema at boot and
|
|
7
|
+
serves every table, so the API follows your migrations — change the schema and
|
|
8
|
+
the endpoints change with it.
|
|
9
|
+
|
|
10
|
+
## Run it
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
pnpm install
|
|
14
|
+
pnpm dev
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- API: `http://localhost:3001/api/data/<table>`
|
|
18
|
+
- Docs: `http://localhost:3001/api/swagger`
|
|
19
|
+
- Health: `http://localhost:3001/health`
|
|
20
|
+
|
|
21
|
+
Set `DATABASE_URL` in `.env` to point at your database.
|
|
22
|
+
|
|
23
|
+
## Use it from an app
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
import { createRebaseClient } from "@rebasepro/client";
|
|
27
|
+
|
|
28
|
+
const rebase = createRebaseClient({ baseUrl: "http://localhost:3001" });
|
|
29
|
+
const posts = await rebase.data.collection("posts").find();
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Adding an admin UI later
|
|
33
|
+
|
|
34
|
+
Nothing here locks you out of it. Switch `mode` to `"cms"` in
|
|
35
|
+
`backend/src/index.ts`, add a `config/collections` directory, and add a frontend
|
|
36
|
+
that renders them. See MODULAR-ARCHITECTURE.md in the Rebase repo for the three
|
|
37
|
+
adoption modes.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}-backend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Rebase BaaS — headless PostgreSQL API",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "tsx watch src/index.ts",
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"start": "node dist/backend/src/index.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@rebasepro/server": "workspace:*",
|
|
14
|
+
"@rebasepro/server-postgres": "workspace:*",
|
|
15
|
+
"@rebasepro/types": "workspace:*",
|
|
16
|
+
"drizzle-orm": "^0.45.2",
|
|
17
|
+
"hono": "^4.12.10",
|
|
18
|
+
"@hono/node-server": "^1.19.12",
|
|
19
|
+
"pg": "^8.11.3",
|
|
20
|
+
"ws": "^8.16.0",
|
|
21
|
+
"dotenv": "^16.0.0",
|
|
22
|
+
"zod": "^4.4.3"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/pg": "^8.6.5",
|
|
26
|
+
"@types/node": "^20.10.5",
|
|
27
|
+
"@types/ws": "^8.5.10",
|
|
28
|
+
"tsx": "^4.20.6",
|
|
29
|
+
"typescript": "^5.9.2"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { cors } from "hono/cors";
|
|
3
|
+
import { secureHeaders } from "hono/secure-headers";
|
|
4
|
+
import { getRequestListener } from "@hono/node-server";
|
|
5
|
+
import { createServer } from "http";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import {
|
|
9
|
+
initializeRebaseBackend,
|
|
10
|
+
installShutdownHandlers,
|
|
11
|
+
HonoEnv,
|
|
12
|
+
listenWithPortRetry,
|
|
13
|
+
cleanupDevPortFile,
|
|
14
|
+
logger
|
|
15
|
+
} from "@rebasepro/server";
|
|
16
|
+
import { createPostgresDatabaseConnection, createPostgresAdapter } from "@rebasepro/server-postgres";
|
|
17
|
+
import { env } from "./env.js";
|
|
18
|
+
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = path.dirname(__filename);
|
|
21
|
+
|
|
22
|
+
// ─── App ─────────────────────────────────────────────────────────────
|
|
23
|
+
const app: Hono<HonoEnv> = new Hono<HonoEnv>();
|
|
24
|
+
|
|
25
|
+
const isProduction = env.NODE_ENV === "production";
|
|
26
|
+
const allowedOrigins = isProduction
|
|
27
|
+
? (() => {
|
|
28
|
+
const origins = env.CORS_ORIGINS || env.FRONTEND_URL;
|
|
29
|
+
if (!origins) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
"CORS_ORIGINS or FRONTEND_URL must be set in production. " +
|
|
32
|
+
"Example: CORS_ORIGINS=https://yourdomain.com"
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return origins.split(",").map(s => s.trim());
|
|
36
|
+
})()
|
|
37
|
+
: [];
|
|
38
|
+
|
|
39
|
+
app.use("/*", cors({
|
|
40
|
+
origin: (origin) => {
|
|
41
|
+
if (!isProduction) return origin || "*";
|
|
42
|
+
return allowedOrigins.includes(origin) ? origin : null;
|
|
43
|
+
},
|
|
44
|
+
credentials: true
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
app.use("/*", secureHeaders());
|
|
48
|
+
|
|
49
|
+
// ─── Database ────────────────────────────────────────────────────────
|
|
50
|
+
const databaseUrl = env.DATABASE_URL;
|
|
51
|
+
|
|
52
|
+
const { db, pool, connectionString } = createPostgresDatabaseConnection(databaseUrl);
|
|
53
|
+
|
|
54
|
+
// ─── Start ───────────────────────────────────────────────────────────
|
|
55
|
+
async function startServer() {
|
|
56
|
+
const jwtSecret = env.JWT_SECRET;
|
|
57
|
+
const PORT = env.PORT;
|
|
58
|
+
const server = createServer(getRequestListener(app.fetch));
|
|
59
|
+
|
|
60
|
+
const backend = await initializeRebaseBackend({
|
|
61
|
+
// BaaS mode: every RLS-protected table is served over REST. There are
|
|
62
|
+
// no collection files to write or keep in sync — change the schema with
|
|
63
|
+
// a migration and the API follows.
|
|
64
|
+
//
|
|
65
|
+
// Your database's own row-level security is the whole authorization
|
|
66
|
+
// model here: requests run as the `rebase_user` role, so a table
|
|
67
|
+
// without RLS has no rules at all and is not served. Protect one with:
|
|
68
|
+
// ALTER TABLE mytable ENABLE ROW LEVEL SECURITY;
|
|
69
|
+
// CREATE POLICY mytable_read ON mytable FOR SELECT TO public USING (true);
|
|
70
|
+
mode: "baas",
|
|
71
|
+
functionsDir: path.resolve(__dirname, "../functions"),
|
|
72
|
+
server,
|
|
73
|
+
app,
|
|
74
|
+
database: createPostgresAdapter({
|
|
75
|
+
connection: db,
|
|
76
|
+
adminConnectionString: env.ADMIN_CONNECTION_STRING || databaseUrl,
|
|
77
|
+
connectionString
|
|
78
|
+
}),
|
|
79
|
+
auth: {
|
|
80
|
+
// No `collection` here: BaaS mode has no collection files, and the
|
|
81
|
+
// auth adapter owns its own user tables.
|
|
82
|
+
jwtSecret,
|
|
83
|
+
accessExpiresIn: env.JWT_ACCESS_EXPIRES_IN,
|
|
84
|
+
refreshExpiresIn: env.JWT_REFRESH_EXPIRES_IN,
|
|
85
|
+
serviceKey: env.REBASE_SERVICE_KEY,
|
|
86
|
+
cookieAuth: { sameSite: "Lax" },
|
|
87
|
+
google: env.GOOGLE_CLIENT_ID
|
|
88
|
+
? { clientId: env.GOOGLE_CLIENT_ID }
|
|
89
|
+
: undefined,
|
|
90
|
+
allowRegistration: env.ALLOW_REGISTRATION,
|
|
91
|
+
email: env.SMTP_HOST
|
|
92
|
+
? {
|
|
93
|
+
from: env.SMTP_FROM || `${env.APP_NAME} <noreply@rebase.pro>`,
|
|
94
|
+
smtp: {
|
|
95
|
+
host: env.SMTP_HOST,
|
|
96
|
+
port: env.SMTP_PORT,
|
|
97
|
+
secure: env.SMTP_SECURE,
|
|
98
|
+
auth: env.SMTP_USER
|
|
99
|
+
? { user: env.SMTP_USER,
|
|
100
|
+
pass: env.SMTP_PASS! }
|
|
101
|
+
: undefined,
|
|
102
|
+
name: env.SMTP_NAME
|
|
103
|
+
},
|
|
104
|
+
appName: env.APP_NAME,
|
|
105
|
+
resetPasswordUrl: env.FRONTEND_URL
|
|
106
|
+
}
|
|
107
|
+
: undefined
|
|
108
|
+
},
|
|
109
|
+
storage: env.STORAGE_TYPE === "s3"
|
|
110
|
+
? {
|
|
111
|
+
type: "s3",
|
|
112
|
+
bucket: env.S3_BUCKET!,
|
|
113
|
+
region: env.S3_REGION || "auto",
|
|
114
|
+
accessKeyId: env.S3_ACCESS_KEY_ID || "",
|
|
115
|
+
secretAccessKey: env.S3_SECRET_ACCESS_KEY || "",
|
|
116
|
+
endpoint: env.S3_ENDPOINT,
|
|
117
|
+
forcePathStyle: env.S3_FORCE_PATH_STYLE
|
|
118
|
+
}
|
|
119
|
+
: {
|
|
120
|
+
type: "local",
|
|
121
|
+
basePath: env.STORAGE_PATH || path.resolve(__dirname, "../../uploads")
|
|
122
|
+
},
|
|
123
|
+
history: true,
|
|
124
|
+
enableSwagger: true
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// ─── Health check ─────────────────────────────────────────────
|
|
128
|
+
app.get("/health", async (c) => {
|
|
129
|
+
const result = await backend.healthCheck();
|
|
130
|
+
const status = result.healthy ? 200 : 503;
|
|
131
|
+
return c.json({
|
|
132
|
+
status: result.healthy ? "ok" : "degraded",
|
|
133
|
+
latencyMs: result.latencyMs,
|
|
134
|
+
...(result.details ? { details: result.details } : {})
|
|
135
|
+
}, status);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
// No serveSPA: this is a headless API. Point any frontend at it over HTTP.
|
|
139
|
+
|
|
140
|
+
if (!isProduction) {
|
|
141
|
+
// Dev mode: retry the next port if the current one is in use
|
|
142
|
+
const projectRoot = path.resolve(__dirname, "../..");
|
|
143
|
+
const actualPort = await listenWithPortRetry(server, PORT, { portFileDir: projectRoot, serviceKey: env.REBASE_SERVICE_KEY });
|
|
144
|
+
|
|
145
|
+
// Clean up port file on exit
|
|
146
|
+
const cleanup = () => cleanupDevPortFile(projectRoot);
|
|
147
|
+
process.on("SIGINT", cleanup);
|
|
148
|
+
process.on("SIGTERM", cleanup);
|
|
149
|
+
process.on("exit", cleanup);
|
|
150
|
+
|
|
151
|
+
logger.info(`API running at http://localhost:${actualPort}`);
|
|
152
|
+
logger.info(`API docs at http://localhost:${actualPort}/api/swagger`);
|
|
153
|
+
} else {
|
|
154
|
+
server.listen(PORT, () => {
|
|
155
|
+
logger.info(`API running at http://localhost:${PORT}`);
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ─── Graceful Shutdown ───────────────────────────────────────────────
|
|
160
|
+
// Drains HTTP, stops crons, tears down realtime, then closes the pool.
|
|
161
|
+
// Guards against double signals and force-exits if shutdown hangs.
|
|
162
|
+
installShutdownHandlers(backend, {
|
|
163
|
+
onCleanup: () => pool.end()
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
startServer().catch(err => {
|
|
168
|
+
logger.error("Failed to start server", { error: err instanceof Error ? err : new Error(String(err)) });
|
|
169
|
+
process.exit(1);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
export { app };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "node",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"sourceMap": true
|
|
16
|
+
},
|
|
17
|
+
"include": ["src/**/*", "drizzle.config.ts"]
|
|
18
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Rebase BaaS — headless PostgreSQL API",
|
|
5
|
+
"private": true,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"workspaces": [
|
|
8
|
+
"backend"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"dev": "rebase dev",
|
|
12
|
+
"build": "rebase build",
|
|
13
|
+
"start": "rebase start",
|
|
14
|
+
"db:migrate": "rebase db migrate",
|
|
15
|
+
"schema:introspect": "rebase schema introspect",
|
|
16
|
+
"generate:sdk": "rebase generate-sdk",
|
|
17
|
+
"skills:install": "rebase skills install",
|
|
18
|
+
"example": "tsx scripts/example.ts",
|
|
19
|
+
"deploy": "rebase build && rebase start"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@rebasepro/cli": "workspace:*",
|
|
23
|
+
"@rebasepro/types": "workspace:*",
|
|
24
|
+
"concurrently": "^8.2.2",
|
|
25
|
+
"tsx": "^4.20.6",
|
|
26
|
+
"typescript": "^5.9.2"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@rebasepro/client": "workspace:*",
|
|
30
|
+
"dotenv": "^16.0.0"
|
|
31
|
+
},
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18.0.0"
|
|
34
|
+
},
|
|
35
|
+
"pnpm": {
|
|
36
|
+
"onlyBuiltDependencies": [
|
|
37
|
+
"esbuild",
|
|
38
|
+
"sharp",
|
|
39
|
+
"@ariga/atlas"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -7,8 +7,12 @@
|
|
|
7
7
|
# Database connection string (required)
|
|
8
8
|
# You can use PostgreSQL (postgresql://) or MongoDB (mongodb:// or mongodb+srv://).
|
|
9
9
|
# The backend will automatically detect the database type.
|
|
10
|
-
DATABASE_URL=postgresql://rebase:changeme@localhost:5432/rebase
|
|
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
|
|
@@ -22,6 +26,8 @@ DATABASE_URL=postgresql://rebase:changeme@localhost:5432/rebase
|
|
|
22
26
|
# ── Server ────────────────────────────────────────────────────────────────────
|
|
23
27
|
PORT=3001
|
|
24
28
|
NODE_ENV=development
|
|
29
|
+
# Allow connecting to localhost / 127.0.0.1 in production mode (for local testing of prod builds)
|
|
30
|
+
# ALLOW_LOCALHOST_IN_PRODUCTION=false
|
|
25
31
|
|
|
26
32
|
# ── Logging ───────────────────────────────────────────────────────────────────
|
|
27
33
|
# Levels: error, warn, info, debug
|
|
@@ -44,6 +50,7 @@ ALLOW_REGISTRATION=true
|
|
|
44
50
|
# SMTP_USER=
|
|
45
51
|
# SMTP_PASS=
|
|
46
52
|
# SMTP_FROM=noreply@yourapp.com
|
|
53
|
+
# SMTP_NAME=
|
|
47
54
|
# APP_NAME=Your App Name
|
|
48
55
|
|
|
49
56
|
# ── URLs ──────────────────────────────────────────────────────────────────────
|
|
@@ -90,7 +97,24 @@ VITE_API_URL=http://localhost:3001
|
|
|
90
97
|
# For native GCS SDK or other providers (Azure Blob, etc.), implement the
|
|
91
98
|
# StorageController interface and pass it directly in your backend config.
|
|
92
99
|
|
|
100
|
+
# ── Backups (optional) ────────────────────────────────────────────────────────
|
|
101
|
+
# Manual backups: `rebase db backup --out ./backups` (or an s3://… URL).
|
|
102
|
+
# Scheduled backups: set BACKUP_SCHEDULE and add a cron file in backend/crons
|
|
103
|
+
# that default-exports createBackupCron (from @rebasepro/server-postgres).
|
|
104
|
+
# Backups contain secrets & PII — use a PRIVATE destination with encryption-at-rest.
|
|
105
|
+
# BACKUP_SCHEDULE=0 3 * * * # cron expression; unset ⇒ scheduled backups off
|
|
106
|
+
# BACKUP_DESTINATION=./backups # local path, or s3://bucket/prefix / gs://bucket/prefix
|
|
107
|
+
# BACKUP_RETENTION_DAYS=14 # delete backups older than N days (unset/0 ⇒ keep all)
|
|
108
|
+
# BACKUP_KEEP_MINIMUM=3 # always retain at least N most-recent backups
|
|
109
|
+
# PG_DUMP_PATH= # override pg_dump binary (must match server major version)
|
|
110
|
+
# PG_RESTORE_PATH= # override pg_restore binary
|
|
111
|
+
|
|
93
112
|
# ── Admin Service Key (optional) ──────────────────────────────────────────────
|
|
94
113
|
# When set, scripts authenticate with: Authorization: Bearer <key>
|
|
95
114
|
# Generate with: node -e "console.log(require('crypto').randomBytes(48).toString('base64'))"
|
|
96
115
|
# REBASE_SERVICE_KEY=
|
|
116
|
+
|
|
117
|
+
# Disable switching to database-level roles (e.g. 'admin') for Studio SQL execution (optional)
|
|
118
|
+
# Falls back to false. Useful for databases with application-only roles or managed setups.
|
|
119
|
+
# DISABLE_DB_ROLE_SWITCHING=true
|
|
120
|
+
|
|
@@ -23,15 +23,15 @@ That's it. Your app is running:
|
|
|
23
23
|
#### Prerequisites
|
|
24
24
|
|
|
25
25
|
- [Node.js](https://nodejs.org) >= 18
|
|
26
|
-
- [pnpm](https://pnpm.io)
|
|
27
|
-
- A PostgreSQL database
|
|
26
|
+
- [pnpm](https://pnpm.io) or [npm](https://www.npmjs.com) (v7+)
|
|
27
|
+
- A PostgreSQL database (you can start the included database container via `docker compose up -d db`)
|
|
28
28
|
|
|
29
29
|
#### Setup
|
|
30
30
|
|
|
31
31
|
1. Install dependencies:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
|
-
pnpm install
|
|
34
|
+
pnpm install # or: npm install
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
2. Configure environment:
|
|
@@ -44,14 +44,14 @@ cp .env.example .env
|
|
|
44
44
|
3. Generate schema and push to database:
|
|
45
45
|
|
|
46
46
|
```bash
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
pnpm run schema:generate # or: npm run schema:generate
|
|
48
|
+
pnpm run db:push # or: npm run db:push
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
4. Start the dev server:
|
|
52
52
|
|
|
53
53
|
```bash
|
|
54
|
-
pnpm dev
|
|
54
|
+
pnpm dev # or: npm run dev
|
|
55
55
|
```
|
|
56
56
|
|
|
57
57
|
Backend (Hono + PostgreSQL) on port 3001, frontend (Vite + React) on port 5173.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Rebase AI Coding Rules
|
|
2
|
+
|
|
3
|
+
Rebase provides agent skills for AI coding assistants (Cursor, Claude Code, Windsurf, Gemini CLI, Antigravity, and more) with detailed guidelines, architecture, SDK APIs, and troubleshooting guides.
|
|
4
|
+
|
|
5
|
+
To install skills for your environment, run:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
rebase skills install
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Core Tenets (Quick Reference)
|
|
12
|
+
|
|
13
|
+
1. **Schema-as-Code**: Always define or edit collections in `config/collections/` (e.g., `config/collections/posts.ts`). Never modify generated Drizzle schemas or PostgreSQL tables manually.
|
|
14
|
+
2. **Two-Step Migrations**:
|
|
15
|
+
- Step 1: Run `rebase schema generate` to compile collections to the Drizzle schema.
|
|
16
|
+
- Step 2: Run `rebase db push` (development) or `rebase db generate && rebase db migrate` (production) to apply schema changes to the database.
|
|
17
|
+
3. **Use the SDK**: Always use the Rebase SDK (`rebase.data.<slug>`) to fetch or modify data. Bypassing it with raw SQL or direct Drizzle/PG queries circumvents model validations, lifecycle hooks, and Row-Level Security (RLS).
|
|
@@ -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
|
|
@@ -33,7 +33,7 @@ RUN pnpm --filter "*-config" run build
|
|
|
33
33
|
RUN pnpm --filter "*-backend" run build
|
|
34
34
|
|
|
35
35
|
# Prune dev dependencies for a smaller runtime
|
|
36
|
-
RUN pnpm install --frozen-lockfile --prod
|
|
36
|
+
RUN CI=true pnpm install --frozen-lockfile --prod
|
|
37
37
|
|
|
38
38
|
# ── Stage 2: Production Runtime ──────────────────────────────────────
|
|
39
39
|
FROM node:22-alpine AS runtime
|
|
@@ -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
|