refactor: 移除 node_modules 缓存模式,仅保留 pnpm store 缓存策略

This commit is contained in:
Lyda
2025-10-11 19:25:35 +08:00
parent 4495633411
commit acea3c86c1
2 changed files with 31 additions and 59 deletions

View File

@@ -1,11 +1,11 @@
# pnpm 依赖安装与缓存 Action # pnpm 依赖安装与缓存 Action
专为 pnpm 项目设计的 GitHub Action通过缓存 `node_modules` pnpm store在 CI/CD 中实现快速复用,二次执行可在 10 秒内完成安装。 专为 pnpm 项目设计的 GitHub Action通过缓存 pnpm store在 CI/CD 中实现快速复用,二次执行可在 10 秒内完成安装。
## ✨ 特性 ## ✨ 特性
- **专注 pnpm**默认缓存 `node_modules`,命中后直接复用,无需重新链接 - **专注 pnpm**:缓存 pnpm store命中后仅执行快速链接
- **双缓存模式**:可在 `node_modules` / `store` 之间切换,满足不同目录约束 - **离线友好**:自动根据缓存命中追加 `--offline`/`--prefer-offline`
- **锁文件准确性**:推荐传入 `cache-hash`(如 `hashFiles('pnpm-lock.yaml')`)确保缓存精准失效 - **锁文件准确性**:推荐传入 `cache-hash`(如 `hashFiles('pnpm-lock.yaml')`)确保缓存精准失效
- **自定义安装**:支持附加参数或完全覆盖安装命令,保留 `force-install` 选项 - **自定义安装**:支持附加参数或完全覆盖安装命令,保留 `force-install` 选项
- **环境整洁**:自动设置 `PNPM_STORE_DIR`,可选清理项目根 `.pnpm-store` - **环境整洁**:自动设置 `PNPM_STORE_DIR`,可选清理项目根 `.pnpm-store`
@@ -14,9 +14,8 @@
| 参数名 | 描述 | 必需 | 默认值 | | 参数名 | 描述 | 必需 | 默认值 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `cache-mode` | 缓存模式:`node_modules` / `store` | 否 | `node_modules` |
| `cache-prefix` | 缓存 key 前缀 | 否 | `modules` | | `cache-prefix` | 缓存 key 前缀 | 否 | `modules` |
| `node-modules-path` | `node_modules` 目录路径(仅 `cache-mode=node_modules` 时生效) | 否 | `node_modules` | | `store-path` | 自定义 pnpm store 路径(默认使用 runner 临时目录) | 否 | `''` |
| `force-install` | 是否强制安装(追加 `--force` | 否 | `false` | | `force-install` | 是否强制安装(追加 `--force` | 否 | `false` |
| `install-command` | 自定义安装命令,覆盖默认的 `pnpm install` | 否 | `''` | | `install-command` | 自定义安装命令,覆盖默认的 `pnpm install` | 否 | `''` |
| `install-args` | 附加参数(仅默认命令时生效) | 否 | `''` | | `install-args` | 附加参数(仅默认命令时生效) | 否 | `''` |
@@ -40,27 +39,20 @@
cache-hash: ${{ hashFiles('pnpm-lock.yaml') }} cache-hash: ${{ hashFiles('pnpm-lock.yaml') }}
``` ```
- 默认缓存 `node_modules`命中后直接跳过安装步骤,满足 10 秒内完成的目标 - 默认缓存 pnpm store命中后执行 `pnpm install --offline --frozen-lockfile`通常在 10 秒内完成链接
- 首次执行或 lock 文件变更时执行 `pnpm install --prefer-offline --frozen-lockfile`,完成后写入缓存。 - 首次执行或 lock 文件变更时执行 `pnpm install --prefer-offline --frozen-lockfile`,完成后写入缓存。
## 🔁 缓存模式 ## 🔁 缓存路径
### `cache-mode: node_modules`(默认) 默认缓存路径为 `${{ runner.temp }}/.pnpm-store`。如需与团队现有目录保持一致,可通过 `store-path` 自定义:
- **适用场景**:希望二次执行直接复用产物、编译型依赖较多。
- **行为**:缓存 `node_modules` 指定目录。命中缓存后跳过安装步骤。
- **额外设定**Action 会自动将 `pnpm node-linker` 设置为 `hoisted`,避免使用 symlink生成更接近 npm 的目录结构。
### `cache-mode: store`
```yaml ```yaml
with: with:
cache-mode: store store-path: ~/.cache/pnpm-store
cache-hash: ${{ hashFiles('pnpm-lock.yaml') }} cache-hash: ${{ hashFiles('pnpm-lock.yaml') }}
``` ```
- **适用场景**:项目对 `node_modules` 目录结构有额外处理,或需要保持 `node_modules` 在工作目录内新生成 > 提示:如自定义路径,需确保同一 Runner 不会被多个并发作业共享该目录,以免产生竞争条件
- **行为**:缓存 `${{ runner.temp }}/.pnpm-store`。命中缓存后执行 `pnpm install --offline --frozen-lockfile`,仅进行符号链接操作。
## ⚙️ 进阶配置 ## ⚙️ 进阶配置

View File

@@ -5,20 +5,15 @@ branding:
color: 'yellow' color: 'yellow'
inputs: inputs:
cache-mode:
description: '缓存模式 (node_modules 或 store)'
required: false
default: 'node_modules'
cache-prefix: cache-prefix:
description: '缓存前缀名称' description: '缓存前缀名称'
required: false required: false
default: 'modules' default: 'modules'
node-modules-path: store-path:
description: 'node_modules目录路径cache-mode=node_modules 时生效' description: '自定义 pnpm store 缓存路径(默认: runner 临时目录/.pnpm-store'
required: false required: false
default: 'node_modules' default: ''
force-install: force-install:
description: '是否强制安装 (true/false)' description: '是否强制安装 (true/false)'
@@ -98,12 +93,8 @@ runs:
CACHE_HASH_SHORT="no-hash" CACHE_HASH_SHORT="no-hash"
fi fi
PNPM_VERSION="${PNPM_VERSION}" PNPM_VERSION="${PNPM_VERSION}"
MODE="${{ inputs.cache-mode }}" CACHE_KEY="${{ runner.os }}-pnpm-v${PNPM_VERSION}-store-${{ inputs.cache-prefix }}-${CACHE_HASH_SHORT}"
if [[ -z "$MODE" ]]; then RESTORE_PREFIX="${{ runner.os }}-pnpm-v${PNPM_VERSION}-store-${{ inputs.cache-prefix }}-"
MODE="node_modules"
fi
CACHE_KEY="${{ runner.os }}-pnpm-v${PNPM_VERSION}-${MODE}-${{ inputs.cache-prefix }}-${CACHE_HASH_SHORT}"
RESTORE_PREFIX="${{ runner.os }}-pnpm-v${PNPM_VERSION}-${MODE}-${{ inputs.cache-prefix }}-"
echo "key=${CACHE_KEY}" >> "$GITHUB_OUTPUT" echo "key=${CACHE_KEY}" >> "$GITHUB_OUTPUT"
echo "restore-prefix=${RESTORE_PREFIX}" >> "$GITHUB_OUTPUT" echo "restore-prefix=${RESTORE_PREFIX}" >> "$GITHUB_OUTPUT"
echo "hash=${CACHE_HASH}" >> "$GITHUB_OUTPUT" echo "hash=${CACHE_HASH}" >> "$GITHUB_OUTPUT"
@@ -113,19 +104,16 @@ runs:
shell: bash shell: bash
run: | run: |
set -euo pipefail set -euo pipefail
MODE="${{ inputs.cache-mode }}" echo "🧠 RUNNER_TEMP=${RUNNER_TEMP:-<unset>}"
if [[ -z "$MODE" ]]; then echo "🏠 HOME=${HOME:-<unset>}"
MODE="node_modules" DEFAULT_STORE="${RUNNER_TEMP:-$HOME}/.pnpm-store"
fi if [[ -n "${{ inputs.store-path }}" ]]; then
if [[ "$MODE" == "node_modules" ]]; then CACHE_PATH="${{ inputs.store-path }}"
CACHE_PATH="${{ inputs.node-modules-path }}"
STORE_DIR="${RUNNER_TEMP:-$HOME}/.pnpm-store"
else else
STORE_DIR="${RUNNER_TEMP:-$HOME}/.pnpm-store" CACHE_PATH="$DEFAULT_STORE"
CACHE_PATH="$STORE_DIR"
fi fi
mkdir -p "$STORE_DIR" mkdir -p "$CACHE_PATH"
echo "PNPM_STORE_DIR=${STORE_DIR}" >> "$GITHUB_ENV" echo "PNPM_STORE_DIR=${CACHE_PATH}" >> "$GITHUB_ENV"
echo "path=${CACHE_PATH}" >> "$GITHUB_OUTPUT" echo "path=${CACHE_PATH}" >> "$GITHUB_OUTPUT"
- name: 拉取缓存 - name: 拉取缓存
@@ -147,44 +135,36 @@ runs:
fi fi
- name: 安装依赖 - name: 安装依赖
if: (inputs.cache-mode == 'node_modules' && steps.cache.outputs.cache-hit != 'true') || (inputs.cache-mode == 'store')
shell: bash shell: bash
run: | run: |
set -euo pipefail set -euo pipefail
export PNPM_STORE_DIR="${PNPM_STORE_DIR:-${RUNNER_TEMP:-$HOME}/.pnpm-store}" STORE_DIR="${PNPM_STORE_DIR:-${{ steps.cache-path.outputs.path }}}"
export PNPM_STORE_DIR="$STORE_DIR"
export npm_config_store_dir="$PNPM_STORE_DIR" export npm_config_store_dir="$PNPM_STORE_DIR"
export PNPM_CONFIG_STORE_DIR="$PNPM_STORE_DIR" export PNPM_CONFIG_STORE_DIR="$PNPM_STORE_DIR"
pnpm config set store-dir "$PNPM_STORE_DIR" --location=project || true pnpm config set store-dir "$PNPM_STORE_DIR" --location=project || true
if [[ "${{ inputs.cache-mode }}" == "node_modules" ]]; then pnpm config set store-dir "$PNPM_STORE_DIR" --location=global || true
export PNPM_NODE_LINKER="hoisted"
pnpm config set node-linker hoisted --location=project || true
pnpm config set node-linker hoisted --location=global || true
echo "🔧 已将 pnpm node-linker 设置为 hoisted避免 symlink"
fi
if [[ -n "${{ inputs.install-command }}" ]]; then if [[ -n "${{ inputs.install-command }}" ]]; then
echo "🔧 使用自定义安装命令: ${{ inputs.install-command }}" echo "🔧 使用自定义安装命令: ${{ inputs.install-command }}"
eval "${{ inputs.install-command }}" eval "${{ inputs.install-command }}"
exit 0 exit 0
fi fi
FLAGS=("--frozen-lockfile") if [[ "${{ steps.cache.outputs.cache-hit }}" == "true" ]]; then
if [[ "${{ inputs.cache-mode }}" == "store" ]]; then FLAGS=("--offline" "--frozen-lockfile")
if [[ "${{ steps.cache.outputs.cache-hit }}" == "true" ]]; then else
FLAGS=("--offline" "--frozen-lockfile") FLAGS=("--prefer-offline" "--frozen-lockfile")
else
FLAGS=("--prefer-offline" "--frozen-lockfile")
fi
fi fi
if [[ "${{ inputs.force-install }}" == "true" ]]; then if [[ "${{ inputs.force-install }}" == "true" ]]; then
FLAGS+=("--force") FLAGS+=("--force")
fi fi
INSTALL_ARGS="${{ inputs.install-args }}" INSTALL_ARGS="${{ inputs.install-args }}"
echo "🔧 执行 pnpm install ${FLAGS[*]} ${INSTALL_ARGS}" echo "🔧 执行 pnpm install ${FLAGS[*]} ${INSTALL_ARGS}"
if [[ -n "$INSTALL_ARGS" ]]; then if [[ -n "$INSTALL_ARGS" ]]; then
pnpm install "${FLAGS[@]}" $INSTALL_ARGS pnpm install "${FLAGS[@]}" $INSTALL_ARGS
else else
pnpm install "${FLAGS[@]}" pnpm install "${FLAGS[@]}"
fi fi
if [[ "${{ inputs.clean-project-store }}" == "true" && -d ".pnpm-store" && "${PNPM_STORE_DIR}" != "$PWD/.pnpm-store" ]]; then if [[ "${{ inputs.clean-project-store }}" == "true" && -d ".pnpm-store" && "$(cd "$PNPM_STORE_DIR" 2>/dev/null && pwd)" != "$(cd .pnpm-store 2>/dev/null && pwd)" ]]; then
rm -rf .pnpm-store || true rm -rf .pnpm-store || true
fi fi