@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
|
@@ -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: ./
|
|
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
|
|
94
|
-
const
|
|
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 (
|
|
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
|
-
"
|
|
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
|
-
|
|
209
|
-
|
|
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(
|
|
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, "
|
|
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 = {
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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 +
|
|
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"] || "./
|
|
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
|