@rebasepro/cli 0.0.1-canary.eae7889 → 0.0.1-canary.eb08332
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 +39 -0
- 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 +3785 -990
- 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 -23
- 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/.dockerignore +1 -1
- package/templates/template/.env.example +120 -0
- 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 +20 -10
- 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 +11 -11
- 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 +15 -35
- 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 +14 -39
- package/templates/template/frontend/Dockerfile +5 -3
- package/templates/template/frontend/package.json +8 -7
- package/templates/template/frontend/src/App.tsx +26 -105
- 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 +47 -4
- package/templates/template/package.json +22 -8
- 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 -1203
- package/dist/index.cjs.map +0 -1
- package/dist/utils/project.test.d.ts +0 -1
- package/templates/template/.env.template +0 -62
- package/templates/template/backend/drizzle.config.ts +0 -22
|
@@ -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.eb08332",
|
|
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,22 +26,25 @@
|
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"arg": "^5.0.2",
|
|
29
|
-
"chalk": "^
|
|
30
|
-
"dotenv": "^
|
|
31
|
-
"execa": "^
|
|
32
|
-
"inquirer": "
|
|
33
|
-
"jiti": "^2.
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"@rebasepro/
|
|
37
|
-
"@rebasepro/
|
|
38
|
-
"@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.eb08332",
|
|
35
|
+
"@rebasepro/client": "0.0.1-canary.eb08332",
|
|
36
|
+
"@rebasepro/server": "0.0.1-canary.eb08332",
|
|
37
|
+
"@rebasepro/types": "0.0.1-canary.eb08332",
|
|
38
|
+
"@rebasepro/codegen": "0.0.1-canary.eb08332",
|
|
39
|
+
"@rebasepro/server-postgres": "0.0.1-canary.eb08332"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
|
-
"@types/node": "^
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
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"
|
|
45
48
|
},
|
|
46
49
|
"files": [
|
|
47
50
|
"bin/",
|
|
@@ -51,17 +54,17 @@
|
|
|
51
54
|
"exports": {
|
|
52
55
|
".": {
|
|
53
56
|
"types": "./dist/index.d.ts",
|
|
54
|
-
"import": "./dist/index.es.js"
|
|
55
|
-
"require": "./dist/index.cjs"
|
|
57
|
+
"import": "./dist/index.es.js"
|
|
56
58
|
}
|
|
57
59
|
},
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "https://github.com/rebasepro/rebase.git",
|
|
63
|
+
"directory": "packages/cli"
|
|
64
|
+
},
|
|
63
65
|
"scripts": {
|
|
64
66
|
"test": "vitest run",
|
|
67
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
65
68
|
"build": "vite build && tsc --emitDeclarationOnly -p tsconfig.json",
|
|
66
69
|
"clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
|
|
67
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
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# ╔══════════════════════════════════════════════════════════════════════════════╗
|
|
2
|
+
# ║ Rebase Environment Configuration ║
|
|
3
|
+
# ║ Copy this file to .env and fill in the values ║
|
|
4
|
+
# ╚══════════════════════════════════════════════════════════════════════════════╝
|
|
5
|
+
|
|
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.
|
|
10
|
+
DATABASE_URL=postgresql://rebase:changeme@localhost:5432/rebase?options=-c%20search_path=public
|
|
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
|
|
16
|
+
|
|
17
|
+
# Separate admin connection string for migrations and schema operations (optional)
|
|
18
|
+
# Falls back to DATABASE_URL if not set
|
|
19
|
+
# ADMIN_CONNECTION_STRING=postgresql://postgres:your-password@localhost:5432/rebase
|
|
20
|
+
|
|
21
|
+
# Connection pool tuning (optional — sensible defaults are built-in)
|
|
22
|
+
# DB_POOL_MAX=20
|
|
23
|
+
# DB_POOL_IDLE_TIMEOUT=30000
|
|
24
|
+
# DB_POOL_CONNECT_TIMEOUT=10000
|
|
25
|
+
|
|
26
|
+
# ── Server ────────────────────────────────────────────────────────────────────
|
|
27
|
+
PORT=3001
|
|
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
|
|
31
|
+
|
|
32
|
+
# ── Logging ───────────────────────────────────────────────────────────────────
|
|
33
|
+
# Levels: error, warn, info, debug
|
|
34
|
+
LOG_LEVEL=info
|
|
35
|
+
|
|
36
|
+
# ── JWT Authentication (required) ─────────────────────────────────────────────
|
|
37
|
+
# Must be at least 32 characters. Generate with:
|
|
38
|
+
# node -e "console.log(require('crypto').randomBytes(48).toString('base64'))"
|
|
39
|
+
JWT_SECRET=
|
|
40
|
+
JWT_ACCESS_EXPIRES_IN=1h
|
|
41
|
+
JWT_REFRESH_EXPIRES_IN=30d
|
|
42
|
+
|
|
43
|
+
# ── Registration ──────────────────────────────────────────────────────────────
|
|
44
|
+
ALLOW_REGISTRATION=true
|
|
45
|
+
|
|
46
|
+
# ── Email (optional — required for password reset / verification) ─────────────
|
|
47
|
+
# SMTP_HOST=smtp.example.com
|
|
48
|
+
# SMTP_PORT=587
|
|
49
|
+
# SMTP_SECURE=false
|
|
50
|
+
# SMTP_USER=
|
|
51
|
+
# SMTP_PASS=
|
|
52
|
+
# SMTP_FROM=noreply@yourapp.com
|
|
53
|
+
# SMTP_NAME=
|
|
54
|
+
# APP_NAME=Your App Name
|
|
55
|
+
|
|
56
|
+
# ── URLs ──────────────────────────────────────────────────────────────────────
|
|
57
|
+
# Canonical frontend URL (used in password-reset / verification emails)
|
|
58
|
+
FRONTEND_URL=http://localhost:5173
|
|
59
|
+
|
|
60
|
+
# Allowed CORS origins in production (comma-separated)
|
|
61
|
+
# CORS_ORIGINS=https://yourdomain.com
|
|
62
|
+
|
|
63
|
+
# ── OAuth Providers (optional — uncomment to enable) ──────────────────────────
|
|
64
|
+
# GOOGLE_CLIENT_ID=
|
|
65
|
+
|
|
66
|
+
# ── Frontend (Vite) ──────────────────────────────────────────────────────────
|
|
67
|
+
VITE_API_URL=http://localhost:3001
|
|
68
|
+
# VITE_GOOGLE_CLIENT_ID=
|
|
69
|
+
|
|
70
|
+
# ── Storage ───────────────────────────────────────────────────────────────────
|
|
71
|
+
# Set STORAGE_TYPE to choose a storage backend. Default is "local" (filesystem).
|
|
72
|
+
#
|
|
73
|
+
# Supported values: local, s3
|
|
74
|
+
#
|
|
75
|
+
# STORAGE_TYPE=local
|
|
76
|
+
|
|
77
|
+
# --- Local filesystem (default — great for dev and single-server deployments) --
|
|
78
|
+
# STORAGE_PATH=./uploads
|
|
79
|
+
|
|
80
|
+
# --- S3-compatible (AWS S3, Cloudflare R2, MinIO, Hetzner Object Storage, Backblaze B2) ---
|
|
81
|
+
# STORAGE_TYPE=s3
|
|
82
|
+
# S3_BUCKET=my-app-uploads
|
|
83
|
+
# S3_REGION=us-east-1
|
|
84
|
+
# S3_ACCESS_KEY_ID=
|
|
85
|
+
# S3_SECRET_ACCESS_KEY=
|
|
86
|
+
# S3_ENDPOINT= # Required for R2, MinIO, Hetzner (e.g. https://fsn1.your-objectstorage.com)
|
|
87
|
+
# S3_FORCE_PATH_STYLE= # Set to "true" for MinIO
|
|
88
|
+
|
|
89
|
+
# --- GCS via S3 interop (Cloud Run, GKE, etc.) ---
|
|
90
|
+
# Use GCS HMAC keys: https://cloud.google.com/storage/docs/interoperability
|
|
91
|
+
# STORAGE_TYPE=s3
|
|
92
|
+
# S3_BUCKET=my-gcs-bucket
|
|
93
|
+
# S3_ENDPOINT=https://storage.googleapis.com
|
|
94
|
+
# S3_ACCESS_KEY_ID=GOOG... # HMAC access key
|
|
95
|
+
# S3_SECRET_ACCESS_KEY=... # HMAC secret
|
|
96
|
+
#
|
|
97
|
+
# For native GCS SDK or other providers (Azure Blob, etc.), implement the
|
|
98
|
+
# StorageController interface and pass it directly in your backend config.
|
|
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
|
+
|
|
112
|
+
# ── Admin Service Key (optional) ──────────────────────────────────────────────
|
|
113
|
+
# When set, scripts authenticate with: Authorization: Bearer <key>
|
|
114
|
+
# Generate with: node -e "console.log(require('crypto').randomBytes(48).toString('base64'))"
|
|
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
|
+
|