From 489cc943162e3874bac42d207188cb1b7aeb65b2 Mon Sep 17 00:00:00 2001 From: Lyda <1829913225@qq.com> Date: Tue, 17 Mar 2026 16:17:03 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20setup-opencode=20a?= =?UTF-8?q?ction=EF=BC=8C=E6=94=AF=E6=8C=81=20npm=20=E5=85=A8=E5=B1=80?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E5=92=8C=E6=99=BA=E8=83=BD=E7=BC=93=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 npm 全局安装 OpenCode,支持版本管理和自动更新 - 智能缓存策略:缓存 npm 目录和全局安装,加速后续构建 - 支持淘宝镜像源和自定义 npm 镜像,国内环境友好 - 版本检测:自动比对已安装版本,避免重复安装 - 输出安装版本、缓存命中状态和更新状态 - 提供详细的使用文档和多场景示例 --- setup-opencode/README.md | 318 +++++++++++++++++++ setup-opencode/action.yml | 295 +++++++++++++++++ setup-opencode/examples/basic-usage.yml | 36 +++ setup-opencode/examples/china-mirror.yml | 33 ++ setup-opencode/examples/custom-registry.yml | 21 ++ setup-opencode/examples/specific-version.yml | 21 ++ 6 files changed, 724 insertions(+) create mode 100644 setup-opencode/README.md create mode 100644 setup-opencode/action.yml create mode 100644 setup-opencode/examples/basic-usage.yml create mode 100644 setup-opencode/examples/china-mirror.yml create mode 100644 setup-opencode/examples/custom-registry.yml create mode 100644 setup-opencode/examples/specific-version.yml diff --git a/setup-opencode/README.md b/setup-opencode/README.md new file mode 100644 index 0000000..0e1fd36 --- /dev/null +++ b/setup-opencode/README.md @@ -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 目录和全局安装,恢复快速 + +## 许可证 + +与主仓库保持一致。 diff --git a/setup-opencode/action.yml b/setup-opencode/action.yml new file mode 100644 index 0000000..d36414e --- /dev/null +++ b/setup-opencode/action.yml @@ -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 已准备就绪!" diff --git a/setup-opencode/examples/basic-usage.yml b/setup-opencode/examples/basic-usage.yml new file mode 100644 index 0000000..cd14674 --- /dev/null +++ b/setup-opencode/examples/basic-usage.yml @@ -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 diff --git a/setup-opencode/examples/china-mirror.yml b/setup-opencode/examples/china-mirror.yml new file mode 100644 index 0000000..c283e5a --- /dev/null +++ b/setup-opencode/examples/china-mirror.yml @@ -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 diff --git a/setup-opencode/examples/custom-registry.yml b/setup-opencode/examples/custom-registry.yml new file mode 100644 index 0000000..ef6aa69 --- /dev/null +++ b/setup-opencode/examples/custom-registry.yml @@ -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 diff --git a/setup-opencode/examples/specific-version.yml b/setup-opencode/examples/specific-version.yml new file mode 100644 index 0000000..f995a9f --- /dev/null +++ b/setup-opencode/examples/specific-version.yml @@ -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