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-ai' use-taobao-registry: description: '使用淘宝 npm 镜像源 (true/false)' required: false default: 'false' 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 }}" GLOBAL_ROOT="${{ steps.npm-paths.outputs.global-root }}" NEED_INSTALL="true" # 检查包目录是否存在(更可靠,不依赖命令名) PACKAGE_DIR="${GLOBAL_ROOT}/${PACKAGE}" if [[ -d "${PACKAGE_DIR}" ]]; then # 从 package.json 获取版本 PACKAGE_JSON="${PACKAGE_DIR}/package.json" if [[ -f "${PACKAGE_JSON}" ]]; then INSTALLED_VERSION=$(grep -o '"version"[[:space:]]*:[[:space:]]*"[^"]*"' "${PACKAGE_JSON}" | head -1 | cut -d'"' -f4 || echo "unknown") else INSTALLED_VERSION="unknown" fi 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: 保存缓存 if: steps.check-installed.outputs.need-install == '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: | # 直接从 opencode 命令获取版本 if command -v opencode &> /dev/null; then VERSION=$(opencode --version 2>/dev/null | head -n1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' || echo "unknown") if [[ "${VERSION}" != "unknown" ]]; then echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "✅ OpenCode 版本: ${VERSION}" else # 如果命令输出格式不同,尝试直接输出 VERSION=$(opencode --version 2>/dev/null | head -n1 || echo "unknown") echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "✅ OpenCode 版本: ${VERSION}" fi else # 备用方案:使用已解析的版本号 VERSION="${{ steps.resolve-version.outputs.version }}" echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "⚠️ opencode 命令不可用,使用已解析版本: ${VERSION}" 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 已准备就绪!"