Skip to content
Latchkey

CI workflow (lovell/sharp)

The CI workflow from lovell/sharp, explained and optimized by Latchkey.

D

CI health: D - needs work

Point runs-on at Latchkey and get caching, run de-duplication, job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.

Grade your own workflow free or run it on Latchkey →
Source: lovell/sharp.github/workflows/ci.ymlLicense Apache-2.0View source

What it does

This is the CI workflow from the lovell/sharp repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.

Below, Latchkey shows a faster, safer version produced by its optimization engine.

The workflow

workflow (.yml)
name: CI
on:
  - push
  - pull_request
permissions: {}
jobs:
  lint:
    permissions:
      contents: read
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: "24"
      - run: npm install --ignore-scripts
      - run: npm run build:dist
      - run: npm run lint-cpp
      - run: npm run lint-js
      - run: npm run lint-types
      - run: npm run lint-publish
  build-native:
    permissions:
      contents: read
    needs: lint
    name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}"
    runs-on: ${{ matrix.os }}
    container: ${{ matrix.container }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-24.04
            container: rockylinux/rockylinux:8-ubi-init
            nodejs_arch: x64
            nodejs_version: "^20.9.0"
            nodejs_version_major: 20
            platform: linux-x64
          - os: ubuntu-24.04
            container: rockylinux/rockylinux:8-ubi-init
            nodejs_arch: x64
            nodejs_version: "^22"
            nodejs_version_major: 22
            platform: linux-x64
            package: true
          - os: ubuntu-24.04
            container: rockylinux/rockylinux:8-ubi-init
            nodejs_arch: x64
            nodejs_version: "^24"
            nodejs_version_major: 24
            platform: linux-x64
          - os: ubuntu-24.04
            container: node:20-alpine3.20
            nodejs_version_major: 20
            platform: linuxmusl-x64
          - os: ubuntu-24.04
            container: node:22-alpine3.20
            nodejs_version_major: 22
            platform: linuxmusl-x64
            package: true
          - os: ubuntu-24.04
            container: node:24-alpine3.22
            nodejs_version_major: 24
            platform: linuxmusl-x64
          - os: ubuntu-24.04-arm
            container: rockylinux/rockylinux:8-ubi-init
            nodejs_arch: arm64
            nodejs_version: "^20.9.0"
            nodejs_version_major: 20
            platform: linux-arm64
          - os: ubuntu-24.04-arm
            container: rockylinux/rockylinux:8-ubi-init
            nodejs_arch: arm64
            nodejs_version: "^22"
            nodejs_version_major: 22
            platform: linux-arm64
            package: true
          - os: ubuntu-24.04-arm
            container: rockylinux/rockylinux:8-ubi-init
            nodejs_arch: arm64
            nodejs_version: "^24"
            nodejs_version_major: 24
            platform: linux-arm64
          - os: macos-15-intel
            nodejs_arch: x64
            nodejs_version: "^20.9.0"
            nodejs_version_major: 20
            platform: darwin-x64
          - os: macos-15-intel
            nodejs_arch: x64
            nodejs_version: "^22"
            nodejs_version_major: 22
            platform: darwin-x64
            package: true
          - os: macos-15-intel
            nodejs_arch: x64
            nodejs_version: "^24"
            nodejs_version_major: 24
            platform: darwin-x64
          - os: macos-15
            nodejs_arch: arm64
            nodejs_version: "^20.9.0"
            nodejs_version_major: 20
            platform: darwin-arm64
          - os: macos-15
            nodejs_arch: arm64
            nodejs_version: "^22"
            nodejs_version_major: 22
            platform: darwin-arm64
            package: true
          - os: macos-15
            nodejs_arch: arm64
            nodejs_version: "^24"
            nodejs_version_major: 24
            platform: darwin-arm64
          - os: windows-2022
            nodejs_arch: x86
            nodejs_version: "^20.9.0"
            nodejs_version_major: 20
            platform: win32-ia32
            package: true
          - os: windows-2022
            nodejs_arch: x64
            nodejs_version: "^20.9.0"
            nodejs_version_major: 20
            platform: win32-x64
          - os: windows-2022
            nodejs_arch: x64
            nodejs_version: "^22"
            nodejs_version_major: 22
            platform: win32-x64
            package: true
          - os: windows-2022
            nodejs_arch: x64
            nodejs_version: "^24"
            nodejs_version_major: 24
            platform: win32-x64
          - os: windows-11-arm
            nodejs_arch: arm64
            nodejs_version: "^20.9.0"
            nodejs_version_major: 20
            platform: win32-arm64
          - os: windows-11-arm
            nodejs_arch: arm64
            nodejs_version: "^22"
            nodejs_version_major: 22
            platform: win32-arm64
            package: true
          - os: windows-11-arm
            nodejs_arch: arm64
            nodejs_version: "^24"
            nodejs_version_major: 24
            platform: win32-arm64
    steps:
      - name: Dependencies (Rocky Linux glibc)
        if: contains(matrix.container, 'rockylinux')
        run: |
          dnf install -y gcc-toolset-14-gcc-c++ make git python3.12 fontconfig
          echo "/opt/rh/gcc-toolset-14/root/usr/bin" >> $GITHUB_PATH
      - name: Dependencies (Linux musl)
        if: contains(matrix.container, 'alpine')
        run: apk add build-base git python3 fontconfig --update-cache
      - name: Dependencies (Python 3.11 - macOS, Windows)
        if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
        uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
        with:
          python-version: "3.12"
      - name: Dependencies (Node.js)
        if: "!contains(matrix.platform, 'linuxmusl')"
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: ${{ matrix.nodejs_version }}
          architecture: ${{ matrix.nodejs_arch }}
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - run: npm install
      - run: npm run build:dist
      - run: npm run build
      - run: npm run test-unit
      - if: matrix.package
        run: |
          npm run package-from-local-build
          npm run lint-publish -- npm/${{ matrix.platform }}
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        if: matrix.package
        with:
          name: ${{ matrix.platform }}
          path: npm/${{ matrix.platform }}
          retention-days: 1
          if-no-files-found: error
  build-linuxmusl-arm64:
    permissions:
      contents: read
    needs: lint
    name: "build-linuxmusl-arm64 [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}"
    runs-on: ubuntu-24.04-arm
    container:
      image: ${{ matrix.container }}
      volumes:
        - /opt:/opt:rw,rshared
        - /opt:/__e/node24:ro,rshared
    strategy:
      fail-fast: false
      matrix:
        include:
        - container: node:20-alpine3.20
          nodejs_version_major: 20
        - container: node:22-alpine3.20
          nodejs_version_major: 22
          package: true
        - container: node:24-alpine3.22
          nodejs_version_major: 24
    steps:
      - name: Allow Linux musl containers on ARM64 runners # https://github.com/actions/runner/issues/801#issuecomment-2394425757
        shell: sh
        run: |
          sed -i "/^ID=/s/alpine/NotpineForGHA/" /etc/os-release
          apk add nodejs --update-cache
          mkdir /opt/bin
          ln -s /usr/bin/node /opt/bin/node
      - name: Dependencies
        run: apk add build-base git python3 fontconfig --update-cache
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - run: npm install
      - run: npm run build:dist && npm run build
      - run: npm run test-unit
      - if: matrix.package
        run: |
          npm run package-from-local-build
          npm run lint-publish -- npm/linuxmusl-arm64
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        if: matrix.package
        with:
          name: linuxmusl-arm64
          path: npm/linuxmusl-arm64
          retention-days: 1
          if-no-files-found: error
  build-qemu:
    permissions:
      contents: read
    needs: lint
    name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] [package]"
    runs-on: ubuntu-24.04
    strategy:
      fail-fast: false
      matrix:
        include:
          - platform: linux-arm
            base_image: "--platform=linux/arm/v7 arm32v7/debian:bookworm"
            compiler_flags: "-marm -mcpu=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard"
            nodejs_arch: armv6l
            nodejs_hostname: unofficial-builds.nodejs.org
            nodejs_version: "22.22.3"
            nodejs_version_major: 22
          - platform: linux-s390x
            base_image: "--platform=linux/s390x s390x/debian:bookworm"
            nodejs_arch: s390x
            nodejs_hostname: nodejs.org
            nodejs_version: "22.22.3"
            nodejs_version_major: 22
          - platform: linux-ppc64
            base_image: "--platform=linux/ppc64le ppc64le/debian:bookworm"
            nodejs_arch: ppc64le
            nodejs_hostname: nodejs.org
            nodejs_version: "22.22.3"
            nodejs_version_major: 22
          - platform: linux-riscv64
            base_image: "--platform=linux/riscv64 riscv64/debian:trixie"
            compiler_flags: "-march=rv64gc"
            nodejs_arch: riscv64
            nodejs_hostname: unofficial-builds.nodejs.org
            nodejs_version: "22.22.3"
            nodejs_version_major: 22
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - uses: uraimo/run-on-arch-action@f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9 # v3.1.0
        with:
          arch: none
          distro: none
          base_image: ${{ matrix.base_image }}
          env: |
            CFLAGS: "${{ matrix.compiler_flags }}"
            CXXFLAGS: "${{ matrix.compiler_flags }}"
          run: |
            apt-get update
            apt-get install -y curl g++ git libatomic1 make python3 xz-utils
            mkdir /opt/nodejs
            curl --silent https://${{ matrix.nodejs_hostname }}/download/release/v${{ matrix.nodejs_version}}/node-v${{ matrix.nodejs_version}}-linux-${{ matrix.nodejs_arch }}.tar.xz | tar xJC /opt/nodejs --strip-components=1
            export PATH=$PATH:/opt/nodejs/bin
            npm install
            npm run build:dist
            npm run build
            node --test test/unit/io.js test/unit/raw.js
            npm run package-from-local-build
            npm run lint-publish -- npm/${{ matrix.platform }}
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: ${{ matrix.platform }}
          path: npm/${{ matrix.platform }}
          retention-days: 1
          if-no-files-found: error
  build-emscripten:
    permissions:
      contents: read
    needs: lint
    name: "build-wasm32 [package]"
    runs-on: ubuntu-24.04
    container: "emscripten/emsdk:6.0.1"
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - name: Dependencies
        run: apt-get update && apt-get install -y pkg-config
      - name: Dependencies (Node.js)
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: "22"
      - run: npm install
      - run: npm run build:dist && emmake npm run build
      - name: Verify emscripten versions match
        run: |
          EMSCRIPTEN_VERSION_LIBVIPS=$(node -p "require('@img/sharp-libvips-dev-wasm32/versions').emscripten")
          EMSCRIPTEN_VERSION_SHARP=$(emcc -dumpversion)
          echo "libvips built with emscripten $EMSCRIPTEN_VERSION_LIBVIPS"
          echo "sharp built with emscripten $EMSCRIPTEN_VERSION_SHARP"
          test "$EMSCRIPTEN_VERSION_LIBVIPS" = "$EMSCRIPTEN_VERSION_SHARP"
      - run: emmake npm run test-unit
      - run: emmake npm run package-from-local-build
      - run: npm run lint-publish -- npm/wasm32
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wasm32
          path: npm/wasm32
          retention-days: 1
          if-no-files-found: error
  release:
    permissions:
      contents: write
      id-token: write
    runs-on: ubuntu-24.04
    needs:
      - build-native
      - build-linuxmusl-arm64
      - build-qemu
      - build-emscripten
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: npm
      - name: Create npm workspace tarball
        run: tar -vcaf npm-workspace.tar.xz --directory npm --exclude=from-local-build.js .
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: '24'
      - name: Populate distribution directory
        run: npm run build:dist
      - name: Populate WebAssembly wrapper packages
        run: npm run package-wasm-wrappers
      - name: Create release notes
        run: npm run package-release-notes
      - name: Create GitHub release for tag
        if: startsWith(github.ref, 'refs/tags/v')
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          gh release create \
            --notes-file release-notes.md \
            ${{ contains(github.ref, '-rc') && '--prerelease --latest=false' || '' }} \
            ${GITHUB_REF_NAME} \
            npm-workspace.tar.xz
      - name: Publish platform-specific npm packages
        if: startsWith(github.ref, 'refs/tags/v')
        run: cd npm && npm publish --workspaces --tag=${{ contains(github.ref, '-rc') && 'next' || 'latest' }}
      - name: Publish sharp npm package
        if: startsWith(github.ref, 'refs/tags/v')
        run: npm publish --tag=${{ contains(github.ref, '-rc') && 'next' || 'latest' }}

