@rebasepro/cli 0.2.1 → 0.2.4

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/README.md CHANGED
@@ -1,52 +1,56 @@
1
1
  ## Rebase CLI
2
2
 
3
- This CLI tool allows you to create new Rebase projects and to deploy them to Rebase Cloud.
4
- **IMPORTANT**: You should not be using this tool directly, but `rebase` instead.
3
+ Developer tools for scaffolding, running, and managing Rebase projects.
5
4
 
6
- ### CLI
5
+ ### Installation
7
6
 
8
- You can use the following commands:
7
+ The CLI is bundled with every Rebase project. You can also install it globally:
9
8
 
10
9
  ```bash
11
- rebase login
10
+ pnpm add -g @rebasepro/cli
12
11
  ```
13
12
 
14
- ```bash
15
- rebase init
16
- ```
13
+ ### Commands
14
+
15
+ | Command | Description |
16
+ |---------|-------------|
17
+ | `rebase init` | Scaffold a new Rebase project |
18
+ | `rebase dev` | Start the development server (backend + frontend) |
19
+ | `rebase build` | Build all workspace packages |
20
+ | `rebase start` | Start the backend server (production) |
21
+ | `rebase schema generate` | Generate Drizzle schema from collection definitions |
22
+ | `rebase schema introspect` | Introspect an existing database to generate collections |
23
+ | `rebase db push` | Apply schema directly to database (development) |
24
+ | `rebase db generate` | Generate SQL migration files |
25
+ | `rebase db migrate` | Run pending migrations |
26
+ | `rebase db studio` | Open Drizzle Studio |
27
+ | `rebase generate-sdk` | Generate a typed JS SDK from collections |
28
+ | `rebase auth reset-password` | Reset a user's password |
29
+ | `rebase doctor` | Detect schema drift between collections, schema, and DB |
30
+
31
+ ### Quick Start
17
32
 
18
33
  ```bash
19
- rebase deploy
34
+ rebase init my-app
35
+ cd my-app
36
+ docker compose up -d db
37
+ pnpm run db:push
38
+ pnpm run dev
20
39
  ```
21
40
 
22
- ### Using different templates
23
-
24
- You can initialize a new project using different templates. Please not that these templates can't
25
- be deployed to Rebase Cloud. For example:
41
+ ### Help
26
42
 
27
- For Rebase Cloud
43
+ Run `rebase --help` or `rebase <command> --help` for detailed usage information.
28
44
 
29
- ```bash
30
- rebase init
31
- ```
45
+ ### Development
32
46
 
33
- For Rebase PRO:
47
+ For local development of the CLI itself, link the package:
34
48
 
35
49
  ```bash
36
- rebase init --pro
50
+ pnpm link --global
37
51
  ```
38
52
 
39
- #### To run locally
40
-
41
- For development purposes, you can link the package locally.
42
-
43
- ```bash
44
- npm link rebase
45
- ```
46
-
47
- ### Development only
48
-
49
- You can change the environment when deploying to Rebase Cloud by defining the --env variable.
53
+ You can change the environment when deploying to Rebase Cloud by defining the `--env` variable.
50
54
  Possible values are `prod` (default) and `dev`.
51
55
 
