@rebasepro/cli 0.0.1-canary.0 → 0.0.1-canary.0e2f892

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.
Files changed (49) hide show
  1. package/dist/commands/auth.d.ts +1 -0
  2. package/dist/commands/cli.test.d.ts +1 -0
  3. package/dist/commands/db.d.ts +1 -0
  4. package/dist/commands/dev.d.ts +1 -0
  5. package/dist/commands/dev.test.d.ts +1 -0
  6. package/dist/commands/doctor.d.ts +1 -0
  7. package/dist/commands/generate_sdk.d.ts +18 -0
  8. package/dist/commands/init.d.ts +3 -0
  9. package/dist/commands/init.test.d.ts +1 -0
  10. package/dist/commands/schema.d.ts +1 -0
  11. package/dist/index.cjs +1058 -30
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.ts +5 -0
  14. package/dist/index.es.js +1060 -29
  15. package/dist/index.es.js.map +1 -1
  16. package/dist/utils/project.d.ts +45 -0
  17. package/dist/utils/project.test.d.ts +1 -0
  18. package/package.json +20 -11
  19. package/templates/template/.dockerignore +28 -0
  20. package/templates/template/.env.template +42 -11
  21. package/templates/template/README.md +74 -17
  22. package/templates/template/backend/Dockerfile +55 -10
  23. package/templates/template/backend/drizzle.config.ts +15 -3
  24. package/templates/template/backend/functions/hello.ts +52 -0
  25. package/templates/template/backend/package.json +30 -34
  26. package/templates/template/backend/src/env.ts +52 -0
  27. package/templates/template/backend/src/index.ts +113 -99
  28. package/templates/template/backend/tsconfig.json +1 -1
  29. package/templates/template/config/collections/authors.ts +45 -0
  30. package/templates/template/config/collections/index.ts +5 -0
  31. package/templates/template/{shared → config}/collections/posts.ts +40 -2
  32. package/templates/template/config/collections/tags.ts +27 -0
  33. package/templates/template/config/package.json +28 -0
  34. package/templates/template/docker-compose.yml +78 -17
  35. package/templates/template/frontend/Dockerfile +36 -14
  36. package/templates/template/frontend/nginx.conf +40 -0
  37. package/templates/template/frontend/package.json +9 -10
  38. package/templates/template/frontend/src/App.tsx +24 -110
  39. package/templates/template/frontend/src/index.css +15 -1
  40. package/templates/template/frontend/src/main.tsx +2 -2
  41. package/templates/template/frontend/vite.config.ts +32 -3
  42. package/templates/template/package.json +12 -16
  43. package/templates/template/pnpm-workspace.yaml +4 -0
  44. package/templates/template/scripts/example.ts +91 -0
  45. package/templates/template/backend/scripts/db-generate.ts +0 -60
  46. package/templates/template/shared/collections/index.ts +0 -3
  47. package/templates/template/shared/package.json +0 -28
  48. /package/templates/template/{shared → config}/index.ts +0 -0
  49. /package/templates/template/{shared → config}/tsconfig.json +0 -0
