Compare commits

...

11 Commits

7 changed files with 344 additions and 57 deletions

View File

@@ -17,9 +17,7 @@
| `git-user-name` | Git 用户名 | ❌ | `GiteaActions` |
| `git-user-email` | Git 用户邮箱 | ❌ | `actions@gitea.com` |
| `kube-config` | Base64 编码的 kubeconfig | ❌ | `''` |
| `require-docker` | 是否校验 docker 可用性 (`true`/`false`) | ❌ | `true` |
| `require-kubectl` | 是否校验 kubectl 可用性 (`true`/`false`) | ❌ | `true` |
| `verify-kubectl-cluster` | 是否验证 kubectl 集群连通性 (`true`/`false`) | ❌ | `true` |
| `enable-validation` | 是否执行环境校验 (`true`/`false`) | ❌ | `true` |
| `docker-registry` | Docker 私有仓库地址 | ❌ | `docker-registry.bjxgj.com` |
| `docker-username` | Docker 仓库用户名 | ❌ | `ci-action` |
| `docker-password` | Docker 仓库密码(当未跳过登录时必填) | ❌ | `''` |
@@ -47,24 +45,13 @@
## 🎯 自定义校验
仅检查 Git 配置与 docker
禁用所有校验,仅进行 Git 配置(若提供)及 kubeconfig 写入
```yaml
- name: 仅验证 docker
- name: 跳过环境校验
uses: actions/xgj/config-env@v1
with:
require-kubectl: "false"
verify-kubectl-cluster: "false"
```
禁用 docker 校验,仅写入 kubeconfig 并验证:
```yaml
- name: 验证 kubectl 集群
uses: actions/xgj/config-env@v1
with:
require-docker: "false"
kube-config: ${{ secrets.KUBE_CONFIG }}
enable-validation: "false"
```
## 🚀 Docker 登录配置
@@ -73,9 +60,7 @@
- name: 验证 kubectl 集群
uses: actions/xgj/config-env@v1
with:
require-docker: "true"
require-kubectl: "true"
verify-kubectl-cluster: "true"
enable-validation: "true"
kube-config: ${{ secrets.KUBE_CONFIG }}
docker-registry: "registry.example.com"
docker-username: "ci-bot"

View File

@@ -19,16 +19,8 @@ inputs:
description: 'Base64 编码的 kubectl 配置文件'
required: false
default: ''
require-docker:
description: '是否校验 docker 可用性 (true/false)'
required: false
default: 'true'
require-kubectl:
description: '是否校验 kubectl 可用性 (true/false)'
required: false
default: 'true'
verify-kubectl-cluster:
description: '是否验证 kubectl 集群连通性 (true/false)'
enable-validation:
description: '是否执行环境校验 (true/false)'
required: false
default: 'true'
docker-registry:
@@ -74,11 +66,10 @@ runs:
shell: bash
run: bash ${{ github.action_path }}/scripts/validate-tools.sh
env:
CHECK_DOCKER: ${{ inputs.require-docker }}
CHECK_KUBECTL: ${{ inputs.require-kubectl }}
ENABLE_VALIDATION: ${{ inputs.enable-validation }}
- name: 配置 kubectl
if: ${{ inputs.kube-config != '' && inputs.require-kubectl != 'false' }}
if: ${{ inputs.kube-config != '' }}
shell: bash
run: bash ${{ github.action_path }}/scripts/configure-kubectl.sh
env:
@@ -86,7 +77,7 @@ runs:
- name: 验证 kubectl 连通性
id: verify-kubectl
if: ${{ inputs.kube-config != '' && inputs.verify-kubectl-cluster != 'false' && inputs.require-kubectl != 'false' }}
if: ${{ inputs.kube-config != '' && inputs.enable-validation != 'false' }}
shell: bash
run: bash ${{ github.action_path }}/scripts/verify-kubectl.sh
@@ -102,12 +93,10 @@ runs:
shell: bash
run: |
echo '🎉 环境校验与配置步骤完成'
if [[ "${{ inputs.require-docker }}" != 'false' ]]; then
if [[ "${{ inputs.enable-validation }}" != 'false' ]]; then
echo " - Docker: ${{ steps.validate-tools.outputs.docker-version }}"
fi
if [[ "${{ inputs.require-kubectl }}" != 'false' ]]; then
echo " - kubectl: ${{ steps.validate-tools.outputs.kubectl-version }}"
fi
if [[ "${{ inputs.kube-config }}" != '' && "${{ inputs.verify-kubectl-cluster }}" != 'false' && "${{ inputs.require-kubectl }}" != 'false' ]]; then
if [[ "${{ inputs.kube-config }}" != '' && "${{ inputs.enable-validation }}" != 'false' ]]; then
echo " - 当前上下文: ${{ steps.verify-kubectl.outputs.current-context }}"
fi

View File

@@ -58,22 +58,17 @@ validate_binary() {
main() {
: "${GITHUB_OUTPUT:?GITHUB_OUTPUT 未设置}" >/dev/null
local check_docker="${CHECK_DOCKER:-true}"
local check_kubectl="${CHECK_KUBECTL:-true}"
local enable_validation="${ENABLE_VALIDATION:-true}"
if [[ "$check_docker" != "true" ]]; then
log_info "跳过 Docker 校验"
if [[ "$enable_validation" != "true" ]]; then
log_info "已通过统一开关禁用环境校验"
printf 'docker-version=%s\n' "skipped" >> "$GITHUB_OUTPUT"
else
validate_binary "docker" "true" "docker --version" "docker-version"
printf 'kubectl-version=%s\n' "skipped" >> "$GITHUB_OUTPUT"
return 0
fi
if [[ "$check_kubectl" != "true" ]]; then
log_info "跳过 kubectl 校验"
printf 'kubectl-version=%s\n' "skipped" >> "$GITHUB_OUTPUT"
else
validate_binary "kubectl" "true" "kubectl version --client --short 2>/dev/null || kubectl version --client" "kubectl-version"
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

View File

@@ -121,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}"
@@ -130,6 +136,7 @@ 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}"
@@ -144,7 +151,7 @@ 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
@@ -200,7 +207,7 @@ runs:
shell: bash
run: |
if [[ "${{ steps.cache.outputs.cache-hit }}" == "true" ]]; then
if [[ "${{ inputs.cache-mode }}" == "store" ]]; then
if [[ "${{ steps.cache-key.outputs.mode }}" == "store" ]]; then
echo "✅ 缓存命中store将执行快速链接安装不会下载包仅链接"
else
echo "✅ 缓存命中,跳过依赖安装"
@@ -210,12 +217,13 @@ runs:
fi
- 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 [[ "${{ inputs.cache-mode }}" == "store" ]]; then
if [[ "$ACTUAL_MODE" == "store" ]]; then
# store 模式cache-path 的 path 即为期望的 store 目录
export PNPM_STORE_DIR="${{ steps.cache-path.outputs.path }}"
else
@@ -240,7 +248,7 @@ runs:
fi
# 根据模式与缓存命中优化安装参数(尽量离线加速)
EXTRA_FLAGS=""
if [[ "${{ inputs.optimize-install-flags }}" == "true" && "${{ inputs.package-manager }}" == "pnpm" && "${{ inputs.cache-mode }}" == "store" ]]; then
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"

88
pnpm-install/README.md Normal file
View 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
View 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 }}"

View 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'] }}"