52
56
  ```bash
@@ -13,4 +13,4 @@ export interface InitOptions {
13
13
  pmCommands: PMCommands;
14
14
  }
15
15
  export declare function createRebaseApp(rawArgs: string[]): Promise<void>;
16
- export declare function configureEnvFile(targetDirectory: string, databaseUrl?: string): void;
16
+ export declare function configureEnvFile(targetDirectory: string, databaseUrl?: string): Promise<void>;
package/dist/index.cjs CHANGED
@@ -1,25 +1,8 @@
1
1
  (function(global, factory) {
2
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("chalk"), require("arg"), require("inquirer"), require("path"), require("fs"), require("util"), require("execa"), require("fs/promises"), require("url"), require("crypto"), require("@rebasepro/sdk-generator"), require("child_process"), require("os")) : typeof define === "function" && define.amd ? define(["exports", "chalk", "arg", "inquirer", "path", "fs", "util", "execa", "fs/promises", "url", "crypto", "@rebasepro/sdk-generator", "child_process", "os"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase CLI"] = {}, global.chalk, global.arg, global.inquirer, global.path, global.fs, global.util, global.execa, global.promises, global.url, global.crypto, global.sdkGenerator, global.child_process, global.os));
3
- })(this, (function(exports2, chalk, arg, inquirer, path, fs, util, execa, promises, url, crypto, sdkGenerator, child_process, os) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("chalk"), require("arg"), require("inquirer"), require("path"), require("fs"), require("net"), require("util"), require("execa"), require("fs/promises"), require("url"), require("crypto"), require("@rebasepro/sdk-generator"), require("child_process")) : typeof define === "function" && define.amd ? define(["exports", "chalk", "arg", "inquirer", "path", "fs", "net", "util", "execa", "fs/promises", "url", "crypto", "@rebasepro/sdk-generator", "child_process"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase CLI"] = {}, global.chalk, global.arg, global.inquirer, global.path, global.fs, global.net, global.util, global.execa, global.promises, global.url, global.crypto, global.sdkGenerator, global.child_process));
3
+ })(this, (function(exports2, chalk, arg, inquirer, path, fs, net, util, execa, promises, url, crypto, sdkGenerator, child_process) {
4
4
  "use strict";
5
5
  var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
6
- function _interopNamespaceDefault(e) {
7
- const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
8
- if (e) {
9
- for (const k in e) {
10
- if (k !== "default") {
11
- const d = Object.getOwnPropertyDescriptor(e, k);
12
- Object.defineProperty(n, k, d.get ? d : {
13
- enumerable: true,
14
- get: () => e[k]
15
- });
16
- }
17
- }
18
- }
19
- n.default = e;
20
- return Object.freeze(n);
21
- }
22
- const os__namespace = /* @__PURE__ */ _interopNamespaceDefault(os);
23
6
  function detectPackageManager(targetDir) {
24
7
  const userAgent = process.env.npm_config_user_agent ?? "";
25
8
  if (userAgent.startsWith("npm/")) return "npm";
@@ -154,7 +137,13 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
154
137
  type: "input",
155
138
  name: "databaseUrl",
156
139
  message: "Enter your PostgreSQL database connection string (leave blank to use a local default):",
157
- default: ""
140
+ default: "",
141
+ validate: (input) => {
142
+ if (input.trim() && /[\r\n]/.test(input)) {
143
+ return "Database URL cannot contain newline characters.";
144
+ }
145
+ return true;
146
+ }
158
147
  });
159
148
  questions.push({
160
149
  type: "confirm",
@@ -209,7 +198,7 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
209
198
  process.exit(1);
210
199
  }
211
200
  await replacePlaceholders(options);
212
- configureEnvFile(options.targetDirectory, options.databaseUrl);
201
+ await configureEnvFile(options.targetDirectory, options.databaseUrl);
213
202
  if (options.git) {
214
203
  console.log(chalk.gray(" Initializing git repository..."));
215
204
  try {
@@ -260,23 +249,37 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
260
249
  console.log(chalk.bold("Next steps:"));
261
250
  console.log("");
262
251
  const runDev = pmCommands.run("dev");
252
+ const runDbPush = pmCommands.run("db:push");
263
253
  console.log(` ${chalk.cyan("cd")} ${options.projectName}`);
264
254
  if (!options.installDeps) {
265
255
  console.log(` ${chalk.cyan(installCmd.join(" "))}`);
266
256
  }
267
257
  console.log("");
268
258
  if (options.databaseUrl) {
269
- console.log(chalk.gray(" # Your database is configured! Start the dev server:"));
259
+ if (options.introspect) {
260
+ console.log(chalk.gray(" # Database has been introspected & collections generated!"));
261
+ console.log(chalk.gray(" # Start the development server (frontend + backend):"));
262
+ console.log(` ${chalk.cyan(runDev.join(" "))}`);
263
+ } else {
264
+ console.log(chalk.gray(" # Your custom database is configured in .env."));
265
+ console.log(chalk.gray(" # If the database is empty, push the Rebase schema to initialize it:"));
266
+ console.log(` ${chalk.cyan(runDbPush.join(" "))}`);
267
+ console.log("");
268
+ console.log(chalk.gray(" # Then start the development server:"));
269
+ console.log(` ${chalk.cyan(runDev.join(" "))}`);
270
+ }
270
271
  } else {
271
272
  console.log(chalk.gray(" # A local database configuration has been generated in .env."));
272
- console.log(chalk.gray(" # If using the included docker-compose.yml, start it with:"));
273
- console.log(` ${chalk.cyan("docker compose up -d")}`);
273
+ console.log(chalk.gray(" # 1. Start the PostgreSQL database container:"));
274
+ console.log(` ${chalk.cyan("docker compose up -d db")}`);
274
275
  console.log("");
275
- console.log(chalk.gray(" # Then start the dev server:"));
276
+ console.log(chalk.gray(" # 2. Push the Rebase schema to initialize database tables:"));
277
+ console.log(` ${chalk.cyan(runDbPush.join(" "))}`);
278
+ console.log("");
279
+ console.log(chalk.gray(" # 3. Start the development server (frontend + backend):"));
280
+ console.log(` ${chalk.cyan(runDev.join(" "))}`);
276
281
  }
277
282
  console.log("");
278
- console.log(` ${chalk.cyan(runDev.join(" "))}`);
279
- console.log("");
280
283
  console.log(chalk.gray("This starts both the backend (Hono + PostgreSQL)") + chalk.gray(" and the frontend (Vite + React) concurrently."));
281
284
  console.log("");
282
285
  console.log(chalk.gray("Docs: https://rebase.pro/docs"));
@@ -290,6 +293,7 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
290
293
  "backend/package.json",
291
294
  "config/package.json",
292
295
  "frontend/index.html",
296
+ "pnpm-workspace.yaml",
293
297
  "README.md"
294
298
  ];
295
299
  const packageJsonPath = path.resolve(cliRoot, "package.json");
@@ -354,7 +358,26 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
354
358
  fs.writeFileSync(fullPath, content, "utf-8");
355
359
  }
356
360
  }
357
- function configureEnvFile(targetDirectory, databaseUrl) {
361
+ async function isPortAvailable(port) {
362
+ return new Promise((resolve) => {
363
+ const server = net.createServer();
364
+ server.once("error", () => {
365
+ resolve(false);
366
+ });
367
+ server.once("listening", () => {
368
+ server.close(() => resolve(true));
369
+ });
370
+ server.listen(port);
371
+ });
372
+ }
373
+ async function findAvailablePort(startPort) {
374
+ let port = startPort;
375
+ while (!await isPortAvailable(port)) {
376
+ port++;
377
+ }
378
+ return port;
379
+ }
380
+ async function configureEnvFile(targetDirectory, databaseUrl) {
358
381
  const envExamplePath = path.join(targetDirectory, ".env.example");
359
382
  const envPath = path.join(targetDirectory, ".env");
360
383
  if (fs.existsSync(envExamplePath) && !fs.existsSync(envPath)) {
@@ -367,15 +390,29 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
367
390
  `JWT_SECRET=${jwtSecret}`
368
391
  );
369
392
  if (databaseUrl) {
393
+ if (/[\r\n]/.test(databaseUrl)) {
394
+ throw new Error("Invalid DATABASE_URL: multiline values are not allowed.");
395
+ }
370
396
  envContent = envContent.replace(
371
397
  /^DATABASE_URL=.*$/m,
372
398
  `DATABASE_URL=${databaseUrl}`
373
399
  );
374
400
  } else {
401
+ const dbPort = await findAvailablePort(5432);
375
402
  envContent = envContent.replace(
376
403
  /^DATABASE_URL=.*$/m,
377
- `DATABASE_URL=postgresql://rebase:${dbPassword}@localhost:5432/rebase`
404
+ `DATABASE_URL=postgresql://rebase:${dbPassword}@localhost:${dbPort}/rebase?options=-c%20search_path=public
405
+ DATABASE_PASSWORD=${dbPassword}`
378
406
  );
