748a54873b
- clean-project-store 默认值从 true 改为 false - 移除 PNPM_VERSION 冗余赋值语句 - 优化 store 路径检测:优先检测 Docker 环境默认路径 /pnpm/store - 仅在非 Docker 环境时才使用 pnpm store path 命令获取 - 移除 PNPM_STORE_DIR 环境变量设置,仅保留输出 - 优化错误提示,明确路径检测失败的可能原因
278 lines
9.8 KiB
YAML
278 lines
9.8 KiB
YAML
name: 'pnpm依赖安装与缓存'
|
||
description: '专注于pnpm的依赖缓存与安装,加速重复执行'
|
||
branding:
|
||
icon: 'package'
|
||
color: 'yellow'
|
||
|
||
inputs:
|
||
cache-prefix:
|
||
description: '缓存前缀名称(留空则自动使用项目名)'
|
||
required: false
|
||
default: ''
|
||
|
||
cache-hash:
|
||
description: '缓存hash值(留空则自动使用 pnpm-lock.yaml 或 package.json)'
|
||
required: false
|
||
default: ''
|
||
|
||
force-install:
|
||
description: '是否强制安装 (true/false)'
|
||
required: false
|
||
default: 'false'
|
||
|
||
install-command:
|
||
description: '自定义安装命令(若设置则完全覆盖默认命令)'
|
||
required: false
|
||
default: ''
|
||
|
||
install-args:
|
||
description: '附加到默认安装命令的参数(当未提供 install-command 时生效)'
|
||
required: false
|
||
default: ''
|
||
|
||
strict-lockfile-check:
|
||
description: '严格检查 lockfile 是否被修改 (true/false)'
|
||
required: false
|
||
default: 'true'
|
||
|
||
clean-project-store:
|
||
description: '安装后清理项目根目录的 .pnpm-store (true/false)'
|
||
required: false
|
||
default: 'false'
|
||
|
||
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: 检查pnpm
|
||
id: detect
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
if ! command -v pnpm >/dev/null 2>&1; then
|
||
echo "❌ pnpm 未安装,请先使用 pnpm/action-setup 安装" >&2
|
||
exit 1
|
||
fi
|
||
VERSION=$(pnpm --version 2>/dev/null | tr -d '\n' | tr -d '\r')
|
||
if [[ -z "$VERSION" ]]; then
|
||
echo "❌ 无法获取 pnpm 版本" >&2
|
||
exit 1
|
||
fi
|
||
echo "✅ 检测到 pnpm 版本: $VERSION"
|
||
echo "pnpm-version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||
echo "PNPM_VERSION=${VERSION}" >> "$GITHUB_ENV"
|
||
|
||
- name: 生成缓存key
|
||
id: cache-key
|
||
shell: bash
|
||
env:
|
||
PNPM_VERSION: ${{ steps.detect.outputs.pnpm-version }}
|
||
LOCKFILE_HASH: ${{ hashFiles('**/pnpm-lock.yaml') }}
|
||
PACKAGE_HASH: ${{ hashFiles('**/package.json') }}
|
||
run: |
|
||
set -euo pipefail
|
||
|
||
# 智能选择最佳 hash 策略
|
||
if [[ -n "${{ inputs.cache-hash }}" ]]; then
|
||
CACHE_HASH="${{ inputs.cache-hash }}"
|
||
HASH_SOURCE="custom"
|
||
elif [[ -n "${LOCKFILE_HASH}" ]]; then
|
||
CACHE_HASH="${LOCKFILE_HASH}"
|
||
HASH_SOURCE="pnpm-lock.yaml"
|
||
elif [[ -n "${PACKAGE_HASH}" ]]; then
|
||
CACHE_HASH="${PACKAGE_HASH}"
|
||
HASH_SOURCE="package.json"
|
||
else
|
||
CACHE_HASH=""
|
||
HASH_SOURCE="none"
|
||
fi
|
||
|
||
# 生成简短的 hash 用于 key
|
||
if [[ -n "$CACHE_HASH" ]]; then
|
||
CACHE_HASH_SHORT=$(echo "$CACHE_HASH" | head -c 12)
|
||
else
|
||
CACHE_HASH_SHORT="no-lock"
|
||
fi
|
||
|
||
# 智能获取缓存前缀
|
||
if [[ -n "${{ inputs.cache-prefix }}" ]]; then
|
||
CACHE_PREFIX="${{ inputs.cache-prefix }}"
|
||
else
|
||
# 获取当前工作目录相对于仓库根目录的路径,避免 monorepo 子包冲突
|
||
REPO_ROOT="$GITHUB_WORKSPACE"
|
||
CURRENT_DIR="$(pwd)"
|
||
|
||
# 计算相对路径
|
||
if [[ "$CURRENT_DIR" == "$REPO_ROOT" ]]; then
|
||
# 在仓库根目录,使用仓库名
|
||
CACHE_PREFIX=$(basename "$REPO_ROOT" 2>/dev/null || echo "project")
|
||
else
|
||
# 在子目录,使用相对路径(替换 / 为 -)
|
||
REL_PATH="${CURRENT_DIR#$REPO_ROOT/}"
|
||
CACHE_PREFIX=$(echo "$REL_PATH" | tr '/' '-')
|
||
fi
|
||
fi
|
||
|
||
# 构建缓存 key
|
||
CACHE_KEY="${{ runner.os }}-${CACHE_PREFIX}-pnpm-v${PNPM_VERSION}-${CACHE_HASH_SHORT}"
|
||
RESTORE_PREFIX="${{ runner.os }}-${CACHE_PREFIX}-pnpm-v${PNPM_VERSION}-"
|
||
|
||
echo "📝 缓存 key: ${CACHE_KEY}"
|
||
echo "📝 Hash 来源: ${HASH_SOURCE} (${CACHE_HASH_SHORT})"
|
||
echo "📝 缓存前缀: ${CACHE_PREFIX}"
|
||
|
||
echo "key=${CACHE_KEY}" >> "$GITHUB_OUTPUT"
|
||
echo "restore-prefix=${RESTORE_PREFIX}" >> "$GITHUB_OUTPUT"
|
||
echo "hash=${CACHE_HASH}" >> "$GITHUB_OUTPUT"
|
||
|
||
- name: 确定缓存路径
|
||
id: cache-path
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
if ! command -v pnpm >/dev/null 2>&1; then
|
||
echo "❌ 未找到 pnpm,请先通过 pnpm/action-setup 安装" >&2
|
||
exit 1
|
||
fi
|
||
|
||
# 优先级:Docker 默认路径 > pnpm store path
|
||
STORE_DIR_CANDIDATE=""
|
||
|
||
# 1. 检查 Docker 环境默认路径
|
||
if [[ -d "/pnpm/store" ]]; then
|
||
STORE_DIR_CANDIDATE="/pnpm/store"
|
||
echo "📝 检测到 Docker 环境,使用默认路径: $STORE_DIR_CANDIDATE"
|
||
# 2. 通过 pnpm 命令获取
|
||
else
|
||
STORE_DIR_CANDIDATE=$(pnpm store path --silent 2>/dev/null | grep -v '^[[:space:]]*$' | tail -n1 | tr -d '\r\n')
|
||
if [[ -n "$STORE_DIR_CANDIDATE" ]]; then
|
||
echo "📝 通过 pnpm store path 获取: $STORE_DIR_CANDIDATE"
|
||
fi
|
||
fi
|
||
|
||
if [[ -z "$STORE_DIR_CANDIDATE" ]]; then
|
||
echo "❌ 无法确定 pnpm store 路径" >&2
|
||
echo "💡 提示: 确保 pnpm 配置正确或检查 /pnpm/store 目录" >&2
|
||
exit 1
|
||
fi
|
||
|
||
echo "📦 pnpm store 路径: $STORE_DIR_CANDIDATE"
|
||
echo "path=${STORE_DIR_CANDIDATE}" >> "$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: 安装依赖
|
||
shell: bash
|
||
run: |
|
||
set -euo pipefail
|
||
|
||
# 获取 store 路径(优先使用已配置的路径)
|
||
STORE_PATH="${{ steps.cache-path.outputs.path }}"
|
||
echo "📦 pnpm store 路径: $STORE_PATH"
|
||
|
||
# 记录安装前的 lockfile 状态
|
||
LOCKFILE_BEFORE=""
|
||
if [[ -f "pnpm-lock.yaml" ]]; then
|
||
LOCKFILE_BEFORE=$(md5sum pnpm-lock.yaml 2>/dev/null || md5 pnpm-lock.yaml 2>/dev/null || echo "")
|
||
fi
|
||
|
||
# 处理自定义安装命令
|
||
if [[ -n "${{ inputs.install-command }}" ]]; then
|
||
echo "🔧 使用自定义安装命令: ${{ inputs.install-command }}"
|
||
eval "${{ inputs.install-command }}"
|
||
else
|
||
# 构建安装参数
|
||
FLAGS=("--prefer-offline" "--frozen-lockfile")
|
||
|
||
if [[ "${{ inputs.force-install }}" == "true" ]]; then
|
||
FLAGS+=("--force")
|
||
echo "⚠️ 强制重新安装模式"
|
||
fi
|
||
|
||
INSTALL_ARGS="${{ inputs.install-args }}"
|
||
|
||
# 显示执行命令
|
||
if [[ -n "$INSTALL_ARGS" ]]; then
|
||
echo "🔧 执行: pnpm install ${FLAGS[*]} ${INSTALL_ARGS}"
|
||
pnpm install "${FLAGS[@]}" ${INSTALL_ARGS}
|
||
else
|
||
echo "🔧 执行: pnpm install ${FLAGS[*]}"
|
||
pnpm install "${FLAGS[@]}"
|
||
fi
|
||
fi
|
||
|
||
# 清理项目本地 .pnpm-store (如果存在且与全局 store 不同)
|
||
if [[ "${{ inputs.clean-project-store }}" == "true" && -d ".pnpm-store" ]]; then
|
||
GLOBAL_STORE=$(cd "$STORE_PATH" 2>/dev/null && pwd || echo "")
|
||
LOCAL_STORE=$(cd .pnpm-store 2>/dev/null && pwd || echo "")
|
||
|
||
if [[ -n "$GLOBAL_STORE" && -n "$LOCAL_STORE" && "$GLOBAL_STORE" != "$LOCAL_STORE" ]]; then
|
||
echo "🧹 清理项目本地 .pnpm-store 目录"
|
||
rm -rf .pnpm-store || true
|
||
fi
|
||
fi
|
||
|
||
# 严格检查 lockfile 是否被意外修改
|
||
if [[ "${{ inputs.strict-lockfile-check }}" == "true" && -n "$LOCKFILE_BEFORE" ]]; then
|
||
LOCKFILE_AFTER=""
|
||
if [[ -f "pnpm-lock.yaml" ]]; then
|
||
LOCKFILE_AFTER=$(md5sum pnpm-lock.yaml 2>/dev/null || md5 pnpm-lock.yaml 2>/dev/null || echo "")
|
||
fi
|
||
|
||
if [[ "$LOCKFILE_BEFORE" != "$LOCKFILE_AFTER" ]]; then
|
||
echo "" >&2
|
||
echo "❌ 检测到 pnpm-lock.yaml 在安装过程中被修改" >&2
|
||
echo "" >&2
|
||
echo "📋 变更内容:" >&2
|
||
git diff pnpm-lock.yaml || true
|
||
echo "" >&2
|
||
echo "💡 原因: package.json 与 pnpm-lock.yaml 不同步" >&2
|
||
echo "💡 解决: 在本地运行 'pnpm install' 并提交更新后的 lockfile" >&2
|
||
echo "💡 或者: 设置 strict-lockfile-check: 'false' 跳过此检查" >&2
|
||
echo "" >&2
|
||
exit 1
|
||
fi
|
||
fi
|
||
|
||
echo "✅ 依赖安装完成"
|
||
|
||
- name: 总结
|
||
shell: bash
|
||
run: |
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "📊 pnpm 安装总结"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo " pnpm 版本: $PNPM_VERSION"
|
||
echo " 缓存命中: ${{ steps.cache.outputs.cache-hit }}"
|
||
echo " 缓存 key: ${{ steps.cache-key.outputs.key }}"
|
||
echo " Store 路径: ${{ steps.cache-path.outputs.path }}"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|