@rebasepro/cli 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/commands/skills.d.ts +1 -0
  2. package/dist/index.cjs +1843 -1470
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.es.js +1615 -1308
  5. package/dist/index.es.js.map +1 -1
  6. package/dist/utils/package-manager.d.ts +2 -0
  7. package/package.json +15 -13
  8. package/skills/rebase-api/SKILL.md +662 -0
  9. package/skills/rebase-api/references/.gitkeep +3 -0
  10. package/skills/rebase-auth/SKILL.md +1143 -0
  11. package/skills/rebase-auth/references/.gitkeep +3 -0
  12. package/skills/rebase-backend-postgres/SKILL.md +633 -0
  13. package/skills/rebase-backend-postgres/references/.gitkeep +3 -0
  14. package/skills/rebase-basics/SKILL.md +749 -0
  15. package/skills/rebase-basics/references/.gitkeep +3 -0
  16. package/skills/rebase-collections/SKILL.md +1328 -0
  17. package/skills/rebase-collections/references/.gitkeep +3 -0
  18. package/skills/rebase-cron-jobs/SKILL.md +699 -0
  19. package/skills/rebase-cron-jobs/references/.gitkeep +1 -0
  20. package/skills/rebase-custom-functions/SKILL.md +233 -0
  21. package/skills/rebase-deployment/SKILL.md +583 -0
  22. package/skills/rebase-deployment/references/.gitkeep +3 -0
  23. package/skills/rebase-design-language/SKILL.md +664 -0
  24. package/skills/rebase-email/SKILL.md +701 -0
  25. package/skills/rebase-email/references/.gitkeep +1 -0
  26. package/skills/rebase-entity-history/SKILL.md +485 -0
  27. package/skills/rebase-entity-history/references/.gitkeep +1 -0
  28. package/skills/rebase-local-env-setup/SKILL.md +189 -0
  29. package/skills/rebase-local-env-setup/references/.gitkeep +3 -0
  30. package/skills/rebase-realtime/SKILL.md +755 -0
  31. package/skills/rebase-realtime/references/.gitkeep +3 -0
  32. package/skills/rebase-sdk/SKILL.md +594 -0
  33. package/skills/rebase-sdk/references/.gitkeep +0 -0
  34. package/skills/rebase-storage/SKILL.md +765 -0
  35. package/skills/rebase-storage/references/.gitkeep +3 -0
  36. package/skills/rebase-studio/SKILL.md +746 -0
  37. package/skills/rebase-studio/references/.gitkeep +3 -0
  38. package/skills/rebase-ui-components/SKILL.md +1411 -0
  39. package/skills/rebase-ui-components/references/.gitkeep +3 -0
  40. package/skills/rebase-webhooks/SKILL.md +623 -0
  41. package/skills/rebase-webhooks/references/.gitkeep +1 -0
  42. package/templates/template/AGENTS.md +2 -0
  43. package/templates/template/CLAUDE.md +2 -0
  44. package/templates/template/ai-instructions.md +6 -3
  45. package/templates/template/backend/package.json +1 -1
  46. package/templates/template/backend/src/env.ts +1 -1
  47. package/templates/template/backend/src/index.ts +9 -6
  48. package/templates/template/config/collections/presets/ecommerce/orders.ts +15 -5
  49. package/templates/template/config/collections/presets/ecommerce/products.ts +9 -3
  50. package/templates/template/config/collections/users.ts +7 -10
  51. package/templates/template/frontend/package.json +2 -2
  52. package/templates/template/frontend/src/App.tsx +1 -7
  53. package/templates/template/frontend/vite.config.ts +0 -1
  54. package/templates/template/package.json +1 -0
  55. package/dist/commands/cli.test.d.ts +0 -1
  56. package/dist/commands/dev.test.d.ts +0 -1
  57. package/dist/commands/init.test.d.ts +0 -1
  58. package/dist/utils/package-manager.test.d.ts +0 -1
  59. package/dist/utils/project.test.d.ts +0 -1
