ci workflow (expressjs/compression)
The ci workflow from expressjs/compression, explained and optimized by Latchkey.
CI health: C - fair
Point runs-on at Latchkey and get caching, job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the ci workflow from the expressjs/compression 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: ci
on:
pull_request:
branches:
- master
paths-ignore:
- '*.md'
push:
paths-ignore:
- '*.md'
permissions:
contents: read
# Cancel in progress workflows
# in the scenario where we already had a run going for that PR/branch/tag but then triggered a new run
concurrency:
group: "${{ github.workflow }} ✨ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
cancel-in-progress: true
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Setup Node.js
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0
with:
node-version: 'lts/*'
- name: Install dependencies
run: npm install --ignore-scripts --only=dev
- name: Run lint
run: npm run lint
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
name:
- Node.js 0.8
- Node.js 0.10
- Node.js 0.12
- io.js 1.x
- io.js 2.x
- io.js 3.x
- Node.js 4.x
- Node.js 5.x
- Node.js 6.x
- Node.js 7.x
- Node.js 8.x
- Node.js 9.x
- Node.js 10.x
- Node.js 11.x
- Node.js 12.x
- Node.js 13.x
- Node.js 14.x
- Node.js 15.x
- Node.js 16.x
- Node.js 17.x
- Node.js 18.x
- Node.js 19.x
- Node.js 20.x
- Node.js 21.x
- Node.js 22.x
- Node.js 23.x
- Node.js 24.x
- Node.js 25.x
include:
- name: Node.js 0.8
node-version: "0.8"
npm-i: mocha@2.5.3 supertest@1.1.0
npm-rm: nyc
- name: Node.js 0.10
node-version: "0.10"
npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0
- name: Node.js 0.12
node-version: "0.12"
npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0
- name: io.js 1.x
node-version: "1.8"
npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0
- name: io.js 2.x
node-version: "2.5"
npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0
- name: io.js 3.x
node-version: "3.3"
npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0
- name: Node.js 4.x
node-version: "4"
npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2
- name: Node.js 5.x
node-version: "5"
npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2
- name: Node.js 6.x
node-version: "6"
npm-i: mocha@6.2.3 nyc@14.1.1 supertest@6.1.6
- name: Node.js 7.x
node-version: "7"
npm-i: mocha@6.2.3 nyc@14.1.1 supertest@6.1.6
- name: Node.js 8.x
node-version: "8"
npm-i: mocha@7.2.0 nyc@14.1.1 supertest@6.1.6
- name: Node.js 9.x
node-version: "9"
npm-i: mocha@7.2.0 nyc@14.1.1 supertest@6.1.6
- name: Node.js 10.x
node-version: "10"
npm-i: mocha@8.4.0 supertest@6.1.6
- name: Node.js 11.x
node-version: "11"
npm-i: mocha@8.4.0 supertest@6.1.6
- name: Node.js 12.x
node-version: "12"
npm-i: "supertest@6.1.6"
- name: Node.js 13.x
node-version: "13"
npm-i: "supertest@6.1.6"
- name: Node.js 14.x
node-version: "14"
- name: Node.js 15.x
node-version: "15"
npm-i: "supertest@6.1.6"
- name: Node.js 16.x
node-version: "16"
- name: Node.js 17.x
node-version: "17"
- name: Node.js 18.x
node-version: "18"
- name: Node.js 19.x
node-version: "19"
- name: Node.js 20.x
node-version: "20"
- name: Node.js 21.x
node-version: "21"
- name: Node.js 22.x
node-version: "22"
- name: Node.js 23.x
node-version: "23"
- name: Node.js 24.x
node-version: "24"
- name: Node.js 25.x
node-version: "25"
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Install Node.js ${{ matrix.node-version }}
shell: bash -eo pipefail -l {0}
run: |
nvm install --default ${{ matrix.node-version }}
if [[ "${{ matrix.node-version }}" == 0.* && "$(cut -d. -f2 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then
nvm install --alias=npm 0.10
nvm use ${{ matrix.node-version }}
sed -i '1s;^.*$;'"$(printf '#!%q' "$(nvm which npm)")"';' "$(readlink -f "$(which npm)")"
npm config set strict-ssl false
fi
dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH"
- name: Configure npm
run: |
if [[ "$(npm config get package-lock)" == "true" ]]; then
npm config set package-lock false
else
npm config set shrinkwrap false
fi
- name: Remove npm module(s) ${{ matrix.npm-rm }}
run: npm rm --silent --save-dev ${{ matrix.npm-rm }}
if: matrix.npm-rm != ''
- name: Install npm module(s) ${{ matrix.npm-i }}
run: npm install --save-dev ${{ matrix.npm-i }}
if: matrix.npm-i != ''
- name: Setup Node.js version-specific dependencies
shell: bash
run: |
# eslint for linting
# - remove on Node.js < 10
if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then
node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \
grep -E '^eslint(-|$)' | \
sort -r | \
xargs -n1 npm rm --silent --save-dev
fi
- name: Install Node.js dependencies
run: npm install
- name: List environment
id: list_env
shell: bash
run: |
echo "node@$(node -v)"
echo "npm@$(npm -v)"
npm -s ls ||:
(npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print "::set-output name=" $2 "::" $3 }'
- name: Run tests
shell: bash
run: |
if npm -ps ls nyc | grep -q nyc; then
npm run test-ci
else
npm test
fi
- name: Upload code coverage
if: steps.list_env.outputs.nyc != ''
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
with:
name: coverage-node-${{ matrix.node-version }}
path: ./coverage/lcov.info
retention-days: 1
coverage:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
checks: write
steps:
- uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
- name: Install lcov
shell: bash
run: sudo apt-get -y install lcov
- name: Collect coverage reports
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
path: ./coverage
pattern: coverage-node-*
- name: Merge coverage reports
shell: bash
run: find ./coverage -name lcov.info -exec printf '-a %q\n' {} \; | xargs lcov -o ./lcov.info
- name: Upload coverage report
uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
file: ./lcov.infoThe same workflow, on Latchkey
Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.
name: ci on: pull_request: branches: - master paths-ignore: - '*.md' push: paths-ignore: - '*.md' permissions: contents: read # Cancel in progress workflows # in the scenario where we already had a run going for that PR/branch/tag but then triggered a new run concurrency: group: "${{ github.workflow }} ✨ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" cancel-in-progress: true jobs: lint: timeout-minutes: 30 name: Lint runs-on: latchkey-small steps: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Setup Node.js uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903 # v6.0.0 with: cache: 'npm' node-version: 'lts/*' - name: Install dependencies run: npm install --ignore-scripts --only=dev - name: Run lint run: npm run lint test: timeout-minutes: 30 runs-on: latchkey-small strategy: fail-fast: false matrix: name: - Node.js 0.8 - Node.js 0.10 - Node.js 0.12 - io.js 1.x - io.js 2.x - io.js 3.x - Node.js 4.x - Node.js 5.x - Node.js 6.x - Node.js 7.x - Node.js 8.x - Node.js 9.x - Node.js 10.x - Node.js 11.x - Node.js 12.x - Node.js 13.x - Node.js 14.x - Node.js 15.x - Node.js 16.x - Node.js 17.x - Node.js 18.x - Node.js 19.x - Node.js 20.x - Node.js 21.x - Node.js 22.x - Node.js 23.x - Node.js 24.x - Node.js 25.x include: - name: Node.js 0.8 node-version: "0.8" npm-i: mocha@2.5.3 supertest@1.1.0 npm-rm: nyc - name: Node.js 0.10 node-version: "0.10" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 - name: Node.js 0.12 node-version: "0.12" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 - name: io.js 1.x node-version: "1.8" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 - name: io.js 2.x node-version: "2.5" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 - name: io.js 3.x node-version: "3.3" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 - name: Node.js 4.x node-version: "4" npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2 - name: Node.js 5.x node-version: "5" npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2 - name: Node.js 6.x node-version: "6" npm-i: mocha@6.2.3 nyc@14.1.1 supertest@6.1.6 - name: Node.js 7.x node-version: "7" npm-i: mocha@6.2.3 nyc@14.1.1 supertest@6.1.6 - name: Node.js 8.x node-version: "8" npm-i: mocha@7.2.0 nyc@14.1.1 supertest@6.1.6 - name: Node.js 9.x node-version: "9" npm-i: mocha@7.2.0 nyc@14.1.1 supertest@6.1.6 - name: Node.js 10.x node-version: "10" npm-i: mocha@8.4.0 supertest@6.1.6 - name: Node.js 11.x node-version: "11" npm-i: mocha@8.4.0 supertest@6.1.6 - name: Node.js 12.x node-version: "12" npm-i: "supertest@6.1.6" - name: Node.js 13.x node-version: "13" npm-i: "supertest@6.1.6" - name: Node.js 14.x node-version: "14" - name: Node.js 15.x node-version: "15" npm-i: "supertest@6.1.6" - name: Node.js 16.x node-version: "16" - name: Node.js 17.x node-version: "17" - name: Node.js 18.x node-version: "18" - name: Node.js 19.x node-version: "19" - name: Node.js 20.x node-version: "20" - name: Node.js 21.x node-version: "21" - name: Node.js 22.x node-version: "22" - name: Node.js 23.x node-version: "23" - name: Node.js 24.x node-version: "24" - name: Node.js 25.x node-version: "25" steps: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Install Node.js ${{ matrix.node-version }} shell: bash -eo pipefail -l {0} run: | nvm install --default ${{ matrix.node-version }} if [[ "${{ matrix.node-version }}" == 0.* && "$(cut -d. -f2 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then nvm install --alias=npm 0.10 nvm use ${{ matrix.node-version }} sed -i '1s;^.*$;'"$(printf '#!%q' "$(nvm which npm)")"';' "$(readlink -f "$(which npm)")" npm config set strict-ssl false fi dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH" - name: Configure npm run: | if [[ "$(npm config get package-lock)" == "true" ]]; then npm config set package-lock false else npm config set shrinkwrap false fi - name: Remove npm module(s) ${{ matrix.npm-rm }} run: npm rm --silent --save-dev ${{ matrix.npm-rm }} if: matrix.npm-rm != '' - name: Install npm module(s) ${{ matrix.npm-i }} run: npm install --save-dev ${{ matrix.npm-i }} if: matrix.npm-i != '' - name: Setup Node.js version-specific dependencies shell: bash run: | # eslint for linting # - remove on Node.js < 10 if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ grep -E '^eslint(-|$)' | \ sort -r | \ xargs -n1 npm rm --silent --save-dev fi - name: Install Node.js dependencies run: npm install - name: List environment id: list_env shell: bash run: | echo "node@$(node -v)" echo "npm@$(npm -v)" npm -s ls ||: (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print "::set-output name=" $2 "::" $3 }' - name: Run tests shell: bash run: | if npm -ps ls nyc | grep -q nyc; then npm run test-ci else npm test fi - name: Upload code coverage if: steps.list_env.outputs.nyc != '' uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0 with: name: coverage-node-${{ matrix.node-version }} path: ./coverage/lcov.info retention-days: 1 coverage: timeout-minutes: 30 needs: test runs-on: latchkey-small permissions: contents: read checks: write steps: - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 - name: Install lcov shell: bash run: sudo apt-get -y install lcov - name: Collect coverage reports uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0 with: path: ./coverage pattern: coverage-node-* - name: Merge coverage reports shell: bash run: find ./coverage -name lcov.info -exec printf '-a %q\n' {} \; | xargs lcov -o ./lcov.info - name: Upload coverage report uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7 with: github-token: ${{ secrets.GITHUB_TOKEN }} file: ./lcov.info
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. - Cache dependency installs on the setup step so they are served from cache.
- Add a job timeout so a hung step cannot burn hours of runner time.
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
This workflow runs 3 jobs (30 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.