@rebasepro/cli 0.10.0 → 0.10.1-canary.14e53ae

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.
@@ -0,0 +1,83 @@
1
+ import type { ManagedCompatibility, RebaseAppConfig, RebaseBackendAppConfig, RebaseProjectManifest } from "@rebasepro/types";
2
+ /** Runtime range written into new manifests. */
3
+ export declare const CURRENT_RUNTIME_RANGE = "^1";
4
+ /** Conventional locations, matching what `rebase init` scaffolds. */
5
+ export declare const DEFAULT_CONFIG_DIR = "config";
6
+ export declare const DEFAULT_FUNCTIONS_DIR = "backend/functions";
7
+ export declare const DEFAULT_CRONS_DIR = "backend/crons";
8
+ export declare const DEFAULT_SCHEMA_FILE = "backend/src/schema.generated.ts";
9
+ export interface ManifestValidationIssue {
10
+ /** Dotted path to the offending value, e.g. `apps.web.output`. */
11
+ path: string;
12
+ message: string;
13
+ }
14
+ export interface LoadedManifest {
15
+ manifest: RebaseProjectManifest;
16
+ /** Where it came from — a real file, or inferred from the directory layout. */
17
+ source: "file" | "synthesized";
18
+ /** Absolute path to `rebase.json`, when one exists. */
19
+ filePath?: string;
20
+ }
21
+ export declare class ManifestError extends Error {
22
+ readonly issues: ManifestValidationIssue[];
23
+ constructor(message: string, issues?: ManifestValidationIssue[]);
24
+ }
25
+ /**
26
+ * Validate a parsed manifest, collecting every problem.
27
+ */
28
+ export declare function validateManifest(raw: unknown): {
29
+ manifest?: RebaseProjectManifest;
30
+ issues: ManifestValidationIssue[];
31
+ };
32
+ /**
33
+ * Infer a manifest from a directory that does not have one.
34
+ *
35
+ * This mirrors exactly what the template scaffolds, which is what makes adopting
36
+ * the manifest a no-op for existing projects: the synthesized result is what
37
+ * they would have written by hand.
38
+ *
39
+ * An ejected backend — one with its own `src/index.ts` entrypoint — is reported
40
+ * as a `custom` app rather than a `backend` app. That is not a downgrade; it is
41
+ * an accurate description, and it is what keeps such a project deploying exactly
42
+ * as it does today.
43
+ */
44
+ export declare function synthesizeManifest(projectRoot: string): RebaseProjectManifest;
45
+ export declare function manifestPath(projectRoot: string): string;
46
+ export declare function manifestExists(projectRoot: string): boolean;
47
+ /**
48
+ * Read the manifest, falling back to a synthesized one.
49
+ *
50
+ * A malformed manifest throws — unlike a missing one. Silently ignoring a file
51
+ * the developer wrote, and building something else instead, is the worst
52
+ * available behaviour.
53
+ */
54
+ export declare function loadManifest(projectRoot: string): LoadedManifest;
55
+ /** Write a manifest, with a trailing newline so it plays well with other tools. */
56
+ export declare function writeManifest(projectRoot: string, manifest: RebaseProjectManifest): string;
57
+ /** Find the single backend app, if this repository declares one. */
58
+ export declare function findBackendApp(manifest: RebaseProjectManifest): {
59
+ name: string;
60
+ app: RebaseBackendAppConfig;
61
+ } | undefined;
62
+ /** Apps that produce build output, in the order they should be built. */
63
+ export declare function buildableApps(manifest: RebaseProjectManifest): {
64
+ name: string;
65
+ app: RebaseAppConfig;
66
+ }[];
67
+ /**
68
+ * Decide whether a project can run on the managed runtime, and say why not.
69
+ *
70
+ * "Not eligible" is never a dead end — it selects the custom-runtime path, which
71
+ * still deploys. The reasons exist so the answer is actionable rather than a
72
+ * verdict.
73
+ */
74
+ export declare function assessManagedCompatibility(manifest: RebaseProjectManifest): ManagedCompatibility;
75
+ /** Resolve a backend app's directories against the conventions it omits. */
76
+ export declare function resolveBackendPaths(app: RebaseBackendAppConfig): {
77
+ config: string;
78
+ functions: string;
79
+ crons: string;
80
+ schema: string;
81
+ usersCollection: string;
82
+ mode: "cms" | "baas";
83
+ };
@@ -19,14 +19,38 @@ export interface PMCommands {
19
19
  /** The workspace dependency protocol: `"workspace:*"` for pnpm, `"*"` for npm. */
20
20
  workspaceProtocol: string;
21
21
  }
