feat: 新增 setup-opencode action,支持 npm 全局安装和智能缓存

- 使用 npm 全局安装 OpenCode,支持版本管理和自动更新
- 智能缓存策略:缓存 npm 目录和全局安装,加速后续构建
- 支持淘宝镜像源和自定义 npm 镜像,国内环境友好
- 版本检测:自动比对已安装版本,避免重复安装
- 输出安装版本、缓存命中状态和更新状态
- 提供详细的使用文档和多场景示例
This commit is contained in:
Lyda
2026-03-17 16:17:03 +08:00
parent 1618c6fe6d
commit 489cc94316
6 changed files with 724 additions and 0 deletions
+318
View File
@@ -0,0 +1,318 @@
# Setup OpenCode Action
使用 npm 全局安装 OpenCode 并缓存,支持版本检测和自动更新。
## 功能特性
-**npm 安装**:使用 npm 全局安装,简单快速
- 🚀 **智能缓存**:缓存 npm 包和全局安装,加速后续构建
- 🔄 **自动更新**:版本变化时自动更新
- 🇨🇳 **国内镜像**:支持淘宝镜像源,国内环境友好
-**快速恢复**:缓存命中时跳过安装,秒级完成
## 使用方法
### 基础用法
```yaml
- name: 安装 OpenCode
uses: ./.gitea/actions/setup-opencode
```
### 国内环境使用(推荐)
```yaml
- name: 安装 OpenCode(淘宝镜像)
uses: ./.gitea/actions/setup-opencode
with:
version: 'latest'
use-taobao-registry: 'true'
```
### 指定版本
```yaml
- name: 安装 OpenCode 1.2.3
uses: ./.gitea/actions/setup-opencode
with:
version: '1.2.3'
```
### 使用最新版本
```yaml
- name: 安装最新版 OpenCode
uses: ./.gitea/actions/setup-opencode
with:
version: 'latest'
```
### 自定义 npm 包名
```yaml
- name: 安装自定义包
uses: ./.gitea/actions/setup-opencode
with:
package-name: '@myorg/opencode'
version: '1.2.3'
```
### 自定义 npm 镜像源
```yaml
- name: 使用自定义镜像源
uses: ./.gitea/actions/setup-opencode
with:
version: 'latest'
npm-registry: 'https://registry.npmmirror.com'
```
### 强制重新安装
```yaml
- name: 强制重新安装 OpenCode
uses: ./.gitea/actions/setup-opencode
with:
version: '1.2.3'
skip-cache: 'true'
```
### 使用输出
```yaml
- name: 安装 OpenCode
id: setup-opencode
uses: ./.gitea/actions/setup-opencode
with:
version: 'latest'
- name: 显示版本信息
run: |
echo "安装版本: ${{ steps.setup-opencode.outputs.version }}"
echo "缓存命中: ${{ steps.setup-opencode.outputs.cache-hit }}"
echo "执行更新: ${{ steps.setup-opencode.outputs.updated }}"
- name: 使用 OpenCode
run: |
opencode --version
opencode build
```
## 输入参数
| 参数 | 描述 | 必填 | 默认值 |
| ---- | ---- | ---- | ------ |
| `version` | OpenCode 版本号(例如: 1.0.0, latest | 否 | `latest` |
| `package-name` | npm 包名 | 否 | `opencode` |
| `use-taobao-registry` | 使用淘宝 npm 镜像源 (true/false) | 否 | `false` |
| `npm-registry` | 自定义 npm 镜像源地址 | 否 | `` |
| `cache-prefix` | 缓存前缀名称 | 否 | `opencode-npm` |
| `skip-cache` | 跳过缓存,强制重新安装 (true/false) | 否 | `false` |
## 输出
| 输出 | 描述 |
| ---- | ---- |
| `version` | 安装的 OpenCode 版本 |
| `cache-hit` | 缓存是否命中 (true/false) |
| `updated` | 是否执行了更新 (true/false) |
## 工作原理
1. **参数验证**:验证输入参数的有效性
2. **配置镜像源**:根据配置设置 npm 镜像源(支持淘宝镜像)
3. **缓存检查**:根据包名和版本生成缓存键,尝试恢复缓存
4. **版本比对**
- 检查已安装版本是否与请求版本一致
- 版本一致:跳过安装
- 版本不一致或未安装:执行 npm 安装
5. **npm 安装**:使用 `npm install -g` 全局安装指定版本
6. **保存缓存**:缓存 npm 包和全局安装目录
## 缓存策略
- **缓存内容**
- `~/.npm` - npm 缓存目录
- `/usr/local/lib/node_modules/{package}` - 全局安装的包
- `/usr/local/bin/{package}` - 全局命令
- **缓存键格式**`{OS}-{cache-prefix}-{package-name}-{version}`
- **缓存更新**:版本号变化时自动更新缓存
- **缓存生命周期**:遵循 GitHub Actions 缓存默认生命周期(最多 7 天未使用)
## 完整示例
### 示例 1:基础 CI/CD 流程
```yaml
name: Build with OpenCode
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 安装 OpenCode
uses: ./.gitea/actions/setup-opencode
with:
version: '1.2.3'
- name: 构建项目
run: |
opencode build --release
- name: 运行测试
run: |
opencode test
```
### 示例 2:国内环境使用
```yaml
name: Build in China
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 OpenCode(淘宝镜像)
uses: ./.gitea/actions/setup-opencode
with:
version: 'latest'
use-taobao-registry: 'true'
- name: 构建
run: opencode build
```
### 示例 3:多版本测试
```yaml
name: Test Multiple Versions
on: [push]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
opencode-version: ['1.0.0', '1.1.0', '1.2.0', 'latest']
steps:
- uses: actions/checkout@v4
- name: 安装 OpenCode ${{ matrix.opencode-version }}
uses: ./.gitea/actions/setup-opencode
with:
version: ${{ matrix.opencode-version }}
- name: 运行测试
run: |
echo "Testing with OpenCode ${{ matrix.opencode-version }}"
opencode --version
opencode test
```
### 示例 4:带缓存状态检查
```yaml
name: Build with Cache Info
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 安装 OpenCode
id: opencode
uses: ./.gitea/actions/setup-opencode
with:
version: 'latest'
use-taobao-registry: 'true'
- name: 显示缓存状态
run: |
if [[ "${{ steps.opencode.outputs.cache-hit }}" == "true" ]]; then
echo "✅ 使用缓存,节省时间!"
else
echo "📥 首次安装或版本更新"
fi
if [[ "${{ steps.opencode.outputs.updated }}" == "true" ]]; then
echo "🔄 OpenCode 已更新到 ${{ steps.opencode.outputs.version }}"
fi
- name: 构建
run: opencode build
```
## 注意事项
1. **国内环境**:🇨🇳 在国内使用时,强烈建议设置 `use-taobao-registry: 'true'` 使用淘宝镜像源
2. **版本格式**:版本号应遵循语义化版本规范(如 1.2.3)
3. **npm 环境**:需要 Node.js 和 npm 已安装(GitHub Actions 默认已安装)
4. **全局安装**:使用 `npm install -g` 全局安装,命令自动添加到 PATH
5. **缓存限制**GitHub Actions 缓存有大小限制(10GB
## 故障排查
### 国内网络问题
```yaml
- name: 使用淘宝镜像源
uses: ./.gitea/actions/setup-opencode
with:
version: '1.2.3'
use-taobao-registry: 'true'
```
### 自定义镜像源
```yaml
- name: 使用自定义镜像
uses: ./.gitea/actions/setup-opencode
with:
version: '1.2.3'
npm-registry: 'https://registry.npmmirror.com'
```
### 安装失败
```yaml
- name: 清除缓存重新安装
uses: ./.gitea/actions/setup-opencode
with:
version: '1.2.3'
skip-cache: 'true'
```
### 版本不匹配
检查 `updated` 输出,如果为 `true` 表示已更新到新版本。
## 优势
相比二进制安装方式,npm 安装具有以下优势:
- ✅ **简单直接**:一条 `npm install -g` 命令搞定
-**依赖管理**npm 自动处理依赖关系
-**版本管理**npm 原生支持版本管理
-**跨平台**npm 包通常支持多平台
-**更新方便**:版本更新只需修改版本号
-**缓存高效**:缓存 npm 目录和全局安装,恢复快速
## 许可证
与主仓库保持一致。
+295
View File
@@ -0,0 +1,295 @@
name: 'Setup OpenCode'
description: '使用 npm 全局安装 OpenCode 并缓存,支持版本检测和自动更新'
author: 'Your Organization'
branding:
icon: 'code'
color: 'purple'
inputs:
version:
description: 'OpenCode 版本号(例如: 1.0.0, latest'
required: false
default: 'latest'
package-name:
description: 'npm 包名'
required: false
default: 'opencode'
use-taobao-registry:
description: '使用淘宝 npm 镜像源 (true/false)'
required: false
default: 'true'
npm-registry:
description: '自定义 npm 镜像源地址'
required: false
default: ''
cache-prefix:
description: '缓存前缀名称'
required: false
default: 'opencode-npm'
skip-cache:
description: '跳过缓存,强制重新安装 (true/false)'
required: false
default: 'false'
outputs:
version:
description: '安装的 OpenCode 版本'
value: ${{ steps.get-version.outputs.version }}
cache-hit:
description: '缓存是否命中 (true/false)'
value: ${{ steps.cache-restore.outputs.cache-hit }}
updated:
description: '是否执行了更新 (true/false)'
value: ${{ steps.check-update.outputs.updated }}
runs:
using: 'composite'
steps:
- name: 验证输入参数
shell: bash
run: |
echo "🔍 验证输入参数..."
if [[ -z "${{ inputs.version }}" ]]; then
echo "❌ version 参数不能为空"
exit 1
fi
if [[ -z "${{ inputs.package-name }}" ]]; then
echo "❌ package-name 参数不能为空"
exit 1
fi
echo "✅ 输入参数验证通过"
echo " - npm 包名: ${{ inputs.package-name }}"
echo " - 版本: ${{ inputs.version }}"
echo " - 使用淘宝镜像: ${{ inputs.use-taobao-registry }}"
echo " - 跳过缓存: ${{ inputs.skip-cache }}"
- name: 配置 npm 镜像源
id: setup-registry
shell: bash
run: |
if [[ -n "${{ inputs.npm-registry }}" ]]; then
echo "🔧 使用自定义 npm 镜像源"
NPM_REGISTRY="${{ inputs.npm-registry }}"
echo " - 镜像源: ${NPM_REGISTRY}"
elif [[ "${{ inputs.use-taobao-registry }}" == "true" ]]; then
echo "🇨🇳 使用淘宝 npm 镜像源"
NPM_REGISTRY="https://registry.npmmirror.com"
echo " - 镜像源: ${NPM_REGISTRY}"
else
echo "🌍 使用默认 npm 镜像源"
NPM_REGISTRY=""
fi
echo "registry=${NPM_REGISTRY}" >> $GITHUB_OUTPUT
- name: 获取 npm 路径
id: npm-paths
shell: bash
run: |
NPM_PREFIX=$(npm config get prefix)
NPM_GLOBAL_ROOT=$(npm root -g)
echo "prefix=${NPM_PREFIX}" >> $GITHUB_OUTPUT
echo "global-root=${NPM_GLOBAL_ROOT}" >> $GITHUB_OUTPUT
echo "📁 npm 全局路径: ${NPM_PREFIX}"
echo "📦 npm 全局包路径: ${NPM_GLOBAL_ROOT}"
- name: 解析版本号
id: resolve-version
shell: bash
env:
NPM_CONFIG_REGISTRY: ${{ steps.setup-registry.outputs.registry }}
run: |
VERSION="${{ inputs.version }}"
PACKAGE="${{ inputs.package-name }}"
if [[ "${VERSION}" == "latest" ]]; then
echo "🔍 获取最新版本号..."
LATEST_VERSION=$(npm view ${PACKAGE} version 2>/dev/null || echo "")
if [[ -n "${LATEST_VERSION}" ]]; then
VERSION="${LATEST_VERSION}"
echo "✅ 最新版本: ${VERSION}"
else
echo "⚠️ 无法获取最新版本,使用 latest 标签"
VERSION="latest"
fi
fi
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "📌 目标版本: ${VERSION}"
- name: 生成缓存键
id: cache-key
shell: bash
run: |
VERSION="${{ steps.resolve-version.outputs.version }}"
PACKAGE="${{ inputs.package-name }}"
CACHE_KEY="${{ runner.os }}-${{ inputs.cache-prefix }}-${PACKAGE}-${VERSION}"
echo "key=${CACHE_KEY}" >> $GITHUB_OUTPUT
echo "🔑 缓存键: ${CACHE_KEY}"
- name: 恢复缓存
id: cache-restore
if: inputs.skip-cache != 'true'
uses: actions/cache/restore@v4
with:
path: |
~/.npm
${{ steps.npm-paths.outputs.global-root }}/${{ inputs.package-name }}
${{ steps.npm-paths.outputs.prefix }}/bin/${{ inputs.package-name }}
key: ${{ steps.cache-key.outputs.key }}
restore-keys: |
${{ runner.os }}-${{ inputs.cache-prefix }}-${{ inputs.package-name }}-
- name: 检查已安装版本
id: check-installed
shell: bash
run: |
PACKAGE="${{ inputs.package-name }}"
TARGET_VERSION="${{ steps.resolve-version.outputs.version }}"
NEED_INSTALL="true"
if command -v $PACKAGE &> /dev/null; then
# 使用 JSON 格式获取版本信息,更可靠
INSTALLED_INFO=$(npm list -g $PACKAGE --json --depth=0 2>/dev/null || echo '{}')
INSTALLED_VERSION=$(echo "${INSTALLED_INFO}" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4 || echo "unknown")
echo "📦 已安装版本: ${INSTALLED_VERSION}"
echo "🎯 目标版本: ${TARGET_VERSION}"
if [[ "${INSTALLED_VERSION}" == "${TARGET_VERSION}" ]]; then
NEED_INSTALL="false"
echo "✅ 版本匹配,无需重新安装"
elif [[ "${INSTALLED_VERSION}" != "unknown" ]]; then
echo "⚠️ 版本不匹配,需要更新"
fi
else
echo "⚠️ 未安装 ${PACKAGE}"
fi
echo "need-install=${NEED_INSTALL}" >> $GITHUB_OUTPUT
- name: 安装 OpenCode
if: steps.check-installed.outputs.need-install == 'true'
shell: bash
env:
NPM_CONFIG_REGISTRY: ${{ steps.setup-registry.outputs.registry }}
run: |
PACKAGE="${{ inputs.package-name }}"
VERSION="${{ steps.resolve-version.outputs.version }}"
INSTALL_SPEC="${PACKAGE}@${VERSION}"
echo "📥 安装 ${INSTALL_SPEC}..."
# 重试机制:最多尝试 3 次
MAX_RETRIES=3
RETRY_COUNT=0
while [[ ${RETRY_COUNT} -lt ${MAX_RETRIES} ]]; do
if npm install -g "${INSTALL_SPEC}" --no-audit --no-fund; then
echo "✅ 安装成功"
break
else
RETRY_COUNT=$((RETRY_COUNT + 1))
if [[ ${RETRY_COUNT} -lt ${MAX_RETRIES} ]]; then
echo "⚠️ 安装失败,等待 3 秒后重试 (${RETRY_COUNT}/${MAX_RETRIES})..."
sleep 3
else
echo "❌ 安装失败,已重试 ${MAX_RETRIES} 次"
exit 1
fi
fi
done
- name: 验证安装
id: verify-install
if: steps.check-installed.outputs.need-install == 'true'
shell: bash
run: |
PACKAGE="${{ inputs.package-name }}"
if command -v $PACKAGE &> /dev/null; then
echo "install-verified=true" >> $GITHUB_OUTPUT
echo "✅ 安装验证成功"
else
echo "install-verified=false" >> $GITHUB_OUTPUT
echo "❌ 安装验证失败"
exit 1
fi
- name: 保存缓存
if: steps.check-installed.outputs.need-install == 'true' && steps.verify-install.outputs.install-verified == 'true' && inputs.skip-cache != 'true'
uses: actions/cache/save@v4
with:
path: |
~/.npm
${{ steps.npm-paths.outputs.global-root }}/${{ inputs.package-name }}
${{ steps.npm-paths.outputs.prefix }}/bin/${{ inputs.package-name }}
key: ${{ steps.cache-key.outputs.key }}
- name: 获取安装版本
id: get-version
shell: bash
run: |
PACKAGE="${{ inputs.package-name }}"
if command -v $PACKAGE &> /dev/null; then
# 使用 JSON 格式获取版本,更可靠
INSTALLED_INFO=$(npm list -g $PACKAGE --json --depth=0 2>/dev/null || echo '{}')
VERSION=$(echo "${INSTALLED_INFO}" | grep -o '"version":"[^"]*"' | head -1 | cut -d'"' -f4 || echo "unknown")
if [[ "${VERSION}" == "unknown" ]]; then
# 备用方案:直接运行命令获取版本
VERSION=$($PACKAGE --version 2>/dev/null | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1 || echo "unknown")
fi
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "✅ OpenCode 版本: ${VERSION}"
else
echo "version=unknown" >> $GITHUB_OUTPUT
echo "⚠️ 无法获取 OpenCode 版本"
fi
- name: 检查更新状态
id: check-update
shell: bash
run: |
if [[ "${{ steps.check-installed.outputs.need-install }}" == "true" ]]; then
echo "updated=true" >> $GITHUB_OUTPUT
echo "🔄 已安装新版本"
else
echo "updated=false" >> $GITHUB_OUTPUT
echo "📌 使用已安装版本,未执行更新"
fi
- name: 安装总结
shell: bash
run: |
echo "📊 OpenCode 安装总结:"
echo " - npm 包名: ${{ inputs.package-name }}"
echo " - 安装版本: ${{ steps.get-version.outputs.version }}"
echo " - 缓存命中: ${{ steps.cache-restore.outputs.cache-hit }}"
echo " - 执行更新: ${{ steps.check-update.outputs.updated }}"
echo " - 缓存键名: ${{ steps.cache-key.outputs.key }}"
if [[ "${{ steps.check-update.outputs.updated }}" == "true" ]]; then
echo " 🔄 已通过 npm 安装新版本"
else
echo " ✅ 使用已安装版本,跳过安装"
fi
echo ""
echo "🎉 OpenCode 已准备就绪!"
+36
View File
@@ -0,0 +1,36 @@
name: Basic OpenCode Usage
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 安装 OpenCode
id: setup-opencode
uses: ./.gitea/actions/setup-opencode
with:
version: 'latest'
- name: 显示安装信息
run: |
echo "OpenCode 版本: ${{ steps.setup-opencode.outputs.version }}"
echo "缓存命中: ${{ steps.setup-opencode.outputs.cache-hit }}"
echo "执行更新: ${{ steps.setup-opencode.outputs.updated }}"
- name: 验证安装
run: |
opencode --version
which opencode
- name: 使用 OpenCode
run: |
opencode build
+33
View File
@@ -0,0 +1,33 @@
name: China Mirror Example
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 安装 OpenCode(淘宝镜像)
id: setup
uses: ./.gitea/actions/setup-opencode
with:
version: 'latest'
use-taobao-registry: 'true'
- name: 显示安装信息
run: |
echo "安装版本: ${{ steps.setup.outputs.version }}"
echo "缓存命中: ${{ steps.setup.outputs.cache-hit }}"
if [[ "${{ steps.setup.outputs.cache-hit }}" == "true" ]]; then
echo "✅ 使用缓存,快速完成安装"
else
echo "📥 通过 npm 安装完成"
fi
- name: 构建项目
run: |
opencode build --release
@@ -0,0 +1,21 @@
name: Custom Registry Example
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 使用自定义 npm 镜像源
uses: ./.gitea/actions/setup-opencode
with:
version: '1.2.3'
npm-registry: 'https://registry.npmmirror.com'
- name: 构建
run: |
opencode build
@@ -0,0 +1,21 @@
name: Specific Version Example
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 检出代码
uses: actions/checkout@v4
- name: 安装 OpenCode 1.2.3
uses: ./.gitea/actions/setup-opencode
with:
version: '1.2.3'
cache-prefix: 'opencode-stable'
- name: 构建项目
run: |
opencode build --release