@cedarjs/pg 0.1.0-alpha.1 → 0.2.0-alpha.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 +276 -25
- package/dist/cli.cjs +60 -14
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +59 -13
- package/dist/cli.mjs.map +1 -1
- package/dist/{constants-CNZn5Xro.cjs → constants-Ct8myrEn.cjs} +2 -2
- package/dist/{constants-BY97wXjA.mjs.map → constants-Ct8myrEn.cjs.map} +1 -1
- package/dist/{constants-BY97wXjA.mjs → constants-NLR0U4NR.mjs} +2 -2
- package/dist/{constants-CNZn5Xro.cjs.map → constants-NLR0U4NR.mjs.map} +1 -1
- package/dist/dev-env.cjs +12 -0
- package/dist/dev-env.cjs.map +1 -0
- package/dist/dev-env.d.cts +1 -0
- package/dist/dev-env.d.mts +1 -0
- package/dist/dev-env.mjs +14 -0
- package/dist/dev-env.mjs.map +1 -0
- package/dist/index.cjs +50 -10
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +168 -82
- package/dist/index.d.mts +168 -82
- package/dist/index.mjs +32 -2
- package/dist/index.mjs.map +1 -0
- package/dist/jest-teardown.cjs +18 -0
- package/dist/jest-teardown.cjs.map +1 -0
- package/dist/jest-teardown.d.cts +13 -0
- package/dist/jest-teardown.d.mts +14 -0
- package/dist/jest-teardown.mjs +18 -0
- package/dist/jest-teardown.mjs.map +1 -0
- package/dist/jest-template.cjs +41 -0
- package/dist/jest-template.cjs.map +1 -0
- package/dist/jest-template.d.cts +28 -0
- package/dist/jest-template.d.mts +28 -0
- package/dist/jest-template.mjs +35 -0
- package/dist/jest-template.mjs.map +1 -0
- package/dist/jest.cjs +12 -15
- package/dist/jest.cjs.map +1 -1
- package/dist/jest.d.cts +10 -7
- package/dist/jest.d.mts +10 -6
- package/dist/jest.mjs +12 -10
- package/dist/jest.mjs.map +1 -1
- package/dist/lease-B3TuX92y.d.mts +24 -0
- package/dist/lease-BSBBpaR4.mjs +201 -0
- package/dist/lease-BSBBpaR4.mjs.map +1 -0
- package/dist/lease-Bq9wKwQa.cjs +272 -0
- package/dist/lease-Bq9wKwQa.cjs.map +1 -0
- package/dist/lease-t4I9JahV.d.cts +24 -0
- package/dist/lifecycle-BOo6xBjD.mjs +684 -0
- package/dist/lifecycle-BOo6xBjD.mjs.map +1 -0
- package/dist/lifecycle-DEJ4GgWV.cjs +856 -0
- package/dist/lifecycle-DEJ4GgWV.cjs.map +1 -0
- package/dist/load-dev-env-7UjMHhw7.mjs +10 -0
- package/dist/load-dev-env-7UjMHhw7.mjs.map +1 -0
- package/dist/load-dev-env-cII6N4ZP.cjs +15 -0
- package/dist/load-dev-env-cII6N4ZP.cjs.map +1 -0
- package/dist/load-mode-env-B0qO9DZx.cjs +39 -0
- package/dist/load-mode-env-B0qO9DZx.cjs.map +1 -0
- package/dist/load-mode-env-C8gy4v9V.mjs +28 -0
- package/dist/load-mode-env-C8gy4v9V.mjs.map +1 -0
- package/dist/load-test-env-C34wpkIX.mjs +18 -0
- package/dist/load-test-env-C34wpkIX.mjs.map +1 -0
- package/dist/load-test-env-CkdUjTpV.cjs +23 -0
- package/dist/load-test-env-CkdUjTpV.cjs.map +1 -0
- package/dist/naming-C2nGVxPk.d.cts +39 -0
- package/dist/naming-C2nGVxPk.d.mts +39 -0
- package/dist/nx.cjs +26 -17
- package/dist/nx.cjs.map +1 -1
- package/dist/nx.d.cts +9 -20
- package/dist/nx.d.mts +9 -20
- package/dist/nx.mjs +20 -18
- package/dist/nx.mjs.map +1 -1
- package/dist/tasks-Caud9yHr.cjs +67 -0
- package/dist/tasks-Caud9yHr.cjs.map +1 -0
- package/dist/tasks-CjaZRi_G.d.mts +19 -0
- package/dist/tasks-DdQoP9We.d.cts +19 -0
- package/dist/tasks-KherHLac.mjs +38 -0
- package/dist/tasks-KherHLac.mjs.map +1 -0
- package/dist/template-CGw3C1Ob.mjs +88 -0
- package/dist/template-CGw3C1Ob.mjs.map +1 -0
- package/dist/template-D0gJ_MS2.cjs +105 -0
- package/dist/template-D0gJ_MS2.cjs.map +1 -0
- package/dist/template-mode-BkEs9LnY.cjs +91 -0
- package/dist/template-mode-BkEs9LnY.cjs.map +1 -0
- package/dist/template-mode-CcxV_Iju.d.cts +28 -0
- package/dist/template-mode-CcxV_Iju.d.mts +28 -0
- package/dist/template-mode-IjlFOG4O.mjs +80 -0
- package/dist/template-mode-IjlFOG4O.mjs.map +1 -0
- package/dist/test-env.cjs +16 -0
- package/dist/test-env.cjs.map +1 -0
- package/dist/test-env.d.cts +1 -0
- package/dist/test-env.d.mts +1 -0
- package/dist/test-env.mjs +18 -0
- package/dist/test-env.mjs.map +1 -0
- package/dist/vite-plus.cjs +8 -33
- package/dist/vite-plus.cjs.map +1 -1
- package/dist/vite-plus.d.cts +4 -48
- package/dist/vite-plus.d.mts +4 -48
- package/dist/vite-plus.mjs +5 -30
- package/dist/vite-plus.mjs.map +1 -1
- package/dist/vitest-template.cjs +48 -0
- package/dist/vitest-template.cjs.map +1 -0
- package/dist/vitest-template.d.cts +31 -0
- package/dist/vitest-template.d.mts +31 -0
- package/dist/vitest-template.mjs +42 -0
- package/dist/vitest-template.mjs.map +1 -0
- package/dist/vitest.cjs +10 -10
- package/dist/vitest.cjs.map +1 -1
- package/dist/vitest.d.cts +7 -3
- package/dist/vitest.d.mts +7 -2
- package/dist/vitest.mjs +10 -5
- package/dist/vitest.mjs.map +1 -1
- package/package.json +36 -12
- package/scripts/autopg-version +1 -0
- package/scripts/ci-install-autopg.sh +73 -0
- package/scripts/postinstall.js +35 -9
- package/dist/lifecycle-BGYtVXtx.cjs +0 -743
- package/dist/lifecycle-BGYtVXtx.cjs.map +0 -1
- package/dist/lifecycle-CT_8AWPv.mjs +0 -577
- package/dist/lifecycle-CT_8AWPv.mjs.map +0 -1
- package/dist/tasks-13P6tMth.mjs +0 -16
- package/dist/tasks-13P6tMth.mjs.map +0 -1
- package/dist/tasks-B8ryV9xo.cjs +0 -21
- package/dist/tasks-B8ryV9xo.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Worktree-isolated local Postgres for **Vite+**, **Nx**, and **CedarJS**, powered
|
|
|
4
4
|
|
|
5
5
|
Published on npm as **`@cedarjs/pg`**.
|
|
6
6
|
|
|
7
|
-
> **Alpha** (`0.
|
|
7
|
+
> **Alpha** (`0.2.0-alpha.0`): APIs may change. Install with the `alpha` dist-tag.
|
|
8
8
|
|
|
9
9
|
## What you get (via autopg)
|
|
10
10
|
|
|
@@ -57,14 +57,16 @@ Examples:
|
|
|
57
57
|
## Prerequisites
|
|
58
58
|
|
|
59
59
|
A running [autopg](https://github.com/automagik-dev/autopg) host (installed automatically by `postinstall`, or manually).
|
|
60
|
-
|
|
60
|
+
The release pin lives in **`scripts/autopg-version`** (single source of truth for postinstall, CI binary install, and docs). Bump that file to upgrade:
|
|
61
61
|
|
|
62
62
|
```bash
|
|
63
|
-
|
|
64
|
-
|
|
63
|
+
# local / non-CI (upstream install.sh; may use pm2)
|
|
64
|
+
VER=$(tr -d '[:space:]' < scripts/autopg-version)
|
|
65
|
+
curl -fsSL "https://raw.githubusercontent.com/automagik-dev/autopg/${VER}/install.sh" \
|
|
66
|
+
| AUTOPG_VERSION="$VER" bash
|
|
65
67
|
```
|
|
66
68
|
|
|
67
|
-
Typical flow: `autopg daemon` (or your usual host install) once per machine → `cedarpg
|
|
69
|
+
Typical flow: `autopg daemon` (or your usual host install) once per machine → `cedarpg acquire` per worktree → connect with the printed `DATABASE_URL`.
|
|
68
70
|
|
|
69
71
|
## Develop this package (Vite+)
|
|
70
72
|
|
|
@@ -72,8 +74,9 @@ Typical flow: `autopg daemon` (or your usual host install) once per machine →
|
|
|
72
74
|
vp install
|
|
73
75
|
vp check
|
|
74
76
|
vp test
|
|
75
|
-
vp pack
|
|
76
|
-
vp run smoke
|
|
77
|
+
vp pack # → dist/ (dts + esm + cjs)
|
|
78
|
+
vp run smoke # build → npm-pack tarball → install + resolve exports
|
|
79
|
+
vp run smoke:pg # pack → Vitest + Jest adapters against real ephemeral Postgres
|
|
77
80
|
```
|
|
78
81
|
|
|
79
82
|
## Local consume (without npm)
|
|
@@ -84,19 +87,76 @@ vp pack
|
|
|
84
87
|
|
|
85
88
|
# in your app / Cedar
|
|
86
89
|
yarn add @cedarjs/pg@file:../cedar-pg
|
|
87
|
-
# or: pnpm pack && yarn add ./cedarjs-pg-0.
|
|
90
|
+
# or: pnpm pack && yarn add ./cedarjs-pg-0.2.0-alpha.0.tgz
|
|
88
91
|
```
|
|
89
92
|
|
|
90
93
|
## CLI
|
|
91
94
|
|
|
92
95
|
```bash
|
|
93
|
-
cedarpg
|
|
94
|
-
cedarpg
|
|
96
|
+
cedarpg acquire --mode=dev
|
|
97
|
+
cedarpg acquire --mode=test --print-env
|
|
98
|
+
cedarpg run --mode=dev -- yarn tsx scripts/apiServer/dev.ts
|
|
99
|
+
cedarpg run --mode=test -- vitest run
|
|
95
100
|
cedarpg dispose --mode=test
|
|
96
101
|
cedarpg print-url --mode=dev
|
|
97
102
|
cedarpg gc # drop DBs whose worktree root is gone (uses ~/.cedarpg/registry)
|
|
98
103
|
```
|
|
99
104
|
|
|
105
|
+
`cedarpg run` acquires (or attaches the lease), force-sets `DATABASE_URL` (and
|
|
106
|
+
`TEST_DATABASE_URL` in test mode) in the **child** process, then execs the command.
|
|
107
|
+
Use it for Nx / e2e / API wrappers — local `.env` URLs do not win inside the child.
|
|
108
|
+
|
|
109
|
+
## Nx consumer adapter
|
|
110
|
+
|
|
111
|
+
Nx `dependsOn` alone does not forward env from an acquire task into dependents
|
|
112
|
+
(Vite+ `env: [...]` does). **Canonical fix:** wrap the child with `cedarpg run`.
|
|
113
|
+
Secondary: point Nx `envFile` at `.cedarpg/<mode>.env` after acquire.
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { cedarPgNxTargets, cedarPgRunCommand, relativeEnvFile } from "@cedarjs/pg/nx";
|
|
117
|
+
|
|
118
|
+
cedarPgNxTargets();
|
|
119
|
+
// { "db:acquire": { command: "cedarpg acquire --mode=dev", cache: false }, … }
|
|
120
|
+
|
|
121
|
+
cedarPgRunCommand("dev", "yarn tsx scripts/apiServer/dev.ts");
|
|
122
|
+
// "cedarpg run --mode=dev -- yarn tsx scripts/apiServer/dev.ts"
|
|
123
|
+
|
|
124
|
+
relativeEnvFile("dev"); // ".cedarpg/dev.env"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```json
|
|
128
|
+
{
|
|
129
|
+
"targets": {
|
|
130
|
+
"dev": {
|
|
131
|
+
"command": "cedarpg run --mode=dev -- yarn tsx scripts/apiServer/dev.ts"
|
|
132
|
+
},
|
|
133
|
+
"db:acquire": { "command": "cedarpg acquire --mode=dev" },
|
|
134
|
+
"serve": {
|
|
135
|
+
"dependsOn": ["db:acquire"],
|
|
136
|
+
"command": "node dist/server.js",
|
|
137
|
+
"options": { "envFile": ".cedarpg/dev.env" }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
For a db:ready-style migrate hook (same compose shape as Jest `createGlobalSetup`):
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
// tools/db-ready.ts
|
|
147
|
+
import { createAcquireTask } from "@cedarjs/pg";
|
|
148
|
+
|
|
149
|
+
await createAcquireTask({
|
|
150
|
+
mode: "dev",
|
|
151
|
+
afterAcquire: async ({ databaseUrl }) => {
|
|
152
|
+
// prisma migrate deploy / drizzle push / …
|
|
153
|
+
},
|
|
154
|
+
})();
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Fallbacks when you cannot wrap with `run`: `loadDevEnv({ overwrite: true })` or
|
|
158
|
+
`import "@cedarjs/pg/dev-env"`. Absolute path helper: `envFilePath(root, mode)`.
|
|
159
|
+
|
|
100
160
|
## Vite+ consumer adapter
|
|
101
161
|
|
|
102
162
|
```ts
|
|
@@ -110,12 +170,12 @@ export default defineConfig({
|
|
|
110
170
|
...cedarPgTasks(),
|
|
111
171
|
test: {
|
|
112
172
|
command: "vp test",
|
|
113
|
-
dependsOn: ["db:
|
|
173
|
+
dependsOn: ["db:acquire-test"],
|
|
114
174
|
env: ["DATABASE_URL", "TEST_DATABASE_URL"],
|
|
115
175
|
},
|
|
116
176
|
dev: {
|
|
117
177
|
command: "vp dev",
|
|
118
|
-
dependsOn: ["db:
|
|
178
|
+
dependsOn: ["db:acquire"],
|
|
119
179
|
env: ["DATABASE_URL"],
|
|
120
180
|
},
|
|
121
181
|
},
|
|
@@ -123,31 +183,222 @@ export default defineConfig({
|
|
|
123
183
|
});
|
|
124
184
|
```
|
|
125
185
|
|
|
186
|
+
## Vitest / Jest adapters
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
// vitest.config.ts
|
|
190
|
+
import { defineConfig } from "vitest/config";
|
|
191
|
+
|
|
192
|
+
export default defineConfig({
|
|
193
|
+
test: {
|
|
194
|
+
globalSetup: ["@cedarjs/pg/vitest"],
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
// jest.config.cjs — standalone apps
|
|
201
|
+
module.exports = {
|
|
202
|
+
globalSetup: require.resolve("@cedarjs/pg/jest"),
|
|
203
|
+
globalTeardown: require.resolve("@cedarjs/pg/jest-teardown"),
|
|
204
|
+
// Jest globalSetup is a separate process — workers load DATABASE_URL from .cedarpg/test.env
|
|
205
|
+
setupFiles: [require.resolve("@cedarjs/pg/test-env")],
|
|
206
|
+
};
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Framework hosts (CedarJS, custom globalSetup)
|
|
210
|
+
|
|
211
|
+
If your runner already owns `globalSetup` (e.g. Prisma push/migrate after acquire), **do not** replace it with `@cedarjs/pg/jest`. Compose instead:
|
|
212
|
+
|
|
213
|
+
1. In your `globalSetup`: call `acquireIfNeeded` when opted in, then run migrations.
|
|
214
|
+
2. Add `setupFiles: [require.resolve('@cedarjs/pg/test-env')]` so Jest **workers** see `DATABASE_URL`.
|
|
215
|
+
3. In your `globalTeardown`: call `dispose({ mode: 'test', root })`.
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
// framework globalSetup (sketch)
|
|
219
|
+
import { acquireIfNeeded } from "@cedarjs/pg";
|
|
220
|
+
|
|
221
|
+
if (process.env.CEDAR_PG === "1" || process.env.CEDAR_PG === "true") {
|
|
222
|
+
await acquireIfNeeded({
|
|
223
|
+
root: projectRoot, // e.g. getPaths().base
|
|
224
|
+
mode: "test",
|
|
225
|
+
setEnv: true, // this process (prisma) — workers use @cedarjs/pg/test-env
|
|
226
|
+
url: process.env.TEST_DATABASE_URL,
|
|
227
|
+
force: process.env.CEDAR_PG_FORCE === "1",
|
|
228
|
+
disabled: false, // framework opt-in; adapters alone use CEDAR_PG=0 opt-out
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
// … prisma db push / migrate …
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
// jest-preset
|
|
236
|
+
setupFiles: [require.resolve("@cedarjs/pg/test-env")],
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Use exported `STATE_DIRNAME` (`.cedarpg`) / `loadTestEnv` / `loadDevEnv` /
|
|
240
|
+
`envFilePath(root, mode)` instead of hardcoding the lease dir.
|
|
241
|
+
|
|
242
|
+
`loadTestEnv` / `loadDevEnv` only fill **undefined** keys by default. Pass
|
|
243
|
+
`{ overwrite: true }` (or import `@cedarjs/pg/dev-env`) when a local `.env`
|
|
244
|
+
`DATABASE_URL` / `TEST_DATABASE_URL` should lose to cedar-pg. That is not the
|
|
245
|
+
same as `CEDAR_PG_FORCE` / acquire `{ force }` (external-URL escape hatch).
|
|
246
|
+
|
|
126
247
|
## Programmatic API
|
|
127
248
|
|
|
128
249
|
```ts
|
|
129
|
-
import {
|
|
250
|
+
import { acquire, dispose, loadTestEnv, loadDevEnv, envFilePath, STATE_DIRNAME } from "@cedarjs/pg";
|
|
130
251
|
|
|
131
|
-
const { databaseUrl, databaseName, dispose: drop } = await
|
|
252
|
+
const { databaseUrl, adminUrl, databaseName, dispose: drop } = await acquire({ mode: "test" });
|
|
132
253
|
// … tests …
|
|
133
254
|
await drop();
|
|
255
|
+
|
|
256
|
+
loadDevEnv({ overwrite: true }); // override .env DATABASE_URL from .cedarpg/dev.env
|
|
134
257
|
```
|
|
135
258
|
|
|
259
|
+
### Host startup (CI ephemeral)
|
|
260
|
+
|
|
261
|
+
By default cedar-pg **attaches** to a live autopg host (`autopg status`). If none is live it runs bare `autopg install` (pm2) — fine for local machines, hostile to GitHub Actions (no pm2) and slower than RAM-backed CI.
|
|
262
|
+
|
|
263
|
+
In CI, cedar-pg starts an **opinionated ephemeral host** automatically when `CI=true` (or when forced). Callers just use `acquire` — no host options bag:
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { acquire } from "@cedarjs/pg";
|
|
267
|
+
|
|
268
|
+
// CI=true → install --no-pm2 --no-ui + detached postmaster (--ram on Linux /dev/shm)
|
|
269
|
+
const { databaseUrl } = await acquire({ mode: "test" });
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
| Signal | Effect |
|
|
273
|
+
| --------------------------- | ------------------------------------------------------------------- |
|
|
274
|
+
| `CEDAR_PG_EPHEMERAL_HOST=1` | Prefer ephemeral start when **no** host is live (attach still wins) |
|
|
275
|
+
| `CEDAR_PG_EPHEMERAL_HOST=0` | Force local attach / pm2 install (even if `CI=true`) |
|
|
276
|
+
| unset + `CI=true` | Prefer ephemeral when no host is live |
|
|
277
|
+
| otherwise | Local: attach if live, else bare `autopg install` |
|
|
278
|
+
|
|
279
|
+
Ephemeral recipe (not configurable via cedar-pg):
|
|
280
|
+
|
|
281
|
+
- `autopg install --no-pm2 --no-ui --port 55432 --data DIR`
|
|
282
|
+
- detached `autopg postmaster --port 55432 --socket-dir DIR --data DIR`
|
|
283
|
+
- Linux when `/dev/shm` exists → also `--ram` and `DIR=/dev/shm/cedar-pg-<uid>`
|
|
284
|
+
- otherwise → disk `DIR` under the OS temp dir (still owned, no pm2)
|
|
285
|
+
- Ready when TCP accepts on the recipe port (not merely `autopg status` after install)
|
|
286
|
+
|
|
287
|
+
If a host is already live, 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.
|
|
288
|
+
|
|
289
|
+
### CI setup (GitHub Actions)
|
|
290
|
+
|
|
291
|
+
Prefer the composite action (cache + attested binary install, no pm2). Version defaults to this repo’s `scripts/autopg-version`:
|
|
292
|
+
|
|
293
|
+
```yaml
|
|
294
|
+
- uses: actions/checkout@v6
|
|
295
|
+
# In cedar-pg:
|
|
296
|
+
- uses: ./.github/actions/setup-autopg
|
|
297
|
+
# From another repo (pin to a tag when publishing the action):
|
|
298
|
+
# - uses: cedarjs/cedar-pg/.github/actions/setup-autopg@main
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
See [`.github/actions/setup-autopg`](.github/actions/setup-autopg/README.md) for inputs (`version`, `cache`, `token`) and outputs.
|
|
302
|
+
|
|
303
|
+
The action runs `scripts/ci-install-autopg.sh` under the hood. For published-package consumers under `CI=true` without the Action, set `CEDAR_PG_INSTALL_AUTOPG=1` so `postinstall` runs that same script (not upstream `install.sh`) — that flag alone is not enough when the package manager disables lifecycle scripts (`--ignore-scripts`, `YARN_ENABLE_SCRIPTS=false`, etc.). Prefer this Action, or bake the binary into the image.
|
|
304
|
+
|
|
305
|
+
### Migrate-once + TEMPLATE clones (Jest / Vitest)
|
|
306
|
+
|
|
307
|
+
Stock `@cedarjs/pg/jest` and `@cedarjs/pg/vitest` only run `acquireIfNeeded` + `dispose` (one shared test DB). They are **not** a full replacement for Redwood-style globalSetup that migrates once and clones per worker. For that, use template mode.
|
|
308
|
+
|
|
309
|
+
Migrate stays app-owned via `createGlobalSetup({ migrate })`, then the adapter marks TEMPLATE and clones per worker. Point `globalSetup` at a **local** module that calls `createGlobalSetup` — string-resolving the package entry without a migrate hook throws.
|
|
310
|
+
|
|
311
|
+
**Jest (template mode):**
|
|
312
|
+
|
|
313
|
+
```js
|
|
314
|
+
// jest.cedar-global.cjs
|
|
315
|
+
const { createGlobalSetup } = require("@cedarjs/pg/jest/template");
|
|
316
|
+
module.exports = createGlobalSetup({
|
|
317
|
+
migrate: async ({ databaseUrl }) => {
|
|
318
|
+
// prisma migrate reset / drizzle push / etc.
|
|
319
|
+
},
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// jest.config.cjs
|
|
323
|
+
module.exports = {
|
|
324
|
+
globalSetup: "<rootDir>/jest.cedar-global.cjs",
|
|
325
|
+
globalTeardown: require.resolve("@cedarjs/pg/jest-teardown"),
|
|
326
|
+
setupFilesAfterEnv: ["<rootDir>/jest.cedar-worker.cjs"],
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// jest.cedar-worker.cjs — once per worker process
|
|
330
|
+
const { cloneWorkerDatabase } = require("@cedarjs/pg/jest/template");
|
|
331
|
+
beforeAll(() => cloneWorkerDatabase());
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Vitest (template mode):**
|
|
335
|
+
|
|
336
|
+
```ts
|
|
337
|
+
// vitest.cedar-global.ts
|
|
338
|
+
import { createGlobalSetup } from "@cedarjs/pg/vitest/template";
|
|
339
|
+
export default createGlobalSetup({
|
|
340
|
+
migrate: async ({ databaseUrl }) => {
|
|
341
|
+
// migrate once
|
|
342
|
+
},
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// vitest.config.ts
|
|
346
|
+
export default defineConfig({
|
|
347
|
+
test: {
|
|
348
|
+
globalSetup: ["./vitest.cedar-global.ts"],
|
|
349
|
+
setupFiles: ["./vitest.cedar-worker.ts"],
|
|
350
|
+
},
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
// vitest.cedar-worker.ts — once per worker process (ESM top-level await)
|
|
354
|
+
import { cloneWorkerDatabase } from "@cedarjs/pg/vitest/template";
|
|
355
|
+
await cloneWorkerDatabase();
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
**Programmatic** (core API — no runner adapters):
|
|
359
|
+
|
|
360
|
+
```ts
|
|
361
|
+
import { acquire, markTemplate, cloneFromTemplate, dispose } from "@cedarjs/pg";
|
|
362
|
+
|
|
363
|
+
const acquired = await acquire({ mode: "test" });
|
|
364
|
+
await migrate({ databaseUrl: acquired.databaseUrl, adminUrl: acquired.adminUrl });
|
|
365
|
+
await markTemplate({ root: acquired.root, mode: "test", adminUrl: acquired.adminUrl });
|
|
366
|
+
const worker = await cloneFromTemplate({
|
|
367
|
+
root: acquired.root,
|
|
368
|
+
mode: "test",
|
|
369
|
+
name: "1",
|
|
370
|
+
setEnv: true,
|
|
371
|
+
});
|
|
372
|
+
// … tests …
|
|
373
|
+
await worker.dropClone(); // optional: drop one clone only
|
|
374
|
+
await dispose({ root: acquired.root, mode: "test" }); // role-scoped: TEMPLATE + all clones + role
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
`acquire` returns `adminUrl` for migrate hooks / privileged DDL; `markTemplate` / `cloneFromTemplate` accept it or rediscover the host when omitted.
|
|
378
|
+
`cloneFromTemplate` uses the admin connection internally (`CREATE DATABASE … TEMPLATE`); test roles stay `LOGIN`-only. `setEnv` defaults to false on `cloneFromTemplate`; `cloneFromTemplateIfNeeded` defaults true (same as `acquireIfNeeded`).
|
|
379
|
+
Worker adapters call `cloneFromTemplateIfNeeded` (shared skip policy via `runIfNeeded`) via `cloneWorkerDatabase`.
|
|
380
|
+
`dispose` is role-scoped suite teardown (not `dropClone`): unsets `IS_TEMPLATE` and drops every database owned by the lease role.
|
|
381
|
+
|
|
136
382
|
## Env
|
|
137
383
|
|
|
138
|
-
| Var
|
|
139
|
-
|
|
|
140
|
-
| `AUTOPG_BIN`
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
146
|
-
| `
|
|
384
|
+
| Var | Meaning |
|
|
385
|
+
| ------------------------------ | -------------------------------------------------------------------------------------------------------------- |
|
|
386
|
+
| `AUTOPG_BIN` | Path to autopg |
|
|
387
|
+
| `AUTOPG_PG_USER` / `_PASSWORD` | Autopg superuser for admin URL (default `postgres` / `postgres`) |
|
|
388
|
+
| `CEDAR_PG=0` | Disable auto-acquire in adapters |
|
|
389
|
+
| `TEST_DATABASE_URL` | Escape hatch: skip acquire for real external DBs (not `cpg_*` / `file:` / `{…}` / `<…>` template placeholders) |
|
|
390
|
+
| `CEDAR_PG_FORCE=1` | Ignore external-URL escape hatch (adapters + `cedarpg acquire --force` / `run --force`) |
|
|
391
|
+
| `CEDAR_PG_EPHEMERAL_HOST` | `1` force / `0` disable ephemeral host (auto when `CI=true`) |
|
|
392
|
+
| `CEDAR_PG_REGISTRY_DIR` | Override global lease registry (for `gc`) |
|
|
393
|
+
| `CEDAR_PG_SKIP_POSTINSTALL=1` | Skip autopg install hook |
|
|
394
|
+
| `CEDAR_PG_INSTALL_AUTOPG=1` | Under `CI=true`, run binary-only `ci-install-autopg.sh` from postinstall |
|
|
147
395
|
|
|
148
396
|
## Alpha caveats
|
|
149
397
|
|
|
150
398
|
- Public API may change before `0.1.0`.
|
|
151
|
-
- End-to-end Postgres flows assume a working local `autopg` host;
|
|
399
|
+
- End-to-end Postgres flows assume a working local `autopg` host; unit tests do not start Postgres.
|
|
400
|
+
CI runs `vp run smoke:pg` for Vitest/Jest adapters against real Postgres
|
|
401
|
+
(ephemeral cold-start when the runner has no live host; attach-wins otherwise).
|
|
152
402
|
- State lives in product-owned `.cedarpg` (worktree + `~/.cedarpg/registry`), not under autopg's `~/.autopg/` or a generic `.pg`.
|
|
153
|
-
-
|
|
403
|
+
- Role passwords are derived from `roleName` (`cedar-pg\\0` + roleName, scheme v2) so TEMPLATE clones that reuse a role keep working; bump the scheme id to change the derivation.
|
|
404
|
+
- Test TEMPLATE flow: `acquire` → app migrate → `markTemplate` → `cloneFromTemplate` → role-scoped `dispose`. Optional `@cedarjs/pg/jest/template` + `@cedarjs/pg/vitest/template` adapters orchestrate that pipeline via `createGlobalSetup({ migrate })`; migrate stays app-owned.
|
package/dist/cli.cjs
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const require_lifecycle = require("./lifecycle-
|
|
3
|
-
const require_constants = require("./constants-
|
|
2
|
+
const require_lifecycle = require("./lifecycle-DEJ4GgWV.cjs");
|
|
3
|
+
const require_constants = require("./constants-Ct8myrEn.cjs");
|
|
4
|
+
const require_lease = require("./lease-Bq9wKwQa.cjs");
|
|
5
|
+
let node_child_process = require("node:child_process");
|
|
4
6
|
//#region src/cli.ts
|
|
5
7
|
function printHelp() {
|
|
6
8
|
process.stdout.write(`${require_constants.CLI_NAME}: worktree-isolated local Postgres (via autopg)
|
|
7
9
|
|
|
8
10
|
Usage:
|
|
9
|
-
${require_constants.CLI_NAME}
|
|
11
|
+
${require_constants.CLI_NAME} acquire --mode=dev|test [--root <path>] [--force] [--json] [--print-env]
|
|
12
|
+
${require_constants.CLI_NAME} run --mode=dev|test [--root <path>] [--force] -- <cmd…>
|
|
10
13
|
${require_constants.CLI_NAME} dispose [--mode=dev|test] [--root <path>]
|
|
11
14
|
${require_constants.CLI_NAME} gc [--json]
|
|
12
15
|
${require_constants.CLI_NAME} print-url [--mode=dev|test] [--root <path>]
|
|
13
16
|
${require_constants.CLI_NAME} --help
|
|
14
17
|
|
|
15
18
|
Modes:
|
|
16
|
-
dev Keep DB across restarts (default
|
|
19
|
+
dev Keep DB across restarts (default when --mode omitted)
|
|
17
20
|
test Drop DB on dispose / test teardown
|
|
18
21
|
|
|
22
|
+
run:
|
|
23
|
+
Acquire, set DATABASE_URL (+ TEST_DATABASE_URL in test) on the child, exec <cmd…>.
|
|
24
|
+
--force sets CEDAR_PG_FORCE (escape hatch); child env overwrite is always on.
|
|
25
|
+
|
|
19
26
|
Env:
|
|
20
|
-
AUTOPG_BIN
|
|
21
|
-
CEDAR_PG=0
|
|
27
|
+
AUTOPG_BIN Path to autopg binary
|
|
28
|
+
CEDAR_PG=0 Disable adapters that auto-acquire
|
|
29
|
+
CEDAR_PG_FORCE=1 Ignore external-URL escape hatch (same as --force)
|
|
22
30
|
`);
|
|
23
31
|
}
|
|
24
32
|
function parseMode(value) {
|
|
@@ -29,19 +37,40 @@ function parseArgs(argv) {
|
|
|
29
37
|
const out = {};
|
|
30
38
|
const rest = [...argv];
|
|
31
39
|
out.cmd = rest.shift();
|
|
32
|
-
|
|
33
|
-
|
|
40
|
+
const dash = rest.indexOf("--");
|
|
41
|
+
let flagArgs = rest;
|
|
42
|
+
if (dash >= 0) {
|
|
43
|
+
flagArgs = rest.slice(0, dash);
|
|
44
|
+
out.child = rest.slice(dash + 1);
|
|
45
|
+
}
|
|
46
|
+
for (let i = 0; i < flagArgs.length; i++) {
|
|
47
|
+
const a = flagArgs[i];
|
|
34
48
|
if (a === "--help" || a === "-h") out.help = true;
|
|
35
49
|
else if (a === "--json") out.json = true;
|
|
36
50
|
else if (a === "--print-env") out.printEnv = true;
|
|
51
|
+
else if (a === "--force") out.force = true;
|
|
37
52
|
else if (a.startsWith("--mode=")) out.mode = parseMode(a.slice(7));
|
|
38
|
-
else if (a === "--mode") out.mode = parseMode(
|
|
53
|
+
else if (a === "--mode") out.mode = parseMode(flagArgs[++i]);
|
|
39
54
|
else if (a.startsWith("--root=")) out.root = a.slice(7);
|
|
40
|
-
else if (a === "--root") out.root =
|
|
55
|
+
else if (a === "--root") out.root = flagArgs[++i];
|
|
41
56
|
else throw new Error(`unknown argument: ${a}`);
|
|
42
57
|
}
|
|
43
58
|
return out;
|
|
44
59
|
}
|
|
60
|
+
function runChild(command, env) {
|
|
61
|
+
const [file, ...args] = command;
|
|
62
|
+
if (!file) throw new Error(`run requires a command after -- (e.g. ${require_constants.CLI_NAME} run --mode=dev -- yarn dev)`);
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const child = (0, node_child_process.spawn)(file, args, {
|
|
65
|
+
stdio: "inherit",
|
|
66
|
+
env
|
|
67
|
+
});
|
|
68
|
+
child.on("error", reject);
|
|
69
|
+
child.on("exit", (code, signal) => {
|
|
70
|
+
resolve(signal ? 1 : code ?? 1);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|
|
45
74
|
async function main() {
|
|
46
75
|
const argv = process.argv.slice(2);
|
|
47
76
|
if (argv.length === 0 || argv[0] === "help" || argv[0] === "--help" || argv[0] === "-h") {
|
|
@@ -54,9 +83,10 @@ async function main() {
|
|
|
54
83
|
return 0;
|
|
55
84
|
}
|
|
56
85
|
try {
|
|
57
|
-
if (args.cmd === "
|
|
86
|
+
if (args.cmd === "acquire") {
|
|
87
|
+
if (args.force) process.env.CEDAR_PG_FORCE = "1";
|
|
58
88
|
const mode = args.mode ?? "dev";
|
|
59
|
-
const result = await require_lifecycle.
|
|
89
|
+
const result = await require_lifecycle.acquire({
|
|
60
90
|
root: args.root,
|
|
61
91
|
mode,
|
|
62
92
|
setEnv: true
|
|
@@ -67,6 +97,7 @@ async function main() {
|
|
|
67
97
|
}
|
|
68
98
|
if (args.json) process.stdout.write(`${JSON.stringify({
|
|
69
99
|
databaseUrl: result.databaseUrl,
|
|
100
|
+
adminUrl: result.adminUrl,
|
|
70
101
|
databaseName: result.databaseName,
|
|
71
102
|
roleName: result.roleName,
|
|
72
103
|
repoSlug: result.repoSlug,
|
|
@@ -82,6 +113,21 @@ async function main() {
|
|
|
82
113
|
}
|
|
83
114
|
return 0;
|
|
84
115
|
}
|
|
116
|
+
if (args.cmd === "run") {
|
|
117
|
+
if (args.force) process.env.CEDAR_PG_FORCE = "1";
|
|
118
|
+
const mode = args.mode ?? "dev";
|
|
119
|
+
const result = await require_lifecycle.acquire({
|
|
120
|
+
root: args.root,
|
|
121
|
+
mode,
|
|
122
|
+
setEnv: true
|
|
123
|
+
});
|
|
124
|
+
const childEnv = {
|
|
125
|
+
...process.env,
|
|
126
|
+
DATABASE_URL: result.databaseUrl
|
|
127
|
+
};
|
|
128
|
+
if (mode === "test") childEnv.TEST_DATABASE_URL = result.databaseUrl;
|
|
129
|
+
return await runChild(args.child ?? [], childEnv);
|
|
130
|
+
}
|
|
85
131
|
if (args.cmd === "dispose") {
|
|
86
132
|
const mode = args.mode ?? "test";
|
|
87
133
|
const result = await require_lifecycle.dispose({
|
|
@@ -110,9 +156,9 @@ async function main() {
|
|
|
110
156
|
}
|
|
111
157
|
if (args.cmd === "print-url") {
|
|
112
158
|
const mode = args.mode ?? "dev";
|
|
113
|
-
const lease =
|
|
159
|
+
const lease = require_lease.readLease(require_lease.resolveWorktreeIdentity(args.root).root, mode);
|
|
114
160
|
if (!lease) {
|
|
115
|
-
process.stderr.write(`${require_constants.CLI_NAME}: no ${mode} lease; run \`${require_constants.CLI_NAME}
|
|
161
|
+
process.stderr.write(`${require_constants.CLI_NAME}: no ${mode} lease; run \`${require_constants.CLI_NAME} acquire --mode=${mode}\` first\n`);
|
|
116
162
|
return 2;
|
|
117
163
|
}
|
|
118
164
|
process.stdout.write(`${require_lifecycle.urlFromLease(lease)}\n`);
|
package/dist/cli.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.cjs","names":["CLI_NAME","ensure","dispose","gc","readLease","resolveWorktreeIdentity","urlFromLease"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { CLI_NAME } from \"./core/constants.ts\";\nimport { ensure, dispose, gc, urlFromLease } from \"./core/lifecycle.ts\";\nimport { resolveWorktreeIdentity } from \"./core/worktree.ts\";\nimport { readLease } from \"./core/lease.ts\";\nimport type { DbMode } from \"./core/naming.ts\";\n\nfunction printHelp(): void {\n process.stdout.write(`${CLI_NAME}: worktree-isolated local Postgres (via autopg)\n\nUsage:\n ${CLI_NAME} ensure --mode=dev|test [--root <path>] [--json] [--print-env]\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} --help\n\nModes:\n dev Keep DB across restarts (default for ensure if omitted: dev)\n test Drop DB on dispose / test teardown\n\nEnv:\n AUTOPG_BIN Path to autopg binary\n CEDAR_PG=0 Disable adapters that auto-ensure\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\nfunction parseArgs(argv: string[]) {\n const out: {\n cmd?: string;\n mode?: DbMode;\n root?: string;\n json?: boolean;\n printEnv?: boolean;\n help?: boolean;\n } = {};\n const rest = [...argv];\n out.cmd = rest.shift();\n for (let i = 0; i < rest.length; i++) {\n const a = rest[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.startsWith(\"--mode=\")) out.mode = parseMode(a.slice(7));\n else if (a === \"--mode\") out.mode = parseMode(rest[++i]);\n else if (a.startsWith(\"--root=\")) out.root = a.slice(7);\n else if (a === \"--root\") out.root = rest[++i];\n else throw new Error(`unknown argument: ${a}`);\n }\n return out;\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 === \"ensure\") {\n const mode = args.mode ?? \"dev\";\n const result = await ensure({\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 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 === \"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 const mode = args.mode ?? \"dev\";\n const identity = resolveWorktreeIdentity(args.root);\n const lease = readLease(identity.root, mode);\n if (!lease) {\n process.stderr.write(\n `${CLI_NAME}: no ${mode} lease; run \\`${CLI_NAME} ensure --mode=${mode}\\` first\\n`,\n );\n return 2;\n }\n process.stdout.write(`${urlFromLease(lease)}\\n`);\n return 0;\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","acquire","dispose","gc","readLease","resolveWorktreeIdentity","urlFromLease"],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { spawn } from \"node:child_process\";\nimport { CLI_NAME } from \"./core/constants.ts\";\nimport { acquire, dispose, gc, urlFromLease } from \"./core/lifecycle.ts\";\nimport { resolveWorktreeIdentity } from \"./core/worktree.ts\";\nimport { readLease } from \"./core/lease.ts\";\nimport type { DbMode } from \"./core/naming.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} --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\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 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.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 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 new Promise((resolve, reject) => {\n const child = spawn(file, args, { stdio: \"inherit\", env });\n child.on(\"error\", reject);\n child.on(\"exit\", (code, signal) => {\n resolve(signal ? 1 : (code ?? 1));\n });\n });\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 const mode = args.mode ?? \"dev\";\n const identity = resolveWorktreeIdentity(args.root);\n const lease = readLease(identity.root, mode);\n if (!lease) {\n process.stderr.write(\n `${CLI_NAME}: no ${mode} lease; run \\`${CLI_NAME} acquire --mode=${mode}\\` first\\n`,\n );\n return 2;\n }\n process.stdout.write(`${urlFromLease(lease)}\\n`);\n return 0;\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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"}
|