@cedarjs/pg 0.1.0-alpha.1 → 0.2.0-beta.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 +361 -89
- package/dist/cli.cjs +131 -35
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.mjs +118 -22
- package/dist/cli.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 +51 -10
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +198 -84
- package/dist/index.d.mts +198 -84
- 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 +44 -0
- package/dist/jest-template.cjs.map +1 -0
- package/dist/jest-template.d.cts +31 -0
- package/dist/jest-template.d.mts +31 -0
- package/dist/jest-template.mjs +38 -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-DS1SX8U_.mjs +221 -0
- package/dist/lease-DS1SX8U_.mjs.map +1 -0
- package/dist/lease-WlmOnNDi.cjs +310 -0
- package/dist/lease-WlmOnNDi.cjs.map +1 -0
- package/dist/lease-t4I9JahV.d.cts +24 -0
- package/dist/lifecycle-BbrvFQvg.cjs +953 -0
- package/dist/lifecycle-BbrvFQvg.cjs.map +1 -0
- package/dist/lifecycle-BvIx0xqq.mjs +781 -0
- package/dist/lifecycle-BvIx0xqq.mjs.map +1 -0
- package/dist/load-dev-env-BULkXyen.mjs +10 -0
- package/dist/load-dev-env-BULkXyen.mjs.map +1 -0
- package/dist/load-dev-env-Bl6Ddv1U.cjs +15 -0
- package/dist/load-dev-env-Bl6Ddv1U.cjs.map +1 -0
- package/dist/load-mode-env-CX9vd56X.cjs +39 -0
- package/dist/load-mode-env-CX9vd56X.cjs.map +1 -0
- package/dist/load-mode-env-Ce9ujisN.mjs +28 -0
- package/dist/load-mode-env-Ce9ujisN.mjs.map +1 -0
- package/dist/load-test-env-BqtA6V-d.mjs +18 -0
- package/dist/load-test-env-BqtA6V-d.mjs.map +1 -0
- package/dist/load-test-env-Dcgh6YHV.cjs +23 -0
- package/dist/load-test-env-Dcgh6YHV.cjs.map +1 -0
- package/dist/naming-C2nGVxPk.d.cts +39 -0
- package/dist/naming-C2nGVxPk.d.mts +39 -0
- package/dist/nx.cjs +25 -18
- 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 +18 -18
- package/dist/nx.mjs.map +1 -1
- package/dist/status-4Uz6A3LB.cjs +31 -0
- package/dist/status-4Uz6A3LB.cjs.map +1 -0
- package/dist/status-Dcnpgnlz.mjs +26 -0
- package/dist/status-Dcnpgnlz.mjs.map +1 -0
- package/dist/studio-DIFw1qWC.mjs +173 -0
- package/dist/studio-DIFw1qWC.mjs.map +1 -0
- package/dist/studio-oJE30_PO.cjs +208 -0
- package/dist/studio-oJE30_PO.cjs.map +1 -0
- package/dist/tasks-CNHvvlsN.cjs +67 -0
- package/dist/tasks-CNHvvlsN.cjs.map +1 -0
- package/dist/tasks-CjaZRi_G.d.mts +19 -0
- package/dist/tasks-D5iWIdlo.mjs +38 -0
- package/dist/tasks-D5iWIdlo.mjs.map +1 -0
- package/dist/tasks-DdQoP9We.d.cts +19 -0
- package/dist/template-BiJ-0TIF.mjs +88 -0
- package/dist/template-BiJ-0TIF.mjs.map +1 -0
- package/dist/template-CXMpODzK.cjs +105 -0
- package/dist/template-CXMpODzK.cjs.map +1 -0
- package/dist/template-mode-BTUsdBtA.mjs +94 -0
- package/dist/template-mode-BTUsdBtA.mjs.map +1 -0
- package/dist/template-mode-CzyVwHsi.d.cts +31 -0
- package/dist/template-mode-CzyVwHsi.d.mts +31 -0
- package/dist/template-mode-JTOxrEgE.cjs +105 -0
- package/dist/template-mode-JTOxrEgE.cjs.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 +113 -35
- package/dist/vite-plus.cjs.map +1 -1
- package/dist/vite-plus.d.cts +26 -46
- package/dist/vite-plus.d.mts +26 -46
- package/dist/vite-plus.mjs +110 -32
- 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 +46 -13
- package/scripts/autopg-version +1 -0
- package/scripts/ci-install-autopg.sh +73 -0
- package/scripts/postinstall.js +35 -9
- package/dist/constants-BY97wXjA.mjs +0 -24
- package/dist/constants-BY97wXjA.mjs.map +0 -1
- package/dist/constants-CNZn5Xro.cjs +0 -41
- package/dist/constants-CNZn5Xro.cjs.map +0 -1
- 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
|
@@ -1,121 +1,102 @@
|
|
|
1
1
|
# cedar-pg
|
|
2
2
|
|
|
3
|
-
Worktree-isolated local Postgres for
|
|
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
|
|
5
|
+
Published as `@cedarjs/pg`. CLI: `cedarpg`. Beta (`0.2.0-beta.0`, `beta` dist-tag). Public APIs may still change.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
autopg runs embedded PostgreSQL 18 (not WASM) with real concurrent connections. No credentials, zero config, and databases are provisioned on first use. Any client works (`psql`, `node-postgres`, Prisma, Drizzle, TypeORM).
|
|
12
|
-
|
|
13
|
-
### Development & testing
|
|
14
|
-
|
|
15
|
-
| Use case | What you get |
|
|
16
|
-
| ----------------------- | ------------------------------------------ |
|
|
17
|
-
| **Local development** | PostgreSQL without Docker |
|
|
18
|
-
| **Integration testing** | Real PostgreSQL, not mocks |
|
|
19
|
-
| **CI/CD pipelines** | Fresh databases per test run |
|
|
20
|
-
| **E2E testing** | Isolated database for Playwright / Cypress |
|
|
21
|
-
|
|
22
|
-
## What cedar-pg adds
|
|
23
|
-
|
|
24
|
-
**cedar-pg** gives each **git worktree** its own database and role on that autopg host (readable names, leases, teardown) so parallel checkouts do not share one DB:
|
|
25
|
-
|
|
26
|
-
- **1 database per git worktree** (visible in `\l` as `cpg_…`)
|
|
27
|
-
- **dev** DBs persist across restarts; **test** DBs drop on dispose
|
|
28
|
-
- First-class **Vite+ Task** + **Nx** / Vitest / Jest adapters
|
|
29
|
-
- `postinstall` ensures the pinned `autopg` binary when missing
|
|
30
|
-
|
|
31
|
-
| Layer | Responsibility |
|
|
32
|
-
| ------------ | ------------------------------------------------------------------- |
|
|
33
|
-
| **autopg** | Embedded Postgres host (concurrent, zero-config, auto-provision) |
|
|
34
|
-
| **cedar-pg** | Per-worktree `CREATE DATABASE` / role, `DATABASE_URL`, dispose + GC |
|
|
7
|
+
| Layer | Owns |
|
|
8
|
+
| -------- | --------------------------------------------------------------------------------- |
|
|
9
|
+
| autopg | Postgres process, port, concurrent connections |
|
|
10
|
+
| cedar-pg | Per-worktree `CREATE DATABASE` and role, lease files, `DATABASE_URL`, dispose, GC |
|
|
35
11
|
|
|
36
12
|
## Install
|
|
37
13
|
|
|
38
|
-
|
|
39
|
-
npm install -D @cedarjs/pg@alpha
|
|
40
|
-
# or: pnpm add -D @cedarjs/pg@alpha
|
|
41
|
-
# or: yarn add -D @cedarjs/pg@alpha
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## Database names (observability)
|
|
14
|
+
Node `>=24`.
|
|
45
15
|
|
|
46
|
-
```
|
|
47
|
-
|
|
16
|
+
```bash
|
|
17
|
+
npm install -D @cedarjs/pg@beta
|
|
18
|
+
# pnpm add -D @cedarjs/pg@beta
|
|
19
|
+
# yarn add -D @cedarjs/pg@beta
|
|
48
20
|
```
|
|
49
21
|
|
|
50
|
-
|
|
22
|
+
`postinstall` installs the pinned autopg binary when it is missing. The pin is `scripts/autopg-version`. To bump autopg, change that file.
|
|
51
23
|
|
|
52
|
-
|
|
53
|
-
| ----------------------------------- | -------------------------- |
|
|
54
|
-
| `cpg_cedar_cedar_dev_a1b2c3d4` | main `cedar` checkout, dev |
|
|
55
|
-
| `cpg_cedar_feat_auth_test_e5f67890` | worktree `feat-auth`, test |
|
|
24
|
+
To install the host by hand (local, non-CI; upstream `install.sh` may use pm2):
|
|
56
25
|
|
|
57
|
-
|
|
26
|
+
```bash
|
|
27
|
+
VER=$(tr -d '[:space:]' < scripts/autopg-version)
|
|
28
|
+
curl -fsSL "https://raw.githubusercontent.com/automagik-dev/autopg/${VER}/install.sh" \
|
|
29
|
+
| AUTOPG_VERSION="$VER" bash
|
|
30
|
+
```
|
|
58
31
|
|
|
59
|
-
|
|
60
|
-
Both the install script and binary are pinned to a release tag (currently `v3.0.7`); bump the pin in `scripts/postinstall.js` to upgrade:
|
|
32
|
+
Once per machine, run the host (`autopg daemon`, or your usual install). Then per worktree:
|
|
61
33
|
|
|
62
34
|
```bash
|
|
63
|
-
|
|
64
|
-
| AUTOPG_VERSION=v3.0.7 bash
|
|
35
|
+
cedarpg acquire --mode=dev
|
|
65
36
|
```
|
|
66
37
|
|
|
67
|
-
|
|
38
|
+
Connect with the printed `DATABASE_URL`.
|
|
68
39
|
|
|
69
|
-
|
|
40
|
+
From another checkout of this repo, pack first, then depend on the build:
|
|
70
41
|
|
|
71
42
|
```bash
|
|
72
|
-
vp
|
|
73
|
-
|
|
74
|
-
vp
|
|
75
|
-
vp pack # → dist/ (dts + esm + cjs)
|
|
76
|
-
vp run smoke # build → npm-pack tarball → install + resolve exports
|
|
43
|
+
vp pack
|
|
44
|
+
# in the app: yarn add @cedarjs/pg@file:../cedar-pg
|
|
45
|
+
# or install the tarball vp pack writes (name includes the version in package.json)
|
|
77
46
|
```
|
|
78
47
|
|
|
79
|
-
##
|
|
48
|
+
## Acquire a database
|
|
80
49
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
50
|
+
`dev` databases persist across restarts. `test` databases drop on `dispose`.
|
|
51
|
+
|
|
52
|
+
Names look like this (visible in `psql` `\l`):
|
|
84
53
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
# or: pnpm pack && yarn add ./cedarjs-pg-0.1.0-alpha.0.tgz
|
|
54
|
+
```text
|
|
55
|
+
cpg_<repo>_<worktree>_<mode>_<pathHash8>
|
|
88
56
|
```
|
|
89
57
|
|
|
58
|
+
Worktree state lives in `.cedarpg`. Import `STATE_DIRNAME` instead of hardcoding that string. `gc` uses `~/.cedarpg/registry`.
|
|
59
|
+
|
|
90
60
|
## CLI
|
|
91
61
|
|
|
92
62
|
```bash
|
|
93
|
-
cedarpg
|
|
94
|
-
cedarpg
|
|
63
|
+
cedarpg acquire --mode=dev
|
|
64
|
+
cedarpg acquire --mode=test --print-env
|
|
65
|
+
cedarpg run --mode=dev -- yarn tsx scripts/apiServer/dev.ts
|
|
66
|
+
cedarpg run --mode=test -- vitest run
|
|
95
67
|
cedarpg dispose --mode=test
|
|
96
68
|
cedarpg print-url --mode=dev
|
|
97
|
-
cedarpg
|
|
69
|
+
cedarpg status --mode=dev
|
|
70
|
+
cedarpg studio --mode=dev # Prisma or Drizzle Studio (--prisma / --drizzle)
|
|
71
|
+
cedarpg gc # drop DBs whose worktree root is gone
|
|
98
72
|
```
|
|
99
73
|
|
|
100
|
-
|
|
74
|
+
`status` and `studio` are read-only. They do not acquire. `studio` walks from cwd up to the worktree.
|
|
75
|
+
|
|
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.
|
|
77
|
+
|
|
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.
|
|
79
|
+
|
|
80
|
+
## Vite+
|
|
101
81
|
|
|
102
82
|
```ts
|
|
103
83
|
// vite.config.ts
|
|
104
84
|
import { defineConfig } from "vite-plus";
|
|
105
|
-
import { cedarPgTasks } from "@cedarjs/pg/vite-plus";
|
|
85
|
+
import { cedarPgTasks, cedarPgDev } from "@cedarjs/pg/vite-plus";
|
|
106
86
|
|
|
107
87
|
export default defineConfig({
|
|
88
|
+
plugins: [cedarPgDev()],
|
|
108
89
|
run: {
|
|
109
90
|
tasks: {
|
|
110
91
|
...cedarPgTasks(),
|
|
111
92
|
test: {
|
|
112
93
|
command: "vp test",
|
|
113
|
-
dependsOn: ["db:
|
|
94
|
+
dependsOn: ["db:acquire-test"],
|
|
114
95
|
env: ["DATABASE_URL", "TEST_DATABASE_URL"],
|
|
115
96
|
},
|
|
116
97
|
dev: {
|
|
117
98
|
command: "vp dev",
|
|
118
|
-
dependsOn: ["db:
|
|
99
|
+
dependsOn: ["db:acquire"],
|
|
119
100
|
env: ["DATABASE_URL"],
|
|
120
101
|
},
|
|
121
102
|
},
|
|
@@ -123,31 +104,322 @@ export default defineConfig({
|
|
|
123
104
|
});
|
|
124
105
|
```
|
|
125
106
|
|
|
107
|
+
`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
|
+
|
|
109
|
+
| Key | Action |
|
|
110
|
+
| --- | ---------------------------------------------------------------------- |
|
|
111
|
+
| `d` | Reprint status (name, port, `DATABASE_URL`, env file) |
|
|
112
|
+
| `s` | Open Prisma Studio or Drizzle Kit Studio with the lease `DATABASE_URL` |
|
|
113
|
+
|
|
114
|
+
Options: `cedarPgDev({ mode, root, cwd, studio: "prisma" | "drizzle" | false })`. Studio walks from `cwd` (default Vite `config.root`) up to the worktree, so an Nx `apps/...` package is found without moving the lease. `prisma` wins when both ORMs sit in the same package.
|
|
115
|
+
|
|
116
|
+
Shortcuts bind on Vite 8 and vite-plus. Vite 7 and Cedar print the listen panel only. Use `cedarpg status` and `cedarpg studio` for Nx and other non-Vite hosts.
|
|
117
|
+
|
|
118
|
+
## Nx
|
|
119
|
+
|
|
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 }, ... }
|
|
132
|
+
|
|
133
|
+
cedarPgRunCommand("dev", "yarn tsx scripts/apiServer/dev.ts");
|
|
134
|
+
// "cedarpg run --mode=dev -- yarn tsx scripts/apiServer/dev.ts"
|
|
135
|
+
|
|
136
|
+
relativeEnvFile("dev"); // ".cedarpg/dev.env"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
```json
|
|
140
|
+
{
|
|
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"
|
|
146
|
+
},
|
|
147
|
+
"serve": {
|
|
148
|
+
"dependsOn": ["db:ready"],
|
|
149
|
+
"command": "cedarpg run --mode=dev --force -- node dist/server.js"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Migrate hook (same compose shape as Jest `createGlobalSetup`):
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
// tools/db-ready.ts
|
|
159
|
+
import { createAcquireTask } from "@cedarjs/pg";
|
|
160
|
+
|
|
161
|
+
await createAcquireTask({
|
|
162
|
+
mode: "dev",
|
|
163
|
+
// Need this when .env already has DATABASE_URL
|
|
164
|
+
force: true,
|
|
165
|
+
afterAcquire: async ({ databaseUrl }) => {
|
|
166
|
+
// prisma migrate deploy, drizzle push, ...
|
|
167
|
+
},
|
|
168
|
+
})();
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
If you cannot wrap with `run`, use `loadDevEnv({ overwrite: true })` or `import "@cedarjs/pg/dev-env"`. Absolute path helper: `envFilePath(root, mode)`.
|
|
172
|
+
|
|
173
|
+
## Vitest and Jest
|
|
174
|
+
|
|
175
|
+
Stock `@cedarjs/pg/vitest` and `@cedarjs/pg/jest` only acquire and dispose. One shared test DB. They do not migrate, and they do not clone per worker. For migrate-once plus clones, see [TEMPLATE clones](#template-clones).
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
// vitest.config.ts
|
|
179
|
+
import { defineConfig } from "vitest/config";
|
|
180
|
+
|
|
181
|
+
export default defineConfig({
|
|
182
|
+
test: {
|
|
183
|
+
globalSetup: ["@cedarjs/pg/vitest"],
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Vitest runs `globalSetup` in the main process, then workers inherit `process.env`. Add `setupFiles: ["@cedarjs/pg/test-env"]` only if your pool does not inherit env.
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
// jest.config.cjs
|
|
192
|
+
module.exports = {
|
|
193
|
+
globalSetup: require.resolve("@cedarjs/pg/jest"),
|
|
194
|
+
globalTeardown: require.resolve("@cedarjs/pg/jest-teardown"),
|
|
195
|
+
// Jest globalSetup is a separate process. Workers load DATABASE_URL from .cedarpg/test.env.
|
|
196
|
+
setupFiles: [require.resolve("@cedarjs/pg/test-env")],
|
|
197
|
+
};
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
`loadTestEnv` and `loadDevEnv` fill undefined keys by default. Pass `{ overwrite: true }` (or import `@cedarjs/pg/dev-env`) when a local `.env` URL should lose to cedar-pg. That is not the same as `CEDAR_PG_FORCE` or `{ force: true }` (external-URL escape hatch).
|
|
201
|
+
|
|
202
|
+
## CedarJS and custom globalSetup
|
|
203
|
+
|
|
204
|
+
If the runner already owns `globalSetup` (Prisma push or migrate after acquire), do not replace it with `@cedarjs/pg/jest`. Compose:
|
|
205
|
+
|
|
206
|
+
1. In `globalSetup`, call `acquireIfNeeded` when opted in, then migrate.
|
|
207
|
+
2. Add `setupFiles: [require.resolve("@cedarjs/pg/test-env")]` so Jest workers see `DATABASE_URL`.
|
|
208
|
+
3. In `globalTeardown`, call `dispose({ mode: "test", root })`.
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
import { acquireIfNeeded } from "@cedarjs/pg";
|
|
212
|
+
|
|
213
|
+
if (process.env.CEDAR_PG === "1" || process.env.CEDAR_PG === "true") {
|
|
214
|
+
await acquireIfNeeded({
|
|
215
|
+
root: projectRoot, // e.g. getPaths().base
|
|
216
|
+
mode: "test",
|
|
217
|
+
setEnv: true, // this process (prisma). Workers use @cedarjs/pg/test-env.
|
|
218
|
+
url: process.env.TEST_DATABASE_URL,
|
|
219
|
+
force: process.env.CEDAR_PG_FORCE === "1",
|
|
220
|
+
disabled: false, // framework opt-in. Stock adapters use CEDAR_PG=0 opt-out.
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## TEMPLATE clones
|
|
226
|
+
|
|
227
|
+
Migrate stays app-owned via `createGlobalSetup({ migrate })`. The adapter then marks TEMPLATE and clones per worker.
|
|
228
|
+
|
|
229
|
+
Point `globalSetup` at a local module that calls `createGlobalSetup`. String-resolving the package entry without a migrate hook throws.
|
|
230
|
+
|
|
231
|
+
### Jest
|
|
232
|
+
|
|
233
|
+
```js
|
|
234
|
+
// jest.cedar-global.cjs
|
|
235
|
+
const { createGlobalSetup } = require("@cedarjs/pg/jest/template");
|
|
236
|
+
module.exports = createGlobalSetup({
|
|
237
|
+
migrate: async ({ databaseUrl }) => {
|
|
238
|
+
// prisma migrate reset, drizzle push
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// jest.config.cjs
|
|
243
|
+
// When .env has a real TEST_DATABASE_URL, set FORCE once here so it
|
|
244
|
+
// inherits into globalSetup and workers (dotenv will not override existing keys).
|
|
245
|
+
process.env.CEDAR_PG_FORCE = "1";
|
|
246
|
+
|
|
247
|
+
module.exports = {
|
|
248
|
+
globalSetup: "<rootDir>/jest.cedar-global.cjs",
|
|
249
|
+
globalTeardown: require.resolve("@cedarjs/pg/jest-teardown"),
|
|
250
|
+
// Prefer setupFilesAfterEnv so you can use beforeAll (Jest globals).
|
|
251
|
+
setupFilesAfterEnv: ["<rootDir>/jest.cedar-worker.cjs"],
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
// jest.cedar-worker.cjs
|
|
255
|
+
const { cloneWorkerDatabase } = require("@cedarjs/pg/jest/template");
|
|
256
|
+
beforeAll(() => cloneWorkerDatabase());
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
`cloneWorkerDatabase` memos on `globalThis`, so Jest `setupFiles` (module reload per file) still shares one clone per worker.
|
|
260
|
+
|
|
261
|
+
### Vitest
|
|
262
|
+
|
|
263
|
+
```ts
|
|
264
|
+
// vitest.cedar-global.ts
|
|
265
|
+
import { createGlobalSetup } from "@cedarjs/pg/vitest/template";
|
|
266
|
+
export default createGlobalSetup({
|
|
267
|
+
migrate: async ({ databaseUrl }) => {
|
|
268
|
+
// migrate once
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
// vitest.config.ts
|
|
273
|
+
export default defineConfig({
|
|
274
|
+
test: {
|
|
275
|
+
globalSetup: ["./vitest.cedar-global.ts"],
|
|
276
|
+
setupFiles: ["./vitest.cedar-worker.ts"],
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// vitest.cedar-worker.ts
|
|
281
|
+
import { cloneWorkerDatabase } from "@cedarjs/pg/vitest/template";
|
|
282
|
+
await cloneWorkerDatabase();
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### Core API
|
|
286
|
+
|
|
287
|
+
No runner adapters.
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
import { acquire, markTemplate, cloneFromTemplate, dispose } from "@cedarjs/pg";
|
|
291
|
+
|
|
292
|
+
const acquired = await acquire({ mode: "test" });
|
|
293
|
+
await migrate({ databaseUrl: acquired.databaseUrl, adminUrl: acquired.adminUrl });
|
|
294
|
+
await markTemplate({ root: acquired.root, mode: "test", adminUrl: acquired.adminUrl });
|
|
295
|
+
const worker = await cloneFromTemplate({
|
|
296
|
+
root: acquired.root,
|
|
297
|
+
mode: "test",
|
|
298
|
+
name: "1",
|
|
299
|
+
setEnv: true,
|
|
300
|
+
});
|
|
301
|
+
await worker.dropClone(); // optional: drop one clone only
|
|
302
|
+
await dispose({ root: acquired.root, mode: "test" }); // TEMPLATE + all clones + role
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
`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
|
+
|
|
307
|
+
`setEnv` defaults to false on `cloneFromTemplate`. It defaults to true on `cloneFromTemplateIfNeeded` (same as `acquireIfNeeded`). Worker adapters call `cloneFromTemplateIfNeeded` via `cloneWorkerDatabase`.
|
|
308
|
+
|
|
309
|
+
`dispose` is role-scoped suite teardown, not `dropClone`. It unsets `IS_TEMPLATE` and drops every database owned by the lease role.
|
|
310
|
+
|
|
311
|
+
## Host and CI
|
|
312
|
+
|
|
313
|
+
`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
|
+
|
|
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.
|
|
316
|
+
|
|
317
|
+
Callers use `acquire` (and `adminUrl`). There is no public host-options object. Ephemeral behavior is env-driven.
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
import { acquire } from "@cedarjs/pg";
|
|
321
|
+
|
|
322
|
+
// CI=true: detached postmaster (--ram on Linux /dev/shm). Does not rewrite ~/.autopg.
|
|
323
|
+
const { databaseUrl } = await acquire({ mode: "test" });
|
|
324
|
+
```
|
|
325
|
+
|
|
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 |
|
|
332
|
+
|
|
333
|
+
Ephemeral recipe (not configurable via cedar-pg):
|
|
334
|
+
|
|
335
|
+
- Detached `autopg postmaster --port 55432 --socket-dir DIR --data DIR`
|
|
336
|
+
- Does not run `autopg install` (that rewrites `~/.autopg/admin.json` and conflicts with a local pm2 host)
|
|
337
|
+
- Linux when `/dev/shm` exists: also `--ram` and `DIR=/dev/shm/cedar-pg-<uid>`
|
|
338
|
+
- Otherwise: disk `DIR` under the OS temp dir
|
|
339
|
+
- 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.
|
|
341
|
+
|
|
342
|
+
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
|
+
|
|
344
|
+
Cloud VMs often ship `/dev/shm` at about 64MB, too small for `--ram`. Remount before tests if needed (`sudo mount -o remount,size=6G /dev/shm`). See [Troubleshooting](#troubleshooting).
|
|
345
|
+
|
|
346
|
+
### GitHub Actions
|
|
347
|
+
|
|
348
|
+
Prefer the composite action (cache plus attested binary install, no pm2). Version defaults to this repo's `scripts/autopg-version`. Inputs and outputs: [`.github/actions/setup-autopg`](.github/actions/setup-autopg/README.md).
|
|
349
|
+
|
|
350
|
+
```yaml
|
|
351
|
+
- uses: actions/checkout@v6
|
|
352
|
+
# In cedar-pg:
|
|
353
|
+
- uses: ./.github/actions/setup-autopg
|
|
354
|
+
# From another repo (pin to a tag when publishing the action):
|
|
355
|
+
# - uses: cedarjs/cedar-pg/.github/actions/setup-autopg@main
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
The action runs `scripts/ci-install-autopg.sh`. For published-package consumers under `CI=true` without the Action, set `CEDAR_PG_INSTALL_AUTOPG=1` so `postinstall` runs that script (not upstream `install.sh`). That flag is not enough when the package manager disables lifecycle scripts (`--ignore-scripts`, `YARN_ENABLE_SCRIPTS=false`). Prefer the Action, or bake the binary into the image.
|
|
359
|
+
|
|
360
|
+
Yarn Berry or ignore-scripts, when you cannot use the Action. Requires a real `node_modules` tree (`nodeLinker: node-modules` or pnpm). Default Yarn PnP has no `node_modules/@cedarjs/pg/...` path. Resolve via `yarn node` or `require.resolve`, or prefer the Action.
|
|
361
|
+
|
|
362
|
+
```yaml
|
|
363
|
+
- name: Ensure autopg binary
|
|
364
|
+
run: |
|
|
365
|
+
set -euo pipefail
|
|
366
|
+
echo "${HOME}/.local/bin" >> "${GITHUB_PATH}"
|
|
367
|
+
export PATH="${HOME}/.local/bin:${PATH}"
|
|
368
|
+
bash node_modules/@cedarjs/pg/scripts/ci-install-autopg.sh
|
|
369
|
+
env:
|
|
370
|
+
GH_TOKEN: ${{ github.token }}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Environment variables
|
|
374
|
+
|
|
375
|
+
| Var | Meaning |
|
|
376
|
+
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
377
|
+
| `AUTOPG_BIN` | Path to the autopg binary |
|
|
378
|
+
| `AUTOPG_PG_USER`, `AUTOPG_PG_PASSWORD` | Autopg superuser for admin URL (default user `postgres`, password `postgres`) |
|
|
379
|
+
| `CEDAR_PG=0` | Disable auto-acquire in adapters |
|
|
380
|
+
| `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`) |
|
|
382
|
+
| `CEDAR_PG_EPHEMERAL_HOST` | `1` owned postmaster. `0` never own one (even in CI). Unset and `CI=true` means ephemeral |
|
|
383
|
+
| `CEDAR_PG_REGISTRY_DIR` | Override the global lease registry (for `gc`) |
|
|
384
|
+
| `CEDAR_PG_SKIP_POSTINSTALL=1` | Skip the autopg install hook |
|
|
385
|
+
| `CEDAR_PG_INSTALL_AUTOPG=1` | Under `CI=true`, run binary-only `ci-install-autopg.sh` from postinstall |
|
|
386
|
+
|
|
387
|
+
`force`, `overwrite`, and `run` are different knobs. Do not collapse them.
|
|
388
|
+
|
|
126
389
|
## Programmatic API
|
|
127
390
|
|
|
128
391
|
```ts
|
|
129
|
-
import {
|
|
392
|
+
import { acquire, loadDevEnv } from "@cedarjs/pg";
|
|
393
|
+
|
|
394
|
+
const { databaseUrl, adminUrl, databaseName, dispose } = await acquire({ mode: "test" });
|
|
395
|
+
await dispose();
|
|
130
396
|
|
|
131
|
-
|
|
132
|
-
// … tests …
|
|
133
|
-
await drop();
|
|
397
|
+
loadDevEnv({ overwrite: true }); // override .env DATABASE_URL from .cedarpg/dev.env
|
|
134
398
|
```
|
|
135
399
|
|
|
136
|
-
|
|
400
|
+
Unit tests in this repo do not start Postgres. CI runs `vp run smoke:pg` for Vitest and Jest adapters against real Postgres (ephemeral cold-start when the runner has no live host; attach wins otherwise).
|
|
137
401
|
|
|
138
|
-
|
|
139
|
-
| ----------------------------- | ------------------------------------------------------------------ |
|
|
140
|
-
| `AUTOPG_BIN` | Path to autopg |
|
|
141
|
-
| `CEDAR_PG=0` | Disable auto-ensure in adapters |
|
|
142
|
-
| `TEST_DATABASE_URL` | Escape hatch: skip ensure for external DBs (not `cpg_*` / `file:`) |
|
|
143
|
-
| `CEDAR_PG_FORCE=1` | Ignore external-URL escape hatch |
|
|
144
|
-
| `CEDAR_PG_REGISTRY_DIR` | Override global lease registry (for `gc`) |
|
|
145
|
-
| `CEDAR_PG_SKIP_POSTINSTALL=1` | Skip autopg install hook |
|
|
146
|
-
| `CEDAR_PG_INSTALL_AUTOPG=1` | Force autopg install in CI |
|
|
402
|
+
## Develop this package
|
|
147
403
|
|
|
148
|
-
|
|
404
|
+
Vite+, Node `>=24` (`.node-version`), `pnpm@11`. Contributor rules: [AGENTS.md](AGENTS.md). User-visible API changes: [CHANGELOG.md](CHANGELOG.md).
|
|
405
|
+
|
|
406
|
+
```bash
|
|
407
|
+
vp install
|
|
408
|
+
vp check
|
|
409
|
+
vp test
|
|
410
|
+
vp pack # dist/ (dts + esm + cjs)
|
|
411
|
+
vp run smoke # pack, then tarball install, then resolve exports
|
|
412
|
+
vp run smoke:pg # pack, then Vitest and Jest adapters against real ephemeral Postgres
|
|
413
|
+
```
|
|
149
414
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
415
|
+
## Troubleshooting
|
|
416
|
+
|
|
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. |
|