22
+ /**
23
+ * Decide availability from a `spawnSync` outcome.
24
+ *
25
+ * Split out from the spawn itself so the decision is testable without starting
26
+ * a process — which is what made the old test load-sensitive and occasionally
27
+ * red for reasons that had nothing to do with the code under test.
28
+ *
29
+ * The three outcomes are distinguishable, and the old code conflated two of
30
+ * them by asking only `status === 0`:
31
+ *
32
+ * not installed status null, signal null, error.code ENOENT
33
+ * timed out status null, signal SIGTERM, error.code ETIMEDOUT
34
+ * broken install status non-zero, no error
35
+ */
36
+ export declare function pnpmAvailabilityFromProbe(res: {
37
+ status: number | null;
38
+ signal?: NodeJS.Signals | null;
39
+ error?: {
40
+ code?: string;
41
+ } | Error;
42
+ }): boolean;
22
43
  /**
23
44
  * Whether pnpm is runnable on this machine.
24
45
  *
25
46
  * Used to decide whether a fresh project can be scaffolded with pnpm. Kept
26
- * cheap and non-interactive (short timeout, output discarded) so it never
27
- * hangs detection if a corepack shim misbehaves.
47
+ * cheap and non-interactive (bounded timeout, output discarded) so it never
48
+ * hangs detection if a corepack shim misbehaves, and memoised so that repeated
49
+ * detection in one CLI run costs one process rather than one per call.
28
50
  */
29
51
  export declare function isPnpmAvailable(): boolean;
52
+ /** Forget the memoised probe. For tests; nothing in a CLI run needs it. */
53
+ export declare function resetPnpmAvailabilityCache(): void;
30
54
  /**
31
55
  * Detect the package manager for a Rebase project.
32
56
  *
@@ -1,9 +1,17 @@
1
+ /** The authored project manifest. Its presence alone marks a project root. */
2
+ export declare const MANIFEST_FILENAME = "rebase.json";
1
3
  /**
2
4
  * Walk up from `startDir` to find the Rebase project root.
3
5
  *
4
- * The root is identified by a `package.json` that either:
5
- * - has `workspaces` containing "backend" or "frontend", OR
6
- * - has a sibling `backend/` directory
6
+ * A directory is the root when it holds a `rebase.json`, or when it holds a
7
+ * `package.json` that either lists `backend` as a workspace or sits beside both
8
+ * `backend/` and `config/`.
9
+ *
10
+ * `rebase.json` is checked first and needs no `package.json` beside it, because
11
+ * the conventions below all describe a repository that *contains the backend*.
12
+ * A repository holding only a frontend — the normal shape once a project's apps
13
+ * live in separate repositories — matches none of them, so without this the
14
+ * tooling could not run there at all.
7
15
  */
8
16
  export declare function findProjectRoot(startDir?: string): string | null;
9
17
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/cli",
3
- "version": "0.10.0",
3
+ "version": "0.10.1-canary.14e53ae",
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.10.0",
35
- "@rebasepro/codegen": "0.10.0",
36
- "@rebasepro/client": "0.10.0",
37
- "@rebasepro/server-postgres": "0.10.0",
38
- "@rebasepro/types": "0.10.0",
39
- "@rebasepro/server": "0.10.0"
34
+ "@rebasepro/agent-skills": "0.10.1-canary.14e53ae",
35
+ "@rebasepro/client": "0.10.1-canary.14e53ae",
36
+ "@rebasepro/codegen": "0.10.1-canary.14e53ae",
37
+ "@rebasepro/server": "0.10.1-canary.14e53ae",
38
+ "@rebasepro/server-postgres": "0.10.1-canary.14e53ae",
39
+ "@rebasepro/types": "0.10.1-canary.14e53ae"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/node": "^25.9.3",
@@ -48,6 +48,7 @@
48
48
  },
