@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/dist/index.es.js CHANGED
@@ -3,6 +3,7 @@ import arg from "arg";
3
3
  import inquirer from "inquirer";
4
4
  import path from "path";
5
5
  import fs from "fs";
6
+ import net from "net";
6
7
  import { promisify } from "util";
7
8
  import { execa, execaCommandSync } from "execa";
8
9
  import { cp } from "fs/promises";
@@ -10,7 +11,6 @@ import { fileURLToPath } from "url";
10
11
  import crypto from "crypto";
11
12
  import { generateSDK } from "@rebasepro/sdk-generator";
12
13
  import { execSync, spawn } from "child_process";
13
- import * as os from "os";
14
14
  function detectPackageManager(targetDir) {
15
15
  const userAgent = process.env.npm_config_user_agent ?? "";
16
16
  if (userAgent.startsWith("npm/")) return "npm";
@@ -145,7 +145,13 @@ async function promptForOptions(rawArgs, pm) {
145
145
  type: "input",
146
146
  name: "databaseUrl",
147
147
  message: "Enter your PostgreSQL database connection string (leave blank to use a local default):",
148
- default: ""
148
+ default: "",
149
+ validate: (input) => {
150
+ if (input.trim() && /[\r\n]/.test(input)) {
151
+ return "Database URL cannot contain newline characters.";
152
+ }
153
+ return true;
154
+ }
149
155
  });
