@rebasepro/cli 0.13.1-canary.gf57a27e → 0.14.0
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/bundle.d.ts +4 -3
- package/dist/commands/api-keys.d.ts +51 -0
- package/dist/commands/auth.d.ts +31 -0
- package/dist/commands/cloud/context.d.ts +143 -10
- package/dist/commands/cloud/databases.d.ts +38 -0
- package/dist/commands/cloud/deployments.d.ts +22 -0
- package/dist/commands/cloud/domains.d.ts +9 -0
- package/dist/commands/cloud/env.d.ts +50 -0
- package/dist/commands/cloud/extensions.d.ts +20 -0
- package/dist/commands/cloud/projects.d.ts +29 -0
- package/dist/commands/cloud/resources.d.ts +13 -0
- package/dist/commands/dev.d.ts +18 -0
- package/dist/commands/eject.d.ts +42 -0
- package/dist/commands/init.d.ts +16 -0
- package/dist/commands/skills.d.ts +81 -0
- package/dist/index.es.js +1741 -654
- package/dist/index.es.js.map +1 -1
- package/dist/manifest.d.ts +16 -1
- package/dist/utils/args.d.ts +76 -0
- package/package.json +7 -7
- package/templates/eject/Dockerfile +29 -4
- package/templates/eject/backend/src/index.ts +60 -8
- package/templates/eject/docker-compose.custom.yml +9 -1
- package/templates/overlays/baas/backend/tsconfig.json +6 -1
- package/templates/overlays/baas/config/index.ts +9 -0
- package/templates/overlays/baas/config/package.json +1 -0
- package/templates/template/backend/functions/hello.ts +8 -4
- package/templates/template/backend/tsconfig.json +6 -1
- package/templates/template/frontend/vite.config.ts +33 -2
package/dist/manifest.d.ts
CHANGED
|
@@ -54,7 +54,22 @@ export declare function manifestExists(projectRoot: string): boolean;
|
|
|
54
54
|
* available behaviour.
|
|
55
55
|
*/
|
|
56
56
|
export declare function loadManifest(projectRoot: string): LoadedManifest;
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Write a manifest, with a trailing newline so it plays well with other tools.
|
|
59
|
+
*
|
|
60
|
+
* **Every key on disk survives.** This used to emit exactly `$schema`, `rebase`
|
|
61
|
+
* and `apps`, so a rewrite deleted the rest of the file — and two commands with
|
|
62
|
+
* no visible relationship to either key rewrite it: `rebase eject` and
|
|
63
|
+
* `rebase apps init --force`. A repository that had committed
|
|
64
|
+
* `"telemetry": false` lost its opt-out, and a multi-bucket project lost its
|
|
65
|
+
* whole `storage` block, in a commit whose stated change was `runtime: custom`.
|
|
66
|
+
*
|
|
67
|
+
* So: the caller's manifest wins for what it models, the file supplies the rest.
|
|
68
|
+
* `storage` and `telemetry` fall back to the file because the callers that
|
|
69
|
+
* synthesize a manifest (`apps init --force`) cannot know them — they are
|
|
70
|
+
* authored, not inferred — and unknown top-level keys are copied verbatim
|
|
71
|
+
* rather than listed, since a hand-listed set loses the next key too.
|
|
72
|
+
*/
|
|
58
73
|
export declare function writeManifest(projectRoot: string, manifest: RebaseProjectManifest): string;
|
|
59
74
|
/** Find the single backend app, if this repository declares one. */
|
|
60
75
|
export declare function findBackendApp(manifest: RebaseProjectManifest): {
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argument parsing for the commands that take positional arguments.
|
|
3
|
+
*
|
|
4
|
+
* Every command in the small group used to parse its own line the same way:
|
|
5
|
+
* `arg(spec, { argv: rawArgs.slice(4), permissive: true })`, then read `_[0]`
|
|
6
|
+
* and `_[1]`. Both halves of that are wrong in a way that costs data.
|
|
7
|
+
*
|
|
8
|
+
* - **`permissive: true` turns an unknown flag into a positional.** `arg`
|
|
9
|
+
* pushes an undeclared flag into `_` as a bare token, so `_[1]` is whatever
|
|
10
|
+
* came second on the line, flag or not. `rebase auth reset-password
|
|
11
|
+
* bob@example.com --debug` set Bob's password to the literal `--debug` — and
|
|
12
|
+
* `--debug` is what `bin/rebase.js` prints after *every* failure as the thing
|
|
13
|
+
* to re-run with, so the single most likely next keystroke after a failed
|
|
14
|
+
* reset was the one that reset the account to a two-word string.
|
|
15
|
+
* - **`slice(4)` assumes the command words are at fixed indices.** They are
|
|
16
|
+
* not: a flag before the command shifts everything, so `rebase --debug auth
|
|
17
|
+
* reset-password bob@example.com NewPass1!` read the email as
|
|
18
|
+
* `reset-password` and the password as `bob@example.com`.
|
|
19
|
+
*
|
|
20
|
+
* So: parse the *whole* line — `rawArgs` is `process.argv` — against a spec
|
|
21
|
+
* strictly, with no permissive mode. `arg` then consumes every flag wherever it
|
|
22
|
+
* appears and rejects the ones nobody declared, which leaves `_` holding the
|
|
23
|
+
* command words followed by the real positionals, in order and at a known
|
|
24
|
+
* offset. An unrecognised flag becomes an error naming the command's help,
|
|
25
|
+
* which is the only safe answer: the alternative is guessing that it was meant
|
|
26
|
+
* as a value.
|
|
27
|
+
*
|
|
28
|
+
* `commands/cloud/index.ts` resolves its positionals against its own spec for
|
|
29
|
+
* the same reason; this is that idea for the commands whose positionals are
|
|
30
|
+
* credentials rather than resource names.
|
|
31
|
+
*/
|
|
32
|
+
import arg from "arg";
|
|
33
|
+
/**
|
|
34
|
+
* Flags accepted on top of whatever a command declares.
|
|
35
|
+
*
|
|
36
|
+
* `--debug` is read by `bin/rebase.js` off `process.argv` and never by a
|
|
37
|
+
* command, but it has to be *declared* somewhere or strict parsing rejects the
|
|
38
|
+
* exact flag the CLI tells people to add. `--help`/`-h` are answered by each
|
|
39
|
+
* command's dispatcher before any work happens.
|
|
40
|
+
*/
|
|
41
|
+
export declare const GLOBAL_COMMAND_FLAGS: {
|
|
42
|
+
readonly "--debug": BooleanConstructor;
|
|
43
|
+
readonly "--help": BooleanConstructor;
|
|
44
|
+
readonly "-h": "--help";
|
|
45
|
+
};
|
|
46
|
+
export interface ParsedCommand<S extends arg.Spec> {
|
|
47
|
+
/** The declared flags, as `arg` resolved them. */
|
|
48
|
+
flags: arg.Result<S>;
|
|
49
|
+
/** What is left after the command words — never a flag. */
|
|
50
|
+
positionals: string[];
|
|
51
|
+
/** `--help` or `-h` appeared anywhere on the line. */
|
|
52
|
+
help: boolean;
|
|
53
|
+
}
|
|
54
|
+
/** Did the line ask for help? Answered before dispatch, never by a handler. */
|
|
55
|
+
export declare function wantsHelp(rawArgs: string[]): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Resolve a command's flags and positionals from the full `process.argv`.
|
|
58
|
+
*
|
|
59
|
+
* `commandWords` is how many words name the command itself — 2 for
|
|
60
|
+
* `auth reset-password`, 1 for `start` — and is applied to the *parsed*
|
|
61
|
+
* positionals rather than to `argv`, so a flag placed before the command no
|
|
62
|
+
* longer shifts them.
|
|
63
|
+
*
|
|
64
|
+
* `command` names the command in error messages, e.g. `auth reset-password`.
|
|
65
|
+
*
|
|
66
|
+
* Throws on an unknown flag, on a positional that looks like a flag, and on
|
|
67
|
+
* more positionals than the command takes. `bin/rebase.js` turns each into a
|
|
68
|
+
* one-line `✗ …` and exit 1.
|
|
69
|
+
*/
|
|
70
|
+
export declare function parseCommandArgs<S extends arg.Spec>({ spec, rawArgs, commandWords, command, maxPositionals }: {
|
|
71
|
+
spec: S;
|
|
72
|
+
rawArgs: string[];
|
|
73
|
+
commandWords: number;
|
|
74
|
+
command: string;
|
|
75
|
+
maxPositionals?: number;
|
|
76
|
+
}): ParsedCommand<S>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Developer tools for Rebase projects",
|
|
5
5
|
"main": "./dist/index.es.js",
|
|
6
6
|
"module": "./dist/index.es.js",
|
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"execa": "^9.6.1",
|
|
32
32
|
"inquirer": "14.0.2",
|
|
33
33
|
"jiti": "^2.7.0",
|
|
34
|
-
"@rebasepro/agent-skills": "0.
|
|
35
|
-
"@rebasepro/
|
|
36
|
-
"@rebasepro/codegen": "0.
|
|
37
|
-
"@rebasepro/
|
|
38
|
-
"@rebasepro/server-postgres": "0.
|
|
39
|
-
"@rebasepro/types": "0.
|
|
34
|
+
"@rebasepro/agent-skills": "0.14.0",
|
|
35
|
+
"@rebasepro/server": "0.14.0",
|
|
36
|
+
"@rebasepro/codegen": "0.14.0",
|
|
37
|
+
"@rebasepro/client": "0.14.0",
|
|
38
|
+
"@rebasepro/server-postgres": "0.14.0",
|
|
39
|
+
"@rebasepro/types": "0.14.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "^26.1.2",
|
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
# not reach this project.
|
|
8
8
|
#
|
|
9
9
|
# Assumes pnpm, which is what `rebase init` scaffolds a workspace for. On npm,
|
|
10
|
-
#
|
|
10
|
+
# every line below that mentions pnpm has to change — the `pnpm install` and
|
|
11
|
+
# `pnpm --filter … run build` lines become `npm ci` and
|
|
12
|
+
# `npm run build --workspace <package>`, `CMD ["pnpm", "start"]` becomes
|
|
13
|
+
# `CMD ["npm", "start"]`, and the two `COPY` lines that name `pnpm-lock.yaml`
|
|
14
|
+
# and `pnpm-workspace.yaml` have to name `package-lock.json` instead. Those
|
|
15
|
+
# COPYs fail first, before any of the build lines runs.
|
|
11
16
|
#
|
|
12
17
|
# Build context: the project root (where pnpm-workspace.yaml lives)
|
|
13
18
|
# Usage:
|
|
@@ -33,15 +38,31 @@ WORKDIR /app
|
|
|
33
38
|
# Copy workspace root files first (cache-friendly layer)
|
|
34
39
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
|
|
35
40
|
|
|
36
|
-
# Copy workspace packages
|
|
41
|
+
# Copy workspace packages. Every workspace the lockfile has an importer for,
|
|
42
|
+
# so `--frozen-lockfile` below sees the same set of packages it was written from.
|
|
37
43
|
COPY backend ./backend
|
|
38
44
|
COPY config ./config
|
|
45
|
+
# {{#frontend}}
|
|
46
|
+
COPY frontend ./frontend
|
|
47
|
+
# {{/frontend}}
|
|
48
|
+
|
|
49
|
+
# The entrypoint reads rebase.json at boot — it is where this project declares
|
|
50
|
+
# its storage buckets, so that the platform, the console and this process all
|
|
51
|
+
# read one list. An image without it boots believing nothing was declared, and
|
|
52
|
+
# every upload lands in the wrong bucket or 501s.
|
|
53
|
+
COPY rebase.json ./
|
|
39
54
|
|
|
40
55
|
# Install all deps (including devDependencies for build)
|
|
41
56
|
RUN pnpm install --frozen-lockfile
|
|
42
57
|
|
|
43
|
-
# Build config first, then backend
|
|
58
|
+
# Build config first, then the rest: both the frontend and the backend import it.
|
|
44
59
|
RUN pnpm --filter "*-config" run build
|
|
60
|
+
# {{#frontend}}
|
|
61
|
+
# The image serves the built site itself (see the `serveSPA` call in
|
|
62
|
+
# backend/src/index.ts), so the frontend is built here rather than deployed
|
|
63
|
+
# separately. Vite reads no API URL: same origin, same container.
|
|
64
|
+
RUN pnpm --filter "*-frontend" run build
|
|
65
|
+
# {{/frontend}}
|
|
45
66
|
RUN pnpm --filter "*-backend" run build
|
|
46
67
|
|
|
47
68
|
# Prune dev dependencies for a smaller runtime
|
|
@@ -63,10 +84,14 @@ RUN addgroup -g 1001 rebase && adduser -u 1001 -G rebase -s /bin/sh -D rebase
|
|
|
63
84
|
WORKDIR /app
|
|
64
85
|
|
|
65
86
|
# Copy only production artifacts
|
|
66
|
-
COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml /app/.npmrc ./
|
|
87
|
+
COPY --from=builder /app/package.json /app/pnpm-lock.yaml /app/pnpm-workspace.yaml /app/.npmrc /app/rebase.json ./
|
|
67
88
|
COPY --from=builder /app/node_modules ./node_modules
|
|
68
89
|
COPY --from=builder /app/backend ./backend
|
|
69
90
|
COPY --from=builder /app/config ./config
|
|
91
|
+
# {{#frontend}}
|
|
92
|
+
# The built site only — its sources and devDependencies stay in the builder.
|
|
93
|
+
COPY --from=builder /app/frontend/dist ./frontend/dist
|
|
94
|
+
# {{/frontend}}
|
|
70
95
|
|
|
71
96
|
# Create uploads directory
|
|
72
97
|
RUN mkdir -p /app/backend/uploads && chown -R rebase:rebase /app
|
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import { Hono } from "hono";
|
|
1
|
+
import { Hono, type Context } from "hono";
|
|
2
2
|
import { cors } from "hono/cors";
|
|
3
3
|
import { secureHeaders } from "hono/secure-headers";
|
|
4
4
|
import { getRequestListener } from "@hono/node-server";
|
|
5
5
|
import { createServer } from "http";
|
|
6
|
+
import fs from "fs";
|
|
6
7
|
import path from "path";
|
|
7
8
|
import { fileURLToPath } from "url";
|
|
8
9
|
import {
|
|
9
10
|
initializeRebaseBackend,
|
|
10
11
|
installShutdownHandlers,
|
|
12
|
+
// {{#frontend}}
|
|
11
13
|
serveSPA,
|
|
14
|
+
// {{/frontend}}
|
|
12
15
|
HonoEnv,
|
|
13
16
|
listenWithPortRetry,
|
|
14
17
|
cleanupDevPortFile,
|
|
@@ -17,10 +20,14 @@ import {
|
|
|
17
20
|
logger
|
|
18
21
|
} from "@rebasepro/server";
|
|
19
22
|
import { createPostgresDatabaseConnection, createPostgresAdapter } from "@rebasepro/server-postgres";
|
|
23
|
+
// {{#collections}}
|
|
20
24
|
import { enums, relations, tables } from "./schema.generated.js";
|
|
25
|
+
// {{/collections}}
|
|
21
26
|
import { storageAuthorize } from "../../config/storage.js";
|
|
22
27
|
import { env } from "./env.js";
|
|
28
|
+
// {{#collections}}
|
|
23
29
|
import usersCollection from "../../config/collections/users.js";
|
|
30
|
+
// {{/collections}}
|
|
24
31
|
|
|
25
32
|
const __filename = fileURLToPath(import.meta.url);
|
|
26
33
|
const __dirname = path.dirname(__filename);
|
|
@@ -86,21 +93,44 @@ async function startServer() {
|
|
|
86
93
|
const PORT = env.PORT;
|
|
87
94
|
const server = createServer(getRequestListener(app.fetch));
|
|
88
95
|
|
|
96
|
+
// `backend/crons` holds the scheduled jobs — nightly backups among them.
|
|
97
|
+
// Passed only when the directory exists, because a configured `cronsDir`
|
|
98
|
+
// with nothing in it mounts the cron routes and warns at every boot.
|
|
99
|
+
// Sibling-relative like `functionsDir`: both compile alongside this file, so
|
|
100
|
+
// the same path is right from source and from `backend/dist/backend/src`.
|
|
101
|
+
const cronsDir = path.resolve(__dirname, "../crons");
|
|
102
|
+
|
|
89
103
|
const backend = await initializeRebaseBackend({
|
|
104
|
+
// {{#collections}}
|
|
90
105
|
collectionsDir: path.resolve(__dirname, "../../config/collections"),
|
|
106
|
+
// {{/collections}}
|
|
107
|
+
// {{^collections}}
|
|
108
|
+
// No `collectionsDir`: this project declares no collections in code, so
|
|
109
|
+
// the server derives them from the live database schema at boot —
|
|
110
|
+
// exactly what the managed runtime did for it.
|
|
111
|
+
// {{/collections}}
|
|
91
112
|
functionsDir: path.resolve(__dirname, "../functions"),
|
|
113
|
+
cronsDir: fs.existsSync(cronsDir) ? cronsDir : undefined,
|
|
92
114
|
server,
|
|
93
115
|
app,
|
|
94
116
|
database: createPostgresAdapter({
|
|
95
117
|
connection: db,
|
|
118
|
+
// {{#collections}}
|
|
96
119
|
schema: { tables,
|
|
97
120
|
enums,
|
|
98
121
|
relations },
|
|
122
|
+
// {{/collections}}
|
|
99
123
|
adminConnectionString: env.ADMIN_CONNECTION_STRING || databaseUrl,
|
|
100
124
|
connectionString
|
|
101
125
|
}),
|
|
102
126
|
auth: {
|
|
127
|
+
// {{#collections}}
|
|
103
128
|
collection: usersCollection,
|
|
129
|
+
// {{/collections}}
|
|
130
|
+
// {{^collections}}
|
|
131
|
+
// No users collection is declared here, so auth falls back to its
|
|
132
|
+
// own default users table — the same fallback a headless bundle gets.
|
|
133
|
+
// {{/collections}}
|
|
104
134
|
jwtSecret,
|
|
105
135
|
accessExpiresIn: env.JWT_ACCESS_EXPIRES_IN,
|
|
106
136
|
refreshExpiresIn: env.JWT_REFRESH_EXPIRES_IN,
|
|
@@ -173,13 +203,23 @@ pass: env.SMTP_PASS! }
|
|
|
173
203
|
//
|
|
174
204
|
// `requireAuth` answers 401 without a valid token; `requireAdmin` answers
|
|
175
205
|
// 403 without the `admin` role and must follow `requireAuth`. Note that
|
|
176
|
-
// `c.get("driver")` — the
|
|
177
|
-
// routers, so out here reach for
|
|
178
|
-
//
|
|
206
|
+
// `c.get("driver")` — the driver carrying the caller's identity — is only
|
|
207
|
+
// set inside the Rebase routers, so out here reach for
|
|
208
|
+
// `rebase.dataAsAdmin`. That one is **admin-scoped, not an RLS bypass**: it
|
|
209
|
+
// runs as `{ uid: "service", roles: ["admin"] }` and your policies still
|
|
210
|
+
// apply, evaluated against that identity — which is exactly an admin's
|
|
211
|
+
// reach, and therefore belongs behind one of those guards. (`rebase.sql()`
|
|
212
|
+
// is the real bypass: owner connection, no policies.)
|
|
179
213
|
|
|
180
214
|
// ─── Health check ─────────────────────────────────────────────
|
|
181
215
|
// Deliberately public: an orchestrator's probe has no token to send.
|
|
182
|
-
|
|
216
|
+
//
|
|
217
|
+
// Answered on both paths. `/health` is what an orchestrator probes and what
|
|
218
|
+
// the generated `docker-compose.yml` already points at, so it stays.
|
|
219
|
+
// `/api/health` is where a developer looks first, because every other route
|
|
220
|
+
// this server has is under `/api` — and a reverse proxy that forwards only
|
|
221
|
+
// `/api` to the backend can reach nothing else.
|
|
222
|
+
const healthCheck = async (c: Context<HonoEnv>) => {
|
|
183
223
|
const result = await backend.healthCheck();
|
|
184
224
|
const status = result.healthy ? 200 : 503;
|
|
185
225
|
return c.json({
|
|
@@ -187,12 +227,24 @@ pass: env.SMTP_PASS! }
|
|
|
187
227
|
latencyMs: result.latencyMs,
|
|
188
228
|
...(result.details ? { details: result.details } : {})
|
|
189
229
|
}, status);
|
|
190
|
-
}
|
|
230
|
+
};
|
|
231
|
+
app.get("/health", healthCheck);
|
|
232
|
+
app.get("/api/health", healthCheck);
|
|
191
233
|
|
|
192
|
-
//
|
|
234
|
+
// {{#frontend}}
|
|
235
|
+
// Serve the frontend in production.
|
|
236
|
+
//
|
|
237
|
+
// Four levels up, not two: in production this file runs compiled, from
|
|
238
|
+
// `backend/dist/backend/src`. The paths above are the same in both modes
|
|
239
|
+
// because `config/` is compiled alongside this file; `frontend/dist` is not
|
|
240
|
+
// in the compiled tree at all, so it stays where the repository put it.
|
|
241
|
+
// `serveSPA` only warns when the path is wrong, which is why the Dockerfile
|
|
242
|
+
// that builds this image also copies `frontend` — verify a mount by
|
|
243
|
+
// fetching `/`, never by reading the log.
|
|
193
244
|
if (isProduction) {
|
|
194
|
-
serveSPA(app, { frontendPath: path.
|
|
245
|
+
serveSPA(app, { frontendPath: path.resolve(__dirname, "../../../../frontend/dist") });
|
|
195
246
|
}
|
|
247
|
+
// {{/frontend}}
|
|
196
248
|
|
|
197
249
|
if (!isProduction) {
|
|
198
250
|
// Dev mode: retry the next port if the current one is in use
|
|
@@ -52,8 +52,16 @@ services:
|
|
|
52
52
|
ADMIN_CONNECTION_STRING: postgresql://rebase_app:${DATABASE_PASSWORD:-changeme}@db:5432/rebase?options=-c%20search_path=public
|
|
53
53
|
NODE_ENV: production
|
|
54
54
|
PORT: "3001"
|
|
55
|
+
# {{#frontend}}
|
|
55
56
|
# Your entrypoint serves the built frontend itself (see the `serveSPA`
|
|
56
|
-
# call in backend/src/index.ts
|
|
57
|
+
# call in backend/src/index.ts, and the frontend build stage in the
|
|
58
|
+
# Dockerfile), so this is one container, same origin.
|
|
59
|
+
# {{/frontend}}
|
|
60
|
+
# {{^frontend}}
|
|
61
|
+
# This image serves the API only — there is no frontend workspace in this
|
|
62
|
+
# project — so CORS_ORIGINS has to list the origins your clients are
|
|
63
|
+
# served from.
|
|
64
|
+
# {{/frontend}}
|
|
57
65
|
CORS_ORIGINS: ${CORS_ORIGINS:?set CORS_ORIGINS to the origin you browse to}
|
|
58
66
|
# A durable named volume, which is the case this flag acknowledges.
|
|
59
67
|
STORAGE_PATH: /uploads
|
|
@@ -21,5 +21,10 @@
|
|
|
21
21
|
//
|
|
22
22
|
// No `rootDir`: it would have to be `.` for both, and letting tsc infer the
|
|
23
23
|
// common root gets there without a second place to keep in sync.
|
|
24
|
-
|
|
24
|
+
//
|
|
25
|
+
// `crons/` too: `rebase build` compiles it through a generated tsconfig, but
|
|
26
|
+
// an ejected project builds with *this* file, so leaving it out meant an
|
|
27
|
+
// ejected server had no compiled jobs to load and silently stopped running
|
|
28
|
+
// every schedule.
|
|
29
|
+
"include": ["src/**/*", "functions/**/*", "crons/**/*"]
|
|
25
30
|
}
|
|
@@ -10,6 +10,15 @@
|
|
|
10
10
|
* a deployment with file storage enabled serves every user's files to every
|
|
11
11
|
* signed-in user. The server refuses to boot in that state, and this is the
|
|
12
12
|
* export it looks for.
|
|
13
|
+
*
|
|
14
|
+
* It depends on `@rebasepro/common` for one thing: `defineCollection`, which
|
|
15
|
+
* `rebase schema introspect` writes its output against. A plain
|
|
16
|
+
* `const x: PostgresCollectionConfig = { … }` annotation widens the property
|
|
17
|
+
* keys to `string`, so nothing checks the keys named elsewhere in the config —
|
|
18
|
+
* and the keys an introspected collection has are precisely the ones nobody
|
|
19
|
+
* typed and nobody remembers. `common` is the headless half of the pair: the
|
|
20
|
+
* same key inference the admin layer's builder gives a CMS project, with no
|
|
21
|
+
* admin surface and no React.
|
|
13
22
|
*/
|
|
14
23
|
|
|
15
24
|
export { storageAuthorize } from "./storage.js";
|
|
@@ -30,10 +30,14 @@ import { defineFunction, requireAuth, requireAdmin } from "@rebasepro/server";
|
|
|
30
30
|
* slot over `app.use("/*", requireAuth)`: `use()` only covers routes declared
|
|
31
31
|
* *below* it, so a route appended later above it is silently unprotected.
|
|
32
32
|
*
|
|
33
|
-
* `rebase.dataAsAdmin` gives you admin-level access to your data
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
33
|
+
* `rebase.dataAsAdmin` gives you admin-level access to your data: it runs as
|
|
34
|
+
* `{ uid: "service", roles: ["admin"] }`, which is **admin-scoped, not an RLS
|
|
35
|
+
* bypass** — your policies are still evaluated, just against that identity. So
|
|
36
|
+
* `policy.serverContext()` is false for it (that arm means "no uid at all"),
|
|
37
|
+
* and anything an `admin` user can reach, it can reach too. For request-scoped
|
|
38
|
+
* data access use c.get("user") and c.get("driver"), which carry the caller's
|
|
39
|
+
* identity. `rebase.sql()` is the real bypass — owner connection, no policies.
|
|
40
|
+
* (`rebase` also exposes auth, storage, email.)
|
|
37
41
|
*/
|
|
38
42
|
export default defineFunction((app, { rebase }) => {
|
|
39
43
|
void rebase; // available for dataAsAdmin/auth/storage/email — see commented usage below
|
|
@@ -36,5 +36,10 @@
|
|
|
36
36
|
// unnoticed until runtime. The inferred common root is already the project
|
|
37
37
|
// root because of `../config`, so this adds no `rootDir` and moves nothing
|
|
38
38
|
// that dist/ already contains.
|
|
39
|
-
|
|
39
|
+
//
|
|
40
|
+
// `crons/` for the same reason, and for one more: `rebase build` compiles it
|
|
41
|
+
// through a generated tsconfig, but an ejected project builds with *this*
|
|
42
|
+
// file — so leaving crons out meant an ejected server had no compiled jobs to
|
|
43
|
+
// load and stopped running every schedule, nightly backups included.
|
|
44
|
+
"include": ["src/**/*", "functions/**/*", "crons/**/*", "../config/**/*", "drizzle.config.ts"]
|
|
40
45
|
}
|
|
@@ -36,7 +36,28 @@ export default defineConfig({
|
|
|
36
36
|
rollupOptions: {
|
|
37
37
|
output: {
|
|
38
38
|
manualChunks(id) {
|
|
39
|
-
// Heavy vendor libraries — split into individually cached chunks
|
|
39
|
+
// Heavy vendor libraries — split into individually cached chunks.
|
|
40
|
+
//
|
|
41
|
+
// A name here says these modules travel TOGETHER. It does not
|
|
42
|
+
// say they travel late: a chunk becomes a static dependency of
|
|
43
|
+
// the entry — and so a `modulepreload` in index.html — the
|
|
44
|
+
// moment any one module in it is statically reachable. Naming
|
|
45
|
+
// a library that is only partly lazy therefore drags the lazy
|
|
46
|
+
// part onto the critical path. Read the two exceptions below
|
|
47
|
+
// before adding a line.
|
|
48
|
+
|
|
49
|
+
// @rollup/plugin-commonjs emits its shared helpers as two
|
|
50
|
+
// virtual modules ("\0commonjsHelpers.js" and
|
|
51
|
+
// "\0commonjs-dynamic-modules") that every CommonJS
|
|
52
|
+
// dependency reaches, the entry's included. They match no
|
|
53
|
+
// rule below, so Rollup parks them in one of the chunks
|
|
54
|
+
// that use them — and it chose `vendor-exceljs`, which
|
|
55
|
+
// meant the entry statically imported 940 kB of
|
|
56
|
+
// spreadsheet reader to get a ten-line `require` shim.
|
|
57
|
+
// Give the helpers a chunk of their own so they can never
|
|
58
|
+
// anchor a heavy one to the critical path.
|
|
59
|
+
if (id.includes("commonjsHelpers") || id.includes("commonjs-dynamic-modules")) return "vendor-commonjs-helpers";
|
|
60
|
+
|
|
40
61
|
if (id.includes("exceljs")) return "vendor-exceljs";
|
|
41
62
|
if (id.includes("prosemirror")) return "vendor-prosemirror";
|
|
42
63
|
if (id.includes("monaco-editor") || id.includes("@monaco-editor")) return "vendor-monaco";
|
|
@@ -45,6 +66,10 @@ export default defineConfig({
|
|
|
45
66
|
if (id.includes("prism-react-renderer")) return "vendor-prism";
|
|
46
67
|
if (id.includes("markdown-it")) return "vendor-markdown";
|
|
47
68
|
if (id.includes("react-dropzone")) return "vendor-dropzone";
|
|
69
|
+
// date-fns core only. The ~77 locales are imported one at a
|
|
70
|
+
// time by the admin's date preview; sharing a chunk name with
|
|
71
|
+
// the core would make all of them eager again.
|
|
72
|
+
if (id.includes("date-fns/locale")) return undefined;
|
|
48
73
|
if (id.includes("date-fns")) return "vendor-datefns";
|
|
49
74
|
if (id.includes("fuse.js")) return "vendor-fuse";
|
|
50
75
|
if (id.includes("node_modules/react-dom/")) return "vendor-react-dom";
|
|
@@ -56,7 +81,13 @@ export default defineConfig({
|
|
|
56
81
|
if (id.includes("node_modules/@floating-ui/")) return "vendor-floating-ui";
|
|
57
82
|
if (id.includes("node_modules/tailwind-merge/")) return "vendor-tailwind-merge";
|
|
58
83
|
if (id.includes("node_modules/notistack/")) return "vendor-notistack";
|
|
59
|
-
|
|
84
|
+
|
|
85
|
+
// lucide-react has no line on purpose. The ~130 icons the
|
|
86
|
+
// chrome imports by name are static; the by-name lookup map
|
|
87
|
+
// is fetched on demand. One chunk name cannot hold both apart,
|
|
88
|
+
// and naming it welded 822 kB of icons into the preload set.
|
|
89
|
+
// Left to Rollup, the named icons land in the entry and the
|
|
90
|
+
// map gets its own async chunk.
|
|
60
91
|
|
|
61
92
|
return undefined;
|
|
62
93
|
}
|