@rebasepro/cli 0.0.1-canary.4d4fb3e → 0.0.1-canary.5584634
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 +295 -73
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +295 -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;
|
|
@@ -377,6 +478,7 @@ Expected a default export of EntityCollection[] or an object with named collecti
|
|
|
377
478
|
path.join(backendDir, "node_modules", pluginName, "src", "cli.ts"),
|
|
378
479
|
path.join(backendDir, "node_modules", pluginName, "dist", "cli.js"),
|
|
379
480
|
// For monorepo dev mode:
|
|
481
|
+
path.resolve(backendDir, "..", "..", "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"),
|
|
380
482
|
path.resolve(backendDir, "..", "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"),
|
|
381
483
|
path.resolve(backendDir, "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts")
|
|
382
484
|
];
|
|
@@ -473,13 +575,13 @@ Expected a default export of EntityCollection[] or an object with named collecti
|
|
|
473
575
|
console.error(chalk.red("✗ Could not find tsx binary."));
|
|
474
576
|
process.exit(1);
|
|
475
577
|
}
|
|
476
|
-
await execa(tsxBin, [pluginCli,
|
|
578
|
+
await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
|
|
477
579
|
cwd: backendDir,
|
|
478
580
|
stdio: "inherit",
|
|
479
581
|
env
|
|
480
582
|
});
|
|
481
583
|
} else {
|
|
482
|
-
await execa("node", [pluginCli,
|
|
584
|
+
await execa("node", [pluginCli, ...rawArgs.slice(2)], {
|
|
483
585
|
cwd: backendDir,
|
|
484
586
|
stdio: "inherit",
|
|
485
587
|
env
|
|
@@ -499,11 +601,15 @@ ${chalk.green.bold("Usage")}
|
|
|
499
601
|
${chalk.green.bold("Commands")}
|
|
500
602
|
${chalk.gray("(Commands are provided by your active database driver plugin)")}
|
|
501
603
|
${chalk.blue.bold("generate")} Generate Schema from collection definitions
|
|
604
|
+
${chalk.blue.bold("introspect")} Introspect an existing database to generate collection definitions
|
|
502
605
|
|
|
503
606
|
${chalk.green.bold("generate Options")}
|
|
504
607
|
${chalk.blue("--collections, -c")} Path to collections directory
|
|
505
608
|
${chalk.blue("--output, -o")} Output path for generated schema
|
|
506
609
|
${chalk.blue("--watch, -w")} Watch for changes and regenerate automatically
|
|
610
|
+
|
|
611
|
+
${chalk.green.bold("introspect Options")}
|
|
612
|
+
${chalk.blue("--output, -o")} Output directory for generated collection files
|
|
507
613
|
`);
|
|
508
614
|
}
|
|
509
615
|
async function dbCommand(subcommand, rawArgs) {
|
|
@@ -537,13 +643,13 @@ ${chalk.green.bold("generate Options")}
|
|
|
537
643
|
console.error(chalk.red("✗ Could not find tsx binary."));
|
|
538
644
|
process.exit(1);
|
|
539
645
|
}
|
|
540
|
-
await execa(tsxBin, [pluginCli,
|
|
646
|
+
await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
|
|
541
647
|
cwd: backendDir,
|
|
542
648
|
stdio: "inherit",
|
|
543
649
|
env
|
|
544
650
|
});
|
|
545
651
|
} else {
|
|
546
|
-
await execa("node", [pluginCli,
|
|
652
|
+
await execa("node", [pluginCli, ...rawArgs.slice(2)], {
|
|
547
653
|
cwd: backendDir,
|
|
548
654
|
stdio: "inherit",
|
|
549
655
|
env
|
|
@@ -581,6 +687,27 @@ ${chalk.green.bold("Examples")}
|
|
|
581
687
|
rebase db branch create feature_auth
|
|
582
688
|
`);
|
|
583
689
|
}
|
|
690
|
+
const DEV_PORT_FILENAME = ".rebase-dev-port";
|
|
691
|
+
function getProjectPort(projectRoot) {
|
|
692
|
+
let hash = 0;
|
|
693
|
+
for (let i = 0; i < projectRoot.length; i++) {
|
|
694
|
+
hash = (hash << 5) - hash + projectRoot.charCodeAt(i) | 0;
|
|
695
|
+
}
|
|
696
|
+
return 3001 + Math.abs(hash) % 999;
|
|
697
|
+
}
|
|
698
|
+
function resolveStartPort(projectRoot, explicitPort) {
|
|
699
|
+
if (explicitPort) return explicitPort;
|
|
700
|
+
if (process.env.PORT) return parseInt(process.env.PORT, 10);
|
|
701
|
+
try {
|
|
702
|
+
const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
|
|
703
|
+
if (fs.existsSync(portFile)) {
|
|
704
|
+
const saved = parseInt(fs.readFileSync(portFile, "utf-8").trim(), 10);
|
|
705
|
+
if (saved > 0 && saved < 65536) return saved;
|
|
706
|
+
}
|
|
707
|
+
} catch {
|
|
708
|
+
}
|
|
709
|
+
return getProjectPort(projectRoot);
|
|
710
|
+
}
|
|
584
711
|
async function devCommand(rawArgs) {
|
|
585
712
|
const args = arg(
|
|
586
713
|
{
|
|
@@ -608,6 +735,7 @@ ${chalk.green.bold("Examples")}
|
|
|
608
735
|
const frontendDir = findFrontendDir(projectRoot);
|
|
609
736
|
const backendOnly = args["--backend-only"] || false;
|
|
610
737
|
const frontendOnly = args["--frontend-only"] || false;
|
|
738
|
+
const startPort = resolveStartPort(projectRoot, args["--port"]);
|
|
611
739
|
console.log("");
|
|
612
740
|
console.log(chalk.bold(" 🚀 Rebase Dev Server"));
|
|
613
741
|
console.log("");
|
|
@@ -616,6 +744,7 @@ ${chalk.green.bold("Examples")}
|
|
|
616
744
|
let backendUrl = "";
|
|
617
745
|
let debounceSummary = null;
|
|
618
746
|
let bannerPrinted = false;
|
|
747
|
+
let resolvedBackendPort = null;
|
|
619
748
|
const stripAnsi = (str) => str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
|
|
620
749
|
function printSummary() {
|
|
621
750
|
if (!frontendUrl || !backendUrl) return;
|
|
@@ -628,7 +757,7 @@ ${chalk.green.bold("Examples")}
|
|
|
628
757
|
console.log(chalk.cyan("│ ✨ Rebase Admin App is ready! │"));
|
|
629
758
|
const cleanUrl = stripAnsi(frontendUrl);
|
|
630
759
|
const paddedUrl = cleanUrl.padEnd(40);
|
|
631
|
-
console.log(chalk.cyan(
|
|
760
|
+
console.log(chalk.cyan("│ 👉 Frontend URL: ") + chalk.white(paddedUrl) + chalk.cyan("│"));
|
|
632
761
|
console.log(chalk.cyan("│ │"));
|
|
633
762
|
console.log(chalk.cyan("└────────────────────────────────────────────────────────────┘"));
|
|
634
763
|
console.log("");
|
|
@@ -636,15 +765,74 @@ ${chalk.green.bold("Examples")}
|
|
|
636
765
|
}, 500);
|
|
637
766
|
}
|
|
638
767
|
const cleanup = () => {
|
|
768
|
+
try {
|
|
769
|
+
const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
|
|
770
|
+
if (fs.existsSync(portFile)) fs.unlinkSync(portFile);
|
|
771
|
+
const urlFile = path.join(projectRoot, ".rebase-dev-url");
|
|
772
|
+
if (fs.existsSync(urlFile)) fs.unlinkSync(urlFile);
|
|
773
|
+
} catch {
|
|
774
|
+
}
|
|
639
775
|
children.forEach((child) => {
|
|
640
|
-
if (!child.killed) {
|
|
641
|
-
|
|
776
|
+
if (child.pid && !child.killed) {
|
|
777
|
+
try {
|
|
778
|
+
if (process.platform === "win32") {
|
|
779
|
+
execa.commandSync(`taskkill /pid ${child.pid} /T /F`);
|
|
780
|
+
} else {
|
|
781
|
+
process.kill(-child.pid, "SIGKILL");
|
|
782
|
+
}
|
|
783
|
+
} catch (e) {
|
|
784
|
+
try {
|
|
785
|
+
child.kill("SIGKILL");
|
|
786
|
+
} catch (err) {
|
|
787
|
+
}
|
|
788
|
+
}
|
|
642
789
|
}
|
|
643
790
|
});
|
|
644
791
|
process.exit(0);
|
|
645
792
|
};
|
|
646
793
|
process.on("SIGINT", cleanup);
|
|
647
794
|
process.on("SIGTERM", cleanup);
|
|
795
|
+
function startFrontend(backendPort) {
|
|
796
|
+
if (!frontendDir) return;
|
|
797
|
+
console.log(` ${chalk.magenta("▶")} Frontend: ${chalk.gray(frontendDir)}`);
|
|
798
|
+
const frontendEnv = { ...process.env };
|
|
799
|
+
if (backendPort) {
|
|
800
|
+
frontendEnv.VITE_API_URL = `http://localhost:${backendPort}`;
|
|
801
|
+
console.log(` ${chalk.gray("↳ VITE_API_URL")} = ${chalk.white(`http://localhost:${backendPort}`)}`);
|
|
802
|
+
}
|
|
803
|
+
const frontendChild = execa(
|
|
804
|
+
"pnpm",
|
|
805
|
+
["run", "dev"],
|
|
806
|
+
{
|
|
807
|
+
cwd: frontendDir,
|
|
808
|
+
stdio: ["inherit", "pipe", "pipe"],
|
|
809
|
+
env: frontendEnv,
|
|
810
|
+
shell: true,
|
|
811
|
+
detached: process.platform !== "win32"
|
|
812
|
+
}
|
|
813
|
+
);
|
|
814
|
+
frontendChild.catch(() => {
|
|
815
|
+
});
|
|
816
|
+
frontendChild.stdout?.on("data", (data) => {
|
|
817
|
+
const lines = data.toString().split("\n").filter(Boolean);
|
|
818
|
+
lines.forEach((line) => {
|
|
819
|
+
console.log(`${chalk.magenta.bold("[admin]")} ${line}`);
|
|
820
|
+
const cleanLine = stripAnsi(line);
|
|
821
|
+
const urlMatch = cleanLine.match(/(http:\/\/(?:localhost|127\.0\.0\.1):\d+)/);
|
|
822
|
+
if (cleanLine.includes("Local:") && urlMatch) {
|
|
823
|
+
frontendUrl = urlMatch[1];
|
|
824
|
+
printSummary();
|
|
825
|
+
}
|
|
826
|
+
});
|
|
827
|
+
});
|
|
828
|
+
frontendChild.stderr?.on("data", (data) => {
|
|
829
|
+
const lines = data.toString().split("\n").filter(Boolean);
|
|
830
|
+
lines.forEach((line) => {
|
|
831
|
+
console.log(`${chalk.magenta.bold("[admin]")} ${line}`);
|
|
832
|
+
});
|
|
833
|
+
});
|
|
834
|
+
children.push(frontendChild);
|
|
835
|
+
}
|
|
648
836
|
if (!frontendOnly && backendDir) {
|
|
649
837
|
const tsxBin = resolveTsx(projectRoot);
|
|
650
838
|
if (!tsxBin) {
|
|
@@ -657,22 +845,19 @@ ${chalk.green.bold("Examples")}
|
|
|
657
845
|
if (envFile) {
|
|
658
846
|
env.DOTENV_CONFIG_PATH = envFile;
|
|
659
847
|
}
|
|
660
|
-
|
|
661
|
-
env.PORT = String(args["--port"]);
|
|
662
|
-
}
|
|
663
|
-
path.join(backendDir, "src", "index.ts");
|
|
664
|
-
const watchDirs = [
|
|
665
|
-
`--watch="${path.join("..", "shared", "**", "*")}"`
|
|
666
|
-
];
|
|
848
|
+
env.PORT = String(startPort);
|
|
667
849
|
console.log(` ${chalk.cyan("▶")} Backend: ${chalk.gray(backendDir)}`);
|
|
850
|
+
console.log(` ${chalk.gray("↳ PORT")} = ${chalk.white(String(startPort))}`);
|
|
851
|
+
let frontendLaunched = false;
|
|
668
852
|
const backendChild = execa(
|
|
669
853
|
tsxBin,
|
|
670
|
-
["watch",
|
|
854
|
+
["watch", `--watch="${path.join("..", "config", "**", "*")}"`, "--conditions", "development", "src/index.ts"],
|
|
671
855
|
{
|
|
672
856
|
cwd: backendDir,
|
|
673
857
|
stdio: ["inherit", "pipe", "pipe"],
|
|
674
858
|
env,
|
|
675
|
-
shell: true
|
|
859
|
+
shell: true,
|
|
860
|
+
detached: process.platform !== "win32"
|
|
676
861
|
}
|
|
677
862
|
);
|
|
678
863
|
backendChild.catch(() => {
|
|
@@ -682,9 +867,19 @@ ${chalk.green.bold("Examples")}
|
|
|
682
867
|
lines.forEach((line) => {
|
|
683
868
|
console.log(`${chalk.cyan.bold("[backend]")} ${line}`);
|
|
684
869
|
const cleanLine = stripAnsi(line);
|
|
685
|
-
|
|
870
|
+
const serverMatch = cleanLine.match(/Server running at http:\/\/(?:localhost|127\.0\.0\.1):(\d+)/);
|
|
871
|
+
if (serverMatch) {
|
|
872
|
+
resolvedBackendPort = parseInt(serverMatch[1], 10);
|
|
686
873
|
backendUrl = "started";
|
|
687
874
|
printSummary();
|
|
875
|
+
const urlFile = path.join(projectRoot, ".rebase-dev-url");
|
|
876
|
+
fs.writeFileSync(urlFile, `http://localhost:${resolvedBackendPort}`, "utf-8");
|
|
877
|
+
const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
|
|
878
|
+
fs.writeFileSync(portFile, String(resolvedBackendPort), "utf-8");
|
|
879
|
+
if (!backendOnly && frontendDir && !frontendLaunched) {
|
|
880
|
+
frontendLaunched = true;
|
|
881
|
+
startFrontend(resolvedBackendPort);
|
|
882
|
+
}
|
|
688
883
|
}
|
|
689
884
|
});
|
|
690
885
|
});
|
|
@@ -698,39 +893,8 @@ ${chalk.green.bold("Examples")}
|
|
|
698
893
|
} else if (!frontendOnly && !backendDir) {
|
|
699
894
|
console.warn(chalk.yellow(" ⚠ No backend/ directory found, skipping backend."));
|
|
700
895
|
}
|
|
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);
|
|
896
|
+
if (!backendOnly && frontendDir && (frontendOnly || !backendDir)) {
|
|
897
|
+
startFrontend(null);
|
|
734
898
|
} else if (!backendOnly && !frontendDir) {
|
|
735
899
|
console.warn(chalk.yellow(" ⚠ No frontend/ directory found, skipping frontend."));
|
|
736
900
|
}
|
|
@@ -759,11 +923,19 @@ ${chalk.green.bold("Usage")}
|
|
|
759
923
|
${chalk.green.bold("Options")}
|
|
760
924
|
${chalk.blue("--backend-only, -b")} Only start the backend server
|
|
761
925
|
${chalk.blue("--frontend-only, -f")} Only start the frontend server
|
|
762
|
-
${chalk.blue("--port, -p")} Backend port (default:
|
|
926
|
+
${chalk.blue("--port, -p")} Backend port (default: auto-detected per project)
|
|
763
927
|
|
|
764
928
|
${chalk.green.bold("Description")}
|
|
765
|
-
Starts both the backend (tsx watch +
|
|
929
|
+
Starts both the backend (tsx watch + Hono) and frontend (Vite)
|
|
766
930
|
dev servers concurrently with color-coded output prefixes.
|
|
931
|
+
|
|
932
|
+
Each project automatically receives a unique default port derived
|
|
933
|
+
from its directory path, preventing collisions when running multiple
|
|
934
|
+
Rebase instances simultaneously.
|
|
935
|
+
|
|
936
|
+
If the assigned port is already in use, the server will automatically
|
|
937
|
+
try the next available port. The frontend is started only after the
|
|
938
|
+
backend is ready, and VITE_API_URL is injected automatically.
|
|
767
939
|
`);
|
|
768
940
|
}
|
|
769
941
|
async function authCommand(subcommand, rawArgs) {
|
|
@@ -900,6 +1072,49 @@ ${chalk.green.bold("Examples")}
|
|
|
900
1072
|
rebase auth reset-password --email user@example.com --password MyNewPass!
|
|
901
1073
|
`);
|
|
902
1074
|
}
|
|
1075
|
+
async function doctorCommand(rawArgs) {
|
|
1076
|
+
const projectRoot = requireProjectRoot();
|
|
1077
|
+
const backendDir = requireBackendDir(projectRoot);
|
|
1078
|
+
const activePlugin = getActiveBackendPlugin(backendDir);
|
|
1079
|
+
if (!activePlugin) {
|
|
1080
|
+
console.error(chalk.red("✗ Could not detect an active database plugin."));
|
|
1081
|
+
console.error(chalk.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
|
|
1082
|
+
process.exit(1);
|
|
1083
|
+
}
|
|
1084
|
+
const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
|
|
1085
|
+
if (!pluginCli) {
|
|
1086
|
+
console.error(chalk.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
|
|
1087
|
+
process.exit(1);
|
|
1088
|
+
}
|
|
1089
|
+
const envFile = findEnvFile(projectRoot);
|
|
1090
|
+
const env = { ...process.env };
|
|
1091
|
+
if (envFile) {
|
|
1092
|
+
env.DOTENV_CONFIG_PATH = envFile;
|
|
1093
|
+
}
|
|
1094
|
+
try {
|
|
1095
|
+
const isTs = pluginCli.endsWith(".ts");
|
|
1096
|
+
if (isTs) {
|
|
1097
|
+
const tsxBin = resolveTsx(projectRoot);
|
|
1098
|
+
if (!tsxBin) {
|
|
1099
|
+
console.error(chalk.red("✗ Could not find tsx binary."));
|
|
1100
|
+
process.exit(1);
|
|
1101
|
+
}
|
|
1102
|
+
await execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
|
|
1103
|
+
cwd: backendDir,
|
|
1104
|
+
stdio: "inherit",
|
|
1105
|
+
env
|
|
1106
|
+
});
|
|
1107
|
+
} else {
|
|
1108
|
+
await execa("node", [pluginCli, ...rawArgs.slice(2)], {
|
|
1109
|
+
cwd: backendDir,
|
|
1110
|
+
stdio: "inherit",
|
|
1111
|
+
env
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
} catch {
|
|
1115
|
+
process.exit(1);
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
903
1118
|
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
1119
|
const __dirname$1 = path.dirname(__filename$1);
|
|
905
1120
|
function getVersion() {
|
|
@@ -931,7 +1146,7 @@ ${chalk.green.bold("Examples")}
|
|
|
931
1146
|
}
|
|
932
1147
|
const command = parsedArgs._[0];
|
|
933
1148
|
const subcommand = parsedArgs._[1];
|
|
934
|
-
const namespacedCommands = ["schema", "db", "dev", "auth"];
|
|
1149
|
+
const namespacedCommands = ["schema", "db", "dev", "auth", "doctor"];
|
|
935
1150
|
if (!command || parsedArgs["--help"] && !namespacedCommands.includes(command)) {
|
|
936
1151
|
printHelp();
|
|
937
1152
|
return;
|
|
@@ -941,7 +1156,7 @@ ${chalk.green.bold("Examples")}
|
|
|
941
1156
|
case "init":
|
|
942
1157
|
await createRebaseApp(args);
|
|
943
1158
|
break;
|
|
944
|
-
case "generate-sdk":
|
|
1159
|
+
case "generate-sdk": {
|
|
945
1160
|
const sdkArgs = arg(
|
|
946
1161
|
{
|
|
947
1162
|
"--collections-dir": String,
|
|
@@ -955,11 +1170,12 @@ ${chalk.green.bold("Examples")}
|
|
|
955
1170
|
}
|
|
956
1171
|
);
|
|
957
1172
|
await generateSdkCommand({
|
|
958
|
-
collectionsDir: sdkArgs["--collections-dir"] || "./
|
|
1173
|
+
collectionsDir: sdkArgs["--collections-dir"] || "./config/collections",
|
|
959
1174
|
output: sdkArgs["--output"] || "./generated/sdk",
|
|
960
1175
|
cwd: process.cwd()
|
|
961
1176
|
});
|
|
962
1177
|
break;
|
|
1178
|
+
}
|
|
963
1179
|
case "schema":
|
|
964
1180
|
await schemaCommand(effectiveSubcommand, args);
|
|
965
1181
|
break;
|
|
@@ -972,6 +1188,9 @@ ${chalk.green.bold("Examples")}
|
|
|
972
1188
|
case "auth":
|
|
973
1189
|
await authCommand(effectiveSubcommand, args);
|
|
974
1190
|
break;
|
|
1191
|
+
case "doctor":
|
|
1192
|
+
await doctorCommand(args);
|
|
1193
|
+
break;
|
|
975
1194
|
default:
|
|
976
1195
|
console.log(chalk.red(`Unknown command: ${command}`));
|
|
977
1196
|
console.log("");
|
|
@@ -1008,6 +1227,9 @@ ${chalk.green.bold("Auth")}
|
|
|
1008
1227
|
${chalk.blue.bold("auth reset-password")} Reset a user's password
|
|
1009
1228
|
${chalk.blue.bold("auth")} ${chalk.gray("--help")} Show auth command help
|
|
1010
1229
|
|
|
1230
|
+
${chalk.green.bold("Diagnostics")}
|
|
1231
|
+
${chalk.blue.bold("doctor")} Detect schema drift between collections, schema, and DB
|
|
1232
|
+
|
|
1011
1233
|
${chalk.green.bold("Options")}
|
|
1012
1234
|
${chalk.blue("--version, -v")} Show version number
|
|
1013
1235
|
${chalk.blue("--help, -h")} Show this help message
|