package/dist/index.cjs CHANGED
@@ -1,1168 +1,1344 @@
1
1
  (function(global, factory) {
2
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("chalk"), require("arg"), require("inquirer"), require("path"), require("fs"), require("net"), require("util"), require("execa"), require("fs/promises"), require("url"), require("crypto"), require("@rebasepro/sdk-generator"), require("child_process")) : typeof define === "function" && define.amd ? define(["exports", "chalk", "arg", "inquirer", "path", "fs", "net", "util", "execa", "fs/promises", "url", "crypto", "@rebasepro/sdk-generator", "child_process"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase CLI"] = {}, global.chalk, global.arg, global.inquirer, global.path, global.fs, global.net, global.util, global.execa, global.promises, global.url, global.crypto, global.sdkGenerator, global.child_process));
3
- })(this, (function(exports2, chalk, arg, inquirer, path, fs, net, util, execa, promises, url, crypto, sdkGenerator, child_process) {
4
- "use strict";
5
- var _documentCurrentScript = typeof document !== "undefined" ? document.currentScript : null;
6
- function detectPackageManager(targetDir) {
7
- const userAgent = process.env.npm_config_user_agent ?? "";
8
- if (userAgent.startsWith("npm/")) return "npm";
9
- if (userAgent.startsWith("pnpm/")) return "pnpm";
10
- if (targetDir) {
11
- if (fs.existsSync(path.join(targetDir, "package-lock.json"))) return "npm";
12
- if (fs.existsSync(path.join(targetDir, "pnpm-lock.yaml"))) return "pnpm";
13
- }
14
- const cwd = process.cwd();
15
- if (cwd !== targetDir) {
16
- if (fs.existsSync(path.join(cwd, "package-lock.json"))) return "npm";
17
- if (fs.existsSync(path.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
18
- }
19
- return "pnpm";
20
- }
21
- function getPMCommands(pm) {
22
- if (pm === "npm") {
23
- return {
24
- name: "npm",
25
- install: ["npm", "install"],
26
- run: (script) => ["npm", "run", script],
27
- exec: (bin, args) => ["npx", bin, ...args],
28
- view: (pkg, field) => ["npm", "view", pkg, field],
29
- runAll: (script) => ["npm", "run", script, "--workspaces", "--if-present"],
30
- runWorkspace: (workspace, script) => ["npm", "run", script, "-w", workspace],
31
- workspaceProtocol: "*"
32
- };
33
- }
34
- return {
35
- name: "pnpm",
36
- install: ["pnpm", "install"],
37
- run: (script) => ["pnpm", "run", script],
38
- exec: (bin, args) => ["pnpm", "exec", bin, ...args],
39
- view: (pkg, field) => ["pnpm", "view", pkg, field],
40
- runAll: (script) => ["pnpm", "-r", "run", script],
41
- runWorkspace: (workspace, script) => ["pnpm", "--filter", workspace, script],
42
- workspaceProtocol: "workspace:*"
43
- };
44
- }
45
- const access = util.promisify(fs.access);
46
- const __filename$2 = 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);
47
- const __dirname$2 = path.dirname(__filename$2);
48
- function findParentDir(currentDir, targetName) {
49
- const root = path.parse(currentDir).root;
50
- while (currentDir && currentDir !== root) {
51
- if (path.basename(currentDir) === targetName) {
52
- return currentDir;
53
- }
54
- currentDir = path.dirname(currentDir);
55
- }
56
- return null;
57
- }
58
- const cliRoot = findParentDir(__dirname$2, "cli");
59
- const PRESET_CHOICES = [
60
- { name: "Blog — Posts, Authors, Tags (with markdown editor)", value: "blog", short: "Blog" },
61
- { name: "E-commerce — Products, Categories, Orders", value: "ecommerce", short: "E-commerce" },
62
- { name: "Blank — Empty project, just authentication", value: "blank", short: "Blank" }
63
- ];
64
- async function createRebaseApp(rawArgs) {
65
- console.log(`
66
- ${chalk.bold("Rebase")} — Create a new project 🚀
2
+ typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("chalk"), require("arg"), require("inquirer"), require("path"), require("fs"), require("net"), require("util"), require("execa"), require("fs/promises"), require("url"), require("crypto"), require("@rebasepro/sdk-generator"), require("child_process")) : typeof define === "function" && define.amd ? define([
3
+ "exports",
4
+ "chalk",
5
+ "arg",
6
+ "inquirer",
7
+ "path",
8
+ "fs",
9
+ "net",
10
+ "util",
11
+ "execa",
12
+ "fs/promises",
13
+ "url",
14
+ "crypto",
15
+ "@rebasepro/sdk-generator",
16
+ "child_process"
17
+ ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase CLI"] = {}, global.chalk, global.arg, global.inquirer, global.path, global.fs, global.net, global.util, global.execa, global.fs_promises, global.url, global.crypto, global._rebasepro_sdk_generator, global.child_process));
18
+ })(this, function(exports, chalk, arg, inquirer, path, fs, net, util, execa, fs_promises, url, crypto, _rebasepro_sdk_generator, child_process) {
19
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
20
+ //#region \0rolldown/runtime.js
21
+ var __create = Object.create;
22
+ var __defProp = Object.defineProperty;
23
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
24
+ var __getOwnPropNames = Object.getOwnPropertyNames;
25
+ var __getProtoOf = Object.getPrototypeOf;
26
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
27
+ var __copyProps = (to, from, except, desc) => {
28
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
29
+ key = keys[i];
30
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
31
+ get: ((k) => from[k]).bind(null, key),
32
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
33
+ });
34
+ }
35
+ return to;
36
+ };
37
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
38
+ value: mod,
39
+ enumerable: true
40
+ }) : target, mod));
41
+ //#endregion
42
+ chalk = __toESM(chalk, 1);
43
+ arg = __toESM(arg, 1);
44
+ inquirer = __toESM(inquirer, 1);
45
+ path = __toESM(path, 1);
46
+ fs = __toESM(fs, 1);
47
+ net = __toESM(net, 1);
48
+ crypto = __toESM(crypto, 1);
49
+ //#region src/utils/package-manager.ts
50
+ /**
51
+ * Package manager detection and command abstraction.
52
+ *
53
+ * Detects whether the user is running pnpm or npm and provides
54
+ * a unified interface for common package-manager operations so
55
+ * the rest of the CLI never has to hardcode a specific PM.
56
+ */
57
+ /**
58
+ * Detect the package manager from the environment or the target directory.
59
+ *
60
+ * Detection order:
61
+ * 1. Explicit override (if provided)
62
+ * 2. `npm_config_user_agent` env var (set by npm/pnpm when running via `npx`/`pnpm dlx`)
63
+ * 3. Lock-file presence in the target directory
64
+ * 4. Default to pnpm (Rebase's recommended PM)
65
+ */
66
+ function detectPackageManager(targetDir) {
67
+ const userAgent = process.env.npm_config_user_agent ?? "";
68
+ if (userAgent.startsWith("npm/")) return "npm";
69
+ if (userAgent.startsWith("pnpm/")) return "pnpm";
70
+ if (targetDir) {
71
+ if (fs.default.existsSync(path.default.join(targetDir, "package-lock.json"))) return "npm";
72
+ if (fs.default.existsSync(path.default.join(targetDir, "pnpm-lock.yaml"))) return "pnpm";
73
+ }
74
+ const cwd = process.cwd();
75
+ if (cwd !== targetDir) {
76
+ if (fs.default.existsSync(path.default.join(cwd, "package-lock.json"))) return "npm";
77
+ if (fs.default.existsSync(path.default.join(cwd, "pnpm-lock.yaml"))) return "pnpm";
78
+ }
79
+ return "pnpm";
80
+ }
81
+ /** Build the command helpers for a given package manager. */
82
+ function getPMCommands(pm) {
83
+ if (pm === "npm") return {
84
+ name: "npm",
85
+ install: ["npm", "install"],
86
+ run: (script) => [
87
+ "npm",
88
+ "run",
89
+ script
90
+ ],
91
+ exec: (bin, args) => [
92
+ "npx",
93
+ bin,
94
+ ...args
95
+ ],
96
+ view: (pkg, field) => [
97
+ "npm",
98
+ "view",
99
+ pkg,
100
+ field
101
+ ],
102
+ runAll: (script) => [
103
+ "npm",
104
+ "run",
105
+ script,
106
+ "--workspaces",
107
+ "--if-present"
108
+ ],
109
+ runWorkspace: (workspace, script) => [
110
+ "npm",
111
+ "run",
112
+ script,
113
+ "-w",
114
+ workspace
115
+ ],
116
+ dlx: (pkg, args) => [
117
+ "npx",
118
+ "-y",
119
+ pkg,
120
+ ...args
121
+ ],
122
+ workspaceProtocol: "*"
123
+ };
124
+ return {
125
+ name: "pnpm",
126
+ install: ["pnpm", "install"],
127
+ run: (script) => [
128
+ "pnpm",
129
+ "run",
130
+ script
131
+ ],
132
+ exec: (bin, args) => [
133
+ "pnpm",
134
+ "exec",
135
+ bin,
136
+ ...args
137
+ ],
138
+ view: (pkg, field) => [
139
+ "pnpm",
140
+ "view",
141
+ pkg,
142
+ field
143
+ ],
144
+ runAll: (script) => [
145
+ "pnpm",
146
+ "-r",
147
+ "run",
148
+ script
149
+ ],
150
+ runWorkspace: (workspace, script) => [
151
+ "pnpm",
152
+ "--filter",
153
+ workspace,
154
+ script
155
+ ],
156
+ dlx: (pkg, args) => [
157
+ "pnpm",
158
+ "dlx",
159
+ pkg,
160
+ ...args
161
+ ],
162
+ workspaceProtocol: "workspace:*"
163
+ };
164
+ }
165
+ //#endregion
166
+ //#region src/commands/init.ts
167
+ var access = (0, util.promisify)(fs.default.access);
168
+ var __filename$2 = (0, url.fileURLToPath)({}.url);
169
+ var __dirname$2 = path.default.dirname(__filename$2);
170
+ function findParentDir$1(currentDir, targetName) {
171
+ const root = path.default.parse(currentDir).root;
172
+ while (currentDir && currentDir !== root) {
173
+ if (path.default.basename(currentDir) === targetName) return currentDir;
174
+ currentDir = path.default.dirname(currentDir);
175
+ }
176
+ return null;
177
+ }
178
+ var cliRoot = findParentDir$1(__dirname$2, "cli");
179
+ var PRESET_CHOICES = [
180
+ {
181
+ name: "Blog — Posts, Authors, Tags (with markdown editor)",
182
+ value: "blog",
183
+ short: "Blog"
184
+ },
185
+ {
186
+ name: "E-commerce — Products, Categories, Orders",
187
+ value: "ecommerce",
188
+ short: "E-commerce"
189
+ },
190
+ {
191
+ name: "Blank — Empty project, just authentication",
192
+ value: "blank",
193
+ short: "Blank"
194
+ }
195
+ ];
196
+ async function createRebaseApp(rawArgs) {
197
+ console.log(`
198
+ ${chalk.default.bold("Rebase")} — Create a new project 🚀
67
199
  `);
68
- const pm = detectPackageManager();
69
- const options = await promptForOptions(rawArgs, pm);
70
- await createProject(options);
71
- }
72
- async function promptForOptions(rawArgs, pm) {
73
- const args = arg(
74
- {
75
- "--git": Boolean,
76
- "--install": Boolean,
77
- "--database-url": String,
78
- "--introspect": Boolean,
79
- "--template": String,
80
- "--yes": Boolean,
81
- "-g": "--git",
82
- "-i": "--install",
83
- "-t": "--template",
84
- "-y": "--yes"
85
- },
86
- {
87
- argv: rawArgs.slice(3),
88
- // skip "node", "rebase", "init"
89
- permissive: true
90
- }
91
- );
92
- const nameArg = args._[0];
93
- const isNonInteractive = args["--yes"] || false;
94
- const templateArg = args["--template"];
95
- if (templateArg && !PRESET_CHOICES.some((p) => p.value === templateArg)) {
96
- console.error(chalk.red(`Unknown template "${templateArg}". Available: ${PRESET_CHOICES.map((p) => p.value).join(", ")}`));
97
- process.exit(1);
98
- }
99
- if (isNonInteractive) {
100
- const projectName2 = nameArg || "my-rebase-app";
101
- const targetDirectory2 = path.resolve(process.cwd(), projectName2);
102
- const templateDirectory2 = path.resolve(cliRoot, "templates", "template");
103
- const pmCommands2 = getPMCommands(pm);
104
- return {
105
- projectName: path.basename(targetDirectory2),
106
- git: args["--git"] ?? false,
107
- installDeps: args["--install"] ?? false,
108
- targetDirectory: targetDirectory2,
109
- templateDirectory: templateDirectory2,
110
- databaseUrl: args["--database-url"] || void 0,
111
- introspect: args["--introspect"] || false,
112
- preset: templateArg || "blog",
113
- pm,
114
- pmCommands: pmCommands2
115
- };
116
- }
117
- const questions = [];
118
- if (!nameArg) {
119
- questions.push({
120
- type: "input",
121
- name: "projectName",
122
- message: "Project name:",
123
- default: "my-rebase-app",
124
- validate: (input) => {
125
- if (!input.trim()) return "Project name is required";
126
- if (!/^[a-z0-9][a-z0-9._-]*$/.test(input)) {
127
- return "Project name must start with a lowercase letter or number and contain only lowercase letters, numbers, hyphens, dots, or underscores";
128
- }
129
- return true;
130
- }
131
- });
132
- }
133
- if (!templateArg) {
134
- questions.push({
135
- type: "list",
136
- name: "preset",
137
- message: "Choose a starter template:",
138
- choices: PRESET_CHOICES,
139
- default: "blog"
140
- });
141
- }
142
- if (!args["--git"]) {
143
- questions.push({
144
- type: "confirm",
145
- name: "git",
146
- message: "Initialize a git repository?",
147
- default: true
148
- });
149
- }
150
- if (!args["--install"]) {
151
- questions.push({
152
- type: "confirm",
153
- name: "installDeps",
154
- message: `Install dependencies with ${pm}?`,
155
- default: true
156
- });
157
- }
158
- questions.push({
159
- type: "input",
160
- name: "databaseUrl",
161
- message: "Enter your PostgreSQL database connection string (leave blank to use a local default):",
162
- default: "",
163
- validate: (input) => {
164
- if (input.trim() && /[\r\n]/.test(input)) {
165
- return "Database URL cannot contain newline characters.";
166
- }
167
- return true;
168
- }
169
- });
170
- questions.push({
171
- type: "confirm",
172
- name: "introspect",
173
- message: "Would you like to introspect this database to automatically generate collections?",
174
- default: true,
175
- when: (answers2) => !!answers2.databaseUrl?.trim()
176
- });
177
- const answers = await inquirer.prompt(questions);
178
- const targetDirectory = path.resolve(process.cwd(), nameArg || answers.projectName);
179
- const projectName = path.basename(targetDirectory);
180
- const templateDirectory = path.resolve(cliRoot, "templates", "template");
181
- const pmCommands = getPMCommands(pm);
182
- return {
183
- projectName,
184
- git: args["--git"] || answers.git || false,
185
- installDeps: args["--install"] || answers.installDeps || false,
186
- targetDirectory,
187
- templateDirectory,
188
- databaseUrl: answers.databaseUrl?.trim() || void 0,
189
- introspect: answers.introspect || false,
190
- preset: templateArg || answers.preset || "blog",
191
- pm,
192
- pmCommands
193
- };
194
- }
195
- async function createProject(options) {
196
- if (fs.existsSync(options.targetDirectory)) {
197
- if (fs.readdirSync(options.targetDirectory).length !== 0) {
198
- console.error(`${chalk.red.bold("ERROR")} Directory "${options.projectName}" already exists and is not empty`);
199
- process.exit(1);
200
- }
201
- } else {
202
- fs.mkdirSync(options.targetDirectory, { recursive: true });
203
- }
204
- try {
205
- await access(options.templateDirectory, fs.constants.R_OK);
206
- } catch {
207
- console.error(`${chalk.red.bold("ERROR")} Template not found at ${options.templateDirectory}`);
208
- process.exit(1);
209
- }
210
- console.log(chalk.gray(" Copying project files..."));
211
- try {
212
- await promises.cp(options.templateDirectory, options.targetDirectory, {
213
- recursive: true,
214
- filter: (source) => {
215
- const basename = path.basename(source);
216
- return basename !== "node_modules" && basename !== ".DS_Store";
217
- }
218
- });
219
- } catch (err) {
220
- console.error(`${chalk.red.bold("ERROR")} Failed to copy template files: ${err instanceof Error ? err.message : String(err)}`);
221
- process.exit(1);
222
- }
223
- await applyPreset(options.targetDirectory, options.preset);
224
- await replacePlaceholders(options);
225
- await configureEnvFile(options.targetDirectory, options.databaseUrl);
226
- if (options.git) {
227
- console.log(chalk.gray(" Initializing git repository..."));
228
- try {
229
- await execa.execa("git", ["init"], { cwd: options.targetDirectory });
230
- } catch {
231
- console.warn(chalk.yellow(" Warning: Failed to initialize git repository"));
232
- }
233
- }
234
- const { pm, pmCommands } = options;
235
- const installCmd = pmCommands.install;
236
- const execCmd = pmCommands.exec("rebase", ["schema", "introspect", "--force"]);
237
- if (options.installDeps) {
238
- console.log("");
239
- console.log(chalk.gray(` Installing dependencies with ${pm}...`));
240
- console.log("");
241
- try {
242
- await execa.execa(installCmd[0], installCmd.slice(1), {
243
- cwd: options.targetDirectory,
244
- stdio: "inherit"
245
- });
246
- } catch {
247
- console.warn(chalk.yellow(` Warning: Failed to install dependencies. You may need to run \`${installCmd.join(" ")}\` manually.`));
248
- }
249
- }
250
- if (options.introspect) {
251
- console.log("");
252
- if (options.installDeps) {
253
- console.log(chalk.gray(" Introspecting database and generating collections..."));
254
- console.log("");
255
- try {
256
- await execa.execa(execCmd[0], execCmd.slice(1), {
257
- cwd: options.targetDirectory,
258
- stdio: "inherit"
259
- });
260
- console.log(chalk.green(" Database successfully introspected!"));
261
- } catch {
262
- console.warn(chalk.yellow(" Warning: Failed to introspect database automatically."));
263
- console.warn(chalk.yellow(` You can run \`${execCmd.join(" ")}\` manually after setup.`));
264
- }
265
- } else {
266
- console.warn(chalk.yellow(" Skipping introspection because dependencies were not installed."));
267
- console.warn(chalk.yellow(` Run \`${installCmd.join(" ")}\` then \`${execCmd.join(" ")}\` manually.`));
268
- }
269
- }
270
- console.log("");
271
- console.log(`${chalk.green.bold("")} Project ${chalk.bold(options.projectName)} created successfully!`);
272
- console.log("");
273
- console.log(chalk.bold("Next steps:"));
274
- console.log("");
275
- const runDev = pmCommands.run("dev");
276
- const runDbPush = pmCommands.run("db:push");
277
- console.log(` ${chalk.cyan("cd")} ${options.projectName}`);
278
- if (!options.installDeps) {
279
- console.log(` ${chalk.cyan(installCmd.join(" "))}`);
280
- }
281
- console.log("");
282
- if (options.databaseUrl) {
283
- if (options.introspect) {
284
- console.log(chalk.gray(" # Database has been introspected & collections generated!"));
285
- console.log(chalk.gray(" # Start the development server (frontend + backend):"));
286
- console.log(` ${chalk.cyan(runDev.join(" "))}`);
287
- } else {
288
- console.log(chalk.gray(" # Your custom database is configured in .env."));
289
- console.log(chalk.gray(" # If the database is empty, push the Rebase schema to initialize it:"));
290
- console.log(` ${chalk.cyan(runDbPush.join(" "))}`);
291
- console.log("");
292
- console.log(chalk.gray(" # Then start the development server:"));
293
- console.log(` ${chalk.cyan(runDev.join(" "))}`);
294
- }
295
- } else {
296
- console.log(chalk.gray(" # A local database configuration has been generated in .env."));
297
- console.log(chalk.gray(" # 1. Start the PostgreSQL database container:"));
298
- console.log(` ${chalk.cyan("docker compose up -d db")}`);
299
- console.log("");
300
- console.log(chalk.gray(" # 2. Push the Rebase schema to initialize database tables:"));
301
- console.log(` ${chalk.cyan(runDbPush.join(" "))}`);
302
- console.log("");
303
- console.log(chalk.gray(" # 3. Start the development server (frontend + backend):"));
304
- console.log(` ${chalk.cyan(runDev.join(" "))}`);
305
- }
306
- console.log("");
307
- console.log(chalk.gray("This starts both the backend (Hono + PostgreSQL)") + chalk.gray(" and the frontend (Vite + React) concurrently."));
308
- console.log("");
309
- console.log(chalk.gray("Docs: https://rebase.pro/docs"));
310
- console.log(chalk.gray("GitHub: https://github.com/rebasepro/rebase"));
311
- console.log("");
312
- }
313
- async function applyPreset(targetDirectory, preset) {
314
- const collectionsDir = path.join(targetDirectory, "config", "collections");
315
- const presetsDir = path.join(collectionsDir, "presets");
316
- if (preset !== "blog") {
317
- const presetDir = path.join(presetsDir, preset);
318
- if (!fs.existsSync(presetDir)) {
319
- console.warn(chalk.yellow(` Warning: Preset "${preset}" not found, falling back to blog template.`));
320
- cleanupPresets(presetsDir);
321
- return;
322
- }
323
- const blogFiles = ["posts.ts", "authors.ts", "tags.ts", "index.ts"];
324
- for (const file of blogFiles) {
325
- const filePath = path.join(collectionsDir, file);
326
- if (fs.existsSync(filePath)) {
327
- fs.unlinkSync(filePath);
328
- }
329
- }
330
- const presetFiles = fs.readdirSync(presetDir).filter((f) => f.endsWith(".ts"));
331
- for (const file of presetFiles) {
332
- fs.copyFileSync(
333
- path.join(presetDir, file),
334
- path.join(collectionsDir, file)
335
- );
336
- }
337
- }
338
- cleanupPresets(presetsDir);
339
- }
340
- function cleanupPresets(presetsDir) {
341
- if (fs.existsSync(presetsDir)) {
342
- fs.rmSync(presetsDir, { recursive: true, force: true });
343
- }
344
- }
345
- async function replacePlaceholders(options) {
346
- const filesToProcess = [
347
- "package.json",
348
- "frontend/package.json",
349
- "backend/package.json",
350
- "config/package.json",
351
- "frontend/index.html",
352
- "pnpm-workspace.yaml",
353
- "README.md"
354
- ];
355
- const packageJsonPath = path.resolve(cliRoot, "package.json");
356
- let cliVersion = "latest";
357
- if (fs.existsSync(packageJsonPath)) {
358
- const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
359
- cliVersion = pkg.version || "latest";
360
- }
361
- const versionCache = /* @__PURE__ */ new Map();
362
- const viewBin = "npm";
363
- const getPackageVersion = async (pkgName) => {
364
- if (versionCache.has(pkgName)) return versionCache.get(pkgName);
365
- if (process.env.REBASE_E2E === "true") {
366
- versionCache.set(pkgName, cliVersion);
367
- return cliVersion;
368
- }
369
- let versionToUse = cliVersion;
370
- try {
371
- const { stdout } = await execa.execa(viewBin, ["view", `${pkgName}@${cliVersion}`, "version"]);
372
- if (!stdout.trim()) throw new Error("Not found");
373
- versionToUse = stdout.trim();
374
- } catch {
375
- try {
376
- const tag = cliVersion.includes("canary") ? "canary" : "latest";
377
- const { stdout } = await execa.execa(viewBin, ["view", `${pkgName}@${tag}`, "version"]);
378
- if (!stdout.trim()) throw new Error("Not found");
379
- versionToUse = stdout.trim();
380
- } catch {
381
- try {
382
- const { stdout } = await execa.execa(viewBin, ["view", pkgName, "version"]);
383
- versionToUse = stdout.trim() || "latest";
384
- } catch {
385
- versionToUse = "latest";
386
- }
387
- }
388
- }
389
- versionCache.set(pkgName, versionToUse);
390
- return versionToUse;
391
- };
392
- const allPackages = /* @__PURE__ */ new Set();
393
- const fileContents = /* @__PURE__ */ new Map();
394
- for (const file of filesToProcess) {
395
- const fullPath = path.resolve(options.targetDirectory, file);
396
- if (!fs.existsSync(fullPath)) continue;
397
- const content = fs.readFileSync(fullPath, "utf-8");
398
- fileContents.set(fullPath, content);
399
- const matches = [...content.matchAll(/"(@rebasepro\/[^"]+)":\s*"workspace:\*"/g)];
400
- for (const match of matches) {
401
- allPackages.add(match[1]);
402
- }
403
- }
404
- console.log(chalk.gray(" Resolving package versions..."));
405
- await Promise.all(Array.from(allPackages).map(getPackageVersion));
406
- for (const [fullPath, originalContent] of fileContents.entries()) {
407
- let content = originalContent.replace(/\{\{PROJECT_NAME\}\}/g, options.projectName);
408
- const matches = [...content.matchAll(/"(@rebasepro\/[^"]+)":\s*"workspace:\*"/g)];
409
- for (const match of matches) {
410
- const pkgName = match[1];
411
- const resolvedVersion = versionCache.get(pkgName) || "latest";
412
- content = content.replace(new RegExp(`"${pkgName}":\\s*"workspace:\\*"`, "g"), `"${pkgName}": "${resolvedVersion}"`);
413
- }
414
- fs.writeFileSync(fullPath, content, "utf-8");
415
- }
416
- }
417
- async function isPortAvailable(port) {
418
- return new Promise((resolve) => {
419
- const server = net.createServer();
420
- server.once("error", () => {
421
- resolve(false);
422
- });
423
- server.once("listening", () => {
424
- server.close(() => resolve(true));
425
- });
426
- server.listen(port);
427
- });
428
- }
429
- async function findAvailablePort(startPort) {
430
- let port = startPort;
431
- while (!await isPortAvailable(port)) {
432
- port++;
433
- }
434
- return port;
435
- }
436
- async function configureEnvFile(targetDirectory, databaseUrl) {
437
- const envExamplePath = path.join(targetDirectory, ".env.example");
438
- const envPath = path.join(targetDirectory, ".env");
439
- if (fs.existsSync(envExamplePath) && !fs.existsSync(envPath)) {
440
- fs.copyFileSync(envExamplePath, envPath);
441
- const jwtSecret = crypto.randomBytes(32).toString("hex");
442
- const dbPassword = crypto.randomBytes(16).toString("hex");
443
- let envContent = fs.readFileSync(envPath, "utf-8");
444
- envContent = envContent.replace(
445
- /^JWT_SECRET=.*$/m,
446
- `JWT_SECRET=${jwtSecret}`
447
- );
448
- if (databaseUrl) {
449
- if (/[\r\n]/.test(databaseUrl)) {
450
- throw new Error("Invalid DATABASE_URL: multiline values are not allowed.");
451
- }
452
- envContent = envContent.replace(
453
- /^DATABASE_URL=.*$/m,
454
- `DATABASE_URL=${databaseUrl}`
455
- );
456
- } else {
457
- const dbPort = await findAvailablePort(5432);
458
- envContent = envContent.replace(
459
- /^DATABASE_URL=.*$/m,
460
- `DATABASE_URL=postgresql://rebase:${dbPassword}@localhost:${dbPort}/rebase?options=-c%20search_path=public
461
- DATABASE_PASSWORD=${dbPassword}`
462
- );
463
- const dockerComposePath = path.join(targetDirectory, "docker-compose.yml");
464
- if (fs.existsSync(dockerComposePath)) {
465
- let dockerComposeContent = fs.readFileSync(dockerComposePath, "utf-8");
466
- dockerComposeContent = dockerComposeContent.replace(
467
- /-\s*"5432:5432"/g,
468
- `- "${dbPort}:5432"`
469
- );
470
- fs.writeFileSync(dockerComposePath, dockerComposeContent, "utf-8");
471
- }
472
- }
473
- fs.writeFileSync(envPath, envContent, "utf-8");
474
- }
475
- }
476
- async function loadCollections(collectionsDir) {
477
- const absDir = path.resolve(collectionsDir);
478
- if (!fs.existsSync(absDir)) {
479
- throw new Error(`Collections directory not found: ${absDir}`);
480
- }
481
- let jiti;
482
- try {
483
- const jitiModule = await import("jiti");
484
- jiti = jitiModule.default || jitiModule;
485
- } catch {
486
- throw new Error(
487
- "Could not load 'jiti'. Install it with: pnpm add -D jiti\njiti is required to dynamically import TypeScript collection definitions."
488
- );
489
- }
490
- const jitiInstance = jiti(absDir, {
491
- interopDefault: true,
492
- esmResolve: true
493
- });
494
- const indexCandidates = ["index.ts", "index.js", "index.mjs"];
495
- let indexPath = null;
496
- for (const candidate of indexCandidates) {
497
- const p = path.join(absDir, candidate);
498
- if (fs.existsSync(p)) {
499
- indexPath = p;
500
- break;
501
- }
502
- }
503
- if (!indexPath) {
504
- console.log(chalk.yellow(" No index file found, scanning individual collection files..."));
505
- const collections = [];
506
- const files = fs.readdirSync(absDir).filter(
507
- (f) => (f.endsWith(".ts") || f.endsWith(".js")) && !f.startsWith(".")
508
- );
509
- for (const file of files) {
510
- try {
511
- const mod2 = jitiInstance(path.join(absDir, file));
512
- const exported2 = mod2.default || mod2;
513
- if (exported2 && typeof exported2 === "object" && "slug" in exported2) {
514
- collections.push(exported2);
515
- } else if (Array.isArray(exported2)) {
516
- collections.push(...exported2);
517
- }
518
- } catch (err) {
519
- console.warn(chalk.yellow(` ⚠ Skipping ${file}: ${err.message}`));
520
- }
521
- }
522
- return collections;
523
- }
524
- const mod = jitiInstance(indexPath);
525
- const exported = mod.default || mod;
526
- if (Array.isArray(exported)) {
527
- return exported;
528
- } else if (typeof exported === "object" && exported !== null) {
529
- if ("collections" in exported && Array.isArray(exported.collections)) {
530
- return exported.collections;
531
- }
532
- const collections = [];
533
- for (const value of Object.values(exported)) {
534
- if (value && typeof value === "object" && "slug" in value) {
535
- collections.push(value);
536
- }
537
- }
538
- if (collections.length > 0) return collections;
539
- }
540
- throw new Error(
541
- `Could not extract collections from ${indexPath}.
542
- Expected a default export of EntityCollection[] or an object with named collection exports.`
543
- );
544
- }
545
- function writeFiles(outputDir, files) {
546
- const absOutput = path.resolve(outputDir);
547
- fs.mkdirSync(absOutput, { recursive: true });
548
- for (const file of files) {
549
- const filePath = path.join(absOutput, file.path);
550
- const dir = path.dirname(filePath);
551
- if (!fs.existsSync(dir)) {
552
- fs.mkdirSync(dir, { recursive: true });
553
- }
554
- fs.writeFileSync(filePath, file.content, "utf-8");
555
- }
556
- }
557
- async function generateSdkCommand(args) {
558
- const { collectionsDir, output, cwd } = args;
559
- const resolvedCollectionsDir = path.isAbsolute(collectionsDir) ? collectionsDir : path.join(cwd, collectionsDir);
560
- const resolvedOutput = path.isAbsolute(output) ? output : path.join(cwd, output);
561
- console.log("");
562
- console.log(chalk.bold(" 🔧 Rebase SDK Generator"));
563
- console.log("");
564
- console.log(` ${chalk.gray("Collections:")} ${resolvedCollectionsDir}`);
565
- console.log(` ${chalk.gray("Output:")} ${resolvedOutput}`);
566
- console.log("");
567
- console.log(chalk.cyan(" → Loading collection definitions..."));
568
- const collections = await loadCollections(resolvedCollectionsDir);
569
- collections.sort((a, b) => a.slug.localeCompare(b.slug));
570
- if (collections.length === 0) {
571
- console.log(chalk.red(" ✗ No collections found. Nothing to generate."));
572
- process.exit(1);
573
- }
574
- console.log(chalk.green(` ✓ Found ${collections.length} collection(s): ${collections.map((c) => c.slug).join(", ")}`));
575
- console.log("");
576
- console.log(chalk.cyan(" → Generating SDK files..."));
577
- const files = sdkGenerator.generateSDK(collections);
578
- console.log(chalk.green(` ✓ Generated ${files.length} file(s)`));
579
- console.log(chalk.cyan(` → Writing to ${resolvedOutput}...`));
580
- writeFiles(resolvedOutput, files);
581
- console.log("");
582
- console.log(chalk.green.bold(" ✓ SDK generated successfully!"));
583
- console.log("");
584
- console.log(chalk.gray(" Usage:"));
585
- console.log(chalk.gray(" import { createRebaseClient } from '@rebasepro/client';"));
586
- console.log(chalk.gray(` import type { Database } from './${path.relative(cwd, path.join(resolvedOutput, "database.types"))}';`));
587
- console.log("");
588
- console.log(chalk.gray(" const rebase = createRebaseClient<Database>({"));
589
- console.log(chalk.gray(" baseUrl: 'http://localhost:3001',"));
590
- console.log(chalk.gray(" // token: 'your-jwt-token',"));
591
- console.log(chalk.gray(" });"));
592
- console.log("");
593
- console.log(chalk.gray(` const { data } = await rebase.collection('${collections[0]?.slug || "my_collection"}').find();`));
594
- console.log("");
595
- }
596
- function findProjectRoot(startDir = process.cwd()) {
597
- let dir = path.resolve(startDir);
598
- const root = path.parse(dir).root;
599
- while (dir !== root) {
600
- const pkgPath = path.join(dir, "package.json");
601
- if (fs.existsSync(pkgPath)) {
602
- try {
603
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
604
- if (pkg.workspaces && Array.isArray(pkg.workspaces)) {
605
- const hasBackend = pkg.workspaces.some(
606
- (w) => w === "backend" || w.includes("backend")
607
- );
608
- if (hasBackend) return dir;
609
- }
610
- } catch {
611
- }
612
- if (fs.existsSync(path.join(dir, "backend")) && fs.existsSync(path.join(dir, "config"))) {
613
- return dir;
614
- }
615
- }
616
- dir = path.dirname(dir);
617
- }
618
- return null;
619
- }
620
- function findBackendDir(projectRoot) {
621
- const backendDir = path.join(projectRoot, "backend");
622
- return fs.existsSync(backendDir) ? backendDir : null;
623
- }
624
- function getActiveBackendPlugin(backendDir) {
625
- const pkgPath = path.join(backendDir, "package.json");
626
- if (!fs.existsSync(pkgPath)) return null;
627
- try {
628
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
629
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
630
- const candidates = Object.keys(deps).filter(
631
- (dep) => dep.startsWith("@rebasepro/server-") && dep !== "@rebasepro/server-core"
632
- );
633
- if (candidates.length === 0) return null;
634
- if (candidates.includes("@rebasepro/server-postgresql")) {
635
- return "@rebasepro/server-postgresql";
636
- }
637
- for (const candidate of candidates) {
638
- if (resolvePluginCliScript(backendDir, candidate)) {
639
- return candidate;
640
- }
641
- }
642
- return candidates[0];
643
- } catch {
644
- }
645
- return null;
646
- }
647
- function resolvePluginCliScript(backendDir, pluginName) {
648
- const candidates = [
649
- path.join(backendDir, "node_modules", pluginName, "src", "cli.ts"),
650
- path.join(backendDir, "node_modules", pluginName, "dist", "cli.js"),
651
- // For monorepo dev mode:
652
- path.resolve(backendDir, "..", "..", "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"),
653
- path.resolve(backendDir, "..", "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"),
654
- path.resolve(backendDir, "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts")
655
- ];
656
- for (const candidate of candidates) {
657
- if (fs.existsSync(candidate)) return candidate;
658
- }
659
- return null;
660
- }
661
- function findFrontendDir(projectRoot) {
662
- const frontendDir = path.join(projectRoot, "frontend");
663
- return fs.existsSync(frontendDir) ? frontendDir : null;
664
- }
665
- function findEnvFile(projectRoot) {
666
- const candidates = [
667
- path.join(projectRoot, ".env"),
668
- path.join(projectRoot, "backend", ".env")
669
- ];
670
- for (const candidate of candidates) {
671
- if (fs.existsSync(candidate)) return candidate;
672
- }
673
- return null;
674
- }
675
- function resolveLocalBin(projectRoot, binName) {
676
- const candidates = [
677
- path.join(projectRoot, "backend", "node_modules", ".bin", binName),
678
- path.join(projectRoot, "node_modules", ".bin", binName)
679
- ];
680
- let parent = path.dirname(projectRoot);
681
- const rootDir = path.parse(parent).root;
682
- while (parent !== rootDir) {
683
- candidates.push(path.join(parent, "node_modules", ".bin", binName));
684
- parent = path.dirname(parent);
685
- }
686
- for (const candidate of candidates) {
687
- if (fs.existsSync(candidate)) return candidate;
688
- }
689
- try {
690
- const globalPath = child_process.execSync(`which ${binName}`, { encoding: "utf-8" }).trim();
691
- if (globalPath && fs.existsSync(globalPath)) return globalPath;
692
- } catch {
693
- }
694
- return null;
695
- }
696
- function resolveTsx(projectRoot) {
697
- return resolveLocalBin(projectRoot, "tsx");
698
- }
699
- function requireProjectRoot() {
700
- const root = findProjectRoot();
701
- if (!root) {
702
- console.error(chalk.red("✗ Could not find a Rebase project root."));
703
- console.error(chalk.gray(" Make sure you are inside a Rebase project directory"));
704
- console.error(chalk.gray(" (one with backend/, frontend/, and config/ directories)."));
705
- process.exit(1);
706
- }
707
- return root;
708
- }
709
- function requireBackendDir(projectRoot) {
710
- const backendDir = findBackendDir(projectRoot);
711
- if (!backendDir) {
712
- console.error(chalk.red("✗ Could not find a backend/ directory."));
713
- console.error(chalk.gray(` Expected at: ${path.join(projectRoot, "backend")}`));
714
- process.exit(1);
715
- }
716
- return backendDir;
717
- }
718
- async function schemaCommand(subcommand, rawArgs) {
719
- if (!subcommand || subcommand === "--help") {
720
- printSchemaHelp();
721
- return;
722
- }
723
- const projectRoot = requireProjectRoot();
724
- const backendDir = requireBackendDir(projectRoot);
725
- const activePlugin = getActiveBackendPlugin(backendDir);
726
- if (!activePlugin) {
727
- console.error(chalk.red("✗ Could not detect an active database plugin."));
728
- console.error(chalk.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
729
- process.exit(1);
730
- }
731
- const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
732
- if (!pluginCli) {
733
- console.error(chalk.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
734
- process.exit(1);
735
- }
736
- const envFile = findEnvFile(projectRoot);
737
- const env = { ...process.env };
738
- if (envFile) {
739
- env.DOTENV_CONFIG_PATH = envFile;
740
- }
741
- try {
742
- const isTs = pluginCli.endsWith(".ts");
743
- if (isTs) {
744
- const tsxBin = resolveTsx(projectRoot);
745
- if (!tsxBin) {
746
- console.error(chalk.red("✗ Could not find tsx binary."));
747
- process.exit(1);
748
- }
749
- await execa.execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
750
- cwd: backendDir,
751
- stdio: "inherit",
752
- env
753
- });
754
- } else {
755
- await execa.execa("node", [pluginCli, ...rawArgs.slice(2)], {
756
- cwd: backendDir,
757
- stdio: "inherit",
758
- env
759
- });
760
- }
761
- } catch {
762
- process.exit(1);
763
- }
764
- }
765
- function printSchemaHelp() {
766
- console.log(`
767
- ${chalk.bold("rebase schema")} — Schema management commands
200
+ await createProject(await promptForOptions(rawArgs, detectPackageManager()));
201
+ }
202
+ async function promptForOptions(rawArgs, pm) {
203
+ const args = (0, arg.default)({
204
+ "--git": Boolean,
205
+ "--install": Boolean,
206
+ "--database-url": String,
207
+ "--introspect": Boolean,
208
+ "--template": String,
209
+ "--yes": Boolean,
210
+ "-g": "--git",
211
+ "-i": "--install",
212
+ "-t": "--template",
213
+ "-y": "--yes"
214
+ }, {
215
+ argv: rawArgs.slice(3),
216
+ permissive: true
217
+ });
218
+ const nameArg = args._[0];
219
+ const isNonInteractive = args["--yes"] || false;
220
+ const templateArg = args["--template"];
221
+ if (templateArg && !PRESET_CHOICES.some((p) => p.value === templateArg)) {
222
+ console.error(chalk.default.red(`Unknown template "${templateArg}". Available: ${PRESET_CHOICES.map((p) => p.value).join(", ")}`));
223
+ process.exit(1);
224
+ }
225
+ if (isNonInteractive) {
226
+ const projectName = nameArg || "my-rebase-app";
227
+ const targetDirectory = path.default.resolve(process.cwd(), projectName);
228
+ const templateDirectory = path.default.resolve(cliRoot, "templates", "template");
229
+ const pmCommands = getPMCommands(pm);
230
+ return {
231
+ projectName: path.default.basename(targetDirectory),
232
+ git: args["--git"] ?? false,
233
+ installDeps: args["--install"] ?? false,
234
+ targetDirectory,
235
+ templateDirectory,
236
+ databaseUrl: args["--database-url"] || void 0,
237
+ introspect: args["--introspect"] || false,
238
+ preset: templateArg || "blog",
239
+ pm,
240
+ pmCommands
241
+ };
242
+ }
243
+ const questions = [];
244
+ if (!nameArg) questions.push({
245
+ type: "input",
246
+ name: "projectName",
247
+ message: "Project name:",
248
+ default: "my-rebase-app",
249
+ validate: (input) => {
250
+ if (!input.trim()) return "Project name is required";
251
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(input)) return "Project name must start with a lowercase letter or number and contain only lowercase letters, numbers, hyphens, dots, or underscores";
252
+ return true;
253
+ }
254
+ });
255
+ if (!templateArg) questions.push({
256
+ type: "list",
257
+ name: "preset",
258
+ message: "Choose a starter template:",
259
+ choices: PRESET_CHOICES,
260
+ default: "blog"
261
+ });
262
+ if (!args["--git"]) questions.push({
263
+ type: "confirm",
264
+ name: "git",
265
+ message: "Initialize a git repository?",
266
+ default: true
267
+ });
268
+ if (!args["--install"]) questions.push({
269
+ type: "confirm",
270
+ name: "installDeps",
271
+ message: `Install dependencies with ${pm}?`,
272
+ default: true
273
+ });
274
+ questions.push({
275
+ type: "input",
276
+ name: "databaseUrl",
277
+ message: "Enter your PostgreSQL database connection string (leave blank to use a local default):",
278
+ default: "",
279
+ validate: (input) => {
280
+ if (input.trim() && /[\r\n]/.test(input)) return "Database URL cannot contain newline characters.";
281
+ return true;
282
+ }
283
+ });
284
+ questions.push({
285
+ type: "confirm",
286
+ name: "introspect",
287
+ message: "Would you like to introspect this database to automatically generate collections?",
288
+ default: true,
289
+ when: (answers) => !!answers.databaseUrl?.trim()
290
+ });
291
+ const answers = await inquirer.default.prompt(questions);
292
+ const targetDirectory = path.default.resolve(process.cwd(), nameArg || answers.projectName);
293
+ const projectName = path.default.basename(targetDirectory);
294
+ const templateDirectory = path.default.resolve(cliRoot, "templates", "template");
295
+ const pmCommands = getPMCommands(pm);
296
+ return {
297
+ projectName,
298
+ git: args["--git"] || answers.git || false,
299
+ installDeps: args["--install"] || answers.installDeps || false,
300
+ targetDirectory,
301
+ templateDirectory,
302
+ databaseUrl: answers.databaseUrl?.trim() || void 0,
303
+ introspect: answers.introspect || false,
304
+ preset: templateArg || answers.preset || "blog",
305
+ pm,
306
+ pmCommands
307
+ };
308
+ }
309
+ async function createProject(options) {
310
+ if (fs.default.existsSync(options.targetDirectory)) {
311
+ if (fs.default.readdirSync(options.targetDirectory).length !== 0) {
312
+ console.error(`${chalk.default.red.bold("ERROR")} Directory "${options.projectName}" already exists and is not empty`);
313
+ process.exit(1);
314
+ }
315
+ } else fs.default.mkdirSync(options.targetDirectory, { recursive: true });
316
+ try {
317
+ await access(options.templateDirectory, fs.default.constants.R_OK);
318
+ } catch {
319
+ console.error(`${chalk.default.red.bold("ERROR")} Template not found at ${options.templateDirectory}`);
320
+ process.exit(1);
321
+ }
322
+ console.log(chalk.default.gray(" Copying project files..."));
323
+ try {
324
+ await (0, fs_promises.cp)(options.templateDirectory, options.targetDirectory, {
325
+ recursive: true,
326
+ filter: (source) => {
327
+ const basename = path.default.basename(source);
328
+ return basename !== "node_modules" && basename !== ".DS_Store";
329
+ }
330
+ });
331
+ } catch (err) {
332
+ console.error(`${chalk.default.red.bold("ERROR")} Failed to copy template files: ${err instanceof Error ? err.message : String(err)}`);
333
+ process.exit(1);
334
+ }
335
+ await applyPreset(options.targetDirectory, options.preset);
336
+ await replacePlaceholders(options);
337
+ await configureEnvFile(options.targetDirectory, options.databaseUrl);
338
+ if (options.git) {
339
+ console.log(chalk.default.gray(" Initializing git repository..."));
340
+ try {
341
+ await (0, execa.execa)("git", ["init"], { cwd: options.targetDirectory });
342
+ } catch {
343
+ console.warn(chalk.default.yellow(" Warning: Failed to initialize git repository"));
344
+ }
345
+ }
346
+ const { pm, pmCommands } = options;
347
+ const installCmd = pmCommands.install;
348
+ const execCmd = pmCommands.exec("rebase", [
349
+ "schema",
350
+ "introspect",
351
+ "--force"
352
+ ]);
353
+ if (options.installDeps) {
354
+ console.log("");
355
+ console.log(chalk.default.gray(` Installing dependencies with ${pm}...`));
356
+ console.log("");
357
+ try {
358
+ await (0, execa.execa)(installCmd[0], installCmd.slice(1), {
359
+ cwd: options.targetDirectory,
360
+ stdio: "inherit"
361
+ });
362
+ } catch {
363
+ console.warn(chalk.default.yellow(` Warning: Failed to install dependencies. You may need to run \`${installCmd.join(" ")}\` manually.`));
364
+ }
365
+ }
366
+ if (options.introspect) {
367
+ console.log("");
368
+ if (options.installDeps) {
369
+ console.log(chalk.default.gray(" Introspecting database and generating collections..."));
370
+ console.log("");
371
+ try {
372
+ await (0, execa.execa)(execCmd[0], execCmd.slice(1), {
373
+ cwd: options.targetDirectory,
374
+ stdio: "inherit"
375
+ });
376
+ console.log(chalk.default.green(" Database successfully introspected!"));
377
+ } catch {
378
+ console.warn(chalk.default.yellow(" Warning: Failed to introspect database automatically."));
379
+ console.warn(chalk.default.yellow(` You can run \`${execCmd.join(" ")}\` manually after setup.`));
380
+ }
381
+ } else {
382
+ console.warn(chalk.default.yellow(" Skipping introspection because dependencies were not installed."));
383
+ console.warn(chalk.default.yellow(` Run \`${installCmd.join(" ")}\` then \`${execCmd.join(" ")}\` manually.`));
384
+ }
385
+ }
386
+ console.log("");
387
+ console.log(`${chalk.default.green.bold("✓")} Project ${chalk.default.bold(options.projectName)} created successfully!`);
388
+ console.log("");
389
+ console.log(chalk.default.bold("Next steps:"));
390
+ console.log("");
391
+ const runDev = pmCommands.run("dev");
392
+ const runDbPush = pmCommands.run("db:push");
393
+ console.log(` ${chalk.default.cyan("cd")} ${options.projectName}`);
394
+ if (!options.installDeps) console.log(` ${chalk.default.cyan(installCmd.join(" "))}`);
395
+ console.log("");
396
+ if (options.databaseUrl) if (options.introspect) {
397
+ console.log(chalk.default.gray(" # Database has been introspected & collections generated!"));
398
+ console.log(chalk.default.gray(" # Start the development server (frontend + backend):"));
399
+ console.log(` ${chalk.default.cyan(runDev.join(" "))}`);
400
+ } else {
401
+ console.log(chalk.default.gray(" # Your custom database is configured in .env."));
402
+ console.log(chalk.default.gray(" # If the database is empty, push the Rebase schema to initialize it:"));
403
+ console.log(` ${chalk.default.cyan(runDbPush.join(" "))}`);
404
+ console.log("");
405
+ console.log(chalk.default.gray(" # Then start the development server:"));
406
+ console.log(` ${chalk.default.cyan(runDev.join(" "))}`);
407
+ }
408
+ else {
409
+ console.log(chalk.default.gray(" # A local database configuration has been generated in .env."));
410
+ console.log(chalk.default.gray(" # 1. Start the PostgreSQL database container:"));
411
+ console.log(` ${chalk.default.cyan("docker compose up -d db")}`);
412
+ console.log("");
413
+ console.log(chalk.default.gray(" # 2. Push the Rebase schema to initialize database tables:"));
414
+ console.log(` ${chalk.default.cyan(runDbPush.join(" "))}`);
415
+ console.log("");
416
+ console.log(chalk.default.gray(" # 3. Start the development server (frontend + backend):"));
417
+ console.log(` ${chalk.default.cyan(runDev.join(" "))}`);
418
+ }
419
+ console.log("");
420
+ console.log(chalk.default.gray("This starts both the backend (Hono + PostgreSQL)") + chalk.default.gray(" and the frontend (Vite + React) concurrently."));
421
+ console.log("");
422
+ console.log(chalk.default.gray("Docs: https://rebase.pro/docs"));
423
+ console.log(chalk.default.gray("GitHub: https://github.com/rebasepro/rebase"));
424
+ console.log("");
425
+ console.log(chalk.default.bold("🤖 AI Agent Skills"));
426
+ console.log("");
427
+ console.log(chalk.default.gray(" Install Rebase agent skills for your AI coding assistant:"));
428
+ console.log("");
429
+ console.log(` ${chalk.default.cyan("rebase skills install")} ${chalk.default.gray("or")} ${chalk.default.cyan(pmCommands.run("skills:install").join(" "))}`);
430
+ console.log("");
431
+ }
432
+ /**
433
+ * Apply a template preset by replacing the default collection files.
434
+ *
435
+ * The template ships with blog collections at the top level and
436
+ * preset alternatives under `config/collections/presets/<name>/`.
437
+ * This function swaps the active collection files and removes the
438
+ * presets directory so the final project is clean.
439
+ */
440
+ async function applyPreset(targetDirectory, preset) {
441
+ const collectionsDir = path.default.join(targetDirectory, "config", "collections");
442
+ const presetsDir = path.default.join(collectionsDir, "presets");
443
+ if (preset !== "blog") {
444
+ const presetDir = path.default.join(presetsDir, preset);
445
+ if (!fs.default.existsSync(presetDir)) {
446
+ console.warn(chalk.default.yellow(` Warning: Preset "${preset}" not found, falling back to blog template.`));
447
+ cleanupPresets(presetsDir);
448
+ return;
449
+ }
450
+ for (const file of [
451
+ "posts.ts",
452
+ "authors.ts",
453
+ "tags.ts",
454
+ "index.ts"
455
+ ]) {
456
+ const filePath = path.default.join(collectionsDir, file);
457
+ if (fs.default.existsSync(filePath)) fs.default.unlinkSync(filePath);
458
+ }
459
+ const presetFiles = fs.default.readdirSync(presetDir).filter((f) => f.endsWith(".ts"));
460
+ for (const file of presetFiles) fs.default.copyFileSync(path.default.join(presetDir, file), path.default.join(collectionsDir, file));
461
+ }
462
+ cleanupPresets(presetsDir);
463
+ }
464
+ function cleanupPresets(presetsDir) {
465
+ if (fs.default.existsSync(presetsDir)) fs.default.rmSync(presetsDir, {
466
+ recursive: true,
467
+ force: true
468
+ });
469
+ }
470
+ async function replacePlaceholders(options) {
471
+ const filesToProcess = [
472
+ "package.json",
473
+ "frontend/package.json",
474
+ "backend/package.json",
475
+ "config/package.json",
476
+ "frontend/index.html",
477
+ "pnpm-workspace.yaml",
478
+ "README.md"
479
+ ];
480
+ const packageJsonPath = path.default.resolve(cliRoot, "package.json");
481
+ let cliVersion = "latest";
482
+ if (fs.default.existsSync(packageJsonPath)) cliVersion = JSON.parse(fs.default.readFileSync(packageJsonPath, "utf-8")).version || "latest";
483
+ const versionCache = /* @__PURE__ */ new Map();
484
+ const viewBin = "npm";
485
+ const getPackageVersion = async (pkgName) => {
486
+ if (versionCache.has(pkgName)) return versionCache.get(pkgName);
487
+ if (process.env.REBASE_E2E === "true") {
488
+ versionCache.set(pkgName, cliVersion);
489
+ return cliVersion;
490
+ }
491
+ let versionToUse = cliVersion;
492
+ try {
493
+ const { stdout } = await (0, execa.execa)(viewBin, [
494
+ "view",
495
+ `${pkgName}@${cliVersion}`,
496
+ "version"
497
+ ]);
498
+ if (!stdout.trim()) throw new Error("Not found");
499
+ versionToUse = stdout.trim();
500
+ } catch {
501
+ try {
502
+ const { stdout } = await (0, execa.execa)(viewBin, [
503
+ "view",
504
+ `${pkgName}@${cliVersion.includes("canary") ? "canary" : "latest"}`,
505
+ "version"
506
+ ]);
507
+ if (!stdout.trim()) throw new Error("Not found");
508
+ versionToUse = stdout.trim();
509
+ } catch {
510
+ try {
511
+ const { stdout } = await (0, execa.execa)(viewBin, [
512
+ "view",
513
+ pkgName,
514
+ "version"
515
+ ]);
516
+ versionToUse = stdout.trim() || "latest";
517
+ } catch {
518
+ versionToUse = "latest";
519
+ }
520
+ }
521
+ }
522
+ versionCache.set(pkgName, versionToUse);
523
+ return versionToUse;
524
+ };
525
+ const allPackages = /* @__PURE__ */ new Set();
526
+ const fileContents = /* @__PURE__ */ new Map();
527
+ for (const file of filesToProcess) {
528
+ const fullPath = path.default.resolve(options.targetDirectory, file);
529
+ if (!fs.default.existsSync(fullPath)) continue;
530
+ const content = fs.default.readFileSync(fullPath, "utf-8");
531
+ fileContents.set(fullPath, content);
532
+ const matches = [...content.matchAll(/"(@rebasepro\/[^"]+)":\s*"workspace:\*"/g)];
533
+ for (const match of matches) allPackages.add(match[1]);
534
+ }
535
+ console.log(chalk.default.gray(" Resolving package versions..."));
536
+ await Promise.all(Array.from(allPackages).map(getPackageVersion));
537
+ for (const [fullPath, originalContent] of fileContents.entries()) {
538
+ let content = originalContent.replace(/\{\{PROJECT_NAME\}\}/g, options.projectName);
539
+ const matches = [...content.matchAll(/"(@rebasepro\/[^"]+)":\s*"workspace:\*"/g)];
540
+ for (const match of matches) {
541
+ const pkgName = match[1];
542
+ const resolvedVersion = versionCache.get(pkgName) || "latest";
543
+ content = content.replace(new RegExp(`"${pkgName}":\\s*"workspace:\\*"`, "g"), `"${pkgName}": "${resolvedVersion}"`);
544
+ }
545
+ fs.default.writeFileSync(fullPath, content, "utf-8");
546
+ }
547
+ }
548
+ async function isPortAvailable(port) {
549
+ return new Promise((resolve) => {
550
+ const server = net.default.createServer();
551
+ server.once("error", () => {
552
+ resolve(false);
553
+ });
554
+ server.once("listening", () => {
555
+ server.close(() => resolve(true));
556
+ });
557
+ server.listen(port);
558
+ });
559
+ }
560
+ async function findAvailablePort(startPort) {
561
+ let port = startPort;
562
+ while (!await isPortAvailable(port)) port++;
563
+ return port;
564
+ }
565
+ async function configureEnvFile(targetDirectory, databaseUrl) {
566
+ const envExamplePath = path.default.join(targetDirectory, ".env.example");
567
+ const envPath = path.default.join(targetDirectory, ".env");
568
+ if (fs.default.existsSync(envExamplePath) && !fs.default.existsSync(envPath)) {
569
+ fs.default.copyFileSync(envExamplePath, envPath);
570
+ const jwtSecret = crypto.default.randomBytes(32).toString("hex");
571
+ const dbPassword = crypto.default.randomBytes(16).toString("hex");
572
+ let envContent = fs.default.readFileSync(envPath, "utf-8");
573
+ envContent = envContent.replace(/^JWT_SECRET=.*$/m, `JWT_SECRET=${jwtSecret}`);
574
+ if (databaseUrl) {
575
+ if (/[\r\n]/.test(databaseUrl)) throw new Error("Invalid DATABASE_URL: multiline values are not allowed.");
576
+ envContent = envContent.replace(/^DATABASE_URL=.*$/m, `DATABASE_URL=${databaseUrl}`);
577
+ } else {
578
+ const dbPort = await findAvailablePort(5432);
579
+ envContent = envContent.replace(/^DATABASE_URL=.*$/m, `DATABASE_URL=postgresql://rebase:${dbPassword}@localhost:${dbPort}/rebase?options=-c%20search_path=public\nDATABASE_PASSWORD=${dbPassword}`);
580
+ const dockerComposePath = path.default.join(targetDirectory, "docker-compose.yml");
581
+ if (fs.default.existsSync(dockerComposePath)) {
582
+ let dockerComposeContent = fs.default.readFileSync(dockerComposePath, "utf-8");
583
+ dockerComposeContent = dockerComposeContent.replace(/-\s*"5432:5432"/g, `- "${dbPort}:5432"`);
584
+ fs.default.writeFileSync(dockerComposePath, dockerComposeContent, "utf-8");
585
+ }
586
+ }
587
+ fs.default.writeFileSync(envPath, envContent, "utf-8");
588
+ }
589
+ }
590
+ //#endregion
591
+ //#region src/commands/generate_sdk.ts
592
+ /**
593
+ * CLI command: generate-sdk
594
+ *
595
+ * Reads collection definitions from a specified directory (default: ./config/collections),
596
+ * generates a typed JS SDK, and writes it to the output directory (default: ./generated/sdk).
597
+ *
598
+ * Uses jiti for dynamic TypeScript import of collection files.
599
+ */
600
+ /**
601
+ * Dynamically load collection definitions from a directory.
602
+ *
603
+ * Expects the directory to have an index.ts/index.js that exports a default
604
+ * array of EntityCollection objects (matching the app/config/collections pattern).
605
+ */
606
+ async function loadCollections(collectionsDir) {
607
+ const absDir = path.default.resolve(collectionsDir);
608
+ if (!fs.default.existsSync(absDir)) throw new Error(`Collections directory not found: ${absDir}`);
609
+ let jiti;
610
+ try {
611
+ const jitiModule = await import("jiti");
612
+ jiti = jitiModule.default || jitiModule;
613
+ } catch {
614
+ const installCmd = [
615
+ ...getPMCommands(detectPackageManager()).install,
616
+ "-D",
617
+ "jiti"
618
+ ].join(" ");
619
+ throw new Error(`Could not load 'jiti'. Install it with: ${installCmd}\njiti is required to dynamically import TypeScript collection definitions.`);
620
+ }
621
+ const jitiInstance = jiti(absDir, {
622
+ interopDefault: true,
623
+ esmResolve: true
624
+ });
625
+ const indexCandidates = [
626
+ "index.ts",
627
+ "index.js",
628
+ "index.mjs"
629
+ ];
630
+ let indexPath = null;
631
+ for (const candidate of indexCandidates) {
632
+ const p = path.default.join(absDir, candidate);
633
+ if (fs.default.existsSync(p)) {
634
+ indexPath = p;
635
+ break;
636
+ }
637
+ }
638
+ if (!indexPath) {
639
+ console.log(chalk.default.yellow(" No index file found, scanning individual collection files..."));
640
+ const collections = [];
641
+ const files = fs.default.readdirSync(absDir).filter((f) => (f.endsWith(".ts") || f.endsWith(".js")) && !f.startsWith("."));
642
+ for (const file of files) try {
643
+ const mod = jitiInstance(path.default.join(absDir, file));
644
+ const exported = mod.default || mod;
645
+ if (exported && typeof exported === "object" && "slug" in exported) collections.push(exported);
646
+ else if (Array.isArray(exported)) collections.push(...exported);
647
+ } catch (err) {
648
+ console.warn(chalk.default.yellow(` ⚠ Skipping ${file}: ${err.message}`));
649
+ }
650
+ return collections;
651
+ }
652
+ const mod = jitiInstance(indexPath);
653
+ const exported = mod.default || mod;
654
+ if (Array.isArray(exported)) return exported;
655
+ else if (typeof exported === "object" && exported !== null) {
656
+ if ("collections" in exported && Array.isArray(exported.collections)) return exported.collections;
657
+ const collections = [];
658
+ for (const value of Object.values(exported)) if (value && typeof value === "object" && "slug" in value) collections.push(value);
659
+ if (collections.length > 0) return collections;
660
+ }
661
+ throw new Error(`Could not extract collections from ${indexPath}.\nExpected a default export of EntityCollection[] or an object with named collection exports.`);
662
+ }
663
+ /**
664
+ * Write generated files to the output directory.
665
+ */
666
+ function writeFiles(outputDir, files) {
667
+ const absOutput = path.default.resolve(outputDir);
668
+ fs.default.mkdirSync(absOutput, { recursive: true });
669
+ for (const file of files) {
670
+ const filePath = path.default.join(absOutput, file.path);
671
+ const dir = path.default.dirname(filePath);
672
+ if (!fs.default.existsSync(dir)) fs.default.mkdirSync(dir, { recursive: true });
673
+ fs.default.writeFileSync(filePath, file.content, "utf-8");
674
+ }
675
+ }
676
+ /**
677
+ * Main entry point for the generate-sdk command.
678
+ */
679
+ async function generateSdkCommand(args) {
680
+ const { collectionsDir, output, cwd } = args;
681
+ const resolvedCollectionsDir = path.default.isAbsolute(collectionsDir) ? collectionsDir : path.default.join(cwd, collectionsDir);
682
+ const resolvedOutput = path.default.isAbsolute(output) ? output : path.default.join(cwd, output);
683
+ console.log("");
684
+ console.log(chalk.default.bold(" 🔧 Rebase SDK Generator"));
685
+ console.log("");
686
+ console.log(` ${chalk.default.gray("Collections:")} ${resolvedCollectionsDir}`);
687
+ console.log(` ${chalk.default.gray("Output:")} ${resolvedOutput}`);
688
+ console.log("");
689
+ console.log(chalk.default.cyan(" Loading collection definitions..."));
690
+ const collections = await loadCollections(resolvedCollectionsDir);
691
+ collections.sort((a, b) => a.slug.localeCompare(b.slug));
692
+ if (collections.length === 0) {
693
+ console.log(chalk.default.red(" ✗ No collections found. Nothing to generate."));
694
+ process.exit(1);
695
+ }
696
+ console.log(chalk.default.green(` ✓ Found ${collections.length} collection(s): ${collections.map((c) => c.slug).join(", ")}`));
697
+ console.log("");
698
+ console.log(chalk.default.cyan(" → Generating SDK files..."));
699
+ const files = (0, _rebasepro_sdk_generator.generateSDK)(collections);
700
+ console.log(chalk.default.green(` ✓ Generated ${files.length} file(s)`));
701
+ console.log(chalk.default.cyan(` → Writing to ${resolvedOutput}...`));
702
+ writeFiles(resolvedOutput, files);
703
+ console.log("");
704
+ console.log(chalk.default.green.bold(" ✓ SDK generated successfully!"));
705
+ console.log("");
706
+ console.log(chalk.default.gray(" Usage:"));
707
+ console.log(chalk.default.gray(" import { createRebaseClient } from '@rebasepro/client';"));
708
+ console.log(chalk.default.gray(` import type { Database } from './${path.default.relative(cwd, path.default.join(resolvedOutput, "database.types"))}';`));
709
+ console.log("");
710
+ console.log(chalk.default.gray(" const rebase = createRebaseClient<Database>({"));
711
+ console.log(chalk.default.gray(" baseUrl: 'http://localhost:3001',"));
712
+ console.log(chalk.default.gray(" // token: 'your-jwt-token',"));
713
+ console.log(chalk.default.gray(" });"));
714
+ console.log("");
715
+ console.log(chalk.default.gray(` const { data } = await rebase.collection('${collections[0]?.slug || "my_collection"}').find();`));
716
+ console.log("");
717
+ }
718
+ //#endregion
719
+ //#region src/utils/project.ts
720
+ /**
721
+ * Project discovery utilities for the Rebase CLI.
722
+ *
723
+ * These helpers locate the project root, backend directory, .env file,
724
+ * and local binaries — used by all CLI command modules.
725
+ */
726
+ /**
727
+ * Walk up from `startDir` to find the Rebase project root.
728
+ *
729
+ * The root is identified by a `package.json` that either:
730
+ * - has `workspaces` containing "backend" or "frontend", OR
731
+ * - has a sibling `backend/` directory
732
+ */
733
+ function findProjectRoot(startDir = process.cwd()) {
734
+ let dir = path.default.resolve(startDir);
735
+ const root = path.default.parse(dir).root;
736
+ while (dir !== root) {
737
+ const pkgPath = path.default.join(dir, "package.json");
738
+ if (fs.default.existsSync(pkgPath)) {
739
+ try {
740
+ const pkg = JSON.parse(fs.default.readFileSync(pkgPath, "utf-8"));
741
+ if (pkg.workspaces && Array.isArray(pkg.workspaces)) {
742
+ if (pkg.workspaces.some((w) => w === "backend" || w.includes("backend"))) return dir;
743
+ }
744
+ } catch {}
745
+ if (fs.default.existsSync(path.default.join(dir, "backend")) && fs.default.existsSync(path.default.join(dir, "config"))) return dir;
746
+ }
747
+ dir = path.default.dirname(dir);
748
+ }
749
+ return null;
750
+ }
751
+ /**
752
+ * Locate the backend directory within the project root.
753
+ */
754
+ function findBackendDir(projectRoot) {
755
+ const backendDir = path.default.join(projectRoot, "backend");
756
+ return fs.default.existsSync(backendDir) ? backendDir : null;
757
+ }
758
+ /**
759
+ * Detect the active backend plugin (e.g. @rebasepro/server-postgresql) from the backend's package.json.
760
+ */
761
+ function getActiveBackendPlugin(backendDir) {
762
+ const pkgPath = path.default.join(backendDir, "package.json");
763
+ if (!fs.default.existsSync(pkgPath)) return null;
764
+ try {
765
+ const pkg = JSON.parse(fs.default.readFileSync(pkgPath, "utf-8"));
766
+ const deps = {
767
+ ...pkg.dependencies,
768
+ ...pkg.devDependencies
769
+ };
770
+ const candidates = Object.keys(deps).filter((dep) => dep.startsWith("@rebasepro/server-") && dep !== "@rebasepro/server-core");
771
+ if (candidates.length === 0) return null;
772
+ if (candidates.includes("@rebasepro/server-postgresql")) return "@rebasepro/server-postgresql";
773
+ for (const candidate of candidates) if (resolvePluginCliScript(backendDir, candidate)) return candidate;
774
+ return candidates[0];
775
+ } catch {}
776
+ return null;
777
+ }
778
+ /**
779
+ * Resolve the active plugin's CLI script.
780
+ */
781
+ function resolvePluginCliScript(backendDir, pluginName) {
782
+ const candidates = [
783
+ path.default.join(backendDir, "node_modules", pluginName, "src", "cli.ts"),
784
+ path.default.join(backendDir, "node_modules", pluginName, "dist", "cli.js"),
785
+ path.default.resolve(backendDir, "..", "..", "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"),
786
+ path.default.resolve(backendDir, "..", "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts"),
787
+ path.default.resolve(backendDir, "..", "packages", pluginName.replace("@rebasepro/", ""), "src", "cli.ts")
788
+ ];
789
+ for (const candidate of candidates) if (fs.default.existsSync(candidate)) return candidate;
790
+ return null;
791
+ }
792
+ /**
793
+ * Locate the frontend directory within the project root.
794
+ */
795
+ function findFrontendDir(projectRoot) {
796
+ const frontendDir = path.default.join(projectRoot, "frontend");
797
+ return fs.default.existsSync(frontendDir) ? frontendDir : null;
798
+ }
799
+ /**
800
+ * Find the .env file. Checks the project root first, then backend.
801
+ */
802
+ function findEnvFile(projectRoot) {
803
+ const candidates = [path.default.join(projectRoot, ".env"), path.default.join(projectRoot, "backend", ".env")];
804
+ for (const candidate of candidates) if (fs.default.existsSync(candidate)) return candidate;
805
+ return null;
806
+ }
807
+ /**
808
+ * Resolve a binary from the project's node_modules/.bin.
809
+ * Checks backend, root, parent monorepo root, then falls back to PATH.
810
+ */
811
+ function resolveLocalBin(projectRoot, binName) {
812
+ const candidates = [path.default.join(projectRoot, "backend", "node_modules", ".bin", binName), path.default.join(projectRoot, "node_modules", ".bin", binName)];
813
+ let parent = path.default.dirname(projectRoot);
814
+ const rootDir = path.default.parse(parent).root;
815
+ while (parent !== rootDir) {
816
+ candidates.push(path.default.join(parent, "node_modules", ".bin", binName));
817
+ parent = path.default.dirname(parent);
818
+ }
819
+ for (const candidate of candidates) if (fs.default.existsSync(candidate)) return candidate;
820
+ try {
821
+ const globalPath = (0, child_process.execSync)(`which ${binName}`, { encoding: "utf-8" }).trim();
822
+ if (globalPath && fs.default.existsSync(globalPath)) return globalPath;
823
+ } catch {}
824
+ return null;
825
+ }
826
+ /**
827
+ * Resolve the tsx binary. Checks backend node_modules first, then root.
828
+ */
829
+ function resolveTsx(projectRoot) {
830
+ return resolveLocalBin(projectRoot, "tsx");
831
+ }
832
+ /**
833
+ * Require the project root or exit with a helpful error.
834
+ */
835
+ function requireProjectRoot() {
836
+ const root = findProjectRoot();
837
+ if (!root) {
838
+ console.error(chalk.default.red("✗ Could not find a Rebase project root."));
839
+ console.error(chalk.default.gray(" Make sure you are inside a Rebase project directory"));
840
+ console.error(chalk.default.gray(" (one with backend/, frontend/, and config/ directories)."));
841
+ process.exit(1);
842
+ }
843
+ return root;
844
+ }
845
+ /**
846
+ * Require the backend directory or exit with a helpful error.
847
+ */
848
+ function requireBackendDir(projectRoot) {
849
+ const backendDir = findBackendDir(projectRoot);
850
+ if (!backendDir) {
851
+ console.error(chalk.default.red("✗ Could not find a backend/ directory."));
852
+ console.error(chalk.default.gray(` Expected at: ${path.default.join(projectRoot, "backend")}`));
853
+ process.exit(1);
854
+ }
855
+ return backendDir;
856
+ }
857
+ //#endregion
858
+ //#region src/commands/schema.ts
859
+ /**
860
+ * CLI command: rebase schema <action>
861
+ */
862
+ async function schemaCommand(subcommand, rawArgs) {
863
+ if (!subcommand || subcommand === "--help") {
864
+ printSchemaHelp();
865
+ return;
866
+ }
867
+ const projectRoot = requireProjectRoot();
868
+ const backendDir = requireBackendDir(projectRoot);
869
+ const activePlugin = getActiveBackendPlugin(backendDir);
870
+ if (!activePlugin) {
871
+ console.error(chalk.default.red("✗ Could not detect an active database plugin."));
872
+ console.error(chalk.default.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
873
+ process.exit(1);
874
+ }
875
+ const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
876
+ if (!pluginCli) {
877
+ console.error(chalk.default.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
878
+ process.exit(1);
879
+ }
880
+ const envFile = findEnvFile(projectRoot);
881
+ const env = { ...process.env };
882
+ if (envFile) env.DOTENV_CONFIG_PATH = envFile;
883
+ try {
884
+ if (pluginCli.endsWith(".ts")) {
885
+ const tsxBin = resolveTsx(projectRoot);
886
+ if (!tsxBin) {
887
+ console.error(chalk.default.red(" Could not find tsx binary."));
888
+ process.exit(1);
889
+ }
890
+ await (0, execa.execa)(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
891
+ cwd: backendDir,
892
+ stdio: "inherit",
893
+ env
894
+ });
895
+ } else await (0, execa.execa)("node", [pluginCli, ...rawArgs.slice(2)], {
896
+ cwd: backendDir,
897
+ stdio: "inherit",
898
+ env
899
+ });
900
+ } catch {
901
+ process.exit(1);
902
+ }
903
+ }
904
+ function printSchemaHelp() {
905
+ console.log(`
906
+ ${chalk.default.bold("rebase schema")} — Schema management commands
768
907
 
769
- ${chalk.green.bold("Usage")}
770
- rebase schema ${chalk.blue("<command>")} [options]
908
+ ${chalk.default.green.bold("Usage")}
909
+ rebase schema ${chalk.default.blue("<command>")} [options]
771
910
 
772
- ${chalk.green.bold("Commands")}
773
- ${chalk.gray("(Commands are provided by your active database driver plugin)")}
774
- ${chalk.blue.bold("generate")} Generate Schema from collection definitions
775
- ${chalk.blue.bold("introspect")} Introspect an existing database to generate collection definitions
911
+ ${chalk.default.green.bold("Commands")}
912
+ ${chalk.default.gray("(Commands are provided by your active database driver plugin)")}
913
+ ${chalk.default.blue.bold("generate")} Generate Schema from collection definitions
914
+ ${chalk.default.blue.bold("introspect")} Introspect an existing database to generate collection definitions
776
915
 
777
- ${chalk.green.bold("generate Options")}
778
- ${chalk.blue("--collections, -c")} Path to collections directory
779
- ${chalk.blue("--output, -o")} Output path for generated schema
780
- ${chalk.blue("--watch, -w")} Watch for changes and regenerate automatically
916
+ ${chalk.default.green.bold("generate Options")}
917
+ ${chalk.default.blue("--collections, -c")} Path to collections directory
918
+ ${chalk.default.blue("--output, -o")} Output path for generated schema
919
+ ${chalk.default.blue("--watch, -w")} Watch for changes and regenerate automatically
781
920
 
782
- ${chalk.green.bold("introspect Options")}
783
- ${chalk.blue("--output, -o")} Output directory for generated collection files
921
+ ${chalk.default.green.bold("introspect Options")}
922
+ ${chalk.default.blue("--output, -o")} Output directory for generated collection files
784
923
  `);
785
- }
786
- async function dbCommand(subcommand, rawArgs) {
787
- if (!subcommand || subcommand === "--help") {
788
- printDbHelp();
789
- return;
790
- }
791
- const projectRoot = requireProjectRoot();
792
- const backendDir = requireBackendDir(projectRoot);
793
- const activePlugin = getActiveBackendPlugin(backendDir);
794
- if (!activePlugin) {
795
- console.error(chalk.red("✗ Could not detect an active database plugin."));
796
- console.error(chalk.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
797
- process.exit(1);
798
- }
799
- const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
800
- if (!pluginCli) {
801
- console.error(chalk.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
802
- process.exit(1);
803
- }
804
- const envFile = findEnvFile(projectRoot);
805
- const env = { ...process.env };
806
- if (envFile) {
807
- env.DOTENV_CONFIG_PATH = envFile;
808
- }
809
- try {
810
- const isTs = pluginCli.endsWith(".ts");
811
- if (isTs) {
812
- const tsxBin = resolveTsx(projectRoot);
813
- if (!tsxBin) {
814
- console.error(chalk.red("✗ Could not find tsx binary."));
815
- process.exit(1);
816
- }
817
- await execa.execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
818
- cwd: backendDir,
819
- stdio: "inherit",
820
- env
821
- });
822
- } else {
823
- await execa.execa("node", [pluginCli, ...rawArgs.slice(2)], {
824
- cwd: backendDir,
825
- stdio: "inherit",
826
- env
827
- });
828
- }
829
- } catch {
830
- process.exit(1);
831
- }
832
- }
833
- function printDbHelp() {
834
- console.log(`
835
- ${chalk.bold("rebase db")} — Database management commands
924
+ }
925
+ //#endregion
926
+ //#region src/commands/db.ts
927
+ /**
928
+ * CLI command: rebase db <action>
929
+ */
930
+ async function dbCommand(subcommand, rawArgs) {
931
+ if (!subcommand || subcommand === "--help") {
932
+ printDbHelp();
933
+ return;
934
+ }
935
+ const projectRoot = requireProjectRoot();
936
+ const backendDir = requireBackendDir(projectRoot);
937
+ const activePlugin = getActiveBackendPlugin(backendDir);
938
+ if (!activePlugin) {
939
+ console.error(chalk.default.red("✗ Could not detect an active database plugin."));
940
+ console.error(chalk.default.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
941
+ process.exit(1);
942
+ }
943
+ const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
944
+ if (!pluginCli) {
945
+ console.error(chalk.default.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
946
+ process.exit(1);
947
+ }
948
+ const envFile = findEnvFile(projectRoot);
949
+ const env = { ...process.env };
950
+ if (envFile) env.DOTENV_CONFIG_PATH = envFile;
951
+ try {
952
+ if (pluginCli.endsWith(".ts")) {
953
+ const tsxBin = resolveTsx(projectRoot);
954
+ if (!tsxBin) {
955
+ console.error(chalk.default.red("✗ Could not find tsx binary."));
956
+ process.exit(1);
957
+ }
958
+ await (0, execa.execa)(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
959
+ cwd: backendDir,
960
+ stdio: "inherit",
961
+ env
962
+ });
963
+ } else await (0, execa.execa)("node", [pluginCli, ...rawArgs.slice(2)], {
964
+ cwd: backendDir,
965
+ stdio: "inherit",
966
+ env
967
+ });
968
+ } catch {
969
+ process.exit(1);
970
+ }
971
+ }
972
+ function printDbHelp() {
973
+ console.log(`
974
+ ${chalk.default.bold("rebase db")} — Database management commands
836
975
 
837
- ${chalk.green.bold("Usage")}
838
- rebase db ${chalk.blue("<command>")} [options]
976
+ ${chalk.default.green.bold("Usage")}
977
+ rebase db ${chalk.default.blue("<command>")} [options]
839
978
 
840
- ${chalk.green.bold("Commands")}
841
- ${chalk.gray("(Commands are provided by your active database driver plugin)")}
842
- ${chalk.blue.bold("push")} Apply schema directly to database (development)
843
- ${chalk.blue.bold("generate")} Generate migration files
844
- ${chalk.blue.bold("migrate")} Run pending migrations
845
- ${chalk.blue.bold("studio")} Open Studio viewer
846
- ${chalk.blue.bold("branch")} Database branching (create, list, delete, info)
979
+ ${chalk.default.green.bold("Commands")}
980
+ ${chalk.default.gray("(Commands are provided by your active database driver plugin)")}
981
+ ${chalk.default.blue.bold("push")} Apply schema directly to database (development)
982
+ ${chalk.default.blue.bold("generate")} Generate migration files
983
+ ${chalk.default.blue.bold("migrate")} Run pending migrations
984
+ ${chalk.default.blue.bold("studio")} Open Studio viewer
985
+ ${chalk.default.blue.bold("branch")} Database branching (create, list, delete, info)
847
986
 
848
- ${chalk.green.bold("Examples")}
849
- ${chalk.gray("# Quick development workflow")}
987
+ ${chalk.default.green.bold("Examples")}
988
+ ${chalk.default.gray("# Quick development workflow")}
850
989
  rebase schema generate && rebase db push
851
990
 
852
- ${chalk.gray("# Production migration workflow")}
991
+ ${chalk.default.gray("# Production migration workflow")}
853
992
  rebase db generate
854
993
  rebase db migrate
855
994
 
856
- ${chalk.gray("# Create a database branch")}
995
+ ${chalk.default.gray("# Create a database branch")}
857
996
  rebase db branch create feature_auth
858
997
  `);
859
- }
860
- const DEV_PORT_FILENAME = ".rebase-dev-port";
861
- function getProjectPort(projectRoot) {
862
- let hash = 0;
863
- for (let i = 0; i < projectRoot.length; i++) {
864
- hash = (hash << 5) - hash + projectRoot.charCodeAt(i) | 0;
865
- }
866
- return 3001 + Math.abs(hash) % 999;
867
- }
868
- function resolveStartPort(projectRoot, explicitPort) {
869
- if (explicitPort) return explicitPort;
870
- if (process.env.PORT) return parseInt(process.env.PORT, 10);
871
- try {
872
- const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
873
- if (fs.existsSync(portFile)) {
874
- const saved = parseInt(fs.readFileSync(portFile, "utf-8").trim(), 10);
875
- if (saved > 0 && saved < 65536) return saved;
876
- }
877
- } catch {
878
- }
879
- return getProjectPort(projectRoot);
880
- }
881
- async function devCommand(rawArgs) {
882
- const args = arg(
883
- {
884
- "--backend-only": Boolean,
885
- "--frontend-only": Boolean,
886
- "--port": Number,
887
- "--generate": Boolean,
888
- "--help": Boolean,
889
- "-b": "--backend-only",
890
- "-f": "--frontend-only",
891
- "-p": "--port",
892
- "-g": "--generate",
893
- "-h": "--help"
894
- },
895
- {
896
- argv: rawArgs.slice(3),
897
- // skip "node rebase dev"
898
- permissive: true
899
- }
900
- );
901
- if (args["--help"]) {
902
- printDevHelp();
903
- return;
904
- }
905
- const projectRoot = requireProjectRoot();
906
- const backendDir = findBackendDir(projectRoot);
907
- const frontendDir = findFrontendDir(projectRoot);
908
- const backendOnly = args["--backend-only"] || false;
909
- const frontendOnly = args["--frontend-only"] || false;
910
- const shouldGenerate = args["--generate"] || process.env.REBASE_AUTO_GENERATE === "true" || process.env.REBASE_GENERATE === "true";
911
- const startPort = resolveStartPort(projectRoot, args["--port"]);
912
- console.log("");
913
- console.log(chalk.bold(" 🚀 Rebase Dev Server"));
914
- console.log("");
915
- const children = [];
916
- let frontendUrl = "";
917
- let backendUrl = "";
918
- let debounceSummary = null;
919
- let bannerPrinted = false;
920
- let resolvedBackendPort = null;
921
- const stripAnsi = (str) => str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
922
- function printSummary() {
923
- if (!frontendUrl || !backendUrl) return;
924
- if (debounceSummary) clearTimeout(debounceSummary);
925
- debounceSummary = setTimeout(() => {
926
- if (bannerPrinted) return;
927
- console.log("");
928
- console.log(chalk.cyan("┌────────────────────────────────────────────────────────────┐"));
929
- console.log(chalk.cyan("│ │"));
930
- console.log(chalk.cyan("│ ✦ Rebase Admin App is ready! │"));
931
- const cleanUrl = stripAnsi(frontendUrl);
932
- const paddedUrl = cleanUrl.padEnd(41);
933
- console.log(chalk.cyan("│ ➜ Frontend URL: ") + chalk.white(paddedUrl) + chalk.cyan("│"));
934
- console.log(chalk.cyan("│ │"));
935
- console.log(chalk.cyan("└────────────────────────────────────────────────────────────┘"));
936
- console.log("");
937
- bannerPrinted = true;
938
- }, 500);
939
- }
940
- const cleanup = () => {
941
- try {
942
- const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
943
- if (fs.existsSync(portFile)) fs.unlinkSync(portFile);
944
- const urlFile = path.join(projectRoot, ".rebase-dev-url");
945
- if (fs.existsSync(urlFile)) fs.unlinkSync(urlFile);
946
- } catch {
947
- }
948
- children.forEach((child) => {
949
- if (child.pid && !child.killed) {
950
- try {
951
- if (process.platform === "win32") {
952
- execa.execaCommandSync(`taskkill /pid ${child.pid} /T /F`);
953
- } else {
954
- process.kill(-child.pid, "SIGKILL");
955
- }
956
- } catch (e) {
957
- try {
958
- child.kill("SIGKILL");
959
- } catch (err) {
960
- }
961
- }
962
- }
963
- });
964
- process.exit(0);
965
- };
966
- process.on("SIGINT", cleanup);
967
- process.on("SIGTERM", cleanup);
968
- function startFrontend(backendPort) {
969
- if (!frontendDir) return;
970
- console.log(` ${chalk.magenta("▶")} Frontend: ${chalk.gray(frontendDir)}`);
971
- const frontendEnv = { ...process.env };
972
- if (backendPort) {
973
- frontendEnv.VITE_API_URL = `http://localhost:${backendPort}`;
974
- console.log(` ${chalk.gray("↳ VITE_API_URL")} = ${chalk.white(`http://localhost:${backendPort}`)}`);
975
- }
976
- const pm = detectPackageManager(projectRoot);
977
- const pmCmds = getPMCommands(pm);
978
- const runDevCmd = pmCmds.run("dev");
979
- const frontendChild = execa.execa(
980
- runDevCmd[0],
981
- runDevCmd.slice(1),
982
- {
983
- cwd: frontendDir,
984
- stdio: ["inherit", "pipe", "pipe"],
985
- env: frontendEnv,
986
- shell: true,
987
- detached: process.platform !== "win32"
988
- }
989
- );
990
- frontendChild.catch(() => {
991
- });
992
- frontendChild.stdout?.on("data", (data) => {
993
- const lines = data.toString().split("\n").filter(Boolean);
994
- lines.forEach((line) => {
995
- console.log(`${chalk.magenta.bold("[admin]")} ${line}`);
996
- const cleanLine = stripAnsi(line);
997
- const urlMatch = cleanLine.match(/(http:\/\/(?:localhost|127\.0\.0\.1):\d+)/);
998
- if (cleanLine.includes("Local:") && urlMatch) {
999
- frontendUrl = urlMatch[1];
1000
- printSummary();
1001
- }
1002
- });
1003
- });
1004
- frontendChild.stderr?.on("data", (data) => {
1005
- const lines = data.toString().split("\n").filter(Boolean);
1006
- lines.forEach((line) => {
1007
- console.log(`${chalk.magenta.bold("[admin]")} ${line}`);
1008
- });
1009
- });
1010
- children.push(frontendChild);
1011
- }
1012
- if (!frontendOnly && backendDir) {
1013
- const tsxBin = resolveTsx(projectRoot);
1014
- if (!tsxBin) {
1015
- const pmName = detectPackageManager(projectRoot);
1016
- const addCmd = pmName === "npm" ? "npm install -D tsx" : "pnpm add -D tsx";
1017
- console.error(chalk.red(" ✗ Could not find tsx binary for backend."));
1018
- console.error(chalk.gray(` Install it with: ${addCmd}`));
1019
- process.exit(1);
1020
- }
1021
- const envFile = findEnvFile(projectRoot);
1022
- const env = { ...process.env };
1023
- if (envFile) {
1024
- env.DOTENV_CONFIG_PATH = envFile;
1025
- }
1026
- env.PORT = String(startPort);
1027
- console.log(` ${chalk.cyan("▶")} Backend: ${chalk.gray(backendDir)}`);
1028
- console.log(` ${chalk.gray("↳ PORT")} = ${chalk.white(String(startPort))}`);
1029
- let frontendLaunched = false;
1030
- if (shouldGenerate) {
1031
- console.log(chalk.gray(" → Ensuring schema and SDK are generated on start..."));
1032
- try {
1033
- const activePlugin = getActiveBackendPlugin(backendDir);
1034
- const pluginCli = activePlugin ? resolvePluginCliScript(backendDir, activePlugin) : null;
1035
- if (pluginCli) {
1036
- await execa.execa(tsxBin, [pluginCli, "schema", "generate"], {
1037
- cwd: backendDir,
1038
- stdio: "inherit",
1039
- env
1040
- });
1041
- }
1042
- await execa.execa("npx", ["rebase", "generate-sdk"], {
1043
- cwd: projectRoot,
1044
- stdio: "inherit",
1045
- env
1046
- });
1047
- console.log(chalk.green(" ✓ Initial schema and SDK generated successfully.\n"));
1048
- } catch (err) {
1049
- console.error(chalk.red(` ✗ Initial schema/SDK generation failed: ${err instanceof Error ? err.message : err}
1050
- `));
1051
- }
1052
- const collectionsDir = path.join(projectRoot, "config", "collections");
1053
- if (fs.existsSync(collectionsDir)) {
1054
- let watchDebounce = null;
1055
- fs.watch(collectionsDir, { recursive: true }, (eventType, filename) => {
1056
- if (!filename || filename.startsWith(".") || filename.endsWith(".tmp")) return;
1057
- if (watchDebounce) clearTimeout(watchDebounce);
1058
- watchDebounce = setTimeout(async () => {
1059
- console.log(chalk.yellow(`
1060
- 🔄 Collection change detected (${filename}). Regenerating schema & SDK...`));
1061
- try {
1062
- const activePlugin = getActiveBackendPlugin(backendDir);
1063
- const pluginCli = activePlugin ? resolvePluginCliScript(backendDir, activePlugin) : null;
1064
- if (pluginCli) {
1065
- await execa.execa(tsxBin, [pluginCli, "schema", "generate"], {
1066
- cwd: backendDir,
1067
- stdio: "inherit",
1068
- env
1069
- });
1070
- }
1071
- await execa.execa("npx", ["rebase", "generate-sdk"], {
1072
- cwd: projectRoot,
1073
- stdio: "inherit",
1074
- env
1075
- });
1076
- console.log(chalk.green(" ✓ Schema & SDK regenerated successfully. Hono will reload."));
1077
- } catch (err) {
1078
- console.error(chalk.red(` Failed to regenerate schema/SDK: ${err instanceof Error ? err.message : err}`));
1079
- }
1080
- }, 300);
1081
- });
1082
- }
1083
- }
1084
- const watchArgs = ["watch", "--conditions", "development", "src/index.ts"];
1085
- if (!shouldGenerate) {
1086
- watchArgs.splice(1, 0, `--watch="${path.join("..", "config", "**", "*")}"`);
1087
- }
1088
- const backendChild = execa.execa(
1089
- tsxBin,
1090
- watchArgs,
1091
- {
1092
- cwd: backendDir,
1093
- stdio: ["inherit", "pipe", "pipe"],
1094
- env,
1095
- shell: true,
1096
- detached: process.platform !== "win32"
1097
- }
1098
- );
1099
- backendChild.catch(() => {
1100
- });
1101
- backendChild.stdout?.on("data", (data) => {
1102
- const lines = data.toString().split("\n").filter(Boolean);
1103
- lines.forEach((line) => {
1104
- console.log(`${chalk.cyan.bold("[backend]")} ${line}`);
1105
- const cleanLine = stripAnsi(line);
1106
- const serverMatch = cleanLine.match(/Server running at http:\/\/(?:localhost|127\.0\.0\.1):(\d+)/);
1107
- if (serverMatch) {
1108
- resolvedBackendPort = parseInt(serverMatch[1], 10);
1109
- backendUrl = "started";
1110
- printSummary();
1111
- const urlFile = path.join(projectRoot, ".rebase-dev-url");
1112
- fs.writeFileSync(urlFile, `http://localhost:${resolvedBackendPort}`, "utf-8");
1113
- const portFile = path.join(projectRoot, DEV_PORT_FILENAME);
1114
- fs.writeFileSync(portFile, String(resolvedBackendPort), "utf-8");
1115
- if (!backendOnly && frontendDir && !frontendLaunched) {
1116
- frontendLaunched = true;
1117
- startFrontend(resolvedBackendPort);
1118
- }
1119
- }
1120
- });
1121
- });
1122
- backendChild.stderr?.on("data", (data) => {
1123
- const lines = data.toString().split("\n").filter(Boolean);
1124
- lines.forEach((line) => {
1125
- console.log(`${chalk.cyan.bold("[backend]")} ${line}`);
1126
- });
1127
- });
1128
- children.push(backendChild);
1129
- } else if (!frontendOnly && !backendDir) {
1130
- console.warn(chalk.yellow(" No backend/ directory found, skipping backend."));
1131
- }
1132
- if (!backendOnly && frontendDir && (frontendOnly || !backendDir)) {
1133
- startFrontend(null);
1134
- } else if (!backendOnly && !frontendDir) {
1135
- console.warn(chalk.yellow(" ⚠ No frontend/ directory found, skipping frontend."));
1136
- }
1137
- if (children.length === 0) {
1138
- console.error(chalk.red(" ✗ Nothing to start. Check your project structure."));
1139
- process.exit(1);
1140
- }
1141
- console.log("");
1142
- console.log(chalk.gray(" Press Ctrl+C to stop all servers."));
1143
- console.log("");
1144
- await Promise.all(
1145
- children.map(
1146
- (child) => new Promise((resolve) => {
1147
- child.finally(() => resolve());
1148
- })
1149
- )
1150
- );
1151
- }
1152
- function printDevHelp() {
1153
- console.log(`
1154
- ${chalk.bold("rebase dev")} — Start the development server
998
+ }
999
+ //#endregion
1000
+ //#region src/commands/dev.ts
1001
+ /**
1002
+ * CLI command: rebase dev
1003
+ *
1004
+ * Starts the full development environment:
1005
+ * - Backend: tsx watch with auto-reload
1006
+ * - Frontend: vite dev server
1007
+ *
1008
+ * Both processes stream output with color-coded prefixes.
1009
+ *
1010
+ * When the backend uses port-retry (i.e. the configured port is busy and it
1011
+ * binds to the next free one), the CLI detects the actual port from stdout
1012
+ * and injects VITE_API_URL into the frontend so it connects automatically.
1013
+ *
1014
+ * Each project gets a deterministic default port derived from the project
1015
+ * root path, so multiple Rebase instances never collide.
1016
+ */
1017
+ /** Well-known filename the backend writes its actual port to. */
1018
+ var DEV_PORT_FILENAME = ".rebase-dev-port";
1019
+ /**
1020
+ * Compute a deterministic port from the project root path.
1021
+ * Range: 3001–3999 (avoids privileged ports and common services).
1022
+ * Two different project directories will almost always get different ports.
1023
+ */
1024
+ function getProjectPort(projectRoot) {
1025
+ let hash = 0;
1026
+ for (let i = 0; i < projectRoot.length; i++) hash = (hash << 5) - hash + projectRoot.charCodeAt(i) | 0;
1027
+ return 3001 + Math.abs(hash) % 999;
1028
+ }
1029
+ /**
1030
+ * Resolve the best starting port for this project:
1031
+ * 1. Explicit --port flag (highest priority)
1032
+ * 2. PORT env var
1033
+ * 3. Previously used port from .rebase-dev-port (port affinity across restarts)
1034
+ * 4. Deterministic hash from project path (unique per project)
1035
+ */
1036
+ function resolveStartPort(projectRoot, explicitPort) {
1037
+ if (explicitPort) return explicitPort;
1038
+ if (process.env.PORT) return parseInt(process.env.PORT, 10);
1039
+ try {
1040
+ const portFile = path.default.join(projectRoot, DEV_PORT_FILENAME);
1041
+ if (fs.default.existsSync(portFile)) {
1042
+ const saved = parseInt(fs.default.readFileSync(portFile, "utf-8").trim(), 10);
1043
+ if (saved > 0 && saved < 65536) return saved;
1044
+ }
1045
+ } catch {}
1046
+ return getProjectPort(projectRoot);
1047
+ }
1048
+ async function devCommand(rawArgs) {
1049
+ const args = (0, arg.default)({
1050
+ "--backend-only": Boolean,
1051
+ "--frontend-only": Boolean,
1052
+ "--port": Number,
1053
+ "--generate": Boolean,
1054
+ "--help": Boolean,
1055
+ "-b": "--backend-only",
1056
+ "-f": "--frontend-only",
1057
+ "-p": "--port",
1058
+ "-g": "--generate",
1059
+ "-h": "--help"
1060
+ }, {
1061
+ argv: rawArgs.slice(3),
1062
+ permissive: true
1063
+ });
1064
+ if (args["--help"]) {
1065
+ printDevHelp();
1066
+ return;
1067
+ }
1068
+ const projectRoot = requireProjectRoot();
1069
+ const backendDir = findBackendDir(projectRoot);
1070
+ const frontendDir = findFrontendDir(projectRoot);
1071
+ const backendOnly = args["--backend-only"] || false;
1072
+ const frontendOnly = args["--frontend-only"] || false;
1073
+ const shouldGenerate = args["--generate"] || process.env.REBASE_AUTO_GENERATE === "true" || process.env.REBASE_GENERATE === "true";
1074
+ const startPort = resolveStartPort(projectRoot, args["--port"]);
1075
+ console.log("");
1076
+ console.log(chalk.default.bold(" 🚀 Rebase Dev Server"));
1077
+ console.log("");
1078
+ const children = [];
1079
+ let frontendUrl = "";
1080
+ let backendUrl = "";
1081
+ let debounceSummary = null;
1082
+ let bannerPrinted = false;
1083
+ /** Actual backend port, resolved once the server prints its URL. */
1084
+ let resolvedBackendPort = null;
1085
+ const stripAnsi = (str) => str.replace(/[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, "");
1086
+ function printSummary() {
1087
+ if (!frontendUrl || !backendUrl) return;
1088
+ if (debounceSummary) clearTimeout(debounceSummary);
1089
+ debounceSummary = setTimeout(() => {
1090
+ if (bannerPrinted) return;
1091
+ console.log("");
1092
+ console.log(chalk.default.cyan("┌────────────────────────────────────────────────────────────┐"));
1093
+ console.log(chalk.default.cyan("│ │"));
1094
+ console.log(chalk.default.cyan("│ ✦ Rebase Admin App is ready! │"));
1095
+ const paddedUrl = stripAnsi(frontendUrl).padEnd(41);
1096
+ console.log(chalk.default.cyan("│ ➜ Frontend URL: ") + chalk.default.white(paddedUrl) + chalk.default.cyan("│"));
1097
+ console.log(chalk.default.cyan("│ │"));
1098
+ console.log(chalk.default.cyan("└────────────────────────────────────────────────────────────┘"));
1099
+ console.log("");
1100
+ bannerPrinted = true;
1101
+ }, 500);
1102
+ }
1103
+ const cleanup = () => {
1104
+ try {
1105
+ const portFile = path.default.join(projectRoot, DEV_PORT_FILENAME);
1106
+ if (fs.default.existsSync(portFile)) fs.default.unlinkSync(portFile);
1107
+ const urlFile = path.default.join(projectRoot, ".rebase-dev-url");
1108
+ if (fs.default.existsSync(urlFile)) fs.default.unlinkSync(urlFile);
1109
+ } catch {}
1110
+ children.forEach((child) => {
1111
+ if (child.pid && !child.killed) try {
1112
+ if (process.platform === "win32") (0, execa.execaCommandSync)(`taskkill /pid ${child.pid} /T /F`);
1113
+ else process.kill(-child.pid, "SIGKILL");
1114
+ } catch (e) {
1115
+ try {
1116
+ child.kill("SIGKILL");
1117
+ } catch (err) {}
1118
+ }
1119
+ });
1120
+ process.exit(0);
1121
+ };
1122
+ process.on("SIGINT", cleanup);
1123
+ process.on("SIGTERM", cleanup);
1124
+ /**
1125
+ * Start the Vite frontend, optionally injecting the backend port.
1126
+ */
1127
+ function startFrontend(backendPort) {
1128
+ if (!frontendDir) return;
1129
+ console.log(` ${chalk.default.magenta("▶")} Frontend: ${chalk.default.gray(frontendDir)}`);
1130
+ const frontendEnv = { ...process.env };
1131
+ if (backendPort) {
1132
+ frontendEnv.VITE_API_URL = `http://localhost:${backendPort}`;
1133
+ console.log(` ${chalk.default.gray("↳ VITE_API_URL")} = ${chalk.default.white(`http://localhost:${backendPort}`)}`);
1134
+ }
1135
+ const runDevCmd = getPMCommands(detectPackageManager(projectRoot)).run("dev");
1136
+ const frontendChild = (0, execa.execa)(runDevCmd[0], runDevCmd.slice(1), {
1137
+ cwd: frontendDir,
1138
+ stdio: [
1139
+ "inherit",
1140
+ "pipe",
1141
+ "pipe"
1142
+ ],
1143
+ env: frontendEnv,
1144
+ shell: true,
1145
+ detached: process.platform !== "win32"
1146
+ });
1147
+ frontendChild.catch(() => {});
1148
+ frontendChild.stdout?.on("data", (data) => {
1149
+ data.toString().split("\n").filter(Boolean).forEach((line) => {
1150
+ console.log(`${chalk.default.magenta.bold("[admin]")} ${line}`);
1151
+ const cleanLine = stripAnsi(line);
1152
+ const urlMatch = cleanLine.match(/(http:\/\/(?:localhost|127\.0\.0\.1):\d+)/);
1153
+ if (cleanLine.includes("Local:") && urlMatch) {
1154
+ frontendUrl = urlMatch[1];
1155
+ printSummary();
1156
+ }
1157
+ });
1158
+ });
1159
+ frontendChild.stderr?.on("data", (data) => {
1160
+ data.toString().split("\n").filter(Boolean).forEach((line) => {
1161
+ console.log(`${chalk.default.magenta.bold("[admin]")} ${line}`);
1162
+ });
1163
+ });
1164
+ children.push(frontendChild);
1165
+ }
1166
+ if (!frontendOnly && backendDir) {
1167
+ const tsxBin = resolveTsx(projectRoot);
1168
+ if (!tsxBin) {
1169
+ const addCmd = [
1170
+ ...getPMCommands(detectPackageManager(projectRoot)).install,
1171
+ "-D",
1172
+ "tsx"
1173
+ ].join(" ");
1174
+ console.error(chalk.default.red(" ✗ Could not find tsx binary for backend."));
1175
+ console.error(chalk.default.gray(` Install it with: ${addCmd}`));
1176
+ process.exit(1);
1177
+ }
1178
+ const envFile = findEnvFile(projectRoot);
1179
+ const env = { ...process.env };
1180
+ if (envFile) env.DOTENV_CONFIG_PATH = envFile;
1181
+ env.PORT = String(startPort);
1182
+ console.log(` ${chalk.default.cyan("▶")} Backend: ${chalk.default.gray(backendDir)}`);
1183
+ console.log(` ${chalk.default.gray("↳ PORT")} = ${chalk.default.white(String(startPort))}`);
1184
+ /** Whether the frontend has been launched (we only launch it once). */
1185
+ let frontendLaunched = false;
1186
+ if (shouldGenerate) {
1187
+ console.log(chalk.default.gray(" → Ensuring schema and SDK are generated on start..."));
1188
+ try {
1189
+ const activePlugin = getActiveBackendPlugin(backendDir);
1190
+ const pluginCli = activePlugin ? resolvePluginCliScript(backendDir, activePlugin) : null;
1191
+ if (pluginCli) await (0, execa.execa)(tsxBin, [
1192
+ pluginCli,
1193
+ "schema",
1194
+ "generate"
1195
+ ], {
1196
+ cwd: backendDir,
1197
+ stdio: "inherit",
1198
+ env
1199
+ });
1200
+ const sdkCmd = getPMCommands(detectPackageManager(projectRoot)).exec("rebase", ["generate-sdk"]);
1201
+ await (0, execa.execa)(sdkCmd[0], sdkCmd.slice(1), {
1202
+ cwd: projectRoot,
1203
+ stdio: "inherit",
1204
+ env
1205
+ });
1206
+ console.log(chalk.default.green(" ✓ Initial schema and SDK generated successfully.\n"));
1207
+ } catch (err) {
1208
+ console.error(chalk.default.red(` ✗ Initial schema/SDK generation failed: ${err instanceof Error ? err.message : err}\n`));
1209
+ }
1210
+ const collectionsDir = path.default.join(projectRoot, "config", "collections");
1211
+ if (fs.default.existsSync(collectionsDir)) {
1212
+ let watchDebounce = null;
1213
+ fs.default.watch(collectionsDir, { recursive: true }, (eventType, filename) => {
1214
+ if (!filename || filename.startsWith(".") || filename.endsWith(".tmp")) return;
1215
+ if (watchDebounce) clearTimeout(watchDebounce);
1216
+ watchDebounce = setTimeout(async () => {
1217
+ console.log(chalk.default.yellow(`\n 🔄 Collection change detected (${filename}). Regenerating schema & SDK...`));
1218
+ try {
1219
+ const activePlugin = getActiveBackendPlugin(backendDir);
1220
+ const pluginCli = activePlugin ? resolvePluginCliScript(backendDir, activePlugin) : null;
1221
+ if (pluginCli) await (0, execa.execa)(tsxBin, [
1222
+ pluginCli,
1223
+ "schema",
1224
+ "generate"
1225
+ ], {
1226
+ cwd: backendDir,
1227
+ stdio: "inherit",
1228
+ env
1229
+ });
1230
+ const sdkCmd = getPMCommands(detectPackageManager(projectRoot)).exec("rebase", ["generate-sdk"]);
1231
+ await (0, execa.execa)(sdkCmd[0], sdkCmd.slice(1), {
1232
+ cwd: projectRoot,
1233
+ stdio: "inherit",
1234
+ env
1235
+ });
1236
+ console.log(chalk.default.green(" ✓ Schema & SDK regenerated successfully. Hono will reload."));
1237
+ } catch (err) {
1238
+ console.error(chalk.default.red(` ✗ Failed to regenerate schema/SDK: ${err instanceof Error ? err.message : err}`));
1239
+ }
1240
+ }, 300);
1241
+ });
1242
+ }
1243
+ }
1244
+ const watchArgs = [
1245
+ "watch",
1246
+ "--conditions",
1247
+ "development",
1248
+ "src/index.ts"
1249
+ ];
1250
+ if (!shouldGenerate) {
1251
+ watchArgs.splice(1, 0, `--watch="${path.default.join("..", "config", "**", "*")}"`);
1252
+ const collectionsDir = path.default.join(projectRoot, "config", "collections");
1253
+ if (fs.default.existsSync(collectionsDir)) {
1254
+ let driftDebounce = null;
1255
+ fs.default.watch(collectionsDir, { recursive: true }, (_eventType, filename) => {
1256
+ if (!filename || filename.startsWith(".") || filename.endsWith(".tmp")) return;
1257
+ if (driftDebounce) clearTimeout(driftDebounce);
1258
+ driftDebounce = setTimeout(() => {
1259
+ console.log([
1260
+ "",
1261
+ chalk.default.yellow(" ┌──────────────────────────────────────────────────────────────┐"),
1262
+ chalk.default.yellow(" │ ⚠️ Collection file changed: ") + chalk.default.white(filename.padEnd(31)) + chalk.default.yellow(""),
1263
+ chalk.default.yellow(" │ │"),
1264
+ chalk.default.yellow(" │ Your schema may be out of sync. Run: │"),
1265
+ chalk.default.yellow(" │ ") + chalk.default.cyan("rebase schema generate") + chalk.default.yellow(" regenerate Drizzle schema │"),
1266
+ chalk.default.yellow(" │ ") + chalk.default.cyan("rebase db push ") + chalk.default.yellow(" sync schema to database │"),
1267
+ chalk.default.yellow(" │ ") + chalk.default.cyan("rebase doctor ") + chalk.default.yellow(" check for drift │"),
1268
+ chalk.default.yellow(" │ │"),
1269
+ chalk.default.yellow(" │ TIP: Use ") + chalk.default.bold("rebase dev --generate") + chalk.default.yellow(" for auto-regeneration │"),
1270
+ chalk.default.yellow(" └──────────────────────────────────────────────────────────────┘"),
1271
+ ""
1272
+ ].join("\n"));
1273
+ }, 500);
1274
+ });
1275
+ }
1276
+ }
1277
+ const backendChild = (0, execa.execa)(tsxBin, watchArgs, {
1278
+ cwd: backendDir,
1279
+ stdio: [
1280
+ "inherit",
1281
+ "pipe",
1282
+ "pipe"
1283
+ ],
1284
+ env,
1285
+ shell: true,
1286
+ detached: process.platform !== "win32"
1287
+ });
1288
+ backendChild.catch(() => {});
1289
+ backendChild.stdout?.on("data", (data) => {
1290
+ data.toString().split("\n").filter(Boolean).forEach((line) => {
1291
+ console.log(`${chalk.default.cyan.bold("[backend]")} ${line}`);
1292
+ const serverMatch = stripAnsi(line).match(/Server running at http:\/\/(?:localhost|127\.0\.0\.1):(\d+)/);
1293
+ if (serverMatch) {
1294
+ resolvedBackendPort = parseInt(serverMatch[1], 10);
1295
+ backendUrl = "started";
1296
+ printSummary();
1297
+ const urlFile = path.default.join(projectRoot, ".rebase-dev-url");
1298
+ fs.default.writeFileSync(urlFile, `http://localhost:${resolvedBackendPort}`, "utf-8");
1299
+ const portFile = path.default.join(projectRoot, DEV_PORT_FILENAME);
1300
+ fs.default.writeFileSync(portFile, String(resolvedBackendPort), "utf-8");
1301
+ if (!backendOnly && frontendDir && !frontendLaunched) {
1302
+ frontendLaunched = true;
1303
+ startFrontend(resolvedBackendPort);
1304
+ }
1305
+ }
1306
+ });
1307
+ });
1308
+ backendChild.stderr?.on("data", (data) => {
1309
+ data.toString().split("\n").filter(Boolean).forEach((line) => {
1310
+ console.log(`${chalk.default.cyan.bold("[backend]")} ${line}`);
1311
+ });
1312
+ });
1313
+ children.push(backendChild);
1314
+ } else if (!frontendOnly && !backendDir) console.warn(chalk.default.yellow(" ⚠ No backend/ directory found, skipping backend."));
1315
+ if (!backendOnly && frontendDir && (frontendOnly || !backendDir)) startFrontend(null);
1316
+ else if (!backendOnly && !frontendDir) console.warn(chalk.default.yellow(" ⚠ No frontend/ directory found, skipping frontend."));
1317
+ if (children.length === 0) {
1318
+ console.error(chalk.default.red(" ✗ Nothing to start. Check your project structure."));
1319
+ process.exit(1);
1320
+ }
1321
+ console.log("");
1322
+ console.log(chalk.default.gray(" Press Ctrl+C to stop all servers."));
1323
+ console.log("");
1324
+ await Promise.all(children.map((child) => new Promise((resolve) => {
1325
+ child.finally(() => resolve());
1326
+ })));
1327
+ }
1328
+ function printDevHelp() {
1329
+ console.log(`
1330
+ ${chalk.default.bold("rebase dev")} — Start the development server
1155
1331
 
1156
- ${chalk.green.bold("Usage")}
1332
+ ${chalk.default.green.bold("Usage")}
1157
1333
  rebase dev [options]
1158
1334
 
1159
- ${chalk.green.bold("Options")}
1160
- ${chalk.blue("--backend-only, -b")} Only start the backend server
1161
- ${chalk.blue("--frontend-only, -f")} Only start the frontend server
1162
- ${chalk.blue("--port, -p")} Backend port (default: auto-detected per project)
1163
- ${chalk.blue("--generate, -g")} Enable automatic schema and SDK generation on startup and file changes
1335
+ ${chalk.default.green.bold("Options")}
1336
+ ${chalk.default.blue("--backend-only, -b")} Only start the backend server
1337
+ ${chalk.default.blue("--frontend-only, -f")} Only start the frontend server
1338
+ ${chalk.default.blue("--port, -p")} Backend port (default: auto-detected per project)
1339
+ ${chalk.default.blue("--generate, -g")} Enable automatic schema and SDK generation on startup and file changes
1164
1340
 
1165
- ${chalk.green.bold("Description")}
1341
+ ${chalk.default.green.bold("Description")}
1166
1342
  Starts both the backend (tsx watch + Hono) and frontend (Vite)
1167
1343
  dev servers concurrently with color-coded output prefixes.
1168
1344
 
@@ -1178,102 +1354,115 @@ ${chalk.green.bold("Description")}
1178
1354
  and file changes. Pass --generate (-g) or set REBASE_AUTO_GENERATE=true
1179
1355
  in your environment to enable it.
1180
1356
  `);
1181
- }
1182
- async function buildCommand() {
1183
- const projectRoot = requireProjectRoot();
1184
- const pm = detectPackageManager(projectRoot);
1185
- const cmds = getPMCommands(pm);
1186
- const buildCmd = cmds.runAll("build");
1187
- console.log(`${chalk.bold("Rebase")} Building all workspaces with ${chalk.cyan(pm)}...
1188
- `);
1189
- try {
1190
- await execa.execa(buildCmd[0], buildCmd.slice(1), {
1191
- cwd: projectRoot,
1192
- stdio: "inherit"
1193
- });
1194
- } catch {
1195
- console.error(chalk.red("\n✗ Build failed."));
1196
- process.exit(1);
1197
- }
1198
- }
1199
- async function startCommand() {
1200
- const projectRoot = requireProjectRoot();
1201
- const pm = detectPackageManager(projectRoot);
1202
- const cmds = getPMCommands(pm);
1203
- const startCmd = cmds.runWorkspace("backend", "start");
1204
- const envFile = findEnvFile(projectRoot);
1205
- const env = { ...process.env };
1206
- if (envFile) {
1207
- env.DOTENV_CONFIG_PATH = envFile;
1208
- }
1209
- console.log(`${chalk.bold("Rebase")} Starting backend server...
1210
- `);
1211
- try {
1212
- await execa.execa(startCmd[0], startCmd.slice(1), {
1213
- cwd: projectRoot,
1214
- stdio: "inherit",
1215
- env
1216
- });
1217
- } catch {
1218
- console.error(chalk.red("\n✗ Failed to start server."));
1219
- process.exit(1);
1220
- }
1221
- }
1222
- async function authCommand(subcommand, rawArgs) {
1223
- if (!subcommand || subcommand === "--help") {
1224
- printAuthHelp();
1225
- return;
1226
- }
1227
- switch (subcommand) {
1228
- case "reset-password":
1229
- await resetPassword(rawArgs);
1230
- break;
1231
- default:
1232
- console.error(chalk.red(`Unknown auth command: ${subcommand}`));
1233
- console.log("");
1234
- printAuthHelp();
1235
- process.exit(1);
1236
- }
1237
- }
1238
- async function resetPassword(rawArgs) {
1239
- const args = arg(
1240
- {
1241
- "--email": String,
1242
- "--password": String,
1243
- "-e": "--email",
1244
- "-p": "--password"
1245
- },
1246
- {
1247
- argv: rawArgs.slice(4),
1248
- // skip "node rebase auth reset-password"
1249
- permissive: true
1250
- }
1251
- );
1252
- const email = args["--email"] || args._[0];
1253
- const newPassword = args["--password"] || args._[1];
1254
- if (!email) {
1255
- console.error(chalk.red("✗ Email is required."));
1256
- console.log("");
1257
- console.log(chalk.gray(" Usage: rebase auth reset-password <email> [new-password]"));
1258
- console.log(chalk.gray(" rebase auth reset-password --email user@example.com --password NewPass123!"));
1259
- process.exit(1);
1260
- }
1261
- const projectRoot = requireProjectRoot();
1262
- const backendDir = requireBackendDir(projectRoot);
1263
- const tsxBin = resolveTsx(projectRoot);
1264
- if (!tsxBin) {
1265
- console.error(chalk.red("✗ Could not find tsx binary."));
1266
- process.exit(1);
1267
- }
1268
- const envFile = findEnvFile(projectRoot);
1269
- const env = { ...process.env };
1270
- if (envFile) {
1271
- env.DOTENV_CONFIG_PATH = envFile;
1272
- }
1273
- env.REBASE_RESET_EMAIL = email;
1274
- env.REBASE_RESET_PASSWORD = newPassword || "NewPassword123!";
1275
- env.REBASE_ENV_FILE_PATH = envFile || path.join(projectRoot, ".env");
1276
- const scriptContent = `
1357
+ }
1358
+ //#endregion
1359
+ //#region src/commands/build.ts
1360
+ /**
1361
+ * CLI command: rebase build
1362
+ *
1363
+ * Runs the build script in all workspace packages.
1364
+ * Automatically detects the package manager (pnpm or npm) and
1365
+ * uses the correct workspace-aware command.
1366
+ */
1367
+ async function buildCommand() {
1368
+ const projectRoot = requireProjectRoot();
1369
+ const pm = detectPackageManager(projectRoot);
1370
+ const buildCmd = getPMCommands(pm).runAll("build");
1371
+ console.log(`${chalk.default.bold("Rebase")} Building all workspaces with ${chalk.default.cyan(pm)}...\n`);
1372
+ try {
1373
+ await (0, execa.execa)(buildCmd[0], buildCmd.slice(1), {
1374
+ cwd: projectRoot,
1375
+ stdio: "inherit"
1376
+ });
1377
+ } catch {
1378
+ console.error(chalk.default.red("\n✗ Build failed."));
1379
+ process.exit(1);
1380
+ }
1381
+ }
1382
+ //#endregion
1383
+ //#region src/commands/start.ts
1384
+ /**
1385
+ * CLI command: rebase start
1386
+ *
1387
+ * Starts the backend server in production mode.
1388
+ * Automatically detects the package manager (pnpm or npm) and
1389
+ * runs the start script in the backend workspace.
1390
+ */
1391
+ async function startCommand() {
1392
+ const projectRoot = requireProjectRoot();
1393
+ const startCmd = getPMCommands(detectPackageManager(projectRoot)).runWorkspace("backend", "start");
1394
+ const envFile = findEnvFile(projectRoot);
1395
+ const env = { ...process.env };
1396
+ if (envFile) env.DOTENV_CONFIG_PATH = envFile;
1397
+ console.log(`${chalk.default.bold("Rebase")} — Starting backend server...\n`);
1398
+ try {
1399
+ await (0, execa.execa)(startCmd[0], startCmd.slice(1), {
1400
+ cwd: projectRoot,
1401
+ stdio: "inherit",
1402
+ env
1403
+ });
1404
+ } catch {
1405
+ console.error(chalk.default.red("\n✗ Failed to start server."));
1406
+ process.exit(1);
1407
+ }
1408
+ }
1409
+ //#endregion
1410
+ //#region src/commands/auth.ts
1411
+ /**
1412
+ * CLI command: rebase auth <action>
1413
+ *
1414
+ * Subcommands:
1415
+ * reset-password Reset a user's password
1416
+ */
1417
+ async function authCommand(subcommand, rawArgs) {
1418
+ if (!subcommand || subcommand === "--help") {
1419
+ printAuthHelp();
1420
+ return;
1421
+ }
1422
+ switch (subcommand) {
1423
+ case "reset-password":
1424
+ await resetPassword(rawArgs);
1425
+ break;
1426
+ default:
1427
+ console.error(chalk.default.red(`Unknown auth command: ${subcommand}`));
1428
+ console.log("");
1429
+ printAuthHelp();
1430
+ process.exit(1);
1431
+ }
1432
+ }
1433
+ async function resetPassword(rawArgs) {
1434
+ const args = (0, arg.default)({
1435
+ "--email": String,
1436
+ "--password": String,
1437
+ "-e": "--email",
1438
+ "-p": "--password"
1439
+ }, {
1440
+ argv: rawArgs.slice(4),
1441
+ permissive: true
1442
+ });
1443
+ const email = args["--email"] || args._[0];
1444
+ const newPassword = args["--password"] || args._[1];
1445
+ if (!email) {
1446
+ console.error(chalk.default.red("✗ Email is required."));
1447
+ console.log("");
1448
+ console.log(chalk.default.gray(" Usage: rebase auth reset-password <email> [new-password]"));
1449
+ console.log(chalk.default.gray(" rebase auth reset-password --email user@example.com --password NewPass123!"));
1450
+ process.exit(1);
1451
+ }
1452
+ const projectRoot = requireProjectRoot();
1453
+ const backendDir = requireBackendDir(projectRoot);
1454
+ const tsxBin = resolveTsx(projectRoot);
1455
+ if (!tsxBin) {
1456
+ console.error(chalk.default.red("✗ Could not find tsx binary."));
1457
+ process.exit(1);
1458
+ }
1459
+ const envFile = findEnvFile(projectRoot);
1460
+ const env = { ...process.env };
1461
+ if (envFile) env.DOTENV_CONFIG_PATH = envFile;
1462
+ env.REBASE_RESET_EMAIL = email;
1463
+ env.REBASE_RESET_PASSWORD = newPassword || "NewPassword123!";
1464
+ env.REBASE_ENV_FILE_PATH = envFile || path.default.join(projectRoot, ".env");
1465
+ const scriptContent = `
1277
1466
  import { createPostgresDatabaseConnection } from "@rebasepro/server-postgresql";
1278
1467
  import { hashPassword } from "@rebasepro/server-core";
1279
1468
  import { eq } from "drizzle-orm";
@@ -1318,7 +1507,7 @@ async function resetPassword() {
1318
1507
 
1319
1508
  if (result.length > 0) {
1320
1509
  console.log("✅ Password reset for: " + result[0].email);
1321
- ${!newPassword ? 'console.log(" New password: " + newPassword);' : ""}
1510
+ ${!newPassword ? "console.log(\" New password: \" + newPassword);" : ""}
1322
1511
  } else {
1323
1512
  console.log("✗ User not found: " + email);
1324
1513
  }
@@ -1327,249 +1516,433 @@ async function resetPassword() {
1327
1516
 
1328
1517
  resetPassword().catch(console.error);
1329
1518
  `;
1330
- const tmpScriptPath = path.join(backendDir, ".tmp-reset-password.ts");
1331
- fs.writeFileSync(tmpScriptPath, scriptContent, "utf-8");
1332
- console.log("");
1333
- console.log(chalk.bold(" 🔑 Rebase Auth — Reset Password"));
1334
- console.log("");
1335
- console.log(` ${chalk.gray("Email:")} ${email}`);
1336
- if (newPassword) {
1337
- console.log(` ${chalk.gray("Password:")} ${"*".repeat(newPassword.length)}`);
1338
- }
1339
- console.log("");
1340
- const child = child_process.spawn(tsxBin, [tmpScriptPath], {
1341
- cwd: backendDir,
1342
- stdio: "inherit",
1343
- env
1344
- });
1345
- return new Promise((resolve) => {
1346
- child.on("close", (code) => {
1347
- try {
1348
- fs.unlinkSync(tmpScriptPath);
1349
- } catch {
1350
- }
1351
- if (code !== 0) {
1352
- process.exit(code ?? 1);
1353
- }
1354
- resolve();
1355
- });
1356
- });
1357
- }
1358
- function printAuthHelp() {
1359
- console.log(`
1360
- ${chalk.bold("rebase auth")} — Authentication management commands
1519
+ const tmpScriptPath = path.default.join(backendDir, ".tmp-reset-password.ts");
1520
+ fs.default.writeFileSync(tmpScriptPath, scriptContent, "utf-8");
1521
+ console.log("");
1522
+ console.log(chalk.default.bold(" 🔑 Rebase Auth — Reset Password"));
1523
+ console.log("");
1524
+ console.log(` ${chalk.default.gray("Email:")} ${email}`);
1525
+ if (newPassword) console.log(` ${chalk.default.gray("Password:")} ${"*".repeat(newPassword.length)}`);
1526
+ console.log("");
1527
+ const child = (0, child_process.spawn)(tsxBin, [tmpScriptPath], {
1528
+ cwd: backendDir,
1529
+ stdio: "inherit",
1530
+ env
1531
+ });
1532
+ return new Promise((resolve) => {
1533
+ child.on("close", (code) => {
1534
+ try {
1535
+ fs.default.unlinkSync(tmpScriptPath);
1536
+ } catch {}
1537
+ if (code !== 0) process.exit(code ?? 1);
1538
+ resolve();
1539
+ });
1540
+ });
1541
+ }
1542
+ function printAuthHelp() {
1543
+ console.log(`
1544
+ ${chalk.default.bold("rebase auth")} — Authentication management commands
1361
1545
 
1362
- ${chalk.green.bold("Usage")}
1363
- rebase auth ${chalk.blue("<command>")} [options]
1546
+ ${chalk.default.green.bold("Usage")}
1547
+ rebase auth ${chalk.default.blue("<command>")} [options]
1364
1548
 
1365
- ${chalk.green.bold("Commands")}
1366
- ${chalk.blue.bold("reset-password")} Reset a user's password
1549
+ ${chalk.default.green.bold("Commands")}
1550
+ ${chalk.default.blue.bold("reset-password")} Reset a user's password
1367
1551
 
1368
- ${chalk.green.bold("reset-password Options")}
1369
- ${chalk.blue("--email, -e")} User's email address
1370
- ${chalk.blue("--password, -p")} New password (default: NewPassword123!)
1552
+ ${chalk.default.green.bold("reset-password Options")}
1553
+ ${chalk.default.blue("--email, -e")} User's email address
1554
+ ${chalk.default.blue("--password, -p")} New password (default: NewPassword123!)
1371
1555
 
1372
- ${chalk.green.bold("Examples")}
1556
+ ${chalk.default.green.bold("Examples")}
1373
1557
  rebase auth reset-password user@example.com
1374
1558
  rebase auth reset-password --email user@example.com --password MyNewPass!
1375
1559
  `);
1376
- }
1377
- async function doctorCommand(rawArgs) {
1378
- const projectRoot = requireProjectRoot();
1379
- const backendDir = requireBackendDir(projectRoot);
1380
- const activePlugin = getActiveBackendPlugin(backendDir);
1381
- if (!activePlugin) {
1382
- console.error(chalk.red("✗ Could not detect an active database plugin."));
1383
- console.error(chalk.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
1384
- process.exit(1);
1385
- }
1386
- const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
1387
- if (!pluginCli) {
1388
- console.error(chalk.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
1389
- process.exit(1);
1390
- }
1391
- const envFile = findEnvFile(projectRoot);
1392
- const env = { ...process.env };
1393
- if (envFile) {
1394
- env.DOTENV_CONFIG_PATH = envFile;
1395
- }
1396
- try {
1397
- const isTs = pluginCli.endsWith(".ts");
1398
- if (isTs) {
1399
- const tsxBin = resolveTsx(projectRoot);
1400
- if (!tsxBin) {
1401
- console.error(chalk.red("✗ Could not find tsx binary."));
1402
- process.exit(1);
1403
- }
1404
- await execa.execa(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
1405
- cwd: backendDir,
1406
- stdio: "inherit",
1407
- env
1408
- });
1409
- } else {
1410
- await execa.execa("node", [pluginCli, ...rawArgs.slice(2)], {
1411
- cwd: backendDir,
1412
- stdio: "inherit",
1413
- env
1414
- });
1415
- }
1416
- } catch {
1417
- process.exit(1);
1418
- }
1419
- }
1420
- 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);
1421
- const __dirname$1 = path.dirname(__filename$1);
1422
- function getVersion() {
1423
- try {
1424
- const pkgPath = path.resolve(__dirname$1, "../package.json");
1425
- if (fs.existsSync(pkgPath)) {
1426
- return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
1427
- }
1428
- } catch {
1429
- }
1430
- return "unknown";
1431
- }
1432
- async function entry(args) {
1433
- const parsedArgs = arg(
1434
- {
1435
- "--version": Boolean,
1436
- "--help": Boolean,
1437
- "-v": "--version",
1438
- "-h": "--help"
1439
- },
1440
- {
1441
- argv: args.slice(2),
1442
- permissive: true
1443
- }
1444
- );
1445
- if (parsedArgs["--version"]) {
1446
- console.log(getVersion());
1447
- return;
1448
- }
1449
- const command = parsedArgs._[0];
1450
- const subcommand = parsedArgs._[1];
1451
- const namespacedCommands = ["schema", "db", "dev", "build", "start", "auth", "doctor"];
1452
- if (!command || parsedArgs["--help"] && !namespacedCommands.includes(command)) {
1453
- printHelp();
1454
- return;
1455
- }
1456
- const effectiveSubcommand = parsedArgs["--help"] ? "--help" : subcommand;
1457
- switch (command) {
1458
- case "init":
1459
- await createRebaseApp(args);
1460
- break;
1461
- case "generate-sdk": {
1462
- const sdkArgs = arg(
1463
- {
1464
- "--collections-dir": String,
1465
- "--output": String,
1466
- "-c": "--collections-dir",
1467
- "-o": "--output"
1468
- },
1469
- {
1470
- argv: args.slice(3),
1471
- permissive: true
1472
- }
1473
- );
1474
- await generateSdkCommand({
1475
- collectionsDir: sdkArgs["--collections-dir"] || "./config/collections",
1476
- output: sdkArgs["--output"] || "./generated/sdk",
1477
- cwd: process.cwd()
1478
- });
1479
- break;
1480
- }
1481
- case "schema":
1482
- await schemaCommand(effectiveSubcommand, args);
1483
- break;
1484
- case "db":
1485
- await dbCommand(effectiveSubcommand, args);
1486
- break;
1487
- case "dev":
1488
- await devCommand(args);
1489
- break;
1490
- case "build":
1491
- await buildCommand();
1492
- break;
1493
- case "start":
1494
- await startCommand();
1495
- break;
1496
- case "auth":
1497
- await authCommand(effectiveSubcommand, args);
1498
- break;
1499
- case "doctor":
1500
- await doctorCommand(args);
1501
- break;
1502
- default:
1503
- console.log(chalk.red(`Unknown command: ${command}`));
1504
- console.log("");
1505
- printHelp();
1506
- }
1507
- }
1508
- function printHelp() {
1509
- console.log(`
1510
- ${chalk.bold("Rebase CLI")} Developer tools for Rebase projects
1560
+ }
1561
+ //#endregion
1562
+ //#region src/commands/doctor.ts
1563
+ /**
1564
+ * CLI command: rebase doctor
1565
+ *
1566
+ * Detects three-way schema drift between collection definitions,
1567
+ * the generated Drizzle schema, and the live PostgreSQL database.
1568
+ */
1569
+ async function doctorCommand(rawArgs) {
1570
+ const projectRoot = requireProjectRoot();
1571
+ const backendDir = requireBackendDir(projectRoot);
1572
+ const activePlugin = getActiveBackendPlugin(backendDir);
1573
+ if (!activePlugin) {
1574
+ console.error(chalk.default.red("✗ Could not detect an active database plugin."));
1575
+ console.error(chalk.default.gray(" Make sure a package like @rebasepro/server-postgresql is installed in backend/package.json."));
1576
+ process.exit(1);
1577
+ }
1578
+ const pluginCli = resolvePluginCliScript(backendDir, activePlugin);
1579
+ if (!pluginCli) {
1580
+ console.error(chalk.default.red(`✗ Could not find CLI entry point for ${activePlugin}.`));
1581
+ process.exit(1);
1582
+ }
1583
+ const envFile = findEnvFile(projectRoot);
1584
+ const env = { ...process.env };
1585
+ if (envFile) env.DOTENV_CONFIG_PATH = envFile;
1586
+ try {
1587
+ if (pluginCli.endsWith(".ts")) {
1588
+ const tsxBin = resolveTsx(projectRoot);
1589
+ if (!tsxBin) {
1590
+ console.error(chalk.default.red("✗ Could not find tsx binary."));
1591
+ process.exit(1);
1592
+ }
1593
+ await (0, execa.execa)(tsxBin, [pluginCli, ...rawArgs.slice(2)], {
1594
+ cwd: backendDir,
1595
+ stdio: "inherit",
1596
+ env
1597
+ });
1598
+ } else await (0, execa.execa)("node", [pluginCli, ...rawArgs.slice(2)], {
1599
+ cwd: backendDir,
1600
+ stdio: "inherit",
1601
+ env
1602
+ });
1603
+ } catch {
1604
+ process.exit(1);
1605
+ }
1606
+ }
1607
+ //#endregion
1608
+ //#region src/commands/skills.ts
1609
+ var __filename$1 = (0, url.fileURLToPath)({}.url);
1610
+ var __dirname$1 = path.default.dirname(__filename$1);
1611
+ /** Supported agent environments and their target directories. */
1612
+ var AGENTS = {
1613
+ cursor: {
1614
+ label: "Cursor",
1615
+ detectDir: ".cursor",
1616
+ targetDir: ".cursor/rules",
1617
+ /** Cursor uses .mdc files (Markdown with Context). */
1618
+ transformFile: (skillName, content) => ({
1619
+ fileName: `${skillName}.mdc`,
1620
+ content
1621
+ })
1622
+ },
1623
+ claude: {
1624
+ label: "Claude Code",
1625
+ detectDir: ".claude",
1626
+ targetDir: ".claude/skills",
1627
+ /** Claude Code uses the standard SKILL.md format in subdirectories. */
1628
+ transformFile: (skillName, content) => ({
1629
+ fileName: path.default.join(skillName, "SKILL.md"),
1630
+ content
1631
+ })
1632
+ },
1633
+ windsurf: {
1634
+ label: "Windsurf",
1635
+ detectDir: ".windsurf",
1636
+ targetDir: ".windsurf/rules",
1637
+ /** Windsurf uses plain .md files. */
1638
+ transformFile: (skillName, content) => ({
1639
+ fileName: `${skillName}.md`,
1640
+ content
1641
+ })
1642
+ },
1643
+ gemini: {
1644
+ label: "Gemini CLI / Antigravity",
1645
+ detectDir: ".agents",
1646
+ targetDir: ".agents/skills",
1647
+ /** Gemini uses the standard SKILL.md format in subdirectories. */
1648
+ transformFile: (skillName, content) => ({
1649
+ fileName: path.default.join(skillName, "SKILL.md"),
1650
+ content
1651
+ })
1652
+ }
1653
+ };
1654
+ function findParentDir(currentDir, targetName) {
1655
+ const root = path.default.parse(currentDir).root;
1656
+ while (currentDir && currentDir !== root) {
1657
+ if (path.default.basename(currentDir) === targetName) return currentDir;
1658
+ currentDir = path.default.dirname(currentDir);
1659
+ }
1660
+ return null;
1661
+ }
1662
+ /** Resolve the path to the bundled skills directory. */
1663
+ function getSkillsSourceDir() {
1664
+ const cliRoot = findParentDir(__dirname$1, "cli");
1665
+ if (cliRoot) {
1666
+ const dir = path.default.join(cliRoot, "skills");
1667
+ if (fs.default.existsSync(dir)) return dir;
1668
+ }
1669
+ const distSkills = path.default.resolve(__dirname$1, "../../skills");
1670
+ if (fs.default.existsSync(distSkills)) return distSkills;
1671
+ throw new Error("Could not find bundled skills directory. Make sure the CLI was built with `pnpm build`.");
1672
+ }
1673
+ /** Read all skill directories and return their names + content. */
1674
+ function loadSkills(skillsDir) {
1675
+ const entries = fs.default.readdirSync(skillsDir, { withFileTypes: true });
1676
+ const skills = [];
1677
+ for (const entry of entries) {
1678
+ if (!entry.isDirectory()) continue;
1679
+ const skillMdPath = path.default.join(skillsDir, entry.name, "SKILL.md");
1680
+ if (!fs.default.existsSync(skillMdPath)) continue;
1681
+ skills.push({
1682
+ name: entry.name,
1683
+ content: fs.default.readFileSync(skillMdPath, "utf-8")
1684
+ });
1685
+ }
1686
+ return skills;
1687
+ }
1688
+ /** Detect which agent environments already exist in the project. */
1689
+ function detectAgents(projectDir) {
1690
+ const detected = [];
1691
+ for (const [key, agent] of Object.entries(AGENTS)) if (fs.default.existsSync(path.default.join(projectDir, agent.detectDir))) detected.push(key);
1692
+ return detected;
1693
+ }
1694
+ /** Install skills for a specific agent into the project directory. */
1695
+ function installForAgent(agentKey, skills, projectDir) {
1696
+ const agent = AGENTS[agentKey];
1697
+ const targetBase = path.default.join(projectDir, agent.targetDir);
1698
+ fs.default.mkdirSync(targetBase, { recursive: true });
1699
+ let count = 0;
1700
+ for (const skill of skills) {
1701
+ const { fileName, content } = agent.transformFile(skill.name, skill.content);
1702
+ const targetPath = path.default.join(targetBase, fileName);
1703
+ fs.default.mkdirSync(path.default.dirname(targetPath), { recursive: true });
1704
+ fs.default.writeFileSync(targetPath, content, "utf-8");
1705
+ count++;
1706
+ }
1707
+ return count;
1708
+ }
1709
+ async function skillsCommand(subcommand, _args) {
1710
+ switch (subcommand) {
1711
+ case "install":
1712
+ await skillsInstall();
1713
+ break;
1714
+ case "--help":
1715
+ case void 0:
1716
+ printSkillsHelp();
1717
+ break;
1718
+ default:
1719
+ console.log(chalk.default.red(`Unknown skills subcommand: ${subcommand}`));
1720
+ console.log("");
1721
+ printSkillsHelp();
1722
+ }
1723
+ }
1724
+ async function skillsInstall() {
1725
+ const projectDir = process.cwd();
1726
+ let skillsDir;
1727
+ try {
1728
+ skillsDir = getSkillsSourceDir();
1729
+ } catch (err) {
1730
+ console.error(`${chalk.default.red.bold("ERROR")} ${err instanceof Error ? err.message : String(err)}`);
1731
+ process.exit(1);
1732
+ }
1733
+ const skills = loadSkills(skillsDir);
1734
+ if (skills.length === 0) {
1735
+ console.error(`${chalk.default.red.bold("ERROR")} No skills found in ${skillsDir}`);
1736
+ process.exit(1);
1737
+ }
1738
+ let agents = detectAgents(projectDir);
1739
+ if (agents.length === 0) {
1740
+ const choices = Object.entries(AGENTS).map(([key, agent]) => ({
1741
+ name: agent.label,
1742
+ value: key,
1743
+ checked: false
1744
+ }));
1745
+ const { selectedAgents } = await inquirer.default.prompt([{
1746
+ type: "checkbox",
1747
+ name: "selectedAgents",
1748
+ message: "No AI agent configuration detected. Which agents do you use?",
1749
+ choices,
1750
+ validate: (input) => {
1751
+ if (input.length === 0) return "Please select at least one agent.";
1752
+ return true;
1753
+ }
1754
+ }]);
1755
+ agents = selectedAgents;
1756
+ }
1757
+ console.log("");
1758
+ console.log(chalk.default.gray(` Found ${chalk.default.white(skills.length)} Rebase skills`));
1759
+ console.log("");
1760
+ for (const agentKey of agents) {
1761
+ const agent = AGENTS[agentKey];
1762
+ const count = installForAgent(agentKey, skills, projectDir);
1763
+ console.log(` ${chalk.default.green("✓")} ${chalk.default.bold(agent.label)} — ${count} skills installed to ${chalk.default.gray(agent.targetDir)}`);
1764
+ }
1765
+ console.log("");
1766
+ console.log(chalk.default.gray(" Skills are project-local. Commit them to share with your team."));
1767
+ console.log(chalk.default.gray(" Re-run this command anytime to update to the latest skills."));
1768
+ console.log("");
1769
+ }
1770
+ function printSkillsHelp() {
1771
+ console.log(`
1772
+ ${chalk.default.bold("rebase skills")} — Manage AI agent skills
1773
+
1774
+ ${chalk.default.green.bold("Usage")}
1775
+ rebase skills ${chalk.default.blue("<subcommand>")}
1776
+
1777
+ ${chalk.default.green.bold("Subcommands")}
1778
+ ${chalk.default.blue.bold("install")} Install Rebase agent skills for your AI coding assistant
1779
+ Supports: Cursor, Claude Code, Windsurf, Gemini CLI, Antigravity
1511
1780
 
1512
- ${chalk.green.bold("Usage")}
1513
- rebase ${chalk.blue("<command>")} [options]
1781
+ ${chalk.default.green.bold("Examples")}
1782
+ ${chalk.default.cyan("rebase skills install")}
1783
+ `);
1784
+ }
1785
+ //#endregion
1786
+ //#region src/cli.ts
1787
+ var __filename = (0, url.fileURLToPath)({}.url);
1788
+ var __dirname = path.default.dirname(__filename);
1789
+ function getVersion() {
1790
+ try {
1791
+ const pkgPath = path.default.resolve(__dirname, "../package.json");
1792
+ if (fs.default.existsSync(pkgPath)) return JSON.parse(fs.default.readFileSync(pkgPath, "utf-8")).version;
1793
+ } catch {}
1794
+ return "unknown";
1795
+ }
1796
+ async function entry(args) {
1797
+ const parsedArgs = (0, arg.default)({
1798
+ "--version": Boolean,
1799
+ "--help": Boolean,
1800
+ "-v": "--version",
1801
+ "-h": "--help"
1802
+ }, {
1803
+ argv: args.slice(2),
1804
+ permissive: true
1805
+ });
1806
+ if (parsedArgs["--version"]) {
1807
+ console.log(getVersion());
1808
+ return;
1809
+ }
1810
+ const command = parsedArgs._[0];
1811
+ const subcommand = parsedArgs._[1];
1812
+ if (!command || parsedArgs["--help"] && ![
1813
+ "schema",
1814
+ "db",
1815
+ "dev",
1816
+ "build",
1817
+ "start",
1818
+ "auth",
1819
+ "doctor",
1820
+ "skills"
1821
+ ].includes(command)) {
1822
+ printHelp();
1823
+ return;
1824
+ }
1825
+ const effectiveSubcommand = parsedArgs["--help"] ? "--help" : subcommand;
1826
+ switch (command) {
1827
+ case "init":
1828
+ await createRebaseApp(args);
1829
+ break;
1830
+ case "generate-sdk": {
1831
+ const sdkArgs = (0, arg.default)({
1832
+ "--collections-dir": String,
1833
+ "--output": String,
1834
+ "-c": "--collections-dir",
1835
+ "-o": "--output"
1836
+ }, {
1837
+ argv: args.slice(3),
1838
+ permissive: true
1839
+ });
1840
+ await generateSdkCommand({
1841
+ collectionsDir: sdkArgs["--collections-dir"] || "./config/collections",
1842
+ output: sdkArgs["--output"] || "./generated/sdk",
1843
+ cwd: process.cwd()
1844
+ });
1845
+ break;
1846
+ }
1847
+ case "schema":
1848
+ await schemaCommand(effectiveSubcommand, args);
1849
+ break;
1850
+ case "db":
1851
+ await dbCommand(effectiveSubcommand, args);
1852
+ break;
1853
+ case "dev":
1854
+ await devCommand(args);
1855
+ break;
1856
+ case "build":
1857
+ await buildCommand();
1858
+ break;
1859
+ case "start":
1860
+ await startCommand();
1861
+ break;
1862
+ case "auth":
1863
+ await authCommand(effectiveSubcommand, args);
1864
+ break;
1865
+ case "doctor":
1866
+ await doctorCommand(args);
1867
+ break;
1868
+ case "skills":
1869
+ await skillsCommand(effectiveSubcommand, args);
1870
+ break;
1871
+ default:
1872
+ console.log(chalk.default.red(`Unknown command: ${command}`));
1873
+ console.log("");
1874
+ printHelp();
1875
+ }
1876
+ }
1877
+ function printHelp() {
1878
+ console.log(`
1879
+ ${chalk.default.bold("Rebase CLI")} — Developer tools for Rebase projects
1880
+
1881
+ ${chalk.default.green.bold("Usage")}
1882
+ rebase ${chalk.default.blue("<command>")} [options]
1514
1883
 
1515
- ${chalk.green.bold("Commands")}
1516
- ${chalk.blue.bold("init")} Create a new Rebase project
1517
- ${chalk.blue.bold("dev")} Start the development server
1518
- ${chalk.blue.bold("build")} Build all workspace packages
1519
- ${chalk.blue.bold("start")} Start the backend server ${chalk.gray("(production)")}
1884
+ ${chalk.default.green.bold("Commands")}
1885
+ ${chalk.default.blue.bold("init")} Create a new Rebase project
1886
+ ${chalk.default.blue.bold("dev")} Start the development server
1887
+ ${chalk.default.blue.bold("build")} Build all workspace packages
1888
+ ${chalk.default.blue.bold("start")} Start the backend server ${chalk.default.gray("(production)")}
1520
1889
 
1521
- ${chalk.green.bold("Schema")}
1522
- ${chalk.blue.bold("schema generate")} Generate Drizzle schema from collections
1523
- ${chalk.blue.bold("schema introspect")} Introspect database → Rebase collections
1524
- ${chalk.blue.bold("schema")} ${chalk.gray("--help")} Show schema command help
1890
+ ${chalk.default.green.bold("Schema")}
1891
+ ${chalk.default.blue.bold("schema generate")} Generate Drizzle schema from collections
1892
+ ${chalk.default.blue.bold("schema introspect")} Introspect database → Rebase collections
1893
+ ${chalk.default.blue.bold("schema")} ${chalk.default.gray("--help")} Show schema command help
1525
1894
 
1526
- ${chalk.green.bold("Database")}
1527
- ${chalk.blue.bold("db push")} Apply schema directly to database ${chalk.gray("(dev)")}
1528
- ${chalk.blue.bold("db generate")} Generate SQL migration files
1529
- ${chalk.blue.bold("db migrate")} Run pending migrations
1530
- ${chalk.blue.bold("db studio")} Open Drizzle Studio
1531
- ${chalk.blue.bold("db")} ${chalk.gray("--help")} Show database command help
1895
+ ${chalk.default.green.bold("Database")}
1896
+ ${chalk.default.blue.bold("db push")} Apply schema directly to database ${chalk.default.gray("(dev)")}
1897
+ ${chalk.default.blue.bold("db generate")} Generate SQL migration files
1898
+ ${chalk.default.blue.bold("db migrate")} Run pending migrations
1899
+ ${chalk.default.blue.bold("db studio")} Open Drizzle Studio
1900
+ ${chalk.default.blue.bold("db")} ${chalk.default.gray("--help")} Show database command help
1532
1901
 
1533
- ${chalk.green.bold("SDK")}
1534
- ${chalk.blue.bold("generate-sdk")} Generate a typed JS SDK from collections
1902
+ ${chalk.default.green.bold("SDK")}
1903
+ ${chalk.default.blue.bold("generate-sdk")} Generate a typed JS SDK from collections
1535
1904
 
1536
- ${chalk.green.bold("Auth")}
1537
- ${chalk.blue.bold("auth reset-password")} Reset a user's password
1538
- ${chalk.blue.bold("auth")} ${chalk.gray("--help")} Show auth command help
1905
+ ${chalk.default.green.bold("Auth")}
1906
+ ${chalk.default.blue.bold("auth reset-password")} Reset a user's password
1907
+ ${chalk.default.blue.bold("auth")} ${chalk.default.gray("--help")} Show auth command help
1539
1908
 
1540
- ${chalk.green.bold("Diagnostics")}
1541
- ${chalk.blue.bold("doctor")} Detect schema drift between collections, schema, and DB
1909
+ ${chalk.default.green.bold("Diagnostics")}
1910
+ ${chalk.default.blue.bold("doctor")} Detect schema drift between collections, schema, and DB
1542
1911
 
1543
- ${chalk.green.bold("Options")}
1544
- ${chalk.blue("--version, -v")} Show version number
1545
- ${chalk.blue("--help, -h")} Show this help message
1912
+ ${chalk.default.green.bold("AI Agent Skills")}
1913
+ ${chalk.default.blue.bold("skills install")} Install Rebase agent skills for your AI coding assistant
1546
1914
 
1547
- ${chalk.gray("Documentation: https://rebase.pro/docs")}
1915
+ ${chalk.default.green.bold("Options")}
1916
+ ${chalk.default.blue("--version, -v")} Show version number
1917
+ ${chalk.default.blue("--help, -h")} Show this help message
1918
+
1919
+ ${chalk.default.gray("Documentation: https://rebase.pro/docs")}
1548
1920
  `);
1549
- }
1550
- exports2.authCommand = authCommand;
1551
- exports2.buildCommand = buildCommand;
1552
- exports2.configureEnvFile = configureEnvFile;
1553
- exports2.createRebaseApp = createRebaseApp;
1554
- exports2.dbCommand = dbCommand;
1555
- exports2.detectPackageManager = detectPackageManager;
1556
- exports2.devCommand = devCommand;
1557
- exports2.doctorCommand = doctorCommand;
1558
- exports2.entry = entry;
1559
- exports2.findBackendDir = findBackendDir;
1560
- exports2.findEnvFile = findEnvFile;
1561
- exports2.findFrontendDir = findFrontendDir;
1562
- exports2.findProjectRoot = findProjectRoot;
1563
- exports2.generateSdkCommand = generateSdkCommand;
1564
- exports2.getActiveBackendPlugin = getActiveBackendPlugin;
1565
- exports2.getPMCommands = getPMCommands;
1566
- exports2.requireBackendDir = requireBackendDir;
1567
- exports2.requireProjectRoot = requireProjectRoot;
1568
- exports2.resolveLocalBin = resolveLocalBin;
1569
- exports2.resolvePluginCliScript = resolvePluginCliScript;
1570
- exports2.resolveTsx = resolveTsx;
1571
- exports2.schemaCommand = schemaCommand;
1572
- exports2.startCommand = startCommand;
1573
- Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
1574
- }));
1575
- //# sourceMappingURL=index.cjs.map
1921
+ }
1922
+ //#endregion
1923
+ exports.authCommand = authCommand;
1924
+ exports.buildCommand = buildCommand;
1925
+ exports.configureEnvFile = configureEnvFile;
1926
+ exports.createRebaseApp = createRebaseApp;
1927
+ exports.dbCommand = dbCommand;
1928
+ exports.detectPackageManager = detectPackageManager;
1929
+ exports.devCommand = devCommand;
1930
+ exports.doctorCommand = doctorCommand;
1931
+ exports.entry = entry;
1932
+ exports.findBackendDir = findBackendDir;
1933
+ exports.findEnvFile = findEnvFile;
1934
+ exports.findFrontendDir = findFrontendDir;
1935
+ exports.findProjectRoot = findProjectRoot;
1936
+ exports.generateSdkCommand = generateSdkCommand;
1937
+ exports.getActiveBackendPlugin = getActiveBackendPlugin;
1938
+ exports.getPMCommands = getPMCommands;
1939
+ exports.requireBackendDir = requireBackendDir;
1940
+ exports.requireProjectRoot = requireProjectRoot;
1941
+ exports.resolveLocalBin = resolveLocalBin;
1942
+ exports.resolvePluginCliScript = resolvePluginCliScript;
1943
+ exports.resolveTsx = resolveTsx;
1944
+ exports.schemaCommand = schemaCommand;
1945
+ exports.startCommand = startCommand;
1946
+ });
1947
+
1948
+ //# sourceMappingURL=index.cjs.map