Skip to content
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

fix(cd): auto-resolve merge conflicts in promote workflow

fix(cd): auto-resolve merge conflicts in promote workflow #161

Workflow file for this run

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"