@rebasepro/cli 0.0.1-canary.4d4fb3e → 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 (35) hide show
  1. package/dist/commands/cli.test.d.ts +1 -0
  2. package/dist/commands/dev.test.d.ts +1 -0
  3. package/dist/commands/doctor.d.ts +1 -0
  4. package/dist/commands/generate_sdk.d.ts +1 -1
  5. package/dist/commands/init.test.d.ts +1 -0
  6. package/dist/index.cjs +232 -64
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.es.js +232 -64
  9. package/dist/index.es.js.map +1 -1
  10. package/dist/utils/project.test.d.ts +1 -0
  11. package/package.json +67 -66
  12. package/templates/template/.env.template +22 -1
  13. package/templates/template/README.md +2 -2
  14. package/templates/template/backend/Dockerfile +6 -6
  15. package/templates/template/backend/functions/hello.ts +24 -6
  16. package/templates/template/backend/package.json +2 -2
  17. package/templates/template/backend/src/env.ts +52 -0
  18. package/templates/template/backend/src/index.ts +86 -34
  19. package/templates/template/backend/tsconfig.json +1 -1
  20. package/templates/template/config/collections/authors.ts +45 -0
  21. package/templates/template/config/collections/index.ts +5 -0
  22. package/templates/template/{shared → config}/collections/posts.ts +39 -1
  23. package/templates/template/config/collections/tags.ts +27 -0
  24. package/templates/template/{shared → config}/package.json +1 -1
  25. package/templates/template/docker-compose.yml +12 -0
  26. package/templates/template/frontend/Dockerfile +3 -3
  27. package/templates/template/frontend/package.json +2 -2
  28. package/templates/template/frontend/src/App.tsx +11 -12
  29. package/templates/template/frontend/src/main.tsx +2 -2
  30. package/templates/template/frontend/vite.config.ts +1 -1
  31. package/templates/template/pnpm-workspace.yaml +1 -1
  32. package/templates/template/scripts/example.ts +91 -0
  33. package/templates/template/shared/collections/index.ts +0 -3
  34. /package/templates/template/{shared → config}/index.ts +0 -0
  35. /package/templates/template/{shared → config}/tsconfig.json +0 -0
package/dist/index.es.js CHANGED
@@ -81,8 +81,8 @@ async function promptForOptions(rawArgs) {
81
81
  });
82
82
  }
83
83
  const answers = await inquirer.prompt(questions);
84
- const projectName = nameArg || answers.projectName;
85
- const targetDirectory = path.resolve(process.cwd(), projectName);
84
+ const targetDirectory = path.resolve(process.cwd(), nameArg || answers.projectName);
85
+ const projectName = path.basename(targetDirectory);
86
86
  const templateDirectory = path.resolve(cliRoot, "templates", "template");
87
87
  return {
88
88
  projectName,
@@ -179,7 +179,7 @@ POSTGRES_PASSWORD=${dbPassword}
179
179
  console.log("");
180
180
  console.log(` ${chalk.cyan("pnpm dev")}`);
181
181
  console.log("");
182
- 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."));
183
183
  console.log("");
184
184
  console.log(chalk.gray("Docs: https://rebase.pro/docs"));
185
185
  console.log(chalk.gray("GitHub: https://github.com/rebasepro/rebase"));
@@ -190,14 +190,63 @@ async function replacePlaceholders(options) {
190
190
  "package.json",
191
191
  "frontend/package.json",
192
192
  "backend/package.json",
193
- "shared/package.json",
193
+ "config/package.json",
194
194
  "frontend/index.html"
195
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();
196
230
  for (const file of filesToProcess) {
197
231
  const fullPath = path.resolve(options.targetDirectory, file);
198
232
  if (!fs.existsSync(fullPath)) continue;
199
- let content = fs.readFileSync(fullPath, "utf-8");
200
- 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
+ }
201
250
  fs.writeFileSync(fullPath, content, "utf-8");
202
251
  }
203
252
  }
