@rebasepro/cli 0.8.0 → 0.9.1-canary.09aaf62

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 (51) hide show
  1. package/README.md +32 -3
  2. package/dist/commands/cloud/auth.d.ts +3 -0
  3. package/dist/commands/cloud/context.d.ts +157 -0
  4. package/dist/commands/cloud/databases.d.ts +1 -0
  5. package/dist/commands/cloud/deploy.d.ts +2 -0
  6. package/dist/commands/cloud/deployments.d.ts +39 -0
  7. package/dist/commands/cloud/domains.d.ts +1 -0
  8. package/dist/commands/cloud/env.d.ts +6 -0
  9. package/dist/commands/cloud/extensions.d.ts +3 -0
  10. package/dist/commands/cloud/index.d.ts +1 -0
  11. package/dist/commands/cloud/link.d.ts +5 -0
  12. package/dist/commands/cloud/orgs.d.ts +1 -0
  13. package/dist/commands/cloud/power.d.ts +4 -0
  14. package/dist/commands/cloud/projects.d.ts +13 -0
  15. package/dist/commands/cloud/resources.d.ts +6 -0
  16. package/dist/commands/cloud/settings.d.ts +8 -0
  17. package/dist/commands/init.d.ts +23 -0
  18. package/dist/commands/skills.d.ts +1 -1
  19. package/dist/index.d.ts +1 -0
  20. package/dist/index.es.js +5347 -1804
  21. package/dist/index.es.js.map +1 -1
  22. package/dist/utils/package-manager.d.ts +19 -5
  23. package/dist/utils/project.d.ts +1 -1
  24. package/package.json +8 -7
  25. package/templates/overlays/baas/README.md +55 -0
  26. package/templates/overlays/baas/backend/package.json +31 -0
  27. package/templates/overlays/baas/backend/src/index.ts +192 -0
  28. package/templates/overlays/baas/backend/tsconfig.json +19 -0
  29. package/templates/overlays/baas/package.json +42 -0
  30. package/templates/overlays/baas/pnpm-workspace.yaml +9 -0
  31. package/templates/template/.env.example +27 -1
  32. package/templates/template/backend/functions/hello.ts +11 -7
  33. package/templates/template/backend/package.json +7 -7
  34. package/templates/template/backend/src/env.ts +32 -2
  35. package/templates/template/backend/src/index.ts +33 -27
  36. package/templates/template/config/collections/authors.ts +2 -2
  37. package/templates/template/config/collections/index.ts +20 -0
  38. package/templates/template/config/collections/posts.ts +2 -2
  39. package/templates/template/config/collections/presets/blank/index.ts +3 -1
  40. package/templates/template/config/collections/presets/ecommerce/categories.ts +2 -2
  41. package/templates/template/config/collections/presets/ecommerce/index.ts +3 -1
  42. package/templates/template/config/collections/presets/ecommerce/orders.ts +2 -2
  43. package/templates/template/config/collections/presets/ecommerce/products.ts +2 -2
  44. package/templates/template/config/collections/tags.ts +2 -2
  45. package/templates/template/config/collections/users.ts +4 -2
  46. package/templates/template/config/package.json +1 -2
  47. package/templates/template/frontend/package.json +2 -3
  48. package/templates/template/frontend/src/App.tsx +7 -5
  49. package/templates/template/frontend/vite.config.ts +15 -1
  50. package/templates/template/gitignore +31 -0
  51. package/templates/template/npmrc +10 -0
@@ -7,14 +7,14 @@ import path from "path";
7
7
  import { fileURLToPath } from "url";
8
8
  import {
9
9
  initializeRebaseBackend,
10
+ installShutdownHandlers,
10
11
  serveSPA,
11
12
  HonoEnv,
12
13
  listenWithPortRetry,
13
14
  cleanupDevPortFile,
14
15
  logger
15
- } from "@rebasepro/server-core";
16
- import type { SecurityRule } from "@rebasepro/types";
17
- import { createPostgresDatabaseConnection, createPostgresAdapter } from "@rebasepro/server-postgresql";
16
+ } from "@rebasepro/server";
17
+ import { createPostgresDatabaseConnection, createPostgresAdapter } from "@rebasepro/server-postgres";
18
18
  import { enums, relations, tables } from "./schema.generated.js";
19
19
  import { env } from "./env.js";
20
20
  import usersCollection from "../../config/collections/users.js";
@@ -39,10 +39,26 @@ const allowedOrigins = isProduction
39
39
  })()
40
40
  : [];
41
41
 
