Files
Lyda 748a54873b fix: 优化 pnpm store 路径检测逻辑,优先使用 Docker 默认路径并调整默认配置
- clean-project-store 默认值从 true 改为 false
- 移除 PNPM_VERSION 冗余赋值语句
- 优化 store 路径检测:优先检测 Docker 环境默认路径 /pnpm/store
- 仅在非 Docker 环境时才使用 pnpm store path 命令获取
- 移除 PNPM_STORE_DIR 环境变量设置,仅保留输出
- 优化错误提示,明确路径检测失败的可能原因
2026-04-03 17:09:31 +08:00

278 lines
9.8 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"