package/dist/index.cjs CHANGED
@@ -1,6 +1,6 @@
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("ncp"), require("url"), require("os")) : typeof define === "function" && define.amd ? define(["exports", "chalk", "arg", "inquirer", "path", "fs", "util", "execa", "ncp", "url", "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.ncp, global.url, global.os));
3
- })(this, (function(exports2, chalk, arg, inquirer, path, fs, util, execa, ncp, url, os) {
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("chalk"), require("arg"), require("inquirer"), require("path"), require("fs"), require("util"), require("execa"), require("ncp"), 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", "ncp", "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.ncp, global.url, global.crypto, global.sdkGenerator, global.child_process, global.os));
3
+ })(this, (function(exports2, chalk, arg, inquirer, path, fs, util, execa, ncp, url, crypto, sdkGenerator, child_process, os) {
4
4
  "use strict";
5
5
  var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
6
6
  function _interopNamespaceDefault(e) {
@@ -46,7 +46,9 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
46
46
  const args = arg(
47
47
  {
48
48
  "--git": Boolean,
49
- "-g": "--git"
49
+ "--install": Boolean,
50
+ "-g": "--git",
51
+ "-i": "--install"
50
52
  },
51
53
  {
52
54
  argv: rawArgs.slice(3),
@@ -79,15 +81,39 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
79
81
  default: true
80
82
  });
81
83
  }
84
+ if (!args["--install"]) {
85
+ questions.push({
86
+ type: "confirm",
87
+ name: "installDeps",
88
+ message: "Install dependencies with pnpm?",
89
+ default: true
90
+ });
91
+ }
92
+ questions.push({
93
+ type: "input",
94
+ name: "databaseUrl",
95
+ message: "Enter your PostgreSQL database connection string (leave blank to use a local default):",
96
+ default: ""
97
+ });
98
+ questions.push({
99
+ type: "confirm",
100
+ name: "introspect",
101
+ message: "Would you like to introspect this database to automatically generate collections?",
102
+ default: true,
103
+ when: (answers2) => !!answers2.databaseUrl?.trim()
104
+ });
82
105
  const answers = await inquirer.prompt(questions);
83
- const projectName = nameArg || answers.projectName;
84
- const targetDirectory = path.resolve(process.cwd(), projectName);
106
+ const targetDirectory = path.resolve(process.cwd(), nameArg || answers.projectName);
107
+ const projectName = path.basename(targetDirectory);
85
108
  const templateDirectory = path.resolve(cliRoot, "templates", "template");
86
109
  return {
87
110
  projectName,
88
111
  git: args["--git"] || answers.git || false,
112
+ installDeps: args["--install"] || answers.installDeps || false,
89
113
  targetDirectory,
90
- templateDirectory
114
+ templateDirectory,
115
+ databaseUrl: answers.databaseUrl?.trim() || void 0,
116
+ introspect: answers.introspect || false
91
117
  };
92
118
  }
93
119
  async function createProject(options) {
@@ -106,19 +132,47 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
106
132
  process.exit(1);
107
133
  }
108
134
  console.log(chalk.gray(" Copying project files..."));
109
- await copy(options.templateDirectory, options.targetDirectory, {
110
- clobber: false,
111
- dot: true,
112
- filter: (source) => {
113
- const basename = path.basename(source);
114
- return basename !== "node_modules" && basename !== ".DS_Store";
115
- }
116
- });
135
+ try {
136
+ await copy(options.templateDirectory, options.targetDirectory, {
137
+ clobber: false,
138
+ dot: true,
139
+ filter: (source) => {
140
+ const basename = path.basename(source);
141
+ return basename !== "node_modules" && basename !== ".DS_Store";
142
+ }
143
+ });
144
+ } catch (err) {
145
+ console.error(`${chalk.red.bold("ERROR")} Failed to copy template files: ${err instanceof Error ? err.message : String(err)}`);
146
+ process.exit(1);
147
+ }
117
148
  await replacePlaceholders(options);
118
149
  const envTemplatePath = path.join(options.targetDirectory, ".env.template");
119
150
  const envPath = path.join(options.targetDirectory, ".env");
120
151
  if (fs.existsSync(envTemplatePath) && !fs.existsSync(envPath)) {
121
152
  fs.renameSync(envTemplatePath, envPath);
153
+ const jwtSecret = crypto.randomBytes(32).toString("hex");
154
+ let envContent = fs.readFileSync(envPath, "utf-8");
155
+ envContent = envContent.replace(
156
+ "change-this-to-a-secure-random-string",
157
+ jwtSecret
158
+ );
159
+ if (options.databaseUrl) {
160
+ envContent = envContent.replace(
161
+ "postgresql://rebase:password@localhost:5432/rebase",
162
+ options.databaseUrl
163
+ );
164
+ } else {
165
+ const dbPassword = crypto.randomBytes(16).toString("hex");
166
+ envContent = envContent.replace(
167
+ "postgresql://rebase:password@localhost:5432/rebase",
168
+ `postgresql://rebase:${dbPassword}@localhost:5432/rebase`
169
+ );
170
+ envContent += `
171
+ # Docker Compose Database Password
172
+ POSTGRES_PASSWORD=${dbPassword}
173
+ `;
174
+ }
175
+ fs.writeFileSync(envPath, envContent, "utf-8");
122
176
  }
123
177
  if (options.git) {
124
178
  console.log(chalk.gray(" Initializing git repository..."));
@@ -128,23 +182,65 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
128
182
  console.warn(chalk.yellow(" Warning: Failed to initialize git repository"));
129
183
  }
130
184
  }
185
+ if (options.installDeps) {
186
+ console.log("");
187
+ console.log(chalk.gray(" Installing dependencies with pnpm..."));
188
+ console.log("");
189
+ try {
190
+ await execa("pnpm", ["install"], {
191
+ cwd: options.targetDirectory,
192
+ stdio: "inherit"
193
+ });
194
+ } catch {
195
+ console.warn(chalk.yellow(" Warning: Failed to install dependencies. You may need to run `pnpm install` manually."));
196
+ }
197
+ }
198
+ if (options.introspect) {
199
+ console.log("");
200
+ if (options.installDeps) {
201
+ console.log(chalk.gray(" Introspecting database and generating collections..."));
202
+ console.log("");
203
+ try {
204
+ await execa("pnpm", ["exec", "rebase", "schema", "introspect"], {
205
+ cwd: options.targetDirectory,
206
+ stdio: "inherit"
207
+ });
208
+ console.log(chalk.green(" Database successfully introspected!"));
209
+ } catch {
210
+ console.warn(chalk.yellow(" Warning: Failed to introspect database automatically."));
211
+ console.warn(chalk.yellow(" You can run `pnpm exec rebase schema introspect` manually after setup."));
212
+ }
213
+ } else {
214
+ console.warn(chalk.yellow(" Skipping introspection because dependencies were not installed."));
215
+ console.warn(chalk.yellow(" Run `pnpm install` then `pnpm exec rebase schema introspect` manually."));
216
+ }
217
+ }
131
218
  console.log("");
132
219
  console.log(`${chalk.green.bold("✓")} Project ${chalk.bold(options.projectName)} created successfully!`);
133
220
  console.log("");
134
221
  console.log(chalk.bold("Next steps:"));
135
222
  console.log("");
136
223
  console.log(` ${chalk.cyan("cd")} ${options.projectName}`);
137
- console.log(` ${chalk.cyan("pnpm install")}`);
224
+ if (!options.installDeps) {
225
+ console.log(` ${chalk.cyan("pnpm install")}`);
226
+ }
138
227
  console.log("");
139
- console.log(chalk.gray(" # Set up your database connection in .env"));
140
- console.log(chalk.gray(" # Then run:"));
228
+ if (options.databaseUrl) {
229
+ console.log(chalk.gray(" # Your database is configured! Start the dev server:"));
230
+ } else {
231
+ console.log(chalk.gray(" # A local database configuration has been generated in .env"));
232
+ console.log(chalk.gray(" # If using the included docker-compose.yml, start it with:"));
233
+ console.log(` ${chalk.cyan("docker compose up -d")}`);
234
+ console.log("");
235
+ console.log(chalk.gray(" # Then start the dev server:"));
236
+ }
141
237
  console.log("");
142
238
  console.log(` ${chalk.cyan("pnpm dev")}`);
143
239
  console.log("");
144
- console.log(chalk.gray("This starts both the backend (Express + PostgreSQL)") + chalk.gray(" and the frontend (Vite + React) concurrently."));
240
+ console.log(chalk.gray("This starts both the backend (Hono + PostgreSQL)") + chalk.gray(" and the frontend (Vite + React) concurrently."));
145
241
  console.log("");
146
242
  console.log(chalk.gray("Docs: https://rebase.pro/docs"));
147
- console.log(chalk.gray("GitHub: https://github.com/rebaseco/rebase"));
243
+ console.log(chalk.gray("GitHub: https://github.com/rebasepro/rebase"));
148
244
  console.log("");
149
245
  }
150
246
  async function replacePlaceholders(options) {
@@ -152,17 +248,872 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
152
248
  "package.json",
153
249
  "frontend/package.json",
154
250
  "backend/package.json",
155
- "shared/package.json",
251
+ "config/package.json",
156
252
  "frontend/index.html"
157
253
  ];
254
+ const packageJsonPath = path.resolve(cliRoot, "package.json");
255
+ let cliVersion = "latest";
256
+ if (fs.existsSync(packageJsonPath)) {
257
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
258
+ cliVersion = pkg.version || "latest";
259
+ }
260
+ const versionCache = /* @__PURE__ */ new Map();
261
+ const getPackageVersion = async (pkgName) => {
262
+ if (versionCache.has(pkgName)) return versionCache.get(pkgName);
263
+ let versionToUse = cliVersion;
264
+ try {
265
+ const { stdout } = await execa("npm", ["--loglevel", "error", "info", `${pkgName}@${cliVersion}`, "version"]);
266
+ if (!stdout.trim()) throw new Error("Not found");
267
+ versionToUse = stdout.trim();
268
+ } catch {
269
+ try {
270
+ const tag = cliVersion.includes("canary") ? "canary" : "latest";
271
+ const { stdout } = await execa("npm", ["--loglevel", "error", "info", `${pkgName}@${tag}`, "version"]);
272
+ if (!stdout.trim()) throw new Error("Not found");
273
+ versionToUse = stdout.trim();
274
+ } catch {
275
+ try {
276
+ const { stdout } = await execa("npm", ["--loglevel", "error", "info", pkgName, "version"]);
277
+ versionToUse = stdout.trim() || "latest";
278
+ } catch {
279
+ versionToUse = "latest";
280
+ }
281
+ }
282
+ }
283
+ versionCache.set(pkgName, versionToUse);
284
+ return versionToUse;
285
+ };
286
+ const allPackages = /* @__PURE__ */ new Set();
287
+ const fileContents = /* @__PURE__ */ new Map();
158
288
  for (const file of filesToProcess) {
159
289
  const fullPath = path.resolve(options.targetDirectory, file);
160
290
  if (!fs.existsSync(fullPath)) continue;
161
- let content = fs.readFileSync(fullPath, "utf-8");
162
- content = content.replace(/\{\{PROJECT_NAME\}\}/g, options.projectName);
291
+ const content = fs.readFileSync(fullPath, "utf-8");
292
+ fileContents.set(fullPath, content);
293
+ const matches = [...content.matchAll(/"(@rebasepro\/[^"]+)":\s*"workspace:\*"/g)];
294
+ for (const match of matches) {
295
+ allPackages.add(match[1]);
296
+ }
297
+ }
298
+ console.log(chalk.gray(" Resolving package versions..."));
299
+ await Promise.all(Array.from(allPackages).map(getPackageVersion));
300
+ for (const [fullPath, originalContent] of fileContents.entries()) {
301
+ let content = originalContent.replace(/\{\{PROJECT_NAME\}\}/g, options.projectName);
302
+ const matches = [...content.matchAll(/"(@rebasepro\/[^"]+)":\s*"workspace:\*"/g)];
303
+ for (const match of matches) {
304
+ const pkgName = match[1];
305
+ const resolvedVersion = versionCache.get(pkgName) || "latest";
306
+ content = content.replace(new RegExp(`"${pkgName}":\\s*"workspace:\\*"`, "g"), `"${pkgName}": "${resolvedVersion}"`);
307
+ }
163
308
  fs.writeFileSync(fullPath, content, "utf-8");
164
309
  }
