diff --git a/npm-install/README.md b/npm-install/README.md index 60d66b8..30e4dfc 100644 --- a/npm-install/README.md +++ b/npm-install/README.md @@ -10,17 +10,21 @@ - 🔑 **精确 hash 控制**: 支持自定义缓存 hash,确保依赖变化时缓存失效 - 🔄 **Git 集成**: 可选的 git stash 功能 - 📊 **详细输出**: 提供缓存命中状态和使用的缓存 key +- 🧰 **自动安装 pnpm**: 当选择 `package-manager: pnpm` 时,自动通过 `pnpm/action-setup@v4` 确保 pnpm 可用(可指定版本) ## 📋 输入参数 | 参数名 | 描述 | 是否必需 | 默认值 | | ------------------- | ---------------------------- | -------- | -------------- | | `package-manager` | 包管理器类型 (npm/pnpm/yarn) | 否 | `npm` | +| `pnpm-version` | pnpm 版本(`package-manager: pnpm` 时生效) | 否 | `9` | +| `cache-mode` | 缓存模式(`node_modules` 或 `store`) | 否 | `node_modules` | | `cache-prefix` | 缓存前缀名称 | 否 | `modules` | | `node-modules-path` | node_modules 目录路径 | 否 | `node_modules` | | `force-install` | 是否强制安装 | 否 | `false` | | `enable-git-stash` | 安装后是否执行 git stash | 否 | `false` | | `install-command` | 自定义安装命令(覆盖默认) | 否 | `''` | +| `install-args` | 附加到默认安装命令的参数(当未提供 `install-command` 时生效) | 否 | `''` | | `cache-hash` | 缓存 hash 值(**推荐使用**) | 否 | 自动计算 | ## 📤 输出参数 @@ -29,6 +33,7 @@ | ----------- | ------------------------- | | `cache-hit` | 是否命中缓存 (true/false) | | `cache-key` | 使用的缓存 key | +| `cache-path` | 缓存路径(用于调试与复用) | ## 💡 重要提示 @@ -65,6 +70,13 @@ with: uses: actions/xgj/npm-install@v1 with: package-manager: "pnpm" + # 可选:指定 pnpm 版本(默认 9) + pnpm-version: "9" + # 可选:切换为包管理器 store 缓存,提升复用率(首次仍需安装链接) + # cache-mode: "store" + # 可选:附加安装参数(当未设置 install-command 时生效) + # 例如:保持锁文件严格、忽略可选依赖 + install-args: "--frozen-lockfile --no-optional" ``` ### 强制安装 + Git Stash @@ -98,6 +110,31 @@ with: 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(推荐用法) ```yaml @@ -177,8 +214,14 @@ jobs: 1. **缓存 Key 生成**: 根据包管理器类型和 lock 文件生成唯一的缓存 key 2. **缓存检查**: 使用`actions/cache@v4`检查是否存在匹配的缓存 -3. **条件安装**: 仅在缓存未命中时执行依赖安装 -4. **可选操作**: 根据配置执行 git stash 等额外操作 +3. **自动安装 pnpm(如需)**: 当 `package-manager=pnpm` 时,使用 `pnpm/action-setup@v4` 确保 pnpm 已安装 +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 with: 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. 合理使用强制安装 @@ -220,6 +278,10 @@ jobs: - name: 重建缓存 if: steps.install.outputs.cache-hit != 'true' 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 的缓存限制 3. **Lock 文件**: 确保 lock 文件已提交到仓库,这是缓存 key 生成的基础 4. **权限**: 某些自定义安装命令可能需要额外的权限 +5. **跨包管理器缓存隔离**: 缓存 key 与 restore-keys 均包含包管理器、版本(pnpm)与模式,切换包管理器/模式时会触发一次干净安装,避免污染 ## 🔍 故障排除 @@ -238,6 +301,11 @@ jobs: - lock 文件是否存在且已提交 - package.json 或 lock 文件是否有变更 - 缓存前缀是否与之前一致 +- 是否切换了包管理器或 `cache-mode`(切换会生成不同的 key 与前缀) + +### pnpm store 路径 + +当 `cache-mode=store` 且 `package-manager=pnpm` 时,本 Action 会通过 `pnpm store path` 动态解析缓存目录;若命令不可用,则回退到 `~/.pnpm-store`。 ### 安装失败 diff --git a/npm-install/action.yml b/npm-install/action.yml index 6ab583f..7c24a59 100644 --- a/npm-install/action.yml +++ b/npm-install/action.yml @@ -10,6 +10,16 @@ inputs: required: false 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: description: '缓存前缀名称' @@ -35,6 +45,11 @@ inputs: description: '自定义安装命令(可选,会覆盖默认命令)' required: false default: '' + + install-args: + description: '附加到默认安装命令的参数(当未提供 install-command 时生效)' + required: false + default: '' cache-hash: description: '缓存hash值(推荐使用hashFiles计算)' @@ -50,6 +65,10 @@ outputs: description: '使用的缓存key' value: ${{ steps.cache-key.outputs.key }} + cache-path: + description: '缓存路径(用于调试与复用)' + value: ${{ steps.cache-path.outputs.path }} + runs: using: 'composite' steps: @@ -80,19 +99,78 @@ runs: echo "⚠️ 使用默认hash值: ${CACHE_HASH_SHORT}" 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:---- + 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 "restore-prefix=${RESTORE_PREFIX}" >> $GITHUB_OUTPUT echo "使用hash: ${CACHE_HASH}" 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: 拉取缓存依赖 id: cache uses: actions/cache@v4 with: - path: ${{ inputs.node-modules-path }} + path: ${{ steps.cache-path.outputs.path }} key: ${{ steps.cache-key.outputs.key }} restore-keys: | - ${{ runner.os }}-${{ inputs.cache-prefix }}- + ${{ steps.cache-key.outputs.restore-prefix }} - name: 显示缓存状态 shell: bash @@ -103,8 +181,15 @@ runs: echo "⚠️ 缓存未命中,开始安装依赖" fi + - name: 确保 pnpm 可用(如需) + if: inputs.package-manager == 'pnpm' + uses: pnpm/action-setup@v4 + with: + version: ${{ inputs.pnpm-version }} + run_install: false + - 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 run: | # 如果提供了自定义安装命令,使用自定义命令 @@ -112,33 +197,37 @@ runs: echo "🔧 使用自定义安装命令: ${{ inputs.install-command }}" ${{ inputs.install-command }} else + INSTALL_ARGS="${{ inputs.install-args }}" + if [[ -n "$INSTALL_ARGS" ]]; then + echo "➕ 附加安装参数: $INSTALL_ARGS" + fi # 根据包管理器选择安装命令 case "${{ inputs.package-manager }}" in "npm") if [[ "${{ inputs.force-install }}" == "true" ]]; then echo "🔧 使用npm强制安装" - npm install --force + npm install --force ${INSTALL_ARGS} else echo "🔧 使用npm安装" - npm install + npm install ${INSTALL_ARGS} fi ;; "pnpm") if [[ "${{ inputs.force-install }}" == "true" ]]; then echo "🔧 使用pnpm强制安装" - pnpm install --force + pnpm install --force ${INSTALL_ARGS} else echo "🔧 使用pnpm安装" - pnpm install + pnpm install ${INSTALL_ARGS} fi ;; "yarn") if [[ "${{ inputs.force-install }}" == "true" ]]; then echo "🔧 使用yarn强制安装" - yarn install --force + yarn install --force ${INSTALL_ARGS} else echo "🔧 使用yarn安装" - yarn install + yarn install ${INSTALL_ARGS} fi ;; *)