diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6379d579..034b1c56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,8 +29,13 @@ jobs: bun-version: 1.3.11 - name: Install run: bun install --frozen-lockfile - - name: Push migrations - run: bun run db:push + # Apply the committed migrations rather than `drizzle-kit push`. Two + # reasons: `push` diffs the schema against whatever is in the database and + # is a development tool, whereas CI wants exactly what is in + # `packages/core/migrations`; and `push` under `strict: true` asks for + # confirmation, which on a runner is a hang rather than a failure. + - name: Apply migrations + run: bun run db:migrate env: DATABASE_URL: postgres://postgres:postgres@localhost:5432/nestri - name: Test diff --git a/package.json b/package.json index a57e4ad7..3b3bb5ea 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,8 @@ "type": "module", "scripts": { "dev": "alchemy dev", + "db:migrate": "bun run --cwd packages/core db:migrate", + "db:push": "bun run --cwd packages/core db:push", "test": "test", "deploy:sandbox": "alchemy deploy --stage sandbox --yes", "deploy:production": "alchemy deploy --stage production --yes" diff --git a/packages/core/drizzle.config.ts b/packages/core/drizzle.config.ts index fda9b062..233a7f0a 100644 --- a/packages/core/drizzle.config.ts +++ b/packages/core/drizzle.config.ts @@ -1,5 +1,35 @@ import { defineConfig } from 'drizzle-kit'; +const url = process.env.DATABASE_URL ? new URL(process.env.DATABASE_URL) : null; + +/** + * Whether to speak TLS, decided by the connection string rather than by + * whether one exists. + * + * The previous form was `ssl: !!process.env.DATABASE_URL ? { rejectUnauthorized: + * false } : false`, which turned TLS on for *any* `DATABASE_URL` — so it failed + * against every plain Postgres, including CI's own `postgres:18-alpine` service + * container, and `drizzle-kit` reports that failure as a spinner and a non-zero + * exit with no message attached. Measured 2026-09-02: with the URL set, + * migrations fail silently; with it unset against the same database, all seven + * apply. + */ +function sslFor(u: URL | null): false | { rejectUnauthorized: boolean } { + if (!u) return false; + + // An explicit sslmode in the URL wins, always. + const mode = u.searchParams.get('sslmode'); + if (mode) { + if (mode === 'disable') return false; + return { rejectUnauthorized: mode === 'verify-full' }; + } + + // Otherwise infer: a local Postgres does not speak TLS at all, and a hosted + // one nearly always does — usually behind a chain we have no root for. + const local = ['localhost', '127.0.0.1', '::1', ''].includes(u.hostname); + return local ? false : { rejectUnauthorized: false }; +} + export default defineConfig({ verbose: true, strict: true, @@ -7,13 +37,11 @@ export default defineConfig({ dialect: 'postgresql', schema: './src/**/*.sql.ts', dbCredentials: { - host: process.env.DATABASE_URL ? new URL(process.env.DATABASE_URL).hostname : 'localhost', - port: process.env.DATABASE_URL ? Number(new URL(process.env.DATABASE_URL).port || 5432) : 5432, - user: process.env.DATABASE_URL ? new URL(process.env.DATABASE_URL).username : 'postgres', - password: process.env.DATABASE_URL ? new URL(process.env.DATABASE_URL).password : 'postgres', - database: process.env.DATABASE_URL - ? new URL(process.env.DATABASE_URL).pathname.slice(1) - : 'nestri', - ssl: !!process.env.DATABASE_URL ? { rejectUnauthorized: false } : false + host: url?.hostname || 'localhost', + port: Number(url?.port || 5432), + user: url?.username || 'postgres', + password: url?.password || 'postgres', + database: url?.pathname.slice(1) || 'nestri', + ssl: sslFor(url) } }); diff --git a/packages/core/package.json b/packages/core/package.json index f7ce8f9b..14c153f5 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,7 +13,8 @@ }, "scripts": { "db": "drizzle-kit", - "db:push": "drizzle-kit push" + "db:push": "drizzle-kit push", + "db:migrate": "drizzle-kit migrate" }, "dependencies": { "@nestri/auth": "workspace:",