@rebasepro/cli 0.0.1-canary.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 (36) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +54 -0
  3. package/bin/rebase.js +4 -0
  4. package/dist/auth.d.ts +5 -0
  5. package/dist/cli.d.ts +1 -0
  6. package/dist/commands/init.d.ts +7 -0
  7. package/dist/index.cjs +277 -0
  8. package/dist/index.cjs.map +1 -0
  9. package/dist/index.d.ts +3 -0
  10. package/dist/index.es.js +265 -0
  11. package/dist/index.es.js.map +1 -0
  12. package/package.json +59 -0
  13. package/templates/template/.env.template +31 -0
  14. package/templates/template/README.md +59 -0
  15. package/templates/template/backend/Dockerfile +25 -0
  16. package/templates/template/backend/drizzle.config.ts +22 -0
  17. package/templates/template/backend/package.json +36 -0
  18. package/templates/template/backend/scripts/db-generate.ts +60 -0
  19. package/templates/template/backend/src/index.ts +134 -0
  20. package/templates/template/backend/src/schema.generated.ts +8 -0
  21. package/templates/template/backend/tsconfig.json +19 -0
  22. package/templates/template/docker-compose.yml +48 -0
  23. package/templates/template/frontend/Dockerfile +28 -0
  24. package/templates/template/frontend/index.html +13 -0
  25. package/templates/template/frontend/package.json +52 -0
  26. package/templates/template/frontend/src/App.tsx +132 -0
  27. package/templates/template/frontend/src/index.css +2 -0
  28. package/templates/template/frontend/src/main.tsx +18 -0
  29. package/templates/template/frontend/tsconfig.json +20 -0
  30. package/templates/template/frontend/vite.config.ts +26 -0
  31. package/templates/template/package.json +32 -0
  32. package/templates/template/shared/collections/index.ts +3 -0
  33. package/templates/template/shared/collections/posts.ts +53 -0
  34. package/templates/template/shared/index.ts +6 -0
  35. package/templates/template/shared/package.json +28 -0
  36. package/templates/template/shared/tsconfig.json +20 -0