@@ -309,7 +358,7 @@ async function generateSdkCommand(args) {
309
358
  console.log(chalk.green.bold(" ✓ SDK generated successfully!"));
310
359
  console.log("");
311
360
  console.log(chalk.gray(" Usage:"));
312
- console.log(chalk.gray(` import { createRebaseClient } from '@rebasepro/client';`));
361
+ console.log(chalk.gray(" import { createRebaseClient } from '@rebasepro/client';"));
313
362
  console.log(chalk.gray(` import type { Database } from './${path.relative(cwd, path.join(resolvedOutput, "database.types"))}';`));
314
363
  console.log("");
315
364
  console.log(chalk.gray(" const rebase = createRebaseClient<Database>({"));
@@ -336,7 +385,7 @@ function findProjectRoot(startDir = process.cwd()) {
336
385
  }
337
386
  } catch {
338
387
  }
339
- if (fs.existsSync(path.join(dir, "backend")) && fs.existsSync(path.join(dir, "shared"))) {
388
+ if (fs.existsSync(path.join(dir, "backend")) && fs.existsSync(path.join(dir, "config"))) {
340
389
  return dir;
341
390
  }
342
391
  }
@@ -353,7 +402,10 @@ function getActiveBackendPlugin(backendDir) {
353
402
  if (!fs.existsSync(pkgPath)) return null;
354
403
  try {
355
404
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
356
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
405
+ const deps = {
406
+ ...pkg.dependencies,
407
+ ...pkg.devDependencies
408
+ };
357
409
  for (const dep of Object.keys(deps)) {
358
410
  if (dep.startsWith("@rebasepro/server-") && dep !== "@rebasepro/server-core") {
359
411
  return dep;
@@ -464,13 +516,13 @@ async function schemaCommand(subcommand, rawArgs) {
464
516
  console.error(chalk.red("✗ Could not find tsx binary."));
465
517
  process.exit(1);
466
518
  }
467
- await execa(tsxBin, [pluginCli, "schema", subcommand, ...rawArgs.slice(2)], {
519
+ await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
468
520
  cwd: backendDir,
469
521
  stdio: "inherit",
470
522
  env
471
523
  });
472
524
  } else {
473
- await execa("node", [pluginCli, "schema", subcommand, ...rawArgs.slice(2)], {
525
+ await execa("node", [pluginCli, ...rawArgs.slice(2)], {
474
526
  cwd: backendDir,
475
527
  stdio: "inherit",
476
528
  env
@@ -528,13 +580,13 @@ async function dbCommand(subcommand, rawArgs) {
528
580
  console.error(chalk.red("✗ Could not find tsx binary."));
529
581
  process.exit(1);
530
582
  }
531
- await execa(tsxBin, [pluginCli, "db", subcommand, ...rawArgs.slice(2)], {
583
+ await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
532
584
  cwd: backendDir,
533
585
  stdio: "inherit",
534
586
  env
535
587
  });
536
588
  } else {
537
- await execa("node", [pluginCli, "db", subcommand, ...rawArgs.slice(2)], {
589
+ await execa("node", [pluginCli, ...rawArgs.slice(2)], {
538
590
  cwd: backendDir,
539
591
  stdio: "inherit",
540
592
  env
@@ -572,6 +624,27 @@ ${chalk.green.bold("Examples")}
572
624
  rebase db branch create feature_auth
573
625
  `);
574
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
+ }
575
648
  async function devCommand(rawArgs) {
576
649
  const args = arg(
577
650
  {
@@ -599,6 +672,7 @@ async function devCommand(rawArgs) {
599
672
  const frontendDir = findFrontendDir(projectRoot);
600
673
  const backendOnly = args["--backend-only"] || false;
601
674
  const frontendOnly = args["--frontend-only"] || false;
675
+ const startPort = resolveStartPort(projectRoot, args["--port"]);
602
676
  console.log("");
603
677
  console.log(chalk.bold(" 🚀 Rebase Dev Server"));
604
678
  console.log("");
@@ -607,6 +681,7 @@ async function devCommand(rawArgs) {
607
681
  let backendUrl = "";
608
682
  let debounceSummary = null;
609
683
  let bannerPrinted = false;
684
+ let resolvedBackendPort = null;
610
685
  const stripAnsi = (str) => str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
611
686
  function printSummary() {
612
687
  if (!frontendUrl || !backendUrl) return;
@@ -619,7 +694,7 @@ async function devCommand(rawArgs) {
619
694
  console.log(chalk.cyan("│ ✨ Rebase Admin App is ready! │"));
620
695
  const cleanUrl = stripAnsi(frontendUrl);
621
696
  const paddedUrl = cleanUrl.padEnd(40);
622
- console.log(chalk.cyan(`│ 👉 Frontend URL: `) + chalk.white(paddedUrl) + chalk.cyan(`│`));
697
+ console.log(chalk.cyan("│ 👉 Frontend URL: ") + chalk.white(paddedUrl) + chalk.cyan("│"));
623
698
  console.log(chalk.cyan("│ │"));
624
699
  console.log(chalk.cyan("└────────────────────────────────────────────────────────────┘"));
625
700
  console.log("");
@@ -627,15 +702,74 @@ async function devCommand(rawArgs) {
627
702
  }, 500);
628
703
  }
629
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
+ }
630
712
  children.forEach((child) => {
631
- if (!child.killed) {
632
- child.kill("SIGTERM");
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
+ }
633
726
  }
634
727
  });
635
728
  process.exit(0);
636
729
  };
637
730
  process.on("SIGINT", cleanup);
638
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
+ }
639
773
  if (!frontendOnly && backendDir) {
640
774
  const tsxBin = resolveTsx(projectRoot);
641
775
  if (!tsxBin) {
@@ -648,22 +782,19 @@ async function devCommand(rawArgs) {
648
782
  if (envFile) {
649
783
  env.DOTENV_CONFIG_PATH = envFile;
650
784
  }
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
- ];
785
+ env.PORT = String(startPort);
658
786
  console.log(` ${chalk.cyan("▶")} Backend: ${chalk.gray(backendDir)}`);
787
+ console.log(` ${chalk.gray("↳ PORT")} = ${chalk.white(String(startPort))}`);
788
+ let frontendLaunched = false;
659
789
  const backendChild = execa(
660
790
  tsxBin,
661
- ["watch", ...watchDirs, "--conditions", "development", "src/index.ts"],
791
+ ["watch", `--watch="${path.join("..", "config", "**", "*")}"`, "--conditions", "development", "src/index.ts"],
662
792
  {
663
793
  cwd: backendDir,
664
794
  stdio: ["inherit", "pipe", "pipe"],
665
795
  env,
666
- shell: true
796
+ shell: true,
797
+ detached: process.platform !== "win32"
667
798
  }
668
799
  );
669
800
  backendChild.catch(() => {
@@ -673,9 +804,19 @@ async function devCommand(rawArgs) {
673
804
  lines.forEach((line) => {
674
805
  console.log(`${chalk.cyan.bold("[backend]")} ${line}`);
675
806
  const cleanLine = stripAnsi(line);
676
- if (cleanLine.includes("Server running at http://")) {
807
+ const serverMatch = cleanLine.match(/Server running at http:\/\/(?:localhost|127\.0\.0\.1):(\d+)/);
808
+ if (serverMatch) {
809
+ resolvedBackendPort = parseInt(serverMatch[1], 10);
677
810
  backendUrl = "started";
678
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
+ }
679
820
  }
680
821
  });
681
822
  });
@@ -689,39 +830,8 @@ async function devCommand(rawArgs) {
689
830
  } else if (!frontendOnly && !backendDir) {
690
831
  console.warn(chalk.yellow(" ⚠ No backend/ directory found, skipping backend."));
691
832
  }
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);
833
+ if (!backendOnly && frontendDir && (frontendOnly || !backendDir)) {
834
+ startFrontend(null);
725
835
  } else if (!backendOnly && !frontendDir) {
726
836
  console.warn(chalk.yellow(" ⚠ No frontend/ directory found, skipping frontend."));
727
837
  }
@@ -750,11 +860,19 @@ ${chalk.green.bold("Usage")}
750
860
  ${chalk.green.bold("Options")}
751
861
  ${chalk.blue("--backend-only, -b")} Only start the backend server
752
862
  ${chalk.blue("--frontend-only, -f")} Only start the frontend server
753
- ${chalk.blue("--port, -p")} Backend port (default: 3001)
863
+ ${chalk.blue("--port, -p")} Backend port (default: auto-detected per project)
754
864
 
755
865
  ${chalk.green.bold("Description")}
756
- Starts both the backend (tsx watch + Express) and frontend (Vite)
866
+ Starts both the backend (tsx watch + Hono) and frontend (Vite)
757
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.
758
876
  `);
759
877
  }
760
878
  async function authCommand(subcommand, rawArgs) {
@@ -891,6 +1009,49 @@ ${chalk.green.bold("Examples")}
891
1009
  rebase auth reset-password --email user@example.com --password MyNewPass!
892
1010
  `);
893
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
+ }
894
1055
  const __filename$1 = fileURLToPath(import.meta.url);
895
1056
  const __dirname$1 = path.dirname(__filename$1);
896
1057
  function getVersion() {
@@ -922,7 +1083,7 @@ async function entry(args) {
922
1083
  }
923
1084
  const command = parsedArgs._[0];
924
1085
  const subcommand = parsedArgs._[1];
925
- const namespacedCommands = ["schema", "db", "dev", "auth"];
1086
+ const namespacedCommands = ["schema", "db", "dev", "auth", "doctor"];
926
1087
  if (!command || parsedArgs["--help"] && !namespacedCommands.includes(command)) {
927
1088
  printHelp();
928
1089
  return;
@@ -932,7 +1093,7 @@ async function entry(args) {
932
1093
  case "init":
933
1094
  await createRebaseApp(args);
934
1095
  break;
935
- case "generate-sdk":
1096
+ case "generate-sdk": {
936
1097
  const sdkArgs = arg(
937
1098
  {
938
1099
  "--collections-dir": String,
@@ -946,11 +1107,12 @@ async function entry(args) {
946
1107
  }
947
1108
  );
948
1109
  await generateSdkCommand({
949
- collectionsDir: sdkArgs["--collections-dir"] || "./shared/collections",
1110
+ collectionsDir: sdkArgs["--collections-dir"] || "./config/collections",
950
1111
  output: sdkArgs["--output"] || "./generated/sdk",
951
1112
  cwd: process.cwd()
952
1113
  });
953
1114
  break;
1115
+ }
954
1116
  case "schema":
955
1117
  await schemaCommand(effectiveSubcommand, args);
956
1118
  break;
@@ -963,6 +1125,9 @@ async function entry(args) {
963
1125
  case "auth":
964
1126
  await authCommand(effectiveSubcommand, args);
965
1127
  break;
1128
+ case "doctor":
1129
+ await doctorCommand(args);
1130
+ break;
966
1131
  default:
967
1132
  console.log(chalk.red(`Unknown command: ${command}`));
968
1133
  console.log("");
@@ -999,6 +1164,9 @@ ${chalk.green.bold("Auth")}
999
1164
  ${chalk.blue.bold("auth reset-password")} Reset a user's password
1000
1165
  ${chalk.blue.bold("auth")} ${chalk.gray("--help")} Show auth command help
1001
1166
 
1167
+ ${chalk.green.bold("Diagnostics")}
1168
+ ${chalk.blue.bold("doctor")} Detect schema drift between collections, schema, and DB
1169
+
1002
1170
  ${chalk.green.bold("Options")}
1003
1171
  ${chalk.blue("--version, -v")} Show version number
1004
1172
  ${chalk.blue("--help, -h")} Show this help message