mirror of
https://git.bjxgj.com/xgj/xgj-actions.git
synced 2025-10-14 06:33:37 +08:00
Compare commits
25 Commits
8a16a8bec5
...
v1
Author | SHA1 | Date | |
---|---|---|---|
![]() |
525abb55fb | ||
![]() |
5aee9b954e | ||
![]() |
fb2505d379 | ||
![]() |
5d48269745 | ||
![]() |
9007eee842 | ||
![]() |
87fb296b8c | ||
![]() |
40ac71da27 | ||
![]() |
acea3c86c1 | ||
![]() |
4495633411 | ||
![]() |
ca7e4be804 | ||
![]() |
a26f91ab25 | ||
![]() |
55e66894bd | ||
![]() |
4473ed9c52 | ||
![]() |
425b35a08a | ||
![]() |
5c74c75f48 | ||
![]() |
f618608667 | ||
![]() |
2fe7c6809c | ||
![]() |
06298f9e15 | ||
![]() |
d35b42c064 | ||
![]() |
af648e65f2 | ||
![]() |
c3e927c596 | ||
![]() |
9e3ffa7ea2 | ||
![]() |
64403850ee | ||
![]() |
86d6bb988f | ||
![]() |
7366d46959 |
82
config-env/README.md
Normal file
82
config-env/README.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Configure Build Environment Action
|
||||
|
||||
该 GitHub Action 用于在已有环境中执行验证与配置操作,确保必要工具可用并完成 Git 与 kubectl 配置,适用于已预装所有依赖的构建机场景。
|
||||
|
||||
## 🚦 能力概览
|
||||
|
||||
- ✅ 配置 Git 用户信息
|
||||
- 🔍 校验 `docker` 与 `kubectl` 可用性及版本
|
||||
- ☸️ 可选写入 Base64 编码的 kubeconfig,并验证集群连通性
|
||||
- 🐳 可选登录私有 Docker 仓库
|
||||
- ⚠️ **不进行任何软件安装**,适用于受控环境
|
||||
|
||||
## 📥 输入参数
|
||||
|
||||
| 参数名 | 描述 | 必填 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| `git-user-name` | Git 用户名 | ❌ | `GiteaActions` |
|
||||
| `git-user-email` | Git 用户邮箱 | ❌ | `actions@gitea.com` |
|
||||
| `kube-config` | Base64 编码的 kubeconfig | ❌ | `''` |
|
||||
| `enable-validation` | 是否执行环境校验 (`true`/`false`) | ❌ | `true` |
|
||||
| `docker-registry` | Docker 私有仓库地址 | ❌ | `docker-registry.bjxgj.com` |
|
||||
| `docker-username` | Docker 仓库用户名 | ❌ | `ci-action` |
|
||||
| `docker-password` | Docker 仓库密码(当未跳过登录时必填) | ❌ | `''` |
|
||||
| `skip-docker-login` | 是否跳过 Docker 登录 (`true`/`false`) | ❌ | `false` |
|
||||
|
||||
## 📤 输出参数
|
||||
|
||||
| 参数名 | 描述 |
|
||||
| --- | --- |
|
||||
| `docker-version` | 检测到的 Docker 版本或状态 |
|
||||
| `kubectl-version` | 检测到的 kubectl 版本或状态 |
|
||||
| `kubectl-context` | 集群验证成功时的当前 context |
|
||||
|
||||
## 🚀 基本用法
|
||||
|
||||
```yaml
|
||||
- name: 校验并配置环境
|
||||
uses: actions/xgj/config-env@v1
|
||||
with:
|
||||
git-user-name: "CI Bot"
|
||||
git-user-email: "ci@example.com"
|
||||
kube-config: ${{ secrets.KUBE_CONFIG }}
|
||||
docker-password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
```
|
||||
|
||||
## 🎯 自定义校验
|
||||
|
||||
禁用所有校验,仅进行 Git 配置(若提供)及 kubeconfig 写入:
|
||||
|
||||
```yaml
|
||||
- name: 跳过环境校验
|
||||
uses: actions/xgj/config-env@v1
|
||||
with:
|
||||
enable-validation: "false"
|
||||
```
|
||||
|
||||
## 🚀 Docker 登录配置
|
||||
|
||||
```yaml
|
||||
- name: 验证 kubectl 集群
|
||||
uses: actions/xgj/config-env@v1
|
||||
with:
|
||||
enable-validation: "true"
|
||||
kube-config: ${{ secrets.KUBE_CONFIG }}
|
||||
docker-registry: "registry.example.com"
|
||||
docker-username: "ci-bot"
|
||||
docker-password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
```
|
||||
|
||||
## 🔒 注意事项
|
||||
|
||||
- 请确保运行环境已安装 `docker` 与 `kubectl`(若开启对应校验)。
|
||||
- kubeconfig 建议通过 GitHub Secrets 以 Base64 编码方式提供。
|
||||
- Action 执行失败会立即终止后续步骤,便于快速发现环境问题。
|
||||
|
||||
## 🧪 示例工作流
|
||||
|
||||
详见 `examples/basic-usage.yml`,展示了与 CI 构建流程结合的典型用法。
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
欢迎提 Issue 或提交 PR!
|
102
config-env/action.yml
Normal file
102
config-env/action.yml
Normal file
@@ -0,0 +1,102 @@
|
||||
name: 'Configure Build Environment'
|
||||
description: '验证已有环境并配置 Git 与 kubectl(不执行软件安装)'
|
||||
author: 'Your Organization'
|
||||
|
||||
branding:
|
||||
icon: 'settings'
|
||||
color: 'green'
|
||||
|
||||
inputs:
|
||||
git-user-name:
|
||||
description: 'Git 用户名'
|
||||
required: false
|
||||
default: 'GiteaActions'
|
||||
git-user-email:
|
||||
description: 'Git 用户邮箱'
|
||||
required: false
|
||||
default: 'actions@gitea.com'
|
||||
kube-config:
|
||||
description: 'Base64 编码的 kubectl 配置文件'
|
||||
required: false
|
||||
default: ''
|
||||
enable-validation:
|
||||
description: '是否执行环境校验 (true/false)'
|
||||
required: false
|
||||
default: 'true'
|
||||
docker-registry:
|
||||
description: 'Docker 私有仓库地址'
|
||||
required: false
|
||||
default: 'docker-registry.bjxgj.com'
|
||||
docker-username:
|
||||
description: 'Docker 仓库用户名'
|
||||
required: false
|
||||
default: 'ci-action'
|
||||
docker-password:
|
||||
description: 'Docker 仓库密码(开启登录时必填)'
|
||||
required: false
|
||||
default: ''
|
||||
skip-docker-login:
|
||||
description: '是否跳过 Docker 登录 (true/false)'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
outputs:
|
||||
docker-version:
|
||||
description: '检测到的 Docker 版本'
|
||||
value: ${{ steps.validate-tools.outputs.docker-version }}
|
||||
kubectl-version:
|
||||
description: '检测到的 kubectl 版本'
|
||||
value: ${{ steps.validate-tools.outputs.kubectl-version }}
|
||||
kubectl-context:
|
||||
description: '验证通过时的当前 kubectl 上下文'
|
||||
value: ${{ steps.verify-kubectl.outputs.current-context }}
|
||||
|
||||
runs:
|
||||
using: 'composite'
|
||||
steps:
|
||||
- name: 配置 Git
|
||||
shell: bash
|
||||
run: bash ${{ github.action_path }}/scripts/configure-git.sh
|
||||
env:
|
||||
GIT_USER_NAME: ${{ inputs.git-user-name }}
|
||||
GIT_USER_EMAIL: ${{ inputs.git-user-email }}
|
||||
|
||||
- name: 校验工具可用性
|
||||
id: validate-tools
|
||||
shell: bash
|
||||
run: bash ${{ github.action_path }}/scripts/validate-tools.sh
|
||||
env:
|
||||
ENABLE_VALIDATION: ${{ inputs.enable-validation }}
|
||||
|
||||
- name: 配置 kubectl
|
||||
if: ${{ inputs.kube-config != '' }}
|
||||
shell: bash
|
||||
run: bash ${{ github.action_path }}/scripts/configure-kubectl.sh
|
||||
env:
|
||||
KUBE_CONFIG_BASE64: ${{ inputs.kube-config }}
|
||||
|
||||
- name: 验证 kubectl 连通性
|
||||
id: verify-kubectl
|
||||
if: ${{ inputs.kube-config != '' && inputs.enable-validation != 'false' }}
|
||||
shell: bash
|
||||
run: bash ${{ github.action_path }}/scripts/verify-kubectl.sh
|
||||
|
||||
- name: 登录私有 Docker 仓库
|
||||
if: ${{ inputs.skip-docker-login != 'true' }}
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ${{ inputs.docker-registry }}
|
||||
username: ${{ inputs.docker-username }}
|
||||
password: ${{ inputs.docker-password }}
|
||||
|
||||
- name: 环境校验完成
|
||||
shell: bash
|
||||
run: |
|
||||
echo '🎉 环境校验与配置步骤完成'
|
||||
if [[ "${{ inputs.enable-validation }}" != 'false' ]]; then
|
||||
echo " - Docker: ${{ steps.validate-tools.outputs.docker-version }}"
|
||||
echo " - kubectl: ${{ steps.validate-tools.outputs.kubectl-version }}"
|
||||
fi
|
||||
if [[ "${{ inputs.kube-config }}" != '' && "${{ inputs.enable-validation }}" != 'false' ]]; then
|
||||
echo " - 当前上下文: ${{ steps.verify-kubectl.outputs.current-context }}"
|
||||
fi
|
29
config-env/examples/basic-usage.yml
Normal file
29
config-env/examples/basic-usage.yml
Normal file
@@ -0,0 +1,29 @@
|
||||
name: Config Env Example
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
validate-environment:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 校验环境并配置 Git
|
||||
id: config-env
|
||||
uses: actions/xgj/config-env@v1
|
||||
with:
|
||||
git-user-name: "CI Bot"
|
||||
git-user-email: "ci@example.com"
|
||||
require-docker: "true"
|
||||
require-kubectl: "true"
|
||||
verify-kubectl-cluster: "true"
|
||||
kube-config: ${{ secrets.KUBE_CONFIG }}
|
||||
|
||||
- name: 输出工具信息
|
||||
run: |
|
||||
echo "Docker 版本: ${{ steps.config-env.outputs.docker-version }}"
|
||||
echo "kubectl 版本: ${{ steps.config-env.outputs.kubectl-version }}"
|
||||
echo "kubectl 上下文: ${{ steps.config-env.outputs.kubectl-context }}"
|
59
config-env/scripts/configure-git.sh
Normal file
59
config-env/scripts/configure-git.sh
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
main() {
|
||||
if ! command -v git >/dev/null 2>&1; then
|
||||
log_error "未检测到 git 命令"
|
||||
exit 1
|
||||
else
|
||||
log_info "已检测到 git: $(git --version)"
|
||||
fi
|
||||
|
||||
local name="${GIT_USER_NAME:-}"
|
||||
local email="${GIT_USER_EMAIL:-}"
|
||||
|
||||
if [[ -z "$name" && -z "$email" ]]; then
|
||||
log_warning "未提供 Git 用户名和邮箱,跳过配置"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ -n "$name" ]]; then
|
||||
git config --global user.name "$name"
|
||||
log_success "已配置 Git 用户名: $name"
|
||||
else
|
||||
log_warning "未提供 Git 用户名"
|
||||
fi
|
||||
|
||||
if [[ -n "$email" ]]; then
|
||||
git config --global user.email "$email"
|
||||
log_success "已配置 Git 邮箱: $email"
|
||||
else
|
||||
log_warning "未提供 Git 邮箱"
|
||||
fi
|
||||
}
|
||||
|
||||
trap 'log_error "Git 配置失败,退出码: $?"' ERR
|
||||
|
||||
main "$@"
|
65
config-env/scripts/configure-kubectl.sh
Normal file
65
config-env/scripts/configure-kubectl.sh
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
ensure_kubectl_available() {
|
||||
if ! command -v kubectl >/dev/null 2>&1; then
|
||||
log_error "kubectl 未安装或不可用"
|
||||
exit 1
|
||||
fi
|
||||
log_info "kubectl 版本: $(kubectl version --client --short 2>/dev/null || kubectl version --client)"
|
||||
}
|
||||
|
||||
write_kube_config() {
|
||||
local encoded="${KUBE_CONFIG_BASE64:-}"
|
||||
if [[ -z "$encoded" ]]; then
|
||||
log_error "KUBE_CONFIG_BASE64 环境变量为空"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$HOME/.kube"
|
||||
local config_path="$HOME/.kube/config"
|
||||
|
||||
echo "$encoded" | base64 -d > "$config_path"
|
||||
chmod 600 "$config_path"
|
||||
log_success "已写入 kubectl 配置: $config_path"
|
||||
}
|
||||
|
||||
validate_kube_config() {
|
||||
if ! kubectl config view --minify >/dev/null 2>&1; then
|
||||
log_error "kubectl 配置文件无效或权限不足"
|
||||
exit 1
|
||||
fi
|
||||
log_success "kubectl 配置文件格式验证通过"
|
||||
}
|
||||
|
||||
trap 'log_error "kubectl 配置失败,退出码: $?"' ERR
|
||||
|
||||
main() {
|
||||
ensure_kubectl_available
|
||||
write_kube_config
|
||||
validate_kube_config
|
||||
}
|
||||
|
||||
main "$@"
|
76
config-env/scripts/validate-tools.sh
Normal file
76
config-env/scripts/validate-tools.sh
Normal file
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
validate_binary() {
|
||||
local name="$1"
|
||||
local required="$2"
|
||||
local version_cmd="$3"
|
||||
local output_var="$4"
|
||||
local version_output=""
|
||||
|
||||
if command -v "$name" >/dev/null 2>&1; then
|
||||
if [[ "$name" == "kubectl" ]]; then
|
||||
if version_output=$(kubectl version --client --short 2>/dev/null | head -n 1); then
|
||||
:
|
||||
else
|
||||
version_output=$(kubectl version --client 2>/dev/null | head -n 1)
|
||||
fi
|
||||
else
|
||||
version_output=$(bash -c "$version_cmd")
|
||||
fi
|
||||
|
||||
log_success "检测到 $name: $version_output"
|
||||
printf '%s=%s\n' "$output_var" "$version_output" >> "$GITHUB_OUTPUT"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [[ "$required" == "true" ]]; then
|
||||
log_error "未检测到必需的命令: $name"
|
||||
exit 1
|
||||
else
|
||||
log_warning "未检测到可选命令: $name"
|
||||
printf '%s=%s\n' "$output_var" "not-found" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
: "${GITHUB_OUTPUT:?GITHUB_OUTPUT 未设置}" >/dev/null
|
||||
|
||||
local enable_validation="${ENABLE_VALIDATION:-true}"
|
||||
|
||||
if [[ "$enable_validation" != "true" ]]; then
|
||||
log_info "已通过统一开关禁用环境校验"
|
||||
printf 'docker-version=%s\n' "skipped" >> "$GITHUB_OUTPUT"
|
||||
printf 'kubectl-version=%s\n' "skipped" >> "$GITHUB_OUTPUT"
|
||||
return 0
|
||||
fi
|
||||
|
||||
validate_binary "docker" "true" "docker --version" "docker-version"
|
||||
validate_binary "kubectl" "true" "kubectl version --client --short 2>/dev/null || kubectl version --client" "kubectl-version"
|
||||
}
|
||||
|
||||
trap 'log_error "工具校验失败,退出码: $?"' ERR
|
||||
|
||||
main "$@"
|
60
config-env/scripts/verify-kubectl.sh
Normal file
60
config-env/scripts/verify-kubectl.sh
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
: "${GITHUB_OUTPUT:?GITHUB_OUTPUT 未设置}" >/dev/null
|
||||
|
||||
verify_cluster() {
|
||||
local timeout=30
|
||||
|
||||
if timeout "$timeout" kubectl cluster-info >/dev/null 2>&1; then
|
||||
log_success "kubectl 集群连接验证通过"
|
||||
log_info "集群信息:"
|
||||
kubectl cluster-info
|
||||
|
||||
log_info "尝试获取节点信息"
|
||||
if kubectl get nodes >/dev/null 2>&1; then
|
||||
kubectl get nodes
|
||||
else
|
||||
log_warning "无法获取节点信息(可能权限不足)"
|
||||
fi
|
||||
|
||||
local current_context
|
||||
current_context=$(kubectl config current-context 2>/dev/null || echo "unknown")
|
||||
log_info "当前上下文: $current_context"
|
||||
printf 'current-context=%s\n' "$current_context" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
log_error "kubectl 集群连接验证失败"
|
||||
kubectl cluster-info || true
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
trap 'log_error "kubectl 验证失败,退出码: $?"' ERR
|
||||
|
||||
main() {
|
||||
verify_cluster
|
||||
}
|
||||
|
||||
main "$@"
|
@@ -11,6 +11,7 @@
|
||||
- 🔄 **Git 集成**: 可选的 git stash 功能
|
||||
- 📊 **详细输出**: 提供缓存命中状态和使用的缓存 key
|
||||
- 🧰 **自动安装 pnpm**: 当选择 `package-manager: pnpm` 时,自动通过 `pnpm/action-setup@v4` 确保 pnpm 可用(可指定版本)
|
||||
- 🗜️ **缓存 pnpm 二进制**: 在安装前缓存 Corepack 的 pnpm 二进制目录,避免重复下载 pnpm 本体(对封闭/慢网环境更友好)
|
||||
|
||||
## 📋 输入参数
|
||||
|
||||
@@ -215,6 +216,7 @@ jobs:
|
||||
1. **缓存 Key 生成**: 根据包管理器类型和 lock 文件生成唯一的缓存 key
|
||||
2. **缓存检查**: 使用`actions/cache@v4`检查是否存在匹配的缓存
|
||||
3. **自动安装 pnpm(如需)**: 当 `package-manager=pnpm` 时,使用 `pnpm/action-setup@v4` 确保 pnpm 已安装
|
||||
- 在此之前,本 Action 会使用 `actions/cache@v4` 缓存 Corepack 的 pnpm 二进制目录,命中后无需再次从外网拉取 pnpm 本体
|
||||
4. **缓存路径确定**:
|
||||
- 当 `cache-mode=node_modules` 时,缓存 `node_modules`(可用 `node-modules-path` 定义目录)。
|
||||
- 当 `cache-mode=store` 时,缓存包管理器的全局存储:`npm` → `~/.npm`,`pnpm` → 通过 `pnpm store path` 动态获取,`yarn` → `~/.cache/yarn`。
|
||||
@@ -292,6 +294,20 @@ jobs:
|
||||
4. **权限**: 某些自定义安装命令可能需要额外的权限
|
||||
5. **跨包管理器缓存隔离**: 缓存 key 与 restore-keys 均包含包管理器、版本(pnpm)与模式,切换包管理器/模式时会触发一次干净安装,避免污染
|
||||
|
||||
## 🛡️ 封闭网络/慢网优化
|
||||
|
||||
在网络较差或对外网络受限的环境中,建议:
|
||||
|
||||
- 本 Action 已默认缓存 pnpm 二进制(Corepack 缓存目录),首次成功后后续运行将跳过 pnpm 下载。
|
||||
- 缓存路径(按 OS 区分,Action 会全部尝试):
|
||||
- Linux: `~/.cache/corepack`
|
||||
- macOS: `~/Library/Caches/CorePack` 与 `~/Library/Caches/Corepack`
|
||||
- Windows: `C:\Users\runneradmin\AppData\Local\Corepack\cache`
|
||||
- 缓存 key:`${{ runner.os }}-corepack-pnpm-${{ inputs.pnpm-version }}`
|
||||
- 将 registry 指向内网镜像或近源镜像,并(如需)配置 `NODE_AUTH_TOKEN`。
|
||||
- 适当降低并发、调大超时与重试,例如设置:`pnpm config set network-concurrency 1` 与对应的 `npm config set network-timeout ...` 等。
|
||||
- 使用 `cache-mode: store` 并确保 `pnpm store path` 被缓存(Action 默认已处理)。首次成功安装后,后续基本本地链接即可。
|
||||
|
||||
## 🔍 故障排除
|
||||
|
||||
### 缓存未命中
|
||||
|
@@ -15,12 +15,21 @@ inputs:
|
||||
required: false
|
||||
default: '10'
|
||||
|
||||
skip-pnpm-setup:
|
||||
description: '当 package-manager=pnpm 时是否跳过 pnpm/action-setup (true/false)'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
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
|
||||
@@ -50,6 +59,11 @@ inputs:
|
||||
description: '附加到默认安装命令的参数(当未提供 install-command 时生效)'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
clean-project-store:
|
||||
description: '在安装前清理项目根的 .pnpm-store 残留(true/false)'
|
||||
required: false
|
||||
default: 'false'
|
||||
|
||||
cache-hash:
|
||||
description: '缓存hash值(推荐使用hashFiles计算)'
|
||||
@@ -107,8 +121,14 @@ runs:
|
||||
fi
|
||||
|
||||
# 模式后缀,隔离不同缓存模式(node_modules vs store)
|
||||
MODE_SUFFIX="${{ inputs.cache-mode }}"
|
||||
if [[ -z "$MODE_SUFFIX" ]]; then MODE_SUFFIX="node_modules"; fi
|
||||
MODE_INPUT="${{ inputs.cache-mode }}"
|
||||
ACTUAL_MODE="$MODE_INPUT"
|
||||
if [[ "$MANAGER" == "npm" ]]; then
|
||||
ACTUAL_MODE="node_modules"
|
||||
elif [[ -z "$ACTUAL_MODE" ]]; then
|
||||
ACTUAL_MODE="node_modules"
|
||||
fi
|
||||
MODE_SUFFIX="$ACTUAL_MODE"
|
||||
|
||||
# 构建缓存key:<OS>-<manager[-vX]>-<mode>-<prefix>-<hash>
|
||||
CACHE_KEY="${{ runner.os }}-${MANAGER_SUFFIX}-${MODE_SUFFIX}-${{ inputs.cache-prefix }}-${CACHE_HASH_SHORT}"
|
||||
@@ -116,11 +136,12 @@ runs:
|
||||
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 "mode=${ACTUAL_MODE}" >> $GITHUB_OUTPUT
|
||||
echo "使用hash: ${CACHE_HASH}"
|
||||
echo "缓存key: ${CACHE_KEY}"
|
||||
|
||||
- name: 确保 pnpm 可用(用于计算 store 路径)
|
||||
if: inputs.package-manager == 'pnpm' && inputs.cache-mode == 'store'
|
||||
- name: 确保 pnpm 可用
|
||||
if: inputs.package-manager == 'pnpm' && inputs.skip-pnpm-setup != 'true'
|
||||
uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: ${{ inputs.pnpm-version }}
|
||||
@@ -130,12 +151,17 @@ runs:
|
||||
id: cache-path
|
||||
shell: bash
|
||||
run: |
|
||||
MODE="${{ inputs.cache-mode }}"
|
||||
MODE="${{ steps.cache-key.outputs.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")
|
||||
@@ -143,13 +169,13 @@ runs:
|
||||
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
|
||||
# 固定 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 常见场景)
|
||||
@@ -161,6 +187,11 @@ runs:
|
||||
;;
|
||||
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: 拉取缓存依赖
|
||||
@@ -176,22 +207,36 @@ runs:
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${{ steps.cache.outputs.cache-hit }}" == "true" ]]; then
|
||||
echo "✅ 缓存命中,跳过依赖安装"
|
||||
if [[ "${{ steps.cache-key.outputs.mode }}" == "store" ]]; then
|
||||
echo "✅ 缓存命中(store),将执行快速链接安装(不会下载包,仅链接)"
|
||||
else
|
||||
echo "✅ 缓存命中,跳过依赖安装"
|
||||
fi
|
||||
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')
|
||||
if: (steps.cache-key.outputs.mode == 'node_modules' && steps.cache.outputs.cache-hit != 'true') || (steps.cache-key.outputs.mode == 'store')
|
||||
shell: bash
|
||||
run: |
|
||||
ACTUAL_MODE="${{ steps.cache-key.outputs.mode }}"
|
||||
# 若使用 pnpm,在本步骤内始终显式设置 PNPM_STORE_DIR(覆盖可能存在的相对配置)
|
||||
if [[ "${{ inputs.package-manager }}" == "pnpm" ]]; then
|
||||
if [[ "$ACTUAL_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
|
||||
# 通过多通道环境变量覆盖(兼容不同版本/解析顺序)
|
||||
export npm_config_store_dir="${PNPM_STORE_DIR}"
|
||||
export PNPM_CONFIG_STORE_DIR="${PNPM_STORE_DIR}"
|
||||
echo "🧩 已设置 PNPM_STORE_DIR=${PNPM_STORE_DIR}"
|
||||
echo "🔎 pnpm 配置: store-dir=$(pnpm config get store-dir || echo '<unknown>')"
|
||||
fi
|
||||
|
||||
# 如果提供了自定义安装命令,使用自定义命令
|
||||
if [[ -n "${{ inputs.install-command }}" ]]; then
|
||||
echo "🔧 使用自定义安装命令: ${{ inputs.install-command }}"
|
||||
@@ -201,6 +246,18 @@ runs:
|
||||
if [[ -n "$INSTALL_ARGS" ]]; then
|
||||
echo "➕ 附加安装参数: $INSTALL_ARGS"
|
||||
fi
|
||||
# 根据模式与缓存命中优化安装参数(尽量离线加速)
|
||||
EXTRA_FLAGS=""
|
||||
if [[ "${{ inputs.optimize-install-flags }}" == "true" && "${{ inputs.package-manager }}" == "pnpm" && "$ACTUAL_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")
|
||||
@@ -215,10 +272,10 @@ runs:
|
||||
"pnpm")
|
||||
if [[ "${{ inputs.force-install }}" == "true" ]]; then
|
||||
echo "🔧 使用pnpm强制安装"
|
||||
pnpm install --force ${INSTALL_ARGS}
|
||||
pnpm install --force ${EXTRA_FLAGS} ${INSTALL_ARGS}
|
||||
else
|
||||
echo "🔧 使用pnpm安装"
|
||||
pnpm install ${INSTALL_ARGS}
|
||||
pnpm install ${EXTRA_FLAGS} ${INSTALL_ARGS}
|
||||
fi
|
||||
;;
|
||||
"yarn")
|
||||
@@ -236,6 +293,16 @@ runs:
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# 可选清理:如启用并发现项目根存在残留的 .pnpm-store,且与目标目录不同,则清理
|
||||
if [[ "${{ inputs.package-manager }}" == "pnpm" && "${{ inputs.clean-project-store }}" == "true" ]]; then
|
||||
# 进一步在项目级设置覆盖一次,杜绝 .npmrc 相对路径带来的影响(仅对本 CI 工作目录生效)
|
||||
pnpm config set store-dir "${PNPM_STORE_DIR}" --location=project || true
|
||||
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
|
||||
|
||||
echo "✅ 依赖安装完成"
|
||||
|
||||
@@ -258,3 +325,22 @@ runs:
|
||||
echo " - 强制安装: ${{ inputs.force-install }}"
|
||||
echo " - Git Stash: ${{ inputs.enable-git-stash }}"
|
||||
fi
|
||||
# 诊断工作目录是否存在 .pnpm-store 以及其 Git 状态
|
||||
echo "\n🧪 目录状态自检:"
|
||||
echo " - PNPM_STORE_DIR: ${PNPM_STORE_DIR:-<unset>}"
|
||||
if [[ -d ".pnpm-store" ]]; then
|
||||
echo " - 工作目录存在 .pnpm-store 目录"
|
||||
if git rev-parse --git-dir >/dev/null 2>&1; then
|
||||
if git ls-files --error-unmatch .pnpm-store >/dev/null 2>&1; then
|
||||
echo " - Git 状态: 已被跟踪 (tracked)"
|
||||
elif git check-ignore -q .pnpm-store; then
|
||||
echo " - Git 状态: 被忽略 (ignored)"
|
||||
else
|
||||
echo " - Git 状态: 未跟踪 (untracked)"
|
||||
fi
|
||||
else
|
||||
echo " - Git 仓库: 未检测到 (非 Git 工作目录)"
|
||||
fi
|
||||
else
|
||||
echo " - 工作目录未发现 .pnpm-store 目录 ✅"
|
||||
fi
|
||||
|
88
pnpm-install/README.md
Normal file
88
pnpm-install/README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# pnpm 依赖安装与缓存 Action
|
||||
|
||||
专为 pnpm 项目设计的 GitHub Action,通过缓存 pnpm store,在 CI/CD 中实现快速复用,二次执行可在 10 秒内完成安装。
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
- **专注 pnpm**:缓存 pnpm store,命中后仅执行快速链接
|
||||
- **离线友好**:自动根据缓存命中追加 `--offline`/`--prefer-offline`
|
||||
- **锁文件准确性**:推荐传入 `cache-hash`(如 `hashFiles('pnpm-lock.yaml')`)确保缓存精准失效
|
||||
- **自定义安装**:支持附加参数或完全覆盖安装命令,保留 `force-install` 选项
|
||||
- **环境整洁**:自动设置 `PNPM_STORE_DIR`,可选清理项目根 `.pnpm-store`
|
||||
|
||||
## 📥 输入参数
|
||||
|
||||
| 参数名 | 描述 | 必需 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| `cache-prefix` | 缓存 key 前缀 | 否 | `modules` |
|
||||
| `force-install` | 是否强制安装(追加 `--force`) | 否 | `false` |
|
||||
| `install-command` | 自定义安装命令,覆盖默认的 `pnpm install` | 否 | `''` |
|
||||
| `install-args` | 附加参数(仅默认命令时生效) | 否 | `''` |
|
||||
| `cache-hash` | 缓存 hash 值(建议:`hashFiles('pnpm-lock.yaml')`) | 否 | `''` |
|
||||
| `clean-project-store` | 安装后是否清理项目根的 `.pnpm-store` | 否 | `false` |
|
||||
|
||||
## 📤 输出参数
|
||||
|
||||
| 参数名 | 描述 |
|
||||
| --- | --- |
|
||||
| `cache-hit` | 缓存是否命中 (`true` / `false`) |
|
||||
| `cache-key` | 实际使用的缓存 key |
|
||||
| `cache-path` | 缓存目录路径(调试/复用用途) |
|
||||
|
||||
## 🚀 快速上手
|
||||
|
||||
```yaml
|
||||
- name: 安装依赖
|
||||
uses: actions/xgj/pnpm-install@v1
|
||||
with:
|
||||
cache-hash: ${{ hashFiles('pnpm-lock.yaml') }}
|
||||
```
|
||||
|
||||
- 默认缓存 pnpm store,命中后执行 `pnpm install --offline --frozen-lockfile`,通常在 10 秒内完成链接。
|
||||
- 首次执行或 lock 文件变更时执行 `pnpm install --prefer-offline --frozen-lockfile`,完成后写入缓存。
|
||||
- 当未显式提供 `cache-hash` 时,会依次使用 `pnpm-lock.yaml`、`package.json` 计算 hash 作为兜底,确保缓存具备基本失效条件。
|
||||
|
||||
## 🔁 缓存路径
|
||||
|
||||
Action 会通过执行 `pnpm store path --silent` 获取当前 pnpm store 目录,并将结果写入日志与 `PNPM_STORE_DIR` 环境变量。
|
||||
|
||||
若命令未返回有效路径,可在调用前手动设置 `PNPM_STORE_DIR=/path/to/store` 以确保后续步骤正常运行。
|
||||
|
||||
## ⚙️ 进阶配置
|
||||
|
||||
- **强制安装**
|
||||
|
||||
```yaml
|
||||
with:
|
||||
force-install: "true"
|
||||
```
|
||||
|
||||
当缓存未命中且怀疑存在依赖冲突时,可追加 `--force`。
|
||||
|
||||
- **自定义命令**
|
||||
|
||||
```yaml
|
||||
with:
|
||||
install-command: "pnpm install --prod"
|
||||
```
|
||||
|
||||
完全覆盖默认安装逻辑,适合部署类任务。
|
||||
|
||||
- **清理项目根 `.pnpm-store`**
|
||||
|
||||
```yaml
|
||||
with:
|
||||
clean-project-store: "true"
|
||||
```
|
||||
|
||||
在部分仓库中 `.npmrc` 会指定相对 store 路径,该选项可避免 CI 工作目录残留。
|
||||
|
||||
## 🧰 示例工作流
|
||||
|
||||
参见 `examples/basic-usage.yml`,展示在 CI 中的集成方式。
|
||||
|
||||
## 🛡️ 注意事项
|
||||
|
||||
- 该 Action 假设 Runner 环境已安装 pnpm。若未预装,请在前一步引入 `pnpm/action-setup@v4`。
|
||||
- 强烈建议传入 `cache-hash`,并确保 `pnpm-lock.yaml` 已提交。
|
||||
- GitHub Actions 缓存配额有限,定期清理或调整 `cache-prefix` 以避免冲突。
|
187
pnpm-install/action.yml
Normal file
187
pnpm-install/action.yml
Normal file
@@ -0,0 +1,187 @@
|
||||
name: 'pnpm依赖安装与缓存'
|
||||
description: '专注于pnpm的依赖缓存与安装,加速重复执行'
|
||||
branding:
|
||||
icon: 'package'
|
||||
color: 'yellow'
|
||||
|
||||
inputs:
|
||||
cache-prefix:
|
||||
description: '缓存前缀名称'
|
||||
required: false
|
||||
default: 'modules'
|
||||
|
||||
force-install:
|
||||
description: '是否强制安装 (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: ''
|
||||
|
||||
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: |
|
||||
if ! command -v pnpm >/dev/null 2>&1; then
|
||||
echo "pnpm 未安装"
|
||||
exit 1
|
||||
fi
|
||||
VERSION=$(pnpm --version | tr -d '\n')
|
||||
if [[ -z "$VERSION" ]]; then
|
||||
echo "无法获取pnpm版本"
|
||||
exit 1
|
||||
fi
|
||||
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 }}
|
||||
FALLBACK_HASH: ${{ hashFiles('pnpm-lock.yaml') }}
|
||||
PACKAGE_HASH: ${{ hashFiles('package.json') }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ -n "${{ inputs.cache-hash }}" ]]; then
|
||||
CACHE_HASH="${{ inputs.cache-hash }}"
|
||||
elif [[ -n "${FALLBACK_HASH}" ]]; then
|
||||
CACHE_HASH="${FALLBACK_HASH}"
|
||||
elif [[ -n "${PACKAGE_HASH}" ]]; then
|
||||
CACHE_HASH="${PACKAGE_HASH}"
|
||||
else
|
||||
CACHE_HASH=""
|
||||
fi
|
||||
if [[ -n "$CACHE_HASH" ]]; then
|
||||
CACHE_HASH_SHORT=$(echo "$CACHE_HASH" | head -c 12)
|
||||
else
|
||||
CACHE_HASH_SHORT="no-hash"
|
||||
fi
|
||||
PNPM_VERSION="${PNPM_VERSION}"
|
||||
CACHE_KEY="${{ runner.os }}-pnpm-v${PNPM_VERSION}-store-${{ inputs.cache-prefix }}-${CACHE_HASH_SHORT}"
|
||||
RESTORE_PREFIX="${{ runner.os }}-pnpm-v${PNPM_VERSION}-store-${{ inputs.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
|
||||
|
||||
STORE_DIR_CANDIDATE=$(pnpm store path --silent 2>/dev/null || true)
|
||||
STORE_DIR_CANDIDATE=$(echo "$STORE_DIR_CANDIDATE" | tail -n1 | tr -d '\r')
|
||||
|
||||
if [[ -z "$STORE_DIR_CANDIDATE" ]]; then
|
||||
echo "❌ pnpm store path 未返回有效路径。可在运行前设置 PNPM_STORE_DIR=/path/to/store 或检查 pnpm 配置" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "pnpm store path: $STORE_DIR_CANDIDATE"
|
||||
echo "PNPM_STORE_DIR=${STORE_DIR_CANDIDATE}" >> "$GITHUB_ENV"
|
||||
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_DIR="${PNPM_STORE_DIR:-${{ steps.cache-path.outputs.path }}}"
|
||||
export PNPM_STORE_DIR="$STORE_DIR"
|
||||
export npm_config_store_dir="$PNPM_STORE_DIR"
|
||||
export PNPM_CONFIG_STORE_DIR="$PNPM_STORE_DIR"
|
||||
echo "📦 Using PNPM_STORE_DIR=$PNPM_STORE_DIR"
|
||||
if [[ -n "${{ inputs.install-command }}" ]]; then
|
||||
echo "🔧 使用自定义安装命令: ${{ inputs.install-command }}"
|
||||
eval "${{ inputs.install-command }}"
|
||||
exit 0
|
||||
fi
|
||||
if [[ "${{ steps.cache.outputs.cache-hit }}" == "true" ]]; then
|
||||
FLAGS=("--offline" "--frozen-lockfile")
|
||||
else
|
||||
FLAGS=("--prefer-offline" "--frozen-lockfile")
|
||||
fi
|
||||
if [[ "${{ inputs.force-install }}" == "true" ]]; then
|
||||
FLAGS+=("--force")
|
||||
fi
|
||||
INSTALL_ARGS="${{ inputs.install-args }}"
|
||||
echo "🔧 执行 pnpm install ${FLAGS[*]} ${INSTALL_ARGS}"
|
||||
if [[ -n "$INSTALL_ARGS" ]]; then
|
||||
pnpm install "${FLAGS[@]}" $INSTALL_ARGS
|
||||
else
|
||||
pnpm install "${FLAGS[@]}"
|
||||
fi
|
||||
if [[ "${{ inputs.clean-project-store }}" == "true" && -d ".pnpm-store" && "$(cd "$PNPM_STORE_DIR" 2>/dev/null && pwd)" != "$(cd .pnpm-store 2>/dev/null && pwd)" ]]; then
|
||||
rm -rf .pnpm-store || true
|
||||
fi
|
||||
echo "🧾 git status --short"
|
||||
CHANGES=$(git status --short || true)
|
||||
if [[ -n "$CHANGES" ]]; then
|
||||
echo "$CHANGES"
|
||||
echo "❌ 安装依赖后检测到工作区存在未提交变更。请检查上述文件,必要时更新配置或在调用前设置 PNPM_STORE_DIR。" >&2
|
||||
exit 1
|
||||
else
|
||||
echo "✅ 工作区保持干净"
|
||||
fi
|
||||
|
||||
- name: 总结
|
||||
shell: bash
|
||||
run: |
|
||||
echo "pnpm版本: $PNPM_VERSION"
|
||||
echo "缓存命中: ${{ steps.cache.outputs.cache-hit }}"
|
||||
echo "缓存key: ${{ steps.cache-key.outputs.key }}"
|
35
pnpm-install/examples/basic-usage.yml
Normal file
35
pnpm-install/examples/basic-usage.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
# 基础pnpm项目示例
|
||||
name: pnpm项目CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main, develop ]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: 检出代码
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: 设置 Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
|
||||
- name: 安装依赖
|
||||
id: deps
|
||||
uses: actions/xgj/pnpm-install@v1
|
||||
with:
|
||||
cache-hash: ${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: 运行测试
|
||||
run: pnpm test
|
||||
|
||||
- name: 打印缓存状态
|
||||
run: |
|
||||
echo "cache-hit: ${{ steps.deps.outputs['cache-hit'] }}"
|
||||
echo "cache-key: ${{ steps.deps.outputs['cache-key'] }}"
|
Reference in New Issue
Block a user