@rebasepro/cli 0.0.1-canary.4d4fb3e → 0.0.1-canary.629af03
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.d.ts +2 -0
- package/dist/commands/init.test.d.ts +1 -0
- package/dist/index.cjs +294 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +294 -73
- 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/drizzle.config.ts +15 -3
- 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 +21 -101
- package/templates/template/frontend/src/main.tsx +2 -2
- package/templates/template/frontend/vite.config.ts +32 -3
- package/templates/template/package.json +5 -4
- 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.
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.cjs
CHANGED
|
@@ -89,16 +89,31 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
|
|
|
89
89
|
default: true
|
|
90
90
|
});
|
|
91
91
|
}
|
|
92
|
+
questions.push({
|
|
93
|
+
type: "input",
|
|
94
|
+
name: "databaseUrl",
|
|
95
|
+
message: "Enter your PostgreSQL database connection string (leave blank to use a local default):",
|
|
96
|
+
default: ""
|
|
97
|
+
});
|
|
98
|
+
questions.push({
|
|
99
|
+
type: "confirm",
|
|
100
|
+
name: "introspect",
|
|
101
|
+
message: "Would you like to introspect this database to automatically generate collections?",
|
|
102
|
+
default: true,
|
|
103
|
+
when: (answers2) => !!answers2.databaseUrl?.trim()
|
|
104
|
+
});
|
|
92
105
|
const answers = await inquirer.prompt(questions);
|
|
93
|
-
const
|
|
94
|
-
const
|
|
106
|
+
const targetDirectory = path.resolve(process.cwd(), nameArg || answers.projectName);
|
|
107
|
+
const projectName = path.basename(targetDirectory);
|
|
95
108
|
const templateDirectory = path.resolve(cliRoot, "templates", "template");
|
|
96
109
|
return {
|
|
97
110
|
projectName,
|
|
98
111
|
git: args["--git"] || answers.git || false,
|
|
99
112
|
installDeps: args["--install"] || answers.installDeps || false,
|
|
100
113
|
targetDirectory,
|
|
101
|
-
templateDirectory
|
|
114
|
+
templateDirectory,
|
|
115
|
+
databaseUrl: answers.databaseUrl?.trim() || void 0,
|
|
116
|
+
introspect: answers.introspect || false
|
|
102
117
|
};
|
|
103
118
|
}
|
|
104
119
|
async function createProject(options) {
|
|
@@ -136,20 +151,27 @@ ${chalk.bold("Rebase")} — Create a new project 🚀
|
|
|
136
151
|
if (fs.existsSync(envTemplatePath) && !fs.existsSync(envPath)) {
|
|
137
152
|
fs.renameSync(envTemplatePath, envPath);
|
|
138
153
|
const jwtSecret = crypto.randomBytes(32).toString("hex");
|
|
139
|
-
const dbPassword = crypto.randomBytes(16).toString("hex");
|
|
140
154
|
let envContent = fs.readFileSync(envPath, "utf-8");
|
|
141
|
-
envContent = envContent.replace(
|
|
142
|
-
"postgresql://rebase:password@localhost:5432/rebase",
|
|
143
|
-
`postgresql://rebase:${dbPassword}@localhost:5432/rebase`
|
|
144
|
-
);
|
|
145
155
|
envContent = envContent.replace(
|
|
146
156
|
"change-this-to-a-secure-random-string",
|
|
147
157
|
jwtSecret
|
|
148
158
|
);
|
|
149
|
-
|
|
159
|
+
if (options.databaseUrl) {
|
|
160
|
+
envContent = envContent.replace(
|
|
161
|
+
"postgresql://rebase:password@localhost:5432/rebase",
|
|
162
|
+
options.databaseUrl
|
|
163
|
+
);
|
|
164
|
+
} else {
|
|
165
|
+
const dbPassword = crypto.randomBytes(16).toString("hex");
|
|
166
|
+
envContent = envContent.replace(
|
|
167
|
+
"postgresql://rebase:password@localhost:5432/rebase",
|
|
168
|
+
`postgresql://rebase:${dbPassword}@localhost:5432/rebase`
|
|
169
|
+
);
|
|
170
|
+
envContent += `
|
|
150
171
|
# Docker Compose Database Password
|
|
151
172
|
POSTGRES_PASSWORD=${dbPassword}
|
|
152
173
|
`;
|
|
174
|
+
}
|
|
153
175
|
fs.writeFileSync(envPath, envContent, "utf-8");
|
|
154
176
|
}
|
|
155
177
|
if (options.git) {
|
|
@@ -173,6 +195,26 @@ POSTGRES_PASSWORD=${dbPassword}
|
|
|
173
195
|
console.warn(chalk.yellow(" Warning: Failed to install dependencies. You may need to run `pnpm install` manually."));
|
|
174
196
|
}
|
|
175
197
|
}
|
|
198
|
+
if (options.introspect) {
|
|
199
|
+
console.log("");
|
|
200
|
+
if (options.installDeps) {
|
|
201
|
+
console.log(chalk.gray(" Introspecting database and generating collections..."));
|
|
202
|
+
console.log("");
|
|
203
|
+
try {
|
|
204
|
+
await execa("pnpm", ["exec", "rebase", "schema", "introspect"], {
|
|
205
|
+
cwd: options.targetDirectory,
|
|
206
|
+
stdio: "inherit"
|
|
207
|
+
});
|
|
208
|
+
console.log(chalk.green(" Database successfully introspected!"));
|
|
209
|
+
} catch {
|
|
210
|
+
console.warn(chalk.yellow(" Warning: Failed to introspect database automatically."));
|
|
211
|
+
console.warn(chalk.yellow(" You can run `pnpm exec rebase schema introspect` manually after setup."));
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
console.warn(chalk.yellow(" Skipping introspection because dependencies were not installed."));
|
|
215
|
+
console.warn(chalk.yellow(" Run `pnpm install` then `pnpm exec rebase schema introspect` manually."));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
176
218
|
console.log("");
|
|
177
219
|
console.log(`${chalk.green.bold("✓")} Project ${chalk.bold(options.projectName)} created successfully!`);
|
|
178
220
|
console.log("");
|
|
@@ -183,12 +225,19 @@ POSTGRES_PASSWORD=${dbPassword}
|
|
|
183
225
|
console.log(` ${chalk.cyan("pnpm install")}`);
|
|
184
226
|
}
|
|
185
227
|
console.log("");
|
|
186
|
-
|
|
187
|
-
|
|
228
|
+
if (options.databaseUrl) {
|
|
229
|
+
console.log(chalk.gray(" # Your database is configured! Start the dev server:"));
|
|
230
|
+
} else {
|
|
231
|
+
console.log(chalk.gray(" # A local database configuration has been generated in .env"));
|
|
232
|
+
console.log(chalk.gray(" # If using the included docker-compose.yml, start it with:"));
|
|
233
|
+
console.log(` ${chalk.cyan("docker compose up -d")}`);
|
|
234
|
+
console.log("");
|
|
235
|
+
console.log(chalk.gray(" # Then start the dev server:"));
|
|
236
|
+
}
|
|
188
237
|
console.log("");
|
|
189
238
|
console.log(` ${chalk.cyan("pnpm dev")}`);
|
|
190
239
|
console.log("");
|
|
191
|
-
console.log(chalk.gray("This starts both the backend (
|
|
240
|
+
console.log(chalk.gray("This starts both the backend (Hono + PostgreSQL)") + chalk.gray(" and the frontend (Vite + React) concurrently."));
|
|
192
241
|
console.log("");
|
|
193
242
|
console.log(chalk.gray("Docs: https://rebase.pro/docs"));
|
|
194
243
|
console.log(chalk.gray("GitHub: https://github.com/rebasepro/rebase"));
|
|
@@ -199,14 +248,63 @@ POSTGRES_PASSWORD=${dbPassword}
|
|
|
199
248
|
"package.json",
|
|
200
249
|
"frontend/package.json",
|
|
201
250
|
"backend/package.json",
|
|
202
|
-
"
|
|
251
|
+
"config/package.json",
|
|
203
252
|
"frontend/index.html"
|
|
204
253
|
];
|
|
254
|
+
const packageJsonPath = path.resolve(cliRoot, "package.json");
|
|
255
|
+
let cliVersion = "latest";
|
|
256
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
257
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
258
|
+
cliVersion = pkg.version || "latest";
|
|
259
|
+
}
|
|
260
|
+
const versionCache = /* @__PURE__ */ new Map();
|
|
261
|
+
const getPackageVersion = async (pkgName) => {
|
|
262
|
+
if (versionCache.has(pkgName)) return versionCache.get(pkgName);
|
|
263
|
+
let versionToUse = cliVersion;
|
|
264
|
+
try {
|
|
265
|
+
const { stdout } = await execa("npm", ["--loglevel", "error", "info", `${pkgName}@${cliVersion}`, "version"]);
|
|
266
|
+
if (!stdout.trim()) throw new Error("Not found");
|
|
267
|
+
versionToUse = stdout.trim();
|
|
268
|
+
} catch {
|
|
269
|
+
try {
|
|
270
|
+
const tag = cliVersion.includes("canary") ? "canary" : "latest";
|
|
271
|
+
const { stdout } = await execa("npm", ["--loglevel", "error", "info", `${pkgName}@${tag}`, "version"]);
|
|
272
|
+
if (!stdout.trim()) throw new Error("Not found");
|
|
273
|
+
versionToUse = stdout.trim();
|
|
274
|
+
} catch {
|
|
275
|
+
try {
|
|
276
|
+
const { stdout } = await execa("npm", ["--loglevel", "error", "info", pkgName, "version"]);
|
|
277
|
+
versionToUse = stdout.trim() || "latest";
|
|
278
|
+
} catch {
|
|
279
|
+
versionToUse = "latest";
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
versionCache.set(pkgName, versionToUse);
|
|
284
|
+
return versionToUse;
|
|
285
|
+
};
|
|
286
|
+
const allPackages = /* @__PURE__ */ new Set();
|
|
287
|
+
const fileContents = /* @__PURE__ */ new Map();
|
|
205
288
|
for (const file of filesToProcess) {
|
|
206
289
|
const fullPath = path.resolve(options.targetDirectory, file);
|
|
207
290
|
if (!fs.existsSync(fullPath)) continue;
|
|
208
|
-
|
|
209
|
-
|
|
291
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
292
|
+
fileContents.set(fullPath, content);
|
|
293
|
+
const matches = [...content.matchAll(/"(@rebasepro\/[^"]+)":\s*"workspace:\*"/g)];
|
|
294
|
+
for (const match of matches) {
|
|
295
|
+
allPackages.add(match[1]);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
console.log(chalk.gray(" Resolving package versions..."));
|
|
299
|
+
await Promise.all(Array.from(allPackages).map(getPackageVersion));
|
|
300
|
+
for (const [fullPath, originalContent] of fileContents.entries()) {
|
|
301
|
+
let content = originalContent.replace(/\{\{PROJECT_NAME\}\}/g, options.projectName);
|
|
302
|
+
const matches = [...content.matchAll(/"(@rebasepro\/[^"]+)":\s*"workspace:\*"/g)];
|
|
303
|
+
for (const match of matches) {
|
|
304
|
+
const pkgName = match[1];
|
|
305
|
+
const resolvedVersion = versionCache.get(pkgName) || "latest";
|
|
306
|
+
content = content.replace(new RegExp(`"${pkgName}":\\s*"workspace:\\*"`, "g"), `"${pkgName}": "${resolvedVersion}"`);
|
|
307
|
+
}
|
|
210
308
|
fs.writeFileSync(fullPath, content, "utf-8");
|
|
211
309
|
}
|
|
212
310
|
}
|
|
@@ -318,7 +416,7 @@ Expected a default export of EntityCollection[] or an object with named collecti
|
|
|
318
416
|
console.log(chalk.green.bold(" ✓ SDK generated successfully!"));
|
|
319
417
|
console.log("");
|
|
320
418
|
console.log(chalk.gray(" Usage:"));
|
|
321
|
-
console.log(chalk.gray(
|
|
419
|
+
console.log(chalk.gray(" import { createRebaseClient } from '@rebasepro/client';"));
|
|
322
420
|
console.log(chalk.gray(` import type { Database } from './${path.relative(cwd, path.join(resolvedOutput, "database.types"))}';`));
|
|
323
421
|
console.log("");
|
|
324
422
|
console.log(chalk.gray(" const rebase = createRebaseClient<Database>({"));
|
|
@@ -345,7 +443,7 @@ Expected a default export of EntityCollection[] or an object with named collecti
|
|
|
345
443
|
}
|
|
346
444
|
} catch {
|
|
347
445
|
}
|
|
348
|
-
if (fs.existsSync(path.join(dir, "backend")) && fs.existsSync(path.join(dir, "
|
|
446
|
+
if (fs.existsSync(path.join(dir, "backend")) && fs.existsSync(path.join(dir, "config"))) {
|
|
349
447
|
return dir;
|
|
350
448
|
}
|
|
351
449
|
}
|
|
@@ -362,7 +460,10 @@ Expected a default export of EntityCollection[] or an object with named collecti
|
|
|
362
460
|
if (!fs.existsSync(pkgPath)) return null;
|
|
363
461
|
try {
|
|
364
462
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
365
|
-
const deps = {
|
|
463
|
+
const deps = {
|
|
464
|
+
...pkg.dependencies,
|
|
465
|
+
...pkg.devDependencies
|
|
466
|
+
};
|
|
366
467
|
for (const dep of Object.keys(deps)) {
|
|
367
468
|
if (dep.startsWith("@rebasepro/server-") && dep !== "@rebasepro/server-core") {
|
|
368
469
|
return dep;
|
|
@@ -473,13 +574,13 @@ Expected a default export of EntityCollection[] or an object with named collecti
|
|
|
473
574
|
console.error(chalk.red("✗ Could not find tsx binary."));
|
|
474
575
|
process.exit(1);
|
|
475
576
|
}
|
|
476
|
-
await execa(tsxBin, [pluginCli,
|
|
577
|
+
await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
|
|
477
578
|
cwd: backendDir,
|
|
478
579
|
stdio: "inherit",
|
|
479
580
|
env
|
|
480
581
|
});
|
|
481
582
|
} else {
|
|
482
|
-
await execa("node", [pluginCli,
|
|
583
|
+
await execa("node", [pluginCli, ...rawArgs.slice(2)], {
|
|
483
584
|
cwd: backendDir,
|
|
484
585
|
stdio: "inherit",
|
|
485
586
|
env
|
|
@@ -499,11 +600,15 @@ ${chalk.green.bold("Usage")}
|
|
|
499
600
|
${chalk.green.bold("Commands")}
|
|
500
601
|
${chalk.gray("(Commands are provided by your active database driver plugin)")}
|
|
501
602
|
${chalk.blue.bold("generate")} Generate Schema from collection definitions
|
|
603
|
+
${chalk.blue.bold("introspect")} Introspect an existing database to generate collection definitions
|
|
502
604
|
|
|
503
605
|
${chalk.green.bold("generate Options")}
|
|
504
606
|
${chalk.blue("--collections, -c")} Path to collections directory
|
|
505
607
|
${chalk.blue("--output, -o")} Output path for generated schema
|
|
506
608
|
${chalk.blue("--watch, -w")} Watch for changes and regenerate automatically
|
|
609
|
+
|
|
610
|
+
${chalk.green.bold("introspect Options")}
|
|
611
|
+
${chalk.blue("--output, -o")} Output directory for generated collection files
|
|
507
612
|
`);
|
|
508
613
|
}
|
|
509
614
|
async function dbCommand(subcommand, rawArgs) {
|
|
@@ -537,13 +642,13 @@ ${chalk.green.bold("generate Options")}
|
|
|
537
642
|
console.error(chalk.red("✗ Could not find tsx binary."));
|
|
538
643
|
process.exit(1);
|
|
539
644
|
}
|
|
540
|
-
await execa(tsxBin, [pluginCli,
|
|
645
|
+
await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
|
|
541
646
|
cwd: backendDir,
|
|
542
647
|
stdio: "inherit",
|
|
543
648
|
env
|
|
544
649
|
});
|
|
545
650
|
} else {
|
|
546
|
-
await execa("node", [pluginCli,
|
|
651
|
+
await execa("node", [pluginCli, ...rawArgs.slice(2)], {
|
|
547
652
|
cwd: backendDir,
|
|
548
653
|
stdio: "inherit",
|
|
549
654
|
env
|
|
@@ -581,6 +686,27 @@ ${chalk.green.bold("Examples")}
|
|
|
581
686
|
rebase db branch create feature_auth
|
|
582
687
|
`);
|
|
583
688
|
}
|
|
689
|
+
const DEV_PORT_FILENAME = ".rebase-dev-port";
|
|
690
|
+
function getProjectPort(projectRoot) {
|
|
691
|
+
let hash = 0;
|
|
692
|
+
for (let i = 0; i < projectRoot.length; i++) {
|
|
693
|
+
hash = (hash << 5) - hash + projectRoot.charCodeAt(i) | 0;
|
|
694
|
+
}
|
|
695
|
+
return 3001 + Math.abs(hash) % 999;
|
|
696
|
+
}
|
|
697
|
+
function resolveStartPort(projectRoot, explicitPort) {
|
|
698
|
+
if (explicitPort) return explicitPort;
|
|
699
|
+
if (process.env.PORT) return parseInt(process.env.PORT, 10);
|
|
700
|
+
try {
|
|
701
|
+
const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
|
|
702
|
+
if (fs.existsSync(portFile)) {
|
|
703
|
+
const saved = parseInt(fs.readFileSync(portFile, "utf-8").trim(), 10);
|
|
704
|
+
if (saved > 0 && saved < 65536) return saved;
|
|
705
|
+
}
|
|
706
|
+
} catch {
|
|
707
|
+
}
|
|
708
|
+
return getProjectPort(projectRoot);
|
|
709
|
+
}
|
|
584
710
|
async function devCommand(rawArgs) {
|
|
585
711
|
const args = arg(
|
|
586
712
|
{
|
|
@@ -608,6 +734,7 @@ ${chalk.green.bold("Examples")}
|
|
|
608
734
|
const frontendDir = findFrontendDir(projectRoot);
|
|
609
735
|
const backendOnly = args["--backend-only"] || false;
|
|
610
736
|
const frontendOnly = args["--frontend-only"] || false;
|
|
737
|
+
const startPort = resolveStartPort(projectRoot, args["--port"]);
|
|
611
738
|
console.log("");
|
|
612
739
|
console.log(chalk.bold(" 🚀 Rebase Dev Server"));
|
|
613
740
|
console.log("");
|
|
@@ -616,6 +743,7 @@ ${chalk.green.bold("Examples")}
|
|
|
616
743
|
let backendUrl = "";
|
|
617
744
|
let debounceSummary = null;
|
|
618
745
|
let bannerPrinted = false;
|
|
746
|
+
let resolvedBackendPort = null;
|
|
619
747
|
const stripAnsi = (str) => str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
|
|
620
748
|
function printSummary() {
|
|
621
749
|
if (!frontendUrl || !backendUrl) return;
|
|
@@ -628,7 +756,7 @@ ${chalk.green.bold("Examples")}
|
|
|
628
756
|
console.log(chalk.cyan("│ ✨ Rebase Admin App is ready! │"));
|
|
629
757
|
const cleanUrl = stripAnsi(frontendUrl);
|
|
630
758
|
const paddedUrl = cleanUrl.padEnd(40);
|
|
631
|
-
console.log(chalk.cyan(
|
|
759
|
+
console.log(chalk.cyan("│ 👉 Frontend URL: ") + chalk.white(paddedUrl) + chalk.cyan("│"));
|
|
632
760
|
console.log(chalk.cyan("│ │"));
|
|
633
761
|
console.log(chalk.cyan("└────────────────────────────────────────────────────────────┘"));
|
|
634
762
|
console.log("");
|
|
@@ -636,15 +764,74 @@ ${chalk.green.bold("Examples")}
|
|
|
636
764
|
}, 500);
|
|
637
765
|
}
|
|
638
766
|
const cleanup = () => {
|
|
767
|
+
try {
|
|
768
|
+
const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
|
|
769
|
+
if (fs.existsSync(portFile)) fs.unlinkSync(portFile);
|
|
770
|
+
const urlFile = path.join(projectRoot, ".rebase-dev-url");
|
|
771
|
+
if (fs.existsSync(urlFile)) fs.unlinkSync(urlFile);
|
|
772
|
+
} catch {
|
|
773
|
+
}
|
|
639
774
|
children.forEach((child) => {
|
|
640
|
-
if (!child.killed) {
|
|
641
|
-
|
|
775
|
+
if (child.pid && !child.killed) {
|
|
776
|
+
try {
|
|
777
|
+
if (process.platform === "win32") {
|
|
778
|
+
execa.commandSync(`taskkill /pid ${child.pid} /T /F`);
|
|
779
|
+
} else {
|
|
780
|
+
process.kill(-child.pid, "SIGKILL");
|
|
781
|
+
}
|
|
782
|
+
} catch (e) {
|
|
783
|
+
try {
|
|
784
|
+
child.kill("SIGKILL");
|
|
785
|
+
} catch (err) {
|
|
786
|
+
}
|
|
787
|
+
}
|
|
642
788
|
}
|
|
643
789
|
});
|
|
644
790
|
process.exit(0);
|
|
645
791
|
};
|
|
646
792
|
process.on("SIGINT", cleanup);
|
|
647
793
|
process.on("SIGTERM", cleanup);
|
|
794
|
+
function startFrontend(backendPort) {
|
|
795
|
+
if (!frontendDir) return;
|
|
796
|
+
console.log(` ${chalk.magenta("▶")} Frontend: ${chalk.gray(frontendDir)}`);
|
|
797
|
+
const frontendEnv = { ...process.env };
|
|
798
|
+
if (backendPort) {
|
|
799
|
+
frontendEnv.VITE_API_URL = `http://localhost:${backendPort}`;
|
|
800
|
+
console.log(` ${chalk.gray("↳ VITE_API_URL")} = ${chalk.white(`http://localhost:${backendPort}`)}`);
|
|
801
|
+
}
|
|
802
|
+
const frontendChild = execa(
|
|
803
|
+
"pnpm",
|
|
804
|
+
["run", "dev"],
|
|
805
|
+
{
|
|
806
|
+
cwd: frontendDir,
|
|
807
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
808
|
+
env: frontendEnv,
|
|
809
|
+
shell: true,
|
|
810
|
+
detached: process.platform !== "win32"
|
|
811
|
+
}
|
|
812
|
+
);
|
|
813
|
+
frontendChild.catch(() => {
|
|
814
|
+
});
|
|
815
|
+
frontendChild.stdout?.on("data", (data) => {
|
|
816
|
+
const lines = data.toString().split("\n").filter(Boolean);
|
|
817
|
+
lines.forEach((line) => {
|
|
818
|
+
console.log(`${chalk.magenta.bold("[admin]")} ${line}`);
|
|
819
|
+
const cleanLine = stripAnsi(line);
|
|
820
|
+
const urlMatch = cleanLine.match(/(http:\/\/(?:localhost|127\.0\.0\.1):\d+)/);
|
|
821
|
+
if (cleanLine.includes("Local:") && urlMatch) {
|
|
822
|
+
frontendUrl = urlMatch[1];
|
|
823
|
+
printSummary();
|
|
824
|
+
}
|
|
825
|
+
});
|
|
826
|
+
});
|
|
827
|
+
frontendChild.stderr?.on("data", (data) => {
|
|
828
|
+
const lines = data.toString().split("\n").filter(Boolean);
|
|
829
|
+
lines.forEach((line) => {
|
|
830
|
+
console.log(`${chalk.magenta.bold("[admin]")} ${line}`);
|
|
831
|
+
});
|
|
832
|
+
});
|
|
833
|
+
children.push(frontendChild);
|
|
834
|
+
}
|
|
648
835
|
if (!frontendOnly && backendDir) {
|
|
649
836
|
const tsxBin = resolveTsx(projectRoot);
|
|
650
837
|
if (!tsxBin) {
|
|
@@ -657,22 +844,19 @@ ${chalk.green.bold("Examples")}
|
|
|
657
844
|
if (envFile) {
|
|
658
845
|
env.DOTENV_CONFIG_PATH = envFile;
|
|
659
846
|
}
|
|
660
|
-
|
|
661
|
-
env.PORT = String(args["--port"]);
|
|
662
|
-
}
|
|
663
|
-
path.join(backendDir, "src", "index.ts");
|
|
664
|
-
const watchDirs = [
|
|
665
|
-
`--watch="${path.join("..", "shared", "**", "*")}"`
|
|
666
|
-
];
|
|
847
|
+
env.PORT = String(startPort);
|
|
667
848
|
console.log(` ${chalk.cyan("▶")} Backend: ${chalk.gray(backendDir)}`);
|
|
849
|
+
console.log(` ${chalk.gray("↳ PORT")} = ${chalk.white(String(startPort))}`);
|
|
850
|
+
let frontendLaunched = false;
|
|
668
851
|
const backendChild = execa(
|
|
669
852
|
tsxBin,
|
|
670
|
-
["watch",
|
|
853
|
+
["watch", `--watch="${path.join("..", "config", "**", "*")}"`, "--conditions", "development", "src/index.ts"],
|
|
671
854
|
{
|
|
672
855
|
cwd: backendDir,
|
|
673
856
|
stdio: ["inherit", "pipe", "pipe"],
|
|
674
857
|
env,
|
|
675
|
-
shell: true
|
|
858
|
+
shell: true,
|
|
859
|
+
detached: process.platform !== "win32"
|
|
676
860
|
}
|
|
677
861
|
);
|
|
678
862
|
backendChild.catch(() => {
|
|
@@ -682,9 +866,19 @@ ${chalk.green.bold("Examples")}
|
|
|
682
866
|
lines.forEach((line) => {
|
|
683
867
|
console.log(`${chalk.cyan.bold("[backend]")} ${line}`);
|
|
684
868
|
const cleanLine = stripAnsi(line);
|
|
685
|
-
|
|
869
|
+
const serverMatch = cleanLine.match(/Server running at http:\/\/(?:localhost|127\.0\.0\.1):(\d+)/);
|
|
870
|
+
if (serverMatch) {
|
|
871
|
+
resolvedBackendPort = parseInt(serverMatch[1], 10);
|
|
686
872
|
backendUrl = "started";
|
|
687
873
|
printSummary();
|
|
874
|
+
const urlFile = path.join(projectRoot, ".rebase-dev-url");
|
|
875
|
+
fs.writeFileSync(urlFile, `http://localhost:${resolvedBackendPort}`, "utf-8");
|
|
876
|
+
const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
|
|
877
|
+
fs.writeFileSync(portFile, String(resolvedBackendPort), "utf-8");
|
|
878
|
+
if (!backendOnly && frontendDir && !frontendLaunched) {
|
|
879
|
+
frontendLaunched = true;
|
|
880
|
+
startFrontend(resolvedBackendPort);
|
|
881
|
+
}
|
|
688
882
|
}
|
|
689
883
|
});
|
|
690
884
|
});
|
|
@@ -698,39 +892,8 @@ ${chalk.green.bold("Examples")}
|
|
|
698
892
|
} else if (!frontendOnly && !backendDir) {
|
|
699
893
|
console.warn(chalk.yellow(" ⚠ No backend/ directory found, skipping backend."));
|
|
700
894
|
}
|
|
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);
|
|
895
|
+
if (!backendOnly && frontendDir && (frontendOnly || !backendDir)) {
|
|
896
|
+
startFrontend(null);
|
|
734
897
|
} else if (!backendOnly && !frontendDir) {
|
|
735
898
|
console.warn(chalk.yellow(" ⚠ No frontend/ directory found, skipping frontend."));
|
|
736
899
|
}
|
|
@@ -759,11 +922,19 @@ ${chalk.green.bold("Usage")}
|
|
|
759
922
|
${chalk.green.bold("Options")}
|
|
760
923
|
${chalk.blue("--backend-only, -b")} Only start the backend server
|
|
761
924
|
${chalk.blue("--frontend-only, -f")} Only start the frontend server
|
|
762
|
-
${chalk.blue("--port, -p")} Backend port (default:
|
|
925
|
+
${chalk.blue("--port, -p")} Backend port (default: auto-detected per project)
|
|
763
926
|
|
|
764
927
|
${chalk.green.bold("Description")}
|
|
765
|
-
Starts both the backend (tsx watch +
|
|
928
|
+
Starts both the backend (tsx watch + Hono) and frontend (Vite)
|
|
766
929
|
dev servers concurrently with color-coded output prefixes.
|
|
930
|
+
|
|
931
|
+
Each project automatically receives a unique default port derived
|
|
932
|
+
from its directory path, preventing collisions when running multiple
|
|
933
|
+
Rebase instances simultaneously.
|
|
934
|
+
|
|
935
|
+
If the assigned port is already in use, the server will automatically
|
|
936
|
+
try the next available port. The frontend is started only after the
|
|
937
|
+
backend is ready, and VITE_API_URL is injected automatically.
|
|
767
938
|
`);
|
|
768
939
|
}
|
|
769
940
|
async function authCommand(subcommand, rawArgs) {
|
|
@@ -900,6 +1071,49 @@ ${chalk.green.bold("Examples")}
|
|
|
900
1071
|
rebase auth reset-password --email user@example.com --password MyNewPass!
|
|
901
1072
|
`);
|
|
902
1073
|
}
|
|
1074
|
+
async function doctorCommand(rawArgs) {
|
|
1075
|
+
const projectRoot = requireProjectRoot();
|
|
1076
|
+
const backendDir = requireBackendDir(projectRoot);
|
|
1077
|
+
const activePlugin = getActiveBackendPlugin(backendDir);
|
|
1078
|
+
if (!activePlugin) {
|
|
1079
|
+
console.error(chalk.red("✗ Could not detect an active database plugin."));
|
|
1080
|
+
console.error(chalk.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
|
|
1081
|
+
process.exit(1);
|
|
1082
|
+
}
|
|
1083
|
+
const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
|
|
1084
|
+
if (!pluginCli) {
|
|
1085
|
+
console.error(chalk.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
|
|
1086
|
+
process.exit(1);
|
|
1087
|
+
}
|
|
1088
|
+
const envFile = findEnvFile(projectRoot);
|
|
1089
|
+
const env = { ...process.env };
|
|
1090
|
+
if (envFile) {
|
|
1091
|
+
env.DOTENV_CONFIG_PATH = envFile;
|
|
1092
|
+
}
|
|
1093
|
+
try {
|
|
1094
|
+
const isTs = pluginCli.endsWith(".ts");
|
|
1095
|
+
if (isTs) {
|
|
1096
|
+
const tsxBin = resolveTsx(projectRoot);
|
|
1097
|
+
if (!tsxBin) {
|
|
1098
|
+
console.error(chalk.red("✗ Could not find tsx binary."));
|
|
1099
|
+
process.exit(1);
|
|
1100
|
+
}
|
|
1101
|
+
await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
|
|
1102
|
+
cwd: backendDir,
|
|
1103
|
+
stdio: "inherit",
|
|
1104
|
+
env
|
|
1105
|
+
});
|
|
1106
|
+
} else {
|
|
1107
|
+
await execa("node", [pluginCli, ...rawArgs.slice(2)], {
|
|
1108
|
+
cwd: backendDir,
|
|
1109
|
+
stdio: "inherit",
|
|
1110
|
+
env
|
|
1111
|
+
});
|
|
1112
|
+
}
|
|
1113
|
+
} catch {
|
|
1114
|
+
process.exit(1);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
903
1117
|
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
1118
|
const __dirname$1 = path.dirname(__filename$1);
|
|
905
1119
|
function getVersion() {
|
|
@@ -931,7 +1145,7 @@ ${chalk.green.bold("Examples")}
|
|
|
931
1145
|
}
|
|
932
1146
|
const command = parsedArgs._[0];
|
|
933
1147
|
const subcommand = parsedArgs._[1];
|
|
934
|
-
const namespacedCommands = ["schema", "db", "dev", "auth"];
|
|
1148
|
+
const namespacedCommands = ["schema", "db", "dev", "auth", "doctor"];
|
|
935
1149
|
if (!command || parsedArgs["--help"] && !namespacedCommands.includes(command)) {
|
|
936
1150
|
printHelp();
|
|
937
1151
|
return;
|
|
@@ -941,7 +1155,7 @@ ${chalk.green.bold("Examples")}
|
|
|
941
1155
|
case "init":
|
|
942
1156
|
await createRebaseApp(args);
|
|
943
1157
|
break;
|
|
944
|
-
case "generate-sdk":
|
|
1158
|
+
case "generate-sdk": {
|
|
945
1159
|
const sdkArgs = arg(
|
|
946
1160
|
{
|
|
947
1161
|
"--collections-dir": String,
|
|
@@ -955,11 +1169,12 @@ ${chalk.green.bold("Examples")}
|
|
|
955
1169
|
}
|
|
956
1170
|
);
|
|
957
1171
|
await generateSdkCommand({
|
|
958
|
-
collectionsDir: sdkArgs["--collections-dir"] || "./
|
|
1172
|
+
collectionsDir: sdkArgs["--collections-dir"] || "./config/collections",
|
|
959
1173
|
output: sdkArgs["--output"] || "./generated/sdk",
|
|
960
1174
|
cwd: process.cwd()
|
|
961
1175
|
});
|
|
962
1176
|
break;
|
|
1177
|
+
}
|
|
963
1178
|
case "schema":
|
|
964
1179
|
await schemaCommand(effectiveSubcommand, args);
|
|
965
1180
|
break;
|
|
@@ -972,6 +1187,9 @@ ${chalk.green.bold("Examples")}
|
|
|
972
1187
|
case "auth":
|
|
973
1188
|
await authCommand(effectiveSubcommand, args);
|
|
974
1189
|
break;
|
|
1190
|
+
case "doctor":
|
|
1191
|
+
await doctorCommand(args);
|
|
1192
|
+
break;
|
|
975
1193
|
default:
|
|
976
1194
|
console.log(chalk.red(`Unknown command: ${command}`));
|
|
977
1195
|
console.log("");
|
|
@@ -1008,6 +1226,9 @@ ${chalk.green.bold("Auth")}
|
|
|
1008
1226
|
${chalk.blue.bold("auth reset-password")} Reset a user's password
|
|
1009
1227
|
${chalk.blue.bold("auth")} ${chalk.gray("--help")} Show auth command help
|
|
1010
1228
|
|
|
1229
|
+
${chalk.green.bold("Diagnostics")}
|
|
1230
|
+
${chalk.blue.bold("doctor")} Detect schema drift between collections, schema, and DB
|
|
1231
|
+
|
|
1011
1232
|
${chalk.green.bold("Options")}
|
|
1012
1233
|
${chalk.blue("--version, -v")} Show version number
|
|
1013
1234
|
${chalk.blue("--help, -h")} Show this help message
|