Node CI workflow (decaporg/decap-cms)
The Node CI workflow from decaporg/decap-cms, explained and optimized by Latchkey.
CI health: B - good
Point runs-on at Latchkey and get job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Node CI workflow from the decaporg/decap-cms repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: Node CI
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
push:
branches:
- main
- master
pull_request:
types: [opened, synchronize, reopened]
jobs:
changes:
runs-on: ubuntu-latest
outputs:
cms: ${{ steps.filter.outputs.cms }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
id: filter
with:
filters: |
cms:
- '!website/**'
build-unit:
needs: changes
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node-version: [24.x]
include:
- os: ubuntu-latest
node-version: 22.x
fail-fast: true
if: ${{ needs.changes.outputs.cms == 'true' }}
steps:
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
check-latest: true
cache: 'npm'
- name: log versions
run: node --version && npm --version
- name: install dependencies (skip Cypress binary)
run: npm ci
env:
CYPRESS_INSTALL_BINARY: 0
- name: run lint + types + unit tests
run: npm run test:ci
e2e:
needs: changes
if: ${{ needs.changes.outputs.cms == 'true' }}
runs-on: depot-ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Use Node.js 24.x
uses: actions/setup-node@v4
with:
node-version: 24.x
check-latest: true
cache: 'npm'
- name: Cache Nx local cache
uses: actions/cache@v4
with:
path: .nx/cache
key: nx-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
nx-${{ runner.os }}-
- name: Cache Cypress binary
uses: actions/cache@v4
with:
path: ~/.cache/Cypress
key: cypress-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
cypress-${{ runner.os }}-
- name: install dependencies
run: npm ci
- name: build demo site
run: npm run build:demo
- name: test package integrity
run: npm run test:package-integrity
- name: run e2e tests
run: npm run test:e2e:run-ci
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
NODE_OPTIONS: --max-old-space-size=4096
TZ: Europe/Amsterdam
GITHUB_EVENT_NAME: ${{ github.event_name }}
GITHUB_BASE_REF: ${{ github.base_ref }}
- uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-results
path: |
cypress/screenshots
cypress/videos
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: Node CI concurrency: group: ci-${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true on: push: branches: - main - master pull_request: types: [opened, synchronize, reopened] jobs: changes: timeout-minutes: 30 runs-on: latchkey-small outputs: cms: ${{ steps.filter.outputs.cms }} steps: - uses: actions/checkout@v4 - uses: dorny/paths-filter@v2 id: filter with: filters: | cms: - '!website/**' build-unit: timeout-minutes: 30 needs: changes runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] node-version: [24.x] include: - os: ubuntu-latest node-version: 22.x fail-fast: true if: ${{ needs.changes.outputs.cms == 'true' }} steps: - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} check-latest: true cache: 'npm' - name: log versions run: node --version && npm --version - name: install dependencies (skip Cypress binary) run: npm ci env: CYPRESS_INSTALL_BINARY: 0 - name: run lint + types + unit tests run: npm run test:ci e2e: timeout-minutes: 30 needs: changes if: ${{ needs.changes.outputs.cms == 'true' }} runs-on: depot-ubuntu-24.04 steps: - uses: actions/checkout@v4 - name: Use Node.js 24.x uses: actions/setup-node@v4 with: node-version: 24.x check-latest: true cache: 'npm' - name: Cache Nx local cache uses: actions/cache@v4 with: path: .nx/cache key: nx-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} restore-keys: | nx-${{ runner.os }}- - name: Cache Cypress binary uses: actions/cache@v4 with: path: ~/.cache/Cypress key: cypress-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} restore-keys: | cypress-${{ runner.os }}- - name: install dependencies run: npm ci - name: build demo site run: npm run build:demo - name: test package integrity run: npm run test:package-integrity - name: run e2e tests run: npm run test:e2e:run-ci env: CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} NODE_OPTIONS: --max-old-space-size=4096 TZ: Europe/Amsterdam GITHUB_EVENT_NAME: ${{ github.event_name }} GITHUB_BASE_REF: ${{ github.base_ref }} - uses: actions/upload-artifact@v4 if: failure() with: name: cypress-results path: | cypress/screenshots cypress/videos
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Add a job timeout so a hung step cannot burn hours of runner time.
1 third-party action is referenced by a movable tag. Pin it to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Dependency installs
- End-to-end and browser tests
This workflow runs 3 jobs (5 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.