← Files okrdevARCHIVED FILE
templates/github/workflows/ci.yml
4.65 KB · Oct 4, 2026 · 12:30 UTC
# ci — lint, typecheck, unit tests, build (okrdev stack module)
#
# Installs to: .github/workflows/ci.yml
#
# This file is part of the OPTIONAL stack module (Next.js + Vercel + Neon + Drizzle;
# see docs/stack.md). The okrdev method needs none of it — if your repo already has
# CI, keep yours. okr-gate.yml works alongside any CI.
#
# Scope: exactly the checks that should block a merge — lint, typecheck, Vitest,
# build. Playwright smoke tests are deliberately NOT here: they run against the
# Vercel preview URL after it deploys, and Drizzle migrations run against the
# preview's Neon branch in that same flow (see templates/stack/README.md).
#
# okrdev short-circuit: weekly okrdev state PRs (check-ins, triage ledgers)
# touch only okrdev/**, and burning a full CI run on them would be pure tax.
# The first step in the job detects an okrdev-only diff and lets every later
# step skip while "ci" still reports green. Read that step's comment before
# "simplifying" this with paths-ignore — you'd brick the required check.
#
# Expects these package.json scripts (templates/stack/README.md sets them up):
# lint, typecheck, test (as `vitest run`, not watch mode), build
#
# The job id is "ci" on purpose: templates/stack/branch-protection.sh registers
# "ci" as the required status check on main. Rename both together or neither.
name: ci
on:
pull_request:
push:
branches: [main]
# Why run on main too? Deploy = merge, so main must always be green. This run
# is the tripwire that says so — or says exactly when it stopped being true.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true # a new push obsoletes the old run; don't pay for both
permissions:
contents: read
jobs:
ci:
name: ci
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2 # the okrdev-only step below diffs HEAD against its parent
- name: okrdev-only short-circuit
id: okrdev-only
# okrdev state PRs change only okrdev/** — no code, so lint/typecheck/
# tests/build have nothing to say. Detect that here and let the steps
# below skip; the job still finishes and "ci" still reports green.
#
# NEVER use workflow-level `paths-ignore` on a required check instead:
# a skipped workflow never reports its status check, GitHub waits on
# "Expected" forever, and the PR is unmergeable. The short-circuit must
# live INSIDE the required job — exactly like this step.
run: |
# On pull_request, HEAD is the PR merge commit, so HEAD^1 is the base
# tip and the diff is exactly what the PR changes. On push it's the
# pushed commit against its parent. Can't compute the diff (first
# commit, force push)? Fail open: run the full job.
if changed=$(git diff --name-only HEAD^1 HEAD 2>/dev/null) &&
[ -n "$changed" ] &&
! grep -qv '^okrdev/' <<<"$changed"; then
echo "okrdev/** only — skipping lint, typecheck, tests, build."
echo "skip=true" >>"$GITHUB_OUTPUT"
else
echo "skip=false" >>"$GITHUB_OUTPUT"
fi
- uses: pnpm/action-setup@v4
if: steps.okrdev-only.outputs.skip != 'true'
# Reads the pnpm version from the "packageManager" field in package.json.
# create-next-app sets it when you pick pnpm; if it's missing, add e.g.
# "packageManager": "pnpm@9.15.0"
- uses: actions/setup-node@v4
if: steps.okrdev-only.outputs.skip != 'true'
with:
node-version: 24 # match your local Node major
cache: pnpm
- name: Install dependencies
if: steps.okrdev-only.outputs.skip != 'true'
run: pnpm install --frozen-lockfile
# --frozen-lockfile: CI installs exactly what the lockfile says or fails
# loudly. A drifted lockfile should break here, not in production.
- name: Lint
if: steps.okrdev-only.outputs.skip != 'true'
run: pnpm lint
- name: Typecheck
if: steps.okrdev-only.outputs.skip != 'true'
run: pnpm typecheck
- name: Unit tests (Vitest)
if: steps.okrdev-only.outputs.skip != 'true'
run: pnpm test
- name: Build
if: steps.okrdev-only.outputs.skip != 'true'
run: pnpm build
# The build should not need real secrets — previews and production get
# theirs from Vercel. If Next.js insists on an env var at build time, give
# it a harmless placeholder here rather than a secret:
# env:
# DATABASE_URL: postgres://user:placeholder@localhost:5432/placeholder
SHA-256: e5eab608aba46cbd41fd05cfdedad05eb1ab4aa222cc2e7377a46b53562dcb92