@rebasepro/cli 0.11.0 → 0.11.1-canary.g8caabf3
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/bin/rebase.js +21 -0
- package/dist/bundle.d.ts +15 -5
- package/dist/commands/cloud/resources.d.ts +20 -0
- package/dist/commands/eject.d.ts +1 -0
- package/dist/commands/init.d.ts +19 -15
- package/dist/fold-static.d.ts +41 -15
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +766 -241
- package/dist/index.es.js.map +1 -1
- package/dist/manifest.d.ts +26 -7
- package/package.json +7 -7
- package/runtime/dev-server.mjs +0 -1
- package/templates/{template/backend → eject}/Dockerfile +16 -4
- package/templates/{template → eject}/backend/src/env.ts +0 -1
- package/templates/eject/docker-compose.custom.yml +71 -0
- package/templates/overlays/baas/backend/package.json +1 -4
- package/templates/overlays/baas/backend/tsconfig.json +8 -2
- package/templates/overlays/baas/config/index.ts +15 -0
- package/templates/overlays/baas/config/package.json +28 -0
- package/templates/overlays/baas/package.json +2 -1
- package/templates/overlays/baas/pnpm-workspace.yaml +1 -0
- package/templates/overlays/baas/rebase.json +2 -6
- package/templates/template/README.md +34 -16
- package/templates/template/backend/package.json +1 -4
- package/templates/template/docker-compose.yml +62 -38
- package/templates/template/frontend/src/main.tsx +8 -1
- package/templates/template/frontend/vite.config.ts +5 -0
- package/templates/template/rebase.json +5 -8
- package/templates/overlays/baas/backend/src/index.ts +0 -216
- package/templates/template/frontend/Dockerfile +0 -52
- package/templates/template/frontend/nginx.conf +0 -40
- /package/templates/{template → eject}/backend/src/index.ts +0 -0
- /package/templates/overlays/baas/{backend/src → config}/storage.ts +0 -0
|
@@ -1,216 +0,0 @@
|
|
|
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
|
-
import { storageAuthorize } from "./storage.js";
|
|
19
|
-
|
|
20
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
21
|
-
const __dirname = path.dirname(__filename);
|
|
22
|
-
|
|
23
|
-
// ─── App ─────────────────────────────────────────────────────────────
|
|
24
|
-
const app: Hono<HonoEnv> = new Hono<HonoEnv>();
|
|
25
|
-
|
|
26
|
-
const isProduction = env.NODE_ENV === "production";
|
|
27
|
-
const allowedOrigins = isProduction
|
|
28
|
-
? (() => {
|
|
29
|
-
const origins = env.CORS_ORIGINS || env.FRONTEND_URL;
|
|
30
|
-
if (!origins) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
"CORS_ORIGINS or FRONTEND_URL must be set in production. " +
|
|
33
|
-
"Example: CORS_ORIGINS=https://yourdomain.com"
|
|
34
|
-
);
|
|
35
|
-
}
|
|
36
|
-
return origins.split(",").map(s => s.trim());
|
|
37
|
-
})()
|
|
38
|
-
: [];
|
|
39
|
-
|
|
40
|
-
// In dev we still restrict which origins are reflected. Because `credentials`
|
|
41
|
-
// is enabled, reflecting an arbitrary Origin would let any website the
|
|
42
|
-
// developer happens to visit make credentialed cross-origin requests to this
|
|
43
|
-
// dev server (and read the responses) using the developer's session. So dev
|
|
44
|
-
// reflects only localhost origins; requests with no Origin (curl, same-origin)
|
|
45
|
-
// are unaffected.
|
|
46
|
-
const isLocalhostOrigin = (origin: string): boolean => {
|
|
47
|
-
try {
|
|
48
|
-
const { hostname } = new URL(origin);
|
|
49
|
-
return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
|
|
50
|
-
} catch {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
app.use("/*", cors({
|
|
56
|
-
origin: (origin) => {
|
|
57
|
-
if (isProduction) return allowedOrigins.includes(origin) ? origin : null;
|
|
58
|
-
if (!origin) return "*";
|
|
59
|
-
return isLocalhostOrigin(origin) ? origin : null;
|
|
60
|
-
},
|
|
61
|
-
credentials: true
|
|
62
|
-
}));
|
|
63
|
-
|
|
64
|
-
app.use("/*", secureHeaders());
|
|
65
|
-
|
|
66
|
-
// ─── Database ────────────────────────────────────────────────────────
|
|
67
|
-
const databaseUrl = env.DATABASE_URL;
|
|
68
|
-
|
|
69
|
-
const { db, pool, connectionString } = createPostgresDatabaseConnection(databaseUrl);
|
|
70
|
-
|
|
71
|
-
// ─── Start ───────────────────────────────────────────────────────────
|
|
72
|
-
async function startServer() {
|
|
73
|
-
const jwtSecret = env.JWT_SECRET;
|
|
74
|
-
const PORT = env.PORT;
|
|
75
|
-
const server = createServer(getRequestListener(app.fetch));
|
|
76
|
-
|
|
77
|
-
const backend = await initializeRebaseBackend({
|
|
78
|
-
// BaaS mode: every RLS-protected table is served over REST. There are
|
|
79
|
-
// no collection files to write or keep in sync — change the schema with
|
|
80
|
-
// a migration and the API follows.
|
|
81
|
-
//
|
|
82
|
-
// Your database's own row-level security is the whole authorization
|
|
83
|
-
// model here: requests run as the `rebase_user` role, so a table
|
|
84
|
-
// without RLS has no rules at all and is not served. Protect one with:
|
|
85
|
-
// ALTER TABLE mytable ENABLE ROW LEVEL SECURITY;
|
|
86
|
-
// CREATE POLICY mytable_read ON mytable FOR SELECT TO public USING (true);
|
|
87
|
-
mode: "baas",
|
|
88
|
-
functionsDir: path.resolve(__dirname, "../functions"),
|
|
89
|
-
server,
|
|
90
|
-
app,
|
|
91
|
-
database: createPostgresAdapter({
|
|
92
|
-
connection: db,
|
|
93
|
-
adminConnectionString: env.ADMIN_CONNECTION_STRING || databaseUrl,
|
|
94
|
-
connectionString
|
|
95
|
-
}),
|
|
96
|
-
auth: {
|
|
97
|
-
// No `collection` here: BaaS mode has no collection files, and the
|
|
98
|
-
// auth adapter owns its own user tables.
|
|
99
|
-
jwtSecret,
|
|
100
|
-
accessExpiresIn: env.JWT_ACCESS_EXPIRES_IN,
|
|
101
|
-
refreshExpiresIn: env.JWT_REFRESH_EXPIRES_IN,
|
|
102
|
-
serviceKey: env.REBASE_SERVICE_KEY,
|
|
103
|
-
cookieAuth: { sameSite: "Lax" },
|
|
104
|
-
google: env.GOOGLE_CLIENT_ID
|
|
105
|
-
? { clientId: env.GOOGLE_CLIENT_ID }
|
|
106
|
-
: undefined,
|
|
107
|
-
allowRegistration: env.ALLOW_REGISTRATION,
|
|
108
|
-
email: env.SMTP_HOST
|
|
109
|
-
? {
|
|
110
|
-
from: env.SMTP_FROM || `${env.APP_NAME} <noreply@rebase.pro>`,
|
|
111
|
-
smtp: {
|
|
112
|
-
host: env.SMTP_HOST,
|
|
113
|
-
port: env.SMTP_PORT,
|
|
114
|
-
secure: env.SMTP_SECURE,
|
|
115
|
-
auth: env.SMTP_USER
|
|
116
|
-
? { user: env.SMTP_USER,
|
|
117
|
-
pass: env.SMTP_PASS! }
|
|
118
|
-
: undefined,
|
|
119
|
-
name: env.SMTP_NAME
|
|
120
|
-
},
|
|
121
|
-
appName: env.APP_NAME,
|
|
122
|
-
resetPasswordUrl: env.FRONTEND_URL
|
|
123
|
-
}
|
|
124
|
-
: undefined
|
|
125
|
-
},
|
|
126
|
-
// File storage is opt-in. With no bucket configured, storage is OFF in
|
|
127
|
-
// production — the upload routes answer 501 STORAGE_NOT_CONFIGURED —
|
|
128
|
-
// rather than writing to the container filesystem, which is erased on
|
|
129
|
-
// every restart and redeploy. Uploads that fail loudly are recoverable;
|
|
130
|
-
// uploads that succeed into a disk about to be wiped are not.
|
|
131
|
-
// Local disk stays the default in development, where it is what you want.
|
|
132
|
-
storage: env.STORAGE_TYPE === "s3"
|
|
133
|
-
? {
|
|
134
|
-
type: "s3",
|
|
135
|
-
bucket: env.S3_BUCKET!,
|
|
136
|
-
region: env.S3_REGION || "auto",
|
|
137
|
-
accessKeyId: env.S3_ACCESS_KEY_ID || "",
|
|
138
|
-
secretAccessKey: env.S3_SECRET_ACCESS_KEY || "",
|
|
139
|
-
endpoint: env.S3_ENDPOINT,
|
|
140
|
-
forcePathStyle: env.S3_FORCE_PATH_STYLE
|
|
141
|
-
}
|
|
142
|
-
: env.STORAGE_TYPE === "gcs"
|
|
143
|
-
? {
|
|
144
|
-
type: "gcs",
|
|
145
|
-
bucket: env.GCS_BUCKET!,
|
|
146
|
-
projectId: env.GCS_PROJECT_ID,
|
|
147
|
-
keyFilename: env.GCS_KEY_FILENAME
|
|
148
|
-
}
|
|
149
|
-
// Set FORCE_LOCAL_STORAGE=true only if this deployment really
|
|
150
|
-
// does have a durable volume mounted at STORAGE_PATH.
|
|
151
|
-
: isProduction && !env.FORCE_LOCAL_STORAGE
|
|
152
|
-
? undefined
|
|
153
|
-
: {
|
|
154
|
-
type: "local",
|
|
155
|
-
basePath: env.STORAGE_PATH || path.resolve(__dirname, "../../uploads")
|
|
156
|
-
},
|
|
157
|
-
// Storage is not under row-level security, so this hook IS its access
|
|
158
|
-
// model — the server refuses to boot in production without one, because
|
|
159
|
-
// "signed in" would otherwise be the only thing between a caller and every
|
|
160
|
-
// file in the bucket. See storage.ts; the default scopes each caller to
|
|
161
|
-
// `users/<uid>/` and is meant to be replaced with your own rule.
|
|
162
|
-
storageAuthorize,
|
|
163
|
-
history: true,
|
|
164
|
-
enableSwagger: true
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
// ─── Health check ─────────────────────────────────────────────
|
|
168
|
-
app.get("/health", async (c) => {
|
|
169
|
-
const result = await backend.healthCheck();
|
|
170
|
-
const status = result.healthy ? 200 : 503;
|
|
171
|
-
return c.json({
|
|
172
|
-
status: result.healthy ? "ok" : "degraded",
|
|
173
|
-
latencyMs: result.latencyMs,
|
|
174
|
-
...(result.details ? { details: result.details } : {})
|
|
175
|
-
}, status);
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// No serveSPA: this is a headless API. Point any frontend at it over HTTP.
|
|
179
|
-
|
|
180
|
-
if (!isProduction) {
|
|
181
|
-
// Dev mode: retry the next port if the current one is in use
|
|
182
|
-
const projectRoot = path.resolve(__dirname, "../..");
|
|
183
|
-
const actualPort = await listenWithPortRetry(server, PORT, { portFileDir: projectRoot, serviceKey: env.REBASE_SERVICE_KEY });
|
|
184
|
-
|
|
185
|
-
// Clean up port file on exit
|
|
186
|
-
const cleanup = () => cleanupDevPortFile(projectRoot);
|
|
187
|
-
process.on("SIGINT", cleanup);
|
|
188
|
-
process.on("SIGTERM", cleanup);
|
|
189
|
-
process.on("exit", cleanup);
|
|
190
|
-
|
|
191
|
-
logger.info(`API running at http://localhost:${actualPort}`);
|
|
192
|
-
// Docs are only mounted once there is something to document; with no
|
|
193
|
-
// servable tables the URL would 404, so don't advertise it.
|
|
194
|
-
if (backend.collectionRegistry.getCollections().length > 0) {
|
|
195
|
-
logger.info(`API docs at http://localhost:${actualPort}/api/swagger`);
|
|
196
|
-
}
|
|
197
|
-
} else {
|
|
198
|
-
server.listen(PORT, () => {
|
|
199
|
-
logger.info(`API running at http://localhost:${PORT}`);
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// ─── Graceful Shutdown ───────────────────────────────────────────────
|
|
204
|
-
// Drains HTTP, stops crons, tears down realtime, then closes the pool.
|
|
205
|
-
// Guards against double signals and force-exits if shutdown hangs.
|
|
206
|
-
installShutdownHandlers(backend, {
|
|
207
|
-
onCleanup: () => pool.end()
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
startServer().catch(err => {
|
|
212
|
-
logger.error("Failed to start server", { error: err instanceof Error ? err : new Error(String(err)) });
|
|
213
|
-
process.exit(1);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
export { app };
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# ─── Multi-stage production Dockerfile for the Rebase frontend ────────
|
|
2
|
-
# Builds the Vite app, then serves via nginx for proper caching/compression.
|
|
3
|
-
#
|
|
4
|
-
# Build context: the project root (where pnpm-workspace.yaml lives)
|
|
5
|
-
# Usage:
|
|
6
|
-
# docker build -t my-app-frontend -f frontend/Dockerfile .
|
|
7
|
-
|
|
8
|
-
# ── Stage 1: Install + Build ─────────────────────────────────────────
|
|
9
|
-
FROM node:24-alpine AS builder
|
|
10
|
-
|
|
11
|
-
ENV PNPM_HOME="/pnpm"
|
|
12
|
-
ENV PATH="$PNPM_HOME:$PATH"
|
|
13
|
-
RUN corepack enable
|
|
14
|
-
|
|
15
|
-
RUN apk add --no-cache python3 make g++
|
|
16
|
-
|
|
17
|
-
WORKDIR /app
|
|
18
|
-
|
|
19
|
-
# Copy workspace root files
|
|
20
|
-
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
|
|
21
|
-
|
|
22
|
-
# Copy workspace packages
|
|
23
|
-
COPY frontend ./frontend
|
|
24
|
-
COPY config ./config
|
|
25
|
-
|
|
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
|
|
30
|
-
|
|
31
|
-
# Build config first, then frontend
|
|
32
|
-
RUN pnpm --filter "*-config" run build
|
|
33
|
-
RUN pnpm --filter "*-frontend" run build
|
|
34
|
-
|
|
35
|
-
# ── Stage 2: Serve with nginx ────────────────────────────────────────
|
|
36
|
-
FROM nginx:1.27-alpine AS runtime
|
|
37
|
-
|
|
38
|
-
# Remove default nginx page
|
|
39
|
-
RUN rm -rf /usr/share/nginx/html/*
|
|
40
|
-
|
|
41
|
-
# Copy built assets
|
|
42
|
-
COPY --from=builder /app/frontend/dist /usr/share/nginx/html
|
|
43
|
-
|
|
44
|
-
# Custom nginx config for SPA routing + compression
|
|
45
|
-
COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf
|
|
46
|
-
|
|
47
|
-
EXPOSE 80
|
|
48
|
-
|
|
49
|
-
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
|
50
|
-
CMD wget --no-verbose --tries=1 --spider http://localhost:80/ || exit 1
|
|
51
|
-
|
|
52
|
-
CMD ["nginx", "-g", "daemon off;"]
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
server {
|
|
2
|
-
listen 80;
|
|
3
|
-
server_name _;
|
|
4
|
-
|
|
5
|
-
root /usr/share/nginx/html;
|
|
6
|
-
index index.html;
|
|
7
|
-
|
|
8
|
-
# Gzip compression
|
|
9
|
-
gzip on;
|
|
10
|
-
gzip_vary on;
|
|
11
|
-
gzip_proxied any;
|
|
12
|
-
gzip_comp_level 6;
|
|
13
|
-
gzip_min_length 256;
|
|
14
|
-
gzip_types
|
|
15
|
-
text/plain
|
|
16
|
-
text/css
|
|
17
|
-
text/javascript
|
|
18
|
-
application/javascript
|
|
19
|
-
application/json
|
|
20
|
-
application/xml
|
|
21
|
-
image/svg+xml
|
|
22
|
-
font/woff2;
|
|
23
|
-
|
|
24
|
-
# Cache static assets aggressively (hashed filenames from Vite)
|
|
25
|
-
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
|
26
|
-
expires 1y;
|
|
27
|
-
add_header Cache-Control "public, immutable";
|
|
28
|
-
try_files $uri =404;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
# SPA fallback: serve index.html for all non-file routes
|
|
32
|
-
location / {
|
|
33
|
-
try_files $uri $uri/ /index.html;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
# Security headers
|
|
37
|
-
add_header X-Frame-Options "SAMEORIGIN" always;
|
|
38
|
-
add_header X-Content-Type-Options "nosniff" always;
|
|
39
|
-
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
|
40
|
-
}
|
|
File without changes
|
|
File without changes
|