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 }}" 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 }}" # 通过 npm list 检查包是否安装,而不是检查命令(因为包名和命令名可能不同) 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 "") if [[ -n "${INSTALLED_VERSION}" ]]; then echo "install-verified=true" >> $GITHUB_OUTPUT echo "✅ 安装验证成功 - 版本: ${INSTALLED_VERSION}" # 尝试查找实际的命令名 BIN_DIR="${{ steps.npm-paths.outputs.prefix }}/bin" if [[ -d "${BIN_DIR}" ]]; then COMMANDS=$(ls -1 "${BIN_DIR}" 2>/dev/null | grep -i opencode || echo "") if [[ -n "${COMMANDS}" ]]; then echo "📦 可用命令: ${COMMANDS}" fi fi else echo "install-verified=false" >> $GITHUB_OUTPUT echo "❌ 安装验证失败 - 未找到包 ${PACKAGE}" 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 }}" # 使用 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 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 已准备就绪!"