This repository was archived by the owner on Feb 12, 2026. It is now read-only.
fix(cd): auto-resolve merge conflicts in promote workflow #161
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CD | |
| on: | |
| push: | |
| branches: [dev, main] | |
| workflow_dispatch: | |
| inputs: | |
| action: | |
| description: "Select action" | |
| required: true | |
| default: "promote-to-stable" | |
| type: choice | |
| options: | |
| - promote-to-stable | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| issues: write | |
| concurrency: | |
| group: cd-${{ github.repository }} | |
| cancel-in-progress: false | |
| jobs: | |
| # ============================================================ | |
| # Promote: Tạo PR từ dev -> main | |
| # ============================================================ | |
| promote: | |
| name: Promote to Stable | |
| if: github.event_name == 'workflow_dispatch' && inputs.action == 'promote-to-stable' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.GH_PAT }} | |
| - name: Check CI/CD status on dev branch | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| chmod +x .github/scripts/check-ci-cd-status.sh | |
| ./.github/scripts/check-ci-cd-status.sh --branch=dev | |
| - name: Merge and create promote PR | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_PAT }} | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| PROMOTE_BRANCH="promote/dev-to-main" | |
| # Close stale release-please PRs on main | |
| STALE_PRS=$(gh pr list --base main --label "autorelease: pending" --json number --jq '.[].number') | |
| for pr in $STALE_PRS; do | |
| echo "Closing stale release PR #$pr" | |
| gh pr close "$pr" --comment "Closed by promote workflow." | |
| done | |
| git push origin --delete release-please--branches--main 2>/dev/null || true | |
| # Close any existing promote PRs | |
| OLD_PRS=$(gh pr list --base main --head "$PROMOTE_BRANCH" --json number --jq '.[].number') | |
| for pr in $OLD_PRS; do | |
| echo "Closing old promote PR #$pr" | |
| gh pr close "$pr" | |
| done | |
| # Save main's release-please manifest | |
| MAIN_MANIFEST=$(cat .release-please-manifest.json) | |
| # Create promote branch from main, merge dev (auto-resolve conflicts) | |
| git checkout -B "$PROMOTE_BRANCH" origin/main | |
| git merge origin/dev -X theirs --no-edit \ | |
| -m "feat: promote dev to main" | |
| # Restore main's manifest (release-please needs stable baseline) | |
| echo "$MAIN_MANIFEST" > .release-please-manifest.json | |
| git add .release-please-manifest.json | |
| git diff --staged --quiet || \ | |
| git commit -m "chore: preserve stable release-please manifest" | |
| git push origin "$PROMOTE_BRANCH" --force | |
| # Create PR | |
| LATEST_TAG=$(git describe --tags --abbrev=0 origin/dev 2>/dev/null || echo "") | |
| PR_TITLE="feat: promote dev to main${LATEST_TAG:+ ($LATEST_TAG)}" | |
| gh pr create \ | |
| --base main \ | |
| --head "$PROMOTE_BRANCH" \ | |
| --title "$PR_TITLE" \ | |
| --body "## Promote dev to main | |
| Automated merge of \`dev\` into \`main\` with conflicts auto-resolved. | |
| ### Pre-checks passed: | |
| - CI workflow passed on dev | |
| - CD workflow passed on dev | |
| ### Latest beta version: ${LATEST_TAG:-N/A}" | |
| # ============================================================ | |
| # Release: release-please tạo Release PR + GitHub Release | |
| # ============================================================ | |
| release-beta: | |
| name: Release (Beta) | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/dev' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| released: ${{ steps.release.outputs.release_created }} | |
| version: ${{ steps.release.outputs.version }} | |
| tag: ${{ steps.release.outputs.tag_name }} | |
| steps: | |
| - uses: googleapis/release-please-action@v4 | |
| id: release | |
| with: | |
| token: ${{ secrets.GH_PAT }} | |
| config-file: release-please-config-beta.json | |
| manifest-file: .release-please-manifest-beta.json | |
| target-branch: dev | |
| release-stable: | |
| name: Release (Stable) | |
| if: github.event_name == 'push' && github.ref == 'refs/heads/main' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| released: ${{ steps.release.outputs.release_created }} | |
| version: ${{ steps.release.outputs.version }} | |
| tag: ${{ steps.release.outputs.tag_name }} | |
| steps: | |
| - uses: googleapis/release-please-action@v4 | |
| id: release | |
| with: | |
| token: ${{ secrets.GH_PAT }} | |
| config-file: release-please-config.json | |
| manifest-file: .release-please-manifest.json | |
| target-branch: main | |
| # ============================================================ | |
| # Prepare: Set release info cho Tauri builds | |
| # (release-please tạo release bằng GH_PAT → không bị immutable) | |
| # ============================================================ | |
| prepare-release: | |
| name: Prepare Release Info | |
| needs: [release-beta, release-stable] | |
| if: | | |
| always() && !cancelled() && | |
| (needs.release-beta.outputs.released == 'true' || needs.release-stable.outputs.released == 'true') | |
| runs-on: ubuntu-latest | |
| outputs: | |
| version: ${{ steps.set.outputs.version }} | |
| tag: ${{ steps.set.outputs.tag }} | |
| is_prerelease: ${{ steps.set.outputs.is_prerelease }} | |
| steps: | |
| - name: Set release info | |
| id: set | |
| run: | | |
| if [ "${{ needs.release-stable.outputs.released }}" = "true" ]; then | |
| echo "version=${{ needs.release-stable.outputs.version }}" >> $GITHUB_OUTPUT | |
| echo "tag=${{ needs.release-stable.outputs.tag }}" >> $GITHUB_OUTPUT | |
| echo "is_prerelease=false" >> $GITHUB_OUTPUT | |
| else | |
| echo "version=${{ needs.release-beta.outputs.version }}" >> $GITHUB_OUTPUT | |
| echo "tag=${{ needs.release-beta.outputs.tag }}" >> $GITHUB_OUTPUT | |
| echo "is_prerelease=true" >> $GITHUB_OUTPUT | |
| fi | |
| # ============================================================ | |
| # Build: Tauri multi-platform | |
| # ============================================================ | |
| build-tauri: | |
| name: Build Tauri (${{ matrix.bundle_name }}) | |
| needs: [prepare-release] | |
| if: always() && !cancelled() && needs.prepare-release.result == 'success' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: ubuntu-22.04 | |
| target: x86_64-unknown-linux-gnu | |
| rclone_url: https://downloads.rclone.org/rclone-current-linux-amd64.zip | |
| rclone_binary: rclone-x86_64-unknown-linux-gnu | |
| crsqlite_url: https://github.com/vlcn-io/cr-sqlite/releases/download/v0.16.3/crsqlite-linux-x86_64.zip | |
| crsqlite_binary: crsqlite-x86_64-unknown-linux-gnu.so | |
| bundle_name: linux-deb | |
| bundle_args: --bundles deb | |
| - platform: ubuntu-22.04 | |
| target: x86_64-unknown-linux-gnu | |
| rclone_url: https://downloads.rclone.org/rclone-current-linux-amd64.zip | |
| rclone_binary: rclone-x86_64-unknown-linux-gnu | |
| crsqlite_url: https://github.com/vlcn-io/cr-sqlite/releases/download/v0.16.3/crsqlite-linux-x86_64.zip | |
| crsqlite_binary: crsqlite-x86_64-unknown-linux-gnu.so | |
| bundle_name: linux-rpm | |
| bundle_args: --bundles rpm | |
| - platform: ubuntu-22.04 | |
| target: x86_64-unknown-linux-gnu | |
| rclone_url: https://downloads.rclone.org/rclone-current-linux-amd64.zip | |
| rclone_binary: rclone-x86_64-unknown-linux-gnu | |
| crsqlite_url: https://github.com/vlcn-io/cr-sqlite/releases/download/v0.16.3/crsqlite-linux-x86_64.zip | |
| crsqlite_binary: crsqlite-x86_64-unknown-linux-gnu.so | |
| bundle_name: linux-appimage | |
| bundle_args: --bundles appimage | |
| - platform: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| rclone_url: https://downloads.rclone.org/rclone-current-windows-amd64.zip | |
| rclone_binary: rclone-x86_64-pc-windows-msvc.exe | |
| crsqlite_url: https://github.com/vlcn-io/cr-sqlite/releases/download/v0.16.3/crsqlite-win-x86_64.zip | |
| crsqlite_binary: crsqlite-x86_64-pc-windows-msvc.dll | |
| bundle_name: windows-nsis | |
| bundle_args: "" | |
| - platform: macos-latest | |
| target: x86_64-apple-darwin | |
| rclone_url: https://downloads.rclone.org/rclone-current-osx-amd64.zip | |
| rclone_binary: rclone-x86_64-apple-darwin | |
| crsqlite_url: https://github.com/vlcn-io/cr-sqlite/releases/download/v0.16.3/crsqlite-darwin-x86_64.zip | |
| crsqlite_binary: crsqlite-x86_64-apple-darwin.dylib | |
| bundle_name: macos-x64 | |
| bundle_args: "" | |
| - platform: macos-latest | |
| target: aarch64-apple-darwin | |
| rclone_url: https://downloads.rclone.org/rclone-current-osx-arm64.zip | |
| rclone_binary: rclone-aarch64-apple-darwin | |
| crsqlite_url: https://github.com/vlcn-io/cr-sqlite/releases/download/v0.16.3/crsqlite-darwin-aarch64.zip | |
| crsqlite_binary: crsqlite-aarch64-apple-darwin.dylib | |
| bundle_name: macos-arm64 | |
| bundle_args: "" | |
| runs-on: ${{ matrix.platform }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Install Linux dependencies | |
| if: matrix.platform == 'ubuntu-22.04' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| with: | |
| package_json_file: apps/web/package.json | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v6 | |
| with: | |
| node-version: "24" | |
| cache: "pnpm" | |
| cache-dependency-path: apps/web/pnpm-lock.yaml | |
| - name: Install frontend dependencies | |
| working-directory: apps/web | |
| run: pnpm install --frozen-lockfile | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: ${{ matrix.platform }}-${{ matrix.target }}-release | |
| - name: Install Tauri CLI | |
| run: cargo install tauri-cli --locked | |
| - name: Download Rclone (Windows) | |
| if: matrix.platform == 'windows-latest' | |
| shell: pwsh | |
| run: | | |
| New-Item -ItemType Directory -Force -Path apps/tauri/binaries | |
| Invoke-WebRequest -Uri "${{ matrix.rclone_url }}" -OutFile "rclone.zip" | |
| Expand-Archive -Path "rclone.zip" -DestinationPath "rclone-temp" | |
| $rcloneDir = Get-ChildItem -Path "rclone-temp" -Directory | Select-Object -First 1 | |
| Copy-Item -Path "$($rcloneDir.FullName)/rclone.exe" -Destination "apps/tauri/binaries/${{ matrix.rclone_binary }}" | |
| - name: Download Rclone (Linux) | |
| if: matrix.platform == 'ubuntu-22.04' | |
| run: | | |
| mkdir -p apps/tauri/binaries | |
| curl -LO ${{ matrix.rclone_url }} | |
| unzip rclone-current-linux-amd64.zip | |
| cp rclone-*/rclone apps/tauri/binaries/${{ matrix.rclone_binary }} | |
| chmod +x apps/tauri/binaries/${{ matrix.rclone_binary }} | |
| - name: Download Rclone (macOS) | |
| if: matrix.platform == 'macos-latest' | |
| run: | | |
| mkdir -p apps/tauri/binaries | |
| curl -LO ${{ matrix.rclone_url }} | |
| unzip rclone-*.zip | |
| cp rclone-*/rclone apps/tauri/binaries/${{ matrix.rclone_binary }} | |
| chmod +x apps/tauri/binaries/${{ matrix.rclone_binary }} | |
| - name: Download CR-SQLite (Windows) | |
| if: matrix.platform == 'windows-latest' | |
| shell: pwsh | |
| run: | | |
| Invoke-WebRequest -Uri "${{ matrix.crsqlite_url }}" -OutFile "crsqlite.zip" | |
| Expand-Archive -Path "crsqlite.zip" -DestinationPath "crsqlite-temp" | |
| Copy-Item -Path "crsqlite-temp/crsqlite.dll" -Destination "apps/tauri/binaries/${{ matrix.crsqlite_binary }}" | |
| - name: Download CR-SQLite (Linux) | |
| if: matrix.platform == 'ubuntu-22.04' | |
| run: | | |
| curl -LO ${{ matrix.crsqlite_url }} | |
| unzip crsqlite-linux-x86_64.zip | |
| cp crsqlite.so apps/tauri/binaries/${{ matrix.crsqlite_binary }} | |
| chmod +x apps/tauri/binaries/${{ matrix.crsqlite_binary }} | |
| - name: Download CR-SQLite (macOS) | |
| if: matrix.platform == 'macos-latest' | |
| run: | | |
| curl -LO ${{ matrix.crsqlite_url }} | |
| unzip crsqlite-darwin-*.zip | |
| cp crsqlite.dylib apps/tauri/binaries/${{ matrix.crsqlite_binary }} | |
| chmod +x apps/tauri/binaries/${{ matrix.crsqlite_binary }} | |
| - name: Update version in tauri.conf.json | |
| env: | |
| RELEASE_VERSION: ${{ needs.prepare-release.outputs.version }} | |
| run: | | |
| node -e " | |
| const fs = require('fs'); | |
| const path = 'apps/tauri/tauri.conf.json'; | |
| const config = JSON.parse(fs.readFileSync(path, 'utf8')); | |
| config.version = process.env.RELEASE_VERSION; | |
| fs.writeFileSync(path, JSON.stringify(config, null, 4) + '\n'); | |
| console.log('Updated version to: ' + process.env.RELEASE_VERSION); | |
| " | |
| - name: Build Tauri App | |
| uses: tauri-apps/tauri-action@v0 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GH_PAT }} | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| with: | |
| tagName: ${{ needs.prepare-release.outputs.tag }} | |
| releaseName: "EchoVault ${{ needs.prepare-release.outputs.tag }}" | |
| releaseBody: "See the assets section below to download the installer for your platform." | |
| releaseDraft: false | |
| prerelease: ${{ needs.prepare-release.outputs.is_prerelease == 'true' }} | |
| projectPath: apps/tauri | |
| args: --target ${{ matrix.target }} ${{ matrix.bundle_args }} | |
| tauriScript: pnpm tauri | |
| includeUpdaterJson: true | |
| # ============================================================ | |
| # Build: CLI multi-platform (standalone binary for MCP server) | |
| # ============================================================ | |
| build-cli: | |
| name: Build CLI (${{ matrix.name }}) | |
| needs: [prepare-release] | |
| if: always() && !cancelled() && needs.prepare-release.result == 'success' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: ubuntu-22.04 | |
| target: x86_64-unknown-linux-gnu | |
| name: linux-x64 | |
| artifact: echovault-cli-linux-x64 | |
| - platform: macos-latest | |
| target: x86_64-apple-darwin | |
| name: macos-x64 | |
| artifact: echovault-cli-macos-x64 | |
| - platform: macos-latest | |
| target: aarch64-apple-darwin | |
| name: macos-arm64 | |
| artifact: echovault-cli-macos-arm64 | |
| - platform: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| name: windows-x64 | |
| artifact: echovault-cli-windows-x64.exe | |
| runs-on: ${{ matrix.platform }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v6 | |
| - name: Install Linux dependencies | |
| if: matrix.platform == 'ubuntu-22.04' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libayatana-appindicator3-dev librsvg2-dev | |
| - name: Install Rust toolchain | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - name: Cache cargo | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| key: cli-${{ matrix.platform }}-${{ matrix.target }} | |
| - name: Build CLI | |
| run: cargo build -p echovault-cli --release --target ${{ matrix.target }} | |
| - name: Rename artifact (Unix) | |
| if: matrix.platform != 'windows-latest' | |
| run: | | |
| cp target/${{ matrix.target }}/release/echovault-cli ${{ matrix.artifact }} | |
| chmod +x ${{ matrix.artifact }} | |
| - name: Rename artifact (Windows) | |
| if: matrix.platform == 'windows-latest' | |
| shell: pwsh | |
| run: | | |
| Copy-Item "target/${{ matrix.target }}/release/echovault-cli.exe" "${{ matrix.artifact }}" | |
| - name: Upload CLI to Release | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_PAT }} | |
| run: | | |
| gh release upload ${{ needs.prepare-release.outputs.tag }} ${{ matrix.artifact }} --clobber | |
| # ============================================================ | |
| # Sync: Tai tao dev branch tu main sau stable release | |
| # ============================================================ | |
| sync-dev: | |
| name: Sync Dev from Main | |
| needs: [release-stable, build-tauri, build-cli] | |
| if: | | |
| always() && !cancelled() && | |
| needs.release-stable.outputs.released == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout main | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: main | |
| fetch-depth: 0 | |
| token: ${{ secrets.GH_PAT }} | |
| - name: Sync beta manifest and recreate dev | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| cp .release-please-manifest.json .release-please-manifest-beta.json | |
| git add .release-please-manifest-beta.json | |
| git diff --cached --quiet || git commit -m "chore: sync beta manifest from stable [skip ci]" | |
| git push origin main | |
| # Clean up promote branch | |
| git push origin --delete promote/dev-to-main 2>/dev/null || true | |
| git push origin --delete dev || true | |
| git checkout -b dev | |
| git push origin dev | |
| echo "Dev branch recreated from main" |