@rebasepro/cli 0.0.1-canary.1 → 0.0.1-canary.4d4fb3e
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/dist/commands/auth.d.ts +1 -0
- package/dist/commands/db.d.ts +1 -0
- package/dist/commands/dev.d.ts +1 -0
- package/dist/commands/generate_sdk.d.ts +18 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/schema.d.ts +1 -0
- package/dist/index.cjs +828 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.es.js +830 -20
- package/dist/index.es.js.map +1 -1
- package/dist/utils/project.d.ts +45 -0
- package/package.json +65 -57
- package/templates/template/.dockerignore +28 -0
- package/templates/template/.env.template +21 -11
- package/templates/template/README.md +73 -16
- package/templates/template/backend/Dockerfile +54 -9
- package/templates/template/backend/functions/hello.ts +34 -0
- package/templates/template/backend/package.json +30 -34
- package/templates/template/backend/src/index.ts +48 -86
- package/templates/template/docker-compose.yml +65 -16
- package/templates/template/frontend/Dockerfile +35 -13
- package/templates/template/frontend/nginx.conf +40 -0
- package/templates/template/frontend/package.json +9 -10
- package/templates/template/frontend/src/App.tsx +18 -24
- package/templates/template/frontend/src/index.css +15 -1
- package/templates/template/package.json +11 -16
- package/templates/template/pnpm-workspace.yaml +4 -0
- package/templates/template/shared/collections/posts.ts +1 -1
- package/templates/template/shared/package.json +25 -25
- package/templates/template/backend/scripts/db-generate.ts +0 -60
|
@@ -1,25 +1,70 @@
|
|
|
1
|
-
|
|
1
|
+
# ─── Multi-stage production Dockerfile for the Rebase backend ─────────
|
|
2
|
+
# Produces a minimal image (~150MB) with only the runtime needed.
|
|
3
|
+
#
|
|
4
|
+
# Build context: the project root (where pnpm-workspace.yaml lives)
|
|
5
|
+
# Usage:
|
|
6
|
+
# docker build -t my-app-backend -f backend/Dockerfile .
|
|
7
|
+
# docker run -p 3001:3001 --env-file .env my-app-backend
|
|
8
|
+
|
|
9
|
+
# ── Stage 1: Install + Build ─────────────────────────────────────────
|
|
10
|
+
FROM node:24-alpine AS builder
|
|
11
|
+
|
|
2
12
|
ENV PNPM_HOME="/pnpm"
|
|
3
13
|
ENV PATH="$PNPM_HOME:$PATH"
|
|
4
14
|
RUN corepack enable
|
|
15
|
+
|
|
16
|
+
# Native dependencies for bcrypt, pg, etc.
|
|
5
17
|
RUN apk add --no-cache python3 make g++
|
|
6
18
|
|
|
7
19
|
WORKDIR /app
|
|
8
20
|
|
|
9
|
-
# Copy root
|
|
10
|
-
COPY package.json ./
|
|
21
|
+
# Copy workspace root files first (cache-friendly layer)
|
|
22
|
+
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
11
23
|
|
|
12
|
-
# Copy
|
|
24
|
+
# Copy workspace packages
|
|
13
25
|
COPY backend ./backend
|
|
14
26
|
COPY shared ./shared
|
|
15
27
|
|
|
16
|
-
# Install
|
|
17
|
-
RUN pnpm install
|
|
28
|
+
# Install all deps (including devDependencies for build)
|
|
29
|
+
RUN pnpm install --frozen-lockfile
|
|
30
|
+
|
|
31
|
+
# Build shared first, then backend
|
|
32
|
+
RUN pnpm --filter "*-shared" run build
|
|
33
|
+
RUN pnpm --filter "*-backend" run build
|
|
34
|
+
|
|
35
|
+
# Prune dev dependencies for a smaller runtime
|
|
36
|
+
RUN pnpm install --frozen-lockfile --prod
|
|
37
|
+
|
|
38
|
+
# ── Stage 2: Production Runtime ──────────────────────────────────────
|
|
39
|
+
FROM node:24-alpine AS runtime
|
|
40
|
+
|
|
41
|
+
ENV PNPM_HOME="/pnpm"
|
|
42
|
+
ENV PATH="$PNPM_HOME:$PATH"
|
|
43
|
+
ENV NODE_ENV=production
|
|
44
|
+
|
|
45
|
+
RUN corepack enable
|
|
46
|
+
|
|
47
|
+
# Security: run as non-root
|
|
48
|
+
RUN addgroup -g 1001 rebase && adduser -u 1001 -G rebase -s /bin/sh -D rebase
|
|
49
|
+
|
|
50
|
+
WORKDIR /app
|
|
18
51
|
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
52
|
+
# Copy only production artifacts
|
|
53
|
+
COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml ./
|
|
54
|
+
COPY --from=builder /app/node_modules ./node_modules
|
|
55
|
+
COPY --from=builder /app/backend ./backend
|
|
56
|
+
COPY --from=builder /app/shared ./shared
|
|
57
|
+
|
|
58
|
+
# Create uploads directory
|
|
59
|
+
RUN mkdir -p /app/backend/uploads && chown -R rebase:rebase /app
|
|
60
|
+
|
|
61
|
+
USER rebase
|
|
22
62
|
|
|
23
63
|
WORKDIR /app/backend
|
|
24
64
|
EXPOSE 3001
|
|
65
|
+
|
|
66
|
+
# Health check for orchestrators (Docker Compose, ECS, k8s)
|
|
67
|
+
HEALTHCHECK --interval=30s --timeout=5s --retries=3 --start-period=10s \
|
|
68
|
+
CMD wget --no-verbose --tries=1 --spider http://localhost:3001/health || exit 1
|
|
69
|
+
|
|
25
70
|
CMD ["pnpm", "start"]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Example custom function route.
|
|
5
|
+
*
|
|
6
|
+
* This file is auto-discovered by Rebase and mounted at:
|
|
7
|
+
* POST /api/functions/hello
|
|
8
|
+
* GET /api/functions/hello
|
|
9
|
+
*
|
|
10
|
+
* Call from the client SDK:
|
|
11
|
+
* const result = await client.call("functions/hello", { name: "World" });
|
|
12
|
+
*
|
|
13
|
+
* This is a standard Hono app — use any Hono middleware,
|
|
14
|
+
* define any HTTP methods, access the request/response directly.
|
|
15
|
+
* The authenticated user and RLS-scoped driver are available
|
|
16
|
+
* via c.get("user") and c.get("driver").
|
|
17
|
+
*/
|
|
18
|
+
const app = new Hono();
|
|
19
|
+
|
|
20
|
+
app.post("/", async (c) => {
|
|
21
|
+
const body = await c.req.json().catch(() => ({}));
|
|
22
|
+
const user = c.get("user") as any;
|
|
23
|
+
|
|
24
|
+
return c.json({
|
|
25
|
+
message: `Hello, ${body.name || "World"}!`,
|
|
26
|
+
user: user?.userId || "anonymous",
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
app.get("/", (c) => {
|
|
31
|
+
return c.json({ status: "ok", endpoint: "hello" });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export default app;
|
|
@@ -1,36 +1,32 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"drizzle-kit": "^0.31.4",
|
|
33
|
-
"tsx": "^4.20.6",
|
|
34
|
-
"typescript": "^5.9.2"
|
|
35
|
-
}
|
|
2
|
+
"name": "{{PROJECT_NAME}}-backend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Rebase backend with PostgreSQL",
|
|
5
|
+
"main": "src/index.ts",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "tsx watch --watch=\"../shared/**/*\" src/index.ts",
|
|
9
|
+
"build": "tsc --noEmit",
|
|
10
|
+
"start": "tsx src/index.ts"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"{{PROJECT_NAME}}-shared": "0.0.1",
|
|
14
|
+
"@rebasepro/server-core": "workspace:*",
|
|
15
|
+
"@rebasepro/server-postgresql": "workspace:*",
|
|
16
|
+
"drizzle-orm": "^0.44.4",
|
|
17
|
+
"hono": "^4.2.0",
|
|
18
|
+
"@hono/node-server": "^1.11.0",
|
|
19
|
+
"pg": "^8.11.3",
|
|
20
|
+
"ws": "^8.16.0",
|
|
21
|
+
"zod": "^3.22.4",
|
|
22
|
+
"dotenv": "^16.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/pg": "^8.6.5",
|
|
26
|
+
"@types/node": "^20.10.5",
|
|
27
|
+
"@types/ws": "^8.5.10",
|
|
28
|
+
"drizzle-kit": "^0.31.4",
|
|
29
|
+
"tsx": "^4.20.6",
|
|
30
|
+
"typescript": "^5.9.2"
|
|
31
|
+
}
|
|
36
32
|
}
|
|
@@ -1,128 +1,90 @@
|
|
|
1
|
-
import
|
|
2
|
-
import cors from "cors";
|
|
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";
|
|
3
6
|
import path from "path";
|
|
4
7
|
import { fileURLToPath } from "url";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
import {
|
|
9
|
+
initializeRebaseBackend,
|
|
10
|
+
serveSPA,
|
|
11
|
+
HonoEnv
|
|
12
|
+
} from "@rebasepro/server-core";
|
|
13
|
+
import { createPostgresDatabaseConnection, createPostgresBootstrapper } from "@rebasepro/server-postgresql";
|
|
8
14
|
import { enums, relations, tables } from "./schema.generated";
|
|
9
|
-
|
|
10
15
|
import * as dotenv from "dotenv";
|
|
11
16
|
|
|
12
17
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
18
|
const __dirname = path.dirname(__filename);
|
|
14
19
|
|
|
15
|
-
// Load environment from project root
|
|
16
20
|
dotenv.config({ path: path.resolve(__dirname, "../../.env") });
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
const
|
|
22
|
+
// ─── App ─────────────────────────────────────────────────────────────
|
|
23
|
+
const app = new Hono<HonoEnv>();
|
|
24
|
+
|
|
25
|
+
app.use("/*", cors({
|
|
26
|
+
origin: process.env.NODE_ENV === "production"
|
|
27
|
+
? [process.env.FRONTEND_URL || "https://yourdomain.com"]
|
|
28
|
+
: ["http://localhost:5173", "http://localhost:3000"],
|
|
29
|
+
credentials: true
|
|
30
|
+
}));
|
|
20
31
|
|
|
21
|
-
|
|
32
|
+
app.use("/*", secureHeaders());
|
|
33
|
+
|
|
34
|
+
// ─── Database ────────────────────────────────────────────────────────
|
|
22
35
|
const databaseUrl = process.env.DATABASE_URL;
|
|
23
|
-
if (!databaseUrl)
|
|
24
|
-
throw new Error("DATABASE_URL environment variable is not set");
|
|
25
|
-
}
|
|
26
|
-
const db = createPostgresDatabaseConnection(databaseUrl);
|
|
36
|
+
if (!databaseUrl) throw new Error("DATABASE_URL is not set");
|
|
27
37
|
|
|
28
|
-
|
|
29
|
-
app.use(cors());
|
|
30
|
-
app.use(express.json({ limit: "10mb" }));
|
|
38
|
+
const { db, connectionString } = createPostgresDatabaseConnection(databaseUrl);
|
|
31
39
|
|
|
40
|
+
// ─── Start ───────────────────────────────────────────────────────────
|
|
32
41
|
async function startServer() {
|
|
33
42
|
const jwtSecret = process.env.JWT_SECRET;
|
|
34
|
-
if (!jwtSecret)
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
if (!jwtSecret) throw new Error("JWT_SECRET is not set");
|
|
44
|
+
|
|
45
|
+
const PORT = parseInt(process.env.PORT || "3001", 10);
|
|
46
|
+
const server = createServer(getRequestListener(app.fetch));
|
|
37
47
|
|
|
38
|
-
// Initialize Rebase Backend with auth
|
|
39
48
|
const backend = await initializeRebaseBackend({
|
|
40
49
|
collectionsDir: path.resolve(__dirname, "../../shared/collections"),
|
|
50
|
+
functionsDir: path.resolve(__dirname, "../functions"),
|
|
41
51
|
server,
|
|
42
52
|
app,
|
|
43
|
-
|
|
44
|
-
|
|
53
|
+
bootstrappers: [
|
|
54
|
+
createPostgresBootstrapper({
|
|
45
55
|
connection: db,
|
|
46
56
|
schema: { tables, enums, relations },
|
|
47
|
-
adminConnectionString: process.env.ADMIN_CONNECTION_STRING ||
|
|
48
|
-
|
|
49
|
-
|
|
57
|
+
adminConnectionString: process.env.ADMIN_CONNECTION_STRING || databaseUrl,
|
|
58
|
+
connectionString
|
|
59
|
+
})
|
|
60
|
+
],
|
|
50
61
|
auth: {
|
|
51
62
|
jwtSecret,
|
|
52
63
|
accessExpiresIn: process.env.JWT_ACCESS_EXPIRES_IN || "1h",
|
|
53
64
|
refreshExpiresIn: process.env.JWT_REFRESH_EXPIRES_IN || "30d",
|
|
54
|
-
google: process.env.GOOGLE_CLIENT_ID
|
|
55
|
-
clientId: process.env.GOOGLE_CLIENT_ID
|
|
56
|
-
|
|
65
|
+
google: process.env.GOOGLE_CLIENT_ID
|
|
66
|
+
? { clientId: process.env.GOOGLE_CLIENT_ID }
|
|
67
|
+
: undefined,
|
|
57
68
|
seedDefaultRoles: true,
|
|
58
|
-
allowRegistration: process.env.ALLOW_REGISTRATION === "true"
|
|
59
|
-
email: process.env.SMTP_HOST ? {
|
|
60
|
-
from: process.env.SMTP_FROM || "noreply@example.com",
|
|
61
|
-
appName: process.env.APP_NAME || "Rebase",
|
|
62
|
-
resetPasswordUrl: process.env.FRONTEND_URL || "http://localhost:5173",
|
|
63
|
-
verifyEmailUrl: process.env.FRONTEND_URL || "http://localhost:5173",
|
|
64
|
-
smtp: {
|
|
65
|
-
host: process.env.SMTP_HOST,
|
|
66
|
-
port: parseInt(process.env.SMTP_PORT || "587"),
|
|
67
|
-
secure: process.env.SMTP_SECURE === "true",
|
|
68
|
-
auth: process.env.SMTP_USER ? {
|
|
69
|
-
user: process.env.SMTP_USER,
|
|
70
|
-
pass: process.env.SMTP_PASS || ""
|
|
71
|
-
} : undefined
|
|
72
|
-
}
|
|
73
|
-
} : undefined
|
|
69
|
+
allowRegistration: process.env.ALLOW_REGISTRATION === "true"
|
|
74
70
|
},
|
|
75
71
|
storage: {
|
|
76
72
|
type: "local",
|
|
77
73
|
basePath: path.resolve(__dirname, "../../uploads")
|
|
78
|
-
}
|
|
74
|
+
},
|
|
75
|
+
history: true,
|
|
79
76
|
});
|
|
80
77
|
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
basePath: "/api",
|
|
84
|
-
collectionsDir: path.resolve(__dirname, "../../shared/collections"),
|
|
85
|
-
enableGraphQL: true,
|
|
86
|
-
enableREST: true,
|
|
87
|
-
cors: {
|
|
88
|
-
origin: true,
|
|
89
|
-
credentials: true
|
|
90
|
-
}
|
|
91
|
-
});
|
|
78
|
+
// ─── Your routes ─────────────────────────────────────────────
|
|
79
|
+
app.get("/health", (c) => c.json({ status: "ok" }));
|
|
92
80
|
|
|
93
|
-
// Serve
|
|
81
|
+
// Serve the frontend in production
|
|
94
82
|
if (process.env.NODE_ENV === "production") {
|
|
95
|
-
serveSPA(app, {
|
|
96
|
-
frontendPath: path.join(__dirname, "../../frontend/dist"),
|
|
97
|
-
excludePaths: ["/health"]
|
|
98
|
-
});
|
|
83
|
+
serveSPA(app, { frontendPath: path.join(__dirname, "../../frontend/dist") });
|
|
99
84
|
}
|
|
100
85
|
|
|
101
|
-
app.get("/health", (req, res) => {
|
|
102
|
-
res.json({
|
|
103
|
-
status: "ok",
|
|
104
|
-
timestamp: new Date().toISOString(),
|
|
105
|
-
environment: process.env.NODE_ENV
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// Error handling
|
|
110
|
-
app.use((error: Error, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
|
|
111
|
-
console.error("Unhandled error:", error);
|
|
112
|
-
res.status(500).json({
|
|
113
|
-
error: "Internal server error",
|
|
114
|
-
message: error.message
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
const PORT = process.env.PORT || 3001;
|
|
119
|
-
|
|
120
86
|
server.listen(PORT, () => {
|
|
121
87
|
console.log(`🚀 Server running at http://localhost:${PORT}`);
|
|
122
|
-
console.log(` • REST API: http://localhost:${PORT}/api`);
|
|
123
|
-
console.log(` • GraphQL: http://localhost:${PORT}/api/graphql`);
|
|
124
|
-
console.log(` • Swagger docs: http://localhost:${PORT}/api/swagger`);
|
|
125
|
-
console.log(` • Health: http://localhost:${PORT}/health`);
|
|
126
88
|
});
|
|
127
89
|
}
|
|
128
90
|
|
|
@@ -131,4 +93,4 @@ startServer().catch(err => {
|
|
|
131
93
|
process.exit(1);
|
|
132
94
|
});
|
|
133
95
|
|
|
134
|
-
export { app
|
|
96
|
+
export { app };
|
|
@@ -1,48 +1,97 @@
|
|
|
1
|
-
|
|
1
|
+
# ─── Rebase — Production Docker Compose ──────────────────────────────
|
|
2
|
+
#
|
|
3
|
+
# Quick start:
|
|
4
|
+
# cp .env.template .env # Edit with your secrets
|
|
5
|
+
# docker compose up -d # Start everything
|
|
6
|
+
#
|
|
7
|
+
# This runs:
|
|
8
|
+
# 1. PostgreSQL 18 with persistent data
|
|
9
|
+
# 2. Backend (Node.js / Hono) with health checks
|
|
10
|
+
# 3. Frontend (nginx) serving the Vite build
|
|
11
|
+
#
|
|
12
|
+
# For development, use `pnpm dev` instead.
|
|
13
|
+
# ─────────────────────────────────────────────────────────────────────
|
|
2
14
|
|
|
3
15
|
services:
|
|
16
|
+
# ── PostgreSQL ───────────────────────────────────────────────────────
|
|
4
17
|
db:
|
|
5
18
|
image: postgres:18-alpine
|
|
19
|
+
restart: unless-stopped
|
|
6
20
|
environment:
|
|
7
|
-
POSTGRES_USER: rebase
|
|
8
|
-
POSTGRES_PASSWORD:
|
|
9
|
-
POSTGRES_DB: rebase
|
|
21
|
+
POSTGRES_USER: ${POSTGRES_USER:-rebase}
|
|
22
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
|
|
23
|
+
POSTGRES_DB: ${POSTGRES_DB:-rebase}
|
|
10
24
|
ports:
|
|
11
|
-
- "5432:5432"
|
|
25
|
+
- "${POSTGRES_PORT:-5432}:5432"
|
|
12
26
|
volumes:
|
|
13
27
|
- postgres_data:/var/lib/postgresql/data
|
|
14
28
|
healthcheck:
|
|
15
|
-
test: ["CMD-SHELL", "pg_isready -U rebase"]
|
|
29
|
+
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-rebase} -d ${POSTGRES_DB:-rebase}"]
|
|
16
30
|
interval: 5s
|
|
17
31
|
timeout: 5s
|
|
18
|
-
retries:
|
|
32
|
+
retries: 10
|
|
33
|
+
start_period: 10s
|
|
34
|
+
# Production tuning (adjust for your workload)
|
|
35
|
+
command:
|
|
36
|
+
- "postgres"
|
|
37
|
+
- "-c"
|
|
38
|
+
- "shared_buffers=256MB"
|
|
39
|
+
- "-c"
|
|
40
|
+
- "max_connections=100"
|
|
41
|
+
- "-c"
|
|
42
|
+
- "work_mem=4MB"
|
|
43
|
+
- "-c"
|
|
44
|
+
- "effective_cache_size=768MB"
|
|
45
|
+
- "-c"
|
|
46
|
+
- "log_min_duration_statement=1000"
|
|
19
47
|
|
|
48
|
+
# ── Backend ──────────────────────────────────────────────────────────
|
|
20
49
|
backend:
|
|
21
|
-
build:
|
|
50
|
+
build:
|
|
22
51
|
context: .
|
|
23
52
|
dockerfile: backend/Dockerfile
|
|
53
|
+
restart: unless-stopped
|
|
24
54
|
ports:
|
|
25
|
-
- "3001:3001"
|
|
55
|
+
- "${PORT:-3001}:3001"
|
|
26
56
|
environment:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
57
|
+
DATABASE_URL: postgresql://${POSTGRES_USER:-rebase}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-rebase}
|
|
58
|
+
ADMIN_CONNECTION_STRING: postgresql://${POSTGRES_USER:-rebase}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB:-rebase}
|
|
59
|
+
JWT_SECRET: ${JWT_SECRET:?Set JWT_SECRET in .env}
|
|
60
|
+
JWT_ACCESS_EXPIRES_IN: ${JWT_ACCESS_EXPIRES_IN:-1h}
|
|
61
|
+
JWT_REFRESH_EXPIRES_IN: ${JWT_REFRESH_EXPIRES_IN:-30d}
|
|
62
|
+
NODE_ENV: production
|
|
63
|
+
PORT: "3001"
|
|
64
|
+
ALLOW_REGISTRATION: ${ALLOW_REGISTRATION:-false}
|
|
65
|
+
FRONTEND_URL: ${FRONTEND_URL:-http://localhost}
|
|
66
|
+
# Optional Google OAuth
|
|
67
|
+
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-}
|
|
68
|
+
# Optional SMTP
|
|
69
|
+
SMTP_HOST: ${SMTP_HOST:-}
|
|
70
|
+
SMTP_PORT: ${SMTP_PORT:-}
|
|
71
|
+
SMTP_SECURE: ${SMTP_SECURE:-}
|
|
72
|
+
SMTP_USER: ${SMTP_USER:-}
|
|
73
|
+
SMTP_PASS: ${SMTP_PASS:-}
|
|
74
|
+
SMTP_FROM: ${SMTP_FROM:-}
|
|
75
|
+
APP_NAME: ${APP_NAME:-Rebase}
|
|
30
76
|
depends_on:
|
|
31
77
|
db:
|
|
32
78
|
condition: service_healthy
|
|
33
79
|
volumes:
|
|
34
|
-
-
|
|
80
|
+
- uploads:/app/backend/uploads
|
|
35
81
|
|
|
82
|
+
# ── Frontend ─────────────────────────────────────────────────────────
|
|
36
83
|
frontend:
|
|
37
84
|
build:
|
|
38
85
|
context: .
|
|
39
86
|
dockerfile: frontend/Dockerfile
|
|
87
|
+
restart: unless-stopped
|
|
40
88
|
ports:
|
|
41
|
-
- "
|
|
42
|
-
environment:
|
|
43
|
-
- VITE_API_URL=http://localhost:3001
|
|
89
|
+
- "${FRONTEND_PORT:-80}:80"
|
|
44
90
|
depends_on:
|
|
45
91
|
- backend
|
|
46
92
|
|
|
47
93
|
volumes:
|
|
48
94
|
postgres_data:
|
|
95
|
+
driver: local
|
|
96
|
+
uploads:
|
|
97
|
+
driver: local
|
|
@@ -1,28 +1,50 @@
|
|
|
1
|
-
|
|
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
|
+
|
|
2
11
|
ENV PNPM_HOME="/pnpm"
|
|
3
12
|
ENV PATH="$PNPM_HOME:$PATH"
|
|
4
13
|
RUN corepack enable
|
|
14
|
+
|
|
5
15
|
RUN apk add --no-cache python3 make g++
|
|
6
16
|
|
|
7
17
|
WORKDIR /app
|
|
8
18
|
|
|
9
|
-
# Copy root
|
|
10
|
-
COPY package.json ./
|
|
19
|
+
# Copy workspace root files
|
|
20
|
+
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
|
11
21
|
|
|
12
|
-
# Copy
|
|
22
|
+
# Copy workspace packages
|
|
13
23
|
COPY frontend ./frontend
|
|
14
24
|
COPY shared ./shared
|
|
15
25
|
|
|
16
26
|
# Install dependencies
|
|
17
|
-
RUN pnpm install
|
|
27
|
+
RUN pnpm install --frozen-lockfile
|
|
28
|
+
|
|
29
|
+
# Build shared first, then frontend
|
|
30
|
+
RUN pnpm --filter "*-shared" run build
|
|
31
|
+
RUN pnpm --filter "*-frontend" run build
|
|
32
|
+
|
|
33
|
+
# ── Stage 2: Serve with nginx ────────────────────────────────────────
|
|
34
|
+
FROM nginx:1.27-alpine AS runtime
|
|
35
|
+
|
|
36
|
+
# Remove default nginx page
|
|
37
|
+
RUN rm -rf /usr/share/nginx/html/*
|
|
38
|
+
|
|
39
|
+
# Copy built assets
|
|
40
|
+
COPY --from=builder /app/frontend/dist /usr/share/nginx/html
|
|
41
|
+
|
|
42
|
+
# Custom nginx config for SPA routing + compression
|
|
43
|
+
COPY frontend/nginx.conf /etc/nginx/conf.d/default.conf
|
|
18
44
|
|
|
19
|
-
|
|
20
|
-
RUN pnpm run build:shared
|
|
21
|
-
RUN pnpm run build:frontend
|
|
45
|
+
EXPOSE 80
|
|
22
46
|
|
|
23
|
-
|
|
24
|
-
|
|
47
|
+
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
|
|
48
|
+
CMD wget --no-verbose --tries=1 --spider http://localhost:80/ || exit 1
|
|
25
49
|
|
|
26
|
-
|
|
27
|
-
EXPOSE 5173
|
|
28
|
-
CMD ["serve", "-s", "dist", "-l", "5173"]
|
|
50
|
+
CMD ["nginx", "-g", "daemon off;"]
|
|
@@ -0,0 +1,40 @@
|
|
|
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
|
+
}
|
|
@@ -4,17 +4,16 @@
|
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"dependencies": {
|
|
7
|
-
"@rebasepro/auth": "
|
|
8
|
-
"@rebasepro/core": "
|
|
9
|
-
"@rebasepro/
|
|
10
|
-
"@rebasepro/
|
|
11
|
-
"@rebasepro/
|
|
12
|
-
"@rebasepro/
|
|
13
|
-
"@rebasepro/
|
|
14
|
-
"@rebasepro/
|
|
15
|
-
"@rebasepro/ui": "^4.0.0",
|
|
7
|
+
"@rebasepro/auth": "workspace:*",
|
|
8
|
+
"@rebasepro/core": "workspace:*",
|
|
9
|
+
"@rebasepro/plugin-data-enhancement": "workspace:*",
|
|
10
|
+
"@rebasepro/client": "workspace:*",
|
|
11
|
+
"@rebasepro/cms": "workspace:*",
|
|
12
|
+
"@rebasepro/studio": "workspace:*",
|
|
13
|
+
"@rebasepro/types": "workspace:*",
|
|
14
|
+
"@rebasepro/ui": "workspace:*",
|
|
16
15
|
"@fontsource/jetbrains-mono": "^5.2.5",
|
|
17
|
-
"{{PROJECT_NAME}}-shared": "
|
|
16
|
+
"{{PROJECT_NAME}}-shared": "0.0.1",
|
|
18
17
|
"react": "^19.0.0",
|
|
19
18
|
"react-dom": "^19.0.0",
|
|
20
19
|
"react-router": "^7.0.0",
|