@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.
Files changed (87) hide show
  1. package/README.md +112 -63
  2. package/dist/cli.cjs +15 -9
  3. package/dist/cli.cjs.map +1 -1
  4. package/dist/cli.mjs +15 -9
  5. package/dist/cli.mjs.map +1 -1
  6. package/dist/index.cjs +3 -3
  7. package/dist/index.d.cts +54 -11
  8. package/dist/index.d.mts +54 -11
  9. package/dist/index.mjs +3 -3
  10. package/dist/jest-teardown.cjs +1 -1
  11. package/dist/jest-teardown.mjs +1 -1
  12. package/dist/jest-template.cjs +5 -3
  13. package/dist/jest-template.cjs.map +1 -1
  14. package/dist/jest-template.d.cts +5 -3
  15. package/dist/jest-template.d.mts +5 -3
  16. package/dist/jest-template.mjs +5 -3
  17. package/dist/jest-template.mjs.map +1 -1
  18. package/dist/jest.cjs +1 -1
  19. package/dist/jest.mjs +1 -1
  20. package/dist/{lifecycle-BvIx0xqq.mjs → lifecycle-2BZgSVz2.mjs} +423 -132
  21. package/dist/lifecycle-2BZgSVz2.mjs.map +1 -0
  22. package/dist/{lifecycle-BbrvFQvg.cjs → lifecycle-BCeM96tI.cjs} +438 -135
  23. package/dist/lifecycle-BCeM96tI.cjs.map +1 -0
  24. package/dist/{naming-C2nGVxPk.d.cts → naming-Df4fz_Fb.d.cts} +3 -2
  25. package/dist/{naming-C2nGVxPk.d.mts → naming-Df4fz_Fb.d.mts} +3 -2
  26. package/dist/{status-Dcnpgnlz.mjs → status-B68SGSKx.mjs} +2 -2
  27. package/dist/{status-Dcnpgnlz.mjs.map → status-B68SGSKx.mjs.map} +1 -1
  28. package/dist/{status-4Uz6A3LB.cjs → status-Z_BYdxzI.cjs} +2 -2
  29. package/dist/{status-4Uz6A3LB.cjs.map → status-Z_BYdxzI.cjs.map} +1 -1
  30. package/dist/{studio-DIFw1qWC.mjs → studio-Bi6SU9jE.mjs} +54 -6
  31. package/dist/studio-Bi6SU9jE.mjs.map +1 -0
  32. package/dist/{studio-oJE30_PO.cjs → studio-CdWIdnxr.cjs} +78 -6
  33. package/dist/studio-CdWIdnxr.cjs.map +1 -0
  34. package/dist/{template-CXMpODzK.cjs → template-Cd77H7Cr.cjs} +21 -18
  35. package/dist/template-Cd77H7Cr.cjs.map +1 -0
  36. package/dist/{template-BiJ-0TIF.mjs → template-DlqHL8Go.mjs} +21 -18
  37. package/dist/template-DlqHL8Go.mjs.map +1 -0
  38. package/dist/template-mode-BqV3GjXI.d.cts +48 -0
  39. package/dist/template-mode-BqV3GjXI.d.mts +48 -0
  40. package/dist/template-mode-DK5cAlv9.mjs +81 -0
  41. package/dist/template-mode-DK5cAlv9.mjs.map +1 -0
  42. package/dist/template-mode-DMO5-z7O.cjs +92 -0
  43. package/dist/template-mode-DMO5-z7O.cjs.map +1 -0
  44. package/dist/vite-plus.cjs +9 -9
  45. package/dist/vite-plus.cjs.map +1 -1
  46. package/dist/vite-plus.d.cts +17 -2
  47. package/dist/vite-plus.d.mts +17 -2
  48. package/dist/vite-plus.mjs +5 -5
  49. package/dist/vite-plus.mjs.map +1 -1
  50. package/dist/vitest-template.cjs +6 -3
  51. package/dist/vitest-template.cjs.map +1 -1
  52. package/dist/vitest-template.d.cts +6 -3
  53. package/dist/vitest-template.d.mts +6 -3
  54. package/dist/vitest-template.mjs +6 -3
  55. package/dist/vitest-template.mjs.map +1 -1
  56. package/dist/vitest.cjs +1 -1
  57. package/dist/vitest.mjs +1 -1
  58. package/package.json +2 -7
  59. package/scripts/autopg-version +1 -1
  60. package/scripts/ci-install-autopg.sh +7 -2
  61. package/scripts/postinstall.js +41 -13
  62. package/dist/lease-B3TuX92y.d.mts +0 -24
  63. package/dist/lease-t4I9JahV.d.cts +0 -24
  64. package/dist/lifecycle-BbrvFQvg.cjs.map +0 -1
  65. package/dist/lifecycle-BvIx0xqq.mjs.map +0 -1
  66. package/dist/nx.cjs +0 -42
  67. package/dist/nx.cjs.map +0 -1
  68. package/dist/nx.d.cts +0 -15
  69. package/dist/nx.d.mts +0 -15
  70. package/dist/nx.mjs +0 -34
  71. package/dist/nx.mjs.map +0 -1
  72. package/dist/studio-DIFw1qWC.mjs.map +0 -1
  73. package/dist/studio-oJE30_PO.cjs.map +0 -1
  74. package/dist/tasks-CNHvvlsN.cjs +0 -67
  75. package/dist/tasks-CNHvvlsN.cjs.map +0 -1
  76. package/dist/tasks-CjaZRi_G.d.mts +0 -19
  77. package/dist/tasks-D5iWIdlo.mjs +0 -38
  78. package/dist/tasks-D5iWIdlo.mjs.map +0 -1
  79. package/dist/tasks-DdQoP9We.d.cts +0 -19
  80. package/dist/template-BiJ-0TIF.mjs.map +0 -1
  81. package/dist/template-CXMpODzK.cjs.map +0 -1
  82. package/dist/template-mode-BTUsdBtA.mjs +0 -94
  83. package/dist/template-mode-BTUsdBtA.mjs.map +0 -1
  84. package/dist/template-mode-CzyVwHsi.d.cts +0 -31
  85. package/dist/template-mode-CzyVwHsi.d.mts +0 -31
  86. package/dist/template-mode-JTOxrEgE.cjs +0 -105
  87. 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`. Beta (`0.2.0-beta.0`, `beta` dist-tag). Public APIs may still change.
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@beta
18
- # pnpm add -D @cedarjs/pg@beta
19
- # yarn add -D @cedarjs/pg@beta
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 -- yarn tsx scripts/apiServer/dev.ts
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 or attaches the lease, 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.
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. Use one acquire, then `run` wrappers.
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
- env: ["DATABASE_URL"],
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 an acquire task into dependents. Vite+ `env: [...]` does. Canonical shape:
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
- cedarPgRunCommand("dev", "yarn tsx scripts/apiServer/dev.ts");
134
- // "cedarpg run --mode=dev -- yarn tsx scripts/apiServer/dev.ts"
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
- relativeEnvFile("dev"); // ".cedarpg/dev.env"
137
- ```
138
-
139
- ```json
138
+ ```jsonc
139
+ // project.json (workspace root)
140
140
  {
141
141
  "targets": {
142
- "db:ready": { "command": "tsx tools/db-ready.ts", "cache": false },
143
- "dev": {
144
- "dependsOn": ["db:ready"],
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
- "serve": {
148
- "dependsOn": ["db:ready"],
149
- "command": "cedarpg run --mode=dev --force -- node dist/server.js"
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
- Migrate hook (same compose shape as Jest `createGlobalSetup`):
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
- // prisma migrate deploy, drizzle push, ...
163
+ // run your migrations against databaseUrl
167
164
  },
168
165
  })();
169
166
  ```
170
167
 
171
- If you cannot wrap with `run`, use `loadDevEnv({ overwrite: true })` or `import "@cedarjs/pg/dev-env"`. Absolute path helper: `envFilePath(root, mode)`.
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
- // Prefer setupFilesAfterEnv so you can use beforeAll (Jest globals).
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 install` if the host was never registered. It attaches once TCP accepts. Same port, same `~/.autopg/data`. It does not start a second local Postgres. If neither verb produces a listener, `acquire` fails with what it tried.
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 own a postmaster (even when `CI=true`). Local autopg host only, or fail |
330
- | unset and `CI=true` | Ephemeral |
331
- | unset | Local: `autopg restart`, then `autopg install`, then fail |
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 prunes leftover `/dev/shm/cedar-pg-*`, `pgserve-*`, and `PostgreSQL.*` (OOM-killed runs filling tmpfs). Safe on isolated CI VMs. On shared self-hosted runners another job's leftovers could match those globs.
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 acquire --force`, `cedarpg run --force`) |
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` (then `install`) and attaches once TCP accepts. `restart` exiting 0 is not proof. When pm2 is missing it still prints "respawned daemon". If both verbs fail, install pm2 or fix the supervisor (`pm2 logs autopg-server`). |
420
- | `database already exists: ..._c_<workerId>` in Jest | Use current `@cedarjs/pg` (`cloneWorkerDatabase` memos on `globalThis`). Prefer `setupFilesAfterEnv` + `beforeAll`. Avoid passing bare `JEST_WORKER_ID` as an explicit `name`. |
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`). On isolated runners only: `rm -rf /dev/shm/cedar-pg-* /dev/shm/pgserve-* /dev/shm/PostgreSQL.*`. Cold-start also prunes these when the recipe port is dead. |
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. Wrap with `cedarpg run --mode=dev --force -- <cmd>`, or `loadDevEnv({ overwrite: true })`. |
425
- | Role or DB errors under parallel Nx targets | Do not run concurrent `acquire` or `run` on the same worktree. One `db:ready`, then `run` wrappers. |
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-BbrvFQvg.cjs");
2
+ const require_lifecycle = require("./lifecycle-BCeM96tI.cjs");
3
3
  const require_lease = require("./lease-WlmOnNDi.cjs");
4
- const require_status = require("./status-4Uz6A3LB.cjs");
5
- const require_studio = require("./studio-oJE30_PO.cjs");
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>] [--force] [--json] [--print-env]
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 result = await require_lifecycle.acquire({
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: result.databaseUrl
189
+ DATABASE_URL: databaseUrl
184
190
  };
185
- if (mode === "test") childEnv.TEST_DATABASE_URL = result.databaseUrl;
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 resolveDevStatus } from "./status-Dcnpgnlz.mjs";
5
- import { a as formatDevStatus, o as runAttached, r as runStudio, t as detectStudio } from "./studio-DIFw1qWC.mjs";
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>] [--force] [--json] [--print-env]
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 result = await acquire({
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: result.databaseUrl
189
+ DATABASE_URL: databaseUrl
184
190
  };
185
- if (mode === "test") childEnv.TEST_DATABASE_URL = result.databaseUrl;
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") {