name: 'npm依赖安装与缓存' description: '自动缓存和安装npm依赖,支持多种包管理器' branding: icon: 'package' color: 'blue' inputs: package-manager: description: '包管理器类型 (npm, pnpm, yarn)' required: false default: 'pnpm' pnpm-version: description: 'pnpm 版本(当 package-manager=pnpm 时生效)' required: false default: '10' cache-mode: description: '缓存模式:node_modules 或 store' required: false default: 'store' cache-prefix: description: '缓存前缀名称' required: false default: 'modules' node-modules-path: description: 'node_modules目录路径' required: false default: 'node_modules' force-install: description: '是否强制安装 (true/false)' required: false default: 'false' enable-git-stash: description: '安装后是否执行git stash (true/false)' required: false default: 'false' install-command: description: '自定义安装命令(可选,会覆盖默认命令)' required: false default: '' install-args: description: '附加到默认安装命令的参数(当未提供 install-command 时生效)' required: false default: '' cache-hash: description: '缓存hash值(推荐使用hashFiles计算)' required: false default: "" outputs: cache-hit: description: '是否命中缓存 (true/false)' value: ${{ steps.cache.outputs.cache-hit }} cache-key: description: '使用的缓存key' value: ${{ steps.cache-key.outputs.key }} cache-path: description: '缓存路径(用于调试与复用)' value: ${{ steps.cache-path.outputs.path }} runs: using: 'composite' steps: - name: 生成缓存key id: cache-key shell: bash env: FALLBACK_HASH: ${{ hashFiles('package.json') }} run: | # 确定使用的hash值 if [[ -n "${{ inputs.cache-hash }}" && "${{ inputs.cache-hash }}" != "" ]]; then CACHE_HASH="${{ inputs.cache-hash }}" echo "✅ 使用用户传入的hash值" elif [[ -n "${FALLBACK_HASH}" && "${FALLBACK_HASH}" != "" ]]; then CACHE_HASH="${FALLBACK_HASH}" echo "📝 未提供hash,使用package.json作为fallback" else CACHE_HASH="" echo "⚠️ 警告: 无法获取hash值,使用默认值" fi # 截取hash的前12位 if [[ -n "${CACHE_HASH}" && "${CACHE_HASH}" != "" ]]; then CACHE_HASH_SHORT=$(echo "${CACHE_HASH}" | head -c 12) echo "✅ 成功计算缓存hash: ${CACHE_HASH_SHORT}" else CACHE_HASH_SHORT="no-hash" echo "⚠️ 使用默认hash值: ${CACHE_HASH_SHORT}" fi # 生成包管理器后缀,避免不同包管理器/版本的缓存互相污染 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 二进制 (Corepack) if: inputs.package-manager == 'pnpm' uses: actions/cache@v4 with: path: | ~/.cache/corepack ~/Library/Caches/CorePack ~/Library/Caches/Corepack C:\Users\runneradmin\AppData\Local\Corepack\cache key: ${{ runner.os }}-corepack-pnpm-${{ inputs.pnpm-version }} restore-keys: | ${{ runner.os }}-corepack-pnpm- - 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: ${{ steps.cache-path.outputs.path }} key: ${{ steps.cache-key.outputs.key }} restore-keys: | ${{ steps.cache-key.outputs.restore-prefix }} - name: 显示缓存状态 shell: bash run: | if [[ "${{ steps.cache.outputs.cache-hit }}" == "true" ]]; then echo "✅ 缓存命中,跳过依赖安装" else echo "⚠️ 缓存未命中,开始安装依赖" fi - name: 确保 pnpm 可用(如需) if: inputs.package-manager == 'pnpm' uses: pnpm/action-setup@v4 with: version: ${{ inputs.pnpm-version }} run_install: false - name: 安装依赖 if: (inputs.cache-mode == 'node_modules' && steps.cache.outputs.cache-hit != 'true') || (inputs.cache-mode == 'store') shell: bash run: | # 如果提供了自定义安装命令,使用自定义命令 if [[ -n "${{ inputs.install-command }}" ]]; then 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 ${INSTALL_ARGS} else echo "🔧 使用npm安装" npm install ${INSTALL_ARGS} fi ;; "pnpm") if [[ "${{ inputs.force-install }}" == "true" ]]; then echo "🔧 使用pnpm强制安装" pnpm install --force ${INSTALL_ARGS} else echo "🔧 使用pnpm安装" pnpm install ${INSTALL_ARGS} fi ;; "yarn") if [[ "${{ inputs.force-install }}" == "true" ]]; then echo "🔧 使用yarn强制安装" yarn install --force ${INSTALL_ARGS} else echo "🔧 使用yarn安装" yarn install ${INSTALL_ARGS} fi ;; *) echo "❌ 不支持的包管理器: ${{ inputs.package-manager }}" exit 1 ;; esac fi echo "✅ 依赖安装完成" - name: 执行Git Stash if: steps.cache.outputs.cache-hit != 'true' && inputs.enable-git-stash == 'true' shell: bash run: | echo "🔄 执行git stash..." git stash echo "✅ git stash完成" - name: 安装总结 shell: bash run: | echo "📦 依赖安装总结:" echo " - 包管理器: ${{ inputs.package-manager }}" echo " - 缓存命中: ${{ steps.cache.outputs.cache-hit }}" echo " - 缓存key: ${{ steps.cache-key.outputs.key }}" if [[ "${{ steps.cache.outputs.cache-hit }}" != "true" ]]; then echo " - 强制安装: ${{ inputs.force-install }}" echo " - Git Stash: ${{ inputs.enable-git-stash }}" fi