Fixing Three CI Failures in One Go — v1.0.75
Fixing Three CI Failures in One Go — v1.0.75
v1.0.75 is a pure infrastructure release. No features changed, no APIs shifted — just three independent CI failures that had stacked up and needed clearing before further development could land cleanly.
Here's what broke and how each one was fixed.
1. Duplicate DATABASE_URL in CI YAML
What happened: The CI workflow file (.github/workflows/ci.yml) defined DATABASE_URL twice under the env block. YAML does not allow duplicate keys at the same level, and GitHub Actions enforces this with a hard parse error — the entire workflow is rejected before a single step runs.
Fix: Removed the duplicate entry. One DATABASE_URL remains:
env:
DATABASE_URL: "postgresql://dummy:dummy@localhost:5432/dummy"
This is a one-line diff but it was blocking every CI run.
2. drizzle-kit push Refusing to Connect to a Dummy Database
What happened: The build script in package.json was:
"build": "yes '' | npx drizzle-kit push --force && next build"
This runs drizzle-kit push — which attempts a live connection to Postgres — before every next build. In a production or staging deployment, the real DATABASE_URL is set and this works fine. In CI, the URL points to a dummy local Postgres instance that doesn't exist.
The deeper issue is that @neondatabase/serverless (used by this project for Postgres access) only supports remote Neon, Vercel Postgres, and Supabase instances via WebSocket. It refuses to connect to a plain local Postgres URL, so it exits with code 1 — killing the build before next build even starts.
Fix: The build script now checks for a SKIP_DB_PUSH environment variable:
"build": "node -e \"if(!process.env.SKIP_DB_PUSH){require('child_process').execSync('yes \\\"\\\" | npx drizzle-kit push --force',{stdio:'inherit'})}\" && next build"
CI sets SKIP_DB_PUSH=true in the workflow env block:
env:
SKIP_DB_PUSH: "true"
Production deployments do not set this variable, so drizzle-kit push continues to run as before on real deployments. The schema push behaviour is unchanged outside of CI.
3. Missing dist Module in @saas-factory-live/shell Crashing Tests
What happened: tests/lib/griffin/client.test.ts imports from @saas-factory-live/shell/schema. The shell package's built dist/schema/users module was missing from the installed package, so Node threw a module-not-found error at import time — before Vitest could run a single assertion. The test file showed as a hard failure rather than a test failure.
Fix: Two lightweight mock files were added under src/__mocks__/:
src/__mocks__/shell-schema.ts — Defines the same Drizzle tables the shell package exports, using drizzle-orm/pg-core directly:
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
export const users = pgTable("users", {
id: text("id").primaryKey(),
name: text("name"),
email: text("email"),
createdAt: timestamp("created_at").defaultNow(),
updatedAt: timestamp("updated_at").defaultNow(),
});
// organizations, accounts, sessions, verificationTokens, organizationMembers ...
src/__mocks__/shell.ts — Re-exports everything from shell-schema:
export * from "./shell-schema";
Vitest is then configured in vitest.config.ts to resolve the shell package to these mocks:
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
"@saas-factory-live/shell/schema": path.resolve(
__dirname,
"./src/__mocks__/shell-schema.ts"
),
"@saas-factory-live/shell": path.resolve(
__dirname,
"./src/__mocks__/shell.ts"
),
},
},
The aliases are ordered longest-match-first (shell/schema before shell) so the sub-path import resolves correctly. Application code at runtime still uses the real shell package — these aliases are Vitest-only.
Result
All three fixes together restore a clean CI run. The pipeline now:
- Parses the workflow YAML without error
- Runs
next buildwithout attempting a database connection - Resolves shell package imports in tests without a missing-module crash
No application-level code was changed in this release.