407
+ const dockerComposePath = path.join(targetDirectory, "docker-compose.yml");
408
+ if (fs.existsSync(dockerComposePath)) {
409
+ let dockerComposeContent = fs.readFileSync(dockerComposePath, "utf-8");
410
+ dockerComposeContent = dockerComposeContent.replace(
411
+ /-\s*"5432:5432"/g,
412
+ `- "${dbPort}:5432"`
413
+ );
414
+ fs.writeFileSync(dockerComposePath, dockerComposeContent, "utf-8");
415
+ }
379
416
  }
380
417
  fs.writeFileSync(envPath, envContent, "utf-8");
381
418
  }
@@ -665,7 +702,7 @@ Expected a default export of EntityCollection[] or an object with named collecti
665
702
  env
666
703
  });
667
704
  }
668
- } catch (err) {
705
+ } catch {
669
706
  process.exit(1);
670
707
  }
671
708
  }
@@ -733,7 +770,7 @@ ${chalk.green.bold("introspect Options")}
733
770
  env
734
771
  });
735
772
  }
736
- } catch (err) {
773
+ } catch {
737
774
  process.exit(1);
738
775
  }
739
776
  }
@@ -953,7 +990,7 @@ ${chalk.green.bold("Examples")}
953
990
  });
954
991
  console.log(chalk.green(" ✓ Initial schema and SDK generated successfully.\n"));
