name: setup-node-pnpm description: "Install pnpm, setup Node.js, install dependencies" inputs: node-version: description: Node.js version required: false default: "24" install-args: description: Arguments passed to pnpm install required: false default: "--frozen-lockfile" registry-url: description: Optional npm registry URL passed to actions/setup-node required: false default: "" skip-install: description: Set to 'true' to skip the pnpm install step; caller runs install themselves required: false default: "false" runs: using: composite steps: - name: Install pnpm uses: pnpm/action-setup@v4 - name: Setup Node.js uses: actions/setup-node@v5 with: node-version: ${{ inputs.node-version }} cache: pnpm registry-url: ${{ inputs.registry-url }} # Cache node_modules only for the exact pnpm-lock.yaml hash and Node/OS tuple. # Intentionally no restore-keys fallback: partial restores can create inconsistent trees. - name: Cache node_modules id: node-modules-cache if: ${{ inputs.skip-install != 'true' }} uses: actions/cache@v4 with: path: | node_modules **/node_modules !**/.cache key: node-modules-${{ runner.os }}-node${{ inputs.node-version }}-${{ hashFiles('pnpm-lock.yaml') }} - name: Install dependencies if: ${{ inputs.skip-install != 'true' && steps.node-modules-cache.outputs.cache-hit != 'true' }} shell: bash run: pnpm install ${{ inputs.install-args }} - name: Verify restored node_modules cache integrity if: ${{ inputs.skip-install != 'true' && steps.node-modules-cache.outputs.cache-hit == 'true' }} shell: bash run: | test -f node_modules/.modules.yaml || { echo "ERROR: node_modules cache hit but node_modules/.modules.yaml is missing" exit 1 } pnpm -v