@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.
- package/LICENSE +201 -0
- package/README.md +54 -0
- package/bin/rebase.js +4 -0
- package/dist/auth.d.ts +5 -0
- package/dist/cli.d.ts +1 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/index.cjs +277 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.es.js +265 -0
- package/dist/index.es.js.map +1 -0
- package/package.json +59 -0
- package/templates/template/.env.template +31 -0
- package/templates/template/README.md +59 -0
- package/templates/template/backend/Dockerfile +25 -0
- package/templates/template/backend/drizzle.config.ts +22 -0
- package/templates/template/backend/package.json +36 -0
- package/templates/template/backend/scripts/db-generate.ts +60 -0
- package/templates/template/backend/src/index.ts +134 -0
- package/templates/template/backend/src/schema.generated.ts +8 -0
- package/templates/template/backend/tsconfig.json +19 -0
- package/templates/template/docker-compose.yml +48 -0
- package/templates/template/frontend/Dockerfile +28 -0
- package/templates/template/frontend/index.html +13 -0
- package/templates/template/frontend/package.json +52 -0
- package/templates/template/frontend/src/App.tsx +132 -0
- package/templates/template/frontend/src/index.css +2 -0
- package/templates/template/frontend/src/main.tsx +18 -0
- package/templates/template/frontend/tsconfig.json +20 -0
- package/templates/template/frontend/vite.config.ts +26 -0
- package/templates/template/package.json +32 -0
- package/templates/template/shared/collections/index.ts +3 -0
- package/templates/template/shared/collections/posts.ts +53 -0
- package/templates/template/shared/index.ts +6 -0
- package/templates/template/shared/package.json +28 -0
- package/templates/template/shared/tsconfig.json +20 -0
package/dist/index.es.js
ADDED
|
@@ -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
|
+
});
|