@rebasepro/cli 0.0.1-canary.1 → 0.0.1-canary.4d4fb3e

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
@@ -7,6 +7,9 @@ import { promisify } from "util";
7
7
  import execa from "execa";
8
8
  import ncp from "ncp";
9
9
  import { fileURLToPath } from "url";
10
+ import crypto from "crypto";
11
+ import { generateSDK } from "@rebasepro/sdk-generator";
12
+ import { execSync, spawn } from "child_process";
10
13
  import * as os from "os";
11
14
  const access = promisify(fs.access);
12
15
  const copy = promisify(ncp);
@@ -34,7 +37,9 @@ async function promptForOptions(rawArgs) {
34
37
  const args = arg(
35
38
  {
36
39
  "--git": Boolean,
37
- "-g": "--git"
40
+ "--install": Boolean,
41
+ "-g": "--git",
42
+ "-i": "--install"
38
43
  },
39
44
  {
40
45
  argv: rawArgs.slice(3),
@@ -67,6 +72,14 @@ async function promptForOptions(rawArgs) {
67
72
  default: true
68
73
  });
69
74
  }
75
+ if (!args["--install"]) {
76
+ questions.push({
77
+ type: "confirm",
78
+ name: "installDeps",
79
+ message: "Install dependencies with pnpm?",
80
+ default: true
81
+ });
82
+ }
70
83
  const answers = await inquirer.prompt(questions);
71
84
  const projectName = nameArg || answers.projectName;
72
85
  const targetDirectory = path.resolve(process.cwd(), projectName);
@@ -74,6 +87,7 @@ async function promptForOptions(rawArgs) {
74
87
  return {
75
88
  projectName,
76
89
  git: args["--git"] || answers.git || false,
90
+ installDeps: args["--install"] || answers.installDeps || false,
77
91
  targetDirectory,
78
92
  templateDirectory
79
93
  };
@@ -94,19 +108,40 @@ async function createProject(options) {
94
108
  process.exit(1);
95
109
  }
96
110
  console.log(chalk.gray(" Copying project files..."));
97
- await copy(options.templateDirectory, options.targetDirectory, {
98
- clobber: false,
99
- dot: true,
100
- filter: (source) => {
101
- const basename = path.basename(source);
102
- return basename !== "node_modules" && basename !== ".DS_Store";
103
- }
104
- });
111
+ try {
112
+ await copy(options.templateDirectory, options.targetDirectory, {
113
+ clobber: false,
114
+ dot: true,
115
+ filter: (source) => {
116
+ const basename = path.basename(source);
117
+ return basename !== "node_modules" && basename !== ".DS_Store";
118
+ }
119
+ });
120
+ } catch (err) {
121
+ console.error(`${chalk.red.bold("ERROR")} Failed to copy template files: ${err instanceof Error ? err.message : String(err)}`);
122
+ process.exit(1);
123
+ }
105
124
  await replacePlaceholders(options);
106
125
  const envTemplatePath = path.join(options.targetDirectory, ".env.template");
107
126
  const envPath = path.join(options.targetDirectory, ".env");
108
127
  if (fs.existsSync(envTemplatePath) && !fs.existsSync(envPath)) {
109
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");
110
145
  }
111
146
  if (options.git) {
112
147
  console.log(chalk.gray(" Initializing git repository..."));
@@ -116,13 +151,28 @@ async function createProject(options) {
116
151
  console.warn(chalk.yellow(" Warning: Failed to initialize git repository"));
117
152
  }
118
153
  }
154
+ if (options.installDeps) {
155
+ console.log("");
156
+ console.log(chalk.gray(" Installing dependencies with pnpm..."));
157
+ console.log("");
158
+ try {
159
+ await execa("pnpm", ["install"], {
160
+ cwd: options.targetDirectory,
161
+ stdio: "inherit"
162
+ });
163
+ } catch {
164
+ console.warn(chalk.yellow(" Warning: Failed to install dependencies. You may need to run `pnpm install` manually."));
165
+ }
166
+ }
119
167
  console.log("");
120
168
  console.log(`${chalk.green.bold("✓")} Project ${chalk.bold(options.projectName)} created successfully!`);
121
169
  console.log("");
122
170
  console.log(chalk.bold("Next steps:"));
123
171
  console.log("");
124
172
  console.log(` ${chalk.cyan("cd")} ${options.projectName}`);
125
- console.log(` ${chalk.cyan("pnpm install")}`);
173
+ if (!options.installDeps) {
174
+ console.log(` ${chalk.cyan("pnpm install")}`);
175
+ }
126
176
  console.log("");
127
177
  console.log(chalk.gray(" # Set up your database connection in .env"));
128
178
  console.log(chalk.gray(" # Then run:"));
@@ -132,7 +182,7 @@ async function createProject(options) {
132
182
  console.log(chalk.gray("This starts both the backend (Express + PostgreSQL)") + chalk.gray(" and the frontend (Vite + React) concurrently."));
133
183
  console.log("");
134
184
  console.log(chalk.gray("Docs: https://rebase.pro/docs"));
135
- console.log(chalk.gray("GitHub: https://github.com/rebaseco/rebase"));
185
+ console.log(chalk.gray("GitHub: https://github.com/rebasepro/rebase"));
136
186
  console.log("");
137
187
  }
138
188
  async function replacePlaceholders(options) {
@@ -151,6 +201,696 @@ async function replacePlaceholders(options) {
151
201
  fs.writeFileSync(fullPath, content, "utf-8");
152
202
  }
153
203
  }
204
+ async function loadCollections(collectionsDir) {
205
+ const absDir = path.resolve(collectionsDir);
206
+ if (!fs.existsSync(absDir)) {
207
+ throw new Error(`Collections directory not found: ${absDir}`);
208
+ }
209
+ let jiti;
210
+ try {
211
+ const jitiModule = await import("jiti");
212
+ jiti = jitiModule.default || jitiModule;
213
+ } catch {
214
+ throw new Error(
215
+ "Could not load 'jiti'. Install it with: pnpm add -D jiti\njiti is required to dynamically import TypeScript collection definitions."
216
+ );
217
+ }
218
+ const jitiInstance = jiti(absDir, {
219
+ interopDefault: true,
220
+ esmResolve: true
221
+ });
222
+ const indexCandidates = ["index.ts", "index.js", "index.mjs"];
223
+ let indexPath = null;
224
+ for (const candidate of indexCandidates) {
225
+ const p = path.join(absDir, candidate);
226
+ if (fs.existsSync(p)) {
227
+ indexPath = p;
228
+ break;
229
+ }
230
+ }
231
+ if (!indexPath) {
232
+ console.log(chalk.yellow(" No index file found, scanning individual collection files..."));
233
+ const collections = [];
234
+ const files = fs.readdirSync(absDir).filter(
235
+ (f) => (f.endsWith(".ts") || f.endsWith(".js")) && !f.startsWith(".")
236
+ );
237
+ for (const file of files) {
238
+ try {
239
+ const mod2 = jitiInstance(path.join(absDir, file));
240
+ const exported2 = mod2.default || mod2;
241
+ if (exported2 && typeof exported2 === "object" && "slug" in exported2) {
242
+ collections.push(exported2);
243
+ } else if (Array.isArray(exported2)) {
244
+ collections.push(...exported2);
245
+ }
246
+ } catch (err) {
247
+ console.warn(chalk.yellow(` ⚠ Skipping ${file}: ${err.message}`));
248
+ }
249
+ }
250
+ return collections;
251
+ }
252
+ const mod = jitiInstance(indexPath);
253
+ const exported = mod.default || mod;
254
+ if (Array.isArray(exported)) {
255
+ return exported;
256
+ } else if (typeof exported === "object" && exported !== null) {
257
+ if ("collections" in exported && Array.isArray(exported.collections)) {
258
+ return exported.collections;
259
+ }
260
+ const collections = [];
261
+ for (const value of Object.values(exported)) {
262
+ if (value && typeof value === "object" && "slug" in value) {
263
+ collections.push(value);
264
+ }
265
+ }
266
+ if (collections.length > 0) return collections;
267
+ }
268
+ throw new Error(
269
+ `Could not extract collections from ${indexPath}.
270
+ Expected a default export of EntityCollection[] or an object with named collection exports.`
271
+ );
272
+ }
273
+ function writeFiles(outputDir, files) {
274
+ const absOutput = path.resolve(outputDir);
275
+ fs.mkdirSync(absOutput, { recursive: true });
276
+ for (const file of files) {
277
+ const filePath = path.join(absOutput, file.path);
278
+ const dir = path.dirname(filePath);
279
+ if (!fs.existsSync(dir)) {
280
+ fs.mkdirSync(dir, { recursive: true });
281
+ }
282
+ fs.writeFileSync(filePath, file.content, "utf-8");
283
+ }
284
+ }
285
+ async function generateSdkCommand(args) {
286
+ const { collectionsDir, output, cwd } = args;
287
+ const resolvedCollectionsDir = path.isAbsolute(collectionsDir) ? collectionsDir : path.join(cwd, collectionsDir);
288
+ const resolvedOutput = path.isAbsolute(output) ? output : path.join(cwd, output);
289
+ console.log("");
290
+ console.log(chalk.bold(" 🔧 Rebase SDK Generator"));
291
+ console.log("");
292
+ console.log(` ${chalk.gray("Collections:")} ${resolvedCollectionsDir}`);
293
+ console.log(` ${chalk.gray("Output:")} ${resolvedOutput}`);
294
+ console.log("");
295
+ console.log(chalk.cyan(" → Loading collection definitions..."));
296
+ const collections = await loadCollections(resolvedCollectionsDir);
297
+ if (collections.length === 0) {
298
+ console.log(chalk.red(" ✗ No collections found. Nothing to generate."));
299
+ process.exit(1);
300
+ }
301
+ console.log(chalk.green(` ✓ Found ${collections.length} collection(s): ${collections.map((c) => c.slug).join(", ")}`));
302
+ console.log("");
303
+ console.log(chalk.cyan(" → Generating SDK files..."));
304
+ const files = generateSDK(collections);
305
+ console.log(chalk.green(` ✓ Generated ${files.length} file(s)`));
306
+ console.log(chalk.cyan(` → Writing to ${resolvedOutput}...`));
307
+ writeFiles(resolvedOutput, files);
308
+ console.log("");
309
+ console.log(chalk.green.bold(" ✓ SDK generated successfully!"));
310
+ console.log("");
311
+ console.log(chalk.gray(" Usage:"));
312
+ console.log(chalk.gray(` import { createRebaseClient } from '@rebasepro/client';`));
313
+ console.log(chalk.gray(` import type { Database } from './${path.relative(cwd, path.join(resolvedOutput, "database.types"))}';`));
314
+ console.log("");
315
+ console.log(chalk.gray(" const rebase = createRebaseClient<Database>({"));
316
+ console.log(chalk.gray(" baseUrl: 'http://localhost:3001',"));
317
+ console.log(chalk.gray(" // token: 'your-jwt-token',"));
318
+ console.log(chalk.gray(" });"));
319
+ console.log("");
320
+ console.log(chalk.gray(` const { data } = await rebase.collection('${collections[0]?.slug || "my_collection"}').find();`));
321
+ console.log("");
322
+ }
323
+ function findProjectRoot(startDir = process.cwd()) {
324
+ let dir = path.resolve(startDir);
325
+ const root = path.parse(dir).root;
326
+ while (dir !== root) {
327
+ const pkgPath = path.join(dir, "package.json");
328
+ if (fs.existsSync(pkgPath)) {
329
+ try {
330
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
331
+ if (pkg.workspaces && Array.isArray(pkg.workspaces)) {
332
+ const hasBackend = pkg.workspaces.some(
333
+ (w) => w === "backend" || w.includes("backend")
334
+ );
335
+ if (hasBackend) return dir;
336
+ }
337
+ } catch {
338
+ }
339
+ if (fs.existsSync(path.join(dir, "backend")) && fs.existsSync(path.join(dir, "shared"))) {
340
+ return dir;
341
+ }
342
+ }
343
+ dir = path.dirname(dir);
344
+ }
345
+ return null;
346
+ }
347
+ function findBackendDir(projectRoot) {
348
+ const backendDir = path.join(projectRoot, "backend");
349
+ return fs.existsSync(backendDir) ? backendDir : null;
350
+ }
351
+ function getActiveBackendPlugin(backendDir) {
352
+ const pkgPath = path.join(backendDir, "package.json");
353
+ if (!fs.existsSync(pkgPath)) return null;
354
+ try {
355
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
356
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
357
+ for (const dep of Object.keys(deps)) {
358
+ if (dep.startsWith("@rebasepro/server-") && dep !== "@rebasepro/server-core") {
359
+ return dep;
360
+ }
361
+ }
362
+ } catch {
363
+ }
364
+ return null;
365
+ }
366
+ function resolvePluginCliScript(backendDir, pluginName) {
367
+ const candidates = [
368
+ path.join(backendDir, "node_modules", pluginName, "src", "cli.ts"),
369
+ path.join(backendDir, "node_modules", pluginName, "dist", "cli.js"),
370
+ // For monorepo dev mode:
371
+ path.resolve(backendDir, "..", "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"),
372
+ path.resolve(backendDir, "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts")
373
+ ];
374
+ for (const candidate of candidates) {
375
+ if (fs.existsSync(candidate)) return candidate;
376
+ }
377
+ return null;
378
+ }
379
+ function findFrontendDir(projectRoot) {
380
+ const frontendDir = path.join(projectRoot, "frontend");
381
+ return fs.existsSync(frontendDir) ? frontendDir : null;
382
+ }
383
+ function findEnvFile(projectRoot) {
384
+ const candidates = [
385
+ path.join(projectRoot, ".env"),
386
+ path.join(projectRoot, "backend", ".env")
387
+ ];
388
+ for (const candidate of candidates) {
389
+ if (fs.existsSync(candidate)) return candidate;
390
+ }
391
+ return null;
392
+ }
393
+ function resolveLocalBin(projectRoot, binName) {
394
+ const candidates = [
395
+ path.join(projectRoot, "backend", "node_modules", ".bin", binName),
396
+ path.join(projectRoot, "node_modules", ".bin", binName)
397
+ ];
398
+ let parent = path.dirname(projectRoot);
399
+ const rootDir = path.parse(parent).root;
400
+ while (parent !== rootDir) {
401
+ candidates.push(path.join(parent, "node_modules", ".bin", binName));
402
+ parent = path.dirname(parent);
403
+ }
404
+ for (const candidate of candidates) {
405
+ if (fs.existsSync(candidate)) return candidate;
406
+ }
407
+ try {
408
+ const globalPath = execSync(`which ${binName}`, { encoding: "utf-8" }).trim();
409
+ if (globalPath && fs.existsSync(globalPath)) return globalPath;
410
+ } catch {
411
+ }
412
+ return null;
413
+ }
414
+ function resolveTsx(projectRoot) {
415
+ return resolveLocalBin(projectRoot, "tsx");
416
+ }
417
+ function requireProjectRoot() {
418
+ const root = findProjectRoot();
419
+ if (!root) {
420
+ console.error(chalk.red("✗ Could not find a Rebase project root."));
421
+ console.error(chalk.gray(" Make sure you are inside a Rebase project directory"));
422
+ console.error(chalk.gray(" (one with backend/, frontend/, and shared/ directories)."));
423
+ process.exit(1);
424
+ }
425
+ return root;
426
+ }
427
+ function requireBackendDir(projectRoot) {
428
+ const backendDir = findBackendDir(projectRoot);
429
+ if (!backendDir) {
430
+ console.error(chalk.red("✗ Could not find a backend/ directory."));
431
+ console.error(chalk.gray(` Expected at: ${path.join(projectRoot, "backend")}`));
432
+ process.exit(1);
433
+ }
434
+ return backendDir;
435
+ }
436
+ async function schemaCommand(subcommand, rawArgs) {
437
+ if (!subcommand || subcommand === "--help") {
438
+ printSchemaHelp();
439
+ return;
440
+ }
441
+ const projectRoot = requireProjectRoot();
442
+ const backendDir = requireBackendDir(projectRoot);
443
+ const activePlugin = getActiveBackendPlugin(backendDir);
444
+ if (!activePlugin) {
445
+ console.error(chalk.red("✗ Could not detect an active database plugin."));
446
+ console.error(chalk.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
447
+ process.exit(1);
448
+ }
449
+ const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
450
+ if (!pluginCli) {
451
+ console.error(chalk.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
452
+ process.exit(1);
453
+ }
454
+ const envFile = findEnvFile(projectRoot);
455
+ const env = { ...process.env };
456
+ if (envFile) {
457
+ env.DOTENV_CONFIG_PATH = envFile;
458
+ }
459
+ try {
460
+ const isTs = pluginCli.endsWith(".ts");
461
+ if (isTs) {
462
+ const tsxBin = resolveTsx(projectRoot);
463
+ if (!tsxBin) {
464
+ console.error(chalk.red("✗ Could not find tsx binary."));
465
+ process.exit(1);
466
+ }
467
+ await execa(tsxBin, [pluginCli, "schema", subcommand, ...rawArgs.slice(2)], {
468
+ cwd: backendDir,
469
+ stdio: "inherit",
470
+ env
471
+ });
472
+ } else {
473
+ await execa("node", [pluginCli, "schema", subcommand, ...rawArgs.slice(2)], {
474
+ cwd: backendDir,
475
+ stdio: "inherit",
476
+ env
477
+ });
478
+ }
479
+ } catch (err) {
480
+ process.exit(1);
481
+ }
482
+ }
483
+ function printSchemaHelp() {
484
+ console.log(`
485
+ ${chalk.bold("rebase schema")} — Schema management commands
486
+
487
+ ${chalk.green.bold("Usage")}
488
+ rebase schema ${chalk.blue("<command>")} [options]
489
+
490
+ ${chalk.green.bold("Commands")}
491
+ ${chalk.gray("(Commands are provided by your active database driver plugin)")}
492
+ ${chalk.blue.bold("generate")} Generate Schema from collection definitions
493
+
494
+ ${chalk.green.bold("generate Options")}
495
+ ${chalk.blue("--collections, -c")} Path to collections directory
496
+ ${chalk.blue("--output, -o")} Output path for generated schema
497
+ ${chalk.blue("--watch, -w")} Watch for changes and regenerate automatically
498
+ `);
499
+ }
500
+ async function dbCommand(subcommand, rawArgs) {
501
+ if (!subcommand || subcommand === "--help") {
502
+ printDbHelp();
503
+ return;
504
+ }
505
+ const projectRoot = requireProjectRoot();
506
+ const backendDir = requireBackendDir(projectRoot);
507
+ const activePlugin = getActiveBackendPlugin(backendDir);
508
+ if (!activePlugin) {
509
+ console.error(chalk.red("✗ Could not detect an active database plugin."));
510
+ console.error(chalk.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
511
+ process.exit(1);
512
+ }
513
+ const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
514
+ if (!pluginCli) {
515
+ console.error(chalk.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
516
+ process.exit(1);
517
+ }
518
+ const envFile = findEnvFile(projectRoot);
519
+ const env = { ...process.env };
520
+ if (envFile) {
521
+ env.DOTENV_CONFIG_PATH = envFile;
522
+ }
523
+ try {
524
+ const isTs = pluginCli.endsWith(".ts");
525
+ if (isTs) {
526
+ const tsxBin = resolveTsx(projectRoot);
527
+ if (!tsxBin) {
528
+ console.error(chalk.red("✗ Could not find tsx binary."));
529
+ process.exit(1);
530
+ }
531
+ await execa(tsxBin, [pluginCli, "db", subcommand, ...rawArgs.slice(2)], {
532
+ cwd: backendDir,
533
+ stdio: "inherit",
534
+ env
535
+ });
536
+ } else {
537
+ await execa("node", [pluginCli, "db", subcommand, ...rawArgs.slice(2)], {
538
+ cwd: backendDir,
539
+ stdio: "inherit",
540
+ env
541
+ });
542
+ }
543
+ } catch (err) {
544
+ process.exit(1);
545
+ }
546
+ }
547
+ function printDbHelp() {
548
+ console.log(`
549
+ ${chalk.bold("rebase db")} — Database management commands
550
+
551
+ ${chalk.green.bold("Usage")}
552
+ rebase db ${chalk.blue("<command>")} [options]
553
+
554
+ ${chalk.green.bold("Commands")}
555
+ ${chalk.gray("(Commands are provided by your active database driver plugin)")}
556
+ ${chalk.blue.bold("push")} Apply schema directly to database (development)
557
+ ${chalk.blue.bold("pull")} Introspect database → Schema
558
+ ${chalk.blue.bold("generate")} Generate migration files
559
+ ${chalk.blue.bold("migrate")} Run pending migrations
560
+ ${chalk.blue.bold("studio")} Open Studio viewer
561
+ ${chalk.blue.bold("branch")} Database branching (create, list, delete, info)
562
+
563
+ ${chalk.green.bold("Examples")}
564
+ ${chalk.gray("# Quick development workflow")}
565
+ rebase schema generate && rebase db push
566
+
567
+ ${chalk.gray("# Production migration workflow")}
568
+ rebase db generate
569
+ rebase db migrate
570
+
571
+ ${chalk.gray("# Create a database branch")}
572
+ rebase db branch create feature_auth
573
+ `);
574
+ }
575
+ async function devCommand(rawArgs) {
576
+ const args = arg(
577
+ {
578
+ "--backend-only": Boolean,
579
+ "--frontend-only": Boolean,
580
+ "--port": Number,
581
+ "--help": Boolean,
582
+ "-b": "--backend-only",
583
+ "-f": "--frontend-only",
584
+ "-p": "--port",
585
+ "-h": "--help"
586
+ },
587
+ {
588
+ argv: rawArgs.slice(3),
589
+ // skip "node rebase dev"
590
+ permissive: true
591
+ }
592
+ );
593
+ if (args["--help"]) {
594
+ printDevHelp();
595
+ return;
596
+ }
597
+ const projectRoot = requireProjectRoot();
598
+ const backendDir = findBackendDir(projectRoot);
599
+ const frontendDir = findFrontendDir(projectRoot);
600
+ const backendOnly = args["--backend-only"] || false;
601
+ const frontendOnly = args["--frontend-only"] || false;
602
+ console.log("");
603
+ console.log(chalk.bold(" 🚀 Rebase Dev Server"));
604
+ console.log("");
605
+ const children = [];
606
+ let frontendUrl = "";
607
+ let backendUrl = "";
608
+ let debounceSummary = null;
609
+ let bannerPrinted = false;
610
+ const stripAnsi = (str) => str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
611
+ function printSummary() {
612
+ if (!frontendUrl || !backendUrl) return;
613
+ if (debounceSummary) clearTimeout(debounceSummary);
614
+ debounceSummary = setTimeout(() => {
615
+ if (bannerPrinted) return;
616
+ console.log("");
617
+ console.log(chalk.cyan("┌────────────────────────────────────────────────────────────┐"));
618
+ console.log(chalk.cyan("│ │"));
619
+ console.log(chalk.cyan("│ ✨ Rebase Admin App is ready! │"));
620
+ const cleanUrl = stripAnsi(frontendUrl);
621
+ const paddedUrl = cleanUrl.padEnd(40);
622
+ console.log(chalk.cyan(`│ 👉 Frontend URL: `) + chalk.white(paddedUrl) + chalk.cyan(`│`));
623
+ console.log(chalk.cyan("│ │"));
624
+ console.log(chalk.cyan("└────────────────────────────────────────────────────────────┘"));
625
+ console.log("");
626
+ bannerPrinted = true;
627
+ }, 500);
628
+ }
629
+ const cleanup = () => {
630
+ children.forEach((child) => {
631
+ if (!child.killed) {
632
+ child.kill("SIGTERM");
633
+ }
634
+ });
635
+ process.exit(0);
636
+ };
637
+ process.on("SIGINT", cleanup);
638
+ process.on("SIGTERM", cleanup);
639
+ if (!frontendOnly && backendDir) {
640
+ const tsxBin = resolveTsx(projectRoot);
641
+ if (!tsxBin) {
642
+ console.error(chalk.red(" ✗ Could not find tsx binary for backend."));
643
+ console.error(chalk.gray(" Install it with: pnpm add -D tsx"));
644
+ process.exit(1);
645
+ }
646
+ const envFile = findEnvFile(projectRoot);
647
+ const env = { ...process.env };
648
+ if (envFile) {
649
+ env.DOTENV_CONFIG_PATH = envFile;
650
+ }
651
+ if (args["--port"]) {
652
+ env.PORT = String(args["--port"]);
653
+ }
654
+ path.join(backendDir, "src", "index.ts");
655
+ const watchDirs = [
656
+ `--watch="${path.join("..", "shared", "**", "*")}"`
657
+ ];
658
+ console.log(` ${chalk.cyan("▶")} Backend: ${chalk.gray(backendDir)}`);
659
+ const backendChild = execa(
660
+ tsxBin,
661
+ ["watch", ...watchDirs, "--conditions", "development", "src/index.ts"],
662
+ {
663
+ cwd: backendDir,
664
+ stdio: ["inherit", "pipe", "pipe"],
665
+ env,
666
+ shell: true
667
+ }
668
+ );
669
+ backendChild.catch(() => {
670
+ });
671
+ backendChild.stdout?.on("data", (data) => {
672
+ const lines = data.toString().split("\n").filter(Boolean);
673
+ lines.forEach((line) => {
674
+ console.log(`${chalk.cyan.bold("[backend]")} ${line}`);
675
+ const cleanLine = stripAnsi(line);
676
+ if (cleanLine.includes("Server running at http://")) {
677
+ backendUrl = "started";
678
+ printSummary();
679
+ }
680
+ });
681
+ });
682
+ backendChild.stderr?.on("data", (data) => {
683
+ const lines = data.toString().split("\n").filter(Boolean);
684
+ lines.forEach((line) => {
685
+ console.log(`${chalk.cyan.bold("[backend]")} ${line}`);
686
+ });
687
+ });
688
+ children.push(backendChild);
689
+ } else if (!frontendOnly && !backendDir) {
690
+ console.warn(chalk.yellow(" ⚠ No backend/ directory found, skipping backend."));
691
+ }
692
+ if (!backendOnly && frontendDir) {
693
+ console.log(` ${chalk.magenta("▶")} Frontend: ${chalk.gray(frontendDir)}`);
694
+ const frontendChild = execa(
695
+ "pnpm",
696
+ ["run", "dev"],
697
+ {
698
+ cwd: frontendDir,
699
+ stdio: ["inherit", "pipe", "pipe"],
700
+ env: process.env,
701
+ shell: true
702
+ }
703
+ );
704
+ frontendChild.catch(() => {
705
+ });
706
+ frontendChild.stdout?.on("data", (data) => {
707
+ const lines = data.toString().split("\n").filter(Boolean);
708
+ lines.forEach((line) => {
709
+ console.log(`${chalk.magenta.bold("[frontend]")} ${line}`);
710
+ const cleanLine = stripAnsi(line);
711
+ const urlMatch = cleanLine.match(/(http:\/\/(?:localhost|127\.0\.0\.1):\d+)/);
712
+ if (cleanLine.includes("Local:") && urlMatch) {
713
+ frontendUrl = urlMatch[1];
714
+ printSummary();
715
+ }
716
+ });
717
+ });
718
+ frontendChild.stderr?.on("data", (data) => {
719
+ const lines = data.toString().split("\n").filter(Boolean);
720
+ lines.forEach((line) => {
721
+ console.log(`${chalk.magenta.bold("[frontend]")} ${line}`);
722
+ });
723
+ });
724
+ children.push(frontendChild);
725
+ } else if (!backendOnly && !frontendDir) {
726
+ console.warn(chalk.yellow(" ⚠ No frontend/ directory found, skipping frontend."));
727
+ }
728
+ if (children.length === 0) {
729
+ console.error(chalk.red(" ✗ Nothing to start. Check your project structure."));
730
+ process.exit(1);
731
+ }
732
+ console.log("");
733
+ console.log(chalk.gray(" Press Ctrl+C to stop all servers."));
734
+ console.log("");
735
+ await Promise.all(
736
+ children.map(
737
+ (child) => new Promise((resolve) => {
738
+ child.finally(() => resolve());
739
+ })
740
+ )
741
+ );
742
+ }
743
+ function printDevHelp() {
744
+ console.log(`
745
+ ${chalk.bold("rebase dev")} — Start the development server
746
+
747
+ ${chalk.green.bold("Usage")}
748
+ rebase dev [options]
749
+
750
+ ${chalk.green.bold("Options")}
751
+ ${chalk.blue("--backend-only, -b")} Only start the backend server
752
+ ${chalk.blue("--frontend-only, -f")} Only start the frontend server
753
+ ${chalk.blue("--port, -p")} Backend port (default: 3001)
754
+
755
+ ${chalk.green.bold("Description")}
756
+ Starts both the backend (tsx watch + Express) and frontend (Vite)
757
+ dev servers concurrently with color-coded output prefixes.
758
+ `);
759
+ }
760
+ async function authCommand(subcommand, rawArgs) {
761
+ if (!subcommand || subcommand === "--help") {
762
+ printAuthHelp();
763
+ return;
764
+ }
765
+ switch (subcommand) {
766
+ case "reset-password":
767
+ await resetPassword(rawArgs);
768
+ break;
769
+ default:
770
+ console.error(chalk.red(`Unknown auth command: ${subcommand}`));
771
+ console.log("");
772
+ printAuthHelp();
773
+ process.exit(1);
774
+ }
775
+ }
776
+ async function resetPassword(rawArgs) {
777
+ const args = arg(
778
+ {
779
+ "--email": String,
780
+ "--password": String,
781
+ "-e": "--email",
782
+ "-p": "--password"
783
+ },
784
+ {
785
+ argv: rawArgs.slice(4),
786
+ // skip "node rebase auth reset-password"
787
+ permissive: true
788
+ }
789
+ );
790
+ const email = args["--email"] || args._[0];
791
+ const newPassword = args["--password"] || args._[1];
792
+ if (!email) {
793
+ console.error(chalk.red("✗ Email is required."));
794
+ console.log("");
795
+ console.log(chalk.gray(" Usage: rebase auth reset-password <email> [new-password]"));
796
+ console.log(chalk.gray(" rebase auth reset-password --email user@example.com --password NewPass123!"));
797
+ process.exit(1);
798
+ }
799
+ const projectRoot = requireProjectRoot();
800
+ const backendDir = requireBackendDir(projectRoot);
801
+ const tsxBin = resolveTsx(projectRoot);
802
+ if (!tsxBin) {
803
+ console.error(chalk.red("✗ Could not find tsx binary."));
804
+ process.exit(1);
805
+ }
806
+ const envFile = findEnvFile(projectRoot);
807
+ const env = { ...process.env };
808
+ if (envFile) {
809
+ env.DOTENV_CONFIG_PATH = envFile;
810
+ }
811
+ const scriptContent = `
812
+ import { createPostgresDatabaseConnection } from "@rebasepro/server-core";
813
+ import { hashPassword } from "@rebasepro/server-core/src/auth/password";
814
+ import { eq } from "drizzle-orm";
815
+ import { users } from "@rebasepro/server-core/src/db/auth-schema";
816
+ import * as dotenv from "dotenv";
817
+ import path from "path";
818
+
819
+ dotenv.config({ path: "${envFile || path.join(projectRoot, ".env")}" });
820
+
821
+ const email = "${email}";
822
+ const newPassword = "${newPassword || "NewPassword123!"}";
823
+
824
+ async function resetPassword() {
825
+ const { db } = createPostgresDatabaseConnection(process.env.DATABASE_URL!);
826
+ const hash = await hashPassword(newPassword);
827
+
828
+ const result = await db.update(users)
829
+ .set({ passwordHash: hash })
830
+ .where(eq(users.email, email))
831
+ .returning({
832
+ id: users.id,
833
+ email: users.email
834
+ });
835
+
836
+ if (result.length > 0) {
837
+ console.log("✅ Password reset for: " + result[0].email);
838
+ ${!newPassword ? 'console.log(" New password: " + newPassword);' : ""}
839
+ } else {
840
+ console.log("✗ User not found: " + email);
841
+ }
842
+ process.exit(0);
843
+ }
844
+
845
+ resetPassword().catch(console.error);
846
+ `;
847
+ const tmpScriptPath = path.join(backendDir, ".tmp-reset-password.ts");
848
+ fs.writeFileSync(tmpScriptPath, scriptContent, "utf-8");
849
+ console.log("");
850
+ console.log(chalk.bold(" 🔑 Rebase Auth — Reset Password"));
851
+ console.log("");
852
+ console.log(` ${chalk.gray("Email:")} ${email}`);
853
+ if (newPassword) {
854
+ console.log(` ${chalk.gray("Password:")} ${"*".repeat(newPassword.length)}`);
855
+ }
856
+ console.log("");
857
+ const child = spawn(tsxBin, [tmpScriptPath], {
858
+ cwd: backendDir,
859
+ stdio: "inherit",
860
+ env
861
+ });
862
+ return new Promise((resolve) => {
863
+ child.on("close", (code) => {
864
+ try {
865
+ fs.unlinkSync(tmpScriptPath);
866
+ } catch {
867
+ }
868
+ if (code !== 0) {
869
+ process.exit(code ?? 1);
870
+ }
871
+ resolve();
872
+ });
873
+ });
874
+ }
875
+ function printAuthHelp() {
876
+ console.log(`
877
+ ${chalk.bold("rebase auth")} — Authentication management commands
878
+
879
+ ${chalk.green.bold("Usage")}
880
+ rebase auth ${chalk.blue("<command>")} [options]
881
+
882
+ ${chalk.green.bold("Commands")}
883
+ ${chalk.blue.bold("reset-password")} Reset a user's password
884
+
885
+ ${chalk.green.bold("reset-password Options")}
886
+ ${chalk.blue("--email, -e")} User's email address
887
+ ${chalk.blue("--password, -p")} New password (default: NewPassword123!)
888
+
889
+ ${chalk.green.bold("Examples")}
890
+ rebase auth reset-password user@example.com
891
+ rebase auth reset-password --email user@example.com --password MyNewPass!
892
+ `);
893
+ }
154
894
  const __filename$1 = fileURLToPath(import.meta.url);
155
895
  const __dirname$1 = path.dirname(__filename$1);
156
896
  function getVersion() {
@@ -181,16 +921,52 @@ async function entry(args) {
181
921
  return;
182
922
  }
183
923
  const command = parsedArgs._[0];
184
- if (!command || parsedArgs["--help"]) {
924
+ const subcommand = parsedArgs._[1];
925
+ const namespacedCommands = ["schema", "db", "dev", "auth"];
926
+ if (!command || parsedArgs["--help"] && !namespacedCommands.includes(command)) {
185
927
  printHelp();
186
928
  return;
187
929
  }
188
- if (command === "init") {
189
- await createRebaseApp(args);
190
- } else {
191
- console.log(chalk.red(`Unknown command: ${command}`));
192
- console.log("");
193
- printHelp();
930
+ const effectiveSubcommand = parsedArgs["--help"] ? "--help" : subcommand;
931
+ switch (command) {
932
+ case "init":
933
+ await createRebaseApp(args);
934
+ break;
935
+ case "generate-sdk":
936
+ const sdkArgs = arg(
937
+ {
938
+ "--collections-dir": String,
939
+ "--output": String,
940
+ "-c": "--collections-dir",
941
+ "-o": "--output"
942
+ },
943
+ {
944
+ argv: args.slice(3),
945
+ permissive: true
946
+ }
947
+ );
948
+ await generateSdkCommand({
949
+ collectionsDir: sdkArgs["--collections-dir"] || "./shared/collections",
950
+ output: sdkArgs["--output"] || "./generated/sdk",
951
+ cwd: process.cwd()
952
+ });
953
+ break;
954
+ case "schema":
955
+ await schemaCommand(effectiveSubcommand, args);
956
+ break;
957
+ case "db":
958
+ await dbCommand(effectiveSubcommand, args);
959
+ break;
960
+ case "dev":
961
+ await devCommand(args);
962
+ break;
963
+ case "auth":
964
+ await authCommand(effectiveSubcommand, args);
965
+ break;
966
+ default:
967
+ console.log(chalk.red(`Unknown command: ${command}`));
968
+ console.log("");
969
+ printHelp();
194
970
  }
195
971
  }
196
972
  function printHelp() {
@@ -201,7 +977,27 @@ ${chalk.green.bold("Usage")}
201
977
  rebase ${chalk.blue("<command>")} [options]
202
978
 
203
979
  ${chalk.green.bold("Commands")}
204
- ${chalk.blue.bold("init")} Create a new Rebase project
980
+ ${chalk.blue.bold("init")} Create a new Rebase project
981
+ ${chalk.blue.bold("dev")} Start the development server
982
+
983
+ ${chalk.green.bold("Schema")}
984
+ ${chalk.blue.bold("schema generate")} Generate Drizzle schema from collections
985
+ ${chalk.blue.bold("schema")} ${chalk.gray("--help")} Show schema command help
986
+
987
+ ${chalk.green.bold("Database")}
988
+ ${chalk.blue.bold("db push")} Apply schema directly to database ${chalk.gray("(dev)")}
989
+ ${chalk.blue.bold("db pull")} Introspect database → Drizzle schema
990
+ ${chalk.blue.bold("db generate")} Generate SQL migration files
991
+ ${chalk.blue.bold("db migrate")} Run pending migrations
992
+ ${chalk.blue.bold("db studio")} Open Drizzle Studio
993
+ ${chalk.blue.bold("db")} ${chalk.gray("--help")} Show database command help
994
+
995
+ ${chalk.green.bold("SDK")}
996
+ ${chalk.blue.bold("generate-sdk")} Generate a typed JS SDK from collections
997
+
998
+ ${chalk.green.bold("Auth")}
999
+ ${chalk.blue.bold("auth reset-password")} Reset a user's password
1000
+ ${chalk.blue.bold("auth")} ${chalk.gray("--help")} Show auth command help
205
1001
 
206
1002
  ${chalk.green.bold("Options")}
207
1003
  ${chalk.blue("--version, -v")} Show version number
@@ -254,12 +1050,26 @@ async function logout(env, _debug) {
254
1050
  }
255
1051
  }
256
1052
  export {
1053
+ authCommand,
257
1054
  createRebaseApp,
1055
+ dbCommand,
1056
+ devCommand,
258
1057
  entry,
1058
+ findBackendDir,
1059
+ findEnvFile,
1060
+ findFrontendDir,
1061
+ findProjectRoot,
1062
+ getActiveBackendPlugin,
259
1063
  getTokens,
260
1064
  login,
261
1065
  logout,
262
1066
  parseJwt,
263
- refreshCredentials
1067
+ refreshCredentials,
1068
+ requireBackendDir,
1069
+ requireProjectRoot,
1070
+ resolveLocalBin,
1071
+ resolvePluginCliScript,
1072
+ resolveTsx,
1073
+ schemaCommand
264
1074
  };
265
1075
  //# sourceMappingURL=index.es.js.map