955
992
  } catch (err) {
956
- console.error(chalk.red(` ✗ Initial schema/SDK generation failed: ${err.message || err}
993
+ console.error(chalk.red(` ✗ Initial schema/SDK generation failed: ${err instanceof Error ? err.message : err}
957
994
  `));
958
995
  }
959
996
  const collectionsDir = path.join(projectRoot, "config", "collections");
@@ -982,7 +1019,7 @@ ${chalk.green.bold("Examples")}
982
1019
  });
983
1020
  console.log(chalk.green(" ✓ Schema & SDK regenerated successfully. Hono will reload."));
984
1021
  } catch (err) {
985
- console.error(chalk.red(` ✗ Failed to regenerate schema/SDK: ${err.message || err}`));
1022
+ console.error(chalk.red(` ✗ Failed to regenerate schema/SDK: ${err instanceof Error ? err.message : err}`));
986
1023
  }
987
1024
  }, 300);
988
1025
  });
@@ -1177,6 +1214,9 @@ ${chalk.green.bold("Description")}
1177
1214
  if (envFile) {
1178
1215
  env.DOTENV_CONFIG_PATH = envFile;
1179
1216
  }
1217
+ env.REBASE_RESET_EMAIL = email;
1218
+ env.REBASE_RESET_PASSWORD = newPassword || "NewPassword123!";
1219
+ env.REBASE_ENV_FILE_PATH = envFile || path.join(projectRoot, ".env");
1180
1220
  const scriptContent = `
1181
1221
  import { createPostgresDatabaseConnection } from "@rebasepro/server-postgresql";
1182
1222
  import { hashPassword } from "@rebasepro/server-core";
@@ -1185,10 +1225,10 @@ import * as dotenv from "dotenv";
1185
1225
  import path from "path";
1186
1226
  import fs from "fs";
1187
1227
 
1188
- dotenv.config({ path: "${envFile || path.join(projectRoot, ".env")}" });
1228
+ dotenv.config({ path: process.env.REBASE_ENV_FILE_PATH });
1189
1229
 
1190
- const email = "${email}";
1191
- const newPassword = "${newPassword || "NewPassword123!"}";
1230
+ const email = process.env.REBASE_RESET_EMAIL!;
1231
+ const newPassword = process.env.REBASE_RESET_PASSWORD!;
1192
1232
 
1193
1233
  async function resetPassword() {
1194
1234
  const { db } = createPostgresDatabaseConnection(process.env.DATABASE_URL!);
@@ -1451,66 +1491,22 @@ ${chalk.green.bold("Options")}
1451
1491
  ${chalk.gray("Documentation: https://rebase.pro/docs")}
1452
1492
  `);
1453
1493
  }
1454
- const TOKEN_DIR = path.join(os__namespace.homedir(), ".rebase");
1455
- function tokenPath(env) {
1456
- return path.join(TOKEN_DIR, (env === "dev" ? "staging." : "") + "tokens.json");
1457
- }
1458
- async function getTokens(env, _debug) {
1459
- const fp = tokenPath(env);
1460
- if (!fs.existsSync(fp)) return null;
1461
- try {
1462
- const data = fs.readFileSync(fp, "utf-8");
1463
- return JSON.parse(data);
1464
- } catch {
1465
- return null;
1466
- }
1467
- }
1468
- function parseJwt(token) {
1469
- if (!token) throw new Error("No JWT token");
1470
- const base64Url = token.split(".")[1];
1471
- const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
1472
- const buffer = Buffer.from(base64, "base64");
1473
- return JSON.parse(buffer.toString());
1474
- }
1475
- async function refreshCredentials(env, credentials, _onErr) {
1476
- if (!credentials) return null;
1477
- const expiryDate = new Date(credentials["expiry_date"]);
1478
- if (expiryDate.getTime() > Date.now()) {
1479
- return credentials;
1480
- }
1481
- return null;
1482
- }
1483
- async function login(env, _debug) {
1484
- console.log(
1485
- "Interactive login is not yet implemented in @rebasepro/cli.\nPlease authenticate via the Rebase dashboard and copy your tokens to " + tokenPath(env)
1486
- );
1487
- }
1488
- async function logout(env, _debug) {
1489
- const fp = tokenPath(env);
1490
- if (fs.existsSync(fp)) {
1491
- fs.unlinkSync(fp);
1492
- console.log("You have been logged out.");
1493
- } else {
1494
- console.log("You are not logged in.");
1495
- }
1496
- }
1497
1494
  exports2.authCommand = authCommand;
1498
1495
  exports2.buildCommand = buildCommand;
1499
1496
  exports2.configureEnvFile = configureEnvFile;
1500
1497
  exports2.createRebaseApp = createRebaseApp;
1501
1498
  exports2.dbCommand = dbCommand;
1499
+ exports2.detectPackageManager = detectPackageManager;
1502
1500
  exports2.devCommand = devCommand;
1501
+ exports2.doctorCommand = doctorCommand;
1503
1502
  exports2.entry = entry;
1504
1503
  exports2.findBackendDir = findBackendDir;
1505
1504
  exports2.findEnvFile = findEnvFile;
1506
1505
  exports2.findFrontendDir = findFrontendDir;
1507
1506
  exports2.findProjectRoot = findProjectRoot;
1507
+ exports2.generateSdkCommand = generateSdkCommand;
1508
1508
  exports2.getActiveBackendPlugin = getActiveBackendPlugin;
1509
- exports2.getTokens = getTokens;
1510
- exports2.login = login;
1511
- exports2.logout = logout;
1512
- exports2.parseJwt = parseJwt;
1513
- exports2.refreshCredentials = refreshCredentials;
1509
+ exports2.getPMCommands = getPMCommands;
1514
1510
  exports2.requireBackendDir = requireBackendDir;
1515
1511
  exports2.requireProjectRoot = requireProjectRoot;
1516
1512
  exports2.resolveLocalBin = resolveLocalBin;