The 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:
  - push
  - pull_request
permissions: {}
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true
 
jobs:
  lint:
    timeout-minutes: 30
    permissions:
      contents: read
    runs-on: latchkey-small
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          cache: 'npm'
          node-version: "24"
      - run: npm install --ignore-scripts
      - run: npm run build:dist
      - run: npm run lint-cpp
      - run: npm run lint-js
      - run: npm run lint-types
      - run: npm run lint-publish
  build-native:
    timeout-minutes: 30
    permissions:
      contents: read
    needs: lint
    name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}"
    runs-on: ${{ matrix.os }}
    container: ${{ matrix.container }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-24.04
            container: rockylinux/rockylinux:8-ubi-init
            nodejs_arch: x64
            nodejs_version: "^20.9.0"
            nodejs_version_major: 20
            platform: linux-x64
          - os: ubuntu-24.04
            container: rockylinux/rockylinux:8-ubi-init
            nodejs_arch: x64
            nodejs_version: "^22"
            nodejs_version_major: 22
            platform: linux-x64
            package: true
          - os: ubuntu-24.04
            container: rockylinux/rockylinux:8-ubi-init
            nodejs_arch: x64
            nodejs_version: "^24"
            nodejs_version_major: 24
            platform: linux-x64
          - os: ubuntu-24.04
            container: node:20-alpine3.20
            nodejs_version_major: 20
            platform: linuxmusl-x64
          - os: ubuntu-24.04
            container: node:22-alpine3.20
            nodejs_version_major: 22
            platform: linuxmusl-x64
            package: true
          - os: ubuntu-24.04
            container: node:24-alpine3.22
            nodejs_version_major: 24
            platform: linuxmusl-x64
          - os: ubuntu-24.04-arm
            container: rockylinux/rockylinux:8-ubi-init
            nodejs_arch: arm64
            nodejs_version: "^20.9.0"
            nodejs_version_major: 20
            platform: linux-arm64
          - os: ubuntu-24.04-arm
            container: rockylinux/rockylinux:8-ubi-init
            nodejs_arch: arm64
            nodejs_version: "^22"
            nodejs_version_major: 22
            platform: linux-arm64
            package: true
          - os: ubuntu-24.04-arm
            container: rockylinux/rockylinux:8-ubi-init
            nodejs_arch: arm64
            nodejs_version: "^24"
            nodejs_version_major: 24
            platform: linux-arm64
          - os: macos-15-intel
            nodejs_arch: x64
            nodejs_version: "^20.9.0"
            nodejs_version_major: 20
            platform: darwin-x64
          - os: macos-15-intel
            nodejs_arch: x64
            nodejs_version: "^22"
            nodejs_version_major: 22
            platform: darwin-x64
            package: true
          - os: macos-15-intel
            nodejs_arch: x64
            nodejs_version: "^24"
            nodejs_version_major: 24
            platform: darwin-x64
          - os: macos-15
            nodejs_arch: arm64
            nodejs_version: "^20.9.0"
            nodejs_version_major: 20
            platform: darwin-arm64
          - os: macos-15
            nodejs_arch: arm64
            nodejs_version: "^22"
            nodejs_version_major: 22
            platform: darwin-arm64
            package: true
          - os: macos-15
            nodejs_arch: arm64
            nodejs_version: "^24"
            nodejs_version_major: 24
            platform: darwin-arm64
          - os: windows-2022
            nodejs_arch: x86
            nodejs_version: "^20.9.0"
            nodejs_version_major: 20
            platform: win32-ia32
            package: true
          - os: windows-2022
            nodejs_arch: x64
            nodejs_version: "^20.9.0"
            nodejs_version_major: 20
            platform: win32-x64
          - os: windows-2022
            nodejs_arch: x64
            nodejs_version: "^22"
            nodejs_version_major: 22
            platform: win32-x64
            package: true
          - os: windows-2022
            nodejs_arch: x64
            nodejs_version: "^24"
            nodejs_version_major: 24
            platform: win32-x64
          - os: windows-11-arm
            nodejs_arch: arm64
            nodejs_version: "^20.9.0"
            nodejs_version_major: 20
            platform: win32-arm64
          - os: windows-11-arm
            nodejs_arch: arm64
            nodejs_version: "^22"
            nodejs_version_major: 22
            platform: win32-arm64
            package: true
          - os: windows-11-arm
            nodejs_arch: arm64
            nodejs_version: "^24"
            nodejs_version_major: 24
            platform: win32-arm64
    steps:
      - name: Dependencies (Rocky Linux glibc)
        if: contains(matrix.container, 'rockylinux')
        run: |
          dnf install -y gcc-toolset-14-gcc-c++ make git python3.12 fontconfig
          echo "/opt/rh/gcc-toolset-14/root/usr/bin" >> $GITHUB_PATH
      - name: Dependencies (Linux musl)
        if: contains(matrix.container, 'alpine')
        run: apk add build-base git python3 fontconfig --update-cache
      - name: Dependencies (Python 3.11 - macOS, Windows)
        if: contains(matrix.os, 'macos') || contains(matrix.os, 'windows')
        uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
        with:
          python-version: "3.12"
      - name: Dependencies (Node.js)
        if: "!contains(matrix.platform, 'linuxmusl')"
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          cache: 'npm'
          node-version: ${{ matrix.nodejs_version }}
          architecture: ${{ matrix.nodejs_arch }}
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - run: npm install
      - run: npm run build:dist
      - run: npm run build
      - run: npm run test-unit
      - if: matrix.package
        run: |
          npm run package-from-local-build
          npm run lint-publish -- npm/${{ matrix.platform }}
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        if: matrix.package
        with:
          name: ${{ matrix.platform }}
          path: npm/${{ matrix.platform }}
          retention-days: 1
          if-no-files-found: error
  build-linuxmusl-arm64:
    timeout-minutes: 30
    permissions:
      contents: read
    needs: lint
    name: "build-linuxmusl-arm64 [Node.js ${{ matrix.nodejs_version_major }}] ${{ matrix.package && '[package]' }}"
    runs-on: latchkey-small-arm
    container:
      image: ${{ matrix.container }}
      volumes:
        - /opt:/opt:rw,rshared
        - /opt:/__e/node24:ro,rshared
    strategy:
      fail-fast: false
      matrix:
        include:
        - container: node:20-alpine3.20
          nodejs_version_major: 20
        - container: node:22-alpine3.20
          nodejs_version_major: 22
          package: true
        - container: node:24-alpine3.22
          nodejs_version_major: 24
    steps:
      - name: Allow Linux musl containers on ARM64 runners # https://github.com/actions/runner/issues/801#issuecomment-2394425757
        shell: sh
        run: |
          sed -i "/^ID=/s/alpine/NotpineForGHA/" /etc/os-release
          apk add nodejs --update-cache
          mkdir /opt/bin
          ln -s /usr/bin/node /opt/bin/node
      - name: Dependencies
        run: apk add build-base git python3 fontconfig --update-cache
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - run: npm install
      - run: npm run build:dist && npm run build
      - run: npm run test-unit
      - if: matrix.package
        run: |
          npm run package-from-local-build
          npm run lint-publish -- npm/linuxmusl-arm64
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        if: matrix.package
        with:
          name: linuxmusl-arm64
          path: npm/linuxmusl-arm64
          retention-days: 1
          if-no-files-found: error
  build-qemu:
    timeout-minutes: 30
    permissions:
      contents: read
    needs: lint
    name: "build-${{ matrix.platform }} [Node.js ${{ matrix.nodejs_version_major }}] [package]"
    runs-on: latchkey-small
    strategy:
      fail-fast: false
      matrix:
        include:
          - platform: linux-arm
            base_image: "--platform=linux/arm/v7 arm32v7/debian:bookworm"
            compiler_flags: "-marm -mcpu=arm1176jzf-s -mfpu=vfp -mfloat-abi=hard"
            nodejs_arch: armv6l
            nodejs_hostname: unofficial-builds.nodejs.org
            nodejs_version: "22.22.3"
            nodejs_version_major: 22
          - platform: linux-s390x
            base_image: "--platform=linux/s390x s390x/debian:bookworm"
            nodejs_arch: s390x
            nodejs_hostname: nodejs.org
            nodejs_version: "22.22.3"
            nodejs_version_major: 22
          - platform: linux-ppc64
            base_image: "--platform=linux/ppc64le ppc64le/debian:bookworm"
            nodejs_arch: ppc64le
            nodejs_hostname: nodejs.org
            nodejs_version: "22.22.3"
            nodejs_version_major: 22
          - platform: linux-riscv64
            base_image: "--platform=linux/riscv64 riscv64/debian:trixie"
            compiler_flags: "-march=rv64gc"
            nodejs_arch: riscv64
            nodejs_hostname: unofficial-builds.nodejs.org
            nodejs_version: "22.22.3"
            nodejs_version_major: 22
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - uses: uraimo/run-on-arch-action@f9b26e3a1a408d5fd530d20c17b9f3f4428ff8d9 # v3.1.0
        with:
          arch: none
          distro: none
          base_image: ${{ matrix.base_image }}
          env: |
            CFLAGS: "${{ matrix.compiler_flags }}"
            CXXFLAGS: "${{ matrix.compiler_flags }}"
          run: |
            apt-get update
            apt-get install -y curl g++ git libatomic1 make python3 xz-utils
            mkdir /opt/nodejs
            curl --silent https://${{ matrix.nodejs_hostname }}/download/release/v${{ matrix.nodejs_version}}/node-v${{ matrix.nodejs_version}}-linux-${{ matrix.nodejs_arch }}.tar.xz | tar xJC /opt/nodejs --strip-components=1
            export PATH=$PATH:/opt/nodejs/bin
            npm install
            npm run build:dist
            npm run build
            node --test test/unit/io.js test/unit/raw.js
            npm run package-from-local-build
            npm run lint-publish -- npm/${{ matrix.platform }}
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: ${{ matrix.platform }}
          path: npm/${{ matrix.platform }}
          retention-days: 1
          if-no-files-found: error
  build-emscripten:
    timeout-minutes: 30
    permissions:
      contents: read
    needs: lint
    name: "build-wasm32 [package]"
    runs-on: latchkey-small
    container: "emscripten/emsdk:6.0.1"
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - name: Dependencies
        run: apt-get update && apt-get install -y pkg-config
      - name: Dependencies (Node.js)
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          cache: 'npm'
          node-version: "22"
      - run: npm install
      - run: npm run build:dist && emmake npm run build
      - name: Verify emscripten versions match
        run: |
          EMSCRIPTEN_VERSION_LIBVIPS=$(node -p "require('@img/sharp-libvips-dev-wasm32/versions').emscripten")
          EMSCRIPTEN_VERSION_SHARP=$(emcc -dumpversion)
          echo "libvips built with emscripten $EMSCRIPTEN_VERSION_LIBVIPS"
          echo "sharp built with emscripten $EMSCRIPTEN_VERSION_SHARP"
          test "$EMSCRIPTEN_VERSION_LIBVIPS" = "$EMSCRIPTEN_VERSION_SHARP"
      - run: emmake npm run test-unit
      - run: emmake npm run package-from-local-build
      - run: npm run lint-publish -- npm/wasm32
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: wasm32
          path: npm/wasm32
          retention-days: 1
          if-no-files-found: error
  release:
    timeout-minutes: 30
    permissions:
      contents: write
      id-token: write
    runs-on: latchkey-small
    needs:
      - build-native
      - build-linuxmusl-arm64
      - build-qemu
      - build-emscripten
    steps:
      - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
        with:
          persist-credentials: false
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          path: npm
      - name: Create npm workspace tarball
        run: tar -vcaf npm-workspace.tar.xz --directory npm --exclude=from-local-build.js .
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          cache: 'npm'
          node-version: '24'
      - name: Populate distribution directory
        run: npm run build:dist
      - name: Populate WebAssembly wrapper packages
        run: npm run package-wasm-wrappers
      - name: Create release notes
        run: npm run package-release-notes
      - name: Create GitHub release for tag
        if: startsWith(github.ref, 'refs/tags/v')
        env:
          GH_TOKEN: ${{ github.token }}
        run: |
          gh release create \
            --notes-file release-notes.md \
            ${{ contains(github.ref, '-rc') && '--prerelease --latest=false' || '' }} \
            ${GITHUB_REF_NAME} \
            npm-workspace.tar.xz
      - name: Publish platform-specific npm packages
        if: startsWith(github.ref, 'refs/tags/v')
        run: cd npm && npm publish --workspaces --tag=${{ contains(github.ref, '-rc') && 'next' || 'latest' }}
      - name: Publish sharp npm package
        if: startsWith(github.ref, 'refs/tags/v')
        run: npm publish --tag=${{ contains(github.ref, '-rc') && 'next' || 'latest' }}
 

What changed

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:

This workflow runs 6 jobs per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.

Actions used in this workflow