name: 'Tests: alpine' on: [push, pull_request] permissions: contents: read jobs: fast: permissions: contents: read # Native runners per arch (no QEMU): arm64 uses the ubuntu-24.04-arm image. name: 'fast ${{ matrix.arch }} (alpine ${{ matrix.alpine }}, ${{ matrix.shell }})' runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} strategy: fail-fast: false matrix: alpine: - '3.15' - '3.19' - '3' arch: - x64 - arm64 shell: - sh - bash - dash - zsh # - ksh (#574) steps: - name: Harden Runner uses: step-security/harden-runner@v2 with: allowed-endpoints: github.com:443 registry.npmjs.org:443 raw.githubusercontent.com:443 nodejs.org:443 iojs.org:443 unofficial-builds.nodejs.org:443 dl-cdn.alpinelinux.org:443 dl-cdn.alpinelinux.org:80 registry-1.docker.io:443 auth.docker.io:443 production.cloudflare.docker.com:443 production.cloudfront.docker.com:443 - uses: actions/checkout@v6 id: checkout continue-on-error: true with: submodules: true - name: 'nvmrc submodule fallback (forks without their own nvmrc)' if: steps.checkout.outcome == 'failure' shell: bash run: | git submodule set-url test/fixtures/nvmrc https://github.com/nvm-sh/nvmrc.git git submodule sync --recursive git submodule update --init --recursive - uses: ljharb/actions/node/install@main name: 'npm install (on host; node_modules is mounted into the container)' with: node-version: 'lts/*' skip-ls-check: true - run: npm ls urchin - name: 'Run fast tests on Alpine ${{ matrix.alpine }} (${{ matrix.arch }})' run: | for i in 1 2 3 4 5; do docker pull alpine:${{ matrix.alpine }} && break echo "docker pull failed, attempt $i/5"; sleep $((i * 5)) done docker run --rm \ -v "${{ github.workspace }}:/workspace" \ -w /workspace \ -e "TEST_SHELL=${{ matrix.shell }}" \ -e "TERM=xterm-256color" \ -e "GITHUB_ACTIONS=true" \ alpine:${{ matrix.alpine }} \ sh -c ' set -ex cat /etc/alpine-release; uname -m for i in 1 2 3 4 5; do apk add --no-cache \ make bash zsh dash \ grep sed gawk coreutils util-linux findutils ncurses \ curl wget ca-certificates openssl tar xz gzip git \ sudo su-exec libstdc++ libgcc && break echo "apk add failed, attempt $i/5"; sleep $((i * 5)) done # Mirror the ubuntu runner: run the suite as a non-root user with # passwordless sudo and a PTY so the permission/terminal tests # behave the same. The user takes the mounted checkout uid so # files stay host-owned (no chown; post-checkout cleanup works). # No node is installed, so no active version skews the output. HOST_UID="$(stat -c %u /workspace)" adduser -D -u "$HOST_UID" tester 2>/dev/null || adduser -D tester echo "tester ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/tester chmod 0440 /etc/sudoers.d/tester export NVM_DIR=/workspace HOME=/home/tester unset NVM_BIN NVM_INC NVM_CD_FLAGS su-exec tester script -q -e -c "make TEST_SUITE=fast SHELL=$TEST_SHELL URCHIN=/workspace/node_modules/.bin/urchin test-$TEST_SHELL" /dev/null ' musl-binary: permissions: contents: read # Regression lock, same-era diagonal only: modern musl node needs a newer # libstdc++ than old Alpine ships, and arm64-musl exists only from # v20.20.1/v22.21.1/v24.9.0 (built on modern Alpine), so arm64 pins modern Alpine. name: 'musl-binary ${{ matrix.arch }} (alpine ${{ matrix.alpine }}, node ${{ matrix.node }})' runs-on: ${{ matrix.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }} strategy: fail-fast: false matrix: include: - { arch: x64, alpine: '3.10', node: v8.17.0 } - { arch: x64, alpine: '3.10', node: v10.24.1 } - { arch: x64, alpine: '3.12', node: v12.22.12 } - { arch: x64, alpine: '3.15', node: v14.21.3 } - { arch: x64, alpine: '3.16', node: v16.20.2 } - { arch: x64, alpine: '3.18', node: v18.20.4 } - { arch: x64, alpine: '3.20', node: v20.18.1 } - { arch: x64, alpine: '3', node: v22.12.0 } - { arch: arm64, alpine: '3.20', node: v20.20.1 } - { arch: arm64, alpine: '3', node: v22.21.1 } - { arch: arm64, alpine: '3', node: v24.9.0 } steps: - name: Harden Runner uses: step-security/harden-runner@v2 with: allowed-endpoints: github.com:443 registry.npmjs.org:443 unofficial-builds.nodejs.org:443 dl-cdn.alpinelinux.org:443 dl-cdn.alpinelinux.org:80 registry-1.docker.io:443 auth.docker.io:443 production.cloudflare.docker.com:443 production.cloudfront.docker.com:443 - uses: actions/checkout@v6 with: submodules: false - name: 'Install node ${{ matrix.node }} from a musl binary on Alpine ${{ matrix.alpine }}' run: | for i in 1 2 3 4 5; do docker pull alpine:${{ matrix.alpine }} && break echo "docker pull failed, attempt $i/5"; sleep $((i * 5)) done docker run --rm \ -v "${{ github.workspace }}:/workspace" \ -w /workspace \ -e "NODE_VERSION=${{ matrix.node }}" \ -e "TERM=xterm-256color" \ alpine:${{ matrix.alpine }} \ sh -c ' set -ex cat /etc/alpine-release; uname -m # libstdc++/libgcc are load-bearing: the unofficial musl node # binary dynamically links them. No build toolchain: -b forbids # the source fallback, so a missing binary is a hard failure. for i in 1 2 3 4 5; do apk add --no-cache \ bash ca-certificates curl wget tar xz gzip \ grep sed coreutils libstdc++ libgcc && break echo "apk add failed, attempt $i/5"; sleep $((i * 5)) done export NVM_DIR=/workspace unset NVM_BIN NVM_INC NVM_CD_FLAGS # The default nodejs.org mirror serves no -musl artifacts; this one does. export NVM_NODEJS_ORG_MIRROR=https://unofficial-builds.nodejs.org/download/release . /workspace/nvm.sh --no-use nvm install -b --skip-default-packages "$NODE_VERSION" nvm use "$NODE_VERSION" [ "$(node -v)" = "$NODE_VERSION" ] || { echo "version mismatch: $(node -v)"; exit 1; } node -e "process.exit(0)" ldd "$(command -v node)" | grep -qi musl ' all: permissions: contents: none name: 'all alpine tests' needs: [fast, musl-binary] runs-on: ubuntu-latest steps: - run: true