CI and releases
The GitHub Actions workflows, the build matrix for every deploy target, Dependabot, Changesets releases and production database migrations.
Pull request checks#
.github/workflows/ci.yml runs on pushes to main and on every pull request. Each job checks
out the repository and runs the composite action in .github/actions/setup (pnpm, Node from
.nvmrc, pnpm install --frozen-lockfile) before its own steps:
| Job | What it runs |
|---|---|
quality |
pnpm lint, pnpm format:check, pnpm check, pnpm audit --audit-level=high |
unit |
pnpm test:coverage, uploading the coverage report as an artifact |
build |
pnpm build:<target> for each of node, vercel, netlify and cloudflare |
e2e |
pnpm build:node then pnpm test:e2e with a cached Playwright Chromium |
lighthouse |
pnpm lhci against the Node build |
The matrix is the guarantee behind “deploy anywhere”: a dependency that only works on one runtime fails the pull request instead of a production deploy.
How the test servers are started#
The e2e and lighthouse jobs start the built Node server on 127.0.0.1 explicitly
(HOST=127.0.0.1) instead of the adapter’s default localhost, which resolves to IPv6 first on
GitHub’s runners while the tests connect over IPv4. The Playwright web-server command also resets
and migrates .data/e2e.db before the server starts (Playwright launches the web server before
globalSetup), and the tracked .data/.gitkeep keeps that directory present on a fresh checkout.
Audit exceptions#
pnpm audit --audit-level=high fails the quality job on new high or critical advisories.
Transitive advisories with no upstream fix (currently extract-zip inside the Netlify adapter’s
development tooling, which never runs in production) are listed under auditConfig.ignoreGhsas
in pnpm-workspace.yaml. Patchable transitive packages are pinned through overrides in the same
file. Review both lists when upgrading dependencies.
Dependency updates#
.github/dependabot.yml opens grouped pull requests every Monday: Astro packages, React and
Motion, auth and data (Better Auth, Drizzle, libSQL, Resend), styling, tooling, and the GitHub
Actions used by the workflows and the composite action. Because the CI matrix builds every
target, merging an update is safe when the checks are green.
Two dependencies are held back on purpose: TypeScript stays on the 6.x line (~6.0.3) because
@astrojs/check and typescript-eslint do not yet support TypeScript 7, and @types/node follows
the Node 24 runtime pinned in engines and .nvmrc. Both are listed under ignore.
Newly published versions wait three days before Dependabot proposes them (cooldown). Dependabot
passes the same gate to pnpm while it rewrites the lockfile; pnpm 12 applies it only to the
packages being updated and keeps the versions already pinned in pnpm-lock.yaml. (pnpm 10 and 11
re-checked every lockfile entry, which made updates fail whenever any package was younger than
three days.) Locally, pnpm waits one day before resolving a newly published version
(minimumReleaseAge); pnpm install from the committed lockfile is unaffected.
Releases with Changesets#
-
When a change is user-facing, add a changeset with the pull request:
pnpm changeset -
Merge the pull request.
.github/workflows/release.ymlopens (or updates) a “Version Packages” pull request that bumpspackage.json, rewritesCHANGELOG.mdand deletes the consumed changesets. The repository setting “Allow GitHub Actions to create and approve pull requests” must be on for this. -
Merge the version pull request. The workflow tags the release, and the next deploy renders the new entry on
/changelog.
The site is a private package, so nothing is published to npm; privatePackages.tag in
.changeset/config.json still creates Git tags.
Database migrations#
.github/workflows/migrate.yml applies the migrations in drizzle/ to the production database
with pnpm db:migrate. Run it from the Actions tab after the first deployment and whenever you
want to apply pending migrations; it also runs automatically when a push to main adds or edits
a file under drizzle/. It reads the database URL and token from GitHub Actions secrets named
DATABASE_URL and DATABASE_AUTH_TOKEN or TURSO_DATABASE_URL and TURSO_AUTH_TOKEN, as
repository secrets or as secrets of the production environment the job references (add a
required-reviewer rule there if you want approvals). The first step logs which of the four
names are present, and the job refuses a file: URL.
Deploy previews#
Vercel and Netlify build every pull request automatically. Preview URLs are added to Better
Auth’s trusted origins at runtime (see src/lib/env.ts), so sign-in works on previews without
extra configuration.
Local pre-commit hook#
Husky runs lint-staged before every commit: ESLint with autofix and Prettier on staged files.
Skip it in an emergency with git commit --no-verify.