@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
@@ -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,63 @@ 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
+ }
211
+ const versionCache = /* @__PURE__ */ new Map();
212
+ const getPackageVersion = async (pkgName) => {
213
+ if (versionCache.has(pkgName)) return versionCache.get(pkgName);
214
+ let versionToUse = cliVersion;
215
+ try {
216
+ const { stdout } = await execa("npm", ["--loglevel", "error", "info", `${pkgName}@${cliVersion}`, "version"]);
217
+ if (!stdout.trim()) throw new Error("Not found");
218
+ versionToUse = stdout.trim();
219
+ } catch {
220
+ try {
221
+ const tag = cliVersion.includes("canary") ? "canary" : "latest";
222
+ const { stdout } = await execa("npm", ["--loglevel", "error", "info", `${pkgName}@${tag}`, "version"]);
223
+ if (!stdout.trim()) throw new Error("Not found");
224
+ versionToUse = stdout.trim();
225
+ } catch {
226
+ try {
227
+ const { stdout } = await execa("npm", ["--loglevel", "error", "info", pkgName, "version"]);
228
+ versionToUse = stdout.trim() || "latest";
229
+ } catch {
230
+ versionToUse = "latest";
231
+ }
232
+ }
233
+ }
234
+ versionCache.set(pkgName, versionToUse);
235
+ return versionToUse;
236
+ };
237
+ const allPackages = /* @__PURE__ */ new Set();
238
+ const fileContents = /* @__PURE__ */ new Map();
205
239
  for (const file of filesToProcess) {
206
240
  const fullPath = path.resolve(options.targetDirectory, file);
207
241
  if (!fs.existsSync(fullPath)) continue;
208
- let content = fs.readFileSync(fullPath, "utf-8");
209
- content = content.replace(/\{\{PROJECT_NAME\}\}/g, options.projectName);
242
+ const content = fs.readFileSync(fullPath, "utf-8");
243
+ fileContents.set(fullPath, content);
244
+ const matches = [...content.matchAll(/"(@rebasepro\/[^"]+)":\s*"workspace:\*"/g)];
245
+ for (const match of matches) {
246
+ allPackages.add(match[1]);
247
+ }
248
+ }
249
+ console.log(chalk.gray(" Resolving package versions..."));
250
+ await Promise.all(Array.from(allPackages).map(getPackageVersion));
251
+ for (const [fullPath, originalContent] of fileContents.entries()) {
252
+ let content = originalContent.replace(/\{\{PROJECT_NAME\}\}/g, options.projectName);
253
+ const matches = [...content.matchAll(/"(@rebasepro\/[^"]+)":\s*"workspace:\*"/g)];
254
+ for (const match of matches) {
255
+ const pkgName = match[1];
256
+ const resolvedVersion = versionCache.get(pkgName) || "latest";
257
+ content = content.replace(new RegExp(`"${pkgName}":\\s*"workspace:\\*"`, "g"), `"${pkgName}": "${resolvedVersion}"`);
258
+ }
210
259
  fs.writeFileSync(fullPath, content, "utf-8");
211
260
  }
212
261
  }
@@ -318,7 +367,7 @@ Expected a default export of EntityCollection[] or an object with named collecti
318
367
  console.log(chalk.green.bold(" ✓ SDK generated successfully!"));
319
368
  console.log("");
320
369
  console.log(chalk.gray(" Usage:"));
321
- console.log(chalk.gray(` import { createRebaseClient } from '@rebasepro/client';`));
370
+ console.log(chalk.gray(" import { createRebaseClient } from '@rebasepro/client';"));
322
371
  console.log(chalk.gray(` import type { Database } from './${path.relative(cwd, path.join(resolvedOutput, "database.types"))}';`));
323
372
  console.log("");
324
373
  console.log(chalk.gray(" const rebase = createRebaseClient<Database>({"));
@@ -345,7 +394,7 @@ Expected a default export of EntityCollection[] or an object with named collecti
345
394
  }
346
395
  } catch {
347
396
  }
348
- if (fs.existsSync(path.join(dir, "backend")) && fs.existsSync(path.join(dir, "shared"))) {
397
+ if (fs.existsSync(path.join(dir, "backend")) && fs.existsSync(path.join(dir, "config"))) {
349
398
  return dir;
350
399
  }
351
400
  }