49
49
  "files": [
50
50
  "bin/",
51
+ "runtime/",
51
52
  "dist/",
52
53
  "templates/"
53
54
  ],
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The development entrypoint.
3
+ *
4
+ * Run under `tsx` by `rebase dev` for projects that have no hand-written
5
+ * `backend/src/index.ts`. It boots the same runtime a deployment does, over the
6
+ * project's TypeScript **source** rather than a compiled bundle — so hot reload
7
+ * still works, and development still predicts production because both go through
8
+ * one boot path.
9
+ *
10
+ * Paths arrive in the environment rather than as arguments because `tsx watch`
11
+ * owns the argument list.
12
+ */
13
+ import path from "node:path";
14
+ import fs from "node:fs";
15
+ import dotenv from "dotenv";
16
+
17
+ const projectRoot = process.env.REBASE_DEV_PROJECT_ROOT || process.cwd();
18
+
19
+ const envFile = process.env.DOTENV_CONFIG_PATH;
20
+ if (envFile && fs.existsSync(envFile)) {
21
+ dotenv.config({ path: envFile });
22
+ }
23
+
24
+ const { createSourceBundle, runFromBundle } = await import("@rebasepro/server");
25
+
26
+ const optional = (value) => (value && value.length > 0 ? value : undefined);
27
+
28
+ const bundle = createSourceBundle({
29
+ projectRoot,
30
+ config: optional(process.env.REBASE_DEV_CONFIG),
31
+ functions: optional(process.env.REBASE_DEV_FUNCTIONS),
32
+ crons: optional(process.env.REBASE_DEV_CRONS),
33
+ schema: optional(process.env.REBASE_DEV_SCHEMA),
34
+ mode: process.env.REBASE_DEV_MODE === "baas" ? "baas" : "cms",
35
+ app: optional(process.env.REBASE_DEV_APP)
36
+ });
37
+
38
+ // The dev port file and the graceful-shutdown handlers both key off the project
39
+ // root, so make sure the process agrees with the CLI about where that is.
40
+ process.chdir(projectRoot);
41
+ void path;
42
+
43
+ await runFromBundle({ bundle });
@@ -122,6 +122,12 @@ pass: env.SMTP_PASS! }
122
122
  }
123
123
  : undefined
124
124
  },
125
+ // File storage is opt-in. With no bucket configured, storage is OFF in
126
+ // production — the upload routes answer 501 STORAGE_NOT_CONFIGURED —
127
+ // rather than writing to the container filesystem, which is erased on
128
+ // every restart and redeploy. Uploads that fail loudly are recoverable;
129
+ // uploads that succeed into a disk about to be wiped are not.
130
+ // Local disk stays the default in development, where it is what you want.
125
131
  storage: env.STORAGE_TYPE === "s3"
126
132
  ? {
127
133
  type: "s3",
@@ -132,10 +138,21 @@ pass: env.SMTP_PASS! }
132
138
  endpoint: env.S3_ENDPOINT,
133
139
  forcePathStyle: env.S3_FORCE_PATH_STYLE
134
140
  }
135
- : {
136
- type: "local",
137
- basePath: env.STORAGE_PATH || path.resolve(__dirname, "../../uploads")
138
- },
141
+ : env.STORAGE_TYPE === "gcs"
142
+ ? {
143
+ type: "gcs",
144
+ bucket: env.GCS_BUCKET!,
145
+ projectId: env.GCS_PROJECT_ID,
146
+ keyFilename: env.GCS_KEY_FILENAME
147
+ }
148
+ // Set FORCE_LOCAL_STORAGE=true only if this deployment really
149
+ // does have a durable volume mounted at STORAGE_PATH.
150
+ : isProduction && !env.FORCE_LOCAL_STORAGE
151
+ ? undefined
152
+ : {
153
+ type: "local",
154
+ basePath: env.STORAGE_PATH || path.resolve(__dirname, "../../uploads")
155
+ },
139
156
  history: true,
140
157
  enableSwagger: true
141
158
  });
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://rebase.pro/schemas/rebase.json",
3
+ "runtime": "^1",
4
+ "apps": {
5
+ "backend": {
6
+ "type": "backend",
7
+ "mode": "baas"
8
+ },
9
+ "admin": {
10
+ "type": "admin",
11
+ "mode": "hosted"
12
+ }
13
+ }
14
+ }
@@ -80,12 +80,19 @@ VITE_API_URL=http://localhost:3001
80
80
  # ── Storage ───────────────────────────────────────────────────────────────────
81
81
  # Set STORAGE_TYPE to choose a storage backend. Default is "local" (filesystem).
82
82
  #
