@rebasepro/cli 0.1.0 → 0.2.1
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 +17 -196
- package/dist/index.cjs +158 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.es.js +142 -28
- package/dist/index.es.js.map +1 -1
- package/package.json +13 -11
- package/templates/template/.cursorrules +2 -0
- package/templates/template/.env.example +7 -0
- package/templates/template/.github/copilot-instructions.md +2 -0
- package/templates/template/.windsurfrules +2 -0
- package/templates/template/README.md +2 -2
- package/templates/template/ai-instructions.md +14 -0
- package/templates/template/backend/Dockerfile +1 -1
- package/templates/template/backend/drizzle.config.ts +11 -5
- package/templates/template/backend/package.json +4 -3
- package/templates/template/backend/src/env.ts +2 -43
- package/templates/template/backend/src/index.ts +10 -12
- package/templates/template/config/collections/index.ts +6 -4
- package/templates/template/config/collections/posts.ts +3 -17
- package/templates/template/config/collections/roles.ts +72 -0
- package/templates/template/config/collections/users.ts +141 -0
- package/templates/template/config/index.ts +1 -1
- package/templates/template/config/package.json +2 -1
- package/templates/template/docker-compose.yml +1 -1
- package/templates/template/frontend/package.json +3 -1
- package/templates/template/frontend/src/App.tsx +3 -3
- package/templates/template/frontend/src/virtual.d.ts +6 -0
- package/templates/template/frontend/tsconfig.json +3 -2
- package/templates/template/frontend/vite.config.ts +1 -1
- package/templates/template/pnpm-workspace.yaml +6 -0
package/dist/index.es.js
CHANGED
|
@@ -4,8 +4,8 @@ import inquirer from "inquirer";
|
|
|
4
4
|
import path from "path";
|
|
5
5
|
import fs from "fs";
|
|
6
6
|
import { promisify } from "util";
|
|
7
|
-
import execa from "execa";
|
|
8
|
-
import
|
|
7
|
+
import { execa, execaCommandSync } from "execa";
|
|
8
|
+
import { cp } from "fs/promises";
|
|
9
9
|
import { fileURLToPath } from "url";
|
|
10
10
|
import crypto from "crypto";
|
|
11
11
|
import { generateSDK } from "@rebasepro/sdk-generator";
|
|
@@ -51,7 +51,6 @@ function getPMCommands(pm) {
|
|
|
51
51
|
};
|
|
52
52
|
}
|
|
53
53
|
const access = promisify(fs.access);
|
|
54
|
-
const copy = promisify(ncp);
|
|
55
54
|
const __filename$2 = fileURLToPath(import.meta.url);
|
|
56
55
|
const __dirname$2 = path.dirname(__filename$2);
|
|
57
56
|
function findParentDir(currentDir, targetName) {
|
|
@@ -78,8 +77,12 @@ async function promptForOptions(rawArgs, pm) {
|
|
|
78
77
|
{
|
|
79
78
|
"--git": Boolean,
|
|
80
79
|
"--install": Boolean,
|
|
80
|
+
"--database-url": String,
|
|
81
|
+
"--introspect": Boolean,
|
|
82
|
+
"--yes": Boolean,
|
|
81
83
|
"-g": "--git",
|
|
82
|
-
"-i": "--install"
|
|
84
|
+
"-i": "--install",
|
|
85
|
+
"-y": "--yes"
|
|
83
86
|
},
|
|
84
87
|
{
|
|
85
88
|
argv: rawArgs.slice(3),
|
|
@@ -88,6 +91,24 @@ async function promptForOptions(rawArgs, pm) {
|
|
|
88
91
|
}
|
|
89
92
|
);
|
|
90
93
|
const nameArg = args._[0];
|
|
94
|
+
const isNonInteractive = args["--yes"] || false;
|
|
95
|
+
if (isNonInteractive) {
|
|
96
|
+
const projectName2 = nameArg || "my-rebase-app";
|
|
97
|
+
const targetDirectory2 = path.resolve(process.cwd(), projectName2);
|
|
98
|
+
const templateDirectory2 = path.resolve(cliRoot, "templates", "template");
|
|
99
|
+
const pmCommands2 = getPMCommands(pm);
|
|
100
|
+
return {
|
|
101
|
+
projectName: path.basename(targetDirectory2),
|
|
102
|
+
git: args["--git"] ?? false,
|
|
103
|
+
installDeps: args["--install"] ?? false,
|
|
104
|
+
targetDirectory: targetDirectory2,
|
|
105
|
+
templateDirectory: templateDirectory2,
|
|
106
|
+
databaseUrl: args["--database-url"] || void 0,
|
|
107
|
+
introspect: args["--introspect"] || false,
|
|
108
|
+
pm,
|
|
109
|
+
pmCommands: pmCommands2
|
|
110
|
+
};
|
|
111
|
+
}
|
|
91
112
|
const questions = [];
|
|
92
113
|
if (!nameArg) {
|
|
93
114
|
questions.push({
|
|
@@ -167,9 +188,8 @@ async function createProject(options) {
|
|
|
167
188
|
}
|
|
168
189
|
console.log(chalk.gray(" Copying project files..."));
|
|
169
190
|
try {
|
|
170
|
-
await
|
|
171
|
-
|
|
172
|
-
dot: true,
|
|
191
|
+
await cp(options.templateDirectory, options.targetDirectory, {
|
|
192
|
+
recursive: true,
|
|
173
193
|
filter: (source) => {
|
|
174
194
|
const basename = path.basename(source);
|
|
175
195
|
return basename !== "node_modules" && basename !== ".DS_Store";
|
|
@@ -273,6 +293,10 @@ async function replacePlaceholders(options) {
|
|
|
273
293
|
const viewBin = "npm";
|
|
274
294
|
const getPackageVersion = async (pkgName) => {
|
|
275
295
|
if (versionCache.has(pkgName)) return versionCache.get(pkgName);
|
|
296
|
+
if (process.env.REBASE_E2E === "true") {
|
|
297
|
+
versionCache.set(pkgName, cliVersion);
|
|
298
|
+
return cliVersion;
|
|
299
|
+
}
|
|
276
300
|
let versionToUse = cliVersion;
|
|
277
301
|
try {
|
|
278
302
|
const { stdout } = await execa(viewBin, ["view", `${pkgName}@${cliVersion}`, "version"]);
|
|
@@ -440,6 +464,7 @@ async function generateSdkCommand(args) {
|
|
|
440
464
|
console.log("");
|
|
441
465
|
console.log(chalk.cyan(" → Loading collection definitions..."));
|
|
442
466
|
const collections = await loadCollections(resolvedCollectionsDir);
|
|
467
|
+
collections.sort((a, b) => a.slug.localeCompare(b.slug));
|
|
443
468
|
if (collections.length === 0) {
|
|
444
469
|
console.log(chalk.red(" ✗ No collections found. Nothing to generate."));
|
|
445
470
|
process.exit(1);
|
|
@@ -499,15 +524,20 @@ function getActiveBackendPlugin(backendDir) {
|
|
|
499
524
|
if (!fs.existsSync(pkgPath)) return null;
|
|
500
525
|
try {
|
|
501
526
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
502
|
-
const deps = {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
527
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
528
|
+
const candidates = Object.keys(deps).filter(
|
|
529
|
+
(dep) => dep.startsWith("@rebasepro/server-") && dep !== "@rebasepro/server-core"
|
|
530
|
+
);
|
|
531
|
+
if (candidates.length === 0) return null;
|
|
532
|
+
if (candidates.includes("@rebasepro/server-postgresql")) {
|
|
533
|
+
return "@rebasepro/server-postgresql";
|
|
534
|
+
}
|
|
535
|
+
for (const candidate of candidates) {
|
|
536
|
+
if (resolvePluginCliScript(backendDir, candidate)) {
|
|
537
|
+
return candidate;
|
|
509
538
|
}
|
|
510
539
|
}
|
|
540
|
+
return candidates[0];
|
|
511
541
|
} catch {
|
|
512
542
|
}
|
|
513
543
|
return null;
|
|
@@ -569,7 +599,7 @@ function requireProjectRoot() {
|
|
|
569
599
|
if (!root) {
|
|
570
600
|
console.error(chalk.red("✗ Could not find a Rebase project root."));
|
|
571
601
|
console.error(chalk.gray(" Make sure you are inside a Rebase project directory"));
|
|
572
|
-
console.error(chalk.gray(" (one with backend/, frontend/, and
|
|
602
|
+
console.error(chalk.gray(" (one with backend/, frontend/, and config/ directories)."));
|
|
573
603
|
process.exit(1);
|
|
574
604
|
}
|
|
575
605
|
return root;
|
|
@@ -752,10 +782,12 @@ async function devCommand(rawArgs) {
|
|
|
752
782
|
"--backend-only": Boolean,
|
|
753
783
|
"--frontend-only": Boolean,
|
|
754
784
|
"--port": Number,
|
|
785
|
+
"--generate": Boolean,
|
|
755
786
|
"--help": Boolean,
|
|
756
787
|
"-b": "--backend-only",
|
|
757
788
|
"-f": "--frontend-only",
|
|
758
789
|
"-p": "--port",
|
|
790
|
+
"-g": "--generate",
|
|
759
791
|
"-h": "--help"
|
|
760
792
|
},
|
|
761
793
|
{
|
|
@@ -773,6 +805,7 @@ async function devCommand(rawArgs) {
|
|
|
773
805
|
const frontendDir = findFrontendDir(projectRoot);
|
|
774
806
|
const backendOnly = args["--backend-only"] || false;
|
|
775
807
|
const frontendOnly = args["--frontend-only"] || false;
|
|
808
|
+
const shouldGenerate = args["--generate"] || process.env.REBASE_AUTO_GENERATE === "true" || process.env.REBASE_GENERATE === "true";
|
|
776
809
|
const startPort = resolveStartPort(projectRoot, args["--port"]);
|
|
777
810
|
console.log("");
|
|
778
811
|
console.log(chalk.bold(" 🚀 Rebase Dev Server"));
|
|
@@ -792,10 +825,10 @@ async function devCommand(rawArgs) {
|
|
|
792
825
|
console.log("");
|
|
793
826
|
console.log(chalk.cyan("┌────────────────────────────────────────────────────────────┐"));
|
|
794
827
|
console.log(chalk.cyan("│ │"));
|
|
795
|
-
console.log(chalk.cyan("│
|
|
828
|
+
console.log(chalk.cyan("│ ✦ Rebase Admin App is ready! │"));
|
|
796
829
|
const cleanUrl = stripAnsi(frontendUrl);
|
|
797
|
-
const paddedUrl = cleanUrl.padEnd(
|
|
798
|
-
console.log(chalk.cyan("│
|
|
830
|
+
const paddedUrl = cleanUrl.padEnd(41);
|
|
831
|
+
console.log(chalk.cyan("│ ➜ Frontend URL: ") + chalk.white(paddedUrl) + chalk.cyan("│"));
|
|
799
832
|
console.log(chalk.cyan("│ │"));
|
|
800
833
|
console.log(chalk.cyan("└────────────────────────────────────────────────────────────┘"));
|
|
801
834
|
console.log("");
|
|
@@ -814,7 +847,7 @@ async function devCommand(rawArgs) {
|
|
|
814
847
|
if (child.pid && !child.killed) {
|
|
815
848
|
try {
|
|
816
849
|
if (process.platform === "win32") {
|
|
817
|
-
|
|
850
|
+
execaCommandSync(`taskkill /pid ${child.pid} /T /F`);
|
|
818
851
|
} else {
|
|
819
852
|
process.kill(-child.pid, "SIGKILL");
|
|
820
853
|
}
|
|
@@ -892,9 +925,67 @@ async function devCommand(rawArgs) {
|
|
|
892
925
|
console.log(` ${chalk.cyan("▶")} Backend: ${chalk.gray(backendDir)}`);
|
|
893
926
|
console.log(` ${chalk.gray("↳ PORT")} = ${chalk.white(String(startPort))}`);
|
|
894
927
|
let frontendLaunched = false;
|
|
928
|
+
if (shouldGenerate) {
|
|
929
|
+
console.log(chalk.gray(" → Ensuring schema and SDK are generated on start..."));
|
|
930
|
+
try {
|
|
931
|
+
const activePlugin = getActiveBackendPlugin(backendDir);
|
|
932
|
+
const pluginCli = activePlugin ? resolvePluginCliScript(backendDir, activePlugin) : null;
|
|
933
|
+
if (pluginCli) {
|
|
934
|
+
await execa(tsxBin, [pluginCli, "schema", "generate"], {
|
|
935
|
+
cwd: backendDir,
|
|
936
|
+
stdio: "inherit",
|
|
937
|
+
env
|
|
938
|
+
});
|
|
939
|
+
}
|
|
940
|
+
await execa("npx", ["rebase", "generate-sdk"], {
|
|
941
|
+
cwd: projectRoot,
|
|
942
|
+
stdio: "inherit",
|
|
943
|
+
env
|
|
944
|
+
});
|
|
945
|
+
console.log(chalk.green(" ✓ Initial schema and SDK generated successfully.\n"));
|
|
946
|
+
} catch (err) {
|
|
947
|
+
console.error(chalk.red(` ✗ Initial schema/SDK generation failed: ${err.message || err}
|
|
948
|
+
`));
|
|
949
|
+
}
|
|
950
|
+
const collectionsDir = path.join(projectRoot, "config", "collections");
|
|
951
|
+
if (fs.existsSync(collectionsDir)) {
|
|
952
|
+
let watchDebounce = null;
|
|
953
|
+
fs.watch(collectionsDir, { recursive: true }, (eventType, filename) => {
|
|
954
|
+
if (!filename || filename.startsWith(".") || filename.endsWith(".tmp")) return;
|
|
955
|
+
if (watchDebounce) clearTimeout(watchDebounce);
|
|
956
|
+
watchDebounce = setTimeout(async () => {
|
|
957
|
+
console.log(chalk.yellow(`
|
|
958
|
+
🔄 Collection change detected (${filename}). Regenerating schema & SDK...`));
|
|
959
|
+
try {
|
|
960
|
+
const activePlugin = getActiveBackendPlugin(backendDir);
|
|
961
|
+
const pluginCli = activePlugin ? resolvePluginCliScript(backendDir, activePlugin) : null;
|
|
962
|
+
if (pluginCli) {
|
|
963
|
+
await execa(tsxBin, [pluginCli, "schema", "generate"], {
|
|
964
|
+
cwd: backendDir,
|
|
965
|
+
stdio: "inherit",
|
|
966
|
+
env
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
await execa("npx", ["rebase", "generate-sdk"], {
|
|
970
|
+
cwd: projectRoot,
|
|
971
|
+
stdio: "inherit",
|
|
972
|
+
env
|
|
973
|
+
});
|
|
974
|
+
console.log(chalk.green(" ✓ Schema & SDK regenerated successfully. Hono will reload."));
|
|
975
|
+
} catch (err) {
|
|
976
|
+
console.error(chalk.red(` ✗ Failed to regenerate schema/SDK: ${err.message || err}`));
|
|
977
|
+
}
|
|
978
|
+
}, 300);
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
const watchArgs = ["watch", "--conditions", "development", "src/index.ts"];
|
|
983
|
+
if (!shouldGenerate) {
|
|
984
|
+
watchArgs.splice(1, 0, `--watch="${path.join("..", "config", "**", "*")}"`);
|
|
985
|
+
}
|
|
895
986
|
const backendChild = execa(
|
|
896
987
|
tsxBin,
|
|
897
|
-
|
|
988
|
+
watchArgs,
|
|
898
989
|
{
|
|
899
990
|
cwd: backendDir,
|
|
900
991
|
stdio: ["inherit", "pipe", "pipe"],
|
|
@@ -967,6 +1058,7 @@ ${chalk.green.bold("Options")}
|
|
|
967
1058
|
${chalk.blue("--backend-only, -b")} Only start the backend server
|
|
968
1059
|
${chalk.blue("--frontend-only, -f")} Only start the frontend server
|
|
969
1060
|
${chalk.blue("--port, -p")} Backend port (default: auto-detected per project)
|
|
1061
|
+
${chalk.blue("--generate, -g")} Enable automatic schema and SDK generation on startup and file changes
|
|
970
1062
|
|
|
971
1063
|
${chalk.green.bold("Description")}
|
|
972
1064
|
Starts both the backend (tsx watch + Hono) and frontend (Vite)
|
|
@@ -979,6 +1071,10 @@ ${chalk.green.bold("Description")}
|
|
|
979
1071
|
If the assigned port is already in use, the server will automatically
|
|
980
1072
|
try the next available port. The frontend is started only after the
|
|
981
1073
|
backend is ready, and VITE_API_URL is injected automatically.
|
|
1074
|
+
|
|
1075
|
+
By default, automatic schema and SDK generation is disabled on startup
|
|
1076
|
+
and file changes. Pass --generate (-g) or set REBASE_AUTO_GENERATE=true
|
|
1077
|
+
in your environment to enable it.
|
|
982
1078
|
`);
|
|
983
1079
|
}
|
|
984
1080
|
async function buildCommand() {
|
|
@@ -1073,12 +1169,12 @@ async function resetPassword(rawArgs) {
|
|
|
1073
1169
|
env.DOTENV_CONFIG_PATH = envFile;
|
|
1074
1170
|
}
|
|
1075
1171
|
const scriptContent = `
|
|
1076
|
-
import { createPostgresDatabaseConnection } from "@rebasepro/server-
|
|
1077
|
-
import { hashPassword } from "@rebasepro/server-core
|
|
1172
|
+
import { createPostgresDatabaseConnection } from "@rebasepro/server-postgresql";
|
|
1173
|
+
import { hashPassword } from "@rebasepro/server-core";
|
|
1078
1174
|
import { eq } from "drizzle-orm";
|
|
1079
|
-
import { users } from "@rebasepro/server-core/src/db/auth-schema";
|
|
1080
1175
|
import * as dotenv from "dotenv";
|
|
1081
1176
|
import path from "path";
|
|
1177
|
+
import fs from "fs";
|
|
1082
1178
|
|
|
1083
1179
|
dotenv.config({ path: "${envFile || path.join(projectRoot, ".env")}" });
|
|
1084
1180
|
|
|
@@ -1089,12 +1185,30 @@ async function resetPassword() {
|
|
|
1089
1185
|
const { db } = createPostgresDatabaseConnection(process.env.DATABASE_URL!);
|
|
1090
1186
|
const hash = await hashPassword(newPassword);
|
|
1091
1187
|
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
.
|
|
1188
|
+
let usersTable;
|
|
1189
|
+
try {
|
|
1190
|
+
const schemaPath = path.resolve("./src/schema.generated.ts");
|
|
1191
|
+
if (fs.existsSync(schemaPath)) {
|
|
1192
|
+
const schema = await import("file://" + schemaPath);
|
|
1193
|
+
usersTable = schema.users || schema.tables?.users;
|
|
1194
|
+
}
|
|
1195
|
+
} catch (e) {
|
|
1196
|
+
// ignore and fallback
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
if (!usersTable) {
|
|
1200
|
+
const pgServer = await import("@rebasepro/server-postgresql");
|
|
1201
|
+
usersTable = pgServer.users;
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
const passwordHashKey = (usersTable.passwordHash || "passwordHash" in usersTable) ? "passwordHash" : "password_hash";
|
|
1205
|
+
|
|
1206
|
+
const result = await db.update(usersTable)
|
|
1207
|
+
.set({ [passwordHashKey]: hash })
|
|
1208
|
+
.where(eq(usersTable.email, email))
|
|
1095
1209
|
.returning({
|
|
1096
|
-
id:
|
|
1097
|
-
email:
|
|
1210
|
+
id: usersTable.id,
|
|
1211
|
+
email: usersTable.email
|
|
1098
1212
|
});
|
|
1099
1213
|
|
|
1100
1214
|
if (result.length > 0) {
|