@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.
- package/dist/commands/cli.test.d.ts +1 -0
- package/dist/commands/dev.test.d.ts +1 -0
- package/dist/commands/doctor.d.ts +1 -0
- package/dist/commands/generate_sdk.d.ts +1 -1
- package/dist/commands/init.test.d.ts +1 -0
- package/dist/index.cjs +232 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +232 -64
- package/dist/index.es.js.map +1 -1
- package/dist/utils/project.test.d.ts +1 -0
- package/package.json +67 -66
- package/templates/template/.env.template +22 -1
- package/templates/template/README.md +2 -2
- package/templates/template/backend/Dockerfile +6 -6
- package/templates/template/backend/functions/hello.ts +24 -6
- package/templates/template/backend/package.json +2 -2
- package/templates/template/backend/src/env.ts +52 -0
- package/templates/template/backend/src/index.ts +86 -34
- package/templates/template/backend/tsconfig.json +1 -1
- package/templates/template/config/collections/authors.ts +45 -0
- package/templates/template/config/collections/index.ts +5 -0
- package/templates/template/{shared → config}/collections/posts.ts +39 -1
- package/templates/template/config/collections/tags.ts +27 -0
- package/templates/template/{shared → config}/package.json +1 -1
- package/templates/template/docker-compose.yml +12 -0
- package/templates/template/frontend/Dockerfile +3 -3
- package/templates/template/frontend/package.json +2 -2
- package/templates/template/frontend/src/App.tsx +11 -12
- package/templates/template/frontend/src/main.tsx +2 -2
- package/templates/template/frontend/vite.config.ts +1 -1
- package/templates/template/pnpm-workspace.yaml +1 -1
- package/templates/template/scripts/example.ts +91 -0
- package/templates/template/shared/collections/index.ts +0 -3
- /package/templates/template/{shared → config}/index.ts +0 -0
- /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
|
|
85
|
-
const
|
|
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 (
|
|
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
|
-
"
|
|
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
|
-
|
|
200
|
-
|
|
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(
|
|
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, "
|
|
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 = {
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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 +
|
|
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"] || "./
|
|
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
|