name: Build and Release permissions: contents: write issues: read pull-requests: read actions: write on: push: branches: [dev] tags: - '*' env: BUN_INSTALL_REGISTRY: 'https://registry.npmjs.org/' jobs: # 独立 code-quality job,供桌面构建和 web-cli 打包共用,避免重复跑 code-quality: name: Code Quality runs-on: ubuntu-latest if: github.ref == 'refs/heads/dev' || (startsWith(github.ref, 'refs/tags/') && !contains(github.ref, '-dev-')) steps: - name: Checkout code uses: actions/checkout@v6 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '22' - name: Setup bun uses: oven-sh/setup-bun@v2 with: bun-version: latest - name: Install dependencies run: bun install --frozen-lockfile - name: Run postinstall run: bun run postinstall || true - name: Run ESLint run: bun run lint - name: Check Prettier formatting run: bun run format:check - name: TypeScript type check run: bunx tsc --noEmit - name: Run unit tests run: bunx vitest run build-pipeline: name: Build Pipeline uses: ./.github/workflows/_build-reusable.yml needs: code-quality # dev 分支或正式 tag 触发构建(排除 -dev- tag,避免重复构建) if: needs.code-quality.result == 'success' && (github.ref == 'refs/heads/dev' || (startsWith(github.ref, 'refs/tags/') && !contains(github.ref, '-dev-'))) with: skip_code_quality: true matrix: >- {"include":[ {"platform":"macos-arm64","os":"macos-14","command":"node scripts/build-with-builder.js arm64 --mac --arm64","artifact-name":"macos-build-arm64","arch":"arm64"}, {"platform":"macos-x64","os":"macos-14","command":"node scripts/build-with-builder.js x64 --mac --x64","artifact-name":"macos-build-x64","arch":"x64"}, {"platform":"windows-x64","os":"windows-2022","command":"node scripts/build-with-builder.js x64 --win --x64","artifact-name":"windows-build-x64","arch":"x64"}, {"platform":"windows-arm64","os":"windows-11-arm","command":"node scripts/build-with-builder.js arm64 --win --arm64","artifact-name":"windows-build-arm64","arch":"arm64"}, {"platform":"linux-x64","os":"ubuntu-latest","command":"node scripts/build-with-builder.js x64 --linux --x64","artifact-name":"linux-build-x64","arch":"x64"}, {"platform":"linux-arm64","os":"ubuntu-24.04-arm","command":"node scripts/build-with-builder.js arm64 --linux --arm64","artifact-name":"linux-build-arm64","arch":"arm64"} ]} secrets: inherit pack-web-cli: name: Pack Web CLI uses: ./.github/workflows/pack-web-cli.yml needs: code-quality # 与桌面构建同频触发:dev 分支或正式 tag(排除 -dev- tag) if: needs.code-quality.result == 'success' && (github.ref == 'refs/heads/dev' || (startsWith(github.ref, 'refs/tags/') && !contains(github.ref, '-dev-'))) with: ref: '' append_commit_hash: false skip_code_quality: true secrets: inherit # 自动重试 workflow(当构建失败时) auto-retry-workflow: name: Auto Retry on Build Failure runs-on: ubuntu-latest needs: build-pipeline # 关键:只在首次失败时触发,避免无限循环 if: | failure() && github.run_attempt == 1 && (github.event_name == 'push' || github.event_name == 'schedule') steps: - name: Log retry information run: | echo "==========================================" echo "🔄 Auto retry triggered (first failure)" echo "==========================================" echo "Build failed on first attempt, preparing auto retry..." echo "Current attempt: ${{ github.run_attempt }}" echo "Wait strategy: 5 minutes cooldown before retry" echo "==========================================" - name: Wait before retry (5 min cooldown) run: | echo "⏳ Waiting 5 minutes before retry..." echo "Start: $(date)" sleep 300 echo "End: $(date)" echo "Triggering retry..." - name: Trigger workflow rerun run: | echo "🔄 Triggering full workflow rerun (attempt 2)..." # Use re-run API (not rerun-failed-jobs, to avoid loops) response=$(curl -X POST \ -H "Accept: application/vnd.github.v3+json" \ -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ -w "\n%{http_code}" \ https://api.github.com/repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/rerun) http_code=$(echo "$response" | tail -n1) if [ "$http_code" = "201" ]; then echo "" echo "✅ Retry triggered successfully" echo "This will be attempt 2" echo "Details: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" else echo "" echo "❌ Retry trigger failed, HTTP status: $http_code" echo "Response:" echo "$response" | head -n-1 exit 1 fi # 自动创建tag(仅 dev 分支推送时) create-tag: name: Create Tag from Branch runs-on: ubuntu-latest needs: [build-pipeline] if: success() && github.ref == 'refs/heads/dev' outputs: tag_name: ${{ steps.create_tag.outputs.tag_name }} is_dev: ${{ steps.create_tag.outputs.is_dev }} steps: - name: Checkout code uses: actions/checkout@v6 with: # GH_TOKEN is a PAT with `repo` + `workflow` scopes — required because # tag pushes whose reachable history contains .github/workflows/ # changes are rejected for the built-in GITHUB_TOKEN (lacks the # `workflow` scope, which GITHUB_TOKEN can never grant). token: ${{ secrets.GH_TOKEN }} - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '22' - name: Setup bun uses: oven-sh/setup-bun@v2 with: bun-version: latest - name: Create and push tag id: create_tag run: | VERSION=$(node -p "require('./package.json').version") COMMIT_SHORT=$(git rev-parse --short HEAD) BRANCH_NAME=${GITHUB_REF#refs/heads/} # 根据分支确定 tag 格式 if [ "$BRANCH_NAME" = "dev" ]; then TAG_NAME="v${VERSION}-dev-${COMMIT_SHORT}" IS_DEV="true" echo "🔧 Development release: $TAG_NAME" else TAG_NAME="v$VERSION" IS_DEV="false" echo "🚀 Production release: $TAG_NAME" fi echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT echo "is_dev=$IS_DEV" >> $GITHUB_OUTPUT # 检查远程tag是否已存在 if git ls-remote --tags origin | grep -q "refs/tags/$TAG_NAME$"; then if [ "$IS_DEV" = "false" ]; then # Main 分支:需要递增版本并修改 package.json echo "⚠️ Tag $TAG_NAME already exists, auto-incrementing version..." if [[ $VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then MAJOR=${BASH_REMATCH[1]} MINOR=${BASH_REMATCH[2]} PATCH=${BASH_REMATCH[3]} NEW_PATCH=$((PATCH + 1)) NEW_VERSION="${MAJOR}.${MINOR}.${NEW_PATCH}" echo "📝 Updating package.json version to $NEW_VERSION" # 更新 package.json 版本号 bun pm version $NEW_VERSION # 配置git用户信息 git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" # 提交版本更新 git add package.json bun.lock git commit -m "chore: bump version to $NEW_VERSION" git remote set-url origin https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }}.git git push origin $BRANCH_NAME # 获取新的 commit ID COMMIT_SHORT=$(git rev-parse --short HEAD) TAG_NAME="v${NEW_VERSION}-${COMMIT_SHORT}" echo "🚀 New tag with updated version: $TAG_NAME" echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT else TAG_NAME="v${VERSION}-${COMMIT_SHORT}" echo "⚠️ Fallback: creating tag with commit ID: $TAG_NAME" echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT fi else # Dev 分支 tag 已存在则失败(不应该发生) echo "⚠️ Dev tag $TAG_NAME already exists, this shouldn't happen" exit 1 fi fi # 配置git用户信息(如果前面没配置) git config user.name "github-actions[bot]" git config user.email "github-actions[bot]@users.noreply.github.com" # 创建并推送tag echo "Creating tag: $TAG_NAME" git tag $TAG_NAME git remote set-url origin https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }}.git git push origin $TAG_NAME echo "✅ Successfully created and pushed tag: $TAG_NAME" env: GH_TOKEN: ${{ secrets.GH_TOKEN }} # 发布到 GitHub Releases release: name: Create Release runs-on: ubuntu-latest needs: [build-pipeline, create-tag, pack-web-cli] # Tag 推送时 create-tag 会被跳过,所以需要检查两种情况(排除 -dev- tag 避免重复) if: always() && needs.build-pipeline.result == 'success' && needs.pack-web-cli.result == 'success' && (needs.create-tag.result == 'success' || (startsWith(github.ref, 'refs/tags/') && !contains(github.ref, '-dev-'))) # dev 分支使用 dev-release 环境,正式 tag 使用 release 环境 environment: ${{ needs.create-tag.outputs.is_dev == 'true' && 'dev-release' || 'release' }} steps: - name: Checkout code uses: actions/checkout@v6 - name: Get version for release id: version run: | # 判断触发来源:正式 tag 推送 or dev 分支 if [[ "$GITHUB_REF" == refs/tags/* ]]; then # 正式 Tag 推送触发(-dev- tag 已被排除,不会到达这里) TAG_NAME="${GITHUB_REF#refs/tags/}" IS_DEV="false" echo "🚀 Creating stable release from tag: $TAG_NAME" else # dev 分支触发,使用 create-tag job 的输出 TAG_NAME="${{ needs.create-tag.outputs.tag_name }}" IS_DEV="${{ needs.create-tag.outputs.is_dev }}" echo "🔧 Creating development release: $TAG_NAME" fi echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT echo "is_dev=$IS_DEV" >> $GITHUB_OUTPUT - name: Download all build artifacts uses: actions/download-artifact@v7 with: path: build-artifacts - name: Prepare release assets (normalize updater metadata) shell: bash run: bash scripts/prepare-release-assets.sh build-artifacts release-assets - name: Create Release uses: softprops/action-gh-release@v2 with: tag_name: ${{ steps.version.outputs.tag_name }} name: ${{ steps.version.outputs.is_dev == 'true' && format('Development Build {0}', steps.version.outputs.tag_name) || steps.version.outputs.tag_name }} files: | release-assets/**/*.exe release-assets/**/*.msi release-assets/**/*.dmg release-assets/**/*.deb release-assets/**/*.zip release-assets/**/*.yml release-assets/**/*.tar.gz release-assets/**/*.sha256 release-assets/install-web.sh generate_release_notes: true draft: true prerelease: ${{ steps.version.outputs.is_dev == 'true' || contains(steps.version.outputs.tag_name, 'beta') || contains(steps.version.outputs.tag_name, 'alpha') || contains(steps.version.outputs.tag_name, 'rc') }} env: GH_TOKEN: ${{ secrets.GH_TOKEN }}