mirror of
https://git.bjxgj.com/xgj/xgj-actions.git
synced 2025-10-14 06:33:37 +08:00
308 lines
12 KiB
YAML
308 lines
12 KiB
YAML
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'
|
||
|
||
optimize-install-flags:
|
||
description: '是否启用安装参数优化(pnpm+store时自动使用 --offline/--prefer-offline 与 --frozen-lockfile)(true/false)'
|
||
required: false
|
||
default: 'true'
|
||
|
||
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: ''
|
||
|
||
clean-project-store:
|
||
description: '在安装前清理项目根的 .pnpm-store 残留(true/false)'
|
||
required: false
|
||
default: 'false'
|
||
|
||
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:<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 "restore-prefix=${RESTORE_PREFIX}" >> $GITHUB_OUTPUT
|
||
echo "使用hash: ${CACHE_HASH}"
|
||
echo "缓存key: ${CACHE_KEY}"
|
||
|
||
- name: 确保 pnpm 可用
|
||
if: inputs.package-manager == 'pnpm'
|
||
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 }}"
|
||
# 即使只缓存 node_modules,pnpm 也会使用 store。为避免在项目根生成 .pnpm-store,这里同样固定 PNPM_STORE_DIR
|
||
if [[ "$MANAGER" == "pnpm" ]]; then
|
||
DEFAULT_PNPM_STORE="${RUNNER_TEMP:-$HOME}/.pnpm-store"
|
||
echo "PNPM_STORE_DIR=${DEFAULT_PNPM_STORE}" >> "$GITHUB_ENV"
|
||
fi
|
||
else
|
||
case "$MANAGER" in
|
||
"npm")
|
||
# npm 的全局缓存目录
|
||
CACHE_PATH="$HOME/.npm"
|
||
;;
|
||
"pnpm")
|
||
# 固定 pnpm store 路径到 runner 的临时目录或 HOME,避免在项目根生成 .pnpm-store
|
||
# 说明:一些仓库的 .npmrc 可能配置了 store-dir=.pnpm-store,会导致在工作目录创建 .pnpm-store
|
||
# 这里通过设置 PNPM_STORE_DIR 环境变量进行覆盖,确保缓存路径稳定可控
|
||
DEFAULT_PNPM_STORE="${RUNNER_TEMP:-$HOME}/.pnpm-store"
|
||
# 将目录导出到环境,供后续安装步骤使用
|
||
echo "PNPM_STORE_DIR=${DEFAULT_PNPM_STORE}" >> "$GITHUB_ENV"
|
||
CACHE_PATH="${DEFAULT_PNPM_STORE}"
|
||
;;
|
||
"yarn")
|
||
# yarn v1 默认缓存目录(yarn berry 采用不同机制,这里聚焦 v1 常见场景)
|
||
CACHE_PATH="$HOME/.cache/yarn"
|
||
;;
|
||
*)
|
||
echo "❌ 不支持的包管理器: $MANAGER"
|
||
exit 1
|
||
;;
|
||
esac
|
||
fi
|
||
# 打印最终缓存目录,便于调试与确认
|
||
echo "📁 最终缓存目录: ${CACHE_PATH}"
|
||
if [[ "$MANAGER" == "pnpm" ]]; then
|
||
echo "📦 PNPM_STORE_DIR=${PNPM_STORE_DIR:-$DEFAULT_PNPM_STORE}"
|
||
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
|
||
if [[ "${{ inputs.cache-mode }}" == "store" ]]; then
|
||
echo "✅ 缓存命中(store),将执行快速链接安装(不会下载包,仅链接)"
|
||
else
|
||
echo "✅ 缓存命中,跳过依赖安装"
|
||
fi
|
||
else
|
||
echo "⚠️ 缓存未命中,开始安装依赖"
|
||
fi
|
||
|
||
- name: 安装依赖
|
||
if: (inputs.cache-mode == 'node_modules' && steps.cache.outputs.cache-hit != 'true') || (inputs.cache-mode == 'store')
|
||
shell: bash
|
||
run: |
|
||
# 若使用 pnpm,在本步骤内始终显式设置 PNPM_STORE_DIR(覆盖可能存在的相对配置)
|
||
if [[ "${{ inputs.package-manager }}" == "pnpm" ]]; then
|
||
if [[ "${{ inputs.cache-mode }}" == "store" ]]; then
|
||
# store 模式:cache-path 的 path 即为期望的 store 目录
|
||
export PNPM_STORE_DIR="${{ steps.cache-path.outputs.path }}"
|
||
else
|
||
# node_modules 模式:不要回退到 cache-path(那是 node_modules 目录),而是使用 RUNNER_TEMP/HOME
|
||
export PNPM_STORE_DIR="${PNPM_STORE_DIR:-${RUNNER_TEMP:-$HOME}/.pnpm-store}"
|
||
fi
|
||
echo "🧩 已设置 PNPM_STORE_DIR=${PNPM_STORE_DIR}"
|
||
fi
|
||
|
||
# 可选清理:如启用并发现项目根存在残留的 .pnpm-store,且与目标目录不同,则清理
|
||
if [[ "${{ inputs.package-manager }}" == "pnpm" && "${{ inputs.clean-project-store }}" == "true" ]]; then
|
||
if [[ -d ".pnpm-store" && "${PNPM_STORE_DIR}" != "$PWD/.pnpm-store" ]]; then
|
||
echo "🧹 清理项目根的残留 .pnpm-store(目标store为 ${PNPM_STORE_DIR})"
|
||
rm -rf .pnpm-store || true
|
||
fi
|
||
fi
|
||
# 如果提供了自定义安装命令,使用自定义命令
|
||
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
|
||
# 根据模式与缓存命中优化安装参数(尽量离线加速)
|
||
EXTRA_FLAGS=""
|
||
if [[ "${{ inputs.optimize-install-flags }}" == "true" && "${{ inputs.package-manager }}" == "pnpm" && "${{ inputs.cache-mode }}" == "store" ]]; then
|
||
if [[ "${{ steps.cache.outputs.cache-hit }}" == "true" ]]; then
|
||
# 缓存命中:使用完全离线与锁定安装,避免网络请求
|
||
EXTRA_FLAGS="--offline --frozen-lockfile"
|
||
else
|
||
# 缓存未命中:尽量离线,但允许必要网络;同时锁定避免解析差异
|
||
EXTRA_FLAGS="--prefer-offline --frozen-lockfile"
|
||
fi
|
||
echo "⚡ pnpm安装优化参数: $EXTRA_FLAGS"
|
||
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 ${EXTRA_FLAGS} ${INSTALL_ARGS}
|
||
else
|
||
echo "🔧 使用pnpm安装"
|
||
pnpm install ${EXTRA_FLAGS} ${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
|