150
156
  questions.push({
151
157
  type: "confirm",
@@ -200,7 +206,7 @@ async function createProject(options) {
200
206
  process.exit(1);
201
207
  }
202
208
  await replacePlaceholders(options);
203
- configureEnvFile(options.targetDirectory, options.databaseUrl);
209
+ await configureEnvFile(options.targetDirectory, options.databaseUrl);
204
210
  if (options.git) {
205
211
  console.log(chalk.gray(" Initializing git repository..."));
206
212
  try {
@@ -251,23 +257,37 @@ async function createProject(options) {
251
257
  console.log(chalk.bold("Next steps:"));
252
258
  console.log("");
253
259
  const runDev = pmCommands.run("dev");
260
+ const runDbPush = pmCommands.run("db:push");
254
261
  console.log(` ${chalk.cyan("cd")} ${options.projectName}`);
255
262
  if (!options.installDeps) {
256
263
  console.log(` ${chalk.cyan(installCmd.join(" "))}`);
257
264
  }
258
265
  console.log("");
259
266
  if (options.databaseUrl) {
260
- console.log(chalk.gray(" # Your database is configured! Start the dev server:"));
267
+ if (options.introspect) {
268
+ console.log(chalk.gray(" # Database has been introspected & collections generated!"));
269
+ console.log(chalk.gray(" # Start the development server (frontend + backend):"));
270
+ console.log(` ${chalk.cyan(runDev.join(" "))}`);
271
+ } else {
272
+ console.log(chalk.gray(" # Your custom database is configured in .env."));
273
+ console.log(chalk.gray(" # If the database is empty, push the Rebase schema to initialize it:"));
274
+ console.log(` ${chalk.cyan(runDbPush.join(" "))}`);
275
+ console.log("");
276
+ console.log(chalk.gray(" # Then start the development server:"));
277
+ console.log(` ${chalk.cyan(runDev.join(" "))}`);
278
+ }
261
279
  } else {
262
280
  console.log(chalk.gray(" # A local database configuration has been generated in .env."));
263
- console.log(chalk.gray(" # If using the included docker-compose.yml, start it with:"));
264
- console.log(` ${chalk.cyan("docker compose up -d")}`);
281
+ console.log(chalk.gray(" # 1. Start the PostgreSQL database container:"));
282
+ console.log(` ${chalk.cyan("docker compose up -d db")}`);
265
283
  console.log("");
266
- console.log(chalk.gray(" # Then start the dev server:"));
284
+ console.log(chalk.gray(" # 2. Push the Rebase schema to initialize database tables:"));
285
+ console.log(` ${chalk.cyan(runDbPush.join(" "))}`);
286
+ console.log("");
287
+ console.log(chalk.gray(" # 3. Start the development server (frontend + backend):"));
288
+ console.log(` ${chalk.cyan(runDev.join(" "))}`);
267
289
  }
268
290
  console.log("");
269
- console.log(` ${chalk.cyan(runDev.join(" "))}`);
270
- console.log("");
271
291
  console.log(chalk.gray("This starts both the backend (Hono + PostgreSQL)") + chalk.gray(" and the frontend (Vite + React) concurrently."));
272
292
  console.log("");
273
293
  console.log(chalk.gray("Docs: https://rebase.pro/docs"));
@@ -281,6 +301,7 @@ async function replacePlaceholders(options) {
281
301
  "backend/package.json",
282
302
  "config/package.json",
283
303
  "frontend/index.html",
304
+ "pnpm-workspace.yaml",
284
305
  "README.md"
285
306
  ];
286
307
  const packageJsonPath = path.resolve(cliRoot, "package.json");
@@ -345,7 +366,26 @@ async function replacePlaceholders(options) {
345
366
  fs.writeFileSync(fullPath, content, "utf-8");
346
367
  }
347
368
  }
348
- function configureEnvFile(targetDirectory, databaseUrl) {
369
+ async function isPortAvailable(port) {
370
+ return new Promise((resolve) => {
371
+ const server = net.createServer();
372
+ server.once("error", () => {
373
+ resolve(false);
374
+ });
375
+ server.once("listening", () => {
376
+ server.close(() => resolve(true));
377
+ });
378
+ server.listen(port);
379
+ });
380
+ }
381
+ async function findAvailablePort(startPort) {
382
+ let port = startPort;
383
+ while (!await isPortAvailable(port)) {
384
+ port++;
385
+ }
386
+ return port;
387
+ }
388
+ async function configureEnvFile(targetDirectory, databaseUrl) {
349
389
  const envExamplePath = path.join(targetDirectory, ".env.example");
350
390
  const envPath = path.join(targetDirectory, ".env");
351
391
  if (fs.existsSync(envExamplePath) && !fs.existsSync(envPath)) {
@@ -358,15 +398,29 @@ function configureEnvFile(targetDirectory, databaseUrl) {
358
398
  `JWT_SECRET=${jwtSecret}`
359
399
  );
360
400
  if (databaseUrl) {
401
+ if (/[\r\n]/.test(databaseUrl)) {
402
+ throw new Error("Invalid DATABASE_URL: multiline values are not allowed.");
403
+ }
361
404
  envContent = envContent.replace(
362
405
  /^DATABASE_URL=.*$/m,
363
406
  `DATABASE_URL=${databaseUrl}`
364
407
  );
365
408
  } else {
409
+ const dbPort = await findAvailablePort(5432);
366
410
  envContent = envContent.replace(
367
411
  /^DATABASE_URL=.*$/m,
368
- `DATABASE_URL=postgresql://rebase:${dbPassword}@localhost:5432/rebase`
412
+ `DATABASE_URL=postgresql://rebase:${dbPassword}@localhost:${dbPort}/rebase?options=-c%20search_path=public
413
+ DATABASE_PASSWORD=${dbPassword}`
369
414
  );
415
+ const dockerComposePath = path.join(targetDirectory, "docker-compose.yml");
416
+ if (fs.existsSync(dockerComposePath)) {
417
+ let dockerComposeContent = fs.readFileSync(dockerComposePath, "utf-8");
418
+ dockerComposeContent = dockerComposeContent.replace(
419
+ /-\s*"5432:5432"/g,
420
+ `- "${dbPort}:5432"`
421
+ );
422
+ fs.writeFileSync(dockerComposePath, dockerComposeContent, "utf-8");
423
+ }
370
424
  }
371
425
  fs.writeFileSync(envPath, envContent, "utf-8");
372
426
  }
@@ -656,7 +710,7 @@ async function schemaCommand(subcommand, rawArgs) {
656
710
  env
657
711
  });
658
712
  }
659
- } catch (err) {
713
+ } catch {
660
714
  process.exit(1);
661
715
  }
662
716
  }
@@ -724,7 +778,7 @@ async function dbCommand(subcommand, rawArgs) {
724
778
  env
725
779
  });
726
780
  }
727
- } catch (err) {
781
+ } catch {
728
782
  process.exit(1);
729
783
  }
730
784
  }
@@ -944,7 +998,7 @@ async function devCommand(rawArgs) {
944
998
  });
945
999
  console.log(chalk.green(" ✓ Initial schema and SDK generated successfully.\n"));
