@rebasepro/cli 0.0.1-canary.2 → 0.0.1-canary.4f204c2

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