42
+ // In dev we still restrict which origins are reflected. Because `credentials`
43
+ // is enabled, reflecting an arbitrary Origin would let any website the
44
+ // developer happens to visit make credentialed cross-origin requests to this
45
+ // dev server (and read the responses) using the developer's session. So dev
46
+ // reflects only localhost origins; requests with no Origin (curl, same-origin)
47
+ // are unaffected.
48
+ const isLocalhostOrigin = (origin: string): boolean => {
49
+ try {
50
+ const { hostname } = new URL(origin);
51
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" || hostname === "[::1]";
52
+ } catch {
53
+ return false;
54
+ }
55
+ };
56
+
42
57
  app.use("/*", cors({
43
58
  origin: (origin) => {
44
- if (!isProduction) return origin || "*";
45
- return allowedOrigins.includes(origin) ? origin : null;
59
+ if (isProduction) return allowedOrigins.includes(origin) ? origin : null;
60
+ if (!origin) return "*";
61
+ return isLocalhostOrigin(origin) ? origin : null;
46
62
  },
47
63
  credentials: true
48
64
  }));
@@ -60,15 +76,6 @@ async function startServer() {
60
76
  const PORT = env.PORT;
61
77
  const server = createServer(getRequestListener(app.fetch));
62
78
 
63
- // Default security rules for collections that don't define their own.
64
- // Authenticated users can read all rows; only admins can write.
65
- const defaultSecurityRules: SecurityRule[] = [
66
- { operation: "select",
67
- access: "public" },
68
- { operations: ["insert", "update", "delete"],
69
- roles: ["admin"] }
70
- ];
71
-
72
79
  const backend = await initializeRebaseBackend({
73
80
  collectionsDir: path.resolve(__dirname, "../../config/collections"),
74
81
  functionsDir: path.resolve(__dirname, "../functions"),
@@ -88,6 +95,12 @@ relations },
88
95
  accessExpiresIn: env.JWT_ACCESS_EXPIRES_IN,
89
96
  refreshExpiresIn: env.JWT_REFRESH_EXPIRES_IN,
90
97
  serviceKey: env.REBASE_SERVICE_KEY,
98
+ // Cookie-based auth: the refresh token is stored in an httpOnly
99
+ // cookie (not readable by JS) instead of localStorage, so it is
100
+ // not exposed to XSS. The frontend opts in via
101
+ // `authFlowMode: "cookie"` on createRebaseClient. Requires CORS
102
+ // `credentials: true` (set above).
103
+ cookieAuth: { sameSite: "Lax" },
91
104
  google: env.GOOGLE_CLIENT_ID
92
105
  ? { clientId: env.GOOGLE_CLIENT_ID }
93
106
  : undefined,
@@ -124,8 +137,7 @@ pass: env.SMTP_PASS! }
124
137
  type: "local",
125
138
  basePath: env.STORAGE_PATH || path.resolve(__dirname, "../../uploads")
126
139
  },
127
- history: true,
128
- defaultSecurityRules
140
+ history: true
129
141
  });
130
142
 
131
143
  // ─── Health check ─────────────────────────────────────────────
@@ -163,17 +175,11 @@ pass: env.SMTP_PASS! }
163
175
  }
164
176
 
165
177
  // ─── Graceful Shutdown ───────────────────────────────────────────────
166
- // Uses the framework's built-in shutdown() which drains connections,
167
- // stops the cron scheduler, and force-exits after 15s timeout.
168
- const gracefulShutdown = async (signal: string) => {
169
- logger.info(`Received ${signal}, shutting down...`);
170
- await backend.shutdown();
171
- await pool.end();
172
- logger.info("Database pool closed");
173
- process.exit(0);
174
- };
175
- process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
176
- process.on("SIGINT", () => gracefulShutdown("SIGINT"));
178
+ // Drains HTTP, stops crons, tears down realtime, then closes the pool.
179
+ // Guards against double signals and force-exits if shutdown hangs.
180
+ installShutdownHandlers(backend, {
181
+ onCleanup: () => pool.end()
182
+ });
177
183
  }
178
184
 
