@rebasepro/cli 0.9.1-canary.b10dcdf → 0.9.1-canary.c0d4c07
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/bin/rebase.js +67 -1
- package/dist/commands/cloud/context.d.ts +0 -6
- package/dist/commands/cloud/deployments.d.ts +0 -1
- package/dist/commands/cloud/power.d.ts +0 -1
- package/dist/commands/cloud/resources.d.ts +1 -1
- package/dist/commands/skills.d.ts +1 -1
- package/dist/index.es.js +178 -19
- package/dist/index.es.js.map +1 -1
- package/package.json +7 -7
- package/templates/template/config/collections/users.ts +2 -0
package/bin/rebase.js
CHANGED
|
@@ -1,4 +1,70 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const distEntry = join(here, "..", "dist", "index.es.js");
|
|
8
|
+
const srcDir = join(here, "..", "src");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Warn when the built CLI is older than the source it was built from.
|
|
12
|
+
*
|
|
13
|
+
* `rebase` runs `dist/`, and a global install of this package is usually a
|
|
14
|
+
* symlink to a working checkout — so every agent and shell on the machine runs
|
|
15
|
+
* whatever was last built, not what is in the code. A stale build is invisible:
|
|
16
|
+
* the command works, it just silently lacks the subcommand you added, which
|
|
17
|
+
* reads as "my change did nothing" rather than "you forgot to build".
|
|
18
|
+
*
|
|
19
|
+
* Development-only by construction: `src/` is not in the published `files`
|
|
20
|
+
* list, so this is skipped entirely for installed copies.
|
|
21
|
+
*
|
|
22
|
+
* Writes to **stderr**. Agents parse stdout as JSON under `--json`, and a
|
|
23
|
+
* warning there would corrupt the one guarantee those commands make.
|
|
24
|
+
*/
|
|
25
|
+
function newestMtimeMs(dir, deadline) {
|
|
26
|
+
let newest = 0;
|
|
27
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
28
|
+
if (Date.now() > deadline) break; // never let a warning cost real time
|
|
29
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
30
|
+
const full = join(dir, entry.name);
|
|
31
|
+
if (entry.isDirectory()) {
|
|
32
|
+
newest = Math.max(newest, newestMtimeMs(full, deadline));
|
|
33
|
+
} else if (/\.tsx?$/.test(entry.name) && !/\.(test|spec)\.tsx?$/.test(entry.name)) {
|
|
34
|
+
// Tests are not bundled, so editing one does not make dist stale.
|
|
35
|
+
// Counting them cried wolf on the ordinary edit-test-run loop.
|
|
36
|
+
newest = Math.max(newest, statSync(full).mtimeMs);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return newest;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function warnIfStale() {
|
|
43
|
+
if (!existsSync(srcDir) || !existsSync(distEntry)) return;
|
|
44
|
+
const builtAt = statSync(distEntry).mtimeMs;
|
|
45
|
+
const editedAt = newestMtimeMs(srcDir, Date.now() + 150);
|
|
46
|
+
// A couple of seconds of slack: a build writes dist while src is being
|
|
47
|
+
// stat'd, and a sub-second delta is that race, not a stale build.
|
|
48
|
+
const staleByMs = editedAt - builtAt;
|
|
49
|
+
if (staleByMs <= 2000) return;
|
|
50
|
+
|
|
51
|
+
const seconds = Math.round(staleByMs / 1000);
|
|
52
|
+
const ago = seconds >= 3600
|
|
53
|
+
? `${Math.round(seconds / 3600)}h`
|
|
54
|
+
: seconds >= 60 ? `${Math.round(seconds / 60)}m` : `${seconds}s`;
|
|
55
|
+
process.stderr.write(
|
|
56
|
+
`[33m⚠ rebase CLI: dist/ is ${ago} older than src/ — you are running a stale build.[0m\n` +
|
|
57
|
+
` Rebuild with: (cd ${join(here, "..")} && npm run build)\n`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// A broken staleness check must never stop the CLI from running.
|
|
62
|
+
try {
|
|
63
|
+
warnIfStale();
|
|
64
|
+
} catch {
|
|
65
|
+
/* ignore */
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { entry } = await import("../dist/index.es.js");
|
|
3
69
|
|
|
4
70
|
entry(process.argv);
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
import { createRebaseClient } from "@rebasepro/client";
|
|
2
|
-
/** Default hosted control plane (the Rebase Cloud console origin). */
|
|
3
|
-
export declare const DEFAULT_CLOUD_URL = "https://app.rebase.pro";
|
|
4
2
|
/** Project-local link file: <project>/.rebase/cloud.json */
|
|
5
3
|
export declare function projectLinkPath(cwd?: string): string;
|
|
6
|
-
/** Host that a bare `rebase cloud` command should target, if any. */
|
|
7
|
-
export declare function currentContextUrl(): string | undefined;
|
|
8
4
|
/** Persist the active organization id for a host. */
|
|
9
5
|
export declare function setContextOrg(url: string, org: string | undefined): void;
|
|
10
6
|
export declare function getContextOrg(url: string): string | undefined;
|
|
@@ -104,8 +100,6 @@ export declare function initOutputMode(rawArgs: string[]): boolean;
|
|
|
104
100
|
export declare function isJsonMode(): boolean;
|
|
105
101
|
/** Force the mode (tests only — production latches it via `initOutputMode`). */
|
|
106
102
|
export declare function setJsonModeForTest(value: boolean): void;
|
|
107
|
-
/** Write one JSON value to stdout, followed by a newline. */
|
|
108
|
-
export declare function printJson(value: unknown): void;
|
|
109
103
|
/**
|
|
110
104
|
* The one output primitive every new command uses: in JSON mode emit `json`
|
|
111
105
|
* (and nothing else); otherwise run `human`. Keeping the two behind a single
|
|
@@ -19,7 +19,6 @@ export interface DeploymentRow {
|
|
|
19
19
|
gitCommitHash?: string;
|
|
20
20
|
gitCommitMessage?: string;
|
|
21
21
|
}
|
|
22
|
-
export declare function deploymentImage(dep: DeploymentRow): string | null;
|
|
23
22
|
/**
|
|
24
23
|
* The backend's rule EXACTLY: a rollback is honoured only for a successful
|
|
25
24
|
* deploy that recorded an image. Any other row 409s `deploy_not_rollbackable`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare function statusCommand(rawArgs: string[]): Promise<void>;
|
|
2
2
|
export declare function metricsCommand(rawArgs: string[]): Promise<void>;
|
|
3
3
|
export declare function webhooksCommand(subcommand: string | undefined, rawArgs: string[]): Promise<void>;
|
|
4
|
-
export declare function storageCommand(rawArgs: string[]): Promise<void>;
|
|
4
|
+
export declare function storageCommand(action: string | undefined, rawArgs: string[]): Promise<void>;
|
|
5
5
|
export declare function clustersCommand(rawArgs: string[]): Promise<void>;
|
|
6
6
|
export declare function billingCommand(rawArgs: string[]): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function skillsCommand(subcommand: string | undefined,
|
|
1
|
+
export declare function skillsCommand(subcommand: string | undefined, rawArgs: string[]): Promise<void>;
|
package/dist/index.es.js
CHANGED
|
@@ -2477,22 +2477,43 @@ function installForAgent(agentKey, skills, projectDir) {
|
|
|
2477
2477
|
}
|
|
2478
2478
|
return count;
|
|
2479
2479
|
}
|
|
2480
|
-
async function skillsCommand(subcommand,
|
|
2480
|
+
async function skillsCommand(subcommand, rawArgs) {
|
|
2481
2481
|
switch (subcommand) {
|
|
2482
2482
|
case "install":
|
|
2483
|
-
await skillsInstall();
|
|
2483
|
+
await skillsInstall(rawArgs);
|
|
2484
2484
|
break;
|
|
2485
2485
|
case "--help":
|
|
2486
2486
|
case void 0:
|
|
2487
2487
|
printSkillsHelp();
|
|
2488
2488
|
break;
|
|
2489
2489
|
default:
|
|
2490
|
-
console.
|
|
2490
|
+
console.error(chalk.red(`Unknown skills subcommand: ${subcommand}`));
|
|
2491
2491
|
console.log("");
|
|
2492
2492
|
printSkillsHelp();
|
|
2493
|
+
process.exit(1);
|
|
2493
2494
|
}
|
|
2494
2495
|
}
|
|
2495
|
-
|
|
2496
|
+
/**
|
|
2497
|
+
* Agents named explicitly on the command line, e.g. `--agent claude --agent cursor`
|
|
2498
|
+
* (also accepts a comma-separated list). Returns null when none were given.
|
|
2499
|
+
*/
|
|
2500
|
+
function parseAgentFlags(rawArgs) {
|
|
2501
|
+
const requested = [];
|
|
2502
|
+
for (let i = 0; i < rawArgs.length; i++) {
|
|
2503
|
+
if (rawArgs[i] !== "--agent" && rawArgs[i] !== "-a") continue;
|
|
2504
|
+
const value = rawArgs[i + 1];
|
|
2505
|
+
if (value && !value.startsWith("-")) requested.push(...value.split(",").map((v) => v.trim()).filter(Boolean));
|
|
2506
|
+
}
|
|
2507
|
+
if (requested.length === 0) return null;
|
|
2508
|
+
const valid = Object.keys(AGENTS);
|
|
2509
|
+
const unknown = requested.filter((a) => !valid.includes(a));
|
|
2510
|
+
if (unknown.length > 0) {
|
|
2511
|
+
console.error(chalk.red(`Unknown agent(s): ${unknown.join(", ")}. Available: ${valid.join(", ")}`));
|
|
2512
|
+
process.exit(1);
|
|
2513
|
+
}
|
|
2514
|
+
return requested;
|
|
2515
|
+
}
|
|
2516
|
+
async function skillsInstall(rawArgs = []) {
|
|
2496
2517
|
const projectDir = process.cwd();
|
|
2497
2518
|
let skillsDir;
|
|
2498
2519
|
try {
|
|
@@ -2506,8 +2527,14 @@ async function skillsInstall() {
|
|
|
2506
2527
|
console.error(`${chalk.red.bold("ERROR")} No skills found in ${skillsDir}`);
|
|
2507
2528
|
process.exit(1);
|
|
2508
2529
|
}
|
|
2509
|
-
let agents = detectAgents(projectDir);
|
|
2530
|
+
let agents = parseAgentFlags(rawArgs) ?? detectAgents(projectDir);
|
|
2510
2531
|
if (agents.length === 0) {
|
|
2532
|
+
if (!process.stdin.isTTY) {
|
|
2533
|
+
console.error(chalk.red("Cannot prompt: this is a non-interactive terminal (no TTY)."));
|
|
2534
|
+
console.error(chalk.yellow(` Name the agents explicitly, e.g. rebase skills install --agent ${Object.keys(AGENTS)[0]}`));
|
|
2535
|
+
console.error(chalk.gray(` Available: ${Object.keys(AGENTS).join(", ")}`));
|
|
2536
|
+
process.exit(1);
|
|
2537
|
+
}
|
|
2511
2538
|
const choices = Object.entries(AGENTS).map(([key, agent]) => ({
|
|
2512
2539
|
name: agent.label,
|
|
2513
2540
|
value: key,
|
|
@@ -2549,8 +2576,15 @@ ${chalk.green.bold("Subcommands")}
|
|
|
2549
2576
|
${chalk.blue.bold("install")} Install Rebase agent skills for your AI coding assistant
|
|
2550
2577
|
Supports: Cursor, Claude Code, Windsurf, Gemini CLI, Antigravity
|
|
2551
2578
|
|
|
2579
|
+
${chalk.green.bold("Options")}
|
|
2580
|
+
${chalk.blue("--agent, -a")} Agent(s) to install for, skipping detection and the prompt.
|
|
2581
|
+
Repeat the flag or pass a comma-separated list.
|
|
2582
|
+
Available: ${Object.keys(AGENTS).join(", ")}
|
|
2583
|
+
|
|
2552
2584
|
${chalk.green.bold("Examples")}
|
|
2553
2585
|
${chalk.cyan("rebase skills install")}
|
|
2586
|
+
${chalk.cyan("rebase skills install --agent claude")}
|
|
2587
|
+
${chalk.cyan("rebase skills install --agent claude,cursor")}
|
|
2554
2588
|
`);
|
|
2555
2589
|
}
|
|
2556
2590
|
//#endregion
|
|
@@ -2582,9 +2616,16 @@ function loadEnv(projectRoot) {
|
|
|
2582
2616
|
}
|
|
2583
2617
|
return env;
|
|
2584
2618
|
}
|
|
2585
|
-
function resolveBaseUrl(env) {
|
|
2586
|
-
|
|
2587
|
-
|
|
2619
|
+
function resolveBaseUrl(env, projectRoot) {
|
|
2620
|
+
if (env.REBASE_BASE_URL) return env.REBASE_BASE_URL;
|
|
2621
|
+
if (projectRoot) try {
|
|
2622
|
+
const urlFile = path.join(projectRoot, ".rebase-dev-url");
|
|
2623
|
+
if (fs.existsSync(urlFile)) {
|
|
2624
|
+
const devUrl = fs.readFileSync(urlFile, "utf-8").trim();
|
|
2625
|
+
if (devUrl) return devUrl;
|
|
2626
|
+
}
|
|
2627
|
+
} catch {}
|
|
2628
|
+
return `http://localhost:${env.PORT || env.REBASE_PORT || "3001"}`;
|
|
2588
2629
|
}
|
|
2589
2630
|
async function apiKeysCommand(subcommand, rawArgs) {
|
|
2590
2631
|
if (!subcommand || subcommand === "--help") {
|
|
@@ -2609,8 +2650,9 @@ async function apiKeysCommand(subcommand, rawArgs) {
|
|
|
2609
2650
|
}
|
|
2610
2651
|
}
|
|
2611
2652
|
async function listKeys(_rawArgs) {
|
|
2612
|
-
const
|
|
2613
|
-
const
|
|
2653
|
+
const projectRoot = requireProjectRoot();
|
|
2654
|
+
const env = loadEnv(projectRoot);
|
|
2655
|
+
const baseUrl = resolveBaseUrl(env, projectRoot);
|
|
2614
2656
|
const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;
|
|
2615
2657
|
if (!serviceKey) {
|
|
2616
2658
|
console.error(chalk.red("✗ SERVICE_KEY not found in .env — required for admin operations."));
|
|
@@ -2715,8 +2757,9 @@ async function createKey(rawArgs) {
|
|
|
2715
2757
|
expires_at = parsed.toISOString();
|
|
2716
2758
|
}
|
|
2717
2759
|
}
|
|
2718
|
-
const
|
|
2719
|
-
const
|
|
2760
|
+
const projectRoot = requireProjectRoot();
|
|
2761
|
+
const env = loadEnv(projectRoot);
|
|
2762
|
+
const baseUrl = resolveBaseUrl(env, projectRoot);
|
|
2720
2763
|
const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;
|
|
2721
2764
|
if (!serviceKey) {
|
|
2722
2765
|
console.error(chalk.red("✗ SERVICE_KEY not found in .env — required for admin operations."));
|
|
@@ -2773,8 +2816,9 @@ async function revokeKey(rawArgs) {
|
|
|
2773
2816
|
console.log(chalk.gray(" Usage: rebase api-keys revoke <key-id>"));
|
|
2774
2817
|
process.exit(1);
|
|
2775
2818
|
}
|
|
2776
|
-
const
|
|
2777
|
-
const
|
|
2819
|
+
const projectRoot = requireProjectRoot();
|
|
2820
|
+
const env = loadEnv(projectRoot);
|
|
2821
|
+
const baseUrl = resolveBaseUrl(env, projectRoot);
|
|
2778
2822
|
const serviceKey = env.SERVICE_KEY || env.REBASE_SERVICE_KEY;
|
|
2779
2823
|
if (!serviceKey) {
|
|
2780
2824
|
console.error(chalk.red("✗ SERVICE_KEY not found in .env — required for admin operations."));
|
|
@@ -3243,13 +3287,18 @@ function fmtDate(value) {
|
|
|
3243
3287
|
*/
|
|
3244
3288
|
var POLL_INTERVAL_MS = 1500;
|
|
3245
3289
|
var POLL_TIMEOUT_MS = 900 * 1e3;
|
|
3290
|
+
var MAX_SOURCE_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
3246
3291
|
function sleep(ms) {
|
|
3247
3292
|
return new Promise((r) => setTimeout(r, ms));
|
|
3248
3293
|
}
|
|
3249
|
-
function run(cmd, cmdArgs, cwd) {
|
|
3294
|
+
function run(cmd, cmdArgs, cwd, env) {
|
|
3250
3295
|
return new Promise((resolve, reject) => {
|
|
3251
3296
|
const child = spawn(cmd, cmdArgs, {
|
|
3252
3297
|
cwd,
|
|
3298
|
+
env: env ? {
|
|
3299
|
+
...process.env,
|
|
3300
|
+
...env
|
|
3301
|
+
} : void 0,
|
|
3253
3302
|
stdio: [
|
|
3254
3303
|
"ignore",
|
|
3255
3304
|
"ignore",
|
|
@@ -3279,7 +3328,7 @@ async function createSourceTarball(sourceDir) {
|
|
|
3279
3328
|
for (const ignore of [".gitignore", ".rebaseignore"]) if (fs.existsSync(path.join(dir, ignore))) tarArgs.push(`--exclude-from=${ignore}`);
|
|
3280
3329
|
tarArgs.push(".");
|
|
3281
3330
|
try {
|
|
3282
|
-
await run("tar", tarArgs, dir);
|
|
3331
|
+
await run("tar", tarArgs, dir, { COPYFILE_DISABLE: "1" });
|
|
3283
3332
|
} catch (e) {
|
|
3284
3333
|
fail(`Failed to package source: ${e instanceof Error ? e.message : String(e)}`);
|
|
3285
3334
|
}
|
|
@@ -3289,6 +3338,7 @@ async function createSourceTarball(sourceDir) {
|
|
|
3289
3338
|
async function uploadSource(url, token, projectId, tarPath) {
|
|
3290
3339
|
const bytes = fs.readFileSync(tarPath);
|
|
3291
3340
|
const sizeMb = (bytes.length / 1024 / 1024).toFixed(1);
|
|
3341
|
+
if (bytes.length > MAX_SOURCE_UPLOAD_BYTES) fail(`Source context is ${sizeMb} MB — the upload cap is ${Math.round(MAX_SOURCE_UPLOAD_BYTES / 1024 / 1024)} MB.`, "Trim the build context: exclude sourcemaps (*.map), build output and large assets via .rebaseignore or .gitignore.");
|
|
3292
3342
|
console.log(chalk.gray(` Uploading source (${sizeMb} MB)...`));
|
|
3293
3343
|
const res = await fetch(`${url}/api/functions/deploy/upload?projectId=${encodeURIComponent(projectId)}`, {
|
|
3294
3344
|
method: "POST",
|
|
@@ -4268,6 +4318,8 @@ ${chalk.green.bold("Options")}
|
|
|
4268
4318
|
${chalk.blue("--secret")} Mark a variable write-only ${chalk.gray("(set)")}
|
|
4269
4319
|
${chalk.blue("--json")} Machine-readable output
|
|
4270
4320
|
${chalk.blue("--project, -p")} Project slug ${chalk.gray("(defaults to the linked project)")}
|
|
4321
|
+
|
|
4322
|
+
${chalk.gray("Values are encrypted at rest (AES-256-GCM) and only decrypted at deploy time.")}
|
|
4271
4323
|
`);
|
|
4272
4324
|
}
|
|
4273
4325
|
function printEnvHelpJson() {
|
|
@@ -5175,7 +5227,10 @@ async function webhooksCommand(subcommand, rawArgs) {
|
|
|
5175
5227
|
reportError(e, "Webhook operation failed");
|
|
5176
5228
|
}
|
|
5177
5229
|
}
|
|
5178
|
-
async function storageCommand(rawArgs) {
|
|
5230
|
+
async function storageCommand(action, rawArgs) {
|
|
5231
|
+
if (action === "create") return storageCreateCommand(rawArgs);
|
|
5232
|
+
if (action === "attach") return storageAttachCommand(rawArgs);
|
|
5233
|
+
if (action === "help") return printStorageHelp();
|
|
5179
5234
|
const { client } = await requireClient(rawArgs);
|
|
5180
5235
|
const projectId = await requireProject(rawArgs, client);
|
|
5181
5236
|
try {
|
|
@@ -5200,6 +5255,107 @@ async function storageCommand(rawArgs) {
|
|
|
5200
5255
|
reportError(e, "Failed to list storage");
|
|
5201
5256
|
}
|
|
5202
5257
|
}
|
|
5258
|
+
function printStorageHelp() {
|
|
5259
|
+
console.log("");
|
|
5260
|
+
console.log(chalk.bold(" rebase cloud storage"));
|
|
5261
|
+
console.log("");
|
|
5262
|
+
console.log(" " + chalk.blue.bold("storage") + " List this project's storage");
|
|
5263
|
+
console.log(" " + chalk.blue.bold("storage create") + " Provision platform-managed storage");
|
|
5264
|
+
console.log(" " + chalk.blue.bold("storage attach") + " Attach your own S3-compatible bucket");
|
|
5265
|
+
console.log("");
|
|
5266
|
+
console.log(chalk.gray(" attach options:"));
|
|
5267
|
+
console.log(chalk.gray(" --bucket <name> Bucket name (required)"));
|
|
5268
|
+
console.log(chalk.gray(" --access-key-id <id> Access key ID (required)"));
|
|
5269
|
+
console.log(chalk.gray(" --secret-access-key <s> Secret access key (required)"));
|
|
5270
|
+
console.log(chalk.gray(" --endpoint <url> S3 endpoint; omit for AWS"));
|
|
5271
|
+
console.log(chalk.gray(" --region <region> Region"));
|
|
5272
|
+
console.log(chalk.gray(" --force-path-style Required by MinIO and some gateways"));
|
|
5273
|
+
console.log("");
|
|
5274
|
+
console.log(chalk.gray(" Without either, a tenant falls back to the container filesystem and"));
|
|
5275
|
+
console.log(chalk.gray(" loses uploaded files on its next restart."));
|
|
5276
|
+
console.log("");
|
|
5277
|
+
}
|
|
5278
|
+
async function storageCreateCommand(rawArgs) {
|
|
5279
|
+
const { client } = await requireClient(rawArgs);
|
|
5280
|
+
const projectId = await requireProject(rawArgs, client);
|
|
5281
|
+
try {
|
|
5282
|
+
console.log("");
|
|
5283
|
+
console.log(chalk.gray(" Provisioning managed storage — this creates a bucket and its credentials..."));
|
|
5284
|
+
const res = await client.functions.invoke(`storage-provision/${encodeURIComponent(projectId)}`, void 0, { method: "POST" });
|
|
5285
|
+
const info = res.data ?? res.data;
|
|
5286
|
+
success(`Managed storage provisioned for ${displayProjectRef(rawArgs)}.`);
|
|
5287
|
+
keyValues([
|
|
5288
|
+
["Bucket", info.bucketName],
|
|
5289
|
+
["Region", info.region],
|
|
5290
|
+
["Endpoint", info.endpoint],
|
|
5291
|
+
["Access key", info.accessKeyId]
|
|
5292
|
+
]);
|
|
5293
|
+
console.log("");
|
|
5294
|
+
console.log(chalk.gray(" The secret key is stored encrypted and injected at deploy time; it is not displayed."));
|
|
5295
|
+
console.log(chalk.gray(" Redeploy for the tenant to pick it up: ") + chalk.bold("rebase cloud deploy"));
|
|
5296
|
+
console.log("");
|
|
5297
|
+
} catch (e) {
|
|
5298
|
+
reportError(e, "Failed to provision managed storage");
|
|
5299
|
+
}
|
|
5300
|
+
}
|
|
5301
|
+
async function storageAttachCommand(rawArgs) {
|
|
5302
|
+
const parsed = arg({
|
|
5303
|
+
"--bucket": String,
|
|
5304
|
+
"--access-key-id": String,
|
|
5305
|
+
"--secret-access-key": String,
|
|
5306
|
+
"--endpoint": String,
|
|
5307
|
+
"--region": String,
|
|
5308
|
+
"--force-path-style": Boolean
|
|
5309
|
+
}, {
|
|
5310
|
+
argv: rawArgs.slice(3),
|
|
5311
|
+
permissive: true
|
|
5312
|
+
});
|
|
5313
|
+
const bucket = parsed["--bucket"];
|
|
5314
|
+
const accessKeyId = parsed["--access-key-id"];
|
|
5315
|
+
const secretAccessKey = parsed["--secret-access-key"];
|
|
5316
|
+
const missing = [
|
|
5317
|
+
!bucket && "--bucket",
|
|
5318
|
+
!accessKeyId && "--access-key-id",
|
|
5319
|
+
!secretAccessKey && "--secret-access-key"
|
|
5320
|
+
].filter(Boolean);
|
|
5321
|
+
if (missing.length > 0) fail(`Missing ${missing.join(", ")}.`, "A bucket without credentials cannot be used, and would be stored as though it could. Run `rebase cloud storage --help` for the full list.");
|
|
5322
|
+
const { client } = await requireClient(rawArgs);
|
|
5323
|
+
const projectId = await requireProject(rawArgs, client);
|
|
5324
|
+
try {
|
|
5325
|
+
const existing = (await client.data.collection("storages").find({
|
|
5326
|
+
where: { project: ["==", projectId] },
|
|
5327
|
+
limit: 1
|
|
5328
|
+
})).data[0];
|
|
5329
|
+
const row = {
|
|
5330
|
+
project: projectId,
|
|
5331
|
+
type: "byos",
|
|
5332
|
+
status: "active",
|
|
5333
|
+
s3Bucket: bucket,
|
|
5334
|
+
s3AccessKeyId: accessKeyId,
|
|
5335
|
+
s3SecretAccessKey: secretAccessKey,
|
|
5336
|
+
bucketName: bucket
|
|
5337
|
+
};
|
|
5338
|
+
if (parsed["--endpoint"]) row.s3Endpoint = parsed["--endpoint"];
|
|
5339
|
+
if (parsed["--region"]) {
|
|
5340
|
+
row.s3Region = parsed["--region"];
|
|
5341
|
+
row.region = parsed["--region"];
|
|
5342
|
+
}
|
|
5343
|
+
if (parsed["--force-path-style"]) row.s3ForcePathStyle = true;
|
|
5344
|
+
if (existing?.id) await client.data.collection("storages").update(String(existing.id), row);
|
|
5345
|
+
else await client.data.collection("storages").create(row);
|
|
5346
|
+
success(`Storage attached to ${displayProjectRef(rawArgs)}.`);
|
|
5347
|
+
keyValues([
|
|
5348
|
+
["Bucket", bucket],
|
|
5349
|
+
["Endpoint", parsed["--endpoint"] ?? "AWS S3"],
|
|
5350
|
+
["Region", parsed["--region"] ?? "(default)"]
|
|
5351
|
+
]);
|
|
5352
|
+
console.log("");
|
|
5353
|
+
console.log(chalk.gray(" Redeploy for the tenant to pick it up: ") + chalk.bold("rebase cloud deploy"));
|
|
5354
|
+
console.log("");
|
|
5355
|
+
} catch (e) {
|
|
5356
|
+
reportError(e, "Failed to attach storage");
|
|
5357
|
+
}
|
|
5358
|
+
}
|
|
5203
5359
|
async function clustersCommand(rawArgs) {
|
|
5204
5360
|
const { client } = await requireClient(rawArgs);
|
|
5205
5361
|
try {
|
|
@@ -5417,7 +5573,7 @@ async function cloudCommand(subcommand, rawArgs) {
|
|
|
5417
5573
|
await webhooksCommand(action, rawArgs);
|
|
5418
5574
|
break;
|
|
5419
5575
|
case "storage":
|
|
5420
|
-
await storageCommand(rawArgs);
|
|
5576
|
+
await storageCommand(action, rawArgs);
|
|
5421
5577
|
break;
|
|
5422
5578
|
case "clusters":
|
|
5423
5579
|
await clustersCommand(rawArgs);
|
|
@@ -5520,6 +5676,8 @@ ${chalk.green.bold("Databases")}
|
|
|
5520
5676
|
${chalk.green.bold("Other resources")}
|
|
5521
5677
|
${chalk.blue.bold("webhooks list|create|delete")}
|
|
5522
5678
|
${chalk.blue.bold("storage")} List storage buckets
|
|
5679
|
+
${chalk.blue.bold("storage create")} Provision platform-managed storage
|
|
5680
|
+
${chalk.blue.bold("storage attach")} Attach your own S3-compatible bucket
|
|
5523
5681
|
${chalk.blue.bold("clusters")} List compute clusters
|
|
5524
5682
|
${chalk.blue.bold("billing setup")} Attach a card to the org ${chalk.gray("(one-time, opens browser)")}
|
|
5525
5683
|
${chalk.blue.bold("billing")} Show billing account + card on file
|
|
@@ -5628,9 +5786,10 @@ async function entry(args) {
|
|
|
5628
5786
|
await cloudCommand(effectiveSubcommand, args);
|
|
5629
5787
|
break;
|
|
5630
5788
|
default:
|
|
5631
|
-
console.
|
|
5789
|
+
console.error(chalk.red(`Unknown command: ${command}`));
|
|
5632
5790
|
console.log("");
|
|
5633
5791
|
printHelp();
|
|
5792
|
+
process.exit(1);
|
|
5634
5793
|
}
|
|
5635
5794
|
}
|
|
5636
5795
|
function printHelp() {
|