165
310
  }
311
+ async function loadCollections(collectionsDir) {
312
+ const absDir = path.resolve(collectionsDir);
313
+ if (!fs.existsSync(absDir)) {
314
+ throw new Error(`Collections directory not found: ${absDir}`);
315
+ }
316
+ let jiti;
317
+ try {
318
+ const jitiModule = await import("jiti");
319
+ jiti = jitiModule.default || jitiModule;
320
+ } catch {
321
+ throw new Error(
322
+ "Could not load 'jiti'. Install it with: pnpm add -D jiti\njiti is required to dynamically import TypeScript collection definitions."
323
+ );
324
+ }
325
+ const jitiInstance = jiti(absDir, {
326
+ interopDefault: true,
327
+ esmResolve: true
328
+ });
329
+ const indexCandidates = ["index.ts", "index.js", "index.mjs"];
330
+ let indexPath = null;
331
+ for (const candidate of indexCandidates) {
332
+ const p = path.join(absDir, candidate);
333
+ if (fs.existsSync(p)) {
334
+ indexPath = p;
335
+ break;
336
+ }
337
+ }
338
+ if (!indexPath) {
339
+ console.log(chalk.yellow(" No index file found, scanning individual collection files..."));
340
+ const collections = [];
341
+ const files = fs.readdirSync(absDir).filter(
342
+ (f) => (f.endsWith(".ts") || f.endsWith(".js")) && !f.startsWith(".")
343
+ );
344
+ for (const file of files) {
345
+ try {
346
+ const mod2 = jitiInstance(path.join(absDir, file));
347
+ const exported2 = mod2.default || mod2;
348
+ if (exported2 && typeof exported2 === "object" && "slug" in exported2) {
349
+ collections.push(exported2);
350
+ } else if (Array.isArray(exported2)) {
351
+ collections.push(...exported2);
352
+ }
353
+ } catch (err) {
354
+ console.warn(chalk.yellow(` ⚠ Skipping ${file}: ${err.message}`));
355
+ }
356
+ }
357
+ return collections;
358
+ }
359
+ const mod = jitiInstance(indexPath);
360
+ const exported = mod.default || mod;
361
+ if (Array.isArray(exported)) {
362
+ return exported;
363
+ } else if (typeof exported === "object" && exported !== null) {
364
+ if ("collections" in exported && Array.isArray(exported.collections)) {
365
+ return exported.collections;
366
+ }
367
+ const collections = [];
368
+ for (const value of Object.values(exported)) {
369
+ if (value && typeof value === "object" && "slug" in value) {
370
+ collections.push(value);
371
+ }
372
+ }
373
+ if (collections.length > 0) return collections;
374
+ }
375
+ throw new Error(
376
+ `Could not extract collections from ${indexPath}.
377
+ Expected a default export of EntityCollection[] or an object with named collection exports.`
378
+ );
379
+ }
380
+ function writeFiles(outputDir, files) {
381
+ const absOutput = path.resolve(outputDir);
382
+ fs.mkdirSync(absOutput, { recursive: true });
383
+ for (const file of files) {
384
+ const filePath = path.join(absOutput, file.path);
385
+ const dir = path.dirname(filePath);
386
+ if (!fs.existsSync(dir)) {
387
+ fs.mkdirSync(dir, { recursive: true });
388
+ }
389
+ fs.writeFileSync(filePath, file.content, "utf-8");
390
+ }
391
+ }
392
+ async function generateSdkCommand(args) {
393
+ const { collectionsDir, output, cwd } = args;
394
+ const resolvedCollectionsDir = path.isAbsolute(collectionsDir) ? collectionsDir : path.join(cwd, collectionsDir);
395
+ const resolvedOutput = path.isAbsolute(output) ? output : path.join(cwd, output);
396
+ console.log("");
397
+ console.log(chalk.bold(" 🔧 Rebase SDK Generator"));
398
+ console.log("");
399
+ console.log(` ${chalk.gray("Collections:")} ${resolvedCollectionsDir}`);
400
+ console.log(` ${chalk.gray("Output:")} ${resolvedOutput}`);
401
+ console.log("");
402
+ console.log(chalk.cyan(" → Loading collection definitions..."));
403
+ const collections = await loadCollections(resolvedCollectionsDir);
404
+ if (collections.length === 0) {
405
+ console.log(chalk.red(" ✗ No collections found. Nothing to generate."));
406
+ process.exit(1);
407
+ }
408
+ console.log(chalk.green(` ✓ Found ${collections.length} collection(s): ${collections.map((c) => c.slug).join(", ")}`));
409
+ console.log("");
410
+ console.log(chalk.cyan(" → Generating SDK files..."));
411
+ const files = sdkGenerator.generateSDK(collections);
412
+ console.log(chalk.green(` ✓ Generated ${files.length} file(s)`));
413
+ console.log(chalk.cyan(` → Writing to ${resolvedOutput}...`));
414
+ writeFiles(resolvedOutput, files);
415
+ console.log("");
416
+ console.log(chalk.green.bold(" ✓ SDK generated successfully!"));
417
+ console.log("");
418
+ console.log(chalk.gray(" Usage:"));
419
+ console.log(chalk.gray(" import { createRebaseClient } from '@rebasepro/client';"));
420
+ console.log(chalk.gray(` import type { Database } from './${path.relative(cwd, path.join(resolvedOutput, "database.types"))}';`));
421
+ console.log("");
422
+ console.log(chalk.gray(" const rebase = createRebaseClient<Database>({"));
423
+ console.log(chalk.gray(" baseUrl: 'http://localhost:3001',"));
424
+ console.log(chalk.gray(" // token: 'your-jwt-token',"));
425
+ console.log(chalk.gray(" });"));
426
+ console.log("");
427
+ console.log(chalk.gray(` const { data } = await rebase.collection('${collections[0]?.slug || "my_collection"}').find();`));
428
+ console.log("");
429
+ }
430
+ function findProjectRoot(startDir = process.cwd()) {
431
+ let dir = path.resolve(startDir);
432
+ const root = path.parse(dir).root;
433
+ while (dir !== root) {
434
+ const pkgPath = path.join(dir, "package.json");
435
+ if (fs.existsSync(pkgPath)) {
436
+ try {
437
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
438
+ if (pkg.workspaces && Array.isArray(pkg.workspaces)) {
439
+ const hasBackend = pkg.workspaces.some(
440
+ (w) => w === "backend" || w.includes("backend")
441
+ );
442
+ if (hasBackend) return dir;
443
+ }
444
+ } catch {
445
+ }
446
+ if (fs.existsSync(path.join(dir, "backend")) && fs.existsSync(path.join(dir, "config"))) {
447
+ return dir;
448
+ }
449
+ }
450
+ dir = path.dirname(dir);
451
+ }
452
+ return null;
453
+ }
454
+ function findBackendDir(projectRoot) {
455
+ const backendDir = path.join(projectRoot, "backend");
456
+ return fs.existsSync(backendDir) ? backendDir : null;
457
+ }
458
+ function getActiveBackendPlugin(backendDir) {
459
+ const pkgPath = path.join(backendDir, "package.json");
460
+ if (!fs.existsSync(pkgPath)) return null;
461
+ try {
462
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
463
+ const deps = {
464
+ ...pkg.dependencies,
465
+ ...pkg.devDependencies
466
+ };
467
+ for (const dep of Object.keys(deps)) {
468
+ if (dep.startsWith("@rebasepro/server-") && dep !== "@rebasepro/server-core") {
469
+ return dep;
470
+ }
471
+ }
472
+ } catch {
473
+ }
474
+ return null;
475
+ }
476
+ function resolvePluginCliScript(backendDir, pluginName) {
477
+ const candidates = [
478
+ path.join(backendDir, "node_modules", pluginName, "src", "cli.ts"),
479
+ path.join(backendDir, "node_modules", pluginName, "dist", "cli.js"),
480
+ // For monorepo dev mode:
481
+ path.resolve(backendDir, "..", "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"),
482
+ path.resolve(backendDir, "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts")
483
+ ];
484
+ for (const candidate of candidates) {
485
+ if (fs.existsSync(candidate)) return candidate;
486
+ }
487
+ return null;
488
+ }
489
+ function findFrontendDir(projectRoot) {
490
+ const frontendDir = path.join(projectRoot, "frontend");
491
+ return fs.existsSync(frontendDir) ? frontendDir : null;
492
+ }
493
+ function findEnvFile(projectRoot) {
494
+ const candidates = [
495
+ path.join(projectRoot, ".env"),
496
+ path.join(projectRoot, "backend", ".env")
497
+ ];
498
+ for (const candidate of candidates) {
499
+ if (fs.existsSync(candidate)) return candidate;
500
+ }
501
+ return null;
502
+ }
503
+ function resolveLocalBin(projectRoot, binName) {
504
+ const candidates = [
505
+ path.join(projectRoot, "backend", "node_modules", ".bin", binName),
506
+ path.join(projectRoot, "node_modules", ".bin", binName)
507
+ ];
508
+ let parent = path.dirname(projectRoot);
509
+ const rootDir = path.parse(parent).root;
510
+ while (parent !== rootDir) {
511
+ candidates.push(path.join(parent, "node_modules", ".bin", binName));
512
+ parent = path.dirname(parent);
513
+ }
514
+ for (const candidate of candidates) {
515
+ if (fs.existsSync(candidate)) return candidate;
516
+ }
517
+ try {
518
+ const globalPath = child_process.execSync(`which ${binName}`, { encoding: "utf-8" }).trim();
519
+ if (globalPath && fs.existsSync(globalPath)) return globalPath;
520
+ } catch {
521
+ }
522
+ return null;
523
+ }
524
+ function resolveTsx(projectRoot) {
525
+ return resolveLocalBin(projectRoot, "tsx");
526
+ }
527
+ function requireProjectRoot() {
528
+ const root = findProjectRoot();
529
+ if (!root) {
530
+ console.error(chalk.red("✗ Could not find a Rebase project root."));
531
+ console.error(chalk.gray(" Make sure you are inside a Rebase project directory"));
532
+ console.error(chalk.gray(" (one with backend/, frontend/, and shared/ directories)."));
533
+ process.exit(1);
534
+ }
535
+ return root;
536
+ }
537
+ function requireBackendDir(projectRoot) {
538
+ const backendDir = findBackendDir(projectRoot);
539
+ if (!backendDir) {
540
+ console.error(chalk.red("✗ Could not find a backend/ directory."));
541
+ console.error(chalk.gray(` Expected at: ${path.join(projectRoot, "backend")}`));
542
+ process.exit(1);
543
+ }
544
+ return backendDir;
545
+ }
546
+ async function schemaCommand(subcommand, rawArgs) {
547
+ if (!subcommand || subcommand === "--help") {
548
+ printSchemaHelp();
549
+ return;
550
+ }
551
+ const projectRoot = requireProjectRoot();
552
+ const backendDir = requireBackendDir(projectRoot);
553
+ const activePlugin = getActiveBackendPlugin(backendDir);
554
+ if (!activePlugin) {
555
+ console.error(chalk.red("✗ Could not detect an active database plugin."));
556
+ console.error(chalk.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
557
+ process.exit(1);
558
+ }
559
+ const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
560
+ if (!pluginCli) {
561
+ console.error(chalk.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
562
+ process.exit(1);
563
+ }
564
+ const envFile = findEnvFile(projectRoot);
565
+ const env = { ...process.env };
566
+ if (envFile) {
567
+ env.DOTENV_CONFIG_PATH = envFile;
568
+ }
569
+ try {
570
+ const isTs = pluginCli.endsWith(".ts");
571
+ if (isTs) {
572
+ const tsxBin = resolveTsx(projectRoot);
573
+ if (!tsxBin) {
574
+ console.error(chalk.red("✗ Could not find tsx binary."));
575
+ process.exit(1);
576
+ }
577
+ await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
578
+ cwd: backendDir,
579
+ stdio: "inherit",
580
+ env
581
+ });
582
+ } else {
583
+ await execa("node", [pluginCli, ...rawArgs.slice(2)], {
584
+ cwd: backendDir,
585
+ stdio: "inherit",
586
+ env
587
+ });
588
+ }
589
+ } catch (err) {
590
+ process.exit(1);
591
+ }
592
+ }
593
+ function printSchemaHelp() {
594
+ console.log(`
595
+ ${chalk.bold("rebase schema")} — Schema management commands
596
+
597
+ ${chalk.green.bold("Usage")}
598
+ rebase schema ${chalk.blue("<command>")} [options]
599
+
600
+ ${chalk.green.bold("Commands")}
601
+ ${chalk.gray("(Commands are provided by your active database driver plugin)")}
602
+ ${chalk.blue.bold("generate")} Generate Schema from collection definitions
603
+ ${chalk.blue.bold("introspect")} Introspect an existing database to generate collection definitions
604
+
605
+ ${chalk.green.bold("generate Options")}
606
+ ${chalk.blue("--collections, -c")} Path to collections directory
607
+ ${chalk.blue("--output, -o")} Output path for generated schema
608
+ ${chalk.blue("--watch, -w")} Watch for changes and regenerate automatically
609
+
610
+ ${chalk.green.bold("introspect Options")}
611
+ ${chalk.blue("--output, -o")} Output directory for generated collection files
612
+ `);
613
+ }
614
+ async function dbCommand(subcommand, rawArgs) {
615
+ if (!subcommand || subcommand === "--help") {
616
+ printDbHelp();
617
+ return;
618
+ }
619
+ const projectRoot = requireProjectRoot();
620
+ const backendDir = requireBackendDir(projectRoot);
621
+ const activePlugin = getActiveBackendPlugin(backendDir);
622
+ if (!activePlugin) {
623
+ console.error(chalk.red("✗ Could not detect an active database plugin."));
624
+ console.error(chalk.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
625
+ process.exit(1);
626
+ }
627
+ const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
628
+ if (!pluginCli) {
629
+ console.error(chalk.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
630
+ process.exit(1);
631
+ }
632
+ const envFile = findEnvFile(projectRoot);
633
+ const env = { ...process.env };
634
+ if (envFile) {
635
+ env.DOTENV_CONFIG_PATH = envFile;
636
+ }
637
+ try {
638
+ const isTs = pluginCli.endsWith(".ts");
639
+ if (isTs) {
640
+ const tsxBin = resolveTsx(projectRoot);
641
+ if (!tsxBin) {
642
+ console.error(chalk.red("✗ Could not find tsx binary."));
643
+ process.exit(1);
644
+ }
645
+ await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
646
+ cwd: backendDir,
647
+ stdio: "inherit",
648
+ env
649
+ });
650
+ } else {
651
+ await execa("node", [pluginCli, ...rawArgs.slice(2)], {
652
+ cwd: backendDir,
653
+ stdio: "inherit",
654
+ env
655
+ });
656
+ }
657
+ } catch (err) {
658
+ process.exit(1);
659
+ }
660
+ }
661
+ function printDbHelp() {
662
+ console.log(`
663
+ ${chalk.bold("rebase db")} — Database management commands
664
+
665
+ ${chalk.green.bold("Usage")}
666
+ rebase db ${chalk.blue("<command>")} [options]
667
+
668
+ ${chalk.green.bold("Commands")}
669
+ ${chalk.gray("(Commands are provided by your active database driver plugin)")}
670
+ ${chalk.blue.bold("push")} Apply schema directly to database (development)
671
+ ${chalk.blue.bold("pull")} Introspect database → Schema
672
+ ${chalk.blue.bold("generate")} Generate migration files
673
+ ${chalk.blue.bold("migrate")} Run pending migrations
674
+ ${chalk.blue.bold("studio")} Open Studio viewer
675
+ ${chalk.blue.bold("branch")} Database branching (create, list, delete, info)
676
+
677
+ ${chalk.green.bold("Examples")}
678
+ ${chalk.gray("# Quick development workflow")}
679
+ rebase schema generate && rebase db push
680
+
681
+ ${chalk.gray("# Production migration workflow")}
682
+ rebase db generate
683
+ rebase db migrate
684
+
685
+ ${chalk.gray("# Create a database branch")}
686
+ rebase db branch create feature_auth
687
+ `);
688
+ }
689
+ const DEV_PORT_FILENAME = ".rebase-dev-port";
690
+ function getProjectPort(projectRoot) {
691
+ let hash = 0;
692
+ for (let i = 0; i < projectRoot.length; i++) {
693
+ hash = (hash << 5) - hash + projectRoot.charCodeAt(i) | 0;
694
+ }
695
+ return 3001 + Math.abs(hash) % 999;
696
+ }
697
+ function resolveStartPort(projectRoot, explicitPort) {
698
+ if (explicitPort) return explicitPort;
699
+ if (process.env.PORT) return parseInt(process.env.PORT, 10);
700
+ try {
701
+ const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
702
+ if (fs.existsSync(portFile)) {
703
+ const saved = parseInt(fs.readFileSync(portFile, "utf-8").trim(), 10);
704
+ if (saved > 0 && saved < 65536) return saved;
705
+ }
706
+ } catch {
707
+ }
708
+ return getProjectPort(projectRoot);
709
+ }
710
+ async function devCommand(rawArgs) {
711
+ const args = arg(
712
+ {
713
+ "--backend-only": Boolean,
714
+ "--frontend-only": Boolean,
715
+ "--port": Number,
716
+ "--help": Boolean,
717
+ "-b": "--backend-only",
718
+ "-f": "--frontend-only",
719
+ "-p": "--port",
720
+ "-h": "--help"
721
+ },
722
+ {
723
+ argv: rawArgs.slice(3),
724
+ // skip "node rebase dev"
725
+ permissive: true
726
+ }
727
+ );
728
+ if (args["--help"]) {
729
+ printDevHelp();
730
+ return;
731
+ }
732
+ const projectRoot = requireProjectRoot();
733
+ const backendDir = findBackendDir(projectRoot);
734
+ const frontendDir = findFrontendDir(projectRoot);
735
+ const backendOnly = args["--backend-only"] || false;
736
+ const frontendOnly = args["--frontend-only"] || false;
737
+ const startPort = resolveStartPort(projectRoot, args["--port"]);
738
+ console.log("");
739
+ console.log(chalk.bold(" 🚀 Rebase Dev Server"));
740
+ console.log("");
741
+ const children = [];
742
+ let frontendUrl = "";
743
+ let backendUrl = "";
744
+ let debounceSummary = null;
745
+ let bannerPrinted = false;
746
+ let resolvedBackendPort = null;
747
+ const stripAnsi = (str) => str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
748
+ function printSummary() {
749
+ if (!frontendUrl || !backendUrl) return;
750
+ if (debounceSummary) clearTimeout(debounceSummary);
751
+ debounceSummary = setTimeout(() => {
752
+ if (bannerPrinted) return;
753
+ console.log("");
754
+ console.log(chalk.cyan("┌────────────────────────────────────────────────────────────┐"));
755
+ console.log(chalk.cyan("│ │"));
756
+ console.log(chalk.cyan("│ ✨ Rebase Admin App is ready! │"));
757
+ const cleanUrl = stripAnsi(frontendUrl);
758
+ const paddedUrl = cleanUrl.padEnd(40);
759
+ console.log(chalk.cyan("│ 👉 Frontend URL: ") + chalk.white(paddedUrl) + chalk.cyan("│"));
760
+ console.log(chalk.cyan("│ │"));
761
+ console.log(chalk.cyan("└────────────────────────────────────────────────────────────┘"));
762
+ console.log("");
763
+ bannerPrinted = true;
764
+ }, 500);
765
+ }
766
+ const cleanup = () => {
767
+ try {
768
+ const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
769
+ if (fs.existsSync(portFile)) fs.unlinkSync(portFile);
770
+ const urlFile = path.join(projectRoot, ".rebase-dev-url");
771
+ if (fs.existsSync(urlFile)) fs.unlinkSync(urlFile);
772
+ } catch {
773
+ }
774
+ children.forEach((child) => {
775
+ if (child.pid && !child.killed) {
776
+ try {
777
+ if (process.platform === "win32") {
778
+ execa.commandSync(`taskkill /pid ${child.pid} /T /F`);
779
+ } else {
780
+ process.kill(-child.pid, "SIGKILL");
781
+ }
782
+ } catch (e) {
783
+ try {
784
+ child.kill("SIGKILL");
785
+ } catch (err) {
786
+ }
787
+ }
788
+ }
789
+ });
790
+ process.exit(0);
791
+ };
792
+ process.on("SIGINT", cleanup);
793
+ process.on("SIGTERM", cleanup);
794
+ function startFrontend(backendPort) {
795
+ if (!frontendDir) return;
796
+ console.log(` ${chalk.magenta("▶")} Frontend: ${chalk.gray(frontendDir)}`);
797
+ const frontendEnv = { ...process.env };
798
+ if (backendPort) {
799
+ frontendEnv.VITE_API_URL = `http://localhost:${backendPort}`;
800
+ console.log(` ${chalk.gray("↳ VITE_API_URL")} = ${chalk.white(`http://localhost:${backendPort}`)}`);
801
+ }
802
+ const frontendChild = execa(
803
+ "pnpm",
804
+ ["run", "dev"],
805
+ {
806
+ cwd: frontendDir,
807
+ stdio: ["inherit", "pipe", "pipe"],
808
+ env: frontendEnv,
809
+ shell: true,
810
+ detached: process.platform !== "win32"
811
+ }
812
+ );
813
+ frontendChild.catch(() => {
814
+ });
815
+ frontendChild.stdout?.on("data", (data) => {
816
+ const lines = data.toString().split("\n").filter(Boolean);
817
+ lines.forEach((line) => {
818
+ console.log(`${chalk.magenta.bold("[admin]")} ${line}`);
819
+ const cleanLine = stripAnsi(line);
820
+ const urlMatch = cleanLine.match(/(http:\/\/(?:localhost|127\.0\.0\.1):\d+)/);
821
+ if (cleanLine.includes("Local:") && urlMatch) {
822
+ frontendUrl = urlMatch[1];
823
+ printSummary();
824
+ }
825
+ });
826
+ });
827
+ frontendChild.stderr?.on("data", (data) => {
828
+ const lines = data.toString().split("\n").filter(Boolean);
829
+ lines.forEach((line) => {
830
+ console.log(`${chalk.magenta.bold("[admin]")} ${line}`);
831
+ });
832
+ });
833
+ children.push(frontendChild);
834
+ }
835
+ if (!frontendOnly && backendDir) {
836
+ const tsxBin = resolveTsx(projectRoot);
837
+ if (!tsxBin) {
838
+ console.error(chalk.red(" ✗ Could not find tsx binary for backend."));
839
+ console.error(chalk.gray(" Install it with: pnpm add -D tsx"));
840
+ process.exit(1);
841
+ }
842
+ const envFile = findEnvFile(projectRoot);
843
+ const env = { ...process.env };
844
+ if (envFile) {
845
+ env.DOTENV_CONFIG_PATH = envFile;
846
+ }
847
+ env.PORT = String(startPort);
848
+ console.log(` ${chalk.cyan("▶")} Backend: ${chalk.gray(backendDir)}`);
849
+ console.log(` ${chalk.gray("↳ PORT")} = ${chalk.white(String(startPort))}`);
850
+ let frontendLaunched = false;
851
+ const backendChild = execa(
852
+ tsxBin,
853
+ ["watch", `--watch="${path.join("..", "config", "**", "*")}"`, "--conditions", "development", "src/index.ts"],
854
+ {
855
+ cwd: backendDir,
856
+ stdio: ["inherit", "pipe", "pipe"],
857
+ env,
858
+ shell: true,
859
+ detached: process.platform !== "win32"
860
+ }
861
+ );
862
+ backendChild.catch(() => {
863
+ });
864
+ backendChild.stdout?.on("data", (data) => {
865
+ const lines = data.toString().split("\n").filter(Boolean);
866
+ lines.forEach((line) => {
867
+ console.log(`${chalk.cyan.bold("[backend]")} ${line}`);
868
+ const cleanLine = stripAnsi(line);
869
+ const serverMatch = cleanLine.match(/Server running at http:\/\/(?:localhost|127\.0\.0\.1):(\d+)/);
870
+ if (serverMatch) {
871
+ resolvedBackendPort = parseInt(serverMatch[1], 10);
872
+ backendUrl = "started";
873
+ printSummary();
874
+ const urlFile = path.join(projectRoot, ".rebase-dev-url");
875
+ fs.writeFileSync(urlFile, `http://localhost:${resolvedBackendPort}`, "utf-8");
876
+ const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
877
+ fs.writeFileSync(portFile, String(resolvedBackendPort), "utf-8");
878
+ if (!backendOnly && frontendDir && !frontendLaunched) {
879
+ frontendLaunched = true;
880
+ startFrontend(resolvedBackendPort);
881
+ }
882
+ }
883
+ });
884
+ });
885
+ backendChild.stderr?.on("data", (data) => {
886
+ const lines = data.toString().split("\n").filter(Boolean);
887
+ lines.forEach((line) => {
888
+ console.log(`${chalk.cyan.bold("[backend]")} ${line}`);
889
+ });
890
+ });
891
+ children.push(backendChild);
892
+ } else if (!frontendOnly && !backendDir) {
893
+ console.warn(chalk.yellow(" ⚠ No backend/ directory found, skipping backend."));
894
+ }
895
+ if (!backendOnly && frontendDir && (frontendOnly || !backendDir)) {
896
+ startFrontend(null);
897
+ } else if (!backendOnly && !frontendDir) {
898
+ console.warn(chalk.yellow(" ⚠ No frontend/ directory found, skipping frontend."));
899
+ }
900
+ if (children.length === 0) {
901
+ console.error(chalk.red(" ✗ Nothing to start. Check your project structure."));
902
+ process.exit(1);
903
+ }
904
+ console.log("");
905
+ console.log(chalk.gray(" Press Ctrl+C to stop all servers."));
906
+ console.log("");
907
+ await Promise.all(
908
+ children.map(
909
+ (child) => new Promise((resolve) => {
910
+ child.finally(() => resolve());
911
+ })
912
+ )
913
+ );
914
+ }
915
+ function printDevHelp() {
916
+ console.log(`
917
+ ${chalk.bold("rebase dev")} — Start the development server
918
+
919
+ ${chalk.green.bold("Usage")}
920
+ rebase dev [options]
921
+
922
+ ${chalk.green.bold("Options")}
923
+ ${chalk.blue("--backend-only, -b")} Only start the backend server
924
+ ${chalk.blue("--frontend-only, -f")} Only start the frontend server
925
+ ${chalk.blue("--port, -p")} Backend port (default: auto-detected per project)
926
+
927
+ ${chalk.green.bold("Description")}
928
+ Starts both the backend (tsx watch + Hono) and frontend (Vite)
929
+ dev servers concurrently with color-coded output prefixes.
930
+
931
+ Each project automatically receives a unique default port derived
932
+ from its directory path, preventing collisions when running multiple
933
+ Rebase instances simultaneously.
934
+
935
+ If the assigned port is already in use, the server will automatically
936
+ try the next available port. The frontend is started only after the
937
+ backend is ready, and VITE_API_URL is injected automatically.
938
+ `);
939
+ }
940
+ async function authCommand(subcommand, rawArgs) {
941
+ if (!subcommand || subcommand === "--help") {
942
+ printAuthHelp();
943
+ return;
944
+ }
945
+ switch (subcommand) {
946
+ case "reset-password":
947
+ await resetPassword(rawArgs);
948
+ break;
949
+ default:
950
+ console.error(chalk.red(`Unknown auth command: ${subcommand}`));
951
+ console.log("");
952
+ printAuthHelp();
953
+ process.exit(1);
954
+ }
955
+ }
956
+ async function resetPassword(rawArgs) {
957
+ const args = arg(
958
+ {
959
+ "--email": String,
960
+ "--password": String,
961
+ "-e": "--email",
962
+ "-p": "--password"
963
+ },
964
+ {
965
+ argv: rawArgs.slice(4),
966
+ // skip "node rebase auth reset-password"
967
+ permissive: true
968
+ }
969
+ );
970
+ const email = args["--email"] || args._[0];
971
+ const newPassword = args["--password"] || args._[1];
972
+ if (!email) {
973
+ console.error(chalk.red("✗ Email is required."));
974
+ console.log("");
975
+ console.log(chalk.gray(" Usage: rebase auth reset-password <email> [new-password]"));
976
+ console.log(chalk.gray(" rebase auth reset-password --email user@example.com --password NewPass123!"));
977
+ process.exit(1);
978
+ }
979
+ const projectRoot = requireProjectRoot();
980
+ const backendDir = requireBackendDir(projectRoot);
981
+ const tsxBin = resolveTsx(projectRoot);
982
+ if (!tsxBin) {
983
+ console.error(chalk.red("✗ Could not find tsx binary."));
984
+ process.exit(1);
985
+ }
986
+ const envFile = findEnvFile(projectRoot);
987
+ const env = { ...process.env };
988
+ if (envFile) {
989
+ env.DOTENV_CONFIG_PATH = envFile;
990
+ }
991
+ const scriptContent = `
992
+ import { createPostgresDatabaseConnection } from "@rebasepro/server-core";
993
+ import { hashPassword } from "@rebasepro/server-core/src/auth/password";
994
+ import { eq } from "drizzle-orm";
995
+ import { users } from "@rebasepro/server-core/src/db/auth-schema";
996
+ import * as dotenv from "dotenv";
997
+ import path from "path";
998
+
999
+ dotenv.config({ path: "${envFile || path.join(projectRoot, ".env")}" });
1000
+
1001
+ const email = "${email}";
1002
+ const newPassword = "${newPassword || "NewPassword123!"}";
1003
+
1004
+ async function resetPassword() {
1005
+ const { db } = createPostgresDatabaseConnection(process.env.DATABASE_URL!);
1006
+ const hash = await hashPassword(newPassword);
1007
+
1008
+ const result = await db.update(users)
1009
+ .set({ passwordHash: hash })
1010
+ .where(eq(users.email, email))
1011
+ .returning({
1012
+ id: users.id,
1013
+ email: users.email
1014
+ });
1015
+
1016
+ if (result.length > 0) {
1017
+ console.log("✅ Password reset for: " + result[0].email);
1018
+ ${!newPassword ? 'console.log(" New password: " + newPassword);' : ""}
1019
+ } else {
1020
+ console.log("✗ User not found: " + email);
1021
+ }
1022
+ process.exit(0);
1023
+ }
1024
+
1025
+ resetPassword().catch(console.error);
1026
+ `;
1027
+ const tmpScriptPath = path.join(backendDir, ".tmp-reset-password.ts");
1028
+ fs.writeFileSync(tmpScriptPath, scriptContent, "utf-8");
1029
+ console.log("");
1030
+ console.log(chalk.bold(" 🔑 Rebase Auth — Reset Password"));
1031
+ console.log("");
1032
+ console.log(` ${chalk.gray("Email:")} ${email}`);
1033
+ if (newPassword) {
1034
+ console.log(` ${chalk.gray("Password:")} ${"*".repeat(newPassword.length)}`);
1035
+ }
1036
+ console.log("");
1037
+ const child = child_process.spawn(tsxBin, [tmpScriptPath], {
1038
+ cwd: backendDir,
1039
+ stdio: "inherit",
1040
+ env
1041
+ });
1042
+ return new Promise((resolve) => {
1043
+ child.on("close", (code) => {
1044
+ try {
1045
+ fs.unlinkSync(tmpScriptPath);
1046
+ } catch {
1047
+ }
1048
+ if (code !== 0) {
1049
+ process.exit(code ?? 1);
1050
+ }
1051
+ resolve();
1052
+ });
1053
+ });
1054
+ }
1055
+ function printAuthHelp() {
1056
+ console.log(`
1057
+ ${chalk.bold("rebase auth")} — Authentication management commands
1058
+
1059
+ ${chalk.green.bold("Usage")}
1060
+ rebase auth ${chalk.blue("<command>")} [options]
1061
+
1062
+ ${chalk.green.bold("Commands")}
1063
+ ${chalk.blue.bold("reset-password")} Reset a user's password
1064
+
1065
+ ${chalk.green.bold("reset-password Options")}
1066
+ ${chalk.blue("--email, -e")} User's email address
1067
+ ${chalk.blue("--password, -p")} New password (default: NewPassword123!)
1068
+
1069
+ ${chalk.green.bold("Examples")}
1070
+ rebase auth reset-password user@example.com
1071
+ rebase auth reset-password --email user@example.com --password MyNewPass!
1072
+ `);
1073
+ }
1074
+ async function doctorCommand(rawArgs) {
1075
+ const projectRoot = requireProjectRoot();
1076
+ const backendDir = requireBackendDir(projectRoot);
1077
+ const activePlugin = getActiveBackendPlugin(backendDir);
1078
+ if (!activePlugin) {
1079
+ console.error(chalk.red("✗ Could not detect an active database plugin."));
1080
+ console.error(chalk.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
1081
+ process.exit(1);
1082
+ }
1083
+ const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
1084
+ if (!pluginCli) {
1085
+ console.error(chalk.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
1086
+ process.exit(1);
1087
+ }
1088
+ const envFile = findEnvFile(projectRoot);
1089
+ const env = { ...process.env };
1090
+ if (envFile) {
1091
+ env.DOTENV_CONFIG_PATH = envFile;
1092
+ }
1093
+ try {
1094
+ const isTs = pluginCli.endsWith(".ts");
1095
+ if (isTs) {
1096
+ const tsxBin = resolveTsx(projectRoot);
1097
+ if (!tsxBin) {
1098
+ console.error(chalk.red("✗ Could not find tsx binary."));
1099
+ process.exit(1);
1100
+ }
1101
+ await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
1102
+ cwd: backendDir,
1103
+ stdio: "inherit",
1104
+ env
1105
+ });
1106
+ } else {
1107
+ await execa("node", [pluginCli, ...rawArgs.slice(2)], {
1108
+ cwd: backendDir,
1109
+ stdio: "inherit",
1110
+ env
1111
+ });
1112
+ }
1113
+ } catch {
1114
+ process.exit(1);
1115
+ }
1116
+ }
166
1117
  const __filename$1 = url.fileURLToPath(typeof document === "undefined" && typeof location === "undefined" ? require("url").pathToFileURL(__filename).href : typeof document === "undefined" ? location.href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href);
167
1118
  const __dirname$1 = path.dirname(__filename$1);
168
1119
  function getVersion() {
@@ -193,16 +1144,56 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
193
1144
  return;
194
1145
  }
195
1146
  const command = parsedArgs._[0];
196
- if (!command || parsedArgs["--help"]) {
1147
+ const subcommand = parsedArgs._[1];
1148
+ const namespacedCommands = ["schema", "db", "dev", "auth", "doctor"];
1149
+ if (!command || parsedArgs["--help"] && !namespacedCommands.includes(command)) {
197
1150
  printHelp();
198
1151
  return;
199
1152
  }
200
- if (command === "init") {
201
- await createRebaseApp(args);
202
- } else {
203
- console.log(chalk.red(`Unknown command: ${command}`));
204
- console.log("");
205
- printHelp();
1153
+ const effectiveSubcommand = parsedArgs["--help"] ? "--help" : subcommand;
1154
+ switch (command) {
1155
+ case "init":
1156
+ await createRebaseApp(args);
1157
+ break;
1158
+ case "generate-sdk": {
1159
+ const sdkArgs = arg(
1160
+ {
1161
+ "--collections-dir": String,
1162
+ "--output": String,
1163
+ "-c": "--collections-dir",
1164
+ "-o": "--output"
1165
+ },
1166
+ {
1167
+ argv: args.slice(3),
1168
+ permissive: true
1169
+ }
1170
+ );
1171
+ await generateSdkCommand({
1172
+ collectionsDir: sdkArgs["--collections-dir"] || "./config/collections",
1173
+ output: sdkArgs["--output"] || "./generated/sdk",
1174
+ cwd: process.cwd()
1175
+ });
1176
+ break;
1177
+ }
1178
+ case "schema":
1179
+ await schemaCommand(effectiveSubcommand, args);
1180
+ break;
1181
+ case "db":
1182
+ await dbCommand(effectiveSubcommand, args);
1183
+ break;
1184
+ case "dev":
1185
+ await devCommand(args);
1186
+ break;
1187
+ case "auth":
1188
+ await authCommand(effectiveSubcommand, args);
1189
+ break;
1190
+ case "doctor":
1191
+ await doctorCommand(args);
1192
+ break;
1193
+ default:
1194
+ console.log(chalk.red(`Unknown command: ${command}`));
1195
+ console.log("");
1196
+ printHelp();
206
1197
  }
207
1198
  }
208
1199
  function printHelp() {
@@ -213,7 +1204,30 @@ ${chalk.green.bold("Usage")}
213
1204
  rebase ${chalk.blue("<command>")} [options]
214
1205
 
215
1206
  ${chalk.green.bold("Commands")}
216
- ${chalk.blue.bold("init")} Create a new Rebase project
1207
+ ${chalk.blue.bold("init")} Create a new Rebase project
1208
+ ${chalk.blue.bold("dev")} Start the development server
1209
+
1210
+ ${chalk.green.bold("Schema")}
1211
+ ${chalk.blue.bold("schema generate")} Generate Drizzle schema from collections
1212
+ ${chalk.blue.bold("schema")} ${chalk.gray("--help")} Show schema command help
1213
+
1214
+ ${chalk.green.bold("Database")}
1215
+ ${chalk.blue.bold("db push")} Apply schema directly to database ${chalk.gray("(dev)")}
1216
+ ${chalk.blue.bold("db pull")} Introspect database → Drizzle schema
1217
+ ${chalk.blue.bold("db generate")} Generate SQL migration files
1218
+ ${chalk.blue.bold("db migrate")} Run pending migrations
1219
+ ${chalk.blue.bold("db studio")} Open Drizzle Studio
1220
+ ${chalk.blue.bold("db")} ${chalk.gray("--help")} Show database command help
1221
+
1222
+ ${chalk.green.bold("SDK")}
1223
+ ${chalk.blue.bold("generate-sdk")} Generate a typed JS SDK from collections
1224
+
1225
+ ${chalk.green.bold("Auth")}
1226
+ ${chalk.blue.bold("auth reset-password")} Reset a user's password
1227
+ ${chalk.blue.bold("auth")} ${chalk.gray("--help")} Show auth command help
1228
+
1229
+ ${chalk.green.bold("Diagnostics")}
1230
+ ${chalk.blue.bold("doctor")} Detect schema drift between collections, schema, and DB
217
1231
 
218
1232
  ${chalk.green.bold("Options")}
219
1233
  ${chalk.blue("--version, -v")} Show version number
@@ -265,13 +1279,27 @@ ${chalk.gray("Documentation: https://rebase.pro/docs")}
265
1279
  console.log("You are not logged in.");
266
1280
  }
267
1281
  }
1282
+ exports2.authCommand = authCommand;
268
1283
  exports2.createRebaseApp = createRebaseApp;
1284
+ exports2.dbCommand = dbCommand;
1285
+ exports2.devCommand = devCommand;
269
1286
  exports2.entry = entry;
1287
+ exports2.findBackendDir = findBackendDir;
1288
+ exports2.findEnvFile = findEnvFile;
1289
+ exports2.findFrontendDir = findFrontendDir;
1290
+ exports2.findProjectRoot = findProjectRoot;
1291
+ exports2.getActiveBackendPlugin = getActiveBackendPlugin;
270
1292
  exports2.getTokens = getTokens;
271
1293
  exports2.login = login;
272
1294
  exports2.logout = logout;
273
1295
  exports2.parseJwt = parseJwt;
274
1296
  exports2.refreshCredentials = refreshCredentials;
1297
+ exports2.requireBackendDir = requireBackendDir;
1298
+ exports2.requireProjectRoot = requireProjectRoot;
1299
+ exports2.resolveLocalBin = resolveLocalBin;
1300
+ exports2.resolvePluginCliScript = resolvePluginCliScript;
1301
+ exports2.resolveTsx = resolveTsx;
1302
+ exports2.schemaCommand = schemaCommand;
275
1303
  Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
276
1304
  }));
277
1305
  //# sourceMappingURL=index.cjs.map