@rebasepro/cli 0.0.1-canary.eae7889 → 0.1.0

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
@@ -11,6 +11,45 @@ import crypto from "crypto";
11
11
  import { generateSDK } from "@rebasepro/sdk-generator";
12
12
  import { execSync, spawn } from "child_process";
13
13
  import * as os from "os";
14
+ function detectPackageManager(targetDir) {
15
+ const userAgent = process.env.npm_config_user_agent ?? "";
16
+ if (userAgent.startsWith("npm/")) return "npm";
17
+ if (userAgent.startsWith("pnpm/")) return "pnpm";
18
+ if (targetDir) {
19
+ if (fs.existsSync(path.join(targetDir, "package-lock.json"))) return "npm";
20
+ if (fs.existsSync(path.join(targetDir, "pnpm-lock.yaml"))) return "pnpm";
21
+ }
22
+ const cwd = process.cwd();
23
+ if (cwd !== targetDir) {
24
+ if (fs.existsSync(path.join(cwd, "package-lock.json"))) return "npm";
25
+ if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
26
+ }
27
+ return "pnpm";
28
+ }
29
+ function getPMCommands(pm) {
30
+ if (pm === "npm") {
31
+ return {
32
+ name: "npm",
33
+ install: ["npm", "install"],
34
+ run: (script) => ["npm", "run", script],
35
+ exec: (bin, args) => ["npx", bin, ...args],
36
+ view: (pkg, field) => ["npm", "view", pkg, field],
37
+ runAll: (script) => ["npm", "run", script, "--workspaces", "--if-present"],
38
+ runWorkspace: (workspace, script) => ["npm", "run", script, "-w", workspace],
39
+ workspaceProtocol: "*"
40
+ };
41
+ }
42
+ return {
43
+ name: "pnpm",
44
+ install: ["pnpm", "install"],
45
+ run: (script) => ["pnpm", "run", script],
46
+ exec: (bin, args) => ["pnpm", "exec", bin, ...args],
47
+ view: (pkg, field) => ["pnpm", "view", pkg, field],
48
+ runAll: (script) => ["pnpm", "-r", "run", script],
49
+ runWorkspace: (workspace, script) => ["pnpm", "--filter", workspace, script],
50
+ workspaceProtocol: "workspace:*"
51
+ };
52
+ }
14
53
  const access = promisify(fs.access);
15
54
  const copy = promisify(ncp);
16
55
  const __filename$2 = fileURLToPath(import.meta.url);
@@ -30,10 +69,11 @@ async function createRebaseApp(rawArgs) {
30
69
  console.log(`
31
70
  ${chalk.bold("Rebase")} — Create a new project šŸš€
32
71
  `);
33
- const options = await promptForOptions(rawArgs);
72
+ const pm = detectPackageManager();
73
+ const options = await promptForOptions(rawArgs, pm);
34
74
  await createProject(options);
35
75
  }
