@rebasepro/cli 0.20.0 → 0.21.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/dist/bundle.d.ts CHANGED
@@ -398,6 +398,8 @@ export declare function foldStaticIntoBundle(options: {
398
398
  path: string;
399
399
  /** Serve `index.html` for unmatched paths under `path`. */
400
400
  spa: boolean;
401
+ /** Where this app mounts the Rebase CMS, if it does. */
402
+ cms?: string;
401
403
  }): {
402
404
  fileCount: number;
403
405
  dir: string;
@@ -412,6 +414,8 @@ export declare function buildStaticBundle(options: {
412
414
  path?: string;
413
415
  /** Serve `index.html` for unmatched paths. Default `true`. */
414
416
  spa?: boolean;
417
+ /** Where this app mounts the Rebase CMS, if it does. */
418
+ cms?: string;
415
419
  }): {
416
420
  outDir: string;
417
421
  manifest: RebaseBundleManifest;
@@ -1,4 +1,30 @@
1
1
  import type { RebaseAppConfig } from "@rebasepro/types";
2
+ /**
3
+ * The two commands that turn a custom-runtime app into an image.
4
+ *
5
+ * Exported for the test, because the failure this replaced was in the printed
6
+ * text and nowhere else. It said:
7
+ *
8
+ * docker build -f backend/Dockerfile .
9
+ *
10
+ * for every custom backend, and the `.` was a guess — `context` was validated,
11
+ * stored on the config, and read by nothing. For the reference project that
12
+ * guess is wrong: `app/backend/Dockerfile` opens by copying `pnpm-lock.yaml`
13
+ * and `pnpm-workspace.yaml`, which live at the monorepo root, so the command
14
+ * `rebase build` handed you died on its first instruction. The deploy that
15
+ * actually works, `infra/cloudbuild.yaml`, has always said
16
+ * `-f app/backend/Dockerfile .` from the root.
17
+ *
18
+ * `dockerfile` is relative to `rebase.json`; `context` is too, and may point
19
+ * above it. Docker resolves `-f` against the working directory, not the
20
+ * context, so the path has to be re-expressed against wherever the command
21
+ * runs — which is what the old line never did and is the whole reason it
22
+ * could not be right for both.
23
+ */
24
+ export declare function dockerBuildHint(projectRoot: string, name: string, app: {
25
+ dockerfile?: string;
26
+ context?: string;
27
+ }): string[];
2
28
  export declare function buildCommand(rawArgs?: string[]): Promise<void>;
3
29
  /**
4
30
  * Build a static app and package it into a static bundle.
@@ -17,6 +17,35 @@ export declare function packBundle(bundleDir: string, outPath: string): Promise<
17
17
  * without unpacking the uploaded archive first — a rejection (native deps, no
18
18
  * matching runtime) is then a fast, cheap answer.
19
19
  */
20
+ /** The commit a bundle was built from, as far as the working directory knows. */
21
+ export interface BundleCommit {
22
+ hash: string;
23
+ message: string;
24
+ }
25
+ /**
26
+ * The commit HEAD is on, read here because here is the only place it exists.
27
+ *
28
+ * A bundle deploy has no repository anywhere near the control plane: the CLI
29
+ * builds a tarball and uploads it, so the three paths `deploy.ts` documents for
30
+ * learning a commit — clone, `ls-remote`, or "there is no repo at all" — all
31
+ * resolve to the third. Every bundle deployment therefore recorded an empty
32
+ * hash, which is 291 of the 305 rows in production: a Deployments list where
33
+ * almost nothing says what it shipped.
34
+ *
35
+ * But the CLI is standing IN the repository. `git -C <dir> log -1` answers
36
+ * exactly, message included — the one thing even the git-build path cannot get
37
+ * from `ls-remote`.
38
+ *
39
+ * Returns null rather than guessing, for every reason it can fail: no git, not a
40
+ * repository, no commits yet. The server records what it is given and nothing
41
+ * more, so null here stays `UNKNOWN_COMMIT_HASH` there.
42
+ *
43
+ * A dirty tree is NOT reported as a different commit. The bundle may contain
44
+ * uncommitted work, and the honest statement about that is "built from a tree at
45
+ * <hash>", not a fabricated identifier — the same rule the rest of this file
46
+ * follows about inventing values.
47
+ */
48
+ export declare function bundleCommit(cwd: string, run?: (args: string[]) => string): BundleCommit | null;
20
49
  export declare function bundleDeployBody(input: {
21
50
  projectId: string;
22
51
  bundleId: string;
@@ -28,6 +57,14 @@ export declare function bundleDeployBody(input: {
28
57
  * register the whole set rather than only the one being deployed.
29
58
  */
30
59
  declaredApps?: DeclaredApp[];
60
+ /**
61
+ * What HEAD said when this bundle was built, or null outside a repository.
62
+ *
63
+ * Omitted from the body entirely when null — an absent field and an empty
64
+ * one are the same to the server, and sending `""` would make "we did not
65
+ * look" indistinguishable from "we looked and there was nothing".
66
+ */
67
+ commit?: BundleCommit | null;
31
68
  }): Record<string, unknown>;
32
69
  /** An app as `rebase.json` declares it, reduced to what the registry stores. */
33
70
  export interface DeclaredApp {
@@ -1,6 +1,17 @@
1
1
  import arg from "arg";
2
2
  import { createRebaseClient } from "@rebasepro/client";
3
3
  /** Project-local link file: <project>/.rebase/cloud.json */
4
+ /**
5
+ * The billing account an organization row points at, whichever key it arrived under.
6
+ *
7
+ * REST serves every column under its own property name, so the relation comes
8
+ * back as `billingAccountId`. Both readers in this CLI checked
9
+ * `billing_account_id` and `billingAccount` — neither is what arrives — so
10
+ * `rebase cloud billing` answered "no billing account" for every organization,
11
+ * and the deploy pre-check never saw an internal plan. The other two spellings
12
+ * are still accepted: older servers and fixtures send them.
13
+ */
14
+ export declare function billingAccountIdOf(org: unknown): string | number | undefined;
4
15
  export declare function projectLinkPath(cwd?: string): string;
5
16
  /** Persist the active organization id for a host. */
6
17
  export declare function setContextOrg(url: string, org: string | undefined): void;
@@ -514,3 +525,46 @@ export declare function reportError(e: unknown, context: string): never;
514
525
  * rather than from a line that happens to end in a URL.
515
526
  */
516
527
  export declare function openUrl(target: string, label?: string): void;
528
+ /**
529
+ * The one thing every control-plane row is required to have.
530
+ *
531
+ * The subcommands each declare their own row shape — `OrgRow`, `ProjectRow`,
532
+ * `DatabaseRow`, `DeploymentRow` — and every one of them is an `id` plus
533
+ * fields that are all optional, because a control plane older than a given
534
+ * column simply does not send it. The reads are written that way too
535
+ * (`o.name ?? "(unnamed)"`, `row.slug ? … : ""`).
536
+ */
537
+ export interface CloudRow {
538
+ id: string | number;
539
+ }
540
+ /**
541
+ * Read the control plane's rows as a subcommand's row shape.
542
+ *
543
+ * `client.data.collection(...)` is typed against the *generated* schema of the
544
+ * project the SDK is pointed at, and the control-plane collections this CLI
545
+ * reads are not in one — so every row arrives as an open
546
+ * `Record<string, unknown>`. A declared `interface` gets no implicit index
547
+ * signature, so it does not overlap that, and a direct `as` is refused: which
548
+ * is how ten call sites came to write `as unknown as XRow[]`, an assertion
549
+ * about a wire payload with nothing checking it in either direction.
550
+ *
551
+ * There is exactly one invariant to check and this checks it. Everything else
552
+ * the shapes declare is optional and already read as such, so there is nothing
553
+ * further to verify — but a row with no usable `id` is not a row any of these
554
+ * commands can act on, and passing it through was how a listing came to print
555
+ * `[undefined]` and a lookup came to compare against the string `"undefined"`.
556
+ */
557
+ export declare function cloudRows<T extends CloudRow>(rows: readonly Record<string, unknown>[] | undefined): T[];
558
+ /** {@link cloudRows} for an endpoint that returns a single row. */
559
+ export declare function cloudRow<T extends CloudRow>(row: Record<string, unknown> | undefined): T | undefined;
560
+ /**
561
+ * {@link cloudRows} for a write that must have produced a row.
562
+ *
563
+ * `create()` returning something with no usable `id` means the control plane
564
+ * accepted the write and then described it in a way this CLI cannot act on.
565
+ * The callers all go straight on to use that id — `setContextOrg(url,
566
+ * String(created.id))` — so asserting the shape, which is what stood here,
567
+ * turned a control-plane fault into an organization whose active id is the
568
+ * seven-letter string `"undefined"`, stored in the user's config file.
569
+ */
570
+ export declare function requireCloudRow<T extends CloudRow>(row: Record<string, unknown> | undefined, what: string): T;
@@ -3,6 +3,8 @@
3
3
  *
4
4
  * db list List databases attached to the project
5
5
  * db create Attach a managed or bring-your-own database
6
+ * db info Where the database is, and what it is called
7
+ * db connect A local port that IS the project's database
6
8
  * db test Test connectivity to the project's database
7
9
  * db backup list|create|restore
8
10
  */
@@ -0,0 +1,70 @@
1
+ /**
2
+ * `rebase cloud db connect` — a local port that is your cloud database.
3
+ *
4
+ * ## What this replaces
5
+ *
6
+ * A managed database lives in a namespace of the platform's cluster, and its
7
+ * address (`postgres-rw.rebase-tenant-….svc.cluster.local`) resolves to nothing
8
+ * on a developer's machine. The console used to bridge that gap by printing
9
+ *
10
+ * kubectl port-forward svc/postgres-rw -n rebase-tenant-… 5432:5432
11
+ *
12
+ * which nobody outside the platform can run: a tenant of Rebase Cloud has no
13
+ * kubeconfig for our cluster, and there is no product that sells one. So the
14
+ * platform reaches into the cluster instead, and this command is the local end
15
+ * of that reach.
16
+ *
17
+ * ## How it works
18
+ *
19
+ * A listener on 127.0.0.1. Every TCP connection it accepts opens its own
20
+ * WebSocket to the control plane, authenticates in-band with the console session
21
+ * this CLI already holds, and from `ready` onwards the two are a byte pipe. The
22
+ * database still asks for a password — the tunnel is a network path, not a
23
+ * credential — so `psql` behaves exactly as it would against a local Postgres.
24
+ *
25
+ * One WebSocket per connection rather than one multiplexed socket: `psql` is one
26
+ * connection and a pool is a handful, and per-connection sockets keep the
27
+ * framing at "these bytes are those bytes" with no stream ids to get wrong.
28
+ *
29
+ * Node's global `WebSocket` is used rather than `ws`, which is why the token
30
+ * goes in the first frame instead of an `Authorization` header — the WHATWG
31
+ * client cannot set request headers, and a header-only endpoint would be
32
+ * unreachable from a browser too.
33
+ */
34
+ import net from "node:net";
35
+ /** Documented in `action-help.ts`, and paired with it by `action-help.test.ts`. */
36
+ export declare const DB_CONNECT_FLAGS: {
37
+ readonly "--port": NumberConstructor;
38
+ readonly "--reveal": BooleanConstructor;
39
+ };
40
+ /** `https://app.rebase.pro` → `wss://app.rebase.pro/api/db-tunnel/p1`. */
41
+ export declare function tunnelUrl(cloudUrl: string, projectId: string): string;
42
+ /**
43
+ * A local DSN for the tunnel, with the password only if the caller asked.
44
+ *
45
+ * Built here and nowhere else. The server reports the *cluster's* URI, which is
46
+ * the one thing that must not be printed as the way to connect — it is exactly
47
+ * the address that does not work from here.
48
+ */
49
+ export declare function localDsn(opts: {
50
+ port: number;
51
+ username: string | null;
52
+ database: string | null;
53
+ password?: string;
54
+ }): string;
55
+ export declare function dbConnect(rawArgs: string[]): Promise<void>;
56
+ /**
57
+ * One accepted connection, carried over one WebSocket.
58
+ *
59
+ * The local socket is paused until the tunnel says `ready`, so a client that
60
+ * sends its startup packet the instant it connects — which every Postgres
61
+ * client does — cannot have those bytes arrive before there is a database to
62
+ * send them to.
63
+ *
64
+ * Exported for `db-connect.pipe.test.ts`, which drives it with a real socket
65
+ * against a real WebSocket: this is the half a developer's client actually
66
+ * talks to, and a pipe is the one component whose bugs are silent — a dropped
67
+ * or reordered chunk does not throw, it corrupts a Postgres message and
68
+ * surfaces as a protocol error nowhere near here.
69
+ */
70
+ export declare function pipeThroughTunnel(socket: net.Socket, endpoint: string, token: string): void;
@@ -53,8 +53,8 @@ export declare const CREATE_PROJECT_FLAGS: {
53
53
  * means the two-command sequence that every project needs is one command,
54
54
  * and `--db none` is there for the case that genuinely wants to decide later.
55
55
  *
56
- * Distinct from `--db-mode`/`--db-cpu` next to it, which are resource dials
57
- * on a database that exists. This is whether there is one.
56
+ * Distinct from `--db-cpu`/`--db-instances` next to it, which are resource
57
+ * dials on a database that exists. This is whether there is one.
58
58
  */
59
59
  readonly "--db": StringConstructor;
60
60
  /** For `--db byodb`. Same spelling as `rebase cloud db create` uses. */
@@ -65,7 +65,6 @@ export declare const CREATE_PROJECT_FLAGS: {
65
65
  readonly "--replicas": StringConstructor;
66
66
  readonly "--spot": StringConstructor;
67
67
  readonly "--scale-to-zero": StringConstructor;
68
- readonly "--db-mode": StringConstructor;
69
68
  readonly "--db-instances": StringConstructor;
70
69
  readonly "--db-cpu": StringConstructor;
71
70
  readonly "--db-memory": StringConstructor;
@@ -56,7 +56,42 @@ export interface BuildQuestionsParams {
56
56
  * Exported for testability — all prompt `type` values must match
57
57
  * types registered by the installed version of inquirer.
58
58
  */
59
- export declare function buildInitQuestions(params: BuildQuestionsParams): Record<string, unknown>[];
59
+ /**
60
+ * The questions this command asks, as inquirer types them.
61
+ *
62
+ * Declared, rather than left as `Record<string, unknown>` and asserted into
63
+ * `Parameters<typeof inquirer.prompt>[0]` at the call. Inquirer infers the
64
+ * answer shape from the array it is handed, so an array of open records matches
65
+ * none of its overloads — which is the refusal that assertion was there to get
66
+ * past, at the cost of every question in this file being unchecked: a
67
+ * misspelled `mesage`, a `choices` on a `confirm`, a `default` of the wrong
68
+ * type for its question kind all compiled.
69
+ */
70
+ export type InitQuestion = {
71
+ type: "input";
72
+ name: string;
73
+ message: string;
74
+ default: string;
75
+ validate?: (input: string) => string | true;
76
+ } | {
77
+ type: "confirm";
78
+ name: string;
79
+ message: string;
80
+ default: boolean;
81
+ when?: (answers: Record<string, unknown>) => boolean;
82
+ } | {
83
+ type: "select";
84
+ name: string;
85
+ message: string;
86
+ choices: ReadonlyArray<{
87
+ name: string;
88
+ value: unknown;
89
+ short: string;
90
+ }>;
91
+ default: unknown;
92
+ when?: (answers: Record<string, unknown>) => boolean;
93
+ };
94
+ export declare function buildInitQuestions(params: BuildQuestionsParams): InitQuestion[];
60
95
  /**
61
96
  * The `cd` a user must type to enter the new project.
62
97
  *
@@ -6,6 +6,7 @@ export interface FoldableManifest {
6
6
  output?: string;
7
7
  path?: string;
8
8
  spa?: boolean;
9
+ cms?: string;
9
10
  }>;
10
11
  }
11
12
  export interface FoldOptions {
@@ -32,6 +33,8 @@ export interface FoldableApp {
32
33
  path: string;
33
34
  /** SPA fallback, defaulted to `true`. */
34
35
  spa: boolean;
36
+ /** Where this app mounts the Rebase CMS, if it does. */
37
+ cms?: string;
35
38
  }
36
39
  /**
37
40
  * Every static app in the manifest, in mount order.