From 3612ceb563f1205b291680c31cab7d28add40661 Mon Sep 17 00:00:00 2001 From: moxiaoshen567 Date: Tue, 25 Aug 2026 04:20:11 +0000 Subject: [PATCH 1/2] feat: default generate command to use pglite without requiring external postgres --- README.md | 2 +- src/cli.ts | 11 +++- src/generate.ts | 2 +- tests/cli.test.ts | 64 +++++++++++++++++++++ tests/generate.pglite.test.ts | 105 +++++++++++++++++++++++----------- tests/pglite.test.ts | 58 +++++++++++-------- 6 files changed, 180 insertions(+), 62 deletions(-) create mode 100644 tests/cli.test.ts diff --git a/README.md b/README.md index 20e3127..67ec42d 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 using an in-memory PGlite instance (no background Postgres required). Use `pgstrap generate --no-pglite` to run against an external Postgres database. - `npm run db:create-migration` - Create a new migration file ### Configuration diff --git a/src/cli.ts b/src/cli.ts index 9a9bdec..382a7df 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -2,7 +2,12 @@ import yargs from "yargs" import { migrate, reset, generate, createMigration, initPgstrap } from "./" import { getProjectContext } from "./get-project-context" -;(yargs as any) +const cli = + typeof (yargs as any) === "function" + ? (yargs as any)(process.argv.slice(2)) + : yargs + +cli .command("init", "initialize pgstrap", {}, async () => { await initPgstrap({ cwd: process.cwd(), @@ -35,10 +40,10 @@ import { getProjectContext } from "./get-project-context" "generate", "generate types and sql documentation from database", (yargs) => { - yargs.option("pglite", { type: "boolean", default: false }) + yargs.option("pglite", { type: "boolean", default: true }) }, async (argv) => { - generate({ ...(await getProjectContext()), pglite: !!argv.pglite }) + generate({ ...(await getProjectContext()), pglite: argv.pglite !== false }) }, ) .parse() diff --git a/src/generate.ts b/src/generate.ts index f337094..09294e8 100644 --- a/src/generate.ts +++ b/src/generate.ts @@ -12,7 +12,7 @@ export const generate = async ({ schemas, defaultDatabase, dbDir, - pglite = false, + pglite = true, migrationsDir, }: Pick & { pglite?: boolean diff --git a/tests/cli.test.ts b/tests/cli.test.ts new file mode 100644 index 0000000..42fe190 --- /dev/null +++ b/tests/cli.test.ts @@ -0,0 +1,64 @@ +import { test, expect } from "bun:test" +import fs from "fs" +import os from "os" +import path from "path" +import { execSync } from "child_process" + +const migrationFile = ` +exports.up = async (pgm) => { + pgm.createTable('cli_test_table', { id: 'id' }) +} +exports.down = async (pgm) => { + pgm.dropTable('cli_test_table') +} +` + +test( + "cli generate runs with pglite by default without postgres", + async () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "pgstrap-cli-test-")) + const dbDir = path.join(tmp, "src", "db") + const migrationsDir = path.join(dbDir, "migrations") + fs.mkdirSync(migrationsDir, { recursive: true }) + fs.writeFileSync( + path.join(migrationsDir, "001_cli_table.js"), + migrationFile, + ) + fs.writeFileSync( + path.join(tmp, "package.json"), + JSON.stringify({ name: "cli-test-pkg" }), + ) + fs.writeFileSync( + path.join(tmp, "pgstrap.config.js"), + `module.exports = { defaultDatabase: "test_db", schemas: ["public"] }`, + ) + + const cliPath = path.resolve(__dirname, "../src/cli.ts") + + // Run cli generate using bun without --pglite argument (relying on default: true) + execSync(`bun ${cliPath} generate`, { + cwd: tmp, + env: { + ...process.env, + // Ensure no DATABASE_URL is pointing to a live DB + DATABASE_URL: "", + }, + stdio: "pipe", + }) + + const zapatosFile = path.join(dbDir, "zapatos", "schema.d.ts") + const structureDir = path.join( + dbDir, + "structure", + "public", + "tables", + "cli_test_table", + ) + + expect(fs.existsSync(zapatosFile)).toBe(true) + expect(fs.existsSync(path.join(structureDir, "table.sql"))).toBe(true) + + fs.rmSync(tmp, { recursive: true, force: true }) + }, + 30000, +) diff --git a/tests/generate.pglite.test.ts b/tests/generate.pglite.test.ts index 56dcd53..fc89a77 100644 --- a/tests/generate.pglite.test.ts +++ b/tests/generate.pglite.test.ts @@ -13,35 +13,76 @@ exports.down = async (pgm) => { } ` -test("generate with pglite runs migrations and dumps structure", 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, - ) - - await generate({ - schemas: ["public"], - defaultDatabase: "postgres", - dbDir: path.join(tmp, "db"), - migrationsDir, - pglite: true, - }) - - const zapatosFile = path.join(tmp, "db", "zapatos", "schema.d.ts") - const structureDir = path.join( - tmp, - "db", - "structure", - "public", - "tables", - "foo", - ) - - expect(fs.existsSync(zapatosFile)).toBe(true) - expect(fs.existsSync(path.join(structureDir, "table.sql"))).toBe(true) - - fs.rmSync(tmp, { recursive: true, force: true }) -}) +test( + "generate with pglite runs migrations and dumps structure", + 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, + ) + + await generate({ + schemas: ["public"], + defaultDatabase: "postgres", + dbDir: path.join(tmp, "db"), + migrationsDir, + pglite: true, + }) + + const zapatosFile = path.join(tmp, "db", "zapatos", "schema.d.ts") + const structureDir = path.join( + tmp, + "db", + "structure", + "public", + "tables", + "foo", + ) + + expect(fs.existsSync(zapatosFile)).toBe(true) + expect(fs.existsSync(path.join(structureDir, "table.sql"))).toBe(true) + + fs.rmSync(tmp, { recursive: true, force: true }) + }, + 30000, +) + +test( + "generate defaults to pglite without requiring external postgres", + async () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "pgstrap-generate-default-")) + const migrationsDir = path.join(tmp, "migrations") + fs.mkdirSync(migrationsDir, { recursive: true }) + fs.writeFileSync( + path.join(migrationsDir, "001_create_table.js"), + migrationFile, + ) + + // Call generate without passing `pglite` option - should default to true + await generate({ + schemas: ["public"], + defaultDatabase: "postgres", + dbDir: path.join(tmp, "db"), + migrationsDir, + }) + + const zapatosFile = path.join(tmp, "db", "zapatos", "schema.d.ts") + const structureDir = path.join( + tmp, + "db", + "structure", + "public", + "tables", + "foo", + ) + + expect(fs.existsSync(zapatosFile)).toBe(true) + expect(fs.existsSync(path.join(structureDir, "table.sql"))).toBe(true) + + fs.rmSync(tmp, { recursive: true, force: true }) + }, + 30000, +) diff --git a/tests/pglite.test.ts b/tests/pglite.test.ts index 2fbc8bc..8486044 100644 --- a/tests/pglite.test.ts +++ b/tests/pglite.test.ts @@ -4,13 +4,15 @@ import { migrate } from "../src/migrate" import fs from "fs" import path from "path" -test("migration of a pglite db works", async () => { - const client = new PGlite() +test( + "migration of a pglite db works", + async () => { + const client = new PGlite() - // Create a temporary migration file - const migrationsDir = path.join(__dirname, "temp_migrations") - fs.mkdirSync(migrationsDir, { recursive: true }) - const migrationContent = ` + // Create a temporary migration file + const migrationsDir = path.join(__dirname, "temp_migrations") + fs.mkdirSync(migrationsDir, { recursive: true }) + const migrationContent = ` exports.up = async (pgm) => { pgm.createTable('test_table', { id: 'id', @@ -27,25 +29,31 @@ test("migration of a pglite db works", async () => { pgm.dropTable('test_table') } ` - fs.writeFileSync( - path.join(migrationsDir, "001_create_test_table.js"), - migrationContent, - ) + fs.writeFileSync( + path.join(migrationsDir, "001_create_test_table.js"), + migrationContent, + ) - // Run migrations - await migrate({ - client: client as any, - defaultDatabase: "test_db", - migrationsDir, - cwd: __dirname, - schemas: ["public"], - }) + // Run migrations + await migrate({ + client: client as any, + defaultDatabase: "test_db", + migrationsDir, + cwd: __dirname, + schemas: ["public"], + }) - // Verify that the table was created - const result = await client.query("SELECT * FROM test_table") - expect(result.fields.length).toBe(3) - expect(result.fields.map((f) => f.name)).toEqual(["id", "name", "created_at"]) + // Verify that the table was created + const result = await client.query("SELECT * FROM test_table") + expect(result.fields.length).toBe(3) + expect(result.fields.map((f) => f.name)).toEqual([ + "id", + "name", + "created_at", + ]) - // Clean up - fs.rmSync(migrationsDir, { recursive: true, force: true }) -}) + // Clean up + fs.rmSync(migrationsDir, { recursive: true, force: true }) + }, + 30000, +) From 119219a1d69b9ed1f39000910ae89978838a7e35 Mon Sep 17 00:00:00 2001 From: moxiaoshen567 Date: Tue, 25 Aug 2026 07:40:35 +0000 Subject: [PATCH 2/2] style: format with biome --- bun.lock | 1 + src/cli.ts | 5 +- tests/cli.test.ts | 81 +++++++++++----------- tests/generate.pglite.test.ts | 122 ++++++++++++++++------------------ tests/pglite.test.ts | 58 +++++++--------- 5 files changed, 125 insertions(+), 142 deletions(-) diff --git a/bun.lock b/bun.lock index 83407d9..e43bb07 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "pgstrap", diff --git a/src/cli.ts b/src/cli.ts index 382a7df..d713776 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -43,7 +43,10 @@ cli yargs.option("pglite", { type: "boolean", default: true }) }, async (argv) => { - generate({ ...(await getProjectContext()), pglite: argv.pglite !== false }) + generate({ + ...(await getProjectContext()), + pglite: argv.pglite !== false, + }) }, ) .parse() diff --git a/tests/cli.test.ts b/tests/cli.test.ts index 42fe190..909a76c 100644 --- a/tests/cli.test.ts +++ b/tests/cli.test.ts @@ -13,52 +13,45 @@ exports.down = async (pgm) => { } ` -test( - "cli generate runs with pglite by default without postgres", - async () => { - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "pgstrap-cli-test-")) - const dbDir = path.join(tmp, "src", "db") - const migrationsDir = path.join(dbDir, "migrations") - fs.mkdirSync(migrationsDir, { recursive: true }) - fs.writeFileSync( - path.join(migrationsDir, "001_cli_table.js"), - migrationFile, - ) - fs.writeFileSync( - path.join(tmp, "package.json"), - JSON.stringify({ name: "cli-test-pkg" }), - ) - fs.writeFileSync( - path.join(tmp, "pgstrap.config.js"), - `module.exports = { defaultDatabase: "test_db", schemas: ["public"] }`, - ) +test("cli generate runs with pglite by default without postgres", async () => { + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "pgstrap-cli-test-")) + const dbDir = path.join(tmp, "src", "db") + const migrationsDir = path.join(dbDir, "migrations") + fs.mkdirSync(migrationsDir, { recursive: true }) + fs.writeFileSync(path.join(migrationsDir, "001_cli_table.js"), migrationFile) + fs.writeFileSync( + path.join(tmp, "package.json"), + JSON.stringify({ name: "cli-test-pkg" }), + ) + fs.writeFileSync( + path.join(tmp, "pgstrap.config.js"), + `module.exports = { defaultDatabase: "test_db", schemas: ["public"] }`, + ) - const cliPath = path.resolve(__dirname, "../src/cli.ts") + const cliPath = path.resolve(__dirname, "../src/cli.ts") - // Run cli generate using bun without --pglite argument (relying on default: true) - execSync(`bun ${cliPath} generate`, { - cwd: tmp, - env: { - ...process.env, - // Ensure no DATABASE_URL is pointing to a live DB - DATABASE_URL: "", - }, - stdio: "pipe", - }) + // Run cli generate using bun without --pglite argument (relying on default: true) + execSync(`bun ${cliPath} generate`, { + cwd: tmp, + env: { + ...process.env, + // Ensure no DATABASE_URL is pointing to a live DB + DATABASE_URL: "", + }, + stdio: "pipe", + }) - const zapatosFile = path.join(dbDir, "zapatos", "schema.d.ts") - const structureDir = path.join( - dbDir, - "structure", - "public", - "tables", - "cli_test_table", - ) + const zapatosFile = path.join(dbDir, "zapatos", "schema.d.ts") + const structureDir = path.join( + dbDir, + "structure", + "public", + "tables", + "cli_test_table", + ) - expect(fs.existsSync(zapatosFile)).toBe(true) - expect(fs.existsSync(path.join(structureDir, "table.sql"))).toBe(true) + expect(fs.existsSync(zapatosFile)).toBe(true) + expect(fs.existsSync(path.join(structureDir, "table.sql"))).toBe(true) - fs.rmSync(tmp, { recursive: true, force: true }) - }, - 30000, -) + fs.rmSync(tmp, { recursive: true, force: true }) +}, 30000) diff --git a/tests/generate.pglite.test.ts b/tests/generate.pglite.test.ts index fc89a77..9b30f90 100644 --- a/tests/generate.pglite.test.ts +++ b/tests/generate.pglite.test.ts @@ -13,76 +13,70 @@ exports.down = async (pgm) => { } ` -test( - "generate with pglite runs migrations and dumps structure", - 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, - ) +test("generate with pglite runs migrations and dumps structure", 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, + ) - await generate({ - schemas: ["public"], - defaultDatabase: "postgres", - dbDir: path.join(tmp, "db"), - migrationsDir, - pglite: true, - }) + await generate({ + schemas: ["public"], + defaultDatabase: "postgres", + dbDir: path.join(tmp, "db"), + migrationsDir, + pglite: true, + }) - const zapatosFile = path.join(tmp, "db", "zapatos", "schema.d.ts") - const structureDir = path.join( - tmp, - "db", - "structure", - "public", - "tables", - "foo", - ) + const zapatosFile = path.join(tmp, "db", "zapatos", "schema.d.ts") + const structureDir = path.join( + tmp, + "db", + "structure", + "public", + "tables", + "foo", + ) - expect(fs.existsSync(zapatosFile)).toBe(true) - expect(fs.existsSync(path.join(structureDir, "table.sql"))).toBe(true) + expect(fs.existsSync(zapatosFile)).toBe(true) + expect(fs.existsSync(path.join(structureDir, "table.sql"))).toBe(true) - fs.rmSync(tmp, { recursive: true, force: true }) - }, - 30000, -) + fs.rmSync(tmp, { recursive: true, force: true }) +}, 30000) -test( - "generate defaults to pglite without requiring external postgres", - async () => { - const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "pgstrap-generate-default-")) - const migrationsDir = path.join(tmp, "migrations") - fs.mkdirSync(migrationsDir, { recursive: true }) - fs.writeFileSync( - path.join(migrationsDir, "001_create_table.js"), - migrationFile, - ) +test("generate defaults to pglite without requiring external postgres", async () => { + const tmp = fs.mkdtempSync( + path.join(os.tmpdir(), "pgstrap-generate-default-"), + ) + const migrationsDir = path.join(tmp, "migrations") + fs.mkdirSync(migrationsDir, { recursive: true }) + fs.writeFileSync( + path.join(migrationsDir, "001_create_table.js"), + migrationFile, + ) - // Call generate without passing `pglite` option - should default to true - await generate({ - schemas: ["public"], - defaultDatabase: "postgres", - dbDir: path.join(tmp, "db"), - migrationsDir, - }) + // Call generate without passing `pglite` option - should default to true + await generate({ + schemas: ["public"], + defaultDatabase: "postgres", + dbDir: path.join(tmp, "db"), + migrationsDir, + }) - const zapatosFile = path.join(tmp, "db", "zapatos", "schema.d.ts") - const structureDir = path.join( - tmp, - "db", - "structure", - "public", - "tables", - "foo", - ) + const zapatosFile = path.join(tmp, "db", "zapatos", "schema.d.ts") + const structureDir = path.join( + tmp, + "db", + "structure", + "public", + "tables", + "foo", + ) - expect(fs.existsSync(zapatosFile)).toBe(true) - expect(fs.existsSync(path.join(structureDir, "table.sql"))).toBe(true) + expect(fs.existsSync(zapatosFile)).toBe(true) + expect(fs.existsSync(path.join(structureDir, "table.sql"))).toBe(true) - fs.rmSync(tmp, { recursive: true, force: true }) - }, - 30000, -) + fs.rmSync(tmp, { recursive: true, force: true }) +}, 30000) diff --git a/tests/pglite.test.ts b/tests/pglite.test.ts index 8486044..a1b0966 100644 --- a/tests/pglite.test.ts +++ b/tests/pglite.test.ts @@ -4,15 +4,13 @@ import { migrate } from "../src/migrate" import fs from "fs" import path from "path" -test( - "migration of a pglite db works", - async () => { - const client = new PGlite() +test("migration of a pglite db works", async () => { + const client = new PGlite() - // Create a temporary migration file - const migrationsDir = path.join(__dirname, "temp_migrations") - fs.mkdirSync(migrationsDir, { recursive: true }) - const migrationContent = ` + // Create a temporary migration file + const migrationsDir = path.join(__dirname, "temp_migrations") + fs.mkdirSync(migrationsDir, { recursive: true }) + const migrationContent = ` exports.up = async (pgm) => { pgm.createTable('test_table', { id: 'id', @@ -29,31 +27,25 @@ test( pgm.dropTable('test_table') } ` - fs.writeFileSync( - path.join(migrationsDir, "001_create_test_table.js"), - migrationContent, - ) + fs.writeFileSync( + path.join(migrationsDir, "001_create_test_table.js"), + migrationContent, + ) - // Run migrations - await migrate({ - client: client as any, - defaultDatabase: "test_db", - migrationsDir, - cwd: __dirname, - schemas: ["public"], - }) + // Run migrations + await migrate({ + client: client as any, + defaultDatabase: "test_db", + migrationsDir, + cwd: __dirname, + schemas: ["public"], + }) - // Verify that the table was created - const result = await client.query("SELECT * FROM test_table") - expect(result.fields.length).toBe(3) - expect(result.fields.map((f) => f.name)).toEqual([ - "id", - "name", - "created_at", - ]) + // Verify that the table was created + const result = await client.query("SELECT * FROM test_table") + expect(result.fields.length).toBe(3) + expect(result.fields.map((f) => f.name)).toEqual(["id", "name", "created_at"]) - // Clean up - fs.rmSync(migrationsDir, { recursive: true, force: true }) - }, - 30000, -) + // Clean up + fs.rmSync(migrationsDir, { recursive: true, force: true }) +}, 30000)