36
- async function promptForOptions(rawArgs) {
76
+ async function promptForOptions(rawArgs, pm) {
37
77
  const args = arg(
38
78
  {
39
79
  "--git": Boolean,
@@ -76,20 +116,38 @@ async function promptForOptions(rawArgs) {
76
116
  questions.push({
77
117
  type: "confirm",
78
118
  name: "installDeps",
79
- message: "Install dependencies with pnpm?",
119
+ message: `Install dependencies with ${pm}?`,
80
120
  default: true
81
121
  });
82
122
  }
123
+ questions.push({
124
+ type: "input",
125
+ name: "databaseUrl",
126
+ message: "Enter your PostgreSQL database connection string (leave blank to use a local default):",
127
+ default: ""
128
+ });
129
+ questions.push({
130
+ type: "confirm",
131
+ name: "introspect",
132
+ message: "Would you like to introspect this database to automatically generate collections?",
133
+ default: true,
134
+ when: (answers2) => !!answers2.databaseUrl?.trim()
135
+ });
83
136
  const answers = await inquirer.prompt(questions);
84
- const projectName = nameArg || answers.projectName;
85
- const targetDirectory = path.resolve(process.cwd(), projectName);
137
+ const targetDirectory = path.resolve(process.cwd(), nameArg || answers.projectName);
138
+ const projectName = path.basename(targetDirectory);
86
139
  const templateDirectory = path.resolve(cliRoot, "templates", "template");
140
+ const pmCommands = getPMCommands(pm);
87
141
  return {
88
142
  projectName,
89
143
  git: args["--git"] || answers.git || false,
90
144
  installDeps: args["--install"] || answers.installDeps || false,
91
145
  targetDirectory,
92
- templateDirectory
146
+ templateDirectory,
147
+ databaseUrl: answers.databaseUrl?.trim() || void 0,
148
+ introspect: answers.introspect || false,
149
+ pm,
150
+ pmCommands
93
151
  };
94
152
  }
95
153
  async function createProject(options) {
@@ -122,27 +180,7 @@ async function createProject(options) {
122
180
  process.exit(1);
123
181
  }
124
182
  await replacePlaceholders(options);
125
- const envTemplatePath = path.join(options.targetDirectory, ".env.template");
126
- const envPath = path.join(options.targetDirectory, ".env");
127
- if (fs.existsSync(envTemplatePath) && !fs.existsSync(envPath)) {
128
- fs.renameSync(envTemplatePath, envPath);
129
- const jwtSecret = crypto.randomBytes(32).toString("hex");
130
- const dbPassword = crypto.randomBytes(16).toString("hex");
131
- let envContent = fs.readFileSync(envPath, "utf-8");
132
- envContent = envContent.replace(
133
- "postgresql://rebase:password@localhost:5432/rebase",
134
- `postgresql://rebase:${dbPassword}@localhost:5432/rebase`
135
- );
136
- envContent = envContent.replace(
137
- "change-this-to-a-secure-random-string",
138
- jwtSecret
139
- );
140
- envContent += `
141
- # Docker Compose Database Password
142
- POSTGRES_PASSWORD=${dbPassword}
143
- `;
144
- fs.writeFileSync(envPath, envContent, "utf-8");
145
- }
183
+ configureEnvFile(options.targetDirectory, options.databaseUrl);
146
184
  if (options.git) {
147
185
  console.log(chalk.gray(" Initializing git repository..."));
148
186
  try {
@@ -151,17 +189,40 @@ POSTGRES_PASSWORD=${dbPassword}
151
189
  console.warn(chalk.yellow(" Warning: Failed to initialize git repository"));
152
190
  }
153
191
  }
192
+ const { pm, pmCommands } = options;
193
+ const installCmd = pmCommands.install;
194
+ const execCmd = pmCommands.exec("rebase", ["schema", "introspect", "--force"]);
154
195
  if (options.installDeps) {
155
196
  console.log("");
156
- console.log(chalk.gray(" Installing dependencies with pnpm..."));
197
+ console.log(chalk.gray(` Installing dependencies with ${pm}...`));
157
198
  console.log("");
158
199
  try {
159
- await execa("pnpm", ["install"], {
200
+ await execa(installCmd[0], installCmd.slice(1), {
160
201
  cwd: options.targetDirectory,
161
202
  stdio: "inherit"
162
203
  });
163
204
  } catch {
164
- console.warn(chalk.yellow(" Warning: Failed to install dependencies. You may need to run `pnpm install` manually."));
205
+ console.warn(chalk.yellow(` Warning: Failed to install dependencies. You may need to run \`${installCmd.join(" ")}\` manually.`));
206
+ }
207
+ }
208
+ if (options.introspect) {
209
+ console.log("");
210
+ if (options.installDeps) {
211
+ console.log(chalk.gray(" Introspecting database and generating collections..."));
212
+ console.log("");
213
+ try {
214
+ await execa(execCmd[0], execCmd.slice(1), {
215
+ cwd: options.targetDirectory,
216
+ stdio: "inherit"
217
+ });
218
+ console.log(chalk.green(" Database successfully introspected!"));
219
+ } catch {
220
+ console.warn(chalk.yellow(" Warning: Failed to introspect database automatically."));
221
+ console.warn(chalk.yellow(` You can run \`${execCmd.join(" ")}\` manually after setup.`));
222
+ }
223
+ } else {
224
+ console.warn(chalk.yellow(" Skipping introspection because dependencies were not installed."));
225
+ console.warn(chalk.yellow(` Run \`${installCmd.join(" ")}\` then \`${execCmd.join(" ")}\` manually.`));
165
226
  }
166
227
  }
167
228
  console.log("");
@@ -169,15 +230,23 @@ POSTGRES_PASSWORD=${dbPassword}
169
230
  console.log("");
170
231
  console.log(chalk.bold("Next steps:"));
171
232
  console.log("");
233
+ const runDev = pmCommands.run("dev");
172
234
  console.log(` ${chalk.cyan("cd")} ${options.projectName}`);
173
235
  if (!options.installDeps) {
174
- console.log(` ${chalk.cyan("pnpm install")}`);
236
+ console.log(` ${chalk.cyan(installCmd.join(" "))}`);
175
237
  }
176
238
  console.log("");
177
- console.log(chalk.gray(" # Set up your database connection in .env"));
178
- console.log(chalk.gray(" # Then run:"));
239
+ if (options.databaseUrl) {
240
+ console.log(chalk.gray(" # Your database is configured! Start the dev server:"));
241
+ } else {
242
+ console.log(chalk.gray(" # A local database configuration has been generated in .env."));
243
+ console.log(chalk.gray(" # If using the included docker-compose.yml, start it with:"));
244
+ console.log(` ${chalk.cyan("docker compose up -d")}`);
245
+ console.log("");
246
+ console.log(chalk.gray(" # Then start the dev server:"));
247
+ }
179
248
  console.log("");
180
- console.log(` ${chalk.cyan("pnpm dev")}`);
249
+ console.log(` ${chalk.cyan(runDev.join(" "))}`);
181
250
  console.log("");
182
251
  console.log(chalk.gray("This starts both the backend (Hono + PostgreSQL)") + chalk.gray(" and the frontend (Vite + React) concurrently."));
183
252
  console.log("");
@@ -191,16 +260,93 @@ async function replacePlaceholders(options) {
191
260
  "frontend/package.json",
192
261
  "backend/package.json",
193
262
  "config/package.json",
194
- "frontend/index.html"
263
+ "frontend/index.html",
264
+ "README.md"
195
265
  ];
266
+ const packageJsonPath = path.resolve(cliRoot, "package.json");
267
+ let cliVersion = "latest";
268
+ if (fs.existsSync(packageJsonPath)) {
269
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
270
+ cliVersion = pkg.version || "latest";
271
+ }
272
+ const versionCache = /* @__PURE__ */ new Map();
273
+ const viewBin = "npm";
274
+ const getPackageVersion = async (pkgName) => {
275
+ if (versionCache.has(pkgName)) return versionCache.get(pkgName);
276
+ let versionToUse = cliVersion;
277
+ try {
278
+ const { stdout } = await execa(viewBin, ["view", `${pkgName}@${cliVersion}`, "version"]);
279
+ if (!stdout.trim()) throw new Error("Not found");
280
+ versionToUse = stdout.trim();
281
+ } catch {
282
+ try {
283
+ const tag = cliVersion.includes("canary") ? "canary" : "latest";
284
+ const { stdout } = await execa(viewBin, ["view", `${pkgName}@${tag}`, "version"]);
285
+ if (!stdout.trim()) throw new Error("Not found");
286
+ versionToUse = stdout.trim();
287
+ } catch {
288
+ try {
289
+ const { stdout } = await execa(viewBin, ["view", pkgName, "version"]);
290
+ versionToUse = stdout.trim() || "latest";
291
+ } catch {
292
+ versionToUse = "latest";
293
+ }
294
+ }
295
+ }
296
+ versionCache.set(pkgName, versionToUse);
297
+ return versionToUse;
298
+ };
299
+ const allPackages = /* @__PURE__ */ new Set();
300
+ const fileContents = /* @__PURE__ */ new Map();
196
301
  for (const file of filesToProcess) {
197
302
  const fullPath = path.resolve(options.targetDirectory, file);
198
303
  if (!fs.existsSync(fullPath)) continue;
199
- let content = fs.readFileSync(fullPath, "utf-8");
200
- content = content.replace(/\{\{PROJECT_NAME\}\}/g, options.projectName);
304
+ const content = fs.readFileSync(fullPath, "utf-8");
305
+ fileContents.set(fullPath, content);
306
+ const matches = [...content.matchAll(/"(@rebasepro\/[^"]+)":\s*"workspace:\*"/g)];
307
+ for (const match of matches) {
308
+ allPackages.add(match[1]);
309
+ }
310
+ }
311
+ console.log(chalk.gray(" Resolving package versions..."));
312
+ await Promise.all(Array.from(allPackages).map(getPackageVersion));
313
+ for (const [fullPath, originalContent] of fileContents.entries()) {
314
+ let content = originalContent.replace(/\{\{PROJECT_NAME\}\}/g, options.projectName);
315
+ const matches = [...content.matchAll(/"(@rebasepro\/[^"]+)":\s*"workspace:\*"/g)];
316
+ for (const match of matches) {
317
+ const pkgName = match[1];
318
+ const resolvedVersion = versionCache.get(pkgName) || "latest";
319
+ content = content.replace(new RegExp(`"${pkgName}":\\s*"workspace:\\*"`, "g"), `"${pkgName}": "${resolvedVersion}"`);
320
+ }
201
321
  fs.writeFileSync(fullPath, content, "utf-8");
202
322
  }
203
323
  }
324
+ function configureEnvFile(targetDirectory, databaseUrl) {
325
+ const envExamplePath = path.join(targetDirectory, ".env.example");
326
+ const envPath = path.join(targetDirectory, ".env");
327
+ if (fs.existsSync(envExamplePath) && !fs.existsSync(envPath)) {
328
+ fs.copyFileSync(envExamplePath, envPath);
329
+ const jwtSecret = crypto.randomBytes(32).toString("hex");
330
+ const dbPassword = crypto.randomBytes(16).toString("hex");
331
+ let envContent = fs.readFileSync(envPath, "utf-8");
332
+ envContent = envContent.replace(
333
+ /^JWT_SECRET=.*$/m,
334
+ `JWT_SECRET=${jwtSecret}`
335
+ );
336
+ if (databaseUrl) {
337
+ envContent = envContent.replace(
338
+ /^DATABASE_URL=.*$/m,
339
+ `DATABASE_URL=${databaseUrl}`
340
+ );
341
+ } else {
342
+ envContent = envContent.replace(
343
+ /^DATABASE_URL=.*$/m,
344
+ `DATABASE_URL=postgresql://rebase:${dbPassword}@localhost:5432/rebase`
345
+ );
346
+ }
347
+ fs.writeFileSync(envPath, envContent, "utf-8");
348
+ }
349
+ }
204
350
  async function loadCollections(collectionsDir) {
205
351
  const absDir = path.resolve(collectionsDir);
206
352
  if (!fs.existsSync(absDir)) {
@@ -371,6 +517,7 @@ function resolvePluginCliScript(backendDir, pluginName) {
371
517
  path.join(backendDir, "node_modules", pluginName, "src", "cli.ts"),
372
518
  path.join(backendDir, "node_modules", pluginName, "dist", "cli.js"),
373
519
  // For monorepo dev mode:
520
+ path.resolve(backendDir, "..", "..", "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"),
374
521
  path.resolve(backendDir, "..", "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"),
375
522
  path.resolve(backendDir, "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts")
376
523
  ];
@@ -493,11 +640,15 @@ ${chalk.green.bold("Usage")}
493
640
  ${chalk.green.bold("Commands")}
494
641
  ${chalk.gray("(Commands are provided by your active database driver plugin)")}
495
642
  ${chalk.blue.bold("generate")} Generate Schema from collection definitions
643
+ ${chalk.blue.bold("introspect")} Introspect an existing database to generate collection definitions
496
644
 
497
645
  ${chalk.green.bold("generate Options")}
498
646
  ${chalk.blue("--collections, -c")} Path to collections directory
499
647
  ${chalk.blue("--output, -o")} Output path for generated schema
500
648
  ${chalk.blue("--watch, -w")} Watch for changes and regenerate automatically
649
+
650
+ ${chalk.green.bold("introspect Options")}
651
+ ${chalk.blue("--output, -o")} Output directory for generated collection files
501
652
  `);
502
653
  }
503
654
  async function dbCommand(subcommand, rawArgs) {
@@ -557,7 +708,6 @@ ${chalk.green.bold("Usage")}
557
708
  ${chalk.green.bold("Commands")}
558
709
  ${chalk.gray("(Commands are provided by your active database driver plugin)")}
559
710
  ${chalk.blue.bold("push")} Apply schema directly to database (development)
560
- ${chalk.blue.bold("pull")} Introspect database → Schema
561
711
  ${chalk.blue.bold("generate")} Generate migration files
562
712
  ${chalk.blue.bold("migrate")} Run pending migrations
563
713
  ${chalk.blue.bold("studio")} Open Studio viewer
@@ -688,9 +838,12 @@ async function devCommand(rawArgs) {
688
838
  frontendEnv.VITE_API_URL = `http://localhost:${backendPort}`;
689
839
  console.log(` ${chalk.gray("↳ VITE_API_URL")} = ${chalk.white(`http://localhost:${backendPort}`)}`);
690
840
  }
841
+ const pm = detectPackageManager(projectRoot);
842
+ const pmCmds = getPMCommands(pm);
843
+ const runDevCmd = pmCmds.run("dev");
691
844
  const frontendChild = execa(
692
- "pnpm",
693
- ["run", "dev"],
845
+ runDevCmd[0],
846
+ runDevCmd.slice(1),
694
847
  {
695
848
  cwd: frontendDir,
696
849
  stdio: ["inherit", "pipe", "pipe"],
@@ -724,8 +877,10 @@ async function devCommand(rawArgs) {
724
877
  if (!frontendOnly && backendDir) {
725
878
  const tsxBin = resolveTsx(projectRoot);
726
879
  if (!tsxBin) {
880
+ const pmName = detectPackageManager(projectRoot);
881
+ const addCmd = pmName === "npm" ? "npm install -D tsx" : "pnpm add -D tsx";
727
882
  console.error(chalk.red(" āœ— Could not find tsx binary for backend."));
728
- console.error(chalk.gray(" Install it with: pnpm add -D tsx"));
883
+ console.error(chalk.gray(` Install it with: ${addCmd}`));
729
884
  process.exit(1);
730
885
  }
731
886
  const envFile = findEnvFile(projectRoot);
@@ -826,6 +981,46 @@ ${chalk.green.bold("Description")}
826
981
  backend is ready, and VITE_API_URL is injected automatically.
827
982
  `);
828
983
  }
984
+ async function buildCommand() {
985
+ const projectRoot = requireProjectRoot();
986
+ const pm = detectPackageManager(projectRoot);
987
+ const cmds = getPMCommands(pm);
988
+ const buildCmd = cmds.runAll("build");
989
+ console.log(`${chalk.bold("Rebase")} — Building all workspaces with ${chalk.cyan(pm)}...
990
+ `);
991
+ try {
992
+ await execa(buildCmd[0], buildCmd.slice(1), {
993
+ cwd: projectRoot,
994
+ stdio: "inherit"
995
+ });
996
+ } catch {
997
+ console.error(chalk.red("\nāœ— Build failed."));
998
+ process.exit(1);
999
+ }
1000
+ }
1001
+ async function startCommand() {
1002
+ const projectRoot = requireProjectRoot();
1003
+ const pm = detectPackageManager(projectRoot);
1004
+ const cmds = getPMCommands(pm);
1005
+ const startCmd = cmds.runWorkspace("backend", "start");
1006
+ const envFile = findEnvFile(projectRoot);
1007
+ const env = { ...process.env };
1008
+ if (envFile) {
1009
+ env.DOTENV_CONFIG_PATH = envFile;
1010
+ }
1011
+ console.log(`${chalk.bold("Rebase")} — Starting backend server...
1012
+ `);
1013
+ try {
1014
+ await execa(startCmd[0], startCmd.slice(1), {
1015
+ cwd: projectRoot,
1016
+ stdio: "inherit",
1017
+ env
1018
+ });
1019
+ } catch {
1020
+ console.error(chalk.red("\nāœ— Failed to start server."));
1021
+ process.exit(1);
1022
+ }
1023
+ }
829
1024
  async function authCommand(subcommand, rawArgs) {
830
1025
  if (!subcommand || subcommand === "--help") {
831
1026
  printAuthHelp();
@@ -1034,7 +1229,7 @@ async function entry(args) {
1034
1229
  }
1035
1230
  const command = parsedArgs._[0];
1036
1231
  const subcommand = parsedArgs._[1];
1037
- const namespacedCommands = ["schema", "db", "dev", "auth", "doctor"];
1232
+ const namespacedCommands = ["schema", "db", "dev", "build", "start", "auth", "doctor"];
1038
1233
  if (!command || parsedArgs["--help"] && !namespacedCommands.includes(command)) {
1039
1234
  printHelp();
1040
1235
  return;
@@ -1073,6 +1268,12 @@ async function entry(args) {
1073
1268
  case "dev":
1074
1269
  await devCommand(args);
1075
1270
  break;
1271
+ case "build":
1272
+ await buildCommand();
1273
+ break;
1274
+ case "start":
1275
+ await startCommand();
1276
+ break;
1076
1277
  case "auth":
1077
1278
  await authCommand(effectiveSubcommand, args);
1078
1279
  break;
@@ -1095,14 +1296,16 @@ ${chalk.green.bold("Usage")}
1095
1296
  ${chalk.green.bold("Commands")}
1096
1297
  ${chalk.blue.bold("init")} Create a new Rebase project
1097
1298
  ${chalk.blue.bold("dev")} Start the development server
1299
+ ${chalk.blue.bold("build")} Build all workspace packages
1300
+ ${chalk.blue.bold("start")} Start the backend server ${chalk.gray("(production)")}
1098
1301
 
1099
1302
  ${chalk.green.bold("Schema")}
1100
1303
  ${chalk.blue.bold("schema generate")} Generate Drizzle schema from collections
1304
+ ${chalk.blue.bold("schema introspect")} Introspect database → Rebase collections
1101
1305
  ${chalk.blue.bold("schema")} ${chalk.gray("--help")} Show schema command help
1102
1306
 
1103
1307
  ${chalk.green.bold("Database")}
1104
1308
  ${chalk.blue.bold("db push")} Apply schema directly to database ${chalk.gray("(dev)")}
1105
- ${chalk.blue.bold("db pull")} Introspect database → Drizzle schema
1106
1309
  ${chalk.blue.bold("db generate")} Generate SQL migration files
1107
1310
  ${chalk.blue.bold("db migrate")} Run pending migrations
1108
1311
  ${chalk.blue.bold("db studio")} Open Drizzle Studio
@@ -1170,6 +1373,8 @@ async function logout(env, _debug) {
1170
1373
  }
1171
1374
  export {
1172
1375
  authCommand,
1376
+ buildCommand,
1377
+ configureEnvFile,
1173
1378
  createRebaseApp,
1174
1379
  dbCommand,
1175
1380
  devCommand,
@@ -1189,6 +1394,7 @@ export {
1189
1394
  resolveLocalBin,
1190
1395
  resolvePluginCliScript,
1191
1396
  resolveTsx,
1192
- schemaCommand
1397
+ schemaCommand,
1398
+ startCommand
1193
1399
  };
1194
1400
  //# sourceMappingURL=index.es.js.map