n8n/.github/actions/setup-nodejs/action.yml
n8n-cat-bot[bot] 3510a86170
ci: Surface pnpm install failures in setup-nodejs action (no-changelog) (#30929)
Co-authored-by: n8n-cat-bot[bot] <n8n-cat-bot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: Declan Carroll <declan@n8n.io>
2026-05-22 07:33:05 +00:00

157 lines
7.1 KiB
YAML

# This action works transparently on both Blacksmith and GitHub-hosted runners.
# Blacksmith runners benefit from transparent caching and optional Docker layer caching.
# GitHub-hosted runners use standard GitHub Actions caching.
name: 'Node.js Build Setup'
description: 'Configures Node.js with pnpm, installs Aikido SafeChain for supply chain protection, installs dependencies, enables Turborepo caching, (optional) sets up Docker layer caching, and builds the project or an optional command.'
inputs:
node-version:
description: 'Node.js version to use. Pinned to 24.15.0 by default for reproducible builds.'
required: false
default: '24.15.0'
enable-docker-cache:
description: 'Whether to set up Blacksmith Buildx for Docker layer caching (Blacksmith runners only).'
required: false
default: 'false'
build-command:
description: 'Command to execute for building the project or an optional command. Leave empty to skip build step.'
required: false
default: 'pnpm build'
install-command:
description: 'Command to execute for installing project dependencies. Leave empty to skip install step.'
required: false
default: 'pnpm install --frozen-lockfile'
cache-dependency-path:
description: 'Path(s) to the lockfile(s) used to compute the pnpm-store cache key. Scope this down (e.g. to `.github/scripts/pnpm-lock.yaml`) when only installing a subset, to avoid restoring the full workspace store.'
required: false
default: 'pnpm-lock.yaml'
runs:
using: 'composite'
steps:
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
# Cache the Node toolcache so setup-node skips the ~33s nodejs.org download
# on every subsequent job. First fresh runner pays the download; later jobs
# (including parallel E2E shards) hit the cache. Blacksmith transparently
# routes actions/cache through its S3 backend.
#
# The `x64.complete` sibling marker file must be cached alongside the
# toolchain — without it, setup-node's `tc.find` returns empty and the
# action re-downloads from nodejs.org. Re-downloads also keep the silent
# fall-through window open: errors during download/extract are swallowed,
# PATH is left untouched, and the runner image's baked-in Node 20.20.0
# quietly takes over. Caching the parent version dir captures the marker
# alongside the toolchain. Key bumped to v2 to invalidate stale entries.
- name: Restore Node.js Toolcache
if: runner.os == 'Linux' && runner.arch == 'X64'
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
with:
path: /opt/hostedtoolcache/node/${{ inputs.node-version }}
key: node-toolcache-v2-${{ runner.os }}-${{ runner.arch }}-${{ inputs.node-version }}
- name: Setup Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: ${{ inputs.node-version }}
cache: 'pnpm'
cache-dependency-path: ${{ inputs.cache-dependency-path }}
# Fail fast if setup-node silently fell through to the runner's baked-in
# Node instead of activating the requested version.
# see: https://github.com/actions/setup-node/issues/1137
- name: Verify Node.js Version
shell: bash
env:
EXPECTED_NODE_VERSION: ${{ inputs.node-version }}
run: |
ACTUAL_NODE_VERSION="$(node --version)"
if [ "${ACTUAL_NODE_VERSION#v}" != "${EXPECTED_NODE_VERSION#v}" ]; then
echo "::error::setup-node did not activate Node ${EXPECTED_NODE_VERSION} (got ${ACTUAL_NODE_VERSION})"
exit 1
fi
# To avoid setup-node cache failure.
# see: https://github.com/actions/setup-node/issues/1137
- name: Verify PNPM Cache Directory
shell: bash
run: |
PNPM_STORE_PATH="$( pnpm store path --silent )"
if [ ! -d "$PNPM_STORE_PATH" ]; then
mkdir -p "$PNPM_STORE_PATH"
fi
- name: Configure SafeChain
shell: bash
run: |
# SafeChain only reads configs from this directory https://github.com/AikidoSec/safe-chain#configuration-options-1
mkdir -p "$HOME/.safe-chain"
cp "${{ github.action_path }}/safe-chain.config.json" "$HOME/.safe-chain/config.json"
- name: Install Aikido SafeChain
run: |
VERSION="1.5.3"
EXPECTED_SHA256="0107cbbbf90159379756157e902acae512d62ffbd174307e42c5fe9f266792d3"
node .github/scripts/retry.mjs --attempts 3 --delay 10 -- \
curl -fsSL -o install-safe-chain.sh "https://github.com/AikidoSec/safe-chain/releases/download/${VERSION}/install-safe-chain.sh"
echo "${EXPECTED_SHA256} install-safe-chain.sh" | sha256sum -c -
sh install-safe-chain.sh --ci
rm install-safe-chain.sh
shell: bash
# `--reporter=append-only` collapses pnpm output to a single terse line and
# drops the ERR_PNPM_* code plus the offending package. Combined with the
# `timeout` wrapper, a SIGKILL or a non-pnpm post-install crash surfaces as
# bare `ELIFECYCLE` with no actionable diagnostics. We let pnpm pick its own
# CI reporter, tee stderr to a file, and re-emit the captured log on
# failure so the failure survives even if the live log buffer is cut off
# when `timeout` terminates the process group.
- name: Install Dependencies
if: ${{ inputs.install-command != '' }}
env:
INSTALL_COMMAND: ${{ inputs.install-command }}
INSTALL_LOG: ${{ runner.temp }}/pnpm-install.err
run: |
set +e
timeout --kill-after=30s 300s $INSTALL_COMMAND 2> >(tee "$INSTALL_LOG" >&2)
rc=$?
set -e
# Let the backgrounded `tee` flush before we read the file back.
wait 2>/dev/null || true
if [ $rc -ne 0 ]; then
echo "::group::pnpm install stderr (captured)"
cat "$INSTALL_LOG" 2>/dev/null || echo "(no captured log)"
echo "::endgroup::"
case $rc in
124) echo "::error::pnpm install timed out after 300s (exit 124)" ;;
137) echo "::error::pnpm install received SIGKILL (exit 137 — likely OOM or kill-after timeout)" ;;
esac
fi
exit $rc
shell: bash
- name: Configure Turborepo Cache
uses: rharkor/caching-for-turbo@5d14fba18e450c09393333cfd4242e8b3cb455a6 # v2.4.2
with:
server-port: 0
- name: Setup Docker Builder for Docker Cache (Blacksmith)
if: ${{ inputs.enable-docker-cache == 'true' && contains(runner.name, 'blacksmith') }}
uses: useblacksmith/setup-docker-builder@ef12d5b165b596e3aa44ea8198d8fde563eab402 # v1.4.0
- name: Setup Docker Builder (GitHub fallback)
if: ${{ inputs.enable-docker-cache == 'true' && !contains(runner.name, 'blacksmith') }}
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
- name: Build Project
if: ${{ inputs.build-command != '' }}
env:
BUILD_COMMAND: ${{ inputs.build-command }}
run: |
$BUILD_COMMAND --summarize
node .github/scripts/send-build-stats.mjs || true
node .github/scripts/send-docker-stats.mjs || true
shell: bash