@rebasepro/cli 0.9.1-canary.97e305f → 0.9.1-canary.a639738
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/index.es.js +108 -2
- package/dist/index.es.js.map +1 -1
- package/package.json +7 -7
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>;
|
package/dist/index.es.js
CHANGED
|
@@ -5227,7 +5227,10 @@ async function webhooksCommand(subcommand, rawArgs) {
|
|
|
5227
5227
|
reportError(e, "Webhook operation failed");
|
|
5228
5228
|
}
|
|
5229
5229
|
}
|
|
5230
|
-
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();
|
|
5231
5234
|
const { client } = await requireClient(rawArgs);
|
|
5232
5235
|
const projectId = await requireProject(rawArgs, client);
|
|
5233
5236
|
try {
|
|
@@ -5252,6 +5255,107 @@ async function storageCommand(rawArgs) {
|
|
|
5252
5255
|
reportError(e, "Failed to list storage");
|
|
5253
5256
|
}
|
|
5254
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
|
+
}
|
|
5255
5359
|
async function clustersCommand(rawArgs) {
|
|
5256
5360
|
const { client } = await requireClient(rawArgs);
|
|
5257
5361
|
try {
|
|
@@ -5469,7 +5573,7 @@ async function cloudCommand(subcommand, rawArgs) {
|
|
|
5469
5573
|
await webhooksCommand(action, rawArgs);
|
|
5470
5574
|
break;
|
|
5471
5575
|
case "storage":
|
|
5472
|
-
await storageCommand(rawArgs);
|
|
5576
|
+
await storageCommand(action, rawArgs);
|
|
5473
5577
|
break;
|
|
5474
5578
|
case "clusters":
|
|
5475
5579
|
await clustersCommand(rawArgs);
|
|
@@ -5572,6 +5676,8 @@ ${chalk.green.bold("Databases")}
|
|
|
5572
5676
|
${chalk.green.bold("Other resources")}
|
|
5573
5677
|
${chalk.blue.bold("webhooks list|create|delete")}
|
|
5574
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
|
|
5575
5681
|
${chalk.blue.bold("clusters")} List compute clusters
|
|
5576
5682
|
${chalk.blue.bold("billing setup")} Attach a card to the org ${chalk.gray("(one-time, opens browser)")}
|
|
5577
5683
|
${chalk.blue.bold("billing")} Show billing account + card on file
|