@rebasepro/cli 0.10.0 → 0.10.1-canary.18115ba
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/dist/commands/cloud/context.d.ts +10 -0
- package/dist/commands/cloud/deployments.d.ts +16 -0
- package/dist/commands/cloud/env.d.ts +2 -0
- package/dist/commands/cloud/resources.d.ts +38 -0
- package/dist/index.es.js +327 -56
- package/dist/index.es.js.map +1 -1
- package/dist/utils/package-manager.d.ts +26 -2
- package/package.json +7 -7
|
@@ -100,6 +100,16 @@ export declare function initOutputMode(rawArgs: string[]): boolean;
|
|
|
100
100
|
export declare function isJsonMode(): boolean;
|
|
101
101
|
/** Force the mode (tests only — production latches it via `initOutputMode`). */
|
|
102
102
|
export declare function setJsonModeForTest(value: boolean): void;
|
|
103
|
+
/**
|
|
104
|
+
* Write one JSON value to stdout, followed by a newline.
|
|
105
|
+
*
|
|
106
|
+
* Indented, because the overwhelmingly common reader is a person or an agent
|
|
107
|
+
* looking at a terminal — JSON mode is entered automatically whenever stdout is
|
|
108
|
+
* not a TTY, so `rebase cloud deployments list` piped anywhere at all produced
|
|
109
|
+
* a project's entire deployment history as one unwrapped line. `JSON.parse`
|
|
110
|
+
* does not care about the whitespace; everything else does.
|
|
111
|
+
*/
|
|
112
|
+
export declare function printJson(value: unknown): void;
|
|
103
113
|
/**
|
|
104
114
|
* The one output primitive every new command uses: in JSON mode emit `json`
|
|
105
115
|
* (and nothing else); otherwise run `human`. Keeping the two behind a single
|
|
@@ -18,6 +18,10 @@ export interface DeploymentRow {
|
|
|
18
18
|
triggered_by_user_id?: string;
|
|
19
19
|
gitCommitHash?: string;
|
|
20
20
|
gitCommitMessage?: string;
|
|
21
|
+
deployMessage?: string;
|
|
22
|
+
deploy_message?: string;
|
|
23
|
+
frameworkVersion?: string;
|
|
24
|
+
framework_version?: string;
|
|
21
25
|
}
|
|
22
26
|
/**
|
|
23
27
|
* The backend's rule EXACTLY: a rollback is honoured only for a successful
|
|
@@ -33,6 +37,18 @@ export declare function triggerInfo(dep: DeploymentRow): {
|
|
|
33
37
|
};
|
|
34
38
|
/** Shape one deployment row into the stable JSON view the CLI publishes. */
|
|
35
39
|
export declare function deploymentView(dep: DeploymentRow): Record<string, unknown>;
|
|
40
|
+
/**
|
|
41
|
+
* Rows shown when `--limit` is not given.
|
|
42
|
+
*
|
|
43
|
+
* History is unbounded and grows one row per deploy, so "all of it" is the
|
|
44
|
+
* wrong default in both directions: a wall of near-identical lines in a
|
|
45
|
+
* terminal, and — since JSON mode is entered automatically for any non-TTY
|
|
46
|
+
* stdout — a project's entire history dumped at anything that pipes the
|
|
47
|
+
* command. Recent deploys are what the question is almost always about.
|
|
48
|
+
*/
|
|
49
|
+
export declare const DEFAULT_DEPLOYMENTS_LIMIT = 20;
|
|
50
|
+
/** `--limit N`, bounded. A garbage value is a refusal, never a silent default. */
|
|
51
|
+
export declare function parseDeploymentsLimit(raw: number | undefined): number;
|
|
36
52
|
export declare function deploymentsListCommand(rawArgs: string[]): Promise<void>;
|
|
37
53
|
export declare function rollbackCommand(rawArgs: string[]): Promise<void>;
|
|
38
54
|
export declare function cancelCommand(rawArgs: string[]): Promise<void>;
|
|
@@ -1,6 +1,44 @@
|
|
|
1
|
+
/** The control plane's verdict on what a tenant's uploads actually do. */
|
|
2
|
+
interface StorageState {
|
|
3
|
+
effective?: {
|
|
4
|
+
kind?: string;
|
|
5
|
+
summary?: string;
|
|
6
|
+
storageType?: string;
|
|
7
|
+
missing?: string[];
|
|
8
|
+
};
|
|
9
|
+
source?: string;
|
|
10
|
+
configured?: string;
|
|
11
|
+
overridden?: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* One line describing this project's storage — or `undefined` when the control
|
|
15
|
+
* plane could not be asked, which prints as a blank rather than a guess.
|
|
16
|
+
*
|
|
17
|
+
* `status` used to render the `storages` row and nothing else, so a project
|
|
18
|
+
* whose bucket is configured through its own `STORAGE_TYPE`/`S3_*` variables —
|
|
19
|
+
* the supported path, and the one `mergeStorageEnv` deliberately lets WIN over
|
|
20
|
+
* the row — was reported as `Storage: none` while its pod logged `Initialized
|
|
21
|
+
* storage backends count: 1` against a live bucket. Storage is the thing an app
|
|
22
|
+
* refuses to boot without, so that false negative sends someone off to
|
|
23
|
+
* provision a bucket they already have. The row is not the answer; the tenant's
|
|
24
|
+
* resolved environment is, and the control plane computes it with the same two
|
|
25
|
+
* functions the build log uses.
|
|
26
|
+
*/
|
|
27
|
+
export declare function describeStorageState(state: StorageState | undefined): string | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* One line describing the database.
|
|
30
|
+
*
|
|
31
|
+
* `connectionStatus` is written `"untested"` at creation and only ever changed
|
|
32
|
+
* by `rebase cloud db test`, so `managed (untested)` was reporting the absence
|
|
33
|
+
* of a manual test as though it were the database's condition — on a project
|
|
34
|
+
* that had just deployed against it. A never-tested database says only its
|
|
35
|
+
* type; the verdict appears once there is one.
|
|
36
|
+
*/
|
|
37
|
+
export declare function describeDatabaseState(db: Record<string, unknown> | undefined): string | undefined;
|
|
1
38
|
export declare function statusCommand(rawArgs: string[]): Promise<void>;
|
|
2
39
|
export declare function metricsCommand(rawArgs: string[]): Promise<void>;
|
|
3
40
|
export declare function webhooksCommand(subcommand: string | undefined, rawArgs: string[]): Promise<void>;
|
|
4
41
|
export declare function storageCommand(action: string | undefined, rawArgs: string[]): Promise<void>;
|
|
5
42
|
export declare function clustersCommand(rawArgs: string[]): Promise<void>;
|
|
6
43
|
export declare function billingCommand(rawArgs: string[]): Promise<void>;
|
|
44
|
+
export {};
|
package/dist/index.es.js
CHANGED
|
@@ -23,21 +23,61 @@ import { createRequire } from "module";
|
|
|
23
23
|
* the rest of the CLI never has to hardcode a specific PM.
|
|
24
24
|
*/
|
|
25
25
|
/**
|
|
26
|
+
* How long to wait for `pnpm --version` before giving up on the probe.
|
|
27
|
+
*
|
|
28
|
+
* `pnpm --version` is a cold Node start, and on a machine that is busy — a
|
|
29
|
+
* parallel install, a full test run — it routinely takes seconds. Measured at
|
|
30
|
+
* 630ms, 990ms and 4293ms on three consecutive runs of one developer laptop
|
|
31
|
+
* under load, so the previous 3s budget was inside the normal spread rather
|
|
32
|
+
* than safely outside it.
|
|
33
|
+
*/
|
|
34
|
+
var PNPM_PROBE_TIMEOUT_MS = 5e3;
|
|
35
|
+
/** Memoised result of the probe. pnpm cannot appear or vanish mid-process. */
|
|
36
|
+
var cachedPnpmAvailable;
|
|
37
|
+
/**
|
|
38
|
+
* Decide availability from a `spawnSync` outcome.
|
|
39
|
+
*
|
|
40
|
+
* Split out from the spawn itself so the decision is testable without starting
|
|
41
|
+
* a process — which is what made the old test load-sensitive and occasionally
|
|
42
|
+
* red for reasons that had nothing to do with the code under test.
|
|
43
|
+
*
|
|
44
|
+
* The three outcomes are distinguishable, and the old code conflated two of
|
|
45
|
+
* them by asking only `status === 0`:
|
|
46
|
+
*
|
|
47
|
+
* not installed status null, signal null, error.code ENOENT
|
|
48
|
+
* timed out status null, signal SIGTERM, error.code ETIMEDOUT
|
|
49
|
+
* broken install status non-zero, no error
|
|
50
|
+
*/
|
|
51
|
+
function pnpmAvailabilityFromProbe(res) {
|
|
52
|
+
const code = res.error?.code;
|
|
53
|
+
if (code === "ENOENT") return false;
|
|
54
|
+
if (code === "ETIMEDOUT" || res.signal) return true;
|
|
55
|
+
if (res.error) return false;
|
|
56
|
+
return res.status === 0;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
26
59
|
* Whether pnpm is runnable on this machine.
|
|
27
60
|
*
|
|
28
61
|
* Used to decide whether a fresh project can be scaffolded with pnpm. Kept
|
|
29
|
-
* cheap and non-interactive (
|
|
30
|
-
* hangs detection if a corepack shim misbehaves
|
|
62
|
+
* cheap and non-interactive (bounded timeout, output discarded) so it never
|
|
63
|
+
* hangs detection if a corepack shim misbehaves, and memoised so that repeated
|
|
64
|
+
* detection in one CLI run costs one process rather than one per call.
|
|
31
65
|
*/
|
|
32
66
|
function isPnpmAvailable() {
|
|
67
|
+
if (cachedPnpmAvailable !== void 0) return cachedPnpmAvailable;
|
|
33
68
|
try {
|
|
34
|
-
|
|
69
|
+
cachedPnpmAvailable = pnpmAvailabilityFromProbe(spawnSync("pnpm", ["--version"], {
|
|
35
70
|
stdio: "ignore",
|
|
36
|
-
timeout:
|
|
37
|
-
})
|
|
71
|
+
timeout: PNPM_PROBE_TIMEOUT_MS
|
|
72
|
+
}));
|
|
38
73
|
} catch {
|
|
39
|
-
|
|
74
|
+
cachedPnpmAvailable = false;
|
|
40
75
|
}
|
|
76
|
+
return cachedPnpmAvailable;
|
|
77
|
+
}
|
|
78
|
+
/** Forget the memoised probe. For tests; nothing in a CLI run needs it. */
|
|
79
|
+
function resetPnpmAvailabilityCache() {
|
|
80
|
+
cachedPnpmAvailable = void 0;
|
|
41
81
|
}
|
|
42
82
|
/**
|
|
43
83
|
* Detect the package manager for a Rebase project.
|
|
@@ -636,9 +676,17 @@ var ANSI_RE = /\[[0-9;]*m/g;
|
|
|
636
676
|
function stripAnsi(s) {
|
|
637
677
|
return s.replace(ANSI_RE, "");
|
|
638
678
|
}
|
|
639
|
-
/**
|
|
679
|
+
/**
|
|
680
|
+
* Write one JSON value to stdout, followed by a newline.
|
|
681
|
+
*
|
|
682
|
+
* Indented, because the overwhelmingly common reader is a person or an agent
|
|
683
|
+
* looking at a terminal — JSON mode is entered automatically whenever stdout is
|
|
684
|
+
* not a TTY, so `rebase cloud deployments list` piped anywhere at all produced
|
|
685
|
+
* a project's entire deployment history as one unwrapped line. `JSON.parse`
|
|
686
|
+
* does not care about the whitespace; everything else does.
|
|
687
|
+
*/
|
|
640
688
|
function printJson(value) {
|
|
641
|
-
process.stdout.write(JSON.stringify(value) + "\n");
|
|
689
|
+
process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
642
690
|
}
|
|
643
691
|
/**
|
|
644
692
|
* The one output primitive every new command uses: in JSON mode emit `json`
|
|
@@ -3421,6 +3469,35 @@ async function createSourceTarball(sourceDir) {
|
|
|
3421
3469
|
}
|
|
3422
3470
|
return tarPath;
|
|
3423
3471
|
}
|
|
3472
|
+
/**
|
|
3473
|
+
* The `@rebasepro/*` version this source directory actually resolves.
|
|
3474
|
+
*
|
|
3475
|
+
* Recorded on the deployment so a row in Deployment History says which
|
|
3476
|
+
* framework build shipped. Nothing else on the platform knows: an app that
|
|
3477
|
+
* links the framework locally pins it at package time, and a silent bump is
|
|
3478
|
+
* invisible afterwards — it has already cost one debugging session.
|
|
3479
|
+
*
|
|
3480
|
+
* `@rebasepro/server` first, because that is what the deployed backend runs;
|
|
3481
|
+
* `@rebasepro/client` is the fallback for a frontend-only bundle. Resolution is
|
|
3482
|
+
* a plain walk up from the source directory rather than `require.resolve`,
|
|
3483
|
+
* which would answer for the CLI's own install tree instead of the app's.
|
|
3484
|
+
*
|
|
3485
|
+
* Best effort by construction: a version that cannot be read is simply not
|
|
3486
|
+
* recorded. Nothing about a deploy should fail over a bookkeeping string.
|
|
3487
|
+
*/
|
|
3488
|
+
function resolveFrameworkVersion(sourceDir) {
|
|
3489
|
+
let dir = path.resolve(sourceDir);
|
|
3490
|
+
for (;;) {
|
|
3491
|
+
for (const pkg of ["@rebasepro/server", "@rebasepro/client"]) try {
|
|
3492
|
+
const manifest = path.join(dir, "node_modules", ...pkg.split("/"), "package.json");
|
|
3493
|
+
const version = JSON.parse(fs.readFileSync(manifest, "utf8")).version;
|
|
3494
|
+
if (typeof version === "string" && version.trim() !== "") return version.trim();
|
|
3495
|
+
} catch {}
|
|
3496
|
+
const parent = path.dirname(dir);
|
|
3497
|
+
if (parent === dir) return void 0;
|
|
3498
|
+
dir = parent;
|
|
3499
|
+
}
|
|
3500
|
+
}
|
|
3424
3501
|
/** Upload a build-context tarball; returns the opaque `source` ref for deploy. */
|
|
3425
3502
|
async function uploadSource(url, token, projectId, tarPath) {
|
|
3426
3503
|
const bytes = fs.readFileSync(tarPath);
|
|
@@ -3446,7 +3523,9 @@ async function uploadSource(url, token, projectId, tarPath) {
|
|
|
3446
3523
|
async function deployCommand(rawArgs, projectRef) {
|
|
3447
3524
|
const args = arg({
|
|
3448
3525
|
"--no-follow": Boolean,
|
|
3449
|
-
"--source": String
|
|
3526
|
+
"--source": String,
|
|
3527
|
+
"--message": String,
|
|
3528
|
+
"-m": "--message"
|
|
3450
3529
|
}, {
|
|
3451
3530
|
argv: rawArgs.slice(2),
|
|
3452
3531
|
permissive: true
|
|
@@ -3466,32 +3545,87 @@ async function deployCommand(rawArgs, projectRef) {
|
|
|
3466
3545
|
}
|
|
3467
3546
|
console.log("");
|
|
3468
3547
|
console.log(` 🚀 Triggering deployment for project ${chalk.bold(projectRef)}${source ? " from uploaded source" : ""}...`);
|
|
3469
|
-
|
|
3548
|
+
const body = { projectId };
|
|
3549
|
+
if (source) body.source = source;
|
|
3550
|
+
if (args["--message"]) body.message = args["--message"];
|
|
3551
|
+
body.client = "cli";
|
|
3552
|
+
const frameworkVersion = resolveFrameworkVersion(args["--source"] ?? process.cwd());
|
|
3553
|
+
if (frameworkVersion) body.frameworkVersion = frameworkVersion;
|
|
3554
|
+
let triggered;
|
|
3470
3555
|
try {
|
|
3471
|
-
const res = await client.functions.invoke("deploy",
|
|
3472
|
-
projectId,
|
|
3473
|
-
source
|
|
3474
|
-
} : { projectId });
|
|
3556
|
+
const res = await client.functions.invoke("deploy", body);
|
|
3475
3557
|
if (!res?.deployment?.id) fail("Control plane did not return a deployment id.");
|
|
3476
|
-
|
|
3558
|
+
triggered = {
|
|
3559
|
+
deploymentId: String(res.deployment.id),
|
|
3560
|
+
deduplicated: res.deduplicated === true
|
|
3561
|
+
};
|
|
3477
3562
|
} catch (e) {
|
|
3478
|
-
|
|
3479
|
-
if (err?.status === 409) fail("A deployment is already in progress for this project.");
|
|
3480
|
-
if (err?.status === 402) fail(err.message || "Payment required before deploying.", "Attach a card once with `rebase cloud billing setup`, then deploy again.");
|
|
3481
|
-
reportError(e, "Failed to trigger deployment");
|
|
3563
|
+
triggered = resolveTriggerFailure(e);
|
|
3482
3564
|
}
|
|
3483
|
-
|
|
3565
|
+
const { deploymentId, deduplicated } = triggered;
|
|
3566
|
+
if (!isJsonMode()) console.log(chalk.gray(deduplicated ? ` Deployment ${deploymentId} is already running — following it.` : ` Deployment ${deploymentId} created.${frameworkVersion ? ` (@rebasepro/* ${frameworkVersion})` : ""}`));
|
|
3484
3567
|
if (args["--no-follow"]) {
|
|
3485
|
-
|
|
3486
|
-
|
|
3568
|
+
emit(() => {
|
|
3569
|
+
console.log(chalk.gray(" Not following logs (--no-follow). Check status with `rebase cloud logs`."));
|
|
3570
|
+
console.log("");
|
|
3571
|
+
}, {
|
|
3572
|
+
deploymentId,
|
|
3573
|
+
deduplicated,
|
|
3574
|
+
frameworkVersion: frameworkVersion ?? null,
|
|
3575
|
+
following: false
|
|
3576
|
+
});
|
|
3487
3577
|
return;
|
|
3488
3578
|
}
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3579
|
+
if (!isJsonMode()) {
|
|
3580
|
+
console.log(chalk.gray(" Streaming build logs (Ctrl-C to stop watching — the build keeps running):"));
|
|
3581
|
+
console.log("");
|
|
3582
|
+
}
|
|
3583
|
+
const status = await streamBuildLogs(client, deploymentId, { quiet: isJsonMode() });
|
|
3584
|
+
emit(() => {}, {
|
|
3585
|
+
deploymentId,
|
|
3586
|
+
deduplicated,
|
|
3587
|
+
frameworkVersion: frameworkVersion ?? null,
|
|
3588
|
+
following: true,
|
|
3589
|
+
status
|
|
3590
|
+
});
|
|
3591
|
+
}
|
|
3592
|
+
/**
|
|
3593
|
+
* Turn a failed trigger into either a deployment to follow, or an exit.
|
|
3594
|
+
*
|
|
3595
|
+
* The 409 is the interesting one. A deploy trigger can reach the control plane
|
|
3596
|
+
* twice without anybody asking twice — the SDK transport replays a request once
|
|
3597
|
+
* after refreshing an expired token, and any lost response has the same effect
|
|
3598
|
+
* — so "a deployment is already in progress" was routinely describing the
|
|
3599
|
+
* deployment this very command had just created. With no id in the message the
|
|
3600
|
+
* only available reading was "someone else is deploying, back off", and the
|
|
3601
|
+
* build stream was lost either way.
|
|
3602
|
+
*
|
|
3603
|
+
* So: if the control plane says the blocking deployment is ours, we attach to
|
|
3604
|
+
* it. If it is not ours, we still name it, because "which one, since when, from
|
|
3605
|
+
* where" is the difference between an actionable refusal and a dead end.
|
|
3606
|
+
*/
|
|
3607
|
+
function resolveTriggerFailure(e) {
|
|
3608
|
+
const err = e;
|
|
3609
|
+
if (err?.status === 409) {
|
|
3610
|
+
const blocking = err.details?.deployment;
|
|
3611
|
+
if (blocking?.id && blocking.mine) return {
|
|
3612
|
+
deploymentId: String(blocking.id),
|
|
3613
|
+
deduplicated: true
|
|
3614
|
+
};
|
|
3615
|
+
fail(blocking?.id ? `Deployment ${blocking.id} is already in progress for this project${blocking.triggerSource && blocking.triggerSource !== "unknown" ? `, triggered from the ${blocking.triggerSource}` : ""}${blocking.createdAt ? ` at ${fmtDate(blocking.createdAt)}` : ""}.` : "A deployment is already in progress for this project.", blocking?.id ? `Follow it with \`rebase cloud logs -f\`, or stop it with \`rebase cloud cancel ${blocking.id}\`.` : "Follow it with `rebase cloud logs -f`.", "deploy_in_progress");
|
|
3616
|
+
}
|
|
3617
|
+
if (err?.status === 402) fail(err.message || "Payment required before deploying.", "Attach a card once with `rebase cloud billing setup`, then deploy again.", "payment_required");
|
|
3618
|
+
reportError(e, "Failed to trigger deployment");
|
|
3492
3619
|
}
|
|
3493
|
-
/**
|
|
3494
|
-
|
|
3620
|
+
/**
|
|
3621
|
+
* Poll a deployment record and print new log output as it arrives. Returns the
|
|
3622
|
+
* terminal status; a non-success still exits non-zero, as it always has.
|
|
3623
|
+
*
|
|
3624
|
+
* `quiet` follows without printing — JSON mode, where the log stream would
|
|
3625
|
+
* corrupt the one object the caller is parsing.
|
|
3626
|
+
*/
|
|
3627
|
+
async function streamBuildLogs(client, deploymentId, opts = {}) {
|
|
3628
|
+
const quiet = opts.quiet === true;
|
|
3495
3629
|
let printed = 0;
|
|
3496
3630
|
const started = Date.now();
|
|
3497
3631
|
for (;;) {
|
|
@@ -3501,26 +3635,37 @@ async function streamBuildLogs(client, deploymentId) {
|
|
|
3501
3635
|
} catch (e) {
|
|
3502
3636
|
reportError(e, "Failed to read deployment status");
|
|
3503
3637
|
}
|
|
3504
|
-
if (!dep) fail(`Deployment ${deploymentId} disappeared
|
|
3638
|
+
if (!dep) fail(`Deployment ${deploymentId} disappeared.`, void 0, "not_found");
|
|
3505
3639
|
const logs = dep.logs ?? "";
|
|
3506
|
-
if (logs.length > printed)
|
|
3507
|
-
|
|
3508
|
-
printed = logs.length;
|
|
3509
|
-
}
|
|
3640
|
+
if (!quiet && logs.length > printed) process.stdout.write(logs.slice(printed));
|
|
3641
|
+
printed = logs.length;
|
|
3510
3642
|
if (dep.status && dep.status !== "deploying") {
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3643
|
+
if (dep.status !== "success") {
|
|
3644
|
+
if (quiet) {
|
|
3645
|
+
printJson({ error: {
|
|
3646
|
+
message: `Deployment ${deploymentId} ${dep.status}.`,
|
|
3647
|
+
code: "deploy_failed",
|
|
3648
|
+
status: null,
|
|
3649
|
+
deploymentId,
|
|
3650
|
+
logs
|
|
3651
|
+
} });
|
|
3652
|
+
process.exit(1);
|
|
3653
|
+
}
|
|
3654
|
+
console.log("");
|
|
3514
3655
|
console.log(chalk.bold.red(` ✗ Deployment ${dep.status}`));
|
|
3515
3656
|
console.log("");
|
|
3516
3657
|
process.exit(1);
|
|
3517
3658
|
}
|
|
3518
|
-
|
|
3519
|
-
|
|
3659
|
+
if (!quiet) {
|
|
3660
|
+
console.log("");
|
|
3661
|
+
console.log(chalk.bold.green(" ✓ Deployment succeeded"));
|
|
3662
|
+
console.log("");
|
|
3663
|
+
}
|
|
3664
|
+
return dep.status;
|
|
3520
3665
|
}
|
|
3521
3666
|
if (Date.now() - started > POLL_TIMEOUT_MS) {
|
|
3522
|
-
console.log("");
|
|
3523
|
-
fail("Timed out waiting for the build to finish.", "The deployment may still be running — check `rebase cloud logs`.");
|
|
3667
|
+
if (!quiet) console.log("");
|
|
3668
|
+
fail("Timed out waiting for the build to finish.", "The deployment may still be running — check `rebase cloud logs`.", "timeout");
|
|
3524
3669
|
}
|
|
3525
3670
|
await sleep(POLL_INTERVAL_MS);
|
|
3526
3671
|
}
|
|
@@ -4233,9 +4378,35 @@ function parseEnvAssignment(operands) {
|
|
|
4233
4378
|
value: operands[1] ?? ""
|
|
4234
4379
|
};
|
|
4235
4380
|
}
|
|
4381
|
+
/**
|
|
4382
|
+
* Prefixes whose variables are read by a BUNDLER at build time, not by the
|
|
4383
|
+
* process at run time.
|
|
4384
|
+
*
|
|
4385
|
+
* These are the ones this command cannot deliver. A project's environment is
|
|
4386
|
+
* applied at rollout — after Kaniko has already built the image — so a
|
|
4387
|
+
* `VITE_API_URL` set here is present in the running container and absent from
|
|
4388
|
+
* the JavaScript that was compiled minutes earlier. Nothing fails: the variable
|
|
4389
|
+
* exists, the deploy succeeds, and the bundle carries `undefined` where the
|
|
4390
|
+
* value should be. The bug then presents in the browser as missing
|
|
4391
|
+
* configuration, which is several steps away from the cause.
|
|
4392
|
+
*
|
|
4393
|
+
* `import.meta.env` inlining is Vite's; `NEXT_PUBLIC_`/`PUBLIC_`/`REACT_APP_`
|
|
4394
|
+
* are the same contract in Next, Astro/SvelteKit and CRA.
|
|
4395
|
+
*/
|
|
4396
|
+
var BUILD_TIME_ENV_PREFIXES = [
|
|
4397
|
+
"VITE_",
|
|
4398
|
+
"NEXT_PUBLIC_",
|
|
4399
|
+
"PUBLIC_",
|
|
4400
|
+
"REACT_APP_"
|
|
4401
|
+
];
|
|
4402
|
+
/** The prefix that makes `key` a build-time variable, or undefined. */
|
|
4403
|
+
function buildTimeEnvPrefix(key) {
|
|
4404
|
+
return BUILD_TIME_ENV_PREFIXES.find((prefix) => key.toUpperCase().startsWith(prefix));
|
|
4405
|
+
}
|
|
4236
4406
|
async function setEnv(rawArgs) {
|
|
4237
4407
|
const args = arg({
|
|
4238
4408
|
"--secret": Boolean,
|
|
4409
|
+
"--force": Boolean,
|
|
4239
4410
|
"--project": String,
|
|
4240
4411
|
"-p": "--project"
|
|
4241
4412
|
}, {
|
|
@@ -4247,6 +4418,8 @@ async function setEnv(rawArgs) {
|
|
|
4247
4418
|
displayProjectRef(rawArgs);
|
|
4248
4419
|
const parsed = parseEnvAssignment(cloudPositionals(rawArgs).slice(2));
|
|
4249
4420
|
if (!parsed || !parsed.key) fail("Usage: rebase cloud env set KEY=VALUE [--secret]", void 0, "usage");
|
|
4421
|
+
const buildTimePrefix = buildTimeEnvPrefix(parsed.key);
|
|
4422
|
+
if (buildTimePrefix && !args["--force"]) fail(`${parsed.key} is read by your bundler at BUILD time, and project variables are applied at rollout — after the image is built. Setting it here would not reach the bundle.`, `Put ${buildTimePrefix}* variables in the source you deploy (a committed .env, or your build config), then \`rebase cloud deploy\`. Pass --force if your build genuinely reads this at run time.`, "build_time_variable");
|
|
4250
4423
|
const body = {
|
|
4251
4424
|
key: parsed.key,
|
|
4252
4425
|
value: parsed.value
|
|
@@ -4403,10 +4576,13 @@ ${chalk.green.bold("Commands")}
|
|
|
4403
4576
|
|
|
4404
4577
|
${chalk.green.bold("Options")}
|
|
4405
4578
|
${chalk.blue("--secret")} Mark a variable write-only ${chalk.gray("(set)")}
|
|
4579
|
+
${chalk.blue("--force")} Set a build-time key anyway ${chalk.gray("(set)")}
|
|
4406
4580
|
${chalk.blue("--json")} Machine-readable output
|
|
4407
4581
|
${chalk.blue("--project, -p")} Project slug ${chalk.gray("(defaults to the linked project)")}
|
|
4408
4582
|
|
|
4409
4583
|
${chalk.gray("Values are encrypted at rest (AES-256-GCM) and only decrypted at deploy time.")}
|
|
4584
|
+
${chalk.gray("VITE_* / NEXT_PUBLIC_* / PUBLIC_* / REACT_APP_* are read by your bundler at BUILD time;")}
|
|
4585
|
+
${chalk.gray("these are applied at rollout, after the image is built, so they never reach the bundle.")}
|
|
4410
4586
|
`);
|
|
4411
4587
|
}
|
|
4412
4588
|
function printEnvHelpJson() {
|
|
@@ -5002,6 +5178,8 @@ function deploymentView(dep) {
|
|
|
5002
5178
|
isRollback: str(dep, "rollbackOf", "rollback_of") !== null,
|
|
5003
5179
|
rollbackable: isRollbackable(dep),
|
|
5004
5180
|
trigger: triggerInfo(dep),
|
|
5181
|
+
message: str(dep, "deployMessage", "deploy_message"),
|
|
5182
|
+
frameworkVersion: str(dep, "frameworkVersion", "framework_version"),
|
|
5005
5183
|
commit: {
|
|
5006
5184
|
hash: str(dep, "gitCommitHash", "gitCommitHash"),
|
|
5007
5185
|
message: str(dep, "gitCommitMessage", "gitCommitMessage")
|
|
@@ -5015,12 +5193,31 @@ async function fetchDeployments(client, projectId, limit = 100) {
|
|
|
5015
5193
|
limit
|
|
5016
5194
|
})).data;
|
|
5017
5195
|
}
|
|
5196
|
+
/** Hard ceiling on `--limit`, matching the backend's own page size. */
|
|
5197
|
+
var MAX_DEPLOYMENTS_LIMIT = 100;
|
|
5198
|
+
/** `--limit N`, bounded. A garbage value is a refusal, never a silent default. */
|
|
5199
|
+
function parseDeploymentsLimit(raw) {
|
|
5200
|
+
if (raw === void 0) return 20;
|
|
5201
|
+
if (!Number.isInteger(raw) || raw < 1 || raw > MAX_DEPLOYMENTS_LIMIT) fail(`--limit must be a whole number between 1 and ${MAX_DEPLOYMENTS_LIMIT}.`, void 0, "usage");
|
|
5202
|
+
return raw;
|
|
5203
|
+
}
|
|
5018
5204
|
async function deploymentsListCommand(rawArgs) {
|
|
5205
|
+
const args = arg({
|
|
5206
|
+
"--limit": Number,
|
|
5207
|
+
"--all": Boolean,
|
|
5208
|
+
"--project": String,
|
|
5209
|
+
"-p": "--project"
|
|
5210
|
+
}, {
|
|
5211
|
+
argv: rawArgs.slice(2),
|
|
5212
|
+
permissive: true
|
|
5213
|
+
});
|
|
5214
|
+
const limit = args["--all"] ? MAX_DEPLOYMENTS_LIMIT : parseDeploymentsLimit(args["--limit"]);
|
|
5019
5215
|
const { client } = await requireClient(rawArgs);
|
|
5020
5216
|
const projectId = await requireProject(rawArgs, client);
|
|
5021
5217
|
const projectRef = displayProjectRef(rawArgs);
|
|
5022
5218
|
try {
|
|
5023
|
-
const views = (await fetchDeployments(client, projectId)).map(deploymentView);
|
|
5219
|
+
const views = (await fetchDeployments(client, projectId, limit)).map(deploymentView);
|
|
5220
|
+
const truncated = views.length === limit;
|
|
5024
5221
|
emit(() => {
|
|
5025
5222
|
console.log("");
|
|
5026
5223
|
console.log(chalk.bold(` 🚀 Deployments — project ${projectRef}`));
|
|
@@ -5035,10 +5232,18 @@ async function deploymentsListCommand(rawArgs) {
|
|
|
5035
5232
|
const trig = v.trigger.source;
|
|
5036
5233
|
const roll = v.rollbackable ? chalk.green(" ↺ rollbackable") : "";
|
|
5037
5234
|
console.log(` ${chalk.gray(`[${v.id}]`)} ${colorStatus(v.status)} ${chalk.gray(String(v.createdAt ?? "—"))} ${dur} ${chalk.gray(trig)}${roll}`);
|
|
5235
|
+
const label = [v.message, v.frameworkVersion ? `@rebasepro/* ${v.frameworkVersion}` : null].filter(Boolean).join(" · ");
|
|
5236
|
+
if (label) console.log(` ${chalk.gray(label)}`);
|
|
5237
|
+
}
|
|
5238
|
+
if (truncated) {
|
|
5239
|
+
console.log("");
|
|
5240
|
+
console.log(chalk.gray(` Showing the ${limit} most recent. Use \`--limit N\` or \`--all\` for more.`));
|
|
5038
5241
|
}
|
|
5039
5242
|
console.log("");
|
|
5040
5243
|
}, {
|
|
5041
5244
|
projectId,
|
|
5245
|
+
limit,
|
|
5246
|
+
truncated,
|
|
5042
5247
|
deployments: views
|
|
5043
5248
|
});
|
|
5044
5249
|
} catch (e) {
|
|
@@ -5938,29 +6143,95 @@ ${chalk.gray("so it works in a deploy script. To restart a workload, use `rebase
|
|
|
5938
6143
|
* `rebase cloud` resource subcommands: status, metrics, webhooks, storage,
|
|
5939
6144
|
* clusters, billing.
|
|
5940
6145
|
*/
|
|
6146
|
+
/**
|
|
6147
|
+
* One line describing this project's storage — or `undefined` when the control
|
|
6148
|
+
* plane could not be asked, which prints as a blank rather than a guess.
|
|
6149
|
+
*
|
|
6150
|
+
* `status` used to render the `storages` row and nothing else, so a project
|
|
6151
|
+
* whose bucket is configured through its own `STORAGE_TYPE`/`S3_*` variables —
|
|
6152
|
+
* the supported path, and the one `mergeStorageEnv` deliberately lets WIN over
|
|
6153
|
+
* the row — was reported as `Storage: none` while its pod logged `Initialized
|
|
6154
|
+
* storage backends count: 1` against a live bucket. Storage is the thing an app
|
|
6155
|
+
* refuses to boot without, so that false negative sends someone off to
|
|
6156
|
+
* provision a bucket they already have. The row is not the answer; the tenant's
|
|
6157
|
+
* resolved environment is, and the control plane computes it with the same two
|
|
6158
|
+
* functions the build log uses.
|
|
6159
|
+
*/
|
|
6160
|
+
function describeStorageState(state) {
|
|
6161
|
+
const verdict = state?.effective;
|
|
6162
|
+
if (!verdict?.kind) return void 0;
|
|
6163
|
+
const via = state?.overridden ? chalk.gray(" · from env vars") : "";
|
|
6164
|
+
switch (verdict.kind) {
|
|
6165
|
+
case "durable": return `${chalk.green("durable")}${verdict.summary ? ` · ${verdict.summary}` : ""}${via}`;
|
|
6166
|
+
case "ephemeral": return `${chalk.yellow("ephemeral")} ${chalk.gray("· uploads are lost on restart")}`;
|
|
6167
|
+
case "incomplete": return `${chalk.red("incomplete")} ${chalk.gray(`· missing ${(verdict.missing ?? []).join(", ")}`)}`;
|
|
6168
|
+
case "unrecognized": return `${chalk.red("unrecognized")} ${chalk.gray(`· STORAGE_TYPE=${verdict.storageType ?? "?"}`)}`;
|
|
6169
|
+
default: return;
|
|
6170
|
+
}
|
|
6171
|
+
}
|
|
6172
|
+
/**
|
|
6173
|
+
* One line describing the database.
|
|
6174
|
+
*
|
|
6175
|
+
* `connectionStatus` is written `"untested"` at creation and only ever changed
|
|
6176
|
+
* by `rebase cloud db test`, so `managed (untested)` was reporting the absence
|
|
6177
|
+
* of a manual test as though it were the database's condition — on a project
|
|
6178
|
+
* that had just deployed against it. A never-tested database says only its
|
|
6179
|
+
* type; the verdict appears once there is one.
|
|
6180
|
+
*/
|
|
6181
|
+
function describeDatabaseState(db) {
|
|
6182
|
+
if (!db) return void 0;
|
|
6183
|
+
const type = typeof db.type === "string" ? db.type : "database";
|
|
6184
|
+
const connection = db.connectionStatus;
|
|
6185
|
+
if (connection === "connected" || connection === "failed") return `${type} (${colorStatus(connection)})`;
|
|
6186
|
+
return `${type} ${chalk.gray("· not tested (`rebase cloud db test`)")}`;
|
|
6187
|
+
}
|
|
5941
6188
|
async function statusCommand(rawArgs) {
|
|
5942
6189
|
const { client, url } = await requireClient(rawArgs);
|
|
5943
6190
|
const projectId = await requireProject(rawArgs, client);
|
|
5944
6191
|
try {
|
|
5945
6192
|
const project = await client.data.collection("projects").findById(projectId);
|
|
5946
|
-
if (!project) fail(`Project ${displayProjectRef(rawArgs)} not found
|
|
6193
|
+
if (!project) fail(`Project ${displayProjectRef(rawArgs)} not found.`, void 0, "not_found");
|
|
5947
6194
|
const [db, storage, deploy, baseDomain] = await Promise.all([
|
|
5948
6195
|
firstRow(client, "databases", projectId),
|
|
5949
|
-
|
|
6196
|
+
client.functions.invoke("storage-provision", void 0, {
|
|
6197
|
+
method: "GET",
|
|
6198
|
+
path: projectId
|
|
6199
|
+
}).catch(() => void 0),
|
|
5950
6200
|
latestDeployment(client, projectId),
|
|
5951
6201
|
fetchTenantBaseDomain(client, url)
|
|
5952
6202
|
]);
|
|
5953
|
-
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
[
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
6203
|
+
const storageLine = describeStorageState(storage);
|
|
6204
|
+
const databaseLine = describeDatabaseState(db);
|
|
6205
|
+
emit(() => {
|
|
6206
|
+
console.log("");
|
|
6207
|
+
console.log(` ${chalk.bold(project.name ?? project.subdomain ?? "")} ${chalk.gray(`[${project.subdomain ?? displayProjectRef(rawArgs)}]`)} ${colorStatus(project.status)}`);
|
|
6208
|
+
console.log("");
|
|
6209
|
+
keyValues([
|
|
6210
|
+
["URL", projectHost(project, baseDomain)],
|
|
6211
|
+
["Branch", project.gitBranch],
|
|
6212
|
+
["Last deploy", deploy ? `${colorStatus(deploy.status)} · ${fmtDate(deploy.createdAt)}` : "never"],
|
|
6213
|
+
["Database", databaseLine],
|
|
6214
|
+
["Storage", storageLine]
|
|
6215
|
+
]);
|
|
6216
|
+
console.log("");
|
|
6217
|
+
}, {
|
|
6218
|
+
projectId: String(project.id),
|
|
6219
|
+
name: project.name ?? null,
|
|
6220
|
+
subdomain: project.subdomain ?? null,
|
|
6221
|
+
status: project.status ?? null,
|
|
6222
|
+
url: projectHost(project, baseDomain) ?? null,
|
|
6223
|
+
branch: project.gitBranch ?? null,
|
|
6224
|
+
lastDeploy: deploy ? {
|
|
6225
|
+
id: String(deploy.id),
|
|
6226
|
+
status: deploy.status ?? null,
|
|
6227
|
+
createdAt: deploy.createdAt ?? null
|
|
6228
|
+
} : null,
|
|
6229
|
+
database: db ? {
|
|
6230
|
+
type: db.type ?? null,
|
|
6231
|
+
connectionStatus: db.connectionStatus ?? null
|
|
6232
|
+
} : null,
|
|
6233
|
+
storage: storage ?? null
|
|
6234
|
+
});
|
|
5964
6235
|
} catch (e) {
|
|
5965
6236
|
reportError(e, "Failed to load status");
|
|
5966
6237
|
}
|
|
@@ -6471,9 +6742,9 @@ ${chalk.green.bold("Projects")}
|
|
|
6471
6742
|
${chalk.blue.bold("projects delete")} ${chalk.gray("[id]")} Delete a project
|
|
6472
6743
|
|
|
6473
6744
|
${chalk.green.bold("Deploy & observe")}
|
|
6474
|
-
${chalk.blue.bold("deploy")} ${chalk.gray("[--source .]")}
|
|
6745
|
+
${chalk.blue.bold("deploy")} ${chalk.gray("[--source .] [-m msg]")} Deploy the linked project + stream build logs
|
|
6475
6746
|
${chalk.blue.bold("logs")} ${chalk.gray("[--runtime] [-f]")} Show build (or runtime) logs
|
|
6476
|
-
${chalk.blue.bold("deployments list")}
|
|
6747
|
+
${chalk.blue.bold("deployments list")} ${chalk.gray("[--limit N|--all]")} Deployment history ${chalk.gray("(status, duration, trigger)")}
|
|
6477
6748
|
${chalk.blue.bold("rollback")} ${chalk.gray("[id] [-y]")} Roll back to a successful deploy
|
|
6478
6749
|
${chalk.blue.bold("cancel")} ${chalk.gray("[-y]")} Cancel the in-flight build
|
|
6479
6750
|
${chalk.blue.bold("start|stop|restart")} ${chalk.gray("[-y]")} Power ops ${chalk.gray("(stop/restart need -y)")}
|
|
@@ -6672,6 +6943,6 @@ ${chalk.gray("Documentation: https://rebase.pro/docs")}
|
|
|
6672
6943
|
`);
|
|
6673
6944
|
}
|
|
6674
6945
|
//#endregion
|
|
6675
|
-
export { authCommand, buildCommand, buildInitQuestions, cloudCommand, configureEnvFile, createRebaseApp, dbCommand, detectPackageManager, devCommand, doctorCommand, entry, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, formatCdTarget, generateSdkCommand, getActiveBackendPlugin, getPMCommands, isPnpmAvailable, printInitHelp, requireBackendDir, requireProjectRoot, resolveLocalBin, resolvePluginCliScript, resolveTsx, schemaCommand, startCommand, validateProjectName, validateTsxInstallation };
|
|
6946
|
+
export { authCommand, buildCommand, buildInitQuestions, cloudCommand, configureEnvFile, createRebaseApp, dbCommand, detectPackageManager, devCommand, doctorCommand, entry, findBackendDir, findEnvFile, findFrontendDir, findProjectRoot, formatCdTarget, generateSdkCommand, getActiveBackendPlugin, getPMCommands, isPnpmAvailable, pnpmAvailabilityFromProbe, printInitHelp, requireBackendDir, requireProjectRoot, resetPnpmAvailabilityCache, resolveLocalBin, resolvePluginCliScript, resolveTsx, schemaCommand, startCommand, validateProjectName, validateTsxInstallation };
|
|
6676
6947
|
|
|
6677
6948
|
//# sourceMappingURL=index.es.js.map
|