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

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 +188 -62
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.es.js +188 -62
  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
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare function doctorCommand(rawArgs: string[]): Promise<void>;
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * CLI command: generate-sdk
3
3
  *
4
- * Reads collection definitions from a specified directory (default: ./shared/collections),
4
+ * Reads collection definitions from a specified directory (default: ./config/collections),
5
5
  * generates a typed JS SDK, and writes it to the output directory (default: ./generated/sdk).
6
6
  *
7
7
  * Uses jiti for dynamic TypeScript import of collection files.
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.cjs CHANGED
@@ -90,8 +90,8 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
90
90
  });
91
91
  }
92
92
  const answers = await inquirer.prompt(questions);
93
- const projectName = nameArg || answers.projectName;
94
- const targetDirectory = path.resolve(process.cwd(), projectName);
93
+ const targetDirectory = path.resolve(process.cwd(), nameArg || answers.projectName);
94
+ const projectName = path.basename(targetDirectory);
95
95
  const templateDirectory = path.resolve(cliRoot, "templates", "template");
96
96
  return {
97
97
  projectName,
@@ -188,7 +188,7 @@ POSTGRES_PASSWORD=${dbPassword}
188
188
  console.log("");
189
189
  console.log(` ${chalk.cyan("pnpm dev")}`);
190
190
  console.log("");
191
- console.log(chalk.gray("This starts both the backend (Express + PostgreSQL)") + chalk.gray(" and the frontend (Vite + React) concurrently."));
191
+ console.log(chalk.gray("This starts both the backend (Hono + PostgreSQL)") + chalk.gray(" and the frontend (Vite + React) concurrently."));
192
192
  console.log("");
193
193
  console.log(chalk.gray("Docs: https://rebase.pro/docs"));
194
194
  console.log(chalk.gray("GitHub: https://github.com/rebasepro/rebase"));
@@ -199,14 +199,21 @@ POSTGRES_PASSWORD=${dbPassword}
199
199
  "package.json",
200
200
  "frontend/package.json",
201
201
  "backend/package.json",
202
- "shared/package.json",
202
+ "config/package.json",
203
203
  "frontend/index.html"
204
204
  ];
205
+ const packageJsonPath = path.resolve(cliRoot, "package.json");
206
+ let cliVersion = "latest";
207
+ if (fs.existsSync(packageJsonPath)) {
208
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
209
+ cliVersion = pkg.version || "latest";
210
+ }
205
211
  for (const file of filesToProcess) {
206
212
  const fullPath = path.resolve(options.targetDirectory, file);
207
213
  if (!fs.existsSync(fullPath)) continue;
208
214
  let content = fs.readFileSync(fullPath, "utf-8");
209
215
  content = content.replace(/\{\{PROJECT_NAME\}\}/g, options.projectName);
216
+ content = content.replace(/("@rebasepro\/[^"]+":\s*)"workspace:\*"/g, `$1"${cliVersion}"`);
210
217
  fs.writeFileSync(fullPath, content, "utf-8");
211
218
  }
212
219
  }
@@ -318,7 +325,7 @@ Expected a default export of EntityCollection[] or an object with named collecti
318
325
  console.log(chalk.green.bold(" ✓ SDK generated successfully!"));
319
326
  console.log("");
320
327
  console.log(chalk.gray(" Usage:"));
321
- console.log(chalk.gray(` import { createRebaseClient } from '@rebasepro/client';`));
328
+ console.log(chalk.gray(" import { createRebaseClient } from '@rebasepro/client';"));
322
329
  console.log(chalk.gray(` import type { Database } from './${path.relative(cwd, path.join(resolvedOutput, "database.types"))}';`));
323
330
  console.log("");
324
331
  console.log(chalk.gray(" const rebase = createRebaseClient<Database>({"));
@@ -345,7 +352,7 @@ Expected a default export of EntityCollection[] or an object with named collecti
345
352
  }
346
353
  } catch {
347
354
  }
348
- if (fs.existsSync(path.join(dir, "backend")) && fs.existsSync(path.join(dir, "shared"))) {
355
+ if (fs.existsSync(path.join(dir, "backend")) && fs.existsSync(path.join(dir, "config"))) {
349
356
  return dir;
350
357
  }
351
358
  }
@@ -362,7 +369,10 @@ Expected a default export of EntityCollection[] or an object with named collecti
362
369
  if (!fs.existsSync(pkgPath)) return null;
363
370
  try {
364
371
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
365
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
372
+ const deps = {
373
+ ...pkg.dependencies,
374
+ ...pkg.devDependencies
375
+ };
366
376
  for (const dep of Object.keys(deps)) {
367
377
  if (dep.startsWith("@rebasepro/server-") && dep !== "@rebasepro/server-core") {
368
378
  return dep;
@@ -473,13 +483,13 @@ Expected a default export of EntityCollection[] or an object with named collecti
473
483
  console.error(chalk.red("✗ Could not find tsx binary."));
474
484
  process.exit(1);
475
485
  }
476
- await execa(tsxBin, [pluginCli, "schema", subcommand, ...rawArgs.slice(2)], {
486
+ await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
477
487
  cwd: backendDir,
478
488
  stdio: "inherit",
479
489
  env
480
490
  });
481
491
  } else {
482
- await execa("node", [pluginCli, "schema", subcommand, ...rawArgs.slice(2)], {
492
+ await execa("node", [pluginCli, ...rawArgs.slice(2)], {
483
493
  cwd: backendDir,
484
494
  stdio: "inherit",
485
495
  env
@@ -537,13 +547,13 @@ ${chalk.green.bold("generate Options")}
537
547
  console.error(chalk.red("✗ Could not find tsx binary."));
538
548
  process.exit(1);
539
549
  }
540
- await execa(tsxBin, [pluginCli, "db", subcommand, ...rawArgs.slice(2)], {
550
+ await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
541
551
  cwd: backendDir,
542
552
  stdio: "inherit",
543
553
  env
544
554
  });
545
555
  } else {
546
- await execa("node", [pluginCli, "db", subcommand, ...rawArgs.slice(2)], {
556
+ await execa("node", [pluginCli, ...rawArgs.slice(2)], {
547
557
  cwd: backendDir,
548
558
  stdio: "inherit",
549
559
  env
@@ -581,6 +591,27 @@ ${chalk.green.bold("Examples")}
581
591
  rebase db branch create feature_auth
582
592
  `);
583
593
  }
594
+ const DEV_PORT_FILENAME = ".rebase-dev-port";
595
+ function getProjectPort(projectRoot) {
596
+ let hash = 0;
597
+ for (let i = 0; i < projectRoot.length; i++) {
598
+ hash = (hash << 5) - hash + projectRoot.charCodeAt(i) | 0;
599
+ }
600
+ return 3001 + Math.abs(hash) % 999;
601
+ }
602
+ function resolveStartPort(projectRoot, explicitPort) {
603
+ if (explicitPort) return explicitPort;
604
+ if (process.env.PORT) return parseInt(process.env.PORT, 10);
605
+ try {
606
+ const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
607
+ if (fs.existsSync(portFile)) {
608
+ const saved = parseInt(fs.readFileSync(portFile, "utf-8").trim(), 10);
609
+ if (saved > 0 && saved < 65536) return saved;
610
+ }
611
+ } catch {
612
+ }
613
+ return getProjectPort(projectRoot);
614
+ }
584
615
  async function devCommand(rawArgs) {
585
616
  const args = arg(
586
617
  {
@@ -608,6 +639,7 @@ ${chalk.green.bold("Examples")}
608
639
  const frontendDir = findFrontendDir(projectRoot);
609
640
  const backendOnly = args["--backend-only"] || false;
610
641
  const frontendOnly = args["--frontend-only"] || false;
642
+ const startPort = resolveStartPort(projectRoot, args["--port"]);
611
643
  console.log("");
612
644
  console.log(chalk.bold(" 🚀 Rebase Dev Server"));
613
645
  console.log("");
@@ -616,6 +648,7 @@ ${chalk.green.bold("Examples")}
616
648
  let backendUrl = "";
617
649
  let debounceSummary = null;
618
650
  let bannerPrinted = false;
651
+ let resolvedBackendPort = null;
619
652
  const stripAnsi = (str) => str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
620
653
  function printSummary() {
621
654
  if (!frontendUrl || !backendUrl) return;
@@ -628,7 +661,7 @@ ${chalk.green.bold("Examples")}
628
661
  console.log(chalk.cyan("│ ✨ Rebase Admin App is ready! │"));
629
662
  const cleanUrl = stripAnsi(frontendUrl);
630
663
  const paddedUrl = cleanUrl.padEnd(40);
631
- console.log(chalk.cyan(`│ 👉 Frontend URL: `) + chalk.white(paddedUrl) + chalk.cyan(`│`));
664
+ console.log(chalk.cyan("│ 👉 Frontend URL: ") + chalk.white(paddedUrl) + chalk.cyan("│"));
632
665
  console.log(chalk.cyan("│ │"));
633
666
  console.log(chalk.cyan("└────────────────────────────────────────────────────────────┘"));
634
667
  console.log("");
@@ -636,15 +669,74 @@ ${chalk.green.bold("Examples")}
636
669
  }, 500);
637
670
  }
638
671
  const cleanup = () => {
672
+ try {
673
+ const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
674
+ if (fs.existsSync(portFile)) fs.unlinkSync(portFile);
675
+ const urlFile = path.join(projectRoot, ".rebase-dev-url");
676
+ if (fs.existsSync(urlFile)) fs.unlinkSync(urlFile);
677
+ } catch {
678
+ }
639
679
  children.forEach((child) => {
640
- if (!child.killed) {
641
- child.kill("SIGTERM");
680
+ if (child.pid && !child.killed) {
681
+ try {
682
+ if (process.platform === "win32") {
683
+ execa.commandSync(`taskkill /pid ${child.pid} /T /F`);
684
+ } else {
685
+ process.kill(-child.pid, "SIGKILL");
686
+ }
687
+ } catch (e) {
688
+ try {
689
+ child.kill("SIGKILL");
690
+ } catch (err) {
691
+ }
692
+ }
642
693
  }
643
694
  });
644
695
  process.exit(0);
645
696
  };
646
697
  process.on("SIGINT", cleanup);
647
698
  process.on("SIGTERM", cleanup);
699
+ function startFrontend(backendPort) {
700
+ if (!frontendDir) return;
701
+ console.log(` ${chalk.magenta("▶")} Frontend: ${chalk.gray(frontendDir)}`);
702
+ const frontendEnv = { ...process.env };
703
+ if (backendPort) {
704
+ frontendEnv.VITE_API_URL = `http://localhost:${backendPort}`;
705
+ console.log(` ${chalk.gray("↳ VITE_API_URL")} = ${chalk.white(`http://localhost:${backendPort}`)}`);
706
+ }
707
+ const frontendChild = execa(
708
+ "pnpm",
709
+ ["run", "dev"],
710
+ {
711
+ cwd: frontendDir,
712
+ stdio: ["inherit", "pipe", "pipe"],
713
+ env: frontendEnv,
714
+ shell: true,
715
+ detached: process.platform !== "win32"
716
+ }
717
+ );
718
+ frontendChild.catch(() => {
719
+ });
720
+ frontendChild.stdout?.on("data", (data) => {
721
+ const lines = data.toString().split("\n").filter(Boolean);
722
+ lines.forEach((line) => {
723
+ console.log(`${chalk.magenta.bold("[admin]")} ${line}`);
724
+ const cleanLine = stripAnsi(line);
725
+ const urlMatch = cleanLine.match(/(http:\/\/(?:localhost|127\.0\.0\.1):\d+)/);
726
+ if (cleanLine.includes("Local:") && urlMatch) {
727
+ frontendUrl = urlMatch[1];
728
+ printSummary();
729
+ }
730
+ });
731
+ });
732
+ frontendChild.stderr?.on("data", (data) => {
733
+ const lines = data.toString().split("\n").filter(Boolean);
734
+ lines.forEach((line) => {
735
+ console.log(`${chalk.magenta.bold("[admin]")} ${line}`);
736
+ });
737
+ });
738
+ children.push(frontendChild);
739
+ }
648
740
  if (!frontendOnly && backendDir) {
649
741
  const tsxBin = resolveTsx(projectRoot);
650
742
  if (!tsxBin) {
@@ -657,22 +749,19 @@ ${chalk.green.bold("Examples")}
657
749
  if (envFile) {
658
750
  env.DOTENV_CONFIG_PATH = envFile;
659
751
  }
660
- if (args["--port"]) {
661
- env.PORT = String(args["--port"]);
662
- }
663
- path.join(backendDir, "src", "index.ts");
664
- const watchDirs = [
665
- `--watch="${path.join("..", "shared", "**", "*")}"`
666
- ];
752
+ env.PORT = String(startPort);
667
753
  console.log(` ${chalk.cyan("▶")} Backend: ${chalk.gray(backendDir)}`);
754
+ console.log(` ${chalk.gray("↳ PORT")} = ${chalk.white(String(startPort))}`);
755
+ let frontendLaunched = false;
668
756
  const backendChild = execa(
669
757
  tsxBin,
670
- ["watch", ...watchDirs, "--conditions", "development", "src/index.ts"],
758
+ ["watch", `--watch="${path.join("..", "config", "**", "*")}"`, "--conditions", "development", "src/index.ts"],
671
759
  {
672
760
  cwd: backendDir,
673
761
  stdio: ["inherit", "pipe", "pipe"],
674
762
  env,
675
- shell: true
763
+ shell: true,
764
+ detached: process.platform !== "win32"
676
765
  }
677
766
  );
678
767
  backendChild.catch(() => {
@@ -682,9 +771,19 @@ ${chalk.green.bold("Examples")}
682
771
  lines.forEach((line) => {
683
772
  console.log(`${chalk.cyan.bold("[backend]")} ${line}`);
684
773
  const cleanLine = stripAnsi(line);
685
- if (cleanLine.includes("Server running at http://")) {
774
+ const serverMatch = cleanLine.match(/Server running at http:\/\/(?:localhost|127\.0\.0\.1):(\d+)/);
775
+ if (serverMatch) {
776
+ resolvedBackendPort = parseInt(serverMatch[1], 10);
686
777
  backendUrl = "started";
687
778
  printSummary();
779
+ const urlFile = path.join(projectRoot, ".rebase-dev-url");
780
+ fs.writeFileSync(urlFile, `http://localhost:${resolvedBackendPort}`, "utf-8");
781
+ const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
782
+ fs.writeFileSync(portFile, String(resolvedBackendPort), "utf-8");
783
+ if (!backendOnly && frontendDir && !frontendLaunched) {
784
+ frontendLaunched = true;
785
+ startFrontend(resolvedBackendPort);
786
+ }
688
787
  }
689
788
  });
690
789
  });
@@ -698,39 +797,8 @@ ${chalk.green.bold("Examples")}
698
797
  } else if (!frontendOnly && !backendDir) {
699
798
  console.warn(chalk.yellow(" ⚠ No backend/ directory found, skipping backend."));
700
799
  }
701
- if (!backendOnly && frontendDir) {
702
- console.log(` ${chalk.magenta("▶")} Frontend: ${chalk.gray(frontendDir)}`);
703
- const frontendChild = execa(
704
- "pnpm",
705
- ["run", "dev"],
706
- {
707
- cwd: frontendDir,
708
- stdio: ["inherit", "pipe", "pipe"],
709
- env: process.env,
710
- shell: true
711
- }
712
- );
713
- frontendChild.catch(() => {
714
- });
715
- frontendChild.stdout?.on("data", (data) => {
716
- const lines = data.toString().split("\n").filter(Boolean);
717
- lines.forEach((line) => {
718
- console.log(`${chalk.magenta.bold("[frontend]")} ${line}`);
719
- const cleanLine = stripAnsi(line);
720
- const urlMatch = cleanLine.match(/(http:\/\/(?:localhost|127\.0\.0\.1):\d+)/);
721
- if (cleanLine.includes("Local:") && urlMatch) {
722
- frontendUrl = urlMatch[1];
723
- printSummary();
724
- }
725
- });
726
- });
727
- frontendChild.stderr?.on("data", (data) => {
728
- const lines = data.toString().split("\n").filter(Boolean);
729
- lines.forEach((line) => {
730
- console.log(`${chalk.magenta.bold("[frontend]")} ${line}`);
731
- });
732
- });
733
- children.push(frontendChild);
800
+ if (!backendOnly && frontendDir && (frontendOnly || !backendDir)) {
801
+ startFrontend(null);
734
802
  } else if (!backendOnly && !frontendDir) {
735
803
  console.warn(chalk.yellow(" ⚠ No frontend/ directory found, skipping frontend."));
736
804
  }
@@ -759,11 +827,19 @@ ${chalk.green.bold("Usage")}
759
827
  ${chalk.green.bold("Options")}
760
828
  ${chalk.blue("--backend-only, -b")} Only start the backend server
761
829
  ${chalk.blue("--frontend-only, -f")} Only start the frontend server
762
- ${chalk.blue("--port, -p")} Backend port (default: 3001)
830
+ ${chalk.blue("--port, -p")} Backend port (default: auto-detected per project)
763
831
 
764
832
  ${chalk.green.bold("Description")}
765
- Starts both the backend (tsx watch + Express) and frontend (Vite)
833
+ Starts both the backend (tsx watch + Hono) and frontend (Vite)
766
834
  dev servers concurrently with color-coded output prefixes.
835
+
836
+ Each project automatically receives a unique default port derived
837
+ from its directory path, preventing collisions when running multiple
838
+ Rebase instances simultaneously.
839
+
840
+ If the assigned port is already in use, the server will automatically
841
+ try the next available port. The frontend is started only after the
842
+ backend is ready, and VITE_API_URL is injected automatically.
767
843
  `);
768
844
  }
769
845
  async function authCommand(subcommand, rawArgs) {
@@ -900,6 +976,49 @@ ${chalk.green.bold("Examples")}
900
976
  rebase auth reset-password --email user@example.com --password MyNewPass!
901
977
  `);
902
978
  }
979
+ async function doctorCommand(rawArgs) {
980
+ const projectRoot = requireProjectRoot();
981
+ const backendDir = requireBackendDir(projectRoot);
982
+ const activePlugin = getActiveBackendPlugin(backendDir);
983
+ if (!activePlugin) {
984
+ console.error(chalk.red("✗ Could not detect an active database plugin."));
985
+ console.error(chalk.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
986
+ process.exit(1);
987
+ }
988
+ const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
989
+ if (!pluginCli) {
990
+ console.error(chalk.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
991
+ process.exit(1);
992
+ }
993
+ const envFile = findEnvFile(projectRoot);
994
+ const env = { ...process.env };
995
+ if (envFile) {
996
+ env.DOTENV_CONFIG_PATH = envFile;
997
+ }
998
+ try {
999
+ const isTs = pluginCli.endsWith(".ts");
1000
+ if (isTs) {
1001
+ const tsxBin = resolveTsx(projectRoot);
1002
+ if (!tsxBin) {
1003
+ console.error(chalk.red("✗ Could not find tsx binary."));
1004
+ process.exit(1);
1005
+ }
1006
+ await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
1007
+ cwd: backendDir,
1008
+ stdio: "inherit",
1009
+ env
1010
+ });
1011
+ } else {
1012
+ await execa("node", [pluginCli, ...rawArgs.slice(2)], {
1013
+ cwd: backendDir,
1014
+ stdio: "inherit",
1015
+ env
1016
+ });
1017
+ }
1018
+ } catch {
1019
+ process.exit(1);
1020
+ }
1021
+ }
903
1022
  const __filename$1 = url.fileURLToPath(typeof document === "undefined" && typeof location === "undefined" ? require("url").pathToFileURL(__filename).href : typeof document === "undefined" ? location.href : _documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === "SCRIPT" && _documentCurrentScript.src || new URL("index.cjs", document.baseURI).href);
904
1023
  const __dirname$1 = path.dirname(__filename$1);
905
1024
  function getVersion() {
@@ -931,7 +1050,7 @@ ${chalk.green.bold("Examples")}
931
1050
  }
932
1051
  const command = parsedArgs._[0];
933
1052
  const subcommand = parsedArgs._[1];
934
- const namespacedCommands = ["schema", "db", "dev", "auth"];
1053
+ const namespacedCommands = ["schema", "db", "dev", "auth", "doctor"];
935
1054
  if (!command || parsedArgs["--help"] && !namespacedCommands.includes(command)) {
936
1055
  printHelp();
937
1056
  return;
@@ -941,7 +1060,7 @@ ${chalk.green.bold("Examples")}
941
1060
  case "init":
942
1061
  await createRebaseApp(args);
943
1062
  break;
944
- case "generate-sdk":
1063
+ case "generate-sdk": {
945
1064
  const sdkArgs = arg(
946
1065
  {
947
1066
  "--collections-dir": String,
@@ -955,11 +1074,12 @@ ${chalk.green.bold("Examples")}
955
1074
  }
956
1075
  );
957
1076
  await generateSdkCommand({
958
- collectionsDir: sdkArgs["--collections-dir"] || "./shared/collections",
1077
+ collectionsDir: sdkArgs["--collections-dir"] || "./config/collections",
959
1078
  output: sdkArgs["--output"] || "./generated/sdk",
960
1079
  cwd: process.cwd()
961
1080
  });
962
1081
  break;
1082
+ }
963
1083
  case "schema":
964
1084
  await schemaCommand(effectiveSubcommand, args);
965
1085
  break;
@@ -972,6 +1092,9 @@ ${chalk.green.bold("Examples")}
972
1092
  case "auth":
973
1093
  await authCommand(effectiveSubcommand, args);
974
1094
  break;
1095
+ case "doctor":
1096
+ await doctorCommand(args);
1097
+ break;
975
1098
  default:
976
1099
  console.log(chalk.red(`Unknown command: ${command}`));
977
1100
  console.log("");
@@ -1008,6 +1131,9 @@ ${chalk.green.bold("Auth")}
1008
1131
  ${chalk.blue.bold("auth reset-password")} Reset a user's password
1009
1132
  ${chalk.blue.bold("auth")} ${chalk.gray("--help")} Show auth command help
1010
1133
 
1134
+ ${chalk.green.bold("Diagnostics")}
1135
+ ${chalk.blue.bold("doctor")} Detect schema drift between collections, schema, and DB
1136
+
1011
1137
  ${chalk.green.bold("Options")}
1012
1138
  ${chalk.blue("--version, -v")} Show version number
1013
1139
  ${chalk.blue("--help, -h")} Show this help message