@rebasepro/cli 0.16.0 → 0.16.1-canary.g041c925
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 +28 -2
- package/dist/commands/build.d.ts +10 -0
- package/dist/commands/cloud/deployments.d.ts +41 -0
- package/dist/commands/cloud/projects.d.ts +14 -4
- package/dist/commands/cloud/resources.d.ts +10 -1
- package/dist/commands/db.d.ts +16 -0
- package/dist/commands/dev.d.ts +11 -0
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/resources.d.ts +1 -0
- package/dist/constraints-BK1_4vci.js +80 -0
- package/dist/constraints-BK1_4vci.js.map +1 -0
- package/dist/daemon-Bdl4lrdt.js +252 -0
- package/dist/daemon-Bdl4lrdt.js.map +1 -0
- package/dist/daemon-entry-Brq-S8XX.js +378 -0
- package/dist/daemon-entry-Brq-S8XX.js.map +1 -0
- package/dist/dev-db/__fixtures__/cli-entry.d.ts +1 -0
- package/dist/dev-db/constraints.d.ts +98 -0
- package/dist/dev-db/daemon-entry.d.ts +35 -0
- package/dist/dev-db/daemon.d.ts +92 -0
- package/dist/dev-db/notification-proxy.d.ts +102 -0
- package/dist/dev-db/prepare.d.ts +63 -0
- package/dist/dev-db/pull.d.ts +92 -0
- package/dist/dev-db/resolve.d.ts +66 -0
- package/dist/dev-db/state.d.ts +93 -0
- package/dist/function-portability.d.ts +45 -0
- package/dist/index.d.ts +17 -17
- package/dist/index.es.js +5699 -4122
- package/dist/index.es.js.map +1 -1
- package/dist/manifest.d.ts +24 -1
- package/dist/pull-DqPRu1te.js +167 -0
- package/dist/pull-DqPRu1te.js.map +1 -0
- package/dist/resources/derive.d.ts +47 -0
- package/dist/resources/eject-infra-command.d.ts +7 -0
- package/dist/resources/eject-infra.d.ts +46 -0
- package/dist/state-c0CJ6Kwb.js +190 -0
- package/dist/state-c0CJ6Kwb.js.map +1 -0
- package/dist/telemetry/consent.d.ts +1 -1
- package/dist/telemetry/index.d.ts +7 -7
- package/dist/utils/dev-preflight.d.ts +73 -0
- package/package.json +13 -8
- package/templates/eject/backend/src/index.ts +15 -8
- package/templates/eject/config/resources.ts +24 -0
- package/templates/template/AGENTS.md +1 -1
- package/templates/template/CLAUDE.md +1 -1
- package/templates/template/README.md +1 -1
- package/templates/template/ai-instructions.md +5 -2
- package/templates/template/backend/functions/hello.ts +43 -22
- package/templates/template/docker-compose.yml +10 -1
- package/templates/template/gitignore +1 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A transparent Postgres proxy that puts LISTEN/NOTIFY back.
|
|
3
|
+
*
|
|
4
|
+
* Without this, realtime does not work against the managed database — and it
|
|
5
|
+
* fails silently, which is worse than failing. The reason is specific and
|
|
6
|
+
* measurable:
|
|
7
|
+
*
|
|
8
|
+
* PGlite is a *single* backend session, and `PGLiteSocketServer` multiplexes
|
|
9
|
+
* every client connection onto it. `LISTEN` is therefore session-wide: whichever
|
|
10
|
+
* client issues it arms the whole database. But a `NotificationResponse` is an
|
|
11
|
+
* asynchronous message with no request to answer, so the multiplexer hands it to
|
|
12
|
+
* whichever socket happens to be reading the protocol stream at that moment —
|
|
13
|
+
* which is the client that *caused* the notification, not the one that asked for
|
|
14
|
+
* it.
|
|
15
|
+
*
|
|
16
|
+
* Measured against pglite-socket 0.2.9:
|
|
17
|
+
*
|
|
18
|
+
* LISTEN and NOTIFY on one connection → delivered
|
|
19
|
+
* trigger-fired pg_notify, same connection → delivered
|
|
20
|
+
* another connection causes the notify → NOT delivered to the listener
|
|
21
|
+
* …and the same notification IS delivered to the notifier, which never asked
|
|
22
|
+
*
|
|
23
|
+
* The realtime engine listens on a dedicated connection and the writes come
|
|
24
|
+
* from request connections, so it is exactly the broken case, every time.
|
|
25
|
+
*
|
|
26
|
+
* The fix is to stop treating a notification as belonging to one connection,
|
|
27
|
+
* which for a single-session database is the truth anyway: this proxy watches
|
|
28
|
+
* the server→client direction, and every `NotificationResponse` frame it sees is
|
|
29
|
+
* copied to every other connected client. A client that never issued `LISTEN`
|
|
30
|
+
* may receive one it did not ask for; `pg` raises a `notification` event nobody
|
|
31
|
+
* has subscribed to, which costs nothing. A client that *did* ask now always
|
|
32
|
+
* gets it, which is the whole point.
|
|
33
|
+
*
|
|
34
|
+
* Two properties make this safe rather than clever:
|
|
35
|
+
*
|
|
36
|
+
* - **It never parses SQL and never rewrites a byte.** Frames are forwarded
|
|
37
|
+
* verbatim; the only edit is delivering a copy of one to more sockets.
|
|
38
|
+
* - **Injection only happens on a message boundary.** The server→client stream
|
|
39
|
+
* is reassembled into whole protocol messages before anything is written on,
|
|
40
|
+
* so an injected frame can never land inside another message.
|
|
41
|
+
*
|
|
42
|
+
* This exists only for the managed development database. Against a real Postgres
|
|
43
|
+
* there is no proxy, because there is no defect to correct.
|
|
44
|
+
*/
|
|
45
|
+
/**
|
|
46
|
+
* Reassembles a server→client byte stream into whole protocol messages.
|
|
47
|
+
*
|
|
48
|
+
* Every backend message is `Int8 type` + `Int32 length` + payload, where the
|
|
49
|
+
* length counts itself but not the type byte. Anything shorter than a full
|
|
50
|
+
* message is held until the rest arrives — TCP offers no guarantee that a
|
|
51
|
+
* message arrives in one chunk, and a proxy that assumed otherwise would inject
|
|
52
|
+
* into the middle of a row description under load.
|
|
53
|
+
*/
|
|
54
|
+
export declare class BackendMessageParser {
|
|
55
|
+
private buffered;
|
|
56
|
+
/** Set once the untyped SSL negotiation byte has been dealt with. */
|
|
57
|
+
private awaitingSslReply;
|
|
58
|
+
expectSslReply(): void;
|
|
59
|
+
/** Feed bytes in; get whole messages out, in order. */
|
|
60
|
+
push(chunk: Buffer): Buffer[];
|
|
61
|
+
/** Bytes held back because they are not yet a whole message. */
|
|
62
|
+
get pending(): number;
|
|
63
|
+
}
|
|
64
|
+
export declare function isNotificationFrame(message: Buffer): boolean;
|
|
65
|
+
/** Channel and payload of a NotificationResponse, for logging and tests. */
|
|
66
|
+
export declare function decodeNotification(message: Buffer): {
|
|
67
|
+
channel: string;
|
|
68
|
+
payload: string;
|
|
69
|
+
} | null;
|
|
70
|
+
export interface NotificationProxyOptions {
|
|
71
|
+
/** Port clients connect to. */
|
|
72
|
+
listenPort: number;
|
|
73
|
+
/** Port the real PGlite socket server is on. */
|
|
74
|
+
upstreamPort: number;
|
|
75
|
+
host?: string;
|
|
76
|
+
/** Called for every notification broadcast. For diagnostics and tests. */
|
|
77
|
+
onNotification?: (channel: string, payload: string, copies: number) => void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The proxy itself.
|
|
81
|
+
*
|
|
82
|
+
* One upstream connection per client connection, so the multiplexer downstream
|
|
83
|
+
* sees exactly what it would have seen without the proxy.
|
|
84
|
+
*/
|
|
85
|
+
export declare class NotificationProxy {
|
|
86
|
+
private readonly options;
|
|
87
|
+
private server;
|
|
88
|
+
private readonly connections;
|
|
89
|
+
constructor(options: NotificationProxyOptions);
|
|
90
|
+
get connectionCount(): number;
|
|
91
|
+
start(): Promise<void>;
|
|
92
|
+
private accept;
|
|
93
|
+
/**
|
|
94
|
+
* Copy a notification to every other client.
|
|
95
|
+
*
|
|
96
|
+
* Written directly rather than through a parser: it is already a whole
|
|
97
|
+
* message, and every other socket is only ever written whole messages, so
|
|
98
|
+
* there is no boundary to land inside.
|
|
99
|
+
*/
|
|
100
|
+
private broadcast;
|
|
101
|
+
stop(): Promise<void>;
|
|
102
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place a command asks "which database, and how do I reach it?".
|
|
3
|
+
*
|
|
4
|
+
* Every command that touches Postgres — `dev`, and the whole `db` namespace
|
|
5
|
+
* through the driver plugin — goes through {@link prepareDatabaseEnv}. It
|
|
6
|
+
* resolves the ordering in `resolve.ts`, starts the managed database if that is
|
|
7
|
+
* what the ordering chose, and hands back the environment additions the child
|
|
8
|
+
* process needs.
|
|
9
|
+
*
|
|
10
|
+
* Two things it deliberately does *not* do:
|
|
11
|
+
*
|
|
12
|
+
* - **It never overwrites an existing `DATABASE_URL`.** When the developer has
|
|
13
|
+
* named a database, the returned environment is empty and the child inherits
|
|
14
|
+
* exactly what it would have inherited before this feature existed. A
|
|
15
|
+
* migration must never be redirected away from the database its author meant.
|
|
16
|
+
*
|
|
17
|
+
* - **It never starts anything for a command that is not going to connect.**
|
|
18
|
+
* `--help` and argument errors are handled by the caller before this is
|
|
19
|
+
* reached, because booting a Postgres to print usage would be absurd.
|
|
20
|
+
*/
|
|
21
|
+
import { type DevDatabase } from "./resolve.js";
|
|
22
|
+
export interface PrepareOptions {
|
|
23
|
+
/** `--database-url <url>`. */
|
|
24
|
+
flagUrl?: string | null;
|
|
25
|
+
/** `--docker`. */
|
|
26
|
+
flagDocker?: boolean;
|
|
27
|
+
/** Suppress the "starting…" progress line. */
|
|
28
|
+
quiet?: boolean;
|
|
29
|
+
/** Where human-facing lines go. Defaults to stdout via the caller. */
|
|
30
|
+
onProgress?: (message: string) => void;
|
|
31
|
+
}
|
|
32
|
+
export interface PreparedDatabase {
|
|
33
|
+
/** What the resolver chose, for the banner and for tests. */
|
|
34
|
+
database: DevDatabase;
|
|
35
|
+
/**
|
|
36
|
+
* Variables to add to a child process's environment.
|
|
37
|
+
*
|
|
38
|
+
* Empty for an external database: the child already has what it needs, and
|
|
39
|
+
* adding to it could only do harm.
|
|
40
|
+
*/
|
|
41
|
+
env: Record<string, string>;
|
|
42
|
+
/** One line naming the database, suitable for a startup banner. */
|
|
43
|
+
description: string;
|
|
44
|
+
/** Absolute path of the managed data directory, when there is one. */
|
|
45
|
+
dataDir?: string;
|
|
46
|
+
/** True when this call started the managed database rather than finding it. */
|
|
47
|
+
startedDaemon?: boolean;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve, start if needed, and describe the database for this command.
|
|
51
|
+
*
|
|
52
|
+
* `projectRoot` is where the managed database's data lives, so two projects on
|
|
53
|
+
* one machine get two databases without either being told about the other.
|
|
54
|
+
*/
|
|
55
|
+
export declare function prepareDatabaseEnv(projectRoot: string, options?: PrepareOptions): Promise<PreparedDatabase>;
|
|
56
|
+
/**
|
|
57
|
+
* The lines to print about a managed database, in the order to print them.
|
|
58
|
+
*
|
|
59
|
+
* Returned rather than printed so the caller decides where they go — `dev` has
|
|
60
|
+
* a banner, `db push` has a single line above its own output — and so a test can
|
|
61
|
+
* assert on them without capturing a stream.
|
|
62
|
+
*/
|
|
63
|
+
export declare function managedNotices(prepared: PreparedDatabase): string[];
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `rebase db pull` — copy a database's contents into local development.
|
|
3
|
+
*
|
|
4
|
+
* The common case is production into local, and the reason it exists is that
|
|
5
|
+
* the alternative is worse: without it people hand-roll a `pg_dump | psql` and
|
|
6
|
+
* get the flags wrong in ways that either fail loudly at 2am or, more often,
|
|
7
|
+
* quietly restore half a schema.
|
|
8
|
+
*
|
|
9
|
+
* Three things this command insists on, because copying a production database
|
|
10
|
+
* onto a laptop is a data-protection event whether or not anyone calls it one:
|
|
11
|
+
*
|
|
12
|
+
* 1. **It says what it is about to do, in full, before doing it** — which
|
|
13
|
+
* database it will read, which one it will overwrite, and where the data will
|
|
14
|
+
* come to rest on disk. The target path matters: people forget that
|
|
15
|
+
* `.rebase/pgdata` is a directory their backup software may be indexing.
|
|
16
|
+
*
|
|
17
|
+
* 2. **It refuses to run unattended without being told to.** The target is
|
|
18
|
+
* destroyed, so a mistyped `--from` with no confirmation would take the
|
|
19
|
+
* developer's working database with it.
|
|
20
|
+
*
|
|
21
|
+
* 3. **It will not write to a remote database.** The target is always the local
|
|
22
|
+
* development database; there is no flag that makes this push. A tool that
|
|
23
|
+
* can copy in both directions eventually copies in the wrong one.
|
|
24
|
+
*
|
|
25
|
+
* Anonymization is opt-in (`--anonymize`), which is a deliberate choice and not
|
|
26
|
+
* an obviously safe one — the flag nobody types is the flag nobody gets. It is
|
|
27
|
+
* a best-effort pass over columns whose *names* look like personal data, and
|
|
28
|
+
* {@link ANONYMIZE_PATTERNS} says exactly which. It cannot find personal data in
|
|
29
|
+
* a column called `notes`, and this file says so rather than implying a
|
|
30
|
+
* guarantee it cannot keep.
|
|
31
|
+
*/
|
|
32
|
+
/**
|
|
33
|
+
* Column-name patterns the anonymizer overwrites.
|
|
34
|
+
*
|
|
35
|
+
* Names, not contents: inspecting values would be slower, and would still miss
|
|
36
|
+
* the same things. This is a reasonable-effort measure for making a local copy
|
|
37
|
+
* less dangerous, and it is not a compliance control.
|
|
38
|
+
*/
|
|
39
|
+
export declare const ANONYMIZE_PATTERNS: readonly {
|
|
40
|
+
pattern: RegExp;
|
|
41
|
+
replacement: string;
|
|
42
|
+
}[];
|
|
43
|
+
export declare function shouldAnonymize(columnName: string): boolean;
|
|
44
|
+
export declare function replacementFor(columnName: string): string | null;
|
|
45
|
+
/** A text-ish column the anonymizer can overwrite without a type error. */
|
|
46
|
+
export interface ColumnRef {
|
|
47
|
+
schema: string;
|
|
48
|
+
table: string;
|
|
49
|
+
column: string;
|
|
50
|
+
dataType: string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Anonymizable columns: name looks personal, and the type can hold the
|
|
54
|
+
* replacement.
|
|
55
|
+
*
|
|
56
|
+
* The type check is what stops this generating `UPDATE … SET user_id =
|
|
57
|
+
* 'Redacted'` for an integer column called `user_id_email_seq` and failing the
|
|
58
|
+
* whole pass on a technicality.
|
|
59
|
+
*/
|
|
60
|
+
export declare function anonymizableColumns(columns: readonly ColumnRef[]): ColumnRef[];
|
|
61
|
+
/** `UPDATE` statements for one anonymization pass, in a stable order. */
|
|
62
|
+
export declare function anonymizeStatements(columns: readonly ColumnRef[]): string[];
|
|
63
|
+
/** Host and database of a connection string, with no credentials in it. */
|
|
64
|
+
export declare function describeTarget(connectionString: string): string;
|
|
65
|
+
export interface PullPlan {
|
|
66
|
+
/** Where the data comes from. */
|
|
67
|
+
source: string;
|
|
68
|
+
/** Where it lands. Always local. */
|
|
69
|
+
target: string;
|
|
70
|
+
anonymize: boolean;
|
|
71
|
+
/** Schemas to copy. Empty means every non-system schema. */
|
|
72
|
+
schemas: string[];
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* `pg_dump` arguments for the source.
|
|
76
|
+
*
|
|
77
|
+
* `--no-owner` and `--no-acl` because the roles on a production server do not
|
|
78
|
+
* exist locally, and without them every `ALTER … OWNER TO` in the dump fails and
|
|
79
|
+
* buries the real output in noise. `--format=custom` so `pg_restore` can be told
|
|
80
|
+
* to continue past errors selectively rather than all-or-nothing.
|
|
81
|
+
*/
|
|
82
|
+
export declare function dumpArgs(plan: PullPlan): string[];
|
|
83
|
+
/**
|
|
84
|
+
* `pg_restore` arguments for the target.
|
|
85
|
+
*
|
|
86
|
+
* `--clean --if-exists` because a pull replaces what is there: restoring into a
|
|
87
|
+
* database that already has the tables would otherwise fail on every one of
|
|
88
|
+
* them. `--no-owner` for the same reason as the dump.
|
|
89
|
+
*/
|
|
90
|
+
export declare function restoreArgs(plan: PullPlan, dumpFile: string): string[];
|
|
91
|
+
/** Is `pg_dump` on PATH, and what version? Checked before anything destructive. */
|
|
92
|
+
export declare function findPgDump(): Promise<string | null>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which database a command should talk to, decided in one place.
|
|
3
|
+
*
|
|
4
|
+
* Before this existed every command that needed Postgres read `DATABASE_URL`
|
|
5
|
+
* for itself, which was fine while there was exactly one answer. Introducing a
|
|
6
|
+
* managed database makes the question real: a project may have no
|
|
7
|
+
* `DATABASE_URL` at all and still expect `rebase db push` to work, and a
|
|
8
|
+
* project that *does* set one must never be quietly redirected somewhere else.
|
|
9
|
+
*
|
|
10
|
+
* So the rule is ordered and boring, and the order is the promise:
|
|
11
|
+
*
|
|
12
|
+
* 1. `--database-url <url>` — said on this command line, wins over everything
|
|
13
|
+
* 2. `DATABASE_URL` in the shell environment
|
|
14
|
+
* 3. `DATABASE_URL` in the project's `.env`
|
|
15
|
+
* 4. `--docker` / a manifest preference of `docker`
|
|
16
|
+
* 5. the managed PGlite database
|
|
17
|
+
*
|
|
18
|
+
* An explicit connection string always wins. That is the whole point of the
|
|
19
|
+
* override: someone pointing Rebase at their own Postgres — a colleague's
|
|
20
|
+
* staging box, a Neon branch, a container they manage — must get exactly that,
|
|
21
|
+
* with no cleverness in between. The managed database is what fills the vacuum
|
|
22
|
+
* when nobody has said anything, and it is the only case where the CLI picks.
|
|
23
|
+
*
|
|
24
|
+
* {@link resolveDevDatabase} is pure: inputs in, decision out, no filesystem
|
|
25
|
+
* and no process. Reading `.env` and starting a daemon happen elsewhere, so
|
|
26
|
+
* the ordering above can be tested without either.
|
|
27
|
+
*/
|
|
28
|
+
/** Where the answer came from. Carried so diagnostics can name it. */
|
|
29
|
+
export type DevDatabaseSource =
|
|
30
|
+
/** `--database-url` on the command line. */
|
|
31
|
+
"flag"
|
|
32
|
+
/** `DATABASE_URL` in the shell environment. */
|
|
33
|
+
| "environment"
|
|
34
|
+
/** `DATABASE_URL` in the project's `.env`. */
|
|
35
|
+
| "env-file"
|
|
36
|
+
/** `--docker`, or `devDatabase: "docker"` in the manifest. */
|
|
37
|
+
| "docker"
|
|
38
|
+
/** Nobody said anything, so the managed database fills in. */
|
|
39
|
+
| "managed";
|
|
40
|
+
export type DevDatabase = {
|
|
41
|
+
kind: "external";
|
|
42
|
+
/** The connection string, exactly as given. Never rewritten. */
|
|
43
|
+
url: string;
|
|
44
|
+
source: Extract<DevDatabaseSource, "flag" | "environment" | "env-file">;
|
|
45
|
+
} | {
|
|
46
|
+
kind: "docker";
|
|
47
|
+
source: "docker";
|
|
48
|
+
} | {
|
|
49
|
+
kind: "managed";
|
|
50
|
+
source: "managed";
|
|
51
|
+
};
|
|
52
|
+
export interface ResolveDevDatabaseInput {
|
|
53
|
+
/** `--database-url <url>`, if given. */
|
|
54
|
+
flagUrl?: string | null;
|
|
55
|
+
/** `--docker`, if given. */
|
|
56
|
+
flagDocker?: boolean;
|
|
57
|
+
/** The shell environment. Only `DATABASE_URL` is read. */
|
|
58
|
+
env?: Record<string, string | undefined>;
|
|
59
|
+
/** Parsed `.env` from the project root. Only `DATABASE_URL` is read. */
|
|
60
|
+
envFile?: Record<string, string> | null;
|
|
61
|
+
/** `devDatabase` from `rebase.json`, if the project recorded a preference. */
|
|
62
|
+
manifestPreference?: "managed" | "docker" | null;
|
|
63
|
+
}
|
|
64
|
+
export declare function resolveDevDatabase(input?: ResolveDevDatabaseInput): DevDatabase;
|
|
65
|
+
/** One line for the startup banner, naming both the database and why. */
|
|
66
|
+
export declare function describeDevDatabase(database: DevDatabase): string;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The managed database's state file, and the rules for trusting it.
|
|
3
|
+
*
|
|
4
|
+
* The daemon outlives the command that started it — `rebase db push` in one
|
|
5
|
+
* terminal and `rebase dev` in another have to reach the *same* PGlite, because
|
|
6
|
+
* two processes opening one data directory would corrupt it. So the daemon
|
|
7
|
+
* records where it is, and every command reads that record.
|
|
8
|
+
*
|
|
9
|
+
* A record on disk is a claim, not a fact. The process it names may have been
|
|
10
|
+
* killed, the machine may have rebooted and handed the pid to something else,
|
|
11
|
+
* and the port may now belong to a stranger. {@link readState} therefore only
|
|
12
|
+
* parses; deciding whether a record is live is {@link isDaemonAlive}'s job, and
|
|
13
|
+
* it asks the daemon rather than the operating system.
|
|
14
|
+
*/
|
|
15
|
+
/** Everything under here is generated and gitignored. */
|
|
16
|
+
export declare const DEV_DB_DIR = ".rebase";
|
|
17
|
+
/** PGlite's own data directory. Deleting it is what `--reset` means. */
|
|
18
|
+
export declare const DATA_DIR_NAME = "pgdata";
|
|
19
|
+
/** The record the daemon writes once it is accepting connections. */
|
|
20
|
+
export declare const STATE_FILE_NAME = "pglite.json";
|
|
21
|
+
/**
|
|
22
|
+
* Held by whoever is currently starting a daemon.
|
|
23
|
+
*
|
|
24
|
+
* Without it, `rebase dev` and `rebase db push` started in the same second both
|
|
25
|
+
* see no state file, both spawn, and two processes open one PGlite data
|
|
26
|
+
* directory — the exact corruption the single-daemon design exists to prevent.
|
|
27
|
+
* Observed as `ENOTEMPTY` during cleanup, which is the harmless way for it to
|
|
28
|
+
* show up; the harmful way is a damaged database.
|
|
29
|
+
*/
|
|
30
|
+
export declare const START_LOCK_NAME = "starting.lock";
|
|
31
|
+
export interface DaemonState {
|
|
32
|
+
/** TCP port the socket server is listening on, chosen when it started. */
|
|
33
|
+
port: number;
|
|
34
|
+
/** The daemon process. Used only as a fast negative check. */
|
|
35
|
+
pid: number;
|
|
36
|
+
/** Absolute path of the PGlite data directory this daemon has open. */
|
|
37
|
+
dataDir: string;
|
|
38
|
+
/** ISO timestamp, for diagnostics. */
|
|
39
|
+
startedAt: string;
|
|
40
|
+
/**
|
|
41
|
+
* A random token the daemon also answers with over the wire, on
|
|
42
|
+
* {@link identityPort}.
|
|
43
|
+
*
|
|
44
|
+
* Without it, "is the daemon alive?" degrades to "is something listening on
|
|
45
|
+
* that port?", which is a different question and answers yes for whatever
|
|
46
|
+
* process happened to take the port after a reboot. Rebase would then send
|
|
47
|
+
* migrations to a stranger.
|
|
48
|
+
*/
|
|
49
|
+
token: string;
|
|
50
|
+
/** Loopback port that answers the identity check. */
|
|
51
|
+
identityPort: number;
|
|
52
|
+
}
|
|
53
|
+
export declare function devDbDir(projectRoot: string): string;
|
|
54
|
+
export declare function dataDir(projectRoot: string): string;
|
|
55
|
+
export declare function stateFile(projectRoot: string): string;
|
|
56
|
+
export declare function startLockFile(projectRoot: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Take the start lock, or report that somebody else holds it.
|
|
59
|
+
*
|
|
60
|
+
* `wx` is the whole mechanism: create-if-absent is a single atomic syscall, so
|
|
61
|
+
* exactly one of two racing processes can succeed no matter how close together
|
|
62
|
+
* they arrive.
|
|
63
|
+
*
|
|
64
|
+
* A lock older than `staleAfterMs` is broken rather than waited on — the holder
|
|
65
|
+
* may have been killed between creating it and starting anything, and a
|
|
66
|
+
* developer should never have to know this file exists in order to unstick
|
|
67
|
+
* their project.
|
|
68
|
+
*/
|
|
69
|
+
export declare function acquireStartLock(projectRoot: string, staleAfterMs: number): boolean;
|
|
70
|
+
export declare function releaseStartLock(projectRoot: string): void;
|
|
71
|
+
/**
|
|
72
|
+
* Parse the record, or `null` for anything that is not one.
|
|
73
|
+
*
|
|
74
|
+
* Every failure is the same answer — absent — because every failure has the
|
|
75
|
+
* same remedy: start a daemon. A corrupt state file is not worth an error
|
|
76
|
+
* message to a user who never wrote it.
|
|
77
|
+
*/
|
|
78
|
+
export declare function readState(projectRoot: string): DaemonState | null;
|
|
79
|
+
export declare function writeState(projectRoot: string, state: DaemonState): void;
|
|
80
|
+
export declare function clearState(projectRoot: string): void;
|
|
81
|
+
/** Is *some* process with this pid running? A fast, cheap negative check. */
|
|
82
|
+
export declare function pidRunning(pid: number): boolean;
|
|
83
|
+
/** Can a TCP connection be opened to this port on loopback? */
|
|
84
|
+
export declare function portAccepting(port: number, timeoutMs?: number): Promise<boolean>;
|
|
85
|
+
/**
|
|
86
|
+
* Ask a port for a free one, then hand back the number.
|
|
87
|
+
*
|
|
88
|
+
* Deliberately not the probe `rebase init` uses: that one has a documented
|
|
89
|
+
* failure where a port is free to probe and unusable to publish. This binds on
|
|
90
|
+
* loopback only, which is also where the daemon listens, so a port that binds
|
|
91
|
+
* here binds there.
|
|
92
|
+
*/
|
|
93
|
+
export declare function findFreePort(): Promise<number>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** What a single finding is about. */
|
|
2
|
+
export type PortabilityIssueKind = "node-builtin" | "node-only-package" | "module-scope-env" | "root-barrel-import" | "unknown-package";
|
|
3
|
+
export interface PortabilityIssue {
|
|
4
|
+
kind: PortabilityIssueKind;
|
|
5
|
+
/** 1-based line in the function file. */
|
|
6
|
+
line: number;
|
|
7
|
+
/** One sentence, already phrased for a terminal. */
|
|
8
|
+
message: string;
|
|
9
|
+
/**
|
|
10
|
+
* Whether this is a problem *today*, on Node, rather than only a constraint
|
|
11
|
+
* on where the function could run. Exactly one kind is:
|
|
12
|
+
* `module-scope-env`, which is a latent crash on any host where a variable
|
|
13
|
+
* happens to be unset at import time, and takes every other function in the
|
|
14
|
+
* same directory down with it — the loader reports a file that throws on
|
|
15
|
+
* import as simply "skipped".
|
|
16
|
+
*/
|
|
17
|
+
actionable: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface FunctionPortability {
|
|
20
|
+
/** Function name — the filename without extension, as it mounts. */
|
|
21
|
+
name: string;
|
|
22
|
+
/** Path relative to the project root. */
|
|
23
|
+
file: string;
|
|
24
|
+
/** Empty when the function depends on nothing host-specific. */
|
|
25
|
+
issues: PortabilityIssue[];
|
|
26
|
+
/** True when `issues` contains nothing that pins it to Node. */
|
|
27
|
+
portable: boolean;
|
|
28
|
+
}
|
|
29
|
+
/** Analyse one function file's source. Exported for testing. */
|
|
30
|
+
export declare function analyseFunctionSource(source: string, name: string, file: string): FunctionPortability;
|
|
31
|
+
/**
|
|
32
|
+
* Analyse every function in a directory.
|
|
33
|
+
*
|
|
34
|
+
* @param functionsDir Absolute path to the functions directory.
|
|
35
|
+
* @param projectRoot For rendering paths people recognise.
|
|
36
|
+
*/
|
|
37
|
+
export declare function analyseFunctionsDirectory(functionsDir: string, projectRoot: string): FunctionPortability[];
|
|
38
|
+
/**
|
|
39
|
+
* The lines to print after a build, or nothing at all.
|
|
40
|
+
*
|
|
41
|
+
* Silent when every function is portable and nothing is actionable, because a
|
|
42
|
+
* report that always prints is a report nobody reads. Actionable findings are
|
|
43
|
+
* always shown; the rest collapse to a single count with a pointer.
|
|
44
|
+
*/
|
|
45
|
+
export declare function summarisePortability(results: FunctionPortability[]): string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
export * from "./cli";
|
|
2
|
-
export * from "./commands/init";
|
|
3
|
-
export * from "./commands/schema";
|
|
4
|
-
export * from "./commands/db";
|
|
5
|
-
export * from "./commands/dev";
|
|
6
|
-
export * from "./commands/build";
|
|
7
|
-
export * from "./commands/eject";
|
|
8
|
-
export * from "./commands/start";
|
|
9
|
-
export * from "./commands/auth";
|
|
10
|
-
export * from "./commands/doctor";
|
|
11
|
-
export * from "./commands/generate_sdk";
|
|
12
|
-
export * from "./commands/cloud";
|
|
13
|
-
export * from "./commands/apps";
|
|
14
|
-
export * from "./utils/project";
|
|
15
|
-
export * from "./utils/package-manager";
|
|
16
|
-
export * from "./manifest";
|
|
17
|
-
export * from "./bundle";
|
|
1
|
+
export * from "./cli.js";
|
|
2
|
+
export * from "./commands/init.js";
|
|
3
|
+
export * from "./commands/schema.js";
|
|
4
|
+
export * from "./commands/db.js";
|
|
5
|
+
export * from "./commands/dev.js";
|
|
6
|
+
export * from "./commands/build.js";
|
|
7
|
+
export * from "./commands/eject.js";
|
|
8
|
+
export * from "./commands/start.js";
|
|
9
|
+
export * from "./commands/auth.js";
|
|
10
|
+
export * from "./commands/doctor.js";
|
|
11
|
+
export * from "./commands/generate_sdk.js";
|
|
12
|
+
export * from "./commands/cloud/index.js";
|
|
13
|
+
export * from "./commands/apps.js";
|
|
14
|
+
export * from "./utils/project.js";
|
|
15
|
+
export * from "./utils/package-manager.js";
|
|
16
|
+
export * from "./manifest.js";
|
|
17
|
+
export * from "./bundle.js";
|