@@ -0,0 +1,265 @@
1
+ import chalk from "chalk";
2
+ import arg from "arg";
3
+ import inquirer from "inquirer";
4
+ import path from "path";
5
+ import fs from "fs";
6
+ import { promisify } from "util";
7
+ import execa from "execa";
8
+ import ncp from "ncp";
9
+ import { fileURLToPath } from "url";
10
+ import * as os from "os";
11
+ const access = promisify(fs.access);
12
+ const copy = promisify(ncp);
13
+ const __filename$2 = fileURLToPath(import.meta.url);
14
+ const __dirname$2 = path.dirname(__filename$2);
15
+ function findParentDir(currentDir, targetName) {
16
+ const root = path.parse(currentDir).root;
17
+ while (currentDir && currentDir !== root) {
18
+ if (path.basename(currentDir) === targetName) {
19
+ return currentDir;
20
+ }
21
+ currentDir = path.dirname(currentDir);
22
+ }
23
+ return null;
24
+ }
25
+ const cliRoot = findParentDir(__dirname$2, "cli");
26
+ async function createRebaseApp(rawArgs) {
27
+ console.log(`
28
+ ${chalk.bold("Rebase")} — Create a new project 🚀
29
+ `);
30
+ const options = await promptForOptions(rawArgs);
31
+ await createProject(options);
32
+ }
33
+ async function promptForOptions(rawArgs) {
34
+ const args = arg(
35
+ {
36
+ "--git": Boolean,
37
+ "-g": "--git"
38
+ },
39
+ {
40
+ argv: rawArgs.slice(3),
41
+ // skip "node", "rebase", "init"
42
+ permissive: true
43
+ }
44
+ );
45
+ const nameArg = args._[0];
46
+ const questions = [];
47
+ if (!nameArg) {
48
+ questions.push({
49
+ type: "input",
50
+ name: "projectName",
51
+ message: "Project name:",
52
+ default: "my-rebase-app",
53
+ validate: (input) => {
54
+ if (!input.trim()) return "Project name is required";
55
+ if (!/^[a-z0-9][a-z0-9._-]*$/.test(input)) {
56
+ return "Project name must start with a lowercase letter or number and contain only lowercase letters, numbers, hyphens, dots, or underscores";
57
+ }
58
+ return true;
59
+ }
60
+ });
61
+ }
62
+ if (!args["--git"]) {
63
+ questions.push({
64
+ type: "confirm",
65
+ name: "git",
66
+ message: "Initialize a git repository?",
67
+ default: true
68
+ });
69
+ }
70
+ const answers = await inquirer.prompt(questions);
71
+ const projectName = nameArg || answers.projectName;
72
+ const targetDirectory = path.resolve(process.cwd(), projectName);
73
+ const templateDirectory = path.resolve(cliRoot, "templates", "template");
74
+ return {
75
+ projectName,
76
+ git: args["--git"] || answers.git || false,
77
+ targetDirectory,
78
+ templateDirectory
79
+ };
80
+ }
81
+ async function createProject(options) {
82
+ if (fs.existsSync(options.targetDirectory)) {
83
+ if (fs.readdirSync(options.targetDirectory).length !== 0) {
84
+ console.error(`${chalk.red.bold("ERROR")} Directory "${options.projectName}" already exists and is not empty`);
85
+ process.exit(1);
86
+ }
87
+ } else {
88
+ fs.mkdirSync(options.targetDirectory, { recursive: true });
89
+ }
90
+ try {
91
+ await access(options.templateDirectory, fs.constants.R_OK);
92
+ } catch {
93
+ console.error(`${chalk.red.bold("ERROR")} Template not found at ${options.templateDirectory}`);
94
+ process.exit(1);
95
+ }
96
+ console.log(chalk.gray(" Copying project files..."));
97
+ await copy(options.templateDirectory, options.targetDirectory, {
98
+ clobber: false,
99
+ dot: true,
100
+ filter: (source) => {
101
+ const basename = path.basename(source);
102
+ return basename !== "node_modules" && basename !== ".DS_Store";
103
+ }
104
+ });
105
+ await replacePlaceholders(options);
106
+ const envTemplatePath = path.join(options.targetDirectory, ".env.template");
107
+ const envPath = path.join(options.targetDirectory, ".env");
108
+ if (fs.existsSync(envTemplatePath) && !fs.existsSync(envPath)) {
109
+ fs.renameSync(envTemplatePath, envPath);
110
+ }
111
+ if (options.git) {
112
+ console.log(chalk.gray(" Initializing git repository..."));
113
+ try {
114
+ await execa("git", ["init"], { cwd: options.targetDirectory });
115
+ } catch {
116
+ console.warn(chalk.yellow(" Warning: Failed to initialize git repository"));
117
+ }
118
+ }
119
+ console.log("");
120
+ console.log(`${chalk.green.bold("✓")} Project ${chalk.bold(options.projectName)} created successfully!`);
121
+ console.log("");
122
+ console.log(chalk.bold("Next steps:"));
123
+ console.log("");
124
+ console.log(` ${chalk.cyan("cd")} ${options.projectName}`);
125
+ console.log(` ${chalk.cyan("pnpm install")}`);
126
+ console.log("");
127
+ console.log(chalk.gray(" # Set up your database connection in .env"));
128
+ console.log(chalk.gray(" # Then run:"));
129
+ console.log("");
130
+ console.log(` ${chalk.cyan("pnpm dev")}`);
131
+ console.log("");
132
+ console.log(chalk.gray("This starts both the backend (Express + PostgreSQL)") + chalk.gray(" and the frontend (Vite + React) concurrently."));
133
+ console.log("");
134
+ console.log(chalk.gray("Docs: https://rebase.pro/docs"));
135
+ console.log(chalk.gray("GitHub: https://github.com/rebaseco/rebase"));
136
+ console.log("");
137
+ }
138
+ async function replacePlaceholders(options) {
139
+ const filesToProcess = [
140
+ "package.json",
141
+ "frontend/package.json",
142
+ "backend/package.json",
143
+ "shared/package.json",
144
+ "frontend/index.html"
145
+ ];
146
+ for (const file of filesToProcess) {
147
+ const fullPath = path.resolve(options.targetDirectory, file);
148
+ if (!fs.existsSync(fullPath)) continue;
149
+ let content = fs.readFileSync(fullPath, "utf-8");
150
+ content = content.replace(/\{\{PROJECT_NAME\}\}/g, options.projectName);
151
+ fs.writeFileSync(fullPath, content, "utf-8");
152
+ }
153
+ }
154
+ const __filename$1 = fileURLToPath(import.meta.url);
155
+ const __dirname$1 = path.dirname(__filename$1);
156
+ function getVersion() {
157
+ try {
158
+ const pkgPath = path.resolve(__dirname$1, "../package.json");
159
+ if (fs.existsSync(pkgPath)) {
160
+ return JSON.parse(fs.readFileSync(pkgPath, "utf-8")).version;
161
+ }
162
+ } catch {
163
+ }
164
+ return "unknown";
165
+ }
166
+ async function entry(args) {
167
+ const parsedArgs = arg(
168
+ {
169
+ "--version": Boolean,
170
+ "--help": Boolean,
171
+ "-v": "--version",
172
+ "-h": "--help"
173
+ },
174
+ {
175
+ argv: args.slice(2),
176
+ permissive: true
177
+ }
178
+ );
179
+ if (parsedArgs["--version"]) {
180
+ console.log(getVersion());
181
+ return;
182
+ }
183
+ const command = parsedArgs._[0];
184
+ if (!command || parsedArgs["--help"]) {
185
+ printHelp();
186
+ return;
187
+ }
188
+ if (command === "init") {
189
+ await createRebaseApp(args);
190
+ } else {
191
+ console.log(chalk.red(`Unknown command: ${command}`));
192
+ console.log("");
193
+ printHelp();
194
+ }
195
+ }
196
+ function printHelp() {
197
+ console.log(`
198
+ ${chalk.bold("Rebase CLI")} — Developer tools for Rebase projects
199
+
200
+ ${chalk.green.bold("Usage")}
201
+ rebase ${chalk.blue("<command>")} [options]
202
+
203
+ ${chalk.green.bold("Commands")}
204
+ ${chalk.blue.bold("init")} Create a new Rebase project
205
+
206
+ ${chalk.green.bold("Options")}
207
+ ${chalk.blue("--version, -v")} Show version number
208
+ ${chalk.blue("--help, -h")} Show this help message
209
+
210
+ ${chalk.gray("Documentation: https://rebase.pro/docs")}
211
+ `);
212
+ }
213
+ const TOKEN_DIR = path.join(os.homedir(), ".rebase");
214
+ function tokenPath(env) {
215
+ return path.join(TOKEN_DIR, (env === "dev" ? "staging." : "") + "tokens.json");
216
+ }
217
+ async function getTokens(env, _debug) {
218
+ const fp = tokenPath(env);
219
+ if (!fs.existsSync(fp)) return null;
220
+ try {
221
+ const data = fs.readFileSync(fp, "utf-8");
222
+ return JSON.parse(data);
223
+ } catch {
224
+ return null;
225
+ }
226
+ }
227
+ function parseJwt(token) {
228
+ if (!token) throw new Error("No JWT token");
229
+ const base64Url = token.split(".")[1];
230
+ const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
231
+ const buffer = Buffer.from(base64, "base64");
232
+ return JSON.parse(buffer.toString());
233
+ }
234
+ async function refreshCredentials(env, credentials, _onErr) {
235
+ if (!credentials) return null;
236
+ const expiryDate = new Date(credentials["expiry_date"]);
237
+ if (expiryDate.getTime() > Date.now()) {
238
+ return credentials;
239
+ }
240
+ return null;
241
+ }
242
+ async function login(env, _debug) {
243
+ console.log(
244
+ "Interactive login is not yet implemented in @rebasepro/cli.\nPlease authenticate via the Rebase dashboard and copy your tokens to " + tokenPath(env)
245
+ );
246
+ }
247
+ async function logout(env, _debug) {
248
+ const fp = tokenPath(env);
249
+ if (fs.existsSync(fp)) {
250
+ fs.unlinkSync(fp);
251
+ console.log("You have been logged out.");
252
+ } else {
253
+ console.log("You are not logged in.");
254
+ }
255
+ }
256
+ export {
257
+ createRebaseApp,
258
+ entry,
259
+ getTokens,
260
+ login,
261
+ logout,
262
+ parseJwt,
263
+ refreshCredentials
264
+ };
265
+ //# sourceMappingURL=index.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.es.js","sources":["../src/commands/init.ts","../src/cli.ts","../src/auth.ts"],"sourcesContent":["import arg from \"arg\";\nimport inquirer from \"inquirer\";\nimport chalk from \"chalk\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport { promisify } from \"util\";\nimport execa from \"execa\";\nimport ncp from \"ncp\";\nimport { fileURLToPath } from \"url\";\n\nconst access = promisify(fs.access);\nconst copy = promisify(ncp);\n\n// Resolve template path relative to this file\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nfunction findParentDir(currentDir: string, targetName: string): string | null {\n const root = path.parse(currentDir).root;\n while (currentDir && currentDir !== root) {\n if (path.basename(currentDir) === targetName) {\n return currentDir;\n }\n currentDir = path.dirname(currentDir);\n }\n return null;\n}\n\nconst cliRoot = findParentDir(__dirname, \"cli\");\n\nexport interface InitOptions {\n projectName: string;\n git: boolean;\n targetDirectory: string;\n templateDirectory: string;\n}\n\nexport async function createRebaseApp(rawArgs: string[]) {\n console.log(`\n${chalk.bold(\"Rebase\")} — Create a new project 🚀\n`);\n\n const options = await promptForOptions(rawArgs);\n await createProject(options);\n}\n\nasync function promptForOptions(rawArgs: string[]): Promise<InitOptions> {\n const args = arg(\n {\n \"--git\": Boolean,\n \"-g\": \"--git\"\n },\n {\n argv: rawArgs.slice(3), // skip \"node\", \"rebase\", \"init\"\n permissive: true\n }\n );\n\n // The first positional arg after \"init\" is the project name\n const nameArg = args._[0];\n\n const questions: any[] = [];\n\n if (!nameArg) {\n questions.push({\n type: \"input\",\n name: \"projectName\",\n message: \"Project name:\",\n default: \"my-rebase-app\",\n validate: (input: string) => {\n if (!input.trim()) return \"Project name is required\";\n if (!/^[a-z0-9][a-z0-9._-]*$/.test(input)) {\n return \"Project name must start with a lowercase letter or number and contain only lowercase letters, numbers, hyphens, dots, or underscores\";\n }\n return true;\n }\n });\n }\n\n if (!args[\"--git\"]) {\n questions.push({\n type: \"confirm\",\n name: \"git\",\n message: \"Initialize a git repository?\",\n default: true\n });\n }\n\n const answers = await inquirer.prompt(questions);\n\n const projectName = nameArg || answers.projectName;\n const targetDirectory = path.resolve(process.cwd(), projectName);\n const templateDirectory = path.resolve(cliRoot!, \"templates\", \"template\");\n\n return {\n projectName,\n git: args[\"--git\"] || answers.git || false,\n targetDirectory,\n templateDirectory\n };\n}\n\nasync function createProject(options: InitOptions) {\n // Check if directory already exists and is not empty\n if (fs.existsSync(options.targetDirectory)) {\n if (fs.readdirSync(options.targetDirectory).length !== 0) {\n console.error(`${chalk.red.bold(\"ERROR\")} Directory \"${options.projectName}\" already exists and is not empty`);\n process.exit(1);\n }\n } else {\n fs.mkdirSync(options.targetDirectory, { recursive: true });\n }\n\n // Verify template exists\n try {\n await access(options.templateDirectory, fs.constants.R_OK);\n } catch {\n console.error(`${chalk.red.bold(\"ERROR\")} Template not found at ${options.templateDirectory}`);\n process.exit(1);\n }\n\n // Copy template files\n console.log(chalk.gray(\" Copying project files...\"));\n await copy(options.templateDirectory, options.targetDirectory, {\n clobber: false,\n dot: true,\n filter: (source: string) => {\n const basename = path.basename(source);\n // Skip node_modules and .DS_Store\n return basename !== \"node_modules\" && basename !== \".DS_Store\";\n }\n });\n\n // Replace placeholder project name in package.json files\n await replacePlaceholders(options);\n\n // Rename .env.template to .env if it exists\n const envTemplatePath = path.join(options.targetDirectory, \".env.template\");\n const envPath = path.join(options.targetDirectory, \".env\");\n if (fs.existsSync(envTemplatePath) && !fs.existsSync(envPath)) {\n fs.renameSync(envTemplatePath, envPath);\n }\n\n // Initialize git\n if (options.git) {\n console.log(chalk.gray(\" Initializing git repository...\"));\n try {\n await execa(\"git\", [\"init\"], { cwd: options.targetDirectory });\n } catch {\n console.warn(chalk.yellow(\" Warning: Failed to initialize git repository\"));\n }\n }\n\n // Success message\n console.log(\"\");\n console.log(`${chalk.green.bold(\"✓\")} Project ${chalk.bold(options.projectName)} created successfully!`);\n console.log(\"\");\n console.log(chalk.bold(\"Next steps:\"));\n console.log(\"\");\n console.log(` ${chalk.cyan(\"cd\")} ${options.projectName}`);\n console.log(` ${chalk.cyan(\"pnpm install\")}`);\n console.log(\"\");\n console.log(chalk.gray(\" # Set up your database connection in .env\"));\n console.log(chalk.gray(\" # Then run:\"));\n console.log(\"\");\n console.log(` ${chalk.cyan(\"pnpm dev\")}`);\n console.log(\"\");\n console.log(chalk.gray(\"This starts both the backend (Express + PostgreSQL)\")\n + chalk.gray(\" and the frontend (Vite + React) concurrently.\"));\n console.log(\"\");\n console.log(chalk.gray(\"Docs: https://rebase.pro/docs\"));\n console.log(chalk.gray(\"GitHub: https://github.com/rebaseco/rebase\"));\n console.log(\"\");\n}\n\nasync function replacePlaceholders(options: InitOptions) {\n const filesToProcess = [\n \"package.json\",\n \"frontend/package.json\",\n \"backend/package.json\",\n \"shared/package.json\",\n \"frontend/index.html\"\n ];\n\n for (const file of filesToProcess) {\n const fullPath = path.resolve(options.targetDirectory, file);\n if (!fs.existsSync(fullPath)) continue;\n\n let content = fs.readFileSync(fullPath, \"utf-8\");\n content = content.replace(/\\{\\{PROJECT_NAME\\}\\}/g, options.projectName);\n fs.writeFileSync(fullPath, content, \"utf-8\");\n }\n}\n","import chalk from \"chalk\";\nimport arg from \"arg\";\nimport { createRebaseApp } from \"./commands/init\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nfunction getVersion(): string {\n try {\n // Try to read version from package.json\n const pkgPath = path.resolve(__dirname, \"../package.json\");\n if (fs.existsSync(pkgPath)) {\n return JSON.parse(fs.readFileSync(pkgPath, \"utf-8\")).version;\n }\n } catch {\n // ignore\n }\n return \"unknown\";\n}\n\nexport async function entry(args: string[]) {\n const parsedArgs = arg(\n {\n \"--version\": Boolean,\n \"--help\": Boolean,\n \"-v\": \"--version\",\n \"-h\": \"--help\"\n },\n {\n argv: args.slice(2),\n permissive: true\n }\n );\n\n if (parsedArgs[\"--version\"]) {\n console.log(getVersion());\n return;\n }\n\n const command = parsedArgs._[0];\n\n if (!command || parsedArgs[\"--help\"]) {\n printHelp();\n return;\n }\n\n if (command === \"init\") {\n await createRebaseApp(args);\n } else {\n console.log(chalk.red(`Unknown command: ${command}`));\n console.log(\"\");\n printHelp();\n }\n}\n\nfunction printHelp() {\n console.log(`\n${chalk.bold(\"Rebase CLI\")} — Developer tools for Rebase projects\n\n${chalk.green.bold(\"Usage\")}\n rebase ${chalk.blue(\"<command>\")} [options]\n\n${chalk.green.bold(\"Commands\")}\n ${chalk.blue.bold(\"init\")} Create a new Rebase project\n\n${chalk.green.bold(\"Options\")}\n ${chalk.blue(\"--version, -v\")} Show version number\n ${chalk.blue(\"--help, -h\")} Show this help message\n\n${chalk.gray(\"Documentation: https://rebase.pro/docs\")}\n`);\n}\n","/**\n * Authentication helpers for the Rebase CLI.\n *\n * Token storage lives at ~/.rebase/tokens.json (prod) or\n * ~/.rebase/staging.tokens.json (dev).\n */\nimport fs from \"fs\";\nimport path from \"path\";\nimport * as os from \"os\";\n\nconst TOKEN_DIR = path.join(os.homedir(), \".rebase\");\n\nfunction tokenPath(env: \"prod\" | \"dev\"): string {\n return path.join(TOKEN_DIR, (env === \"dev\" ? \"staging.\" : \"\") + \"tokens.json\");\n}\n\n// ─── Token persistence ────────────────────────────────────\n\nfunction saveTokens(tokens: object, env: \"prod\" | \"dev\") {\n if (!fs.existsSync(TOKEN_DIR)) {\n fs.mkdirSync(TOKEN_DIR, { recursive: true });\n }\n fs.writeFileSync(tokenPath(env), JSON.stringify(tokens));\n}\n\n// ─── Public API ───────────────────────────────────────────\n\nexport async function getTokens(env: \"prod\" | \"dev\", _debug?: boolean): Promise<object | null> {\n const fp = tokenPath(env);\n if (!fs.existsSync(fp)) return null;\n try {\n const data = fs.readFileSync(fp, \"utf-8\");\n return JSON.parse(data);\n } catch {\n return null;\n }\n}\n\nexport function parseJwt(token: string): object {\n if (!token) throw new Error(\"No JWT token\");\n const base64Url = token.split(\".\")[1];\n const base64 = base64Url.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const buffer = Buffer.from(base64, \"base64\");\n return JSON.parse(buffer.toString());\n}\n\nexport async function refreshCredentials(\n env: \"dev\" | \"prod\",\n credentials?: object | null,\n _onErr?: (e: unknown) => void,\n): Promise<object | null> {\n if (!credentials) return null;\n // If the token hasn't expired, just return it.\n const expiryDate = new Date((credentials as any)[\"expiry_date\"]);\n if (expiryDate.getTime() > Date.now()) {\n return credentials;\n }\n // Without a server endpoint we can't actually refresh –\n // the caller should handle the null return by re-authenticating.\n return null;\n}\n\nexport async function login(env: \"prod\" | \"dev\", _debug?: boolean): Promise<void> {\n console.log(\n \"Interactive login is not yet implemented in @rebasepro/cli.\\n\" +\n \"Please authenticate via the Rebase dashboard and copy your tokens to \" +\n tokenPath(env),\n );\n}\n\nexport async function logout(env: \"prod\" | \"dev\", _debug?: boolean): Promise<void> {\n const fp = tokenPath(env);\n if (fs.existsSync(fp)) {\n fs.unlinkSync(fp);\n console.log(\"You have been logged out.\");\n } else {\n console.log(\"You are not logged in.\");\n }\n}\n"],"names":["__filename","__dirname"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@rebasepro/cli",
3
+ "version": "0.0.1-canary.0",
4
+ "description": "Developer tools for Rebase projects",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.es.js",
7
+ "types": "./dist/index.d.ts",
8
+ "type": "module",
9
+ "source": "src/index.ts",
10
+ "bin": "bin/rebase.js",
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
14
+ "scripts": {
15
+ "test": "echo \"Error: no test specified\" && exit 0",
16
+ "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.json",
17
+ "clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
18
+ },
19
+ "keywords": [
20
+ "cli",
21
+ "create-rebase-app",
22
+ "rebase",
23
+ "cms",
24
+ "react",
25
+ "typescript",
26
+ "postgresql"
27
+ ],
28
+ "author": "rebase.pro",
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "arg": "^5.0.2",
32
+ "chalk": "^4.1.2",
33
+ "execa": "^4.1.0",
34
+ "inquirer": "12.11.1",
35
+ "ncp": "^2.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^20.19.17",
39
+ "typescript": "^5.9.3",
40
+ "vite": "^7.2.4"
41
+ },
42
+ "files": [
43
+ "bin/",
44
+ "dist/",
45
+ "templates/"
46
+ ],
47
+ "exports": {
48
+ ".": {
49
+ "types": "./dist/index.d.ts",
50
+ "import": "./dist/index.es.js",
51
+ "require": "./dist/index.cjs"
52
+ }
53
+ },
54
+ "exclude": [
55
+ "node_modules",
56
+ "templates/template/node_modules"
57
+ ],
58
+ "gitHead": "646afae9c387c3ce02c699d98cd8c7272bdd1929"
59
+ }
@@ -0,0 +1,31 @@
1
+ # Database Configuration
2
+ DATABASE_URL=postgresql://postgres:password@localhost:5432/rebase
3
+
4
+ # Server Configuration
5
+ PORT=3001
6
+ NODE_ENV=development
7
+
8
+ # JWT Authentication
9
+ JWT_SECRET=change-this-to-a-secure-random-string
10
+ JWT_ACCESS_EXPIRES_IN=1h
11
+ JWT_REFRESH_EXPIRES_IN=30d
12
+
13
+ # Allow new user registration
14
+ ALLOW_REGISTRATION=true
15
+
16
+ # Frontend API URL
17
+ VITE_API_URL=http://localhost:3001
18
+
19
+ # Google OAuth (optional — uncomment to enable)
20
+ # GOOGLE_CLIENT_ID=your-google-client-id
21
+ # VITE_GOOGLE_CLIENT_ID=your-google-client-id
22
+
23
+ # Email (optional — uncomment to enable password reset emails)
24
+ # SMTP_HOST=smtp.example.com
25
+ # SMTP_PORT=587
26
+ # SMTP_SECURE=false
27
+ # SMTP_USER=your-smtp-username
28
+ # SMTP_PASS=your-smtp-password
29
+ # SMTP_FROM=noreply@yourapp.com
30
+ # APP_NAME=My Rebase App
31
+ # FRONTEND_URL=http://localhost:5173
@@ -0,0 +1,59 @@
1
+ # {{PROJECT_NAME}}
2
+
3
+ A [Rebase](https://rebase.pro) project with a PostgreSQL backend.
4
+
5
+ ## Getting Started
6
+
7
+ ### Prerequisites
8
+
9
+ - [Node.js](https://nodejs.org) >= 18
10
+ - [pnpm](https://pnpm.io)
11
+ - A PostgreSQL database
12
+
13
+ ### Setup
14
+
15
+ 1. Install dependencies:
16
+
17
+ ```bash
18
+ pnpm install
19
+ ```
20
+
21
+ 2. Configure your database — edit `.env` and set `DATABASE_URL`:
22
+
23
+ ```
24
+ DATABASE_URL=postgresql://postgres:password@localhost:5432/mydb
25
+ ```
26
+
27
+ 3. Generate and run database migrations:
28
+
29
+ ```bash
30
+ pnpm db:generate
31
+ pnpm db:migrate
32
+ ```
33
+
34
+ 4. Start the dev server:
35
+
36
+ ```bash
37
+ pnpm dev
38
+ ```
39
+
40
+ This starts both the backend (Express + PostgreSQL on port 3001) and the frontend (Vite + React on port 5173) concurrently.
41
+
42
+ ## Project Structure
43
+
44
+ ```
45
+ ├── frontend/ # React frontend (Vite)
46
+ ├── backend/ # Express backend with PostgreSQL
47
+ ├── shared/ # Shared collection definitions
48
+ ├── .env # Environment variables
49
+ └── package.json # Root workspace config
50
+ ```
51
+
52
+ ### Shared Collections
53
+
54
+ Collections are defined once in `shared/collections/` and used by both the frontend and backend. This ensures your schema stays in sync across the stack.
55
+
56
+ ## Documentation
57
+
58
+ - [Rebase Docs](https://rebase.pro/docs)
59
+ - [GitHub](https://github.com/rebaseco/rebase)
@@ -0,0 +1,25 @@
1
+ FROM node:24-alpine AS base
2
+ ENV PNPM_HOME="/pnpm"
3
+ ENV PATH="$PNPM_HOME:$PATH"
4
+ RUN corepack enable
5
+ RUN apk add --no-cache python3 make g++
6
+
7
+ WORKDIR /app
8
+
9
+ # Copy root configurations
10
+ COPY package.json ./
11
+
12
+ # Copy internal workspaces
13
+ COPY backend ./backend
14
+ COPY shared ./shared
15
+
16
+ # Install dependencies
17
+ RUN pnpm install
18
+
19
+ # Build shared and backend
20
+ RUN pnpm run build:shared
21
+ RUN pnpm run build:backend
22
+
23
+ WORKDIR /app/backend
24
+ EXPOSE 3001
25
+ CMD ["pnpm", "start"]
@@ -0,0 +1,22 @@
1
+ import "dotenv/config";
2
+ import { defineConfig } from "drizzle-kit";
3
+ import { tables } from "./src/schema.generated";
4
+ import { getTableName, Table } from "drizzle-orm";
5
+
6
+ if (!process.env.DATABASE_URL) {
7
+ throw new Error("DATABASE_URL is not set. Make sure .env file exists in the project root and contains DATABASE_URL");
8
+ }
9
+
10
+ // Extract table names from the generated schema
11
+ // This ensures drizzle-kit ONLY manages tables defined in the schema
12
+ const tableNames = Object.values(tables).map(table => getTableName(table as Table));
13
+
14
+ export default defineConfig({
15
+ schema: "./src/schema.generated.ts",
16
+ out: "./drizzle",
17
+ dialect: "postgresql",
18
+ dbCredentials: {
19
+ url: process.env.DATABASE_URL
20
+ },
21
+ tablesFilter: tableNames
22
+ });
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}-backend",
3
+ "version": "1.0.0",
4
+ "description": "Rebase backend with PostgreSQL",
5
+ "main": "src/index.ts",
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "tsx watch --watch=\"../shared/**/*\" src/index.ts",
9
+ "build": "tsc --noEmit",
10
+ "start": "tsx src/index.ts",
11
+ "generate:schema": "tsx node_modules/@rebasepro/backend/src/generate-drizzle-schema.ts --collections=../shared/collections --output=src/schema.generated.ts",
12
+ "db:generate": "pnpm generate:schema && tsx scripts/db-generate.ts",
13
+ "db:migrate": "DOTENV_CONFIG_PATH=../.env drizzle-kit migrate",
14
+ "db:push": "DOTENV_CONFIG_PATH=../.env drizzle-kit push",
15
+ "db:studio": "DOTENV_CONFIG_PATH=../.env drizzle-kit studio"
16
+ },
17
+ "dependencies": {
18
+ "{{PROJECT_NAME}}-shared": "workspace:*",
19
+ "@rebasepro/backend": "^4.0.0",
20
+ "cors": "^2.8.5",
21
+ "drizzle-orm": "^0.44.4",
22
+ "express": "^5.1.0",
23
+ "pg": "^8.11.3",
24
+ "ws": "^8.16.0",
25
+ "zod": "^3.22.4",
26
+ "dotenv": "^16.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/pg": "^8.6.5",
30
+ "@types/node": "^20.10.5",
31
+ "@types/ws": "^8.5.10",
32
+ "drizzle-kit": "^0.31.4",
33
+ "tsx": "^4.20.6",
34
+ "typescript": "^5.9.2"
35
+ }
36
+ }
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from "child_process";
3
+
4
+ // --- Helper Functions ---
5
+ const formatTerminalText = (text: string, options: {
6
+ bold?: boolean;
7
+ backgroundColor?: "blue" | "green" | "red" | "yellow" | "cyan" | "magenta";
8
+ textColor?: "white" | "black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan";
9
+ } = {}): string => {
10
+ let codes = "";
11
+ if (options.bold) codes += "\x1b[1m";
12
+ if (options.backgroundColor) {
13
+ const bgColors = {
14
+ blue: "\x1b[44m",
15
+ green: "\x1b[42m",
16
+ red: "\x1b[41m",
17
+ yellow: "\x1b[43m",
18
+ cyan: "\x1b[46m",
19
+ magenta: "\x1b[45m"
20
+ } as const;
21
+ codes += bgColors[options.backgroundColor];
22
+ }
23
+ if (options.textColor) {
24
+ const textColors = {
25
+ white: "\x1b[37m",
26
+ black: "\x1b[30m",
27
+ red: "\x1b[31m",
28
+ green: "\x1b[32m",
29
+ yellow: "\x1b[33m",
30
+ blue: "\x1b[34m",
31
+ magenta: "\x1b[35m",
32
+ cyan: "\x1b[36m"
33
+ } as const;
34
+ codes += textColors[options.textColor];
35
+ }
36
+ return `${codes}${text}\x1b[0m`;
37
+ };
38
+
39
+ // Run drizzle-kit generate with the correct env path
40
+ const child = spawn("npx", ["drizzle-kit", "generate"], {
41
+ stdio: "inherit",
42
+ env: {
43
+ ...process.env,
44
+ DOTENV_CONFIG_PATH: "../.env"
45
+ },
46
+ shell: true
47
+ });
48
+
49
+ child.on("close", (code) => {
50
+ if (code === 0) {
51
+ console.log("");
52
+ console.log(`You can now run ${formatTerminalText("pnpm db:migrate", {
53
+ bold: true,
54
+ backgroundColor: "green",
55
+ textColor: "black"
56
+ })} to apply the migrations to your database.`);
57
+ console.log("");
58
+ }
59
+ process.exit(code ?? 0);
60
+ });