83
- # Supported values: local, s3
83
+ # Supported values: local, s3, gcs
84
+ #
85
+ # In production, "local" means NO file storage: uploads answer
86
+ # 501 STORAGE_NOT_CONFIGURED instead of landing on a container filesystem that
87
+ # is erased on the next redeploy. Configure a bucket below to switch it on, or
88
+ # set FORCE_LOCAL_STORAGE=true if a durable volume really is mounted at
89
+ # STORAGE_PATH (the generated docker-compose.yml does exactly that).
84
90
  #
85
91
  # STORAGE_TYPE=local
86
92
 
87
93
  # --- Local filesystem (default — great for dev and single-server deployments) --
88
94
  # STORAGE_PATH=./uploads
95
+ # FORCE_LOCAL_STORAGE=true # Only with a durable volume mounted at STORAGE_PATH
89
96
 
90
97
  # --- S3-compatible (AWS S3, Cloudflare R2, MinIO, Hetzner Object Storage, Backblaze B2) ---
91
98
  # STORAGE_TYPE=s3
@@ -103,9 +110,15 @@ VITE_API_URL=http://localhost:3001
103
110
  # S3_ENDPOINT=https://storage.googleapis.com
104
111
  # S3_ACCESS_KEY_ID=GOOG... # HMAC access key
105
112
  # S3_SECRET_ACCESS_KEY=... # HMAC secret
113
+
114
+ # --- Native GCS (Google Cloud Storage, Firebase Storage) ---
115
+ # STORAGE_TYPE=gcs
116
+ # GCS_BUCKET=my-gcs-bucket
117
+ # GCS_PROJECT_ID= # Optional — auto-detected from credentials
118
+ # GCS_KEY_FILENAME= # Omit on GCP: Workload Identity/ADC supplies credentials
106
119
  #
107
- # For native GCS SDK or other providers (Azure Blob, etc.), implement the
108
- # StorageController interface and pass it directly in your backend config.
120
+ # For other providers (Azure Blob, etc.), implement the StorageController
121
+ # interface and pass it directly in your backend config.
109
122
 
110
123
  # ── Backups (optional) ────────────────────────────────────────────────────────
111
124
  # Manual backups: `rebase db backup --out ./backups` (or an s3://… URL).
