@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 +34 -30
- package/dist/commands/init.d.ts +1 -1
- package/dist/index.cjs +80 -84
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.es.js +79 -66
- package/dist/index.es.js.map +1 -1
- package/package.json +10 -9
- package/templates/template/.env.example +1 -1
- package/templates/template/README.md +1 -1
- package/templates/template/config/collections/posts.ts +6 -12
- package/templates/template/config/collections/roles.ts +0 -10
- package/templates/template/docker-compose.yml +3 -3
- package/templates/template/frontend/src/App.tsx +15 -12
- package/templates/template/frontend/src/main.tsx +4 -0
- package/templates/template/pnpm-workspace.yaml +1 -0
- package/dist/auth.d.ts +0 -5
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
|
-
|
|
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(" #
|
|
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(" #
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
1236
|
+
dotenv.config({ path: process.env.REBASE_ENV_FILE_PATH });
|
|
1180
1237
|
|
|
1181
|
-
const email =
|
|
1182
|
-
const newPassword =
|
|
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
|
-
|
|
1502
|
-
login,
|
|
1503
|
-
logout,
|
|
1504
|
-
parseJwt,
|
|
1505
|
-
refreshCredentials,
|
|
1518
|
+
getPMCommands,
|
|
1506
1519
|
requireBackendDir,
|
|
1507
1520
|
requireProjectRoot,
|
|
1508
1521
|
resolveLocalBin,
|