@rebasepro/cli 0.6.1 → 0.8.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/README.md +0 -1
- package/dist/commands/api-keys.d.ts +1 -0
- package/dist/commands/generate_sdk.d.ts +1 -1
- package/dist/index.es.js +464 -58
- package/dist/index.es.js.map +1 -1
- package/dist/utils/project.d.ts +15 -0
- package/package.json +8 -9
- package/templates/template/backend/Dockerfile +4 -3
- package/templates/template/backend/functions/hello.ts +32 -32
- package/templates/template/backend/package.json +1 -1
- package/templates/template/backend/src/index.ts +1 -2
- package/templates/template/config/collections/posts.ts +4 -4
- package/templates/template/config/collections/presets/ecommerce/categories.ts +3 -1
- package/templates/template/config/collections/presets/ecommerce/orders.ts +3 -1
- package/templates/template/config/collections/presets/ecommerce/products.ts +3 -1
- package/templates/template/config/collections/tags.ts +1 -3
- package/templates/template/config/collections/users.ts +4 -5
- package/templates/template/frontend/Dockerfile +5 -3
- package/templates/template/package.json +3 -2
- package/templates/template/pnpm-workspace.yaml +2 -0
- package/templates/template/scripts/example.ts +5 -2
- package/skills/rebase-admin/SKILL.md +0 -710
- package/skills/rebase-api/SKILL.md +0 -662
- package/skills/rebase-api/references/.gitkeep +0 -3
- package/skills/rebase-auth/SKILL.md +0 -1143
- package/skills/rebase-auth/references/.gitkeep +0 -3
- package/skills/rebase-backend-postgres/SKILL.md +0 -633
- package/skills/rebase-backend-postgres/references/.gitkeep +0 -3
- package/skills/rebase-basics/SKILL.md +0 -749
- package/skills/rebase-basics/references/.gitkeep +0 -3
- package/skills/rebase-collections/SKILL.md +0 -1328
- package/skills/rebase-collections/references/.gitkeep +0 -3
- package/skills/rebase-cron-jobs/SKILL.md +0 -699
- package/skills/rebase-cron-jobs/references/.gitkeep +0 -1
- package/skills/rebase-custom-functions/SKILL.md +0 -233
- package/skills/rebase-deployment/SKILL.md +0 -885
- package/skills/rebase-deployment/references/.gitkeep +0 -3
- package/skills/rebase-design-language/SKILL.md +0 -692
- package/skills/rebase-email/SKILL.md +0 -701
- package/skills/rebase-email/references/.gitkeep +0 -1
- package/skills/rebase-entity-history/SKILL.md +0 -485
- package/skills/rebase-entity-history/references/.gitkeep +0 -1
- package/skills/rebase-local-env-setup/SKILL.md +0 -189
- package/skills/rebase-local-env-setup/references/.gitkeep +0 -3
- package/skills/rebase-realtime/SKILL.md +0 -755
- package/skills/rebase-realtime/references/.gitkeep +0 -3
- package/skills/rebase-sdk/SKILL.md +0 -594
- package/skills/rebase-sdk/references/.gitkeep +0 -0
- package/skills/rebase-storage/SKILL.md +0 -765
- package/skills/rebase-storage/references/.gitkeep +0 -3
- package/skills/rebase-studio/SKILL.md +0 -746
- package/skills/rebase-studio/references/.gitkeep +0 -3
- package/skills/rebase-ui-components/SKILL.md +0 -1488
- package/skills/rebase-ui-components/references/.gitkeep +0 -3
- package/skills/rebase-webhooks/SKILL.md +0 -623
- package/skills/rebase-webhooks/references/.gitkeep +0 -1
- package/templates/template/backend/drizzle.config.ts +0 -51
package/README.md
CHANGED
|
@@ -23,7 +23,6 @@ The CLI is also bundled with every Rebase project as a local dependency.
|
|
|
23
23
|
| `rebase db push` | Apply schema directly to database (dev) |
|
|
24
24
|
| `rebase db generate` | Generate SQL migration files |
|
|
25
25
|
| `rebase db migrate` | Run pending migrations |
|
|
26
|
-
| `rebase db studio` | Open Drizzle Studio |
|
|
27
26
|
| `rebase generate-sdk` | Generate a typed TypeScript SDK from collections |
|
|
28
27
|
| `rebase auth reset-password` | Reset a user's password |
|
|
29
28
|
| `rebase doctor` | Detect schema drift between collections, Drizzle schema, and database |
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function apiKeysCommand(subcommand: string | undefined, rawArgs: string[]): Promise<void>;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* CLI command: generate-sdk
|
|
3
3
|
*
|
|
4
4
|
* Reads collection definitions from a specified directory (default: ./config/collections),
|
|
5
|
-
* generates a typed
|
|
5
|
+
* generates a typed TypeScript SDK, and writes it to the output directory (default: ./generated/sdk).
|
|
6
6
|
*
|
|
7
7
|
* Uses jiti for dynamic TypeScript import of collection files.
|
|
8
8
|
*/
|
package/dist/index.es.js
CHANGED
|
@@ -11,6 +11,7 @@ import { fileURLToPath } from "url";
|
|
|
11
11
|
import crypto from "crypto";
|
|
12
12
|
import { generateSDK } from "@rebasepro/sdk-generator";
|
|
13
13
|
import { execSync, spawn } from "child_process";
|
|
14
|
+
import { createRequire } from "module";
|
|
14
15
|
//#region src/utils/package-manager.ts
|
|
15
16
|
/**
|
|
16
17
|
* Package manager detection and command abstraction.
|
|
@@ -130,9 +131,9 @@ function getPMCommands(pm) {
|
|
|
130
131
|
//#endregion
|
|
131
132
|
//#region src/commands/init.ts
|
|
132
133
|
var access = promisify(fs.access);
|
|
133
|
-
var __filename$
|
|
134
|
-
var __dirname$
|
|
135
|
-
function findParentDir
|
|
134
|
+
var __filename$1 = fileURLToPath(import.meta.url);
|
|
135
|
+
var __dirname$1 = path.dirname(__filename$1);
|
|
136
|
+
function findParentDir(currentDir, targetName) {
|
|
136
137
|
const root = path.parse(currentDir).root;
|
|
137
138
|
while (currentDir && currentDir !== root) {
|
|
138
139
|
if (path.basename(currentDir) === targetName) return currentDir;
|
|
@@ -140,7 +141,7 @@ function findParentDir$1(currentDir, targetName) {
|
|
|
140
141
|
}
|
|
141
142
|
return null;
|
|
142
143
|
}
|
|
143
|
-
var cliRoot = findParentDir
|
|
144
|
+
var cliRoot = findParentDir(__dirname$1, "cli");
|
|
144
145
|
var PRESET_CHOICES = [
|
|
145
146
|
{
|
|
146
147
|
name: "Blog — Posts, Authors, Tags (with markdown editor)",
|
|
@@ -574,7 +575,7 @@ async function configureEnvFile(targetDirectory, databaseUrl) {
|
|
|
574
575
|
* CLI command: generate-sdk
|
|
575
576
|
*
|
|
576
577
|
* Reads collection definitions from a specified directory (default: ./config/collections),
|
|
577
|
-
* generates a typed
|
|
578
|
+
* generates a typed TypeScript SDK, and writes it to the output directory (default: ./generated/sdk).
|
|
578
579
|
*
|
|
579
580
|
* Uses jiti for dynamic TypeScript import of collection files.
|
|
580
581
|
*/
|
|
@@ -720,7 +721,7 @@ function findProjectRoot(startDir = process.cwd()) {
|
|
|
720
721
|
try {
|
|
721
722
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
722
723
|
if (pkg.workspaces && Array.isArray(pkg.workspaces)) {
|
|
723
|
-
if (pkg.workspaces.some((w) => w === "backend"
|
|
724
|
+
if (pkg.workspaces.some((w) => w === "backend")) return dir;
|
|
724
725
|
}
|
|
725
726
|
} catch {}
|
|
726
727
|
if (fs.existsSync(path.join(dir, "backend")) && fs.existsSync(path.join(dir, "config"))) return dir;
|
|
@@ -811,6 +812,41 @@ function resolveTsx(projectRoot) {
|
|
|
811
812
|
return resolveLocalBin(projectRoot, "tsx");
|
|
812
813
|
}
|
|
813
814
|
/**
|
|
815
|
+
* Validate that a resolved tsx binary actually has an intact installation.
|
|
816
|
+
*
|
|
817
|
+
* `resolveLocalBin` only checks whether `node_modules/.bin/tsx` (a symlink)
|
|
818
|
+
* exists. If the pnpm content-addressable store was cleaned or a previous
|
|
819
|
+
* install was interrupted, the symlink can exist while critical files inside
|
|
820
|
+
* the tsx package (e.g. `dist/preflight.cjs`) are missing — causing a
|
|
821
|
+
* confusing MODULE_NOT_FOUND error at runtime.
|
|
822
|
+
*
|
|
823
|
+
* This function follows the symlink, walks up to find the tsx package root
|
|
824
|
+
* (`package.json` with `name: "tsx"`), and verifies that `dist/preflight.cjs`
|
|
825
|
+
* is present. Returns `null` when the installation looks healthy, or an
|
|
826
|
+
* error description string when it appears corrupted.
|
|
827
|
+
*/
|
|
828
|
+
function validateTsxInstallation(tsxBinPath) {
|
|
829
|
+
try {
|
|
830
|
+
const realPath = fs.realpathSync(tsxBinPath);
|
|
831
|
+
let dir = path.dirname(realPath);
|
|
832
|
+
const fsRoot = path.parse(dir).root;
|
|
833
|
+
for (let depth = 0; depth < 10 && dir !== fsRoot; depth++) {
|
|
834
|
+
const pkgPath = path.join(dir, "package.json");
|
|
835
|
+
if (fs.existsSync(pkgPath)) try {
|
|
836
|
+
if (JSON.parse(fs.readFileSync(pkgPath, "utf-8")).name === "tsx") {
|
|
837
|
+
const preflightPath = path.join(dir, "dist", "preflight.cjs");
|
|
838
|
+
if (!fs.existsSync(preflightPath)) return `tsx package at ${dir} is missing dist/preflight.cjs`;
|
|
839
|
+
return null;
|
|
840
|
+
}
|
|
841
|
+
} catch {}
|
|
842
|
+
dir = path.dirname(dir);
|
|
843
|
+
}
|
|
844
|
+
return null;
|
|
845
|
+
} catch (err) {
|
|
846
|
+
return `tsx binary symlink is broken: ${err instanceof Error ? err.message : String(err)}`;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
814
850
|
* Require the project root or exit with a helpful error.
|
|
815
851
|
*/
|
|
816
852
|
function requireProjectRoot() {
|
|
@@ -962,7 +998,6 @@ ${chalk.green.bold("Commands")}
|
|
|
962
998
|
${chalk.blue.bold("push")} Apply schema directly to database (development)
|
|
963
999
|
${chalk.blue.bold("generate")} Generate migration files
|
|
964
1000
|
${chalk.blue.bold("migrate")} Run pending migrations
|
|
965
|
-
${chalk.blue.bold("studio")} Open Studio viewer
|
|
966
1001
|
${chalk.blue.bold("branch")} Database branching (create, list, delete, info)
|
|
967
1002
|
|
|
968
1003
|
${chalk.green.bold("Examples")}
|
|
@@ -1156,6 +1191,16 @@ async function devCommand(rawArgs) {
|
|
|
1156
1191
|
console.error(chalk.gray(` Install it with: ${addCmd}`));
|
|
1157
1192
|
process.exit(1);
|
|
1158
1193
|
}
|
|
1194
|
+
const tsxValidationError = validateTsxInstallation(tsxBin);
|
|
1195
|
+
if (tsxValidationError) {
|
|
1196
|
+
const installCmd = getPMCommands(detectPackageManager(projectRoot)).install.join(" ");
|
|
1197
|
+
console.error(chalk.red(" ✗ tsx installation appears corrupted."));
|
|
1198
|
+
console.error(chalk.gray(` ${tsxValidationError}`));
|
|
1199
|
+
console.error("");
|
|
1200
|
+
console.error(chalk.gray(" To fix, run:"));
|
|
1201
|
+
console.error(chalk.cyan(` rm -rf node_modules && ${installCmd}`));
|
|
1202
|
+
process.exit(1);
|
|
1203
|
+
}
|
|
1159
1204
|
const envFile = findEnvFile(projectRoot);
|
|
1160
1205
|
const env = { ...process.env };
|
|
1161
1206
|
if (envFile) env.DOTENV_CONFIG_PATH = envFile;
|
|
@@ -1286,9 +1331,28 @@ async function devCommand(rawArgs) {
|
|
|
1286
1331
|
}
|
|
1287
1332
|
});
|
|
1288
1333
|
});
|
|
1334
|
+
/** Whether we've already shown a corrupted-modules recovery hint. */
|
|
1335
|
+
let corruptedModulesWarned = false;
|
|
1289
1336
|
backendChild.stderr?.on("data", (data) => {
|
|
1290
1337
|
data.toString().split("\n").filter(Boolean).forEach((line) => {
|
|
1291
1338
|
console.log(`${chalk.cyan.bold("[backend]")} ${line}`);
|
|
1339
|
+
if (!corruptedModulesWarned) {
|
|
1340
|
+
const cleanLine = stripAnsi(line);
|
|
1341
|
+
if (cleanLine.includes("Cannot find module") && cleanLine.includes("node_modules/.pnpm/")) {
|
|
1342
|
+
corruptedModulesWarned = true;
|
|
1343
|
+
setTimeout(() => {
|
|
1344
|
+
const installCmd = getPMCommands(detectPackageManager(projectRoot)).install.join(" ");
|
|
1345
|
+
console.error("");
|
|
1346
|
+
console.error(chalk.red(" ✗ node_modules appears corrupted — a required file is missing."));
|
|
1347
|
+
console.error(chalk.gray(" This usually happens when a previous install was interrupted"));
|
|
1348
|
+
console.error(chalk.gray(" or the package manager store was cleaned."));
|
|
1349
|
+
console.error("");
|
|
1350
|
+
console.error(chalk.gray(" To fix, stop the dev server and run:"));
|
|
1351
|
+
console.error(chalk.cyan(` rm -rf node_modules && ${installCmd}`));
|
|
1352
|
+
console.error("");
|
|
1353
|
+
}, 200);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1292
1356
|
});
|
|
1293
1357
|
});
|
|
1294
1358
|
children.push(backendChild);
|
|
@@ -1431,19 +1495,90 @@ async function resetPassword(rawArgs) {
|
|
|
1431
1495
|
process.exit(1);
|
|
1432
1496
|
}
|
|
1433
1497
|
const projectRoot = requireProjectRoot();
|
|
1498
|
+
let envServiceKey;
|
|
1499
|
+
const envFile = findEnvFile(projectRoot);
|
|
1500
|
+
if (envFile && fs.existsSync(envFile)) try {
|
|
1501
|
+
const match = fs.readFileSync(envFile, "utf8").match(/^\s*REBASE_SERVICE_KEY\s*=\s*['"]?(.*?)['"]?\s*$/m);
|
|
1502
|
+
if (match && match[1]) envServiceKey = match[1];
|
|
1503
|
+
} catch {}
|
|
1504
|
+
let baseUrl = process.env.REBASE_BASE_URL;
|
|
1505
|
+
let serviceKey = process.env.REBASE_SERVICE_KEY || envServiceKey;
|
|
1506
|
+
const statePath = path.join(projectRoot, ".rebase", "state.json");
|
|
1507
|
+
if (fs.existsSync(statePath)) try {
|
|
1508
|
+
const state = JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
1509
|
+
if (state && typeof state === "object") {
|
|
1510
|
+
if (typeof state.baseUrl === "string" && !baseUrl) baseUrl = state.baseUrl;
|
|
1511
|
+
if (typeof state.serviceKey === "string" && !serviceKey) serviceKey = state.serviceKey;
|
|
1512
|
+
}
|
|
1513
|
+
} catch {}
|
|
1514
|
+
const devUrlPath = path.join(projectRoot, ".rebase-dev-url");
|
|
1515
|
+
if (fs.existsSync(devUrlPath) && !baseUrl) try {
|
|
1516
|
+
baseUrl = fs.readFileSync(devUrlPath, "utf8").trim();
|
|
1517
|
+
} catch {}
|
|
1518
|
+
if (baseUrl && serviceKey) {
|
|
1519
|
+
console.log("Trying API-first reset via running backend...");
|
|
1520
|
+
try {
|
|
1521
|
+
const finalPass = newPassword || "NewPassword123!";
|
|
1522
|
+
const cleanBaseUrl = baseUrl.replace(/\/+$/, "");
|
|
1523
|
+
const searchUrl = `${cleanBaseUrl}/api/admin/users?search=${encodeURIComponent(email)}&limit=1`;
|
|
1524
|
+
const searchRes = await fetch(searchUrl, { headers: {
|
|
1525
|
+
"Authorization": `Bearer ${serviceKey}`,
|
|
1526
|
+
"Accept": "application/json"
|
|
1527
|
+
} });
|
|
1528
|
+
if (!searchRes.ok) throw new Error(`Failed to list users: ${searchRes.statusText}`);
|
|
1529
|
+
const searchData = await searchRes.json();
|
|
1530
|
+
if (!searchData || typeof searchData !== "object") throw new Error("Invalid response format from user search API.");
|
|
1531
|
+
let userId;
|
|
1532
|
+
if (Array.isArray(searchData)) {
|
|
1533
|
+
const firstUser = searchData[0];
|
|
1534
|
+
if (firstUser && typeof firstUser === "object" && "id" in firstUser && typeof firstUser.id === "string") userId = firstUser.id;
|
|
1535
|
+
else if (firstUser && typeof firstUser === "object" && "uid" in firstUser && typeof firstUser.uid === "string") userId = firstUser.uid;
|
|
1536
|
+
} else if ("users" in searchData && Array.isArray(searchData.users)) {
|
|
1537
|
+
const firstUser = searchData.users[0];
|
|
1538
|
+
if (firstUser && typeof firstUser === "object" && "id" in firstUser && typeof firstUser.id === "string") userId = firstUser.id;
|
|
1539
|
+
else if (firstUser && typeof firstUser === "object" && "uid" in firstUser && typeof firstUser.uid === "string") userId = firstUser.uid;
|
|
1540
|
+
}
|
|
1541
|
+
if (!userId) throw new Error(`User not found with email: ${email}`);
|
|
1542
|
+
const resetUrl = `${cleanBaseUrl}/api/admin/users/${userId}/reset-password`;
|
|
1543
|
+
const resetRes = await fetch(resetUrl, {
|
|
1544
|
+
method: "POST",
|
|
1545
|
+
headers: {
|
|
1546
|
+
"Authorization": `Bearer ${serviceKey}`,
|
|
1547
|
+
"Content-Type": "application/json",
|
|
1548
|
+
"Accept": "application/json"
|
|
1549
|
+
},
|
|
1550
|
+
body: JSON.stringify({ password: finalPass })
|
|
1551
|
+
});
|
|
1552
|
+
if (!resetRes.ok) {
|
|
1553
|
+
const errText = await resetRes.text();
|
|
1554
|
+
throw new Error(`Password reset endpoint failed: ${errText || resetRes.statusText}`);
|
|
1555
|
+
}
|
|
1556
|
+
console.log("API reset successful.");
|
|
1557
|
+
console.log(chalk.bold(" 🔑 Rebase Auth — Reset Password (via API)"));
|
|
1558
|
+
console.log("");
|
|
1559
|
+
console.log(` ${chalk.gray("Email:")} ${email}`);
|
|
1560
|
+
console.log(` ${chalk.gray("Password:")} ${finalPass}`);
|
|
1561
|
+
console.log("");
|
|
1562
|
+
return;
|
|
1563
|
+
} catch (err) {
|
|
1564
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1565
|
+
console.warn(chalk.yellow("API reset failed, falling back to direct database update..."));
|
|
1566
|
+
console.warn(chalk.gray(` Details: ${errMsg}`));
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1434
1569
|
const backendDir = requireBackendDir(projectRoot);
|
|
1435
1570
|
const tsxBin = resolveTsx(projectRoot);
|
|
1436
1571
|
if (!tsxBin) {
|
|
1437
1572
|
console.error(chalk.red("✗ Could not find tsx binary."));
|
|
1438
1573
|
process.exit(1);
|
|
1439
1574
|
}
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1575
|
+
try {
|
|
1576
|
+
const env = { ...process.env };
|
|
1577
|
+
if (envFile) env.DOTENV_CONFIG_PATH = envFile;
|
|
1578
|
+
env.REBASE_RESET_EMAIL = email;
|
|
1579
|
+
env.REBASE_RESET_PASSWORD = newPassword || "NewPassword123!";
|
|
1580
|
+
env.REBASE_ENV_FILE_PATH = envFile || path.join(projectRoot, ".env");
|
|
1581
|
+
const scriptContent = `
|
|
1447
1582
|
import { createPostgresDatabaseConnection } from "@rebasepro/server-postgresql";
|
|
1448
1583
|
import { hashPassword } from "@rebasepro/server-core";
|
|
1449
1584
|
import { eq } from "drizzle-orm";
|
|
@@ -1497,28 +1632,33 @@ async function resetPassword() {
|
|
|
1497
1632
|
|
|
1498
1633
|
resetPassword().catch(console.error);
|
|
1499
1634
|
`;
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
});
|
|
1513
|
-
return new Promise((resolve) => {
|
|
1514
|
-
child.on("close", (code) => {
|
|
1515
|
-
try {
|
|
1516
|
-
fs.unlinkSync(tmpScriptPath);
|
|
1517
|
-
} catch {}
|
|
1518
|
-
if (code !== 0) process.exit(code ?? 1);
|
|
1519
|
-
resolve();
|
|
1635
|
+
const tmpScriptPath = path.join(backendDir, ".tmp-reset-password.ts");
|
|
1636
|
+
fs.writeFileSync(tmpScriptPath, scriptContent, "utf-8");
|
|
1637
|
+
console.log("");
|
|
1638
|
+
console.log(chalk.bold(" 🔑 Rebase Auth — Reset Password (Direct DB Fallback)"));
|
|
1639
|
+
console.log("");
|
|
1640
|
+
console.log(` ${chalk.gray("Email:")} ${email}`);
|
|
1641
|
+
if (newPassword) console.log(` ${chalk.gray("Password:")} ${"*".repeat(newPassword.length)}`);
|
|
1642
|
+
console.log("");
|
|
1643
|
+
const child = spawn(tsxBin, [tmpScriptPath], {
|
|
1644
|
+
cwd: backendDir,
|
|
1645
|
+
stdio: "inherit",
|
|
1646
|
+
env
|
|
1520
1647
|
});
|
|
1521
|
-
|
|
1648
|
+
return new Promise((resolve) => {
|
|
1649
|
+
child.on("close", (code) => {
|
|
1650
|
+
try {
|
|
1651
|
+
fs.unlinkSync(tmpScriptPath);
|
|
1652
|
+
} catch {}
|
|
1653
|
+
if (code !== 0) process.exit(code ?? 1);
|
|
1654
|
+
resolve();
|
|
1655
|
+
});
|
|
1656
|
+
});
|
|
1657
|
+
} catch (err) {
|
|
1658
|
+
console.error(chalk.red("✗ Direct database update failed."));
|
|
1659
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
1660
|
+
process.exit(1);
|
|
1661
|
+
}
|
|
1522
1662
|
}
|
|
1523
1663
|
function printAuthHelp() {
|
|
1524
1664
|
console.log(`
|
|
@@ -1587,8 +1727,7 @@ async function doctorCommand(rawArgs) {
|
|
|
1587
1727
|
}
|
|
1588
1728
|
//#endregion
|
|
1589
1729
|
//#region src/commands/skills.ts
|
|
1590
|
-
var
|
|
1591
|
-
var __dirname$1 = path.dirname(__filename$1);
|
|
1730
|
+
var require = createRequire(import.meta.url);
|
|
1592
1731
|
/** Supported agent environments and their target directories. */
|
|
1593
1732
|
var AGENTS = {
|
|
1594
1733
|
cursor: {
|
|
@@ -1632,24 +1771,16 @@ var AGENTS = {
|
|
|
1632
1771
|
})
|
|
1633
1772
|
}
|
|
1634
1773
|
};
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
currentDir = path.dirname(currentDir);
|
|
1640
|
-
}
|
|
1641
|
-
return null;
|
|
1642
|
-
}
|
|
1643
|
-
/** Resolve the path to the bundled skills directory. */
|
|
1774
|
+
/**
|
|
1775
|
+
* Resolve the path to the skills directory from @rebasepro/agent-skills.
|
|
1776
|
+
* Works in both workspace (symlink) and published (real files) layouts.
|
|
1777
|
+
*/
|
|
1644
1778
|
function getSkillsSourceDir() {
|
|
1645
|
-
const
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
const distSkills = path.resolve(__dirname$1, "../../skills");
|
|
1651
|
-
if (fs.existsSync(distSkills)) return distSkills;
|
|
1652
|
-
throw new Error("Could not find bundled skills directory. Make sure the CLI was built with `pnpm build`.");
|
|
1779
|
+
const pkgJsonPath = require.resolve("@rebasepro/agent-skills/package.json");
|
|
1780
|
+
const pkgRoot = path.dirname(pkgJsonPath);
|
|
1781
|
+
const skillsDir = path.join(pkgRoot, "skills");
|
|
1782
|
+
if (!fs.existsSync(skillsDir)) throw new Error(`Skills directory not found at ${skillsDir}. Make sure @rebasepro/agent-skills is installed.`);
|
|
1783
|
+
return skillsDir;
|
|
1653
1784
|
}
|
|
1654
1785
|
/** Read all skill directories and return their names + content. */
|
|
1655
1786
|
function loadSkills(skillsDir) {
|
|
@@ -1764,6 +1895,272 @@ ${chalk.green.bold("Examples")}
|
|
|
1764
1895
|
`);
|
|
1765
1896
|
}
|
|
1766
1897
|
//#endregion
|
|
1898
|
+
//#region src/commands/api-keys.ts
|
|
1899
|
+
/**
|
|
1900
|
+
* CLI command: rebase api-keys <action>
|
|
1901
|
+
*
|
|
1902
|
+
* Subcommands:
|
|
1903
|
+
* list — List all API keys (masked)
|
|
1904
|
+
* create — Create a new API key
|
|
1905
|
+
* revoke — Revoke an existing API key
|
|
1906
|
+
*/
|
|
1907
|
+
function loadEnv(projectRoot) {
|
|
1908
|
+
const envFile = findEnvFile(projectRoot);
|
|
1909
|
+
const env = {};
|
|
1910
|
+
if (envFile && fs.existsSync(envFile)) {
|
|
1911
|
+
const content = fs.readFileSync(envFile, "utf-8");
|
|
1912
|
+
for (const line of content.split("\n")) {
|
|
1913
|
+
const trimmed = line.trim();
|
|
1914
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1915
|
+
const idx = trimmed.indexOf("=");
|
|
1916
|
+
if (idx > 0) {
|
|
1917
|
+
const key = trimmed.slice(0, idx).trim();
|
|
1918
|
+
let value = trimmed.slice(idx + 1).trim();
|
|
1919
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
1920
|
+
env[key] = value;
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
return env;
|
|
1925
|
+
}
|
|
1926
|
+
function resolveBaseUrl(env) {
|
|
1927
|
+
const port = env.PORT || env.REBASE_PORT || "3001";
|
|
1928
|
+
return env.REBASE_BASE_URL || `http://localhost:${port}`;
|
|
1929
|
+
}
|
|
1930
|
+
async function apiKeysCommand(subcommand, rawArgs) {
|
|
1931
|
+
if (!subcommand || subcommand === "--help") {
|
|
1932
|
+
printApiKeysHelp();
|
|
1933
|
+
return;
|
|
1934
|
+
}
|
|
1935
|
+
switch (subcommand) {
|
|
1936
|
+
case "list":
|
|
1937
|
+
await listKeys(rawArgs);
|
|
1938
|
+
break;
|
|
1939
|
+
case "create":
|
|
1940
|
+
await createKey(rawArgs);
|
|
1941
|
+
break;
|
|
1942
|
+
case "revoke":
|
|
1943
|
+
await revokeKey(rawArgs);
|
|
1944
|
+
break;
|
|
1945
|
+
default:
|
|
1946
|
+
console.error(chalk.red(`Unknown api-keys command: ${subcommand}`));
|
|
1947
|
+
console.log("");
|
|
1948
|
+
printApiKeysHelp();
|
|
1949
|
+
process.exit(1);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
async function listKeys(_rawArgs) {
|
|
1953
|
+
const env = loadEnv(requireProjectRoot());
|
|
1954
|
+
const baseUrl = resolveBaseUrl(env);
|
|
1955
|
+
const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;
|
|
1956
|
+
if (!serviceKey) {
|
|
1957
|
+
console.error(chalk.red("✗ SERVICE_KEY not found in .env — required for admin operations."));
|
|
1958
|
+
process.exit(1);
|
|
1959
|
+
}
|
|
1960
|
+
try {
|
|
1961
|
+
const res = await fetch(`${baseUrl}/api/admin/api-keys`, { headers: { Authorization: `Bearer ${serviceKey}` } });
|
|
1962
|
+
if (!res.ok) {
|
|
1963
|
+
const body = await res.text();
|
|
1964
|
+
console.error(chalk.red(`✗ Failed to list API keys: ${res.status} ${body}`));
|
|
1965
|
+
process.exit(1);
|
|
1966
|
+
}
|
|
1967
|
+
const { keys } = await res.json();
|
|
1968
|
+
console.log("");
|
|
1969
|
+
console.log(chalk.bold(" 🔑 API Keys"));
|
|
1970
|
+
console.log("");
|
|
1971
|
+
if (keys.length === 0) {
|
|
1972
|
+
console.log(chalk.gray(" No API keys found."));
|
|
1973
|
+
console.log("");
|
|
1974
|
+
return;
|
|
1975
|
+
}
|
|
1976
|
+
for (const key of keys) {
|
|
1977
|
+
const status = key.revoked_at ? chalk.red("revoked") : key.expires_at && new Date(key.expires_at) < /* @__PURE__ */ new Date() ? chalk.yellow("expired") : chalk.green("active");
|
|
1978
|
+
const perms = key.permissions.map((p) => `${p.collection}(${p.operations.join(",")})`).join(", ");
|
|
1979
|
+
console.log(` ${chalk.bold(key.name)} ${chalk.gray(`[${key.key_prefix}•••]`)} ${status}`);
|
|
1980
|
+
console.log(` ${chalk.gray("ID:")} ${key.id}`);
|
|
1981
|
+
console.log(` ${chalk.gray("Permissions:")} ${perms || "none"}`);
|
|
1982
|
+
console.log(` ${chalk.gray("Created:")} ${new Date(key.created_at).toLocaleDateString()}`);
|
|
1983
|
+
if (key.last_used_at) console.log(` ${chalk.gray("Last used:")} ${new Date(key.last_used_at).toLocaleDateString()}`);
|
|
1984
|
+
console.log("");
|
|
1985
|
+
}
|
|
1986
|
+
} catch (e) {
|
|
1987
|
+
console.error(chalk.red(`✗ ${e instanceof Error ? e.message : String(e)}`));
|
|
1988
|
+
console.error(chalk.gray(" Is the Rebase server running?"));
|
|
1989
|
+
process.exit(1);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
async function createKey(rawArgs) {
|
|
1993
|
+
const args = arg({
|
|
1994
|
+
"--name": String,
|
|
1995
|
+
"--permissions": String,
|
|
1996
|
+
"--admin": Boolean,
|
|
1997
|
+
"--rate-limit": Number,
|
|
1998
|
+
"--expires": String,
|
|
1999
|
+
"-n": "--name"
|
|
2000
|
+
}, {
|
|
2001
|
+
argv: rawArgs.slice(4),
|
|
2002
|
+
permissive: true
|
|
2003
|
+
});
|
|
2004
|
+
const name = args["--name"] || args._[0];
|
|
2005
|
+
const permissionsRaw = args["--permissions"];
|
|
2006
|
+
if (!name) {
|
|
2007
|
+
console.error(chalk.red("✗ Name is required."));
|
|
2008
|
+
console.log("");
|
|
2009
|
+
console.log(chalk.gray(" Usage: rebase api-keys create --name \"My Key\" --permissions '[{\"collection\":\"*\",\"operations\":[\"read\"]}]'"));
|
|
2010
|
+
process.exit(1);
|
|
2011
|
+
}
|
|
2012
|
+
let permissions;
|
|
2013
|
+
if (permissionsRaw) try {
|
|
2014
|
+
permissions = JSON.parse(permissionsRaw);
|
|
2015
|
+
} catch {
|
|
2016
|
+
console.error(chalk.red("✗ Invalid --permissions JSON."));
|
|
2017
|
+
console.log(chalk.gray(" Example: '[{\"collection\":\"*\",\"operations\":[\"read\",\"write\"]}]'"));
|
|
2018
|
+
process.exit(1);
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
2021
|
+
else permissions = [{
|
|
2022
|
+
collection: "*",
|
|
2023
|
+
operations: [
|
|
2024
|
+
"read",
|
|
2025
|
+
"write",
|
|
2026
|
+
"delete"
|
|
2027
|
+
]
|
|
2028
|
+
}];
|
|
2029
|
+
let expires_at = null;
|
|
2030
|
+
const expiresFlag = args["--expires"];
|
|
2031
|
+
if (expiresFlag) {
|
|
2032
|
+
const days = {
|
|
2033
|
+
"7d": 7,
|
|
2034
|
+
"30d": 30,
|
|
2035
|
+
"90d": 90,
|
|
2036
|
+
"1y": 365
|
|
2037
|
+
};
|
|
2038
|
+
if (days[expiresFlag]) expires_at = new Date(Date.now() + days[expiresFlag] * 864e5).toISOString();
|
|
2039
|
+
else {
|
|
2040
|
+
const parsed = new Date(expiresFlag);
|
|
2041
|
+
if (isNaN(parsed.getTime())) {
|
|
2042
|
+
console.error(chalk.red("✗ Invalid --expires value. Use 7d, 30d, 90d, 1y, or an ISO date."));
|
|
2043
|
+
process.exit(1);
|
|
2044
|
+
}
|
|
2045
|
+
expires_at = parsed.toISOString();
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
const env = loadEnv(requireProjectRoot());
|
|
2049
|
+
const baseUrl = resolveBaseUrl(env);
|
|
2050
|
+
const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;
|
|
2051
|
+
if (!serviceKey) {
|
|
2052
|
+
console.error(chalk.red("✗ SERVICE_KEY not found in .env — required for admin operations."));
|
|
2053
|
+
process.exit(1);
|
|
2054
|
+
}
|
|
2055
|
+
try {
|
|
2056
|
+
const body = {
|
|
2057
|
+
name,
|
|
2058
|
+
permissions,
|
|
2059
|
+
admin: args["--admin"] ?? false,
|
|
2060
|
+
rate_limit: args["--rate-limit"] ?? null,
|
|
2061
|
+
expires_at
|
|
2062
|
+
};
|
|
2063
|
+
const res = await fetch(`${baseUrl}/api/admin/api-keys`, {
|
|
2064
|
+
method: "POST",
|
|
2065
|
+
headers: {
|
|
2066
|
+
Authorization: `Bearer ${serviceKey}`,
|
|
2067
|
+
"Content-Type": "application/json"
|
|
2068
|
+
},
|
|
2069
|
+
body: JSON.stringify(body)
|
|
2070
|
+
});
|
|
2071
|
+
if (!res.ok) {
|
|
2072
|
+
const errBody = await res.text();
|
|
2073
|
+
console.error(chalk.red(`✗ Failed to create API key: ${res.status} ${errBody}`));
|
|
2074
|
+
process.exit(1);
|
|
2075
|
+
}
|
|
2076
|
+
const { key } = await res.json();
|
|
2077
|
+
console.log("");
|
|
2078
|
+
console.log(chalk.bold.green(" ✓ API key created successfully"));
|
|
2079
|
+
console.log("");
|
|
2080
|
+
console.log(` ${chalk.gray("Name:")} ${key.name}`);
|
|
2081
|
+
console.log(` ${chalk.gray("ID:")} ${key.id}`);
|
|
2082
|
+
console.log(` ${chalk.gray("Prefix:")} ${key.key_prefix}`);
|
|
2083
|
+
console.log("");
|
|
2084
|
+
console.log(chalk.bold.yellow(" ⚠ Copy your key now — it won't be shown again:"));
|
|
2085
|
+
console.log("");
|
|
2086
|
+
console.log(` ${chalk.cyan(key.key)}`);
|
|
2087
|
+
console.log("");
|
|
2088
|
+
} catch (e) {
|
|
2089
|
+
console.error(chalk.red(`✗ ${e instanceof Error ? e.message : String(e)}`));
|
|
2090
|
+
console.error(chalk.gray(" Is the Rebase server running?"));
|
|
2091
|
+
process.exit(1);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
async function revokeKey(rawArgs) {
|
|
2095
|
+
const args = arg({ "--id": String }, {
|
|
2096
|
+
argv: rawArgs.slice(4),
|
|
2097
|
+
permissive: true
|
|
2098
|
+
});
|
|
2099
|
+
const id = args["--id"] || args._[0];
|
|
2100
|
+
if (!id) {
|
|
2101
|
+
console.error(chalk.red("✗ Key ID is required."));
|
|
2102
|
+
console.log("");
|
|
2103
|
+
console.log(chalk.gray(" Usage: rebase api-keys revoke <key-id>"));
|
|
2104
|
+
process.exit(1);
|
|
2105
|
+
}
|
|
2106
|
+
const env = loadEnv(requireProjectRoot());
|
|
2107
|
+
const baseUrl = resolveBaseUrl(env);
|
|
2108
|
+
const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;
|
|
2109
|
+
if (!serviceKey) {
|
|
2110
|
+
console.error(chalk.red("✗ SERVICE_KEY not found in .env — required for admin operations."));
|
|
2111
|
+
process.exit(1);
|
|
2112
|
+
}
|
|
2113
|
+
try {
|
|
2114
|
+
const res = await fetch(`${baseUrl}/api/admin/api-keys/${encodeURIComponent(id)}`, {
|
|
2115
|
+
method: "DELETE",
|
|
2116
|
+
headers: { Authorization: `Bearer ${serviceKey}` }
|
|
2117
|
+
});
|
|
2118
|
+
if (!res.ok) {
|
|
2119
|
+
const errBody = await res.text();
|
|
2120
|
+
console.error(chalk.red(`✗ Failed to revoke API key: ${res.status} ${errBody}`));
|
|
2121
|
+
process.exit(1);
|
|
2122
|
+
}
|
|
2123
|
+
console.log("");
|
|
2124
|
+
console.log(chalk.bold.green(" ✓ API key revoked successfully"));
|
|
2125
|
+
console.log(` ${chalk.gray("ID:")} ${id}`);
|
|
2126
|
+
console.log("");
|
|
2127
|
+
} catch (e) {
|
|
2128
|
+
console.error(chalk.red(`✗ ${e instanceof Error ? e.message : String(e)}`));
|
|
2129
|
+
console.error(chalk.gray(" Is the Rebase server running?"));
|
|
2130
|
+
process.exit(1);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
function printApiKeysHelp() {
|
|
2134
|
+
console.log(`
|
|
2135
|
+
${chalk.bold("rebase api-keys")} — Manage Service API Keys
|
|
2136
|
+
|
|
2137
|
+
${chalk.green.bold("Usage")}
|
|
2138
|
+
rebase api-keys ${chalk.blue("<command>")} [options]
|
|
2139
|
+
|
|
2140
|
+
${chalk.green.bold("Commands")}
|
|
2141
|
+
${chalk.blue.bold("list")} List all API keys
|
|
2142
|
+
${chalk.blue.bold("create")} Create a new API key
|
|
2143
|
+
${chalk.blue.bold("revoke")} Revoke an API key
|
|
2144
|
+
|
|
2145
|
+
${chalk.green.bold("create Options")}
|
|
2146
|
+
${chalk.blue("--name, -n")} Key name ${chalk.gray("(required)")}
|
|
2147
|
+
${chalk.blue("--permissions")} JSON array of permissions
|
|
2148
|
+
${chalk.gray("Default: [{\"collection\":\"*\",\"operations\":[\"read\",\"write\",\"delete\"]}]")}
|
|
2149
|
+
${chalk.blue("--admin")} Grant admin role (access to admin routes)
|
|
2150
|
+
${chalk.blue("--rate-limit")} Requests per 15-min window ${chalk.gray("(default: 1000)")}
|
|
2151
|
+
${chalk.blue("--expires")} Expiration: 7d, 30d, 90d, 1y, or ISO date
|
|
2152
|
+
|
|
2153
|
+
${chalk.green.bold("revoke Options")}
|
|
2154
|
+
${chalk.blue("--id")} API key ID to revoke ${chalk.gray("(or positional arg)")}
|
|
2155
|
+
|
|
2156
|
+
${chalk.green.bold("Examples")}
|
|
2157
|
+
rebase api-keys list
|
|
2158
|
+
rebase api-keys create --name "Analytics" --permissions '[{"collection":"events","operations":["read"]}]'
|
|
2159
|
+
rebase api-keys create -n "Full Access" --expires 90d
|
|
2160
|
+
rebase api-keys revoke abc123-def456
|
|
2161
|
+
`);
|
|
2162
|
+
}
|
|
2163
|
+
//#endregion
|
|
1767
2164
|
//#region src/cli.ts
|
|
1768
2165
|
var __filename = fileURLToPath(import.meta.url);
|
|
1769
2166
|
var __dirname = path.dirname(__filename);
|
|
@@ -1798,7 +2195,8 @@ async function entry(args) {
|
|
|
1798
2195
|
"start",
|
|
1799
2196
|
"auth",
|
|
1800
2197
|
"doctor",
|
|
1801
|
-
"skills"
|
|
2198
|
+
"skills",
|
|
2199
|
+
"api-keys"
|
|
1802
2200
|
].includes(command)) {
|
|
1803
2201
|
printHelp();
|
|
1804
2202
|
return;
|
|
@@ -1849,6 +2247,9 @@ async function entry(args) {
|
|
|
1849
2247
|
case "skills":
|
|
1850
2248
|
await skillsCommand(effectiveSubcommand, args);
|
|
1851
2249
|
break;
|
|
2250
|
+
case "api-keys":
|
|
2251
|
+
await apiKeysCommand(effectiveSubcommand, args);
|
|
2252
|
+
break;
|
|
1852
2253
|
default:
|
|
1853
2254
|
console.log(chalk.red(`Unknown command: ${command}`));
|
|
1854
2255
|
console.log("");
|
|
@@ -1877,11 +2278,10 @@ ${chalk.green.bold("Database")}
|
|
|
1877
2278
|
${chalk.blue.bold("db push")} Apply schema directly to database ${chalk.gray("(dev)")}
|
|
1878
2279
|
${chalk.blue.bold("db generate")} Generate SQL migration files
|
|
1879
2280
|
${chalk.blue.bold("db migrate")} Run pending migrations
|
|
1880
|
-
${chalk.blue.bold("db studio")} Open Drizzle Studio
|
|
1881
2281
|
${chalk.blue.bold("db")} ${chalk.gray("--help")} Show database command help
|
|
1882
2282
|
|
|
1883
2283
|
${chalk.green.bold("SDK")}
|
|
1884
|
-
${chalk.blue.bold("generate-sdk")} Generate a typed
|
|
2284
|
+
${chalk.blue.bold("generate-sdk")} Generate a typed TypeScript SDK from collections
|
|
1885
2285
|
|
|
1886
2286
|
${chalk.green.bold("Auth")}
|
|
1887
2287
|
${chalk.blue.bold("auth reset-password")} Reset a user's password
|
|
@@ -1893,6 +2293,12 @@ ${chalk.green.bold("Diagnostics")}
|
|
|
1893
2293
|
${chalk.green.bold("AI Agent Skills")}
|
|
1894
2294
|
${chalk.blue.bold("skills install")} Install Rebase agent skills for your AI coding assistant
|
|
1895
2295
|
|
|
2296
|
+
${chalk.green.bold("API Keys")}
|
|
2297
|
+
${chalk.blue.bold("api-keys list")} List all service API keys
|
|
2298
|
+
${chalk.blue.bold("api-keys create")} Create a new scoped API key
|
|
2299
|
+
${chalk.blue.bold("api-keys revoke")} Revoke an existing API key
|
|
2300
|
+
${chalk.blue.bold("api-keys")} ${chalk.gray("--help")} Show API key command help
|
|
2301
|
+
|
|
1896
2302
|
${chalk.green.bold("Options")}
|
|
1897
2303
|
${chalk.blue("--version, -v")} Show version number
|
|
1898
2304
|
${chalk.blue("--help, -h")} Show this help message
|
|
@@ -1901,6 +2307,6 @@ ${chalk.gray("Documentation: https://rebase.pro/docs")}
|
|
|
1901
2307
|
`);
|
|
1902
2308
|
}
|
|
1903
2309
|
//#endregion
|
|
1904
|
-
export { authCommand, buildCommand, buildInitQuestions, configureEnvFile, createRebaseApp, dbCommand, detectPackageManager, devCommand, doctorCommand, entry, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, generateSdkCommand, getActiveBackendPlugin, getPMCommands, requireBackendDir, requireProjectRoot, resolveLocalBin, resolvePluginCliScript, resolveTsx, schemaCommand, startCommand };
|
|
2310
|
+
export { authCommand, buildCommand, buildInitQuestions, configureEnvFile, createRebaseApp, dbCommand, detectPackageManager, devCommand, doctorCommand, entry, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, generateSdkCommand, getActiveBackendPlugin, getPMCommands, requireBackendDir, requireProjectRoot, resolveLocalBin, resolvePluginCliScript, resolveTsx, schemaCommand, startCommand, validateTsxInstallation };
|
|
1905
2311
|
|
|
1906
2312
|
//# sourceMappingURL=index.es.js.map
|