@@ -4,57 +4,53 @@ A [Rebase](https://rebase.pro) project with a PostgreSQL backend.
4
4
 
5
5
  ## Quick Start
6
6
 
7
- ### Option 1: Docker (recommended for production)
7
+ `rebase init` already generated a `.env` for you with a real `JWT_SECRET`, a
8
+ database password, and a free local database port. **Don't run
9
+ `cp .env.example .env`** — that would overwrite those generated values.
10
+ `.env.example` is a reference for the variables you can set, not a starting
11
+ point for this project.
8
12
 
9
- ```bash
10
- cp .env.example .env
11
- # Edit .env — set JWT_SECRET and DATABASE_URL (see comments for generators)
12
-
13
- docker compose up -d
14
- ```
15
-
16
- That's it. Your app is running:
17
- - **Frontend**: http://localhost (port 80)
18
- - **Backend API**: http://localhost:3001
19
- - **PostgreSQL**: localhost:5432
20
-
21
- ### Option 2: Local Development
22
-
23
- #### Prerequisites
13
+ ### Prerequisites
24
14
 
25
15
  - [Node.js](https://nodejs.org) >= 18
26
16
  - [pnpm](https://pnpm.io) or [npm](https://www.npmjs.com) (v7+)
27
- - A PostgreSQL database (you can start the included database container via `docker compose up -d db`)
17
+ - [Docker](https://www.docker.com) (to run the included PostgreSQL container),
18
+ or your own PostgreSQL database
28
19
 
29
- #### Setup
20
+ ### Run it
30
21
 
31
- 1. Install dependencies:
22
+ 1. Install dependencies (skip if `init` already did this):
32
23
 
33
24
  ```bash
34
25
  pnpm install # or: npm install
35
26
  ```
36
27
 
37
- 2. Configure environment:
28
+ 2. Start the PostgreSQL database container:
38
29
 
39
30
  ```bash
40
- cp .env.example .env
41
- # Edit .env — set DATABASE_URL, JWT_SECRET
31
+ docker compose up -d db
42
32
  ```
43
33
 
44
- 3. Generate schema and push to database:
34
+ 3. Create the database tables from your collections:
45
35
 
46
36
  ```bash
47
- pnpm run schema:generate # or: npm run schema:generate
48
- pnpm run db:push # or: npm run db:push
37
+ pnpm run db:push # or: npm run db:push
49
38
  ```
50
39
 
51
- 4. Start the dev server:
40
+ 4. Start the dev servers:
52
41
 
53
42
  ```bash
54
43
  pnpm dev # or: npm run dev
55
44
  ```
56
45
 
57
- Backend (Hono + PostgreSQL) on port 3001, frontend (Vite + React) on port 5173.
46
+ Open **http://localhost:5173** the admin panel. The first account you
47
+ register becomes the admin. The backend API (Hono + PostgreSQL) runs on
48
+ port 3001; the frontend (Vite + React) on port 5173.
49
+
50
+ > The `db:push` step is what creates the tables for the example `posts`,
51
+ > `authors`, and `tags` collections. Skip it and the admin panel still opens,
52
+ > but those collections will be empty and their API calls will fail until the
53
+ > tables exist.
58
54
 
59
55
  ## Project Structure
60
56
 
@@ -102,12 +98,21 @@ All configuration is managed through a single `.env` file in the project root. B
102
98
  - **Frontend**: Vite reads `VITE_*` variables via `envDir` pointing to the project root
103
99
  - **Scripts**: load via `dotenv` from the project root
104
100
 
105
- Copy `.env.example` to `.env` to get started. See the comments in `.env.example` for details on each variable.
101
+ `init` already generated your `.env`. See the comments in `.env.example` for details on each variable, and edit `.env` directly to change any of them.
106
102
 
107
103
  ## Production Deployment
108
104
 
105
+ The full stack — PostgreSQL, backend, and frontend — runs from
106
+ `docker compose`. The backend image builds and boots, but it does **not**
107
+ create your collection tables on its own, so push the schema once the
108
+ database is up before (or right after) starting the rest of the stack.
109
+
109
110
  ```bash
110
- # Build and start
111
+ # 1. Start the database and create the tables from your collections
112
+ docker compose up -d db
113
+ pnpm run db:push # or: npm run db:push
114
+
115
+ # 2. Build and start the backend + frontend
111
116
  docker compose up -d --build
112
117
 
113
118
  # View logs
@@ -120,6 +125,11 @@ docker compose down
120
125
  docker compose down -v
121
126
  ```
122
127
 
128
+ > `docker compose` reads the generated `.env` as-is. Set production values
129
+ > (a strong `DATABASE_PASSWORD`, `JWT_SECRET`, storage credentials, …) by
130
+ > editing `.env` directly — see `.env.example` for the full list. Do not
131
+ > `cp .env.example .env`; that discards the values `init` generated.
132
+
123
133
  ## Documentation
124
134
 
125
135
  - [Rebase Docs](https://rebase.pro/docs)
@@ -123,6 +123,12 @@ pass: env.SMTP_PASS! }
123
123
  }
124
124
  : undefined
125
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.
126
132
  storage: env.STORAGE_TYPE === "s3"
127
133
  ? {
128
134
  type: "s3",
@@ -133,10 +139,21 @@ pass: env.SMTP_PASS! }
133
139
  endpoint: env.S3_ENDPOINT,
134
140
  forcePathStyle: env.S3_FORCE_PATH_STYLE
135
141
  }
136
- : {
137
- type: "local",
138
- basePath: env.STORAGE_PATH || path.resolve(__dirname, "../../uploads")
139
- },
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
+ },
140
157
  history: true
141
158
  });
142
159
 
@@ -29,3 +29,7 @@ uploads/
29
29
  # Rebase dev
30
30
  .rebase-dev-url
31
31
  .rebase-dev-port
32
+
33
+ # Built project bundle (`rebase build`)
34
+ dist-bundle/
35
+ .rebase/
@@ -0,0 +1,20 @@
1
+ {
2
+ "$schema": "https://rebase.pro/schemas/rebase.json",
3
+ "runtime": "^1",
4
+ "apps": {
5
+ "backend": {
6
+ "type": "backend"
7
+ },
8
+ "web": {
9
+ "type": "static",
10
+ "root": "frontend",
11
+ "build": "npm run build --workspace frontend",
12
+ "output": "frontend/dist",
13
+ "spa": true
14
+ },
15
+ "admin": {
16
+ "type": "admin",
17
+ "mode": "hosted"
18
+ }
19
+ }
20
+ }