946
1000
  } catch (err) {
947
- console.error(chalk.red(` ✗ Initial schema/SDK generation failed: ${err.message || err}
1001
+ console.error(chalk.red(` ✗ Initial schema/SDK generation failed: ${err instanceof Error ? err.message : err}
948
1002
  `));
949
1003
  }
950
1004
  const collectionsDir = path.join(projectRoot, "config", "collections");
@@ -973,7 +1027,7 @@ async function devCommand(rawArgs) {
973
1027
  });
974
1028
  console.log(chalk.green(" ✓ Schema & SDK regenerated successfully. Hono will reload."));
975
1029
  } catch (err) {
976
- console.error(chalk.red(` ✗ Failed to regenerate schema/SDK: ${err.message || err}`));
1030
+ console.error(chalk.red(` ✗ Failed to regenerate schema/SDK: ${err instanceof Error ? err.message : err}`));
977
1031
  }
978
1032
  }, 300);
979
1033
  });
@@ -1168,6 +1222,9 @@ async function resetPassword(rawArgs) {
1168
1222
  if (envFile) {
1169
1223
  env.DOTENV_CONFIG_PATH = envFile;
1170
1224
  }
1225
+ env.REBASE_RESET_EMAIL = email;
1226
+ env.REBASE_RESET_PASSWORD = newPassword || "NewPassword123!";
1227
+ env.REBASE_ENV_FILE_PATH = envFile || path.join(projectRoot, ".env");
1171
1228
  const scriptContent = `
1172
1229
  import { createPostgresDatabaseConnection } from "@rebasepro/server-postgresql";
1173
1230
  import { hashPassword } from "@rebasepro/server-core";
@@ -1176,10 +1233,10 @@ import * as dotenv from "dotenv";
1176
1233
  import path from "path";
1177
1234
  import fs from "fs";
1178
1235
 
1179
- dotenv.config({ path: "${envFile || path.join(projectRoot, ".env")}" });
1236
+ dotenv.config({ path: process.env.REBASE_ENV_FILE_PATH });
1180
1237
 
1181
- const email = "${email}";
1182
- const newPassword = "${newPassword || "NewPassword123!"}";
1238
+ const email = process.env.REBASE_RESET_EMAIL!;
1239
+ const newPassword = process.env.REBASE_RESET_PASSWORD!;
1183
1240
 
1184
1241
  async function resetPassword() {
1185
1242
  const { db } = createPostgresDatabaseConnection(process.env.DATABASE_URL!);
@@ -1442,67 +1499,23 @@ ${chalk.green.bold("Options")}
1442
1499
  ${chalk.gray("Documentation: https://rebase.pro/docs")}
1443
1500
  `);
1444
1501
  }
1445
- const TOKEN_DIR = path.join(os.homedir(), ".rebase");
1446
- function tokenPath(env) {
1447
- return path.join(TOKEN_DIR, (env === "dev" ? "staging." : "") + "tokens.json");
1448
- }
1449
- async function getTokens(env, _debug) {
1450
- const fp = tokenPath(env);
1451
- if (!fs.existsSync(fp)) return null;
1452
- try {
1453
- const data = fs.readFileSync(fp, "utf-8");
1454
- return JSON.parse(data);
1455
- } catch {
1456
- return null;
1457
- }
1458
- }
1459
- function parseJwt(token) {
1460
- if (!token) throw new Error("No JWT token");
1461
- const base64Url = token.split(".")[1];
1462
- const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
1463
- const buffer = Buffer.from(base64, "base64");
1464
- return JSON.parse(buffer.toString());
1465
- }
1466
- async function refreshCredentials(env, credentials, _onErr) {
1467
- if (!credentials) return null;
1468
- const expiryDate = new Date(credentials["expiry_date"]);
1469
- if (expiryDate.getTime() > Date.now()) {
1470
- return credentials;
1471
- }
1472
- return null;
1473
- }
1474
- async function login(env, _debug) {
1475
- console.log(
1476
- "Interactive login is not yet implemented in @rebasepro/cli.\nPlease authenticate via the Rebase dashboard and copy your tokens to " + tokenPath(env)
1477
- );
1478
- }
1479
- async function logout(env, _debug) {
1480
- const fp = tokenPath(env);
1481
- if (fs.existsSync(fp)) {
1482
- fs.unlinkSync(fp);
1483
- console.log("You have been logged out.");
1484
- } else {
1485
- console.log("You are not logged in.");
1486
- }
1487
- }
1488
1502
  export {
1489
1503
  authCommand,
1490
1504
  buildCommand,
1491
1505
  configureEnvFile,
1492
1506
  createRebaseApp,
1493
1507
  dbCommand,
1508
+ detectPackageManager,
1494
1509
  devCommand,
1510
+ doctorCommand,
1495
1511
  entry,
1496
1512
  findBackendDir,
1497
1513
  findEnvFile,
1498
1514
  findFrontendDir,
1499
1515
  findProjectRoot,
1516
+ generateSdkCommand,
1500
1517
  getActiveBackendPlugin,
1501
- getTokens,
1502
- login,
1503
- logout,
1504
- parseJwt,
1505
- refreshCredentials,
1518
+ getPMCommands,
1506
1519
  requireBackendDir,
1507
1520
  requireProjectRoot,
1508
1521
  resolveLocalBin,