Merge pull request #46 from DavidAmunga/feat/add-topcontacts-sheet #140
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: "Main Release" | |
| on: | |
| push: | |
| branches: [main] | |
| paths-ignore: | |
| - "**.md" | |
| - "docs/**" | |
| - ".github/**.md" | |
| - "README.md" | |
| - "CHANGELOG.md" | |
| workflow_dispatch: | |
| inputs: | |
| force_release: | |
| description: "Force a release even if no changesets" | |
| required: false | |
| default: false | |
| type: boolean | |
| release_type: | |
| description: "Type of release" | |
| required: false | |
| default: "auto" | |
| type: choice | |
| options: | |
| - auto | |
| - patch | |
| - minor | |
| - major | |
| concurrency: | |
| group: main-release | |
| cancel-in-progress: false | |
| jobs: | |
| # Step 1: Determine if we should release | |
| check-release: | |
| name: "Check Release Status" | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_release: ${{ steps.check.outputs.should_release }} | |
| version: ${{ steps.check.outputs.version }} | |
| has_changesets: ${{ steps.check.outputs.has_changesets }} | |
| is_changeset_release: ${{ steps.check.outputs.is_changeset_release }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: lts/* | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Check release conditions | |
| id: check | |
| run: | | |
| COMMIT_MSG=$(git log -1 --pretty=format:"%s") | |
| RECENT_COMMITS=$(git log -10 --pretty=format:"%s") | |
| IS_CHANGESET_RELEASE="false" | |
| if [[ "$COMMIT_MSG" == *"chore: release version packages"* ]] || \ | |
| [[ "$COMMIT_MSG" == *"Version Packages"* ]] || \ | |
| [[ "$COMMIT_MSG" == *"changeset-release/main"* ]] || \ | |
| [[ "$COMMIT_MSG" == *"chore: sync version files"* ]] || \ | |
| [[ "$RECENT_COMMITS" == *"chore: release version packages"* ]] || \ | |
| [[ "$RECENT_COMMITS" == *"Version Packages"* ]] || \ | |
| [[ "$RECENT_COMMITS" == *"chore: sync version files"* ]]; then | |
| IS_CHANGESET_RELEASE="true" | |
| fi | |
| HAS_CHANGESETS="false" | |
| CHANGESET_COUNT=0 | |
| if [ -d ".changeset" ]; then | |
| CHANGESET_COUNT=$(find .changeset -name "*.md" -not -name "README.md" -not -name "config.json" 2>/dev/null | wc -l) | |
| if [ "$CHANGESET_COUNT" -gt 0 ]; then | |
| HAS_CHANGESETS="true" | |
| fi | |
| fi | |
| VERSION_CHANGED="false" | |
| if git diff HEAD~1 HEAD --name-only | grep -q "package.json\|src-tauri/Cargo.toml\|src-tauri/tauri.conf.json"; then | |
| VERSION_CHANGED="true" | |
| fi | |
| MANUAL_VERSION_BUMP="false" | |
| if [[ "$COMMIT_MSG" == *"bump version"* ]] || [[ "$COMMIT_MSG" == *"version"* ]] && [[ "$COMMIT_MSG" == *"v"* ]]; then | |
| MANUAL_VERSION_BUMP="true" | |
| fi | |
| SHOULD_RELEASE="false" | |
| RELEASE_REASON="" | |
| if [[ "$IS_CHANGESET_RELEASE" == "true" ]]; then | |
| SHOULD_RELEASE="true" | |
| RELEASE_REASON="changeset release commit" | |
| elif [[ "$HAS_CHANGESETS" == "true" && "$VERSION_CHANGED" == "true" ]]; then | |
| SHOULD_RELEASE="true" | |
| RELEASE_REASON="changesets present and version files updated" | |
| elif [[ "${{ github.event.inputs.force_release }}" == "true" ]]; then | |
| SHOULD_RELEASE="true" | |
| RELEASE_REASON="manual force release" | |
| elif [[ "$HAS_CHANGESETS" == "true" && "${{ github.event_name }}" == "workflow_dispatch" ]]; then | |
| SHOULD_RELEASE="true" | |
| RELEASE_REASON="manual dispatch with pending changesets" | |
| elif [[ "$MANUAL_VERSION_BUMP" == "true" ]]; then | |
| SHOULD_RELEASE="true" | |
| RELEASE_REASON="manual version bump detected" | |
| fi | |
| if [[ "$SHOULD_RELEASE" == "true" ]]; then | |
| echo "✅ Release conditions met: $RELEASE_REASON" | |
| fi | |
| VERSION=$(node -p "require('./package.json').version") | |
| echo "should_release=$SHOULD_RELEASE" >> $GITHUB_OUTPUT | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "has_changesets=$HAS_CHANGESETS" >> $GITHUB_OUTPUT | |
| echo "is_changeset_release=$IS_CHANGESET_RELEASE" >> $GITHUB_OUTPUT | |
| echo "release_reason=$RELEASE_REASON" >> $GITHUB_OUTPUT | |
| # Step 2: Pre-release validation | |
| pre-release-validation: | |
| name: "Pre-Release Validation" | |
| runs-on: ubuntu-latest | |
| needs: check-release | |
| if: needs.check-release.outputs.should_release == 'true' | |
| outputs: | |
| validation_passed: ${{ steps.validate.outputs.passed }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: lts/* | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Install system dependencies for validation | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libglib2.0-dev curl wget unzip | |
| - name: Run comprehensive validation | |
| id: validate | |
| run: | | |
| VALIDATION_PASSED="true" | |
| VALIDATION_ERRORS="" | |
| if ! pnpm audit --audit-level moderate; then | |
| VALIDATION_ERRORS="${VALIDATION_ERRORS}Security vulnerabilities found. " | |
| VALIDATION_PASSED="false" | |
| fi | |
| VERSION=$(node -p "require('./package.json').version") | |
| if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then | |
| VALIDATION_ERRORS="${VALIDATION_ERRORS}Invalid version format: $VERSION. " | |
| VALIDATION_PASSED="false" | |
| fi | |
| if ! git diff --quiet; then | |
| VALIDATION_ERRORS="${VALIDATION_ERRORS}Uncommitted changes detected. " | |
| VALIDATION_PASSED="false" | |
| fi | |
| if ! node -e "JSON.parse(require('fs').readFileSync('src-tauri/tauri.conf.json', 'utf8'))"; then | |
| VALIDATION_ERRORS="${VALIDATION_ERRORS}Invalid tauri.conf.json. " | |
| VALIDATION_PASSED="false" | |
| fi | |
| if ! pnpm run build; then | |
| VALIDATION_ERRORS="${VALIDATION_ERRORS}Frontend build failed. " | |
| VALIDATION_PASSED="false" | |
| fi | |
| if ! bash src-tauri/scripts/setup-build-jre.sh; then | |
| VALIDATION_ERRORS="${VALIDATION_ERRORS}JRE setup failed. " | |
| VALIDATION_PASSED="false" | |
| fi | |
| cd src-tauri | |
| if ! cargo check --quiet; then | |
| VALIDATION_ERRORS="${VALIDATION_ERRORS}Cargo check failed. " | |
| VALIDATION_PASSED="false" | |
| fi | |
| cd .. | |
| if [[ "$VALIDATION_PASSED" == "true" ]]; then | |
| echo "✅ Validation passed" | |
| else | |
| echo "❌ Validation failed: $VALIDATION_ERRORS" | |
| fi | |
| echo "passed=$VALIDATION_PASSED" >> $GITHUB_OUTPUT | |
| # Step 3: Handle changesets (version bumping) | |
| handle-changesets: | |
| name: "Handle Changesets" | |
| runs-on: ubuntu-latest | |
| needs: [check-release] | |
| if: needs.check-release.outputs.has_changesets == 'true' && needs.check-release.outputs.is_changeset_release == 'false' | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: lts/* | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Create Release Pull Request | |
| uses: changesets/action@v1 | |
| with: | |
| version: pnpm run version | |
| title: "chore: release version packages" | |
| commit: "chore: release version packages" | |
| createGithubReleases: false | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Step 4: Sync versions and create tag | |
| prepare-release: | |
| name: "Prepare Release" | |
| runs-on: ubuntu-latest | |
| needs: [check-release, pre-release-validation] | |
| if: needs.check-release.outputs.should_release == 'true' && needs.pre-release-validation.outputs.validation_passed == 'true' | |
| permissions: | |
| contents: write | |
| outputs: | |
| version: ${{ steps.version.outputs.version }} | |
| tag: ${{ steps.version.outputs.tag }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: lts/* | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Sync versions | |
| run: | | |
| pnpm run sync-versions | |
| if ! git diff --quiet; then | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add . | |
| git commit -m "chore: sync version files" | |
| git push | |
| fi | |
| - name: Get version and create tag | |
| id: version | |
| run: | | |
| VERSION=$(node -p "require('./package.json').version") | |
| TAG="v$VERSION" | |
| echo "version=$VERSION" >> $GITHUB_OUTPUT | |
| echo "tag=$TAG" >> $GITHUB_OUTPUT | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # Clean up any existing draft releases | |
| RELEASE_ID=$(gh release view "$TAG" --json id,isDraft --jq 'select(.isDraft == true) | .id' 2>/dev/null || echo "") | |
| if [ -n "$RELEASE_ID" ]; then | |
| gh release delete "$TAG" --yes 2>/dev/null || true | |
| fi | |
| # Create tag if needed | |
| if ! git tag -l "$TAG" | grep -q "$TAG"; then | |
| if git ls-remote --tags origin | grep -q "refs/tags/$TAG$"; then | |
| git fetch origin "refs/tags/$TAG:refs/tags/$TAG" | |
| else | |
| git tag -a "$TAG" -m "Release $TAG" | |
| git push origin "$TAG" | |
| fi | |
| fi | |
| # Step 5: Build and release desktop | |
| build-and-release: | |
| name: "Build & Release" | |
| needs: [check-release, pre-release-validation, prepare-release] | |
| if: needs.check-release.outputs.should_release == 'true' && needs.pre-release-validation.outputs.validation_passed == 'true' | |
| permissions: | |
| contents: write | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - platform: "macos-latest" | |
| args: "--target aarch64-apple-darwin" | |
| target: "aarch64-apple-darwin" | |
| build_name: "macos-aarch64" | |
| - platform: "macos-latest" | |
| args: "--target x86_64-apple-darwin" | |
| target: "x86_64-apple-darwin" | |
| build_name: "macos-x64" | |
| - platform: "ubuntu-22.04" | |
| args: "--target x86_64-unknown-linux-gnu" | |
| target: "x86_64-unknown-linux-gnu" | |
| build_name: "linux-x64" | |
| - platform: "windows-latest" | |
| args: "--target x86_64-pc-windows-msvc" | |
| target: "x86_64-pc-windows-msvc" | |
| build_name: "windows-x64" | |
| runs-on: ${{ matrix.platform }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.prepare-release.outputs.tag }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: lts/* | |
| - name: Setup pnpm | |
| uses: pnpm/action-setup@v4 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - name: Install system dependencies (Ubuntu) | |
| if: matrix.platform == 'ubuntu-22.04' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libwebkit2gtk-4.1-dev \ | |
| libappindicator3-dev \ | |
| librsvg2-dev \ | |
| patchelf \ | |
| curl \ | |
| wget \ | |
| unzip \ | |
| libfuse2 \ | |
| file \ | |
| libglib2.0-dev | |
| # Configure AppImage environment for CI | |
| # AppImages need FUSE but GitHub Actions doesn't have it by default | |
| # We'll extract the AppImage instead of running it with FUSE | |
| echo "APPIMAGE_EXTRACT_AND_RUN=1" >> $GITHUB_ENV | |
| - name: Install create-dmg (macOS) | |
| if: matrix.platform == 'macos-latest' | |
| run: | | |
| brew install create-dmg | |
| - name: Import Apple Code Signing Certificate | |
| if: matrix.platform == 'macos-latest' | |
| run: | | |
| # Create keychain | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain | |
| security default-keychain -s build.keychain | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain | |
| security set-keychain-settings -t 3600 -u build.keychain | |
| # Import certificate | |
| echo "$APPLE_CERTIFICATE" | base64 --decode > certificate.p12 | |
| security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain | |
| # Clean up | |
| rm certificate.p12 | |
| env: | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| shell: bash | |
| run: | | |
| echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT | |
| - name: Cache pnpm dependencies | |
| if: matrix.platform != 'windows-latest' | |
| uses: actions/cache@v4 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: | | |
| ${{ runner.os }}-pnpm-store- | |
| - name: Cache Rust dependencies | |
| if: matrix.platform != 'windows-latest' | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| ~/.cargo/registry | |
| ~/.cargo/git | |
| key: ${{ runner.os }}-${{ matrix.target }}-cargo-${{ hashFiles('**/Cargo.lock') }} | |
| restore-keys: | | |
| ${{ runner.os }}-${{ matrix.target }}-cargo- | |
| ${{ runner.os }}-cargo- | |
| - name: Cache JRE for Tabula | |
| uses: actions/cache@v4 | |
| with: | |
| path: | | |
| src-tauri/resources/build-jre | |
| key: ${{ runner.os }}-${{ matrix.platform == 'macos-latest' && 'macos-universal' || matrix.target }}-jre-v4-${{ hashFiles('src-tauri/scripts/setup-build-jre.sh') }} | |
| restore-keys: | | |
| ${{ runner.os }}-${{ matrix.platform == 'macos-latest' && 'macos-universal' || matrix.target }}-jre-v4- | |
| - name: Install frontend dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Setup JRE for Tabula | |
| shell: bash | |
| run: | | |
| # For macOS, download both architectures since we build universal | |
| if [[ "${{ matrix.platform }}" == "macos-latest" ]]; then | |
| echo "📦 Setting up JREs for macOS (both architectures)..." | |
| # Download ARM64 JRE | |
| echo "⬇️ Downloading ARM64 JRE..." | |
| TARGET_PLATFORM=jre-macos-arm64 bash src-tauri/scripts/setup-build-jre.sh | |
| # Download x86_64 JRE | |
| echo "⬇️ Downloading x86_64 JRE..." | |
| TARGET_PLATFORM=jre-macos-x64 bash src-tauri/scripts/setup-build-jre.sh | |
| echo "✅ Both JREs downloaded successfully" | |
| else | |
| # For other platforms, use default detection | |
| # The script will automatically configure Linux JRE for AppImage bundling | |
| bash src-tauri/scripts/setup-build-jre.sh | |
| fi | |
| - name: Verify JRE Setup | |
| shell: bash | |
| run: | | |
| echo "🔍 Verifying JRE setup..." | |
| if [[ "${{ matrix.platform }}" == "windows-latest" ]]; then | |
| JRE_DIR="src-tauri/resources/build-jre/jre-windows-x64" | |
| JAVA_EXE="$JRE_DIR/bin/java.exe" | |
| elif [[ "${{ matrix.platform }}" == "ubuntu-22.04" ]]; then | |
| JRE_DIR="src-tauri/resources/build-jre/jre-linux-x64" | |
| JAVA_EXE="$JRE_DIR/bin/java" | |
| elif [[ "${{ matrix.platform }}" == "macos-latest" ]]; then | |
| # Check both macOS JREs | |
| for arch in arm64 x64; do | |
| JRE_DIR="src-tauri/resources/build-jre/jre-macos-$arch" | |
| if [ -d "$JRE_DIR" ]; then | |
| echo "✅ Found macOS $arch JRE at: $JRE_DIR" | |
| echo " Size: $(du -sh "$JRE_DIR" | cut -f1)" | |
| if [ -f "$JRE_DIR/bin/java" ]; then | |
| echo "✅ java binary exists" | |
| "$JRE_DIR/bin/java" -version 2>&1 || echo "⚠️ Could not run java" | |
| else | |
| echo "❌ java binary NOT found at: $JRE_DIR/bin/java" | |
| fi | |
| echo "" | |
| else | |
| echo "❌ macOS $arch JRE NOT found at: $JRE_DIR" | |
| fi | |
| done | |
| exit 0 | |
| fi | |
| echo "Checking JRE directory: $JRE_DIR" | |
| if [ -d "$JRE_DIR" ]; then | |
| echo "✅ JRE directory exists" | |
| echo " Size: $(du -sh "$JRE_DIR" | cut -f1)" | |
| echo "" | |
| if [ -d "$JRE_DIR/bin" ]; then | |
| echo "✅ bin directory exists" | |
| echo " Contents of bin directory (first 20 files):" | |
| ls -lh "$JRE_DIR/bin" | head -20 | |
| else | |
| echo "❌ bin directory NOT found" | |
| echo " Contents of JRE directory:" | |
| ls -lh "$JRE_DIR" | |
| fi | |
| echo "" | |
| if [ -f "$JAVA_EXE" ]; then | |
| echo "✅ Java executable exists at: $JAVA_EXE" | |
| echo " Testing Java execution:" | |
| "$JAVA_EXE" -version 2>&1 || echo "⚠️ Java execution failed" | |
| else | |
| echo "❌ Java executable NOT found at: $JAVA_EXE" | |
| fi | |
| else | |
| echo "❌ JRE directory NOT found: $JRE_DIR" | |
| echo "" | |
| echo "Contents of build-jre:" | |
| ls -lh src-tauri/resources/build-jre/ || echo "build-jre directory doesn't exist" | |
| fi | |
| - name: Setup AppImage build tools (Ubuntu) | |
| if: matrix.platform == 'ubuntu-22.04' | |
| shell: bash | |
| run: | | |
| echo "🔧 Setting up AppImage build tools for CI environment..." | |
| # Create tools directory | |
| mkdir -p ~/.local/bin | |
| export PATH="$HOME/.local/bin:$PATH" | |
| # Pre-download and extract linuxdeploy (so it doesn't need FUSE) | |
| echo "⬇️ Downloading linuxdeploy..." | |
| cd ~/.local/bin | |
| # Download linuxdeploy | |
| wget -q https://github.com/tauri-apps/binary-releases/releases/download/linuxdeploy/linuxdeploy-x86_64.AppImage | |
| chmod +x linuxdeploy-x86_64.AppImage | |
| # Extract it so it doesn't need FUSE | |
| ./linuxdeploy-x86_64.AppImage --appimage-extract >/dev/null 2>&1 || true | |
| if [ -d "squashfs-root" ]; then | |
| mv squashfs-root linuxdeploy-extracted | |
| # Create a wrapper script | |
| cat > linuxdeploy-x86_64.AppImage << 'EOF' | |
| #!/bin/bash | |
| DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| exec "$DIR/linuxdeploy-extracted/AppRun" "$@" | |
| EOF | |
| chmod +x linuxdeploy-x86_64.AppImage | |
| echo "✅ linuxdeploy extracted and ready" | |
| fi | |
| # Download linuxdeploy-plugin-appimage | |
| echo "⬇️ Downloading linuxdeploy-plugin-appimage..." | |
| wget -q https://github.com/linuxdeploy/linuxdeploy-plugin-appimage/releases/download/continuous/linuxdeploy-plugin-appimage-x86_64.AppImage | |
| chmod +x linuxdeploy-plugin-appimage-x86_64.AppImage | |
| # Extract it | |
| ./linuxdeploy-plugin-appimage-x86_64.AppImage --appimage-extract >/dev/null 2>&1 || true | |
| if [ -d "squashfs-root" ]; then | |
| mv squashfs-root linuxdeploy-plugin-appimage-extracted | |
| # Create a wrapper script | |
| cat > linuxdeploy-plugin-appimage-x86_64.AppImage << 'EOF' | |
| #!/bin/bash | |
| DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" | |
| export APPIMAGE_EXTRACT_AND_RUN=1 | |
| exec "$DIR/linuxdeploy-plugin-appimage-extracted/AppRun" "$@" | |
| EOF | |
| chmod +x linuxdeploy-plugin-appimage-x86_64.AppImage | |
| echo "✅ linuxdeploy-plugin-appimage extracted and ready" | |
| fi | |
| # Download GTK and GStreamer plugins (these are shell scripts, not AppImages) | |
| echo "⬇️ Downloading GTK plugin..." | |
| wget -q https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gtk/master/linuxdeploy-plugin-gtk.sh | |
| chmod +x linuxdeploy-plugin-gtk.sh | |
| echo "⬇️ Downloading GStreamer plugin..." | |
| wget -q https://raw.githubusercontent.com/tauri-apps/linuxdeploy-plugin-gstreamer/master/linuxdeploy-plugin-gstreamer.sh | |
| chmod +x linuxdeploy-plugin-gstreamer.sh | |
| echo "✅ All plugins downloaded" | |
| # Add to PATH for the rest of the workflow | |
| echo "$HOME/.local/bin" >> $GITHUB_PATH | |
| # Return to workspace | |
| cd "$GITHUB_WORKSPACE" | |
| # Verify JRE is set up | |
| echo "" | |
| echo "🔍 Verifying build environment..." | |
| if [ -d "src-tauri/resources/build-jre" ]; then | |
| echo "✅ JRE directory exists" | |
| echo "JRE size: $(du -sh src-tauri/resources/build-jre | cut -f1)" | |
| else | |
| echo "⚠️ JRE directory not found" | |
| fi | |
| # List what's in PATH | |
| echo "" | |
| echo "Tools in ~/.local/bin:" | |
| ls -lh ~/.local/bin/ | grep -E "linuxdeploy|AppImage" || echo "No tools found" | |
| echo "✅ AppImage build tools setup complete" | |
| - name: Extract changelog for release | |
| id: changelog | |
| shell: bash | |
| continue-on-error: false | |
| run: | | |
| set +e # Disable exit on error for this script | |
| VERSION="${{ needs.prepare-release.outputs.version }}" | |
| if [ -f "CHANGELOG.md" ]; then | |
| # Extract the changelog section for the current version | |
| CHANGELOG_CONTENT=$(awk -v version="$VERSION" ' | |
| /^## / { | |
| if (found) exit | |
| if ($0 ~ "## .*" version) found=1 | |
| next | |
| } | |
| found && /^## / { exit } | |
| found { print } | |
| ' CHANGELOG.md | sed '/^$/d' | head -30) | |
| # Get previous version for comparison link | |
| PREVIOUS_VERSION=$(awk '/^## / && !/'"$VERSION"'/ { print $2; exit }' CHANGELOG.md) | |
| if [ -n "$CHANGELOG_CONTENT" ]; then | |
| # Analyze change types - handle both "feat:" and "hash: feat:" formats | |
| FEATURES=$(echo "$CHANGELOG_CONTENT" | grep -E "(^- [a-f0-9]+: )?feat:" | wc -l | tr -d ' ') | |
| FIXES=$(echo "$CHANGELOG_CONTENT" | grep -E "(^- [a-f0-9]+: )?fix:" | wc -l | tr -d ' ') | |
| BREAKING=$(echo "$CHANGELOG_CONTENT" | grep -E "BREAKING CHANGE|!" | wc -l | tr -d ' ') | |
| DOCS=$(echo "$CHANGELOG_CONTENT" | grep -E "(^- [a-f0-9]+: )?docs:" | wc -l | tr -d ' ') | |
| PERF=$(echo "$CHANGELOG_CONTENT" | grep -E "(^- [a-f0-9]+: )?perf:" | wc -l | tr -d ' ') | |
| REFACTOR=$(echo "$CHANGELOG_CONTENT" | grep -E "(^- [a-f0-9]+: )?refactor:" | wc -l | tr -d ' ') | |
| CHORE=$(echo "$CHANGELOG_CONTENT" | grep -E "(^- [a-f0-9]+: )?chore:" | wc -l | tr -d ' ') | |
| SECURITY=$(echo "$CHANGELOG_CONTENT" | grep -i "security\|vulnerability\|cve\|exploit" | wc -l | tr -d ' ') | |
| # Count total changes | |
| TOTAL_CHANGES=$(echo "$CHANGELOG_CONTENT" | grep "^-" | wc -l | tr -d ' ') | |
| FORMATTED_CHANGELOG="$CHANGELOG_CONTENT" | |
| # Sanitize: remove all control characters except newline, escape backslashes and quotes | |
| FORMATTED_CHANGELOG=$(printf '%s' "$FORMATTED_CHANGELOG" | LC_ALL=C tr -d '\000-\011\013-\037\177' | sed 's/\\/\\\\/g' | sed 's/"/\\"/g') | |
| echo "changelog<<EOF" >> $GITHUB_OUTPUT | |
| printf '%s\n' "$FORMATTED_CHANGELOG" >> $GITHUB_OUTPUT | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| # Output change statistics | |
| echo "features=$FEATURES" >> $GITHUB_OUTPUT | |
| echo "fixes=$FIXES" >> $GITHUB_OUTPUT | |
| echo "breaking=$BREAKING" >> $GITHUB_OUTPUT | |
| echo "docs=$DOCS" >> $GITHUB_OUTPUT | |
| echo "perf=$PERF" >> $GITHUB_OUTPUT | |
| echo "refactor=$REFACTOR" >> $GITHUB_OUTPUT | |
| echo "security=$SECURITY" >> $GITHUB_OUTPUT | |
| echo "total_changes=$TOTAL_CHANGES" >> $GITHUB_OUTPUT | |
| else | |
| echo "changelog=See CHANGELOG.md for details." >> $GITHUB_OUTPUT | |
| echo "features=0" >> $GITHUB_OUTPUT | |
| echo "fixes=0" >> $GITHUB_OUTPUT | |
| echo "breaking=0" >> $GITHUB_OUTPUT | |
| echo "security=0" >> $GITHUB_OUTPUT | |
| echo "total_changes=0" >> $GITHUB_OUTPUT | |
| fi | |
| echo "previous_version=$PREVIOUS_VERSION" >> $GITHUB_OUTPUT | |
| else | |
| echo "changelog=No changelog available." >> $GITHUB_OUTPUT | |
| echo "previous_version=0.0.2" >> $GITHUB_OUTPUT | |
| echo "features=0" >> $GITHUB_OUTPUT | |
| echo "fixes=0" >> $GITHUB_OUTPUT | |
| echo "breaking=0" >> $GITHUB_OUTPUT | |
| echo "security=0" >> $GITHUB_OUTPUT | |
| echo "total_changes=0" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Generate build timestamp | |
| id: timestamp | |
| run: echo "build_time=$(date -u +"%Y-%m-%d %H:%M UTC")" >> $GITHUB_OUTPUT | |
| - name: Build Tauri app | |
| id: tauri-build | |
| continue-on-error: true | |
| shell: bash | |
| run: | | |
| echo "🔨 Building Tauri app for ${{ matrix.build_name }}..." | |
| # Clean previous build artifacts to avoid version conflicts | |
| echo "🧹 Cleaning previous build artifacts..." | |
| rm -rf dist src-tauri/target/release/bundle | |
| echo "✅ Build directory cleaned" | |
| # Configure LD_LIBRARY_PATH for Linux to help linuxdeploy find JRE libraries | |
| if [[ "${{ matrix.platform }}" == "ubuntu-22.04" ]]; then | |
| JRE_DIRS=$(find src-tauri/resources/build-jre -type d -name "lib" 2>/dev/null || echo "") | |
| if [ -n "$JRE_DIRS" ]; then | |
| for JRE_LIB in $JRE_DIRS; do | |
| if [ -d "$JRE_LIB/server" ]; then | |
| export LD_LIBRARY_PATH="$PWD/$JRE_LIB/server:${LD_LIBRARY_PATH}" | |
| fi | |
| export LD_LIBRARY_PATH="$PWD/$JRE_LIB:${LD_LIBRARY_PATH}" | |
| done | |
| fi | |
| fi | |
| # Build with retry logic | |
| set +e | |
| pnpm tauri build ${{ matrix.args }} --verbose | |
| BUILD_EXIT_CODE=$? | |
| set -e | |
| if [[ $BUILD_EXIT_CODE -ne 0 ]]; then | |
| echo "❌ First build attempt failed (exit code: $BUILD_EXIT_CODE)" | |
| echo "🔄 Attempting clean build..." | |
| # Clean and retry | |
| rm -rf dist src-tauri/target/release | |
| pnpm tauri build ${{ matrix.args }} --verbose | |
| BUILD_EXIT_CODE=$? | |
| if [[ $BUILD_EXIT_CODE -ne 0 ]]; then | |
| echo "❌ Build failed after retry (exit code: $BUILD_EXIT_CODE)" | |
| echo "build_success=false" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| fi | |
| echo "✅ Tauri build completed successfully" | |
| echo "build_success=true" >> $GITHUB_OUTPUT | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| TAURI_BUNDLE_MACOS_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} | |
| CI: true | |
| - name: Create GitHub Release | |
| if: steps.tauri-build.outputs.build_success == 'true' | |
| uses: softprops/action-gh-release@v1 | |
| with: | |
| tag_name: ${{ needs.prepare-release.outputs.tag }} | |
| name: "mpesa2csv ${{ needs.prepare-release.outputs.tag }}" | |
| body: | | |
| ## What's New in v${{ needs.prepare-release.outputs.version }} | |
| ${{ steps.changelog.outputs.total_changes > 0 && format('📊 **Release Summary**: {0} changes', steps.changelog.outputs.total_changes) || '' }} | |
| ${{ steps.changelog.outputs.breaking > 0 && '> ⚠️ **BREAKING CHANGES**: This release contains breaking changes. Please review the changelog carefully before updating.' || '' }} | |
| ${{ steps.changelog.outputs.security > 0 && '> 🔒 **SECURITY UPDATE**: This release includes important security fixes. We recommend updating as soon as possible.' || '' }} | |
| ${{ steps.changelog.outputs.changelog }} | |
| ${{ steps.changelog.outputs.features > 0 && steps.changelog.outputs.fixes > 0 && format('🎯 **Highlights**: This release brings {0} new features and fixes {1} bugs for a better user experience.', steps.changelog.outputs.features, steps.changelog.outputs.fixes) || '' }} | |
| ${{ steps.changelog.outputs.features > 0 && steps.changelog.outputs.fixes == 0 && format('✨ **Feature Release**: Introducing {0} new features to enhance your workflow.', steps.changelog.outputs.features) || '' }} | |
| ${{ steps.changelog.outputs.features == 0 && steps.changelog.outputs.fixes > 0 && format('🔧 **Maintenance Release**: This update focuses on stability with {0} bug fixes.', steps.changelog.outputs.fixes) || '' }} | |
| --- | |
| ## 📥 Download & Installation | |
| Choose the appropriate download for your platform: | |
| | Platform | Architecture | Download | Installation | | |
| |----------|-------------|----------|--------------| | |
| | **Windows** | x64 (Intel/AMD) | [📥 Download .exe](https://github.com/DavidAmunga/mpesa2csv/releases/download/${{ needs.prepare-release.outputs.tag }}/mpesa2csv_${{ needs.prepare-release.outputs.version }}_x64-setup.exe) | Run the installer and follow the setup wizard | | |
| | **macOS** | Apple Silicon (M1/M2/M3) | [📥 Download .dmg](https://github.com/DavidAmunga/mpesa2csv/releases/download/${{ needs.prepare-release.outputs.tag }}/mpesa2csv_${{ needs.prepare-release.outputs.version }}_aarch64.dmg) | Open DMG and drag to Applications | | |
| | **macOS** | Intel x64 | [📥 Download .dmg](https://github.com/DavidAmunga/mpesa2csv/releases/download/${{ needs.prepare-release.outputs.tag }}/mpesa2csv_${{ needs.prepare-release.outputs.version }}_x64.dmg) | Open DMG and drag to Applications | | |
| | **Linux** | x64 (Ubuntu/Debian) | [📥 Download .deb](https://github.com/DavidAmunga/mpesa2csv/releases/download/${{ needs.prepare-release.outputs.tag }}/mpesa2csv_${{ needs.prepare-release.outputs.version }}_amd64.deb) | `sudo dpkg -i mpesa2csv_*.deb` | | |
| | **Linux** | x64 (Portable) | [📥 Download .AppImage](https://github.com/DavidAmunga/mpesa2csv/releases/download/${{ needs.prepare-release.outputs.tag }}/mpesa2csv_${{ needs.prepare-release.outputs.version }}_amd64.AppImage) | `chmod +x mpesa2csv_*.AppImage && ./mpesa2csv_*.AppImage` | | |
| ### 📊 Release Metadata | |
| - **Version**: `${{ needs.prepare-release.outputs.version }}` | |
| - **Build Date**: `${{ steps.timestamp.outputs.build_time }}` | |
| - **Total Changes**: ${{ steps.changelog.outputs.total_changes }} | |
| - **Platforms**: Windows, macOS (Intel + Apple Silicon), Linux | |
| ${{ steps.changelog.outputs.breaking > 0 && format('- **Breaking Changes**: {0}', steps.changelog.outputs.breaking) || '' }} | |
| ### 🔍 System Requirements | |
| - **Windows**: Windows 10 version 1903 or later | |
| - **macOS**: macOS 10.15 Catalina or later | |
| - **Linux**: Modern distribution with GTK 3.24+ and WebKit2GTK 4.1+ | |
| ### 🔄 Auto-Update | |
| > **Note**: If you have a previous version installed, the app will automatically notify you when this update is available and guide you through the update process. | |
| --- | |
| **Full Changelog**: https://github.com/${{ github.repository }}/compare/v${{ steps.changelog.outputs.previous_version || '0.0.2' }}...v${{ needs.prepare-release.outputs.version }} | |
| draft: false | |
| prerelease: false | |
| files: | | |
| src-tauri/target/*/release/bundle/dmg/*.dmg | |
| src-tauri/target/*/release/bundle/deb/*.deb | |
| src-tauri/target/*/release/bundle/appimage/*.AppImage | |
| src-tauri/target/*/release/bundle/nsis/*.exe | |
| src-tauri/target/*/release/bundle/msi/*.msi | |
| src-tauri/target/*/release/bundle/macos/*.app.tar.gz | |
| src-tauri/target/*/release/bundle/appimage/*.AppImage.tar.gz | |
| src-tauri/target/*/release/bundle/nsis/*.nsis.zip | |
| src-tauri/target/*/release/bundle/**/*.sig | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Step 6: Generate and upload updater JSON | |
| generate-updater-json: | |
| name: "Generate Updater JSON" | |
| needs: | |
| [ | |
| check-release, | |
| pre-release-validation, | |
| prepare-release, | |
| build-and-release, | |
| ] | |
| if: needs.check-release.outputs.should_release == 'true' && needs.pre-release-validation.outputs.validation_passed == 'true' | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Generate and upload latest.json | |
| run: | | |
| echo "🔄 Generating latest.json for Tauri updater..." | |
| VERSION="${{ needs.prepare-release.outputs.version }}" | |
| TAG="${{ needs.prepare-release.outputs.tag }}" | |
| # Create the latest.json file with proper Tauri updater format | |
| # Try to get release notes from the GitHub API | |
| RELEASE_NOTES="Update to version $VERSION. See full release notes at https://github.com/${{ github.repository }}/releases/tag/$TAG" | |
| # Try to fetch release notes from GitHub API | |
| if command -v gh &> /dev/null; then | |
| GITHUB_NOTES=$(gh api repos/${{ github.repository }}/releases/latest --jq '.body' 2>/dev/null || echo "") | |
| if [ -n "$GITHUB_NOTES" ] && [ "$GITHUB_NOTES" != "null" ]; then | |
| RELEASE_NOTES=$(printf '%s' "$GITHUB_NOTES" | head -20 | sed 's/^## /## /g' | sed 's/^### /### /g' | LC_ALL=C tr -d '\000-\011\013-\037\177') | |
| fi | |
| fi | |
| # Wait for GitHub to process release assets | |
| sleep 10 | |
| # Download signature files from the release | |
| mkdir -p signatures | |
| for sig_file in "mpesa2csv.app.tar.gz.sig" "mpesa2csv_${VERSION}_amd64.AppImage.sig" "mpesa2csv_${VERSION}_x64-setup.exe.sig"; do | |
| gh release download "$TAG" -p "$sig_file" -D signatures 2>/dev/null || echo "" > "signatures/$sig_file" | |
| done | |
| # Read signature files | |
| # Tauri v2 uses updater bundle signatures, not installer signatures | |
| DARWIN_SIG=$(cat signatures/mpesa2csv.app.tar.gz.sig 2>/dev/null || echo "") | |
| LINUX_X86_64_SIG=$(cat signatures/mpesa2csv_${VERSION}_amd64.AppImage.sig 2>/dev/null || echo "") | |
| WINDOWS_X86_64_SIG=$(cat signatures/mpesa2csv_${VERSION}_x64-setup.exe.sig 2>/dev/null || echo "") | |
| # Use jq to properly create JSON with escaped strings | |
| # URLs point to updater bundles (.tar.gz, .zip), not installers | |
| jq -n \ | |
| --arg version "$VERSION" \ | |
| --arg notes "$RELEASE_NOTES" \ | |
| --arg pub_date "$(date -u +"%Y-%m-%dT%H:%M:%S.000Z")" \ | |
| --arg darwin_aarch64_url "https://github.com/${{ github.repository }}/releases/download/$TAG/mpesa2csv.app.tar.gz" \ | |
| --arg darwin_aarch64_sig "$DARWIN_SIG" \ | |
| --arg darwin_x86_64_url "https://github.com/${{ github.repository }}/releases/download/$TAG/mpesa2csv.app.tar.gz" \ | |
| --arg darwin_x86_64_sig "$DARWIN_SIG" \ | |
| --arg linux_x86_64_url "https://github.com/${{ github.repository }}/releases/download/$TAG/mpesa2csv_${VERSION}_amd64.AppImage" \ | |
| --arg linux_x86_64_sig "$LINUX_X86_64_SIG" \ | |
| --arg windows_x86_64_url "https://github.com/${{ github.repository }}/releases/download/$TAG/mpesa2csv_${VERSION}_x64-setup.exe" \ | |
| --arg windows_x86_64_sig "$WINDOWS_X86_64_SIG" \ | |
| '{ | |
| version: $version, | |
| notes: $notes, | |
| pub_date: $pub_date, | |
| platforms: { | |
| "darwin-aarch64": { | |
| signature: $darwin_aarch64_sig, | |
| url: $darwin_aarch64_url | |
| }, | |
| "darwin-x86_64": { | |
| signature: $darwin_x86_64_sig, | |
| url: $darwin_x86_64_url | |
| }, | |
| "linux-x86_64": { | |
| signature: $linux_x86_64_sig, | |
| url: $linux_x86_64_url | |
| }, | |
| "windows-x86_64": { | |
| signature: $windows_x86_64_sig, | |
| url: $windows_x86_64_url | |
| } | |
| } | |
| }' > latest.json | |
| # Upload to the release | |
| gh release upload "$TAG" latest.json --clobber | |
| echo "✅ latest.json uploaded" | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # Step 7: Create release branch for hotfixes | |
| create-release-branch: | |
| name: "Create Release Branch" | |
| needs: | |
| [ | |
| check-release, | |
| pre-release-validation, | |
| prepare-release, | |
| build-and-release, | |
| generate-updater-json, | |
| ] | |
| if: needs.check-release.outputs.should_release == 'true' && needs.pre-release-validation.outputs.validation_passed == 'true' | |
| uses: ./.github/workflows/reusable-create-release-branch.yml | |
| with: | |
| version: ${{ needs.prepare-release.outputs.tag }} | |
| max_branches: 5 | |
| secrets: inherit | |
| # Step 8: Notify about release completion | |
| notify-completion: | |
| name: "Release Complete" | |
| runs-on: ubuntu-latest | |
| needs: | |
| [ | |
| pre-release-validation, | |
| prepare-release, | |
| build-and-release, | |
| generate-updater-json, | |
| create-release-branch, | |
| ] | |
| if: always() && needs.prepare-release.result == 'success' | |
| steps: | |
| - name: Release summary | |
| run: | | |
| echo "🎉 Release ${{ needs.prepare-release.outputs.tag }} completed!" | |
| echo "✅ Desktop binaries built and published" | |
| echo "✅ Release branch created for hotfixes" | |
| echo "🔗 View release: https://github.com/${{ github.repository }}/releases/tag/${{ needs.prepare-release.outputs.tag }}" | |
| # Create a comprehensive release summary | |
| cat > release-summary.md << EOF | |
| # 🚀 Release ${{ needs.prepare-release.outputs.tag }} Summary | |
| ## ✅ Completed Tasks | |
| - [x] Pre-release validation passed | |
| - [x] Version files synced | |
| - [x] Git tag created: ${{ needs.prepare-release.outputs.tag }} | |
| - [x] Desktop binaries built for all platforms | |
| - [x] GitHub release created | |
| - [x] Updater JSON generated | |
| - [x] Release branch created for hotfixes | |
| ## 📊 Build Status | |
| - **Release Reason**: ${{ needs.check-release.outputs.release_reason }} | |
| - **Validation**: ✅ Passed | |
| - **Desktop Builds**: ✅ Success | |
| - **Release Creation**: ✅ Success | |
| ## 🔗 Links | |
| - **Release**: https://github.com/${{ github.repository }}/releases/tag/${{ needs.prepare-release.outputs.tag }} | |
| - **Release Branch**: release/${{ needs.prepare-release.outputs.version }} | |
| - **Updater JSON**: https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare-release.outputs.tag }}/latest.json | |
| ## 📱 Available Downloads | |
| - Windows x64: [Download .exe](https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare-release.outputs.tag }}/mpesa2csv_${{ needs.prepare-release.outputs.version }}_x64-setup.exe) | |
| - macOS Apple Silicon: [Download .dmg](https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare-release.outputs.tag }}/mpesa2csv_${{ needs.prepare-release.outputs.version }}_aarch64.dmg) | |
| - macOS Intel: [Download .dmg](https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare-release.outputs.tag }}/mpesa2csv_${{ needs.prepare-release.outputs.version }}_x64.dmg) | |
| - Linux x64: [Download .deb](https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare-release.outputs.tag }}/mpesa2csv_${{ needs.prepare-release.outputs.version }}_amd64.deb) | |
| - Linux Portable: [Download .AppImage](https://github.com/${{ github.repository }}/releases/download/${{ needs.prepare-release.outputs.tag }}/mpesa2csv_${{ needs.prepare-release.outputs.version }}_amd64.AppImage) | |
| --- | |
| **Release completed at**: $(date -u '+%Y-%m-%d %H:%M:%S UTC') | |
| EOF | |
| echo "📄 Release summary created" | |
| cat release-summary.md |