179
185
  startServer().catch(err => {
@@ -1,6 +1,6 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
2
 
3
- const authorsCollection: EntityCollection = {
3
+ const authorsCollection: CollectionConfig = {
4
4
  name: "Authors",
5
5
  singularName: "Author",
6
6
  slug: "authors",
@@ -2,5 +2,25 @@ import postsCollection from "./posts.js";
2
2
  import authorsCollection from "./authors.js";
3
3
  import tagsCollection from "./tags.js";
4
4
  import usersCollection from "./users.js";
5
+ import type { SecurityRule } from "@rebasepro/types";
5
6
 
6
7
  export const collections = [postsCollection, authorsCollection, tagsCollection, usersCollection];
8
+
9
+ /**
10
+ * Applied to any collection in this directory that declares no
11
+ * `securityRules` of its own: anyone can read, only admins can write.
12
+ *
13
+ * These live here, next to the collections, because `rebase db push`
14
+ * generates the Postgres policies from these files — that is what actually
15
+ * enforces access. A default declared on the server could never reach the
16
+ * database.
17
+ *
18
+ * A collection that declares neither its own rules nor inherits these is
19
+ * locked by default: admin-only.
20
+ */
21
+ export const defaultSecurityRules: SecurityRule[] = [
22
+ { operation: "select",
23
+ access: "public" },
24
+ { operations: ["insert", "update", "delete"],
25
+ roles: ["admin"] }
26
+ ];
@@ -1,8 +1,8 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
2
  import authorsCollection from "./authors.js";
3
3
  import tagsCollection from "./tags.js";
4
4
 
5
- const postsCollection: EntityCollection = {
5
+ const postsCollection: CollectionConfig = {
6
6
  name: "Posts",
7
7
  singularName: "Post",
8
8
  slug: "posts",
@@ -1,3 +1,5 @@
1
- import usersCollection from "../../users.js";
1
+ // Resolves after `rebase init` copies this file into config/collections/,
2
+ // next to the shared users.ts — not from inside presets/, which never runs.
3
+ import usersCollection from "./users.js";
2
4
 
3
5
  export const collections = [usersCollection];
@@ -1,6 +1,6 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
2
 
3
- const categoriesCollection: EntityCollection = {
3
+ const categoriesCollection: CollectionConfig = {
4
4
  name: "Categories",
5
5
  singularName: "Category",
6
6
  slug: "categories",
@@ -1,6 +1,8 @@
1
1
  import productsCollection from "./products.js";
2
2
  import categoriesCollection from "./categories.js";
3
3
  import ordersCollection from "./orders.js";
4
- import usersCollection from "../../users.js";
4
+ // Resolves after `rebase init` copies this file into config/collections/,
5
+ // next to the shared users.ts — not from inside presets/, which never runs.
6
+ import usersCollection from "./users.js";
5
7
 
6
8
  export const collections = [productsCollection, categoriesCollection, ordersCollection, usersCollection];
@@ -1,6 +1,6 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
2
 
3
- const ordersCollection: EntityCollection = {
3
+ const ordersCollection: CollectionConfig = {
4
4
  name: "Orders",
5
5
  singularName: "Order",
6
6
  slug: "orders",
@@ -1,7 +1,7 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
2
  import categoriesCollection from "./categories.js";
3
3
 
4
- const productsCollection: EntityCollection = {
4
+ const productsCollection: CollectionConfig = {
5
5
  name: "Products",
6
6
  singularName: "Product",
7
7
  slug: "products",
@@ -1,6 +1,6 @@
1
- import { EntityCollection } from "@rebasepro/types";
1
+ import { CollectionConfig } from "@rebasepro/types";
2
2
 
3
- const tagsCollection: EntityCollection = {
3
+ const tagsCollection: CollectionConfig = {
4
4
  name: "Tags",
5
5
  singularName: "Tag",
6
6
  slug: "tags",
@@ -1,6 +1,6 @@
1
- import type { EntityCollection } from "@rebasepro/types";
1
+ import type { CollectionConfig } from "@rebasepro/types";
2
2
 
3
- const usersCollection: EntityCollection = {
3
+ const usersCollection: CollectionConfig = {
4
4
  name: "Users",
5
5
  singularName: "User",
6
6
  slug: "users",
@@ -66,6 +66,7 @@ roles: ["admin"] }
66
66
  name: "Password Hash",
67
67
  type: "string",
68
68
  columnName: "password_hash",
69
+ excludeFromApi: true,
69
70
  ui: {
70
71
  hideFromCollection: true,
71
72
  disabled: { hidden: true }
@@ -85,6 +86,7 @@ roles: ["admin"] }
85
86
  name: "Email Verification Token",
86
87
  type: "string",
87
88
  columnName: "email_verification_token",
89
+ excludeFromApi: true,
88
90
  ui: {
89
91
  hideFromCollection: true,
90
92
  disabled: { hidden: true }
@@ -12,8 +12,7 @@
12
12
  "clean": "rm -rf dist"
13
13
  },
14
14
  "dependencies": {
15
- "@rebasepro/types": "workspace:*",
16
- "@rebasepro/admin": "workspace:*"
15
+ "@rebasepro/types": "workspace:*"
17
16
  },
18
17
  "devDependencies": {
19
18
  "typescript": "^5.9.2"
@@ -4,9 +4,8 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "dependencies": {
7
- "@rebasepro/auth": "workspace:*",
8
- "@rebasepro/core": "workspace:*",
9
- "@rebasepro/plugin-data-enhancement": "workspace:*",
7
+ "@rebasepro/app": "workspace:*",
8
+ "@rebasepro/plugin-ai": "workspace:*",
10
9
  "@rebasepro/client": "workspace:*",
11
10
  "@rebasepro/admin": "workspace:*",
12
11
  "@rebasepro/studio": "workspace:*",
@@ -3,9 +3,8 @@ import React from "react";
3
3
  import "@fontsource/jetbrains-mono";
4
4
  import "@fontsource/rubik";
5
5
 
6
- import { useRebaseAuthController } from "@rebasepro/auth";
7
- import { Rebase, RebaseAuth } from "@rebasepro/core";
8
- import { RebaseCMS, RebaseShell } from "@rebasepro/admin";
6
+ import { Rebase, RebaseAuth, useRebaseAuthController } from "@rebasepro/app";
7
+ import { RebaseAdmin, RebaseShell } from "@rebasepro/admin";
9
8
  import { ErrorBoundary } from "@rebasepro/ui";
10
9
  import { RebaseStudio } from "@rebasepro/studio";
11
10
  import { createRebaseClient } from "@rebasepro/client";
@@ -17,7 +16,10 @@ const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID;
17
16
 
18
17
  export function App() {
19
18
  const rebaseClient = React.useMemo(() => createRebaseClient({
20
- baseUrl: API_URL
19
+ baseUrl: API_URL,
20
+ // Store the refresh token in an httpOnly cookie (XSS-safe) rather than
21
+ // localStorage. The backend issues it via `auth.cookieAuth`.
22
+ auth: { authFlowMode: "cookie" }
21
23
  }), []);
22
24
 
23
25
  const authController = useRebaseAuthController({
@@ -32,7 +34,7 @@ export function App() {
32
34
  authController={authController}
33
35
  >
34
36
  <RebaseAuth />
35
- <RebaseCMS
37
+ <RebaseAdmin
36
38
  collections={collections}
37
39
  />
38
40
  <RebaseStudio/>
@@ -3,10 +3,24 @@ import { defineConfig } from "vite";
3
3
  import react from "@vitejs/plugin-react";
4
4
  import svgr from "vite-plugin-svgr";
5
5
  import tailwindcss from "@tailwindcss/vite";
6
- import { rebaseCollectionsPlugin } from "@rebasepro/core/vitePlugin";
6
+ import { rebaseCollectionsPlugin } from "@rebasepro/app/vitePlugin";
7
7
 
8
8
  export default defineConfig({
9
9
  envDir: path.resolve(__dirname, ".."),
10
+ // Force a single copy of React and React Router across the app and all
11
+ // @rebasepro/* packages. Without this, a locally `link:`ed Rebase checkout
12
+ // resolves its own copies of react-router, producing "multiple copies of
13
+ // React" and "useBlocker must be used within a data router" errors in the
14
+ // admin. Safe to keep for npm-installed setups too.
15
+ resolve: {
16
+ dedupe: [
17
+ "react",
18
+ "react-dom",
19
+ "react-router",
20
+ "react-router-dom",
21
+ "@remix-run/router"
22
+ ]
23
+ },
10
24
  esbuild: {
11
25
  logOverride: { "this-is-undefined-in-esm": "silent" }
12
26
  },
@@ -0,0 +1,31 @@
1
+ # Dependencies
2
+ node_modules/
3
+
4
+ # Build output
5
+ dist/
6
+ build/
7
+
8
+ # Environment (secrets — .env.example is tracked)
9
+ .env
10
+ .env.local
11
+ !.env.example
12
+
13
+ # IDE
14
+ .idea/
15
+ .vscode/
16
+ *.swp
17
+ *.swo
18
+
19
+ # OS
20
+ .DS_Store
21
+ Thumbs.db
22
+
23
+ # Drizzle
24
+ drizzle/meta/
25
+
26
+ # Uploads
27
+ uploads/
28
+
29
+ # Rebase dev
30
+ .rebase-dev-url
31
+ .rebase-dev-port
@@ -0,0 +1,10 @@
1
+ # Ensure pnpm links local workspace packages when version specifiers
2
+ # like "*" are used (instead of the pnpm-specific "workspace:*" protocol).
3
+ # This makes the project compatible with both pnpm and npm workspaces.
4
+ link-workspace-packages=true
5
+
6
+ # Disable automatic dependency check before running scripts to prevent background install failures in read-only environments
7
+ verify-deps-before-run=false
8
+
9
+ # Prevent pnpm from prompting for confirmation when purging modules in non-TTY environments (e.g. Docker, CI)
10
+ confirm-modules-purge=false