@@ -362,7 +411,10 @@ Expected a default export of EntityCollection[] or an object with named collecti
362
411
  if (!fs.existsSync(pkgPath)) return null;
363
412
  try {
364
413
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
365
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
414
+ const deps = {
415
+ ...pkg.dependencies,
416
+ ...pkg.devDependencies
417
+ };
366
418
  for (const dep of Object.keys(deps)) {
367
419
  if (dep.startsWith("@rebasepro/server-") && dep !== "@rebasepro/server-core") {
368
420
  return dep;
@@ -473,13 +525,13 @@ Expected a default export of EntityCollection[] or an object with named collecti
473
525
  console.error(chalk.red("✗ Could not find tsx binary."));
474
526
  process.exit(1);
475
527
  }
476
- await execa(tsxBin, [pluginCli, "schema", subcommand, ...rawArgs.slice(2)], {
528
+ await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
477
529
  cwd: backendDir,
478
530
  stdio: "inherit",
479
531
  env
480
532
  });
481
533
  } else {
482
- await execa("node", [pluginCli, "schema", subcommand, ...rawArgs.slice(2)], {
534
+ await execa("node", [pluginCli, ...rawArgs.slice(2)], {
483
535
  cwd: backendDir,
484
536
  stdio: "inherit",
485
537
  env
@@ -537,13 +589,13 @@ ${chalk.green.bold("generate Options")}
537
589
  console.error(chalk.red("✗ Could not find tsx binary."));
538
590
  process.exit(1);
539
591
  }
540
- await execa(tsxBin, [pluginCli, "db", subcommand, ...rawArgs.slice(2)], {
592
+ await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
541
593
  cwd: backendDir,
542
594
  stdio: "inherit",
543
595
  env
544
596
  });
545
597
  } else {
546
- await execa("node", [pluginCli, "db", subcommand, ...rawArgs.slice(2)], {
598
+ await execa("node", [pluginCli, ...rawArgs.slice(2)], {
547
599
  cwd: backendDir,
548
600
  stdio: "inherit",
549
601
  env
@@ -581,6 +633,27 @@ ${chalk.green.bold("Examples")}
581
633
  rebase db branch create feature_auth
582
634
  `);
583
635
  }
636
+ const DEV_PORT_FILENAME = ".rebase-dev-port";
637
+ function getProjectPort(projectRoot) {
638
+ let hash = 0;
639
+ for (let i = 0; i < projectRoot.length; i++) {
640
+ hash = (hash << 5) - hash + projectRoot.charCodeAt(i) | 0;
641
+ }
642
+ return 3001 + Math.abs(hash) % 999;
643
+ }
644
+ function resolveStartPort(projectRoot, explicitPort) {
645
+ if (explicitPort) return explicitPort;
646
+ if (process.env.PORT) return parseInt(process.env.PORT, 10);
647
+ try {
648
+ const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
649
+ if (fs.existsSync(portFile)) {
650
+ const saved = parseInt(fs.readFileSync(portFile, "utf-8").trim(), 10);
651
+ if (saved > 0 && saved < 65536) return saved;
652
+ }
653
+ } catch {
654
+ }
655
+ return getProjectPort(projectRoot);
656
+ }
584
657
  async function devCommand(rawArgs) {
585
658
  const args = arg(
586
659
  {
@@ -608,6 +681,7 @@ ${chalk.green.bold("Examples")}
608
681
  const frontendDir = findFrontendDir(projectRoot);
609
682
  const backendOnly = args["--backend-only"] || false;
610
683
  const frontendOnly = args["--frontend-only"] || false;
684
+ const startPort = resolveStartPort(projectRoot, args["--port"]);
611
685
  console.log("");
612
686
  console.log(chalk.bold(" 🚀 Rebase Dev Server"));
613
687
  console.log("");
@@ -616,6 +690,7 @@ ${chalk.green.bold("Examples")}
616
690
  let backendUrl = "";
617
691
  let debounceSummary = null;
618
692
  let bannerPrinted = false;
693
+ let resolvedBackendPort = null;
619
694
  const stripAnsi = (str) => str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
620
695
  function printSummary() {
621
696
  if (!frontendUrl || !backendUrl) return;
@@ -628,7 +703,7 @@ ${chalk.green.bold("Examples")}
628
703
  console.log(chalk.cyan("│ ✨ Rebase Admin App is ready! │"));
629
704
  const cleanUrl = stripAnsi(frontendUrl);
630
705
  const paddedUrl = cleanUrl.padEnd(40);
631
- console.log(chalk.cyan(`│ 👉 Frontend URL: `) + chalk.white(paddedUrl) + chalk.cyan(`│`));
706
+ console.log(chalk.cyan("│ 👉 Frontend URL: ") + chalk.white(paddedUrl) + chalk.cyan("│"));
632
707
  console.log(chalk.cyan("│ │"));
633
708
  console.log(chalk.cyan("└────────────────────────────────────────────────────────────┘"));
634
709
  console.log("");
@@ -636,15 +711,74 @@ ${chalk.green.bold("Examples")}
636
711
  }, 500);
637
712
  }
638
713
  const cleanup = () => {
714
+ try {
715
+ const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
716
+ if (fs.existsSync(portFile)) fs.unlinkSync(portFile);
717
+ const urlFile = path.join(projectRoot, ".rebase-dev-url");
718
+ if (fs.existsSync(urlFile)) fs.unlinkSync(urlFile);
719
+ } catch {
720
+ }
639
721
  children.forEach((child) => {
640
- if (!child.killed) {
641
- child.kill("SIGTERM");
722
+ if (child.pid && !child.killed) {
723
+ try {
724
+ if (process.platform === "win32") {
725
+ execa.commandSync(`taskkill /pid ${child.pid} /T /F`);
726
+ } else {
727
+ process.kill(-child.pid, "SIGKILL");
728
+ }
729
+ } catch (e) {
730
+ try {
731
+ child.kill("SIGKILL");
732
+ } catch (err) {
733
+ }
734
+ }
642
735
  }
643
736
  });
644
737
  process.exit(0);
645
738
  };
646
739
  process.on("SIGINT", cleanup);
647
740
  process.on("SIGTERM", cleanup);
741
+ function startFrontend(backendPort) {
742
+ if (!frontendDir) return;
743
+ console.log(` ${chalk.magenta("▶")} Frontend: ${chalk.gray(frontendDir)}`);
744
+ const frontendEnv = { ...process.env };
745
+ if (backendPort) {
746
+ frontendEnv.VITE_API_URL = `http://localhost:${backendPort}`;
747
+ console.log(` ${chalk.gray("↳ VITE_API_URL")} = ${chalk.white(`http://localhost:${backendPort}`)}`);
748
+ }
749
+ const frontendChild = execa(
750
+ "pnpm",
751
+ ["run", "dev"],
752
+ {
753
+ cwd: frontendDir,
754
+ stdio: ["inherit", "pipe", "pipe"],
755
+ env: frontendEnv,
756
+ shell: true,
757
+ detached: process.platform !== "win32"
758
+ }
759
+ );
760
+ frontendChild.catch(() => {
761
+ });
762
+ frontendChild.stdout?.on("data", (data) => {
763
+ const lines = data.toString().split("\n").filter(Boolean);
764
+ lines.forEach((line) => {
765
+ console.log(`${chalk.magenta.bold("[admin]")} ${line}`);
766
+ const cleanLine = stripAnsi(line);
767
+ const urlMatch = cleanLine.match(/(http:\/\/(?:localhost|127\.0\.0\.1):\d+)/);
768
+ if (cleanLine.includes("Local:") && urlMatch) {
769
+ frontendUrl = urlMatch[1];
770
+ printSummary();
771
+ }
772
+ });
773
+ });
774
+ frontendChild.stderr?.on("data", (data) => {
775
+ const lines = data.toString().split("\n").filter(Boolean);
776
+ lines.forEach((line) => {
777
+ console.log(`${chalk.magenta.bold("[admin]")} ${line}`);
778
+ });
779
+ });
780
+ children.push(frontendChild);
781
+ }
648
782
  if (!frontendOnly && backendDir) {
649
783
  const tsxBin = resolveTsx(projectRoot);
650
784
  if (!tsxBin) {
@@ -657,22 +791,19 @@ ${chalk.green.bold("Examples")}
657
791
  if (envFile) {
658
792
  env.DOTENV_CONFIG_PATH = envFile;
659
793
  }
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
- ];
794
+ env.PORT = String(startPort);
667
795
  console.log(` ${chalk.cyan("▶")} Backend: ${chalk.gray(backendDir)}`);
796
+ console.log(` ${chalk.gray("↳ PORT")} = ${chalk.white(String(startPort))}`);
797
+ let frontendLaunched = false;
668
798
  const backendChild = execa(
669
799
  tsxBin,
670
- ["watch", ...watchDirs, "--conditions", "development", "src/index.ts"],
800
+ ["watch", `--watch="${path.join("..", "config", "**", "*")}"`, "--conditions", "development", "src/index.ts"],
671
801
  {
672
802
  cwd: backendDir,
673
803
  stdio: ["inherit", "pipe", "pipe"],
674
804
  env,
675
- shell: true
805
+ shell: true,
806
+ detached: process.platform !== "win32"
676
807
  }
677
808
  );
678
809
  backendChild.catch(() => {
@@ -682,9 +813,19 @@ ${chalk.green.bold("Examples")}
682
813
  lines.forEach((line) => {
683
814
  console.log(`${chalk.cyan.bold("[backend]")} ${line}`);
684
815
  const cleanLine = stripAnsi(line);
685
- if (cleanLine.includes("Server running at http://")) {
816
+ const serverMatch = cleanLine.match(/Server running at http:\/\/(?:localhost|127\.0\.0\.1):(\d+)/);
817
+ if (serverMatch) {
818
+ resolvedBackendPort = parseInt(serverMatch[1], 10);
686
819
  backendUrl = "started";
687
820
  printSummary();
821
+ const urlFile = path.join(projectRoot, ".rebase-dev-url");
822
+ fs.writeFileSync(urlFile, `http://localhost:${resolvedBackendPort}`, "utf-8");
823
+ const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
824
+ fs.writeFileSync(portFile, String(resolvedBackendPort), "utf-8");
825
+ if (!backendOnly && frontendDir && !frontendLaunched) {
826
+ frontendLaunched = true;
827
+ startFrontend(resolvedBackendPort);
828
+ }
688
829
  }
689
830
  });
690
831
  });
@@ -698,39 +839,8 @@ ${chalk.green.bold("Examples")}
698
839
  } else if (!frontendOnly && !backendDir) {
699
840
  console.warn(chalk.yellow(" ⚠ No backend/ directory found, skipping backend."));
700
841
  }
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);
842
+ if (!backendOnly && frontendDir && (frontendOnly || !backendDir)) {
843
+ startFrontend(null);
734
844
  } else if (!backendOnly && !frontendDir) {
735
845
  console.warn(chalk.yellow(" ⚠ No frontend/ directory found, skipping frontend."));
736
846
  }
@@ -759,11 +869,19 @@ ${chalk.green.bold("Usage")}
759
869
  ${chalk.green.bold("Options")}
760
870
  ${chalk.blue("--backend-only, -b")} Only start the backend server
761
871
  ${chalk.blue("--frontend-only, -f")} Only start the frontend server
762
- ${chalk.blue("--port, -p")} Backend port (default: 3001)
872
+ ${chalk.blue("--port, -p")} Backend port (default: auto-detected per project)
763
873
 
764
874
  ${chalk.green.bold("Description")}
765
- Starts both the backend (tsx watch + Express) and frontend (Vite)
875
+ Starts both the backend (tsx watch + Hono) and frontend (Vite)
766
876
  dev servers concurrently with color-coded output prefixes.
877
+
878
+ Each project automatically receives a unique default port derived
879
+ from its directory path, preventing collisions when running multiple
880
+ Rebase instances simultaneously.
881
+
882
+ If the assigned port is already in use, the server will automatically
883
+ try the next available port. The frontend is started only after the
884
+ backend is ready, and VITE_API_URL is injected automatically.
767
885
  `);
768
886
  }
769
887
  async function authCommand(subcommand, rawArgs) {
@@ -900,6 +1018,49 @@ ${chalk.green.bold("Examples")}
900
1018
  rebase auth reset-password --email user@example.com --password MyNewPass!
901
1019
  `);
902
1020
  }
1021
+ async function doctorCommand(rawArgs) {
1022
+ const projectRoot = requireProjectRoot();
1023
+ const backendDir = requireBackendDir(projectRoot);
1024
+ const activePlugin = getActiveBackendPlugin(backendDir);
1025
+ if (!activePlugin) {
1026
+ console.error(chalk.red("✗ Could not detect an active database plugin."));
1027
+ console.error(chalk.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
1028
+ process.exit(1);
1029
+ }
1030
+ const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
1031
+ if (!pluginCli) {
1032
+ console.error(chalk.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
1033
+ process.exit(1);
1034
+ }
1035
+ const envFile = findEnvFile(projectRoot);
1036
+ const env = { ...process.env };
1037
+ if (envFile) {
1038
+ env.DOTENV_CONFIG_PATH = envFile;
1039
+ }
1040
+ try {
1041
+ const isTs = pluginCli.endsWith(".ts");
1042
+ if (isTs) {
1043
+ const tsxBin = resolveTsx(projectRoot);
1044
+ if (!tsxBin) {
1045
+ console.error(chalk.red("✗ Could not find tsx binary."));
1046
+ process.exit(1);
1047
+ }
1048
+ await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
1049
+ cwd: backendDir,
1050
+ stdio: "inherit",
1051
+ env
1052
+ });
1053
+ } else {
1054
+ await execa("node", [pluginCli, ...rawArgs.slice(2)], {
1055
+ cwd: backendDir,
1056
+ stdio: "inherit",
1057
+ env
1058
+ });
1059
+ }
1060
+ } catch {
1061
+ process.exit(1);
1062
+ }
1063
+ }
903
1064
  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
1065
  const __dirname$1 = path.dirname(__filename$1);
905
1066
  function getVersion() {
@@ -931,7 +1092,7 @@ ${chalk.green.bold("Examples")}
931
1092
  }
932
1093
  const command = parsedArgs._[0];
933
1094
  const subcommand = parsedArgs._[1];
934
- const namespacedCommands = ["schema", "db", "dev", "auth"];
1095
+ const namespacedCommands = ["schema", "db", "dev", "auth", "doctor"];
935
1096
  if (!command || parsedArgs["--help"] && !namespacedCommands.includes(command)) {
936
1097
  printHelp();
937
1098
  return;
@@ -941,7 +1102,7 @@ ${chalk.green.bold("Examples")}
941
1102
  case "init":
942
1103
  await createRebaseApp(args);
943
1104
  break;
944
- case "generate-sdk":
1105
+ case "generate-sdk": {
945
1106
  const sdkArgs = arg(
946
1107
  {
947
1108
  "--collections-dir": String,
@@ -955,11 +1116,12 @@ ${chalk.green.bold("Examples")}
955
1116
  }
956
1117
  );
957
1118
  await generateSdkCommand({
958
- collectionsDir: sdkArgs["--collections-dir"] || "./shared/collections",
1119
+ collectionsDir: sdkArgs["--collections-dir"] || "./config/collections",
959
1120
  output: sdkArgs["--output"] || "./generated/sdk",
960
1121
  cwd: process.cwd()
961
1122
  });
962
1123
  break;
1124
+ }
963
1125
  case "schema":
964
1126
  await schemaCommand(effectiveSubcommand, args);
965
1127
  break;
@@ -972,6 +1134,9 @@ ${chalk.green.bold("Examples")}
972
1134
  case "auth":
973
1135
  await authCommand(effectiveSubcommand, args);
974
1136
  break;
1137
+ case "doctor":
1138
+ await doctorCommand(args);
1139
+ break;
975
1140
  default:
976
1141
  console.log(chalk.red(`Unknown command: ${command}`));
977
1142
  console.log("");
@@ -1008,6 +1173,9 @@ ${chalk.green.bold("Auth")}
1008
1173
  ${chalk.blue.bold("auth reset-password")} Reset a user's password
1009
1174
  ${chalk.blue.bold("auth")} ${chalk.gray("--help")} Show auth command help
1010
1175
 
1176
+ ${chalk.green.bold("Diagnostics")}
1177
+ ${chalk.blue.bold("doctor")} Detect schema drift between collections, schema, and DB
1178
+
1011
1179
  ${chalk.green.bold("Options")}
1012
1180
  ${chalk.blue("--version, -v")} Show version number
1013
1181
  ${chalk.blue("--help, -h")} Show this help message