A GitHub Actions CI workflow builder for Node.js that generates a valid .github/workflows/ci.yml. Pick the Node versions to test, your package manager, and which jobs to run, and it produces a workflow with a version matrix, dependency caching, and lint, test, and build steps.
How the generated workflow is structured
The workflow triggers on push and pull_request to the branch you choose. It defines a single build job on ubuntu-latest with a strategy.matrix.node-version listing your versions, so the job runs once per version in parallel. fail-fast: false keeps the other versions running even if one fails, so you see every failure at once rather than stopping at the first.
Each matrix run executes these steps in order:
actions/checkout@v4— clones the repositoryactions/setup-node@v4withcache: npm|yarn|pnpm— installs Node and sets up dependency caching keyed on the lockfile- Install —
npm ci,yarn install --frozen-lockfile, orpnpm install --frozen-lockfile(for pnpm, a precedingpnpm/action-setupstep is added automatically) - Lint (if enabled) — runs
npm run lint - Test — runs
npm test - Build (if enabled) — runs
npm run build
A typical generated workflow
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
Why npm ci instead of npm install?
npm ci installs exactly what is locked in package-lock.json. It fails fast if the lockfile and package.json disagree — catching mismatches before they cause mysterious test failures. It is also typically faster in CI because it skips resolution. The yarn and pnpm equivalents use --frozen-lockfile for the same guarantee.
Practical tips
- Choose your matrix wisely. Testing on the oldest and newest Node versions you support (for example 18 and 22) catches most compatibility issues without using extra minutes. If you maintain a library supporting many versions, add the LTS releases in between.
- Script names must exist. The lint and build steps run your
package.jsonscripts by those exact names. Untick any step you do not have a script for. - Add a deploy job downstream. To publish or deploy after the matrix passes, add a second job with
needs: buildand the conditionif: github.ref == 'refs/heads/main'so it only runs on the main branch, not on every PR. - Pin major action versions.
actions/checkout@v4andactions/setup-node@v4are pinned to major versions. Dependabot can keep these updated automatically — enable it under your repository’s Code Security settings.
Common issues and how to fix them
“Missing script: lint” failure — the lint step runs npm run lint, which requires a lint script in package.json. If your script is named differently (e.g. eslint), update the step’s run command. Same applies to build.
Cache misses on every run — the setup-node cache key is derived from the lockfile. If you commit package.json changes without updating the lockfile, or if you do not have a lockfile at all, the cache never hits. Always commit package-lock.json, yarn.lock, or pnpm-lock.yaml.
pnpm workflow fails at the install step — pnpm requires a pnpm/action-setup step before setup-node. The generated workflow includes it automatically, but if you add pnpm to an existing workflow manually, this step is commonly forgotten.
The matrix takes too long — if you are testing on many Node versions and the workflow is slow, consider using the --if-present flag (npm run lint --if-present) to skip steps gracefully, or reduce the matrix to just the LTS versions most relevant to your users.