From de27f0ec393218236047049140d66bfefc1008dc Mon Sep 17 00:00:00 2001 From: jacobsenscott235-web <233192809+jacobsenscott235-web@users.noreply.github.com> Date: Fri, 2 Oct 2026 11:13:03 -0500 Subject: [PATCH 1/2] Use PGlite by default for scaffolded db:generate New projects get "pgstrap generate --pglite", so generating types no longer needs a running Postgres server (#2). - init: scaffold db:generate with --pglite - generate: bind the temporary PGlite gateway to 127.0.0.1, and close it and restore DATABASE_URL even when generation fails - cli: await generate so failures surface through yargs - config: import pgstrap.config.js through a file:// URL; Node's ESM loader rejects absolute Windows paths, so every command failed there - tests: give the two PGlite tests a 30 s timeout; they take 6-7 s on Windows and failed the 5 s default - README: document the default Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- src/cli.ts | 2 +- src/generate.ts | 40 ++++++++++++++++++----------------- src/get-project-context.ts | 5 ++++- src/init.ts | 2 +- tests/generate.pglite.test.ts | 2 +- tests/init.test.ts | 2 +- tests/pglite.test.ts | 2 +- 8 files changed, 31 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 20e3127..b7c719e 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ npm install pgstrap --save-dev - `npm run db:migrate` - Run pending migrations - `npm run db:reset` - Drop and recreate the database, then run all migrations -- `npm run db:generate` - Generate types and structure dumps. Use `pgstrap generate --pglite` to run migrations against an in-memory PGlite instance. +- `npm run db:generate` - Generate types and structure dumps. The scaffolded script runs `pgstrap generate --pglite`, which applies your migrations to an in-memory PGlite instance, so no Postgres server is needed. Run `pgstrap generate` without the flag to generate from a running database instead. - `npm run db:create-migration` - Create a new migration file ### Configuration diff --git a/src/cli.ts b/src/cli.ts index 9a9bdec..1626f86 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -38,7 +38,7 @@ import { getProjectContext } from "./get-project-context" yargs.option("pglite", { type: "boolean", default: false }) }, async (argv) => { - generate({ ...(await getProjectContext()), pglite: !!argv.pglite }) + await generate({ ...(await getProjectContext()), pglite: !!argv.pglite }) }, ) .parse() diff --git a/src/generate.ts b/src/generate.ts index f337094..cecf1d8 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -60,32 +60,34 @@ export const generate = async ({ }) }) - await new Promise((resolve) => server.listen(0, resolve)) + await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)) const port = (server.address() as any).port const connectionString = `postgres://postgres:postgres@127.0.0.1:${port}/postgres` const prevDbUrl = process.env.DATABASE_URL process.env.DATABASE_URL = connectionString - await zg.generate({ - db: { - connectionString, - }, - schemas: Object.fromEntries( - schemas.map((s) => [s, { include: "*", exclude: [] }]), - ), - outDir: dbDir, - }) - - await dumpTree({ - targetDir: path.join(dbDir, "structure"), - defaultDatabase: "postgres", - schemas, - }) + try { + await zg.generate({ + db: { + connectionString, + }, + schemas: Object.fromEntries( + schemas.map((s) => [s, { include: "*", exclude: [] }]), + ), + outDir: dbDir, + }) - server.close() - if (prevDbUrl === undefined) delete process.env.DATABASE_URL - else process.env.DATABASE_URL = prevDbUrl + await dumpTree({ + targetDir: path.join(dbDir, "structure"), + defaultDatabase: "postgres", + schemas, + }) + } finally { + server.close() + if (prevDbUrl === undefined) delete process.env.DATABASE_URL + else process.env.DATABASE_URL = prevDbUrl + } return } diff --git a/src/get-project-context.ts b/src/get-project-context.ts index 8d01d80..35680a8 100644 --- a/src/get-project-context.ts +++ b/src/get-project-context.ts @@ -1,5 +1,6 @@ import fs from "fs" import path from "path" +import { pathToFileURL } from "url" import { PgstrapConfig } from "./define-config" export interface Context extends PgstrapConfig { @@ -13,7 +14,9 @@ export const getProjectContext = async (): Promise => { ) } - const config = await import(path.join(process.cwd(), "pgstrap.config.js")) + const config = await import( + pathToFileURL(path.join(process.cwd(), "pgstrap.config.js")).href + ) return { cwd: process.cwd(), diff --git a/src/init.ts b/src/init.ts index b84f9cd..e396f28 100644 --- a/src/init.ts +++ b/src/init.ts @@ -16,7 +16,7 @@ export const initPgstrap = async (ctx: Pick) => { pkg.scripts["db:migrate"] = "pgstrap migrate" pkg.scripts["db:reset"] = "pgstrap reset" - pkg.scripts["db:generate"] = "pgstrap generate" + pkg.scripts["db:generate"] = "pgstrap generate --pglite" pkg.scripts["db:create-migration"] = "pgstrap create-migration" if (!pkg.devDependencies) pkg.devDependencies = {} diff --git a/tests/generate.pglite.test.ts b/tests/generate.pglite.test.ts index 56dcd53..70b21ff 100644 --- a/tests/generate.pglite.test.ts +++ b/tests/generate.pglite.test.ts @@ -44,4 +44,4 @@ test("generate with pglite runs migrations and dumps structure", async () => { expect(fs.existsSync(path.join(structureDir, "table.sql"))).toBe(true) fs.rmSync(tmp, { recursive: true, force: true }) -}) +}, 30_000) diff --git a/tests/init.test.ts b/tests/init.test.ts index cd4ec4b..aabc7f7 100644 --- a/tests/init.test.ts +++ b/tests/init.test.ts @@ -25,6 +25,6 @@ test("initPgstrap writes scripts to package.json", async () => { ) expect(pkg.scripts["db:migrate"]).toBe("pgstrap migrate") expect(pkg.scripts["db:reset"]).toBe("pgstrap reset") - expect(pkg.scripts["db:generate"]).toBe("pgstrap generate") + expect(pkg.scripts["db:generate"]).toBe("pgstrap generate --pglite") expect(pkg.scripts["db:create-migration"]).toBe("pgstrap create-migration") }) diff --git a/tests/pglite.test.ts b/tests/pglite.test.ts index 2fbc8bc..111d635 100644 --- a/tests/pglite.test.ts +++ b/tests/pglite.test.ts @@ -48,4 +48,4 @@ test("migration of a pglite db works", async () => { // Clean up fs.rmSync(migrationsDir, { recursive: true, force: true }) -}) +}, 30_000) From 54a10b1e8735dc9e8fff487159cf57b728650349 Mon Sep 17 00:00:00 2001 From: jacobsenscott235-web <233192809+jacobsenscott235-web@users.noreply.github.com> Date: Fri, 2 Oct 2026 19:01:03 -0500 Subject: [PATCH 2/2] Await the PGlite gateway close and test DATABASE_URL restore - generate: wait for the temporary gateway to finish closing before returning - tests: check that DATABASE_URL is restored when generation fails after the gateway has started Co-Authored-By: Claude Opus 5.5 --- src/generate.ts | 2 +- tests/generate.pglite.test.ts | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/src/generate.ts b/src/generate.ts index cecf1d8..a611005 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -84,7 +84,7 @@ export const generate = async ({ schemas, }) } finally { - server.close() + await new Promise((resolve) => server.close(() => resolve())) if (prevDbUrl === undefined) delete process.env.DATABASE_URL else process.env.DATABASE_URL = prevDbUrl } diff --git a/tests/generate.pglite.test.ts b/tests/generate.pglite.test.ts index 70b21ff..de4b243 100644 --- a/tests/generate.pglite.test.ts +++ b/tests/generate.pglite.test.ts @@ -45,3 +45,36 @@ test("generate with pglite runs migrations and dumps structure", async () => { fs.rmSync(tmp, { recursive: true, force: true }) }, 30_000) + +test("generate with pglite restores DATABASE_URL on failure", async () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "pgstrap-generate-")) + const migrationsDir = path.join(tmp, "migrations") + fs.mkdirSync(migrationsDir, { recursive: true }) + fs.writeFileSync( + path.join(migrationsDir, "001_create_table.js"), + migrationFile, + ) + // dbDir is a file, so writing the generated types fails after the + // gateway has started + const dbDir = path.join(tmp, "db") + fs.writeFileSync(dbDir, "") + + const prevDbUrl = process.env.DATABASE_URL + process.env.DATABASE_URL = "postgres://sentinel" + try { + await expect( + generate({ + schemas: ["public"], + defaultDatabase: "postgres", + dbDir, + migrationsDir, + pglite: true, + }), + ).rejects.toThrow() + expect(process.env.DATABASE_URL).toBe("postgres://sentinel") + } finally { + if (prevDbUrl === undefined) delete process.env.DATABASE_URL + else process.env.DATABASE_URL = prevDbUrl + fs.rmSync(tmp, { recursive: true, force: true }) + } +}, 30_000)