feat: 添加pnpm自动安装和store缓存模式支持

This commit is contained in:
Lyda
2025-09-18 10:46:31 +08:00
parent 6fff7a90e1
commit 5c47db3ee2
2 changed files with 169 additions and 12 deletions

View File

@@ -10,17 +10,21 @@
- 🔑 **精确 hash 控制**: 支持自定义缓存 hash确保依赖变化时缓存失效 - 🔑 **精确 hash 控制**: 支持自定义缓存 hash确保依赖变化时缓存失效
- 🔄 **Git 集成**: 可选的 git stash 功能 - 🔄 **Git 集成**: 可选的 git stash 功能
- 📊 **详细输出**: 提供缓存命中状态和使用的缓存 key - 📊 **详细输出**: 提供缓存命中状态和使用的缓存 key
- 🧰 **自动安装 pnpm**: 当选择 `package-manager: pnpm` 时,自动通过 `pnpm/action-setup@v4` 确保 pnpm 可用(可指定版本)
## 📋 输入参数 ## 📋 输入参数
| 参数名 | 描述 | 是否必需 | 默认值 | | 参数名 | 描述 | 是否必需 | 默认值 |
| ------------------- | ---------------------------- | -------- | -------------- | | ------------------- | ---------------------------- | -------- | -------------- |
| `package-manager` | 包管理器类型 (npm/pnpm/yarn) | 否 | `npm` | | `package-manager` | 包管理器类型 (npm/pnpm/yarn) | 否 | `npm` |
| `pnpm-version` | pnpm 版本(`package-manager: pnpm` 时生效) | 否 | `9` |
| `cache-mode` | 缓存模式(`node_modules``store` | 否 | `node_modules` |
| `cache-prefix` | 缓存前缀名称 | 否 | `modules` | | `cache-prefix` | 缓存前缀名称 | 否 | `modules` |
| `node-modules-path` | node_modules 目录路径 | 否 | `node_modules` | | `node-modules-path` | node_modules 目录路径 | 否 | `node_modules` |
| `force-install` | 是否强制安装 | 否 | `false` | | `force-install` | 是否强制安装 | 否 | `false` |
| `enable-git-stash` | 安装后是否执行 git stash | 否 | `false` | | `enable-git-stash` | 安装后是否执行 git stash | 否 | `false` |
| `install-command` | 自定义安装命令(覆盖默认) | 否 | `''` | | `install-command` | 自定义安装命令(覆盖默认) | 否 | `''` |
| `install-args` | 附加到默认安装命令的参数(当未提供 `install-command` 时生效) | 否 | `''` |
| `cache-hash` | 缓存 hash 值(**推荐使用** | 否 | 自动计算 | | `cache-hash` | 缓存 hash 值(**推荐使用** | 否 | 自动计算 |
## 📤 输出参数 ## 📤 输出参数
@@ -29,6 +33,7 @@
| ----------- | ------------------------- | | ----------- | ------------------------- |
| `cache-hit` | 是否命中缓存 (true/false) | | `cache-hit` | 是否命中缓存 (true/false) |
| `cache-key` | 使用的缓存 key | | `cache-key` | 使用的缓存 key |
| `cache-path` | 缓存路径(用于调试与复用) |
## 💡 重要提示 ## 💡 重要提示
@@ -65,6 +70,13 @@ with:
uses: actions/xgj/npm-install@v1 uses: actions/xgj/npm-install@v1
with: with:
package-manager: "pnpm" package-manager: "pnpm"
# 可选:指定 pnpm 版本(默认 9
pnpm-version: "9"
# 可选:切换为包管理器 store 缓存,提升复用率(首次仍需安装链接)
# cache-mode: "store"
# 可选:附加安装参数(当未设置 install-command 时生效)
# 例如:保持锁文件严格、忽略可选依赖
install-args: "--frozen-lockfile --no-optional"
``` ```
### 强制安装 + Git Stash ### 强制安装 + Git Stash
@@ -98,6 +110,31 @@ with:
install-command: "npm ci --only=production" install-command: "npm ci --only=production"
``` ```
### 仅附加参数(不覆盖命令)
```yaml
# npm 示例:追加只生产依赖
- name: 安装npm仅生产依赖
uses: actions/xgj/npm-install@v1
with:
package-manager: npm
install-args: "--only=production"
# pnpm 示例:严格锁文件 + 忽略可选依赖
- name: 安装pnpm严格锁文件
uses: actions/xgj/npm-install@v1
with:
package-manager: pnpm
install-args: "--frozen-lockfile --no-optional"
# yarn 示例:纯安装(禁用脚本)
- name: 安装yarn禁用脚本
uses: actions/xgj/npm-install@v1
with:
package-manager: yarn
install-args: "--ignore-scripts"
```
### 使用自定义缓存 hash推荐用法 ### 使用自定义缓存 hash推荐用法
```yaml ```yaml
@@ -177,8 +214,14 @@ jobs:
1. **缓存 Key 生成**: 根据包管理器类型和 lock 文件生成唯一的缓存 key 1. **缓存 Key 生成**: 根据包管理器类型和 lock 文件生成唯一的缓存 key
2. **缓存检查**: 使用`actions/cache@v4`检查是否存在匹配的缓存 2. **缓存检查**: 使用`actions/cache@v4`检查是否存在匹配的缓存
3. **条件安装**: 仅在缓存未命中时执行依赖安装 3. **自动安装 pnpm如需**: 当 `package-manager=pnpm` 时,使用 `pnpm/action-setup@v4` 确保 pnpm 已安装
4. **可选操作**: 根据配置执行 git stash 等额外操作 4. **缓存路径确定**:
-`cache-mode=node_modules` 时,缓存 `node_modules`(可用 `node-modules-path` 定义目录)。
-`cache-mode=store` 时,缓存包管理器的全局存储:`npm``~/.npm``pnpm` → 通过 `pnpm store path` 动态获取,`yarn``~/.cache/yarn`
5. **条件安装**: 仅在缓存未命中时执行依赖安装
6. **可选操作**: 根据配置执行 git stash 等额外操作
> 说明:缓存 key 中包含 OS、包管理器以及 pnpm 的版本)、缓存模式与自定义前缀,避免跨包管理器/模式的缓存误用。
## 🎯 最佳实践 ## 🎯 最佳实践
@@ -189,6 +232,21 @@ jobs:
- uses: actions/xgj/npm-install@v1 - uses: actions/xgj/npm-install@v1
with: with:
package-manager: "pnpm" # 如果项目使用pnpm package-manager: "pnpm" # 如果项目使用pnpm
### 1.1 选择合适的缓存模式
```yaml
# 追求最快的安装且目录较稳定:缓存 node_modules默认
- uses: actions/xgj/npm-install@v1
with:
package-manager: pnpm
cache-mode: node_modules
# 追求更高的通用性和复用率:缓存包管理器 store首次仍需 install 链接)
- uses: actions/xgj/npm-install@v1
with:
package-manager: pnpm
cache-mode: store
``` ```
### 2. 合理使用强制安装 ### 2. 合理使用强制安装
@@ -220,6 +278,10 @@ jobs:
- name: 重建缓存 - name: 重建缓存
if: steps.install.outputs.cache-hit != 'true' if: steps.install.outputs.cache-hit != 'true'
run: npm run build:cache run: npm run build:cache
# 调试:打印缓存路径(可用于后续步骤复用路径)
- name: 打印缓存路径
run: echo "Cache path is: ${{ steps.install.outputs.cache-path }}"
``` ```
## 🚨 注意事项 ## 🚨 注意事项
@@ -228,6 +290,7 @@ jobs:
2. **缓存大小**: node_modules 可能很大,请关注 GitHub Actions 的缓存限制 2. **缓存大小**: node_modules 可能很大,请关注 GitHub Actions 的缓存限制
3. **Lock 文件**: 确保 lock 文件已提交到仓库,这是缓存 key 生成的基础 3. **Lock 文件**: 确保 lock 文件已提交到仓库,这是缓存 key 生成的基础
4. **权限**: 某些自定义安装命令可能需要额外的权限 4. **权限**: 某些自定义安装命令可能需要额外的权限
5. **跨包管理器缓存隔离**: 缓存 key 与 restore-keys 均包含包管理器、版本pnpm与模式切换包管理器/模式时会触发一次干净安装,避免污染
## 🔍 故障排除 ## 🔍 故障排除
@@ -238,6 +301,11 @@ jobs:
- lock 文件是否存在且已提交 - lock 文件是否存在且已提交
- package.json 或 lock 文件是否有变更 - package.json 或 lock 文件是否有变更
- 缓存前缀是否与之前一致 - 缓存前缀是否与之前一致
- 是否切换了包管理器或 `cache-mode`(切换会生成不同的 key 与前缀)
### pnpm store 路径
`cache-mode=store``package-manager=pnpm` 时,本 Action 会通过 `pnpm store path` 动态解析缓存目录;若命令不可用,则回退到 `~/.pnpm-store`
### 安装失败 ### 安装失败

View File

@@ -10,6 +10,16 @@ inputs:
required: false required: false
default: 'npm' default: 'npm'
pnpm-version:
description: 'pnpm 版本(当 package-manager=pnpm 时生效)'
required: false
default: '10'
cache-mode:
description: '缓存模式node_modules 或 store'
required: false
default: 'store'
cache-prefix: cache-prefix:
description: '缓存前缀名称' description: '缓存前缀名称'
@@ -35,6 +45,11 @@ inputs:
description: '自定义安装命令(可选,会覆盖默认命令)' description: '自定义安装命令(可选,会覆盖默认命令)'
required: false required: false
default: '' default: ''
install-args:
description: '附加到默认安装命令的参数(当未提供 install-command 时生效)'
required: false
default: ''
cache-hash: cache-hash:
description: '缓存hash值推荐使用hashFiles计算' description: '缓存hash值推荐使用hashFiles计算'
@@ -50,6 +65,10 @@ outputs:
description: '使用的缓存key' description: '使用的缓存key'
value: ${{ steps.cache-key.outputs.key }} value: ${{ steps.cache-key.outputs.key }}
cache-path:
description: '缓存路径(用于调试与复用)'
value: ${{ steps.cache-path.outputs.path }}
runs: runs:
using: 'composite' using: 'composite'
steps: steps:
@@ -80,19 +99,78 @@ runs:
echo "⚠️ 使用默认hash值: ${CACHE_HASH_SHORT}" echo "⚠️ 使用默认hash值: ${CACHE_HASH_SHORT}"
fi fi
CACHE_KEY="${{ runner.os }}-${{ inputs.cache-prefix }}-${CACHE_HASH_SHORT}" # 生成包管理器后缀,避免不同包管理器/版本的缓存互相污染
MANAGER="${{ inputs.package-manager }}"
MANAGER_SUFFIX="$MANAGER"
if [[ "$MANAGER" == "pnpm" && -n "${{ inputs.pnpm-version }}" && "${{ inputs.pnpm-version }}" != "" ]]; then
MANAGER_SUFFIX="${MANAGER}-v${{ inputs.pnpm-version }}"
fi
# 模式后缀隔离不同缓存模式node_modules vs store
MODE_SUFFIX="${{ inputs.cache-mode }}"
if [[ -z "$MODE_SUFFIX" ]]; then MODE_SUFFIX="node_modules"; fi
# 构建缓存key<OS>-<manager[-vX]>-<mode>-<prefix>-<hash>
CACHE_KEY="${{ runner.os }}-${MANAGER_SUFFIX}-${MODE_SUFFIX}-${{ inputs.cache-prefix }}-${CACHE_HASH_SHORT}"
# 恢复前缀:用于 restore-keys防止不同包管理器/模式的回退误命中
RESTORE_PREFIX="${{ runner.os }}-${MANAGER_SUFFIX}-${MODE_SUFFIX}-${{ inputs.cache-prefix }}-"
echo "key=${CACHE_KEY}" >> $GITHUB_OUTPUT echo "key=${CACHE_KEY}" >> $GITHUB_OUTPUT
echo "restore-prefix=${RESTORE_PREFIX}" >> $GITHUB_OUTPUT
echo "使用hash: ${CACHE_HASH}" echo "使用hash: ${CACHE_HASH}"
echo "缓存key: ${CACHE_KEY}" echo "缓存key: ${CACHE_KEY}"
- name: 确保 pnpm 可用(用于计算 store 路径)
if: inputs.package-manager == 'pnpm' && inputs.cache-mode == 'store'
uses: pnpm/action-setup@v4
with:
version: ${{ inputs.pnpm-version }}
run_install: false
- name: 确定缓存路径
id: cache-path
shell: bash
run: |
MODE="${{ inputs.cache-mode }}"
MANAGER="${{ inputs.package-manager }}"
if [[ -z "$MODE" ]]; then MODE="node_modules"; fi
if [[ "$MODE" == "node_modules" ]]; then
CACHE_PATH="${{ inputs.node-modules-path }}"
else
case "$MANAGER" in
"npm")
# npm 的全局缓存目录
CACHE_PATH="$HOME/.npm"
;;
"pnpm")
# pnpm store 路径(通过命令获取,以兼容自定义配置)
if command -v pnpm >/dev/null 2>&1; then
CACHE_PATH="$(pnpm store path)"
else
# 回退:若未能获取,则使用常见默认路径
CACHE_PATH="$HOME/.pnpm-store"
fi
;;
"yarn")
# yarn v1 默认缓存目录yarn berry 采用不同机制,这里聚焦 v1 常见场景)
CACHE_PATH="$HOME/.cache/yarn"
;;
*)
echo "❌ 不支持的包管理器: $MANAGER"
exit 1
;;
esac
fi
echo "path=${CACHE_PATH}" >> $GITHUB_OUTPUT
- name: 拉取缓存依赖 - name: 拉取缓存依赖
id: cache id: cache
uses: actions/cache@v4 uses: actions/cache@v4
with: with:
path: ${{ inputs.node-modules-path }} path: ${{ steps.cache-path.outputs.path }}
key: ${{ steps.cache-key.outputs.key }} key: ${{ steps.cache-key.outputs.key }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ inputs.cache-prefix }}- ${{ steps.cache-key.outputs.restore-prefix }}
- name: 显示缓存状态 - name: 显示缓存状态
shell: bash shell: bash
@@ -103,8 +181,15 @@ runs:
echo "⚠️ 缓存未命中,开始安装依赖" echo "⚠️ 缓存未命中,开始安装依赖"
fi fi
- name: 确保 pnpm 可用(如需)
if: inputs.package-manager == 'pnpm'
uses: pnpm/action-setup@v4
with:
version: ${{ inputs.pnpm-version }}
run_install: false
- name: 安装依赖 - name: 安装依赖
if: steps.cache.outputs.cache-hit != 'true' if: (inputs.cache-mode == 'node_modules' && steps.cache.outputs.cache-hit != 'true') || (inputs.cache-mode == 'store')
shell: bash shell: bash
run: | run: |
# 如果提供了自定义安装命令,使用自定义命令 # 如果提供了自定义安装命令,使用自定义命令
@@ -112,33 +197,37 @@ runs:
echo "🔧 使用自定义安装命令: ${{ inputs.install-command }}" echo "🔧 使用自定义安装命令: ${{ inputs.install-command }}"
${{ inputs.install-command }} ${{ inputs.install-command }}
else else
INSTALL_ARGS="${{ inputs.install-args }}"
if [[ -n "$INSTALL_ARGS" ]]; then
echo " 附加安装参数: $INSTALL_ARGS"
fi
# 根据包管理器选择安装命令 # 根据包管理器选择安装命令
case "${{ inputs.package-manager }}" in case "${{ inputs.package-manager }}" in
"npm") "npm")
if [[ "${{ inputs.force-install }}" == "true" ]]; then if [[ "${{ inputs.force-install }}" == "true" ]]; then
echo "🔧 使用npm强制安装" echo "🔧 使用npm强制安装"
npm install --force npm install --force ${INSTALL_ARGS}
else else
echo "🔧 使用npm安装" echo "🔧 使用npm安装"
npm install npm install ${INSTALL_ARGS}
fi fi
;; ;;
"pnpm") "pnpm")
if [[ "${{ inputs.force-install }}" == "true" ]]; then if [[ "${{ inputs.force-install }}" == "true" ]]; then
echo "🔧 使用pnpm强制安装" echo "🔧 使用pnpm强制安装"
pnpm install --force pnpm install --force ${INSTALL_ARGS}
else else
echo "🔧 使用pnpm安装" echo "🔧 使用pnpm安装"
pnpm install pnpm install ${INSTALL_ARGS}
fi fi
;; ;;
"yarn") "yarn")
if [[ "${{ inputs.force-install }}" == "true" ]]; then if [[ "${{ inputs.force-install }}" == "true" ]]; then
echo "🔧 使用yarn强制安装" echo "🔧 使用yarn强制安装"
yarn install --force yarn install --force ${INSTALL_ARGS}
else else
echo "🔧 使用yarn安装" echo "🔧 使用yarn安装"
yarn install yarn install ${INSTALL_ARGS}
fi fi
;; ;;
*) *)