This repository was archived by the owner on Feb 12, 2026. It is now read-only.
chore: implement dual manifest strategy for release pipeline #155
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: Close stale release-please PRs on main | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_PAT }} | |
| run: | | |
| 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. A new release PR will be created after promotion." | |
| done | |
| git push origin --delete release-please--branches--main 2>/dev/null || true | |
| - name: Create PR to promote dev to main | |
| env: | |
| GH_TOKEN: ${{ secrets.GH_PAT }} | |
| run: | | |
| EXISTING_PR=$(gh pr list --base main --head dev --json number --jq '.[0].number') | |
| if [ -n "$EXISTING_PR" ]; then | |
| echo "PR #$EXISTING_PR already exists for dev -> main" | |
| echo "URL: https://github.com/${{ github.repository }}/pull/$EXISTING_PR" | |
| exit 0 | |
| fi | |
| LATEST_TAG=$(git describe --tags --abbrev=0 origin/dev 2>/dev/null || echo "") | |
| if [ -z "$LATEST_TAG" ]; then | |
| PR_TITLE="feat: promote dev to main" | |
| else | |
| PR_TITLE="feat: promote dev to main ($LATEST_TAG)" | |
| fi | |
| gh pr create \ | |
| --base main \ | |
| --head dev \ | |
| --title "$PR_TITLE" \ | |
| --body "## Promote dev to main | |
| This PR promotes the latest changes from \`dev\` branch to \`main\`. | |
| ### Pre-checks passed: | |
| - CI workflow passed on dev | |
| - CD workflow passed on dev | |
| ### Latest beta version: $LATEST_TAG" | |
| # ============================================================ | |
| # 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: | | |
| 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 | |
| git push origin --delete dev || true | |
| git checkout -b dev | |
| git push origin dev | |
| echo "Dev branch recreated from main" |