@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.
Files changed (33) hide show
  1. package/bin/rebase.js +21 -0
  2. package/dist/bundle.d.ts +15 -5
  3. package/dist/commands/cloud/resources.d.ts +20 -0
  4. package/dist/commands/eject.d.ts +1 -0
  5. package/dist/commands/init.d.ts +19 -15
  6. package/dist/fold-static.d.ts +41 -15
  7. package/dist/index.d.ts +1 -0
  8. package/dist/index.es.js +766 -241
  9. package/dist/index.es.js.map +1 -1
  10. package/dist/manifest.d.ts +26 -7
  11. package/package.json +7 -7
  12. package/runtime/dev-server.mjs +0 -1
  13. package/templates/{template/backend → eject}/Dockerfile +16 -4
  14. package/templates/{template → eject}/backend/src/env.ts +0 -1
  15. package/templates/eject/docker-compose.custom.yml +71 -0
  16. package/templates/overlays/baas/backend/package.json +1 -4
  17. package/templates/overlays/baas/backend/tsconfig.json +8 -2
  18. package/templates/overlays/baas/config/index.ts +15 -0
  19. package/templates/overlays/baas/config/package.json +28 -0
  20. package/templates/overlays/baas/package.json +2 -1
  21. package/templates/overlays/baas/pnpm-workspace.yaml +1 -0
  22. package/templates/overlays/baas/rebase.json +2 -6
  23. package/templates/template/README.md +34 -16
  24. package/templates/template/backend/package.json +1 -4
  25. package/templates/template/docker-compose.yml +62 -38
  26. package/templates/template/frontend/src/main.tsx +8 -1
  27. package/templates/template/frontend/vite.config.ts +5 -0
  28. package/templates/template/rebase.json +5 -8
  29. package/templates/overlays/baas/backend/src/index.ts +0 -216
  30. package/templates/template/frontend/Dockerfile +0 -52
  31. package/templates/template/frontend/nginx.conf +0 -40
  32. /package/templates/{template → eject}/backend/src/index.ts +0 -0
  33. /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
- }