Every new repo needs CI, and I kept rewriting the same YAML from memory and forgetting something each time. So here's the version I now copy everywhere, with notes on why each setting is there.
The workflow
Save as .github/workflows/ci.yml:
name: CI (Node.js)
on:
push:
branches: [main]
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint --if-present
- run: npm test --if-present
- run: npm run build --if-present
Four settings most templates skip
1. Cancel superseded runs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Push three fixes to a PR in a row and only the latest run finishes. Fewer wasted minutes and a faster signal. (For deploy workflows, set cancel-in-progress: false so a deploy is never cut off halfway.)
2. Least-privilege token
permissions:
contents: read
CI only needs to read your code. Saying so explicitly limits what a compromised dependency or action could do with GITHUB_TOKEN.
3. Built-in dependency caching
cache: npm on actions/setup-node caches downloads keyed on package-lock.json, with no extra actions/cache step. Pair it with npm ci for installs that exactly match your lockfile.
4. --if-present
npm run lint --if-present skips the step if the script doesn't exist, so the same file works on day one of a project and on day 500.
Bonus: lint your workflows
bash <(curl -sL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash)
./actionlint
actionlint catches bad expressions, typos in action inputs and (with shellcheck installed) bugs in your run: scripts before you push.
Get it
- 📄 Free commented version: https://8686702537545.gumroad.com/l/sqjvco
- 📦 If you want more, the ShipKit GitHub Actions Starter Pack has 15 workflows (Python/Go CI, OS matrix, semver releases, Docker → GHCR, Pages/Vercel/Fly deploys, CodeQL, Dependabot) plus repo templates: https://8686702537545.gumroad.com/l/clhec
What's the one thing you always add to CI? Let me know in the comments.
Top comments (0)