@cedarjs/pg 0.2.0-beta.0 → 0.3.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/README.md +112 -63
- package/dist/cli.cjs +15 -9
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +15 -9
- package/dist/cli.mjs.map +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +54 -11
- package/dist/index.d.mts +54 -11
- package/dist/index.mjs +3 -3
- package/dist/jest-teardown.cjs +1 -1
- package/dist/jest-teardown.mjs +1 -1
- package/dist/jest-template.cjs +5 -3
- package/dist/jest-template.cjs.map +1 -1
- package/dist/jest-template.d.cts +5 -3
- package/dist/jest-template.d.mts +5 -3
- package/dist/jest-template.mjs +5 -3
- package/dist/jest-template.mjs.map +1 -1
- package/dist/jest.cjs +1 -1
- package/dist/jest.mjs +1 -1
- package/dist/{lifecycle-BvIx0xqq.mjs → lifecycle-2BZgSVz2.mjs} +423 -132
- package/dist/lifecycle-2BZgSVz2.mjs.map +1 -0
- package/dist/{lifecycle-BbrvFQvg.cjs → lifecycle-BCeM96tI.cjs} +438 -135
- package/dist/lifecycle-BCeM96tI.cjs.map +1 -0
- package/dist/{naming-C2nGVxPk.d.cts → naming-Df4fz_Fb.d.cts} +3 -2
- package/dist/{naming-C2nGVxPk.d.mts → naming-Df4fz_Fb.d.mts} +3 -2
- package/dist/{status-Dcnpgnlz.mjs → status-B68SGSKx.mjs} +2 -2
- package/dist/{status-Dcnpgnlz.mjs.map → status-B68SGSKx.mjs.map} +1 -1
- package/dist/{status-4Uz6A3LB.cjs → status-Z_BYdxzI.cjs} +2 -2
- package/dist/{status-4Uz6A3LB.cjs.map → status-Z_BYdxzI.cjs.map} +1 -1
- package/dist/{studio-DIFw1qWC.mjs → studio-Bi6SU9jE.mjs} +54 -6
- package/dist/studio-Bi6SU9jE.mjs.map +1 -0
- package/dist/{studio-oJE30_PO.cjs → studio-CdWIdnxr.cjs} +78 -6
- package/dist/studio-CdWIdnxr.cjs.map +1 -0
- package/dist/{template-CXMpODzK.cjs → template-Cd77H7Cr.cjs} +21 -18
- package/dist/template-Cd77H7Cr.cjs.map +1 -0
- package/dist/{template-BiJ-0TIF.mjs → template-DlqHL8Go.mjs} +21 -18
- package/dist/template-DlqHL8Go.mjs.map +1 -0
- package/dist/template-mode-BqV3GjXI.d.cts +48 -0
- package/dist/template-mode-BqV3GjXI.d.mts +48 -0
- package/dist/template-mode-DK5cAlv9.mjs +81 -0
- package/dist/template-mode-DK5cAlv9.mjs.map +1 -0
- package/dist/template-mode-DMO5-z7O.cjs +92 -0
- package/dist/template-mode-DMO5-z7O.cjs.map +1 -0
- package/dist/vite-plus.cjs +9 -9
- package/dist/vite-plus.cjs.map +1 -1
- package/dist/vite-plus.d.cts +17 -2
- package/dist/vite-plus.d.mts +17 -2
- package/dist/vite-plus.mjs +5 -5
- package/dist/vite-plus.mjs.map +1 -1
- package/dist/vitest-template.cjs +6 -3
- package/dist/vitest-template.cjs.map +1 -1
- package/dist/vitest-template.d.cts +6 -3
- package/dist/vitest-template.d.mts +6 -3
- package/dist/vitest-template.mjs +6 -3
- package/dist/vitest-template.mjs.map +1 -1
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +1 -1
- package/package.json +2 -7
- package/scripts/autopg-version +1 -1
- package/scripts/ci-install-autopg.sh +7 -2
- package/scripts/postinstall.js +41 -13
- package/dist/lease-B3TuX92y.d.mts +0 -24
- package/dist/lease-t4I9JahV.d.cts +0 -24
- package/dist/lifecycle-BbrvFQvg.cjs.map +0 -1
- package/dist/lifecycle-BvIx0xqq.mjs.map +0 -1
- package/dist/nx.cjs +0 -42
- package/dist/nx.cjs.map +0 -1
- package/dist/nx.d.cts +0 -15
- package/dist/nx.d.mts +0 -15
- package/dist/nx.mjs +0 -34
- package/dist/nx.mjs.map +0 -1
- package/dist/studio-DIFw1qWC.mjs.map +0 -1
- package/dist/studio-oJE30_PO.cjs.map +0 -1
- package/dist/tasks-CNHvvlsN.cjs +0 -67
- package/dist/tasks-CNHvvlsN.cjs.map +0 -1
- package/dist/tasks-CjaZRi_G.d.mts +0 -19
- package/dist/tasks-D5iWIdlo.mjs +0 -38
- package/dist/tasks-D5iWIdlo.mjs.map +0 -1
- package/dist/tasks-DdQoP9We.d.cts +0 -19
- package/dist/template-BiJ-0TIF.mjs.map +0 -1
- package/dist/template-CXMpODzK.cjs.map +0 -1
- package/dist/template-mode-BTUsdBtA.mjs +0 -94
- package/dist/template-mode-BTUsdBtA.mjs.map +0 -1
- package/dist/template-mode-CzyVwHsi.d.cts +0 -31
- package/dist/template-mode-CzyVwHsi.d.mts +0 -31
- package/dist/template-mode-JTOxrEgE.cjs +0 -105
- package/dist/template-mode-JTOxrEgE.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Worktree-isolated local Postgres for Vite+, Nx, and CedarJS. The host is [autopg](https://github.com/automagik-dev/autopg) (embedded PostgreSQL 18). cedar-pg creates one database and role per git worktree so parallel checkouts do not share a DB.
|
|
4
4
|
|
|
5
|
-
Published as `@cedarjs/pg`. CLI: `cedarpg`.
|
|
5
|
+
Published as `@cedarjs/pg`. CLI: `cedarpg`. Pre-1.0 (`0.x`, `latest` dist-tag). Public APIs may still change between minor versions.
|
|
6
6
|
|
|
7
7
|
| Layer | Owns |
|
|
8
8
|
| -------- | --------------------------------------------------------------------------------- |
|
|
@@ -14,13 +14,15 @@ Published as `@cedarjs/pg`. CLI: `cedarpg`. Beta (`0.2.0-beta.0`, `beta` dist-ta
|
|
|
14
14
|
Node `>=24`.
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
|
-
npm install -D @cedarjs/pg
|
|
18
|
-
# pnpm add -D @cedarjs/pg
|
|
19
|
-
# yarn add -D @cedarjs/pg
|
|
17
|
+
npm install -D @cedarjs/pg
|
|
18
|
+
# pnpm add -D @cedarjs/pg
|
|
19
|
+
# yarn add -D @cedarjs/pg
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
`postinstall` installs the pinned autopg binary when it is missing. The pin is `scripts/autopg-version`. To bump autopg, change that file.
|
|
23
23
|
|
|
24
|
+
An autopg that is already installed is never upgraded for you, because upgrading restarts the host every worktree shares. When it is older than the pin, `postinstall` and the first host attach of each process (`acquire`, `cedarpg run`, test setup) print a warning with the upgrade commands. It never fails. `cedarpg run --attach` and skipped acquires (external URL, `CEDAR_PG=0`) do not check. To upgrade, run the install command below, then `autopg update` (autopg's own migrations; on its own it does not download a new binary). `scripts/ci-install-autopg.sh` and the GitHub Action replace any binary that is not exactly the pinned version, since CI hosts are ephemeral.
|
|
25
|
+
|
|
24
26
|
To install the host by hand (local, non-CI; upstream `install.sh` may use pm2):
|
|
25
27
|
|
|
26
28
|
```bash
|
|
@@ -45,6 +47,12 @@ vp pack
|
|
|
45
47
|
# or install the tarball vp pack writes (name includes the version in package.json)
|
|
46
48
|
```
|
|
47
49
|
|
|
50
|
+
Every push to `main` and every PR also builds a preview on [pkg.pr.new](https://pkg.pr.new) (not the npm registry):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pnpm add -D https://pkg.pr.new/@cedarjs/pg@<commit-sha-or-pr-number>
|
|
54
|
+
```
|
|
55
|
+
|
|
48
56
|
## Acquire a database
|
|
49
57
|
|
|
50
58
|
`dev` databases persist across restarts. `test` databases drop on `dispose`.
|
|
@@ -62,8 +70,9 @@ Worktree state lives in `.cedarpg`. Import `STATE_DIRNAME` instead of hardcoding
|
|
|
62
70
|
```bash
|
|
63
71
|
cedarpg acquire --mode=dev
|
|
64
72
|
cedarpg acquire --mode=test --print-env
|
|
65
|
-
cedarpg run --mode=dev --
|
|
73
|
+
cedarpg run --mode=dev -- prisma migrate deploy
|
|
66
74
|
cedarpg run --mode=test -- vitest run
|
|
75
|
+
cedarpg run --attach --mode=dev -- node dist/server.js # existing lease only, no DDL
|
|
67
76
|
cedarpg dispose --mode=test
|
|
68
77
|
cedarpg print-url --mode=dev
|
|
69
78
|
cedarpg status --mode=dev
|
|
@@ -73,9 +82,11 @@ cedarpg gc # drop DBs whose worktree root is gone
|
|
|
73
82
|
|
|
74
83
|
`status` and `studio` are read-only. They do not acquire. `studio` walks from cwd up to the worktree.
|
|
75
84
|
|
|
76
|
-
`cedarpg run` acquires
|
|
85
|
+
`cedarpg run` acquires (idempotent role and database DDL), then execs the command. The child always gets `DATABASE_URL` from the lease. In test mode it also gets `TEST_DATABASE_URL`. `--force` only sets `CEDAR_PG_FORCE=1`, so nested adapters do not treat an ambient URL as an escape hatch.
|
|
86
|
+
|
|
87
|
+
`cedarpg run --attach` skips the acquire. It reads the lease a prior `acquire` / `run` wrote, sets the same child env, and execs. It never runs DDL and never starts or revives the host. It fails (nonzero, before the child starts) when there is no lease or nothing is listening on the leased port.
|
|
77
88
|
|
|
78
|
-
If two targets run `cedarpg acquire` or `cedarpg run` on the same worktree, role and database DDL can race.
|
|
89
|
+
If two targets run `cedarpg acquire` or plain `cedarpg run` on the same worktree, role and database DDL can race. Acquire once, then wrap children with `run --attach`. Any number of those can run concurrently.
|
|
79
90
|
|
|
80
91
|
## Vite+
|
|
81
92
|
|
|
@@ -95,15 +106,17 @@ export default defineConfig({
|
|
|
95
106
|
env: ["DATABASE_URL", "TEST_DATABASE_URL"],
|
|
96
107
|
},
|
|
97
108
|
dev: {
|
|
98
|
-
command: "vp dev",
|
|
109
|
+
command: "cedarpg run --attach --mode=dev -- vp dev",
|
|
99
110
|
dependsOn: ["db:acquire"],
|
|
100
|
-
|
|
111
|
+
cache: false,
|
|
101
112
|
},
|
|
102
113
|
},
|
|
103
114
|
},
|
|
104
115
|
});
|
|
105
116
|
```
|
|
106
117
|
|
|
118
|
+
Like Nx `dependsOn`, Vite+ `dependsOn` does not forward env, and task `env` only fingerprints and passes through variables already set in the `vp` process. So `dev` acquires once via `db:acquire`, then `cedarpg run --attach` hands the lease `DATABASE_URL` to `vp dev`. `test` needs no wrapper: the stock Vitest setup acquires and sets the URL itself.
|
|
119
|
+
|
|
107
120
|
`cedarPgDev()` does not acquire. Keep `dependsOn: ["db:acquire"]`. On listen it prints a status panel (TTY, non-CI). Vite CLI shortcuts (`key` then Enter; also listed under `h`):
|
|
108
121
|
|
|
109
122
|
| Key | Action |
|
|
@@ -117,58 +130,84 @@ Shortcuts bind on Vite 8 and vite-plus. Vite 7 and Cedar print the listen panel
|
|
|
117
130
|
|
|
118
131
|
## Nx
|
|
119
132
|
|
|
120
|
-
Nx `dependsOn` does not forward env from
|
|
121
|
-
|
|
122
|
-
1. One `db:ready` (or `createAcquireTask`) that acquires and migrates.
|
|
123
|
-
2. Wrap API, dev, and e2e children with `cedarpg run --mode=dev --force -- <cmd>`.
|
|
124
|
-
|
|
125
|
-
Pointing Nx `envFile` at `.cedarpg/<mode>.env` after acquire still loses to an ambient `.env` unless you also force or overwrite.
|
|
126
|
-
|
|
127
|
-
```ts
|
|
128
|
-
import { cedarPgNxTargets, cedarPgRunCommand, relativeEnvFile } from "@cedarjs/pg/nx";
|
|
129
|
-
|
|
130
|
-
cedarPgNxTargets();
|
|
131
|
-
// { "db:acquire": { command: "cedarpg acquire --mode=dev", cache: false }, ... }
|
|
133
|
+
Nx `dependsOn` does not forward env from one target to the next, so the setup has two parts:
|
|
132
134
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
1. **One `db:ready` target** acquires the worktree database and migrates it. It is the only target that runs role/database DDL.
|
|
136
|
+
2. **Children** (`dev`, `serve`, workers, e2e) depend on `db:ready` and pick up the lease URL in one of two ways. They never acquire again: two acquires on the same worktree race DDL.
|
|
135
137
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
```json
|
|
138
|
+
```jsonc
|
|
139
|
+
// project.json (workspace root)
|
|
140
140
|
{
|
|
141
141
|
"targets": {
|
|
142
|
-
"db:ready": {
|
|
143
|
-
|
|
144
|
-
"
|
|
145
|
-
"command": "cedarpg run --mode=dev --force -- yarn tsx scripts/apiServer/dev.ts"
|
|
142
|
+
"db:ready": {
|
|
143
|
+
"command": "cedarpg run --mode=dev -- prisma migrate deploy",
|
|
144
|
+
"cache": false,
|
|
146
145
|
},
|
|
147
|
-
"
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
146
|
+
"db:status": { "command": "cedarpg status --mode=dev", "cache": false },
|
|
147
|
+
"db:url": { "command": "cedarpg print-url --mode=dev", "cache": false },
|
|
148
|
+
"db:studio": { "command": "cedarpg studio --mode=dev", "cache": false },
|
|
149
|
+
},
|
|
152
150
|
}
|
|
153
151
|
```
|
|
154
152
|
|
|
155
|
-
|
|
153
|
+
Swap in your migrate command (`drizzle-kit migrate`, …). Add `--force` (`cedarpg run --mode=dev --force -- …`) when a shell or `.env` `DATABASE_URL` would otherwise trip the [external-URL escape hatch](#environment-variables). For a migrate step written in TypeScript, call `createAcquireTask` from a script instead:
|
|
156
154
|
|
|
157
155
|
```ts
|
|
158
|
-
// tools/db-ready.ts
|
|
156
|
+
// tools/db-ready.ts → "command": "tsx tools/db-ready.ts"
|
|
159
157
|
import { createAcquireTask } from "@cedarjs/pg";
|
|
160
158
|
|
|
161
159
|
await createAcquireTask({
|
|
162
160
|
mode: "dev",
|
|
163
|
-
// Need this when .env already has DATABASE_URL
|
|
164
161
|
force: true,
|
|
165
162
|
afterAcquire: async ({ databaseUrl }) => {
|
|
166
|
-
//
|
|
163
|
+
// run your migrations against databaseUrl
|
|
167
164
|
},
|
|
168
165
|
})();
|
|
169
166
|
```
|
|
170
167
|
|
|
171
|
-
|
|
168
|
+
`db:status`, `db:url`, and `db:studio` are optional local helpers. They read the lease and never acquire. `studio` opens Prisma Studio or Drizzle Kit Studio, whichever it finds.
|
|
169
|
+
|
|
170
|
+
### Children: preload or attach
|
|
171
|
+
|
|
172
|
+
Pick one per target. Both give the child the `DATABASE_URL` that `db:ready` leased, overriding a `DATABASE_URL` from `.env`.
|
|
173
|
+
|
|
174
|
+
```jsonc
|
|
175
|
+
{
|
|
176
|
+
"targets": {
|
|
177
|
+
// Preload: Node loads .cedarpg/dev.env with overwrite before your code runs.
|
|
178
|
+
"serve": {
|
|
179
|
+
"dependsOn": ["db:ready"],
|
|
180
|
+
"command": "node --require @cedarjs/pg/dev-env dist/server.js",
|
|
181
|
+
},
|
|
182
|
+
"dev": {
|
|
183
|
+
"dependsOn": ["db:ready"],
|
|
184
|
+
"executor": "nx:run-commands",
|
|
185
|
+
"options": {
|
|
186
|
+
"command": "tsx watch src/server.ts",
|
|
187
|
+
"env": { "NODE_OPTIONS": "--require @cedarjs/pg/dev-env" },
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
// Attach: cedarpg sets the env, then execs any command (Node or not).
|
|
191
|
+
"worker": {
|
|
192
|
+
"dependsOn": ["db:ready"],
|
|
193
|
+
"command": "cedarpg run --attach --mode=dev -- node dist/worker.js",
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
- **Preload** (`@cedarjs/pg/dev-env`, or `loadDevEnv({ overwrite: true })` at the top of your entry) costs nothing extra but only works for Node processes. With no lease it is a no-op, and the process keeps its ambient URL.
|
|
200
|
+
- **Attach** (`cedarpg run --attach --mode=dev -- <cmd>`) works for any command. It reads the lease and checks the port, and fails before the child starts when either is missing. It never runs DDL and never starts the host, so any number of attached children can start at once.
|
|
201
|
+
|
|
202
|
+
Avoid Nx `envFile: ".cedarpg/dev.env"`. An ambient `.env` `DATABASE_URL` wins over it.
|
|
203
|
+
|
|
204
|
+
### Tests
|
|
205
|
+
|
|
206
|
+
Tests do not go through `db:ready`. The Jest TEMPLATE adapter acquires its own `test` lease, migrates once, clones per worker, and drops everything in teardown. Point Jest at it (see [TEMPLATE clones → Jest](#jest)) and make the Nx `test` target a plain runner command:
|
|
207
|
+
|
|
208
|
+
```jsonc
|
|
209
|
+
{ "targets": { "test": { "command": "jest --config jest.config.cjs" } } }
|
|
210
|
+
```
|
|
172
211
|
|
|
173
212
|
## Vitest and Jest
|
|
174
213
|
|
|
@@ -228,6 +267,14 @@ Migrate stays app-owned via `createGlobalSetup({ migrate })`. The adapter then m
|
|
|
228
267
|
|
|
229
268
|
Point `globalSetup` at a local module that calls `createGlobalSetup`. String-resolving the package entry without a migrate hook throws.
|
|
230
269
|
|
|
270
|
+
Each setup starts clean. Before migrate, `createGlobalSetup` drops every database owned by this worktree's test role: a TEMPLATE and worker clones left behind by a crashed or killed run, even if its lease file is gone. `migrate` therefore always runs against an empty database, and you do not need your own pre-cleanup. Only this worktree's `cpg_*_test_*` role is touched, never other databases on the shared host. Core API: `acquire({ mode: "test", fresh: true })`.
|
|
271
|
+
|
|
272
|
+
`cloneWorkerDatabase()` gives each worker one clone, `<template>_c_<JEST_WORKER_ID | VITEST_POOL_ID | pid>`, shared by every test file that worker runs. The first file creates it. Later files find it in Postgres and reuse it. Nothing is cached in memory, so this holds under Jest's per-file `globalThis` and module registry. If the clone name exists but belongs to a different role, the call fails with a clear error rather than reusing it.
|
|
273
|
+
|
|
274
|
+
Each file then starts from empty tables. By default `cloneWorkerDatabase()` runs one `TRUNCATE ... RESTART IDENTITY` over every user table in the clone, so rows from earlier files in the worker are gone and `serial` / identity IDs restart at 1. Tests that expect a first row with `id = 1` behave the same whichever file a worker runs first. It truncates on every call, the first file included, so rows that `migrate` seeded into the TEMPLATE (including bookkeeping tables such as `_prisma_migrations`) are cleared too. It skips system schemas, temp tables, and tables an extension owns (for example PostGIS `spatial_ref_sys`). If skip policy uses an external URL or `CEDAR_PG=0` is set, nothing is cloned and nothing is truncated.
|
|
275
|
+
|
|
276
|
+
To keep the clone as the previous file left it and reset on your own, pass `cloneWorkerDatabase({ reset: "none" })`. Resets between tests inside one file stay app-owned.
|
|
277
|
+
|
|
231
278
|
### Jest
|
|
232
279
|
|
|
233
280
|
```js
|
|
@@ -247,7 +294,8 @@ process.env.CEDAR_PG_FORCE = "1";
|
|
|
247
294
|
module.exports = {
|
|
248
295
|
globalSetup: "<rootDir>/jest.cedar-global.cjs",
|
|
249
296
|
globalTeardown: require.resolve("@cedarjs/pg/jest-teardown"),
|
|
250
|
-
//
|
|
297
|
+
// Runs once per test file; every file in a worker reuses that worker's clone,
|
|
298
|
+
// truncated with RESTART IDENTITY first.
|
|
251
299
|
setupFilesAfterEnv: ["<rootDir>/jest.cedar-worker.cjs"],
|
|
252
300
|
};
|
|
253
301
|
|
|
@@ -256,8 +304,6 @@ const { cloneWorkerDatabase } = require("@cedarjs/pg/jest/template");
|
|
|
256
304
|
beforeAll(() => cloneWorkerDatabase());
|
|
257
305
|
```
|
|
258
306
|
|
|
259
|
-
`cloneWorkerDatabase` memos on `globalThis`, so Jest `setupFiles` (module reload per file) still shares one clone per worker.
|
|
260
|
-
|
|
261
307
|
### Vitest
|
|
262
308
|
|
|
263
309
|
```ts
|
|
@@ -304,7 +350,9 @@ await dispose({ root: acquired.root, mode: "test" }); // TEMPLATE + all clones +
|
|
|
304
350
|
|
|
305
351
|
`acquire` returns `adminUrl` for migrate hooks and privileged DDL. `markTemplate` and `cloneFromTemplate` accept it, or rediscover the host when it is omitted. `cloneFromTemplate` uses the admin connection (`CREATE DATABASE ... TEMPLATE`). Test roles stay `LOGIN`-only.
|
|
306
352
|
|
|
307
|
-
`setEnv` defaults to false on `cloneFromTemplate`. It defaults to true on `cloneFromTemplateIfNeeded` (same as `acquireIfNeeded`). Worker adapters call `cloneFromTemplateIfNeeded` via `cloneWorkerDatabase`.
|
|
353
|
+
`setEnv` defaults to false on `cloneFromTemplate`. It defaults to true on `cloneFromTemplateIfNeeded` (same as `acquireIfNeeded`). Worker adapters call `cloneFromTemplateIfNeeded` via `cloneWorkerDatabase`, with `reuse: true`.
|
|
354
|
+
|
|
355
|
+
An explicit `name` that already exists fails with `database already exists` unless you pass `reuse: true`. Then a clone owned by the lease role is kept as is.
|
|
308
356
|
|
|
309
357
|
`dispose` is role-scoped suite teardown, not `dropClone`. It unsets `IS_TEMPLATE` and drops every database owned by the lease role.
|
|
310
358
|
|
|
@@ -312,7 +360,7 @@ await dispose({ root: acquired.root, mode: "test" }); // TEMPLATE + all clones +
|
|
|
312
360
|
|
|
313
361
|
`acquire` attaches when TCP accepts on the port from `autopg status --json`. Registration is not liveness. An installed-but-stopped host still reports a port.
|
|
314
362
|
|
|
315
|
-
When nothing is listening, cedar-pg brings up the registered local host. It runs `autopg restart`, then `autopg
|
|
363
|
+
When nothing is listening, cedar-pg brings up the registered local host. It runs `autopg restart`, then (if still dark) detached `autopg postmaster` on the registered port and `~/.autopg/data`. `autopg restart` (autopg ≥ v3.2) drives pm2 only and exits 0 once ready; without pm2, or under systemd-user / launchd, it exits 1 and cedar-pg moves on. Either way TCP accept is the gate. It does not run `autopg install` on an already-registered host (that path wants pm2). `install` is only for a never-registered machine. Same port, same data dir — not a second Postgres. The data dir and socket dir come from `autopg status --json`. They are never guessed: if autopg reports no `dataDir`, revive is skipped. If a live `postmaster.pid` already owns the data dir (pm2 still recovering, or a concurrent acquire), cedar-pg waits for that postmaster instead of starting a competitor. The revived postmaster has no supervisor and lives until reboot or crash. Its output is appended to `cedarpg-postmaster.log` in autopg's `logsDir` (`~/.autopg/logs`). If revive still produces no listener, `acquire` fails with what it tried.
|
|
316
364
|
|
|
317
365
|
Callers use `acquire` (and `adminUrl`). There is no public host-options object. Ephemeral behavior is env-driven.
|
|
318
366
|
|
|
@@ -323,12 +371,12 @@ import { acquire } from "@cedarjs/pg";
|
|
|
323
371
|
const { databaseUrl } = await acquire({ mode: "test" });
|
|
324
372
|
```
|
|
325
373
|
|
|
326
|
-
| Signal | Effect (attach always wins when something is listening)
|
|
327
|
-
| --------------------------- |
|
|
328
|
-
| `CEDAR_PG_EPHEMERAL_HOST=1` | Ephemeral owned postmaster on 55432
|
|
329
|
-
| `CEDAR_PG_EPHEMERAL_HOST=0` | Never
|
|
330
|
-
| unset and `CI=true` | Ephemeral
|
|
331
|
-
| unset | Local: `autopg restart`, then `
|
|
374
|
+
| Signal | Effect (attach always wins when something is listening) |
|
|
375
|
+
| --------------------------- | -------------------------------------------------------------------------------------------- |
|
|
376
|
+
| `CEDAR_PG_EPHEMERAL_HOST=1` | Ephemeral owned postmaster on 55432 |
|
|
377
|
+
| `CEDAR_PG_EPHEMERAL_HOST=0` | Never start the ephemeral 55432 postmaster (even in CI). Local registered host only, or fail |
|
|
378
|
+
| unset and `CI=true` | Ephemeral |
|
|
379
|
+
| unset | Local: `autopg restart`, then registered `postmaster` if still dark, then fail |
|
|
332
380
|
|
|
333
381
|
Ephemeral recipe (not configurable via cedar-pg):
|
|
334
382
|
|
|
@@ -337,7 +385,7 @@ Ephemeral recipe (not configurable via cedar-pg):
|
|
|
337
385
|
- Linux when `/dev/shm` exists: also `--ram` and `DIR=/dev/shm/cedar-pg-<uid>`
|
|
338
386
|
- Otherwise: disk `DIR` under the OS temp dir
|
|
339
387
|
- Ready when TCP accepts on the recipe port
|
|
340
|
-
- Before cold-start, if the recipe port is not live, cedar-pg
|
|
388
|
+
- Before cold-start, if the recipe port is not live, cedar-pg removes its own leftover data dir (`/dev/shm/cedar-pg-<uid>`, or `$TMPDIR/cedar-pg-host` without `--ram`) from an OOM-killed run. It never touches other `/dev/shm` entries: `PostgreSQL.*` segments belong to every running Postgres, the registered host included.
|
|
341
389
|
|
|
342
390
|
If TCP already accepts on the discovered autopg port, or in ephemeral mode on 55432, cedar-pg attaches and does not start another. The CI job owns ephemeral postmaster lifetime (runner teardown, `/dev/shm`). There is no cedar-pg host dispose API.
|
|
343
391
|
|
|
@@ -378,7 +426,7 @@ Yarn Berry or ignore-scripts, when you cannot use the Action. Requires a real `n
|
|
|
378
426
|
| `AUTOPG_PG_USER`, `AUTOPG_PG_PASSWORD` | Autopg superuser for admin URL (default user `postgres`, password `postgres`) |
|
|
379
427
|
| `CEDAR_PG=0` | Disable auto-acquire in adapters |
|
|
380
428
|
| `TEST_DATABASE_URL` | Default escape hatch for `acquireIfNeeded`. Skip acquire for a real external DB (not `cpg_*`, `file:`, or `{...}` or `<...>` placeholders). Callers may pass `url` (`DATABASE_URL` is common in dev) |
|
|
381
|
-
| `CEDAR_PG_FORCE=1` | Ignore the external-URL escape hatch (adapters, `cedarpg
|
|
429
|
+
| `CEDAR_PG_FORCE=1` | Ignore the external-URL escape hatch (adapters, `cedarpg run --force`) |
|
|
382
430
|
| `CEDAR_PG_EPHEMERAL_HOST` | `1` owned postmaster. `0` never own one (even in CI). Unset and `CI=true` means ephemeral |
|
|
383
431
|
| `CEDAR_PG_REGISTRY_DIR` | Override the global lease registry (for `gc`) |
|
|
384
432
|
| `CEDAR_PG_SKIP_POSTINSTALL=1` | Skip the autopg install hook |
|
|
@@ -414,12 +462,13 @@ vp run smoke:pg # pack, then Vitest and Jest adapters against real ephemeral
|
|
|
414
462
|
|
|
415
463
|
## Troubleshooting
|
|
416
464
|
|
|
417
|
-
| Symptom | Fix
|
|
418
|
-
| --------------------------------------------------------------------------------------- |
|
|
419
|
-
| `ECONNREFUSED 127.0.0.1:25432` on `cedarpg acquire` | autopg is registered but not listening. cedar-pg runs `autopg restart
|
|
420
|
-
| `database already exists: ..._c_<workerId>` in Jest |
|
|
421
|
-
| Acquire skipped. Tests hit shared or stale Postgres | A real `.env` `TEST_DATABASE_URL` (or a `url` the caller passed) trips the escape hatch. Set `CEDAR_PG_FORCE=1` once in `jest.config.js`, or `force: true`, or `cedarpg run --force`.
|
|
422
|
-
| `Disk quota exceeded` / `No space left on device` / Postgres `53100` on ephemeral start | Enlarge `/dev/shm` (`sudo mount -o remount,size=6G /dev/shm`).
|
|
423
|
-
| `autopg: command not found` in CI with Yarn `YARN_ENABLE_SCRIPTS=false` | `CEDAR_PG_INSTALL_AUTOPG=1` is not enough when lifecycle scripts are off. With `nodeLinker: node-modules`, run `bash node_modules/@cedarjs/pg/scripts/ci-install-autopg.sh` and put `~/.local/bin` on `PATH` (or use `setup-autopg`). PnP: resolve the script path via Yarn, or prefer the Action.
|
|
424
|
-
| Nx child still uses `.env` `DATABASE_URL` | `dependsOn` does not forward acquire env.
|
|
425
|
-
| Role or DB errors under parallel Nx targets |
|
|
465
|
+
| Symptom | Fix |
|
|
466
|
+
| --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
467
|
+
| `ECONNREFUSED 127.0.0.1:25432` on `cedarpg acquire` | autopg is registered but not listening. cedar-pg attaches only after TCP accepts. It runs `autopg restart`, then detached `autopg postmaster` on the registered port/data if still dark — including when pm2 is missing and `restart` exits 1. A revived postmaster logs to `~/.autopg/logs/cedarpg-postmaster.log`. `status=stopped` with `runtime.live=true` is healthy once TCP accepts; cedar-pg will not `install` pm2. If revive fails, the error lists what was tried. |
|
|
468
|
+
| `database already exists: ..._c_<workerId>` in Jest | Upgrade `@cedarjs/pg`. Older releases cached the clone on `globalThis`, which Jest resets for every test file. `cloneWorkerDatabase` now reuses the worker's clone from Postgres (`reuse: true`), and template setup drops crashed-run leftovers first. If the error says `owned by <other role>`, a database outside this worktree's lease has that name: drop it or pass another `name`. |
|
|
469
|
+
| Acquire skipped. Tests hit shared or stale Postgres | A real `.env` `TEST_DATABASE_URL` (or a `url` the caller passed) trips the escape hatch. Set `CEDAR_PG_FORCE=1` once in `jest.config.js`, or `force: true`, or `cedarpg run --force`. |
|
|
470
|
+
| `Disk quota exceeded` / `No space left on device` / Postgres `53100` on ephemeral start | Enlarge `/dev/shm` (`sudo mount -o remount,size=6G /dev/shm`). Cold-start already removes cedar-pg's own leftover `/dev/shm/cedar-pg-<uid>` when the recipe port is dead. Do not `rm /dev/shm/PostgreSQL.*`: a live Postgres (your registered host included) then fails every new connection with `58P01 could not open shared memory segment`, and only a restart of that host recovers it. |
|
|
471
|
+
| `autopg: command not found` in CI with Yarn `YARN_ENABLE_SCRIPTS=false` | `CEDAR_PG_INSTALL_AUTOPG=1` is not enough when lifecycle scripts are off. With `nodeLinker: node-modules`, run `bash node_modules/@cedarjs/pg/scripts/ci-install-autopg.sh` and put `~/.local/bin` on `PATH` (or use `setup-autopg`). PnP: resolve the script path via Yarn, or prefer the Action. |
|
|
472
|
+
| Nx child still uses `.env` `DATABASE_URL` | `dependsOn` does not forward acquire env. Preload `@cedarjs/pg/dev-env` (`node --require` / `NODE_OPTIONS`), or wrap with `cedarpg run --attach --mode=dev -- <cmd>`. Not Nx `envFile`. |
|
|
473
|
+
| Role or DB errors under parallel Nx targets | Children are running plain `cedarpg run` (or `acquire`), so each one runs DDL. Keep the acquire in one `db:ready`, and switch the children to `cedarpg run --attach`. |
|
|
474
|
+
| `no dev lease ...; attach never acquires` from `cedarpg run --attach` | Nothing has acquired this worktree yet. Make the target `dependsOn` your `db:ready` (or run `cedarpg acquire --mode=dev`). On `nothing is listening`, re-run `db:ready` / `acquire` to bring the host back; attach never starts it. |
|
package/dist/cli.cjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const require_lifecycle = require("./lifecycle-
|
|
2
|
+
const require_lifecycle = require("./lifecycle-BCeM96tI.cjs");
|
|
3
3
|
const require_lease = require("./lease-WlmOnNDi.cjs");
|
|
4
|
-
const require_status = require("./status-
|
|
5
|
-
const require_studio = require("./studio-
|
|
4
|
+
const require_status = require("./status-Z_BYdxzI.cjs");
|
|
5
|
+
const require_studio = require("./studio-CdWIdnxr.cjs");
|
|
6
6
|
//#region src/cli.ts
|
|
7
7
|
function printHelp() {
|
|
8
8
|
process.stdout.write(`${require_lease.CLI_NAME}: worktree-isolated local Postgres (via autopg)
|
|
9
9
|
|
|
10
10
|
Usage:
|
|
11
|
-
${require_lease.CLI_NAME} acquire --mode=dev|test [--root <path>] [--
|
|
12
|
-
${require_lease.CLI_NAME} run --mode=dev|test [--root <path>] [--force] -- <cmd…>
|
|
11
|
+
${require_lease.CLI_NAME} acquire --mode=dev|test [--root <path>] [--json] [--print-env]
|
|
12
|
+
${require_lease.CLI_NAME} run --mode=dev|test [--root <path>] [--force] [--attach] -- <cmd…>
|
|
13
13
|
${require_lease.CLI_NAME} dispose [--mode=dev|test] [--root <path>]
|
|
14
14
|
${require_lease.CLI_NAME} gc [--json]
|
|
15
15
|
${require_lease.CLI_NAME} print-url [--mode=dev|test] [--root <path>]
|
|
@@ -24,6 +24,9 @@ Modes:
|
|
|
24
24
|
run:
|
|
25
25
|
Acquire, set DATABASE_URL (+ TEST_DATABASE_URL in test) on the child, exec <cmd…>.
|
|
26
26
|
--force sets CEDAR_PG_FORCE (escape hatch); child env overwrite is always on.
|
|
27
|
+
--attach reads the existing lease instead: no acquire, no role/DB DDL, fails
|
|
28
|
+
when there is no lease or the host is not listening. Use it for children that
|
|
29
|
+
run after one db:ready (safe to run concurrently).
|
|
27
30
|
|
|
28
31
|
status / studio:
|
|
29
32
|
Read-only lease inspection (no acquire). studio runs Prisma or Drizzle Kit Studio
|
|
@@ -56,6 +59,7 @@ function parseArgs(argv) {
|
|
|
56
59
|
else if (a === "--json") out.json = true;
|
|
57
60
|
else if (a === "--print-env") out.printEnv = true;
|
|
58
61
|
else if (a === "--force") out.force = true;
|
|
62
|
+
else if (a === "--attach") out.attach = true;
|
|
59
63
|
else if (a === "--prisma") out.prisma = true;
|
|
60
64
|
else if (a === "--drizzle") out.drizzle = true;
|
|
61
65
|
else if (a.startsWith("--mode=")) out.mode = parseMode(a.slice(7));
|
|
@@ -141,7 +145,6 @@ async function main() {
|
|
|
141
145
|
}
|
|
142
146
|
try {
|
|
143
147
|
if (args.cmd === "acquire") {
|
|
144
|
-
if (args.force) process.env.CEDAR_PG_FORCE = "1";
|
|
145
148
|
const mode = args.mode ?? "dev";
|
|
146
149
|
const result = await require_lifecycle.acquire({
|
|
147
150
|
root: args.root,
|
|
@@ -173,16 +176,19 @@ async function main() {
|
|
|
173
176
|
if (args.cmd === "run") {
|
|
174
177
|
if (args.force) process.env.CEDAR_PG_FORCE = "1";
|
|
175
178
|
const mode = args.mode ?? "dev";
|
|
176
|
-
const
|
|
179
|
+
const { databaseUrl } = args.attach ? await require_lifecycle.attach({
|
|
180
|
+
root: args.root,
|
|
181
|
+
mode
|
|
182
|
+
}) : await require_lifecycle.acquire({
|
|
177
183
|
root: args.root,
|
|
178
184
|
mode,
|
|
179
185
|
setEnv: true
|
|
180
186
|
});
|
|
181
187
|
const childEnv = {
|
|
182
188
|
...process.env,
|
|
183
|
-
DATABASE_URL:
|
|
189
|
+
DATABASE_URL: databaseUrl
|
|
184
190
|
};
|
|
185
|
-
if (mode === "test") childEnv.TEST_DATABASE_URL =
|
|
191
|
+
if (mode === "test") childEnv.TEST_DATABASE_URL = databaseUrl;
|
|
186
192
|
return await runChild(args.child ?? [], childEnv);
|
|
187
193
|
}
|
|
188
194
|
if (args.cmd === "dispose") {
|
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.cjs","names":["CLI_NAME","runAttached","resolveDevStatus","formatDevStatus","detectStudio","runStudio","acquire","dispose","gc"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { CLI_NAME } from \"./core/constants.ts\";\nimport { acquire, dispose, gc } from \"./core/lifecycle.ts\";\nimport { resolveDevStatus } from \"./core/status.ts\";\nimport type { DbMode } from \"./core/naming.ts\";\nimport { runAttached } from \"./adapters/child.ts\";\nimport { formatDevStatus } from \"./adapters/status-format.ts\";\nimport { detectStudio, runStudio, type StudioKind } from \"./adapters/studio.ts\";\n\nfunction printHelp(): void {\n process.stdout.write(`${CLI_NAME}: worktree-isolated local Postgres (via autopg)\n\nUsage:\n ${CLI_NAME} acquire --mode=dev|test [--root <path>] [--force] [--json] [--print-env]\n ${CLI_NAME} run --mode=dev|test [--root <path>] [--force] -- <cmd…>\n ${CLI_NAME} dispose [--mode=dev|test] [--root <path>]\n ${CLI_NAME} gc [--json]\n ${CLI_NAME} print-url [--mode=dev|test] [--root <path>]\n ${CLI_NAME} status [--mode=dev|test] [--root <path>] [--json]\n ${CLI_NAME} studio [--mode=dev|test] [--root <path>] [--prisma|--drizzle]\n ${CLI_NAME} --help\n\nModes:\n dev Keep DB across restarts (default when --mode omitted)\n test Drop DB on dispose / test teardown\n\nrun:\n Acquire, set DATABASE_URL (+ TEST_DATABASE_URL in test) on the child, exec <cmd…>.\n --force sets CEDAR_PG_FORCE (escape hatch); child env overwrite is always on.\n\nstatus / studio:\n Read-only lease inspection (no acquire). studio runs Prisma or Drizzle Kit Studio\n attached (stdio + exit code) with DATABASE_URL from the lease. Detects from cwd\n up to the worktree (auto; --prisma / --drizzle to force).\n\nEnv:\n AUTOPG_BIN Path to autopg binary\n CEDAR_PG=0 Disable adapters that auto-acquire\n CEDAR_PG_FORCE=1 Ignore external-URL escape hatch (same as --force)\n`);\n}\n\nfunction parseMode(value: string | undefined): DbMode {\n if (value === \"dev\" || value === \"test\") return value;\n throw new Error(\"--mode must be dev or test\");\n}\n\ntype ParsedArgs = {\n cmd?: string;\n mode?: DbMode;\n root?: string;\n json?: boolean;\n printEnv?: boolean;\n force?: boolean;\n help?: boolean;\n prisma?: boolean;\n drizzle?: boolean;\n child?: string[];\n};\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const out: ParsedArgs = {};\n const rest = [...argv];\n out.cmd = rest.shift();\n\n const dash = rest.indexOf(\"--\");\n let flagArgs = rest;\n if (dash >= 0) {\n flagArgs = rest.slice(0, dash);\n out.child = rest.slice(dash + 1);\n }\n\n for (let i = 0; i < flagArgs.length; i++) {\n const a = flagArgs[i]!;\n if (a === \"--help\" || a === \"-h\") out.help = true;\n else if (a === \"--json\") out.json = true;\n else if (a === \"--print-env\") out.printEnv = true;\n else if (a === \"--force\") out.force = true;\n else if (a === \"--prisma\") out.prisma = true;\n else if (a === \"--drizzle\") out.drizzle = true;\n else if (a.startsWith(\"--mode=\")) out.mode = parseMode(a.slice(7));\n else if (a === \"--mode\") out.mode = parseMode(flagArgs[++i]);\n else if (a.startsWith(\"--root=\")) out.root = a.slice(7);\n else if (a === \"--root\") out.root = flagArgs[++i];\n else throw new Error(`unknown argument: ${a}`);\n }\n return out;\n}\n\nfunction studioPrefer(args: ParsedArgs): StudioKind | undefined {\n if (args.prisma && args.drizzle) {\n throw new Error(\"pass only one of --prisma or --drizzle\");\n }\n if (args.prisma) return \"prisma\";\n if (args.drizzle) return \"drizzle\";\n return undefined;\n}\n\nfunction writeLines(stream: NodeJS.WriteStream, lines: string[]): void {\n for (const line of lines) stream.write(`${line}\\n`);\n}\n\nfunction runChild(command: string[], env: NodeJS.ProcessEnv): Promise<number> {\n const [file, ...args] = command;\n if (!file) {\n throw new Error(\n `run requires a command after -- (e.g. ${CLI_NAME} run --mode=dev -- yarn dev)`,\n );\n }\n return runAttached(file, args, { env });\n}\n\nfunction cmdPrintUrl(args: ParsedArgs): number {\n const status = resolveDevStatus({ root: args.root, mode: args.mode });\n if (!status.ok) {\n writeLines(process.stderr, formatDevStatus(status));\n return 2;\n }\n process.stdout.write(`${status.databaseUrl}\\n`);\n return 0;\n}\n\nfunction cmdStatus(args: ParsedArgs): number {\n const status = resolveDevStatus({ root: args.root, mode: args.mode });\n if (args.json) {\n process.stdout.write(`${JSON.stringify(status, null, 2)}\\n`);\n return status.ok ? 0 : 2;\n }\n writeLines(process.stdout, formatDevStatus(status));\n return status.ok ? 0 : 2;\n}\n\nasync function cmdStudio(args: ParsedArgs): Promise<number> {\n const prefer = studioPrefer(args);\n const status = resolveDevStatus({ root: args.root, mode: args.mode });\n if (!status.ok) {\n writeLines(process.stderr, formatDevStatus(status));\n return 2;\n }\n const studio = detectStudio({ root: status.root, cwd: process.cwd(), prefer });\n if (!studio) {\n process.stderr.write(\n `${CLI_NAME}: no Prisma or Drizzle Studio found (install prisma or drizzle-kit)\\n`,\n );\n return 1;\n }\n process.stdout.write(\n `${CLI_NAME}: opening ${studio.kind} studio (${studio.command} ${studio.args.join(\" \")})\\n`,\n );\n return runStudio({ databaseUrl: status.databaseUrl, studio });\n}\n\nasync function main(): Promise<number> {\n const argv = process.argv.slice(2);\n if (argv.length === 0 || argv[0] === \"help\" || argv[0] === \"--help\" || argv[0] === \"-h\") {\n printHelp();\n return 0;\n }\n const args = parseArgs(argv);\n if (args.help) {\n printHelp();\n return 0;\n }\n\n try {\n if (args.cmd === \"acquire\") {\n if (args.force) process.env.CEDAR_PG_FORCE = \"1\";\n const mode = args.mode ?? \"dev\";\n const result = await acquire({\n root: args.root,\n mode,\n setEnv: true,\n });\n if (args.printEnv) {\n process.stdout.write(`DATABASE_URL=${result.databaseUrl}\\n`);\n if (mode === \"test\") {\n process.stdout.write(`TEST_DATABASE_URL=${result.databaseUrl}\\n`);\n }\n }\n if (args.json) {\n process.stdout.write(\n `${JSON.stringify(\n {\n databaseUrl: result.databaseUrl,\n adminUrl: result.adminUrl,\n databaseName: result.databaseName,\n roleName: result.roleName,\n repoSlug: result.repoSlug,\n worktreeSlug: result.worktreeSlug,\n pathHash: result.pathHash,\n mode: result.mode,\n port: result.port,\n root: result.root,\n },\n null,\n 2,\n )}\\n`,\n );\n } else if (!args.printEnv) {\n process.stdout.write(\n `${CLI_NAME}: ${result.databaseName} (${result.repoSlug}/${result.worktreeSlug} ${result.mode})\\n`,\n );\n process.stdout.write(`${result.databaseUrl}\\n`);\n }\n return 0;\n }\n\n if (args.cmd === \"run\") {\n if (args.force) process.env.CEDAR_PG_FORCE = \"1\";\n const mode = args.mode ?? \"dev\";\n const result = await acquire({\n root: args.root,\n mode,\n setEnv: true,\n });\n const childEnv: NodeJS.ProcessEnv = {\n ...process.env,\n DATABASE_URL: result.databaseUrl,\n };\n if (mode === \"test\") childEnv.TEST_DATABASE_URL = result.databaseUrl;\n return await runChild(args.child ?? [], childEnv);\n }\n\n if (args.cmd === \"dispose\") {\n const mode = args.mode ?? \"test\";\n const result = await dispose({ root: args.root, mode });\n if (result.dropped) {\n process.stdout.write(`${CLI_NAME}: disposed ${mode} (${result.databaseName})\\n`);\n return 0;\n }\n if (result.reason === \"no-lease\") {\n process.stdout.write(`${CLI_NAME}: nothing to dispose for ${mode} (no lease)\\n`);\n return 0;\n }\n process.stderr.write(\n `${CLI_NAME}: could not dispose ${mode}: autopg host unavailable (lease kept for retry)\\n`,\n );\n return 1;\n }\n\n if (args.cmd === \"gc\") {\n const result = await gc();\n if (args.json) {\n process.stdout.write(`${JSON.stringify(result)}\\n`);\n } else {\n process.stdout.write(`${CLI_NAME} gc: dropped ${result.dropped.length} database(s)\\n`);\n for (const name of result.dropped) process.stdout.write(` ${name}\\n`);\n }\n return 0;\n }\n\n if (args.cmd === \"print-url\") {\n return cmdPrintUrl(args);\n }\n\n if (args.cmd === \"status\") {\n return cmdStatus(args);\n }\n\n if (args.cmd === \"studio\") {\n return await cmdStudio(args);\n }\n\n process.stderr.write(`${CLI_NAME}: unknown command ${args.cmd}\\n`);\n printHelp();\n return 64;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`${CLI_NAME}: ${message}\\n`);\n return 1;\n }\n}\n\nmain()\n .then((code) => {\n process.exit(code);\n })\n .catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`${CLI_NAME}: ${message}\\n`);\n process.exit(1);\n });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.cjs","names":["CLI_NAME","runAttached","resolveDevStatus","formatDevStatus","detectStudio","runStudio","acquire","attach","dispose","gc"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { CLI_NAME } from \"./core/constants.ts\";\nimport { acquire, attach, dispose, gc } from \"./core/lifecycle.ts\";\nimport { resolveDevStatus } from \"./core/status.ts\";\nimport type { DbMode } from \"./core/naming.ts\";\nimport { runAttached } from \"./adapters/child.ts\";\nimport { formatDevStatus } from \"./adapters/status-format.ts\";\nimport { detectStudio, runStudio, type StudioKind } from \"./adapters/studio.ts\";\n\nfunction printHelp(): void {\n process.stdout.write(`${CLI_NAME}: worktree-isolated local Postgres (via autopg)\n\nUsage:\n ${CLI_NAME} acquire --mode=dev|test [--root <path>] [--json] [--print-env]\n ${CLI_NAME} run --mode=dev|test [--root <path>] [--force] [--attach] -- <cmd…>\n ${CLI_NAME} dispose [--mode=dev|test] [--root <path>]\n ${CLI_NAME} gc [--json]\n ${CLI_NAME} print-url [--mode=dev|test] [--root <path>]\n ${CLI_NAME} status [--mode=dev|test] [--root <path>] [--json]\n ${CLI_NAME} studio [--mode=dev|test] [--root <path>] [--prisma|--drizzle]\n ${CLI_NAME} --help\n\nModes:\n dev Keep DB across restarts (default when --mode omitted)\n test Drop DB on dispose / test teardown\n\nrun:\n Acquire, set DATABASE_URL (+ TEST_DATABASE_URL in test) on the child, exec <cmd…>.\n --force sets CEDAR_PG_FORCE (escape hatch); child env overwrite is always on.\n --attach reads the existing lease instead: no acquire, no role/DB DDL, fails\n when there is no lease or the host is not listening. Use it for children that\n run after one db:ready (safe to run concurrently).\n\nstatus / studio:\n Read-only lease inspection (no acquire). studio runs Prisma or Drizzle Kit Studio\n attached (stdio + exit code) with DATABASE_URL from the lease. Detects from cwd\n up to the worktree (auto; --prisma / --drizzle to force).\n\nEnv:\n AUTOPG_BIN Path to autopg binary\n CEDAR_PG=0 Disable adapters that auto-acquire\n CEDAR_PG_FORCE=1 Ignore external-URL escape hatch (same as --force)\n`);\n}\n\nfunction parseMode(value: string | undefined): DbMode {\n if (value === \"dev\" || value === \"test\") return value;\n throw new Error(\"--mode must be dev or test\");\n}\n\ntype ParsedArgs = {\n cmd?: string;\n mode?: DbMode;\n root?: string;\n json?: boolean;\n printEnv?: boolean;\n force?: boolean;\n attach?: boolean;\n help?: boolean;\n prisma?: boolean;\n drizzle?: boolean;\n child?: string[];\n};\n\nfunction parseArgs(argv: string[]): ParsedArgs {\n const out: ParsedArgs = {};\n const rest = [...argv];\n out.cmd = rest.shift();\n\n const dash = rest.indexOf(\"--\");\n let flagArgs = rest;\n if (dash >= 0) {\n flagArgs = rest.slice(0, dash);\n out.child = rest.slice(dash + 1);\n }\n\n for (let i = 0; i < flagArgs.length; i++) {\n const a = flagArgs[i]!;\n if (a === \"--help\" || a === \"-h\") out.help = true;\n else if (a === \"--json\") out.json = true;\n else if (a === \"--print-env\") out.printEnv = true;\n else if (a === \"--force\") out.force = true;\n else if (a === \"--attach\") out.attach = true;\n else if (a === \"--prisma\") out.prisma = true;\n else if (a === \"--drizzle\") out.drizzle = true;\n else if (a.startsWith(\"--mode=\")) out.mode = parseMode(a.slice(7));\n else if (a === \"--mode\") out.mode = parseMode(flagArgs[++i]);\n else if (a.startsWith(\"--root=\")) out.root = a.slice(7);\n else if (a === \"--root\") out.root = flagArgs[++i];\n else throw new Error(`unknown argument: ${a}`);\n }\n return out;\n}\n\nfunction studioPrefer(args: ParsedArgs): StudioKind | undefined {\n if (args.prisma && args.drizzle) {\n throw new Error(\"pass only one of --prisma or --drizzle\");\n }\n if (args.prisma) return \"prisma\";\n if (args.drizzle) return \"drizzle\";\n return undefined;\n}\n\nfunction writeLines(stream: NodeJS.WriteStream, lines: string[]): void {\n for (const line of lines) stream.write(`${line}\\n`);\n}\n\nfunction runChild(command: string[], env: NodeJS.ProcessEnv): Promise<number> {\n const [file, ...args] = command;\n if (!file) {\n throw new Error(\n `run requires a command after -- (e.g. ${CLI_NAME} run --mode=dev -- yarn dev)`,\n );\n }\n return runAttached(file, args, { env });\n}\n\nfunction cmdPrintUrl(args: ParsedArgs): number {\n const status = resolveDevStatus({ root: args.root, mode: args.mode });\n if (!status.ok) {\n writeLines(process.stderr, formatDevStatus(status));\n return 2;\n }\n process.stdout.write(`${status.databaseUrl}\\n`);\n return 0;\n}\n\nfunction cmdStatus(args: ParsedArgs): number {\n const status = resolveDevStatus({ root: args.root, mode: args.mode });\n if (args.json) {\n process.stdout.write(`${JSON.stringify(status, null, 2)}\\n`);\n return status.ok ? 0 : 2;\n }\n writeLines(process.stdout, formatDevStatus(status));\n return status.ok ? 0 : 2;\n}\n\nasync function cmdStudio(args: ParsedArgs): Promise<number> {\n const prefer = studioPrefer(args);\n const status = resolveDevStatus({ root: args.root, mode: args.mode });\n if (!status.ok) {\n writeLines(process.stderr, formatDevStatus(status));\n return 2;\n }\n const studio = detectStudio({ root: status.root, cwd: process.cwd(), prefer });\n if (!studio) {\n process.stderr.write(\n `${CLI_NAME}: no Prisma or Drizzle Studio found (install prisma or drizzle-kit)\\n`,\n );\n return 1;\n }\n process.stdout.write(\n `${CLI_NAME}: opening ${studio.kind} studio (${studio.command} ${studio.args.join(\" \")})\\n`,\n );\n return runStudio({ databaseUrl: status.databaseUrl, studio });\n}\n\nasync function main(): Promise<number> {\n const argv = process.argv.slice(2);\n if (argv.length === 0 || argv[0] === \"help\" || argv[0] === \"--help\" || argv[0] === \"-h\") {\n printHelp();\n return 0;\n }\n const args = parseArgs(argv);\n if (args.help) {\n printHelp();\n return 0;\n }\n\n try {\n if (args.cmd === \"acquire\") {\n const mode = args.mode ?? \"dev\";\n const result = await acquire({\n root: args.root,\n mode,\n setEnv: true,\n });\n if (args.printEnv) {\n process.stdout.write(`DATABASE_URL=${result.databaseUrl}\\n`);\n if (mode === \"test\") {\n process.stdout.write(`TEST_DATABASE_URL=${result.databaseUrl}\\n`);\n }\n }\n if (args.json) {\n process.stdout.write(\n `${JSON.stringify(\n {\n databaseUrl: result.databaseUrl,\n adminUrl: result.adminUrl,\n databaseName: result.databaseName,\n roleName: result.roleName,\n repoSlug: result.repoSlug,\n worktreeSlug: result.worktreeSlug,\n pathHash: result.pathHash,\n mode: result.mode,\n port: result.port,\n root: result.root,\n },\n null,\n 2,\n )}\\n`,\n );\n } else if (!args.printEnv) {\n process.stdout.write(\n `${CLI_NAME}: ${result.databaseName} (${result.repoSlug}/${result.worktreeSlug} ${result.mode})\\n`,\n );\n process.stdout.write(`${result.databaseUrl}\\n`);\n }\n return 0;\n }\n\n if (args.cmd === \"run\") {\n if (args.force) process.env.CEDAR_PG_FORCE = \"1\";\n const mode = args.mode ?? \"dev\";\n const { databaseUrl } = args.attach\n ? await attach({ root: args.root, mode })\n : await acquire({ root: args.root, mode, setEnv: true });\n const childEnv: NodeJS.ProcessEnv = {\n ...process.env,\n DATABASE_URL: databaseUrl,\n };\n if (mode === \"test\") childEnv.TEST_DATABASE_URL = databaseUrl;\n return await runChild(args.child ?? [], childEnv);\n }\n\n if (args.cmd === \"dispose\") {\n const mode = args.mode ?? \"test\";\n const result = await dispose({ root: args.root, mode });\n if (result.dropped) {\n process.stdout.write(`${CLI_NAME}: disposed ${mode} (${result.databaseName})\\n`);\n return 0;\n }\n if (result.reason === \"no-lease\") {\n process.stdout.write(`${CLI_NAME}: nothing to dispose for ${mode} (no lease)\\n`);\n return 0;\n }\n process.stderr.write(\n `${CLI_NAME}: could not dispose ${mode}: autopg host unavailable (lease kept for retry)\\n`,\n );\n return 1;\n }\n\n if (args.cmd === \"gc\") {\n const result = await gc();\n if (args.json) {\n process.stdout.write(`${JSON.stringify(result)}\\n`);\n } else {\n process.stdout.write(`${CLI_NAME} gc: dropped ${result.dropped.length} database(s)\\n`);\n for (const name of result.dropped) process.stdout.write(` ${name}\\n`);\n }\n return 0;\n }\n\n if (args.cmd === \"print-url\") {\n return cmdPrintUrl(args);\n }\n\n if (args.cmd === \"status\") {\n return cmdStatus(args);\n }\n\n if (args.cmd === \"studio\") {\n return await cmdStudio(args);\n }\n\n process.stderr.write(`${CLI_NAME}: unknown command ${args.cmd}\\n`);\n printHelp();\n return 64;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`${CLI_NAME}: ${message}\\n`);\n return 1;\n }\n}\n\nmain()\n .then((code) => {\n process.exit(code);\n })\n .catch((err: unknown) => {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`${CLI_NAME}: ${message}\\n`);\n process.exit(1);\n });\n"],"mappings":"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"}
|
package/dist/cli.mjs
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { i as gc, r as dispose, t as acquire } from "./lifecycle-BvIx0xqq.mjs";
|
|
3
2
|
import { p as CLI_NAME } from "./lease-DS1SX8U_.mjs";
|
|
4
|
-
import { t as
|
|
5
|
-
import {
|
|
3
|
+
import { a as gc, i as dispose, r as attach, t as acquire } from "./lifecycle-2BZgSVz2.mjs";
|
|
4
|
+
import { t as resolveDevStatus } from "./status-B68SGSKx.mjs";
|
|
5
|
+
import { a as formatDevStatus, r as runStudio, t as detectStudio, u as runAttached } from "./studio-Bi6SU9jE.mjs";
|
|
6
6
|
//#region src/cli.ts
|
|
7
7
|
function printHelp() {
|
|
8
8
|
process.stdout.write(`${CLI_NAME}: worktree-isolated local Postgres (via autopg)
|
|
9
9
|
|
|
10
10
|
Usage:
|
|
11
|
-
${CLI_NAME} acquire --mode=dev|test [--root <path>] [--
|
|
12
|
-
${CLI_NAME} run --mode=dev|test [--root <path>] [--force] -- <cmd…>
|
|
11
|
+
${CLI_NAME} acquire --mode=dev|test [--root <path>] [--json] [--print-env]
|
|
12
|
+
${CLI_NAME} run --mode=dev|test [--root <path>] [--force] [--attach] -- <cmd…>
|
|
13
13
|
${CLI_NAME} dispose [--mode=dev|test] [--root <path>]
|
|
14
14
|
${CLI_NAME} gc [--json]
|
|
15
15
|
${CLI_NAME} print-url [--mode=dev|test] [--root <path>]
|
|
@@ -24,6 +24,9 @@ Modes:
|
|
|
24
24
|
run:
|
|
25
25
|
Acquire, set DATABASE_URL (+ TEST_DATABASE_URL in test) on the child, exec <cmd…>.
|
|
26
26
|
--force sets CEDAR_PG_FORCE (escape hatch); child env overwrite is always on.
|
|
27
|
+
--attach reads the existing lease instead: no acquire, no role/DB DDL, fails
|
|
28
|
+
when there is no lease or the host is not listening. Use it for children that
|
|
29
|
+
run after one db:ready (safe to run concurrently).
|
|
27
30
|
|
|
28
31
|
status / studio:
|
|
29
32
|
Read-only lease inspection (no acquire). studio runs Prisma or Drizzle Kit Studio
|
|
@@ -56,6 +59,7 @@ function parseArgs(argv) {
|
|
|
56
59
|
else if (a === "--json") out.json = true;
|
|
57
60
|
else if (a === "--print-env") out.printEnv = true;
|
|
58
61
|
else if (a === "--force") out.force = true;
|
|
62
|
+
else if (a === "--attach") out.attach = true;
|
|
59
63
|
else if (a === "--prisma") out.prisma = true;
|
|
60
64
|
else if (a === "--drizzle") out.drizzle = true;
|
|
61
65
|
else if (a.startsWith("--mode=")) out.mode = parseMode(a.slice(7));
|
|
@@ -141,7 +145,6 @@ async function main() {
|
|
|
141
145
|
}
|
|
142
146
|
try {
|
|
143
147
|
if (args.cmd === "acquire") {
|
|
144
|
-
if (args.force) process.env.CEDAR_PG_FORCE = "1";
|
|
145
148
|
const mode = args.mode ?? "dev";
|
|
146
149
|
const result = await acquire({
|
|
147
150
|
root: args.root,
|
|
@@ -173,16 +176,19 @@ async function main() {
|
|
|
173
176
|
if (args.cmd === "run") {
|
|
174
177
|
if (args.force) process.env.CEDAR_PG_FORCE = "1";
|
|
175
178
|
const mode = args.mode ?? "dev";
|
|
176
|
-
const
|
|
179
|
+
const { databaseUrl } = args.attach ? await attach({
|
|
180
|
+
root: args.root,
|
|
181
|
+
mode
|
|
182
|
+
}) : await acquire({
|
|
177
183
|
root: args.root,
|
|
178
184
|
mode,
|
|
179
185
|
setEnv: true
|
|
180
186
|
});
|
|
181
187
|
const childEnv = {
|
|
182
188
|
...process.env,
|
|
183
|
-
DATABASE_URL:
|
|
189
|
+
DATABASE_URL: databaseUrl
|
|
184
190
|
};
|
|
185
|
-
if (mode === "test") childEnv.TEST_DATABASE_URL =
|
|
191
|
+
if (mode === "test") childEnv.TEST_DATABASE_URL = databaseUrl;
|
|
186
192
|
return await runChild(args.child ?? [], childEnv);
|
|
187
193
|
}
|
|
188
194
|
if (args.cmd === "dispose") {
|