@rebasepro/cli 0.0.1-canary.ca2cb6e → 0.0.1-canary.cbdd980

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
137
  const targetDirectory = path.resolve(process.cwd(), nameArg || answers.projectName);
85
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,7 +260,8 @@ 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
  ];
196
266
  const packageJsonPath = path.resolve(cliRoot, "package.json");
197
267
  let cliVersion = "latest";
@@ -199,15 +269,84 @@ async function replacePlaceholders(options) {
199
269
  const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
200
270
  cliVersion = pkg.version || "latest";
201
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();
202
301
  for (const file of filesToProcess) {
203
302
  const fullPath = path.resolve(options.targetDirectory, file);
204
303
  if (!fs.existsSync(fullPath)) continue;
205
- let content = fs.readFileSync(fullPath, "utf-8");
206
- content = content.replace(/\{\{PROJECT_NAME\}\}/g, options.projectName);
207
- content = content.replace(/("@rebasepro\/[^"]+":\s*)"workspace:\*"/g, `$1"${cliVersion}"`);
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
+ }
208
321
  fs.writeFileSync(fullPath, content, "utf-8");
209
322
  }
210
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
+ }
211
350
  async function loadCollections(collectionsDir) {
212
351
  const absDir = path.resolve(collectionsDir);
213
352
  if (!fs.existsSync(absDir)) {
@@ -378,6 +517,7 @@ function resolvePluginCliScript(backendDir, pluginName) {
378
517
  path.join(backendDir, "node_modules", pluginName, "src", "cli.ts"),
379
518
  path.join(backendDir, "node_modules", pluginName, "dist", "cli.js"),
380
519
  // For monorepo dev mode:
520
+ path.resolve(backendDir, "..", "..", "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"),
381
521
  path.resolve(backendDir, "..", "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"),
382
522
  path.resolve(backendDir, "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts")
383
523
  ];
@@ -500,11 +640,15 @@ ${chalk.green.bold("Usage")}
500
640
  ${chalk.green.bold("Commands")}
501
641
  ${chalk.gray("(Commands are provided by your active database driver plugin)")}
502
642
  ${chalk.blue.bold("generate")} Generate Schema from collection definitions
643
+ ${chalk.blue.bold("introspect")} Introspect an existing database to generate collection definitions
503
644
 
504
645
  ${chalk.green.bold("generate Options")}
505
646
  ${chalk.blue("--collections, -c")} Path to collections directory
506
647
  ${chalk.blue("--output, -o")} Output path for generated schema
507
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
508
652
  `);
509
653
  }
510
654
  async function dbCommand(subcommand, rawArgs) {
@@ -564,7 +708,6 @@ ${chalk.green.bold("Usage")}
564
708
  ${chalk.green.bold("Commands")}
565
709
  ${chalk.gray("(Commands are provided by your active database driver plugin)")}
566
710
  ${chalk.blue.bold("push")} Apply schema directly to database (development)
567
- ${chalk.blue.bold("pull")} Introspect database → Schema
568
711
  ${chalk.blue.bold("generate")} Generate migration files
569
712
  ${chalk.blue.bold("migrate")} Run pending migrations
570
713
  ${chalk.blue.bold("studio")} Open Studio viewer
@@ -695,9 +838,12 @@ async function devCommand(rawArgs) {
695
838
  frontendEnv.VITE_API_URL = `http://localhost:${backendPort}`;
696
839
  console.log(` ${chalk.gray("↳ VITE_API_URL")} = ${chalk.white(`http://localhost:${backendPort}`)}`);
697
840
  }
841
+ const pm = detectPackageManager(projectRoot);
842
+ const pmCmds = getPMCommands(pm);
843
+ const runDevCmd = pmCmds.run("dev");
698
844
  const frontendChild = execa(
699
- "pnpm",
700
- ["run", "dev"],
845
+ runDevCmd[0],
846
+ runDevCmd.slice(1),
701
847
  {
702
848
  cwd: frontendDir,
703
849
  stdio: ["inherit", "pipe", "pipe"],
@@ -731,8 +877,10 @@ async function devCommand(rawArgs) {
731
877
  if (!frontendOnly && backendDir) {
732
878
  const tsxBin = resolveTsx(projectRoot);
733
879
  if (!tsxBin) {
880
+ const pmName = detectPackageManager(projectRoot);
881
+ const addCmd = pmName === "npm" ? "npm install -D tsx" : "pnpm add -D tsx";
734
882
  console.error(chalk.red(" āœ— Could not find tsx binary for backend."));
735
- console.error(chalk.gray(" Install it with: pnpm add -D tsx"));
883
+ console.error(chalk.gray(` Install it with: ${addCmd}`));
736
884
  process.exit(1);
737
885
  }
738
886
  const envFile = findEnvFile(projectRoot);
@@ -833,6 +981,46 @@ ${chalk.green.bold("Description")}
833
981
  backend is ready, and VITE_API_URL is injected automatically.
834
982
  `);
835
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
+ }
836
1024
  async function authCommand(subcommand, rawArgs) {
837
1025
  if (!subcommand || subcommand === "--help") {
838
1026
  printAuthHelp();
@@ -1041,7 +1229,7 @@ async function entry(args) {
1041
1229
  }
1042
1230
  const command = parsedArgs._[0];
1043
1231
  const subcommand = parsedArgs._[1];
1044
- const namespacedCommands = ["schema", "db", "dev", "auth", "doctor"];
1232
+ const namespacedCommands = ["schema", "db", "dev", "build", "start", "auth", "doctor"];
1045
1233
  if (!command || parsedArgs["--help"] && !namespacedCommands.includes(command)) {
1046
1234
  printHelp();
1047
1235
  return;
@@ -1080,6 +1268,12 @@ async function entry(args) {
1080
1268
  case "dev":
1081
1269
  await devCommand(args);
1082
1270
  break;
1271
+ case "build":
1272
+ await buildCommand();
1273
+ break;
1274
+ case "start":
1275
+ await startCommand();
1276
+ break;
1083
1277
  case "auth":
1084
1278
  await authCommand(effectiveSubcommand, args);
1085
1279
  break;
@@ -1102,14 +1296,16 @@ ${chalk.green.bold("Usage")}
1102
1296
  ${chalk.green.bold("Commands")}
1103
1297
  ${chalk.blue.bold("init")} Create a new Rebase project
1104
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)")}
1105
1301
 
1106
1302
  ${chalk.green.bold("Schema")}
1107
1303
  ${chalk.blue.bold("schema generate")} Generate Drizzle schema from collections
1304
+ ${chalk.blue.bold("schema introspect")} Introspect database → Rebase collections
1108
1305
  ${chalk.blue.bold("schema")} ${chalk.gray("--help")} Show schema command help
1109
1306
 
1110
1307
  ${chalk.green.bold("Database")}
1111
1308
  ${chalk.blue.bold("db push")} Apply schema directly to database ${chalk.gray("(dev)")}
1112
- ${chalk.blue.bold("db pull")} Introspect database → Drizzle schema
1113
1309
  ${chalk.blue.bold("db generate")} Generate SQL migration files
1114
1310
  ${chalk.blue.bold("db migrate")} Run pending migrations
1115
1311
  ${chalk.blue.bold("db studio")} Open Drizzle Studio
@@ -1177,6 +1373,8 @@ async function logout(env, _debug) {
1177
1373
  }
1178
1374
  export {
1179
1375
  authCommand,
1376
+ buildCommand,
1377
+ configureEnvFile,
1180
1378
  createRebaseApp,
1181
1379
  dbCommand,
1182
1380
  devCommand,
@@ -1196,6 +1394,7 @@ export {
1196
1394
  resolveLocalBin,
1197
1395
  resolvePluginCliScript,
1198
1396
  resolveTsx,
1199
- schemaCommand
1397
+ schemaCommand,
1398
+ startCommand
1200
1399
  };
1201
1400
  //# sourceMappingURL=index.es.js.map