name: Release on: push: branches: - main - develop concurrency: group: ${{ github.workflow }}-${{ github.ref_name }} cancel-in-progress: true jobs: # Semantic versioning analysis (main only) semantic-release: if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest outputs: new_release_published: ${{ steps.semantic.outputs.new_release_published }} new_release_version: ${{ steps.semantic.outputs.new_release_version }} new_release_git_tag: ${{ steps.semantic.outputs.new_release_git_tag }} permissions: contents: write steps: - name: ๐Ÿ“ฅ Checkout code uses: actions/checkout@v5 with: fetch-depth: 0 - name: ๐Ÿ Set up Node.js uses: actions/setup-node@v6 with: node-version: '20' - name: ๐Ÿ“ฆ Install semantic-release run: | npm install -g semantic-release@22 \ @semantic-release/github@9 \ conventional-changelog-conventionalcommits@7 - name: ๐Ÿท๏ธ Semantic Release id: semantic env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Run semantic-release and capture output npx semantic-release --debug > release_output.log 2>&1 || true # Check if a release was created if grep -q "Published release" release_output.log; then echo "new_release_published=true" >> $GITHUB_OUTPUT # Extract version from the log VERSION=$(grep "Published release" release_output.log | sed -n 's/.*Published release \([0-9]\+\.[0-9]\+\.[0-9]\+\).*/\1/p') echo "new_release_version=$VERSION" >> $GITHUB_OUTPUT echo "new_release_git_tag=v$VERSION" >> $GITHUB_OUTPUT echo "โœ… Released version $VERSION" else echo "new_release_published=false" >> $GITHUB_OUTPUT echo "โ„น๏ธ No release created (no releasable changes found)" fi # Show the full log for debugging cat release_output.log # Build desktop apps for all platforms build-windows: runs-on: windows-latest needs: [semantic-release] if: always() && (needs.semantic-release.result == 'success' || needs.semantic-release.result == 'skipped') steps: - name: ๐Ÿ“ฅ Checkout code uses: actions/checkout@v5 - name: ๐Ÿ Set up Python uses: actions/setup-python@v6 with: python-version: '3.11' cache: pip cache-dependency-path: requirements.txt - name: ๐Ÿ“ Generate version file id: version shell: pwsh run: | if ("${{ github.ref }}" -eq "refs/heads/main" -and "${{ needs.semantic-release.outputs.new_release_published }}" -eq "true") { $version = "${{ needs.semantic-release.outputs.new_release_version }}" $channel = "stable" } else { $version = "dev-$($env:GITHUB_SHA.Substring(0,7))" $channel = "develop" } @" # Auto-generated at build time VERSION = "$version" RELEASE_CHANNEL = "$channel" "@ | Out-File -FilePath src/jarvis/_version.py -Encoding utf8 Write-Host "Generated version file with VERSION=$version, RELEASE_CHANNEL=$channel" echo "app_version=$version" >> $env:GITHUB_OUTPUT - name: ๐Ÿ“ฆ Install dependencies run: | python -m pip install --upgrade pip # Install requirements but skip heavy optional packages (PyTorch, etc.) # Filter out chatterbox-tts, mlx-whisper, and nvidia-* (CUDA libs are # downloaded by the installer on-demand, not bundled in the build) Get-Content requirements.txt | Where-Object { $_ -notmatch '^(chatterbox-tts|mlx-whisper|nvidia-)' } | Set-Content requirements-desktop.txt pip install -r requirements-desktop.txt pip install pyinstaller - name: ๐ŸŽจ Generate icons run: | python src/desktop_app/desktop_assets/generate_icons.py - name: ๐Ÿ”จ Build executable (onedir) run: | pyinstaller jarvis_desktop.spec - name: ๐Ÿ› ๏ธ Install Inno Setup run: | choco install innosetup -y - name: ๐Ÿ“ฆ Build Windows installer run: | & "C:\Program Files (x86)\Inno Setup 6\ISCC.exe" /DMyAppVersion="${{ steps.version.outputs.app_version }}" installer\windows\jarvis_setup.iss - name: ๐Ÿ“ฆ Package installer as Jarvis-Windows-x64.zip run: | # Rename installer to Jarvis.exe for backwards compatibility with old updaters Copy-Item dist\Jarvis-Setup-x64.exe dist\Jarvis.exe cd dist Compress-Archive -Path Jarvis.exe -DestinationPath Jarvis-Windows-x64.zip - name: ๐Ÿ“ค Upload Windows artifact uses: actions/upload-artifact@v7 with: name: Jarvis-Windows path: dist/Jarvis-Windows-x64.zip build-macos: runs-on: ${{ matrix.os }} needs: [semantic-release] if: always() && (needs.semantic-release.result == 'success' || needs.semantic-release.result == 'skipped') strategy: fail-fast: false matrix: include: - os: macos-latest # Apple Silicon (arm64) arch: arm64 - os: macos-15-intel # Intel (x64) arch: x64 steps: - name: ๐Ÿ“ฅ Checkout code uses: actions/checkout@v5 - name: ๐Ÿ Set up Python uses: actions/setup-python@v6 with: python-version: '3.11' cache: pip cache-dependency-path: requirements.txt - name: ๐Ÿ“ Generate version file run: | if [ "${{ github.ref }}" = "refs/heads/main" ] && [ "${{ needs.semantic-release.outputs.new_release_published }}" = "true" ]; then VERSION="${{ needs.semantic-release.outputs.new_release_version }}" CHANNEL="stable" else VERSION="dev-${GITHUB_SHA:0:7}" CHANNEL="develop" fi cat > src/jarvis/_version.py << EOF # Auto-generated at build time VERSION = "$VERSION" RELEASE_CHANNEL = "$CHANNEL" EOF echo "Generated version file with VERSION=$VERSION, RELEASE_CHANNEL=$CHANNEL" - name: ๐Ÿ“ฆ Install dependencies run: | python -m pip install --upgrade pip # Install requirements but skip heavy optional packages (PyTorch/Chatterbox) # MLX Whisper is only included on arm64 - it requires Apple Silicon if [ "${{ matrix.arch }}" = "arm64" ]; then grep -v -E '^chatterbox-tts' requirements.txt > requirements-desktop.txt else grep -v -E '^(chatterbox-tts|mlx-whisper)' requirements.txt > requirements-desktop.txt fi pip install -r requirements-desktop.txt pip install pyinstaller - name: ๐ŸŽจ Generate icons run: | python src/desktop_app/desktop_assets/generate_icons.py - name: ๐Ÿ”จ Build application run: | pyinstaller jarvis_desktop.spec # Note: Ad-hoc code signing is intentionally skipped # codesign --force --deep breaks Qt WebEngine's symlink structure # causing crashes when QWebEngineView is shown. # See: https://github.com/pyinstaller/pyinstaller/issues/6612 # Users can bypass Gatekeeper by right-clicking and selecting "Open" - name: ๐Ÿ“ฆ Package macOS build run: | cd dist # `ditto -c -k --keepParent` preserves the symlinks, xattrs, and # permissions that Qt/Qt WebEngine frameworks rely on. Plain # `zip -r` follows symlinks, producing a zip that extracts into a # bundle macOS refuses to launch ("Jarvis.app can't be opened"). ditto -c -k --keepParent Jarvis.app Jarvis-macOS-${{ matrix.arch }}.zip - name: ๐Ÿ“ค Upload macOS artifact uses: actions/upload-artifact@v7 with: name: Jarvis-macOS-${{ matrix.arch }} path: dist/Jarvis-macOS-${{ matrix.arch }}.zip build-linux: runs-on: ubuntu-latest needs: [semantic-release] if: always() && (needs.semantic-release.result == 'success' || needs.semantic-release.result == 'skipped') steps: - name: ๐Ÿงน Free up disk space run: | # Remove unnecessary large packages to free up disk space sudo rm -rf /usr/share/dotnet sudo rm -rf /usr/local/lib/android sudo rm -rf /opt/ghc sudo rm -rf /opt/hostedtoolcache/CodeQL sudo docker image prune --all --force df -h - name: ๐Ÿ“ฅ Checkout code uses: actions/checkout@v5 - name: ๐Ÿ Set up Python uses: actions/setup-python@v6 with: python-version: '3.11' cache: pip cache-dependency-path: requirements.txt - name: ๐Ÿ“ฆ Install system dependencies run: | sudo apt-get update sudo apt-get install -y libxcb-cursor0 libxkbcommon-x11-0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 portaudio19-dev binutils - name: ๐Ÿ“ Generate version file run: | if [ "${{ github.ref }}" = "refs/heads/main" ] && [ "${{ needs.semantic-release.outputs.new_release_published }}" = "true" ]; then VERSION="${{ needs.semantic-release.outputs.new_release_version }}" CHANNEL="stable" else VERSION="dev-${GITHUB_SHA:0:7}" CHANNEL="develop" fi cat > src/jarvis/_version.py << EOF # Auto-generated at build time VERSION = "$VERSION" RELEASE_CHANNEL = "$CHANNEL" EOF echo "Generated version file with VERSION=$VERSION, RELEASE_CHANNEL=$CHANNEL" - name: ๐Ÿ“ฆ Install Python dependencies run: | python -m pip install --upgrade pip # Install requirements but skip heavy optional packages (PyTorch, etc.) grep -v -E '^(chatterbox-tts|mlx-whisper)' requirements.txt > requirements-desktop.txt pip install -r requirements-desktop.txt pip install pyinstaller - name: ๐ŸŽจ Generate icons run: | python src/desktop_app/desktop_assets/generate_icons.py - name: ๐Ÿ”จ Build executable run: | pyinstaller jarvis_desktop.spec - name: ๐Ÿ“ฆ Package Linux build run: | cd dist # Package the Jarvis directory (not a single file anymore) tar -czf Jarvis-Linux-x64.tar.gz Jarvis/ - name: ๐Ÿ“ค Upload Linux artifact uses: actions/upload-artifact@v7 with: name: Jarvis-Linux path: dist/Jarvis-Linux-x64.tar.gz # Create versioned release (main only, if semantic-release published) release-main: needs: [semantic-release, build-windows, build-macos, build-linux] runs-on: ubuntu-latest # Run even if some builds failed - upload whatever succeeded if: always() && needs.semantic-release.result == 'success' && needs.semantic-release.outputs.new_release_published == 'true' permissions: contents: write steps: - name: ๐Ÿ“ฅ Download all artifacts uses: actions/download-artifact@v8 with: path: artifacts - name: ๐Ÿ“‹ List available artifacts run: | echo "Available artifacts:" find artifacts -type f \( -name "*.zip" -o -name "*.tar.gz" \) | sort - name: ๐Ÿ“Ž Attach binaries to release uses: softprops/action-gh-release@v3 with: tag_name: ${{ needs.semantic-release.outputs.new_release_git_tag }} # Use glob to upload only artifacts that exist files: | artifacts/**/*.zip artifacts/**/*.tar.gz fail_on_unmatched_files: false append_body: true body: | --- ### โšก Prerequisites - [Ollama](https://ollama.com/download) (all platforms) ### ๐Ÿ“ฆ Downloads | Platform | File | Notes | |----------|------|-------| | **Windows** | `Jarvis-Windows-x64.zip` | Extract โ†’ Run `Jarvis.exe` | | **macOS (Apple Silicon)** | `Jarvis-macOS-arm64.zip` | Extract โ†’ Move to Applications โ†’ Right-click โ†’ Open | | **macOS (Intel)** | `Jarvis-macOS-x64.zip` | Extract โ†’ Move to Applications โ†’ Right-click โ†’ Open | | **Linux** | `Jarvis-Linux-x64.tar.gz` | `tar -xzf` โ†’ Run `./Jarvis/Jarvis` | env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Create/update latest pre-release (develop only) release-develop: needs: [build-windows, build-macos, build-linux] runs-on: ubuntu-latest # Run even if some builds failed - upload whatever succeeded if: always() && github.ref == 'refs/heads/develop' permissions: contents: write steps: - name: ๐Ÿ“ฅ Checkout code uses: actions/checkout@v5 with: fetch-depth: 0 # Full history for changelog generation fetch-tags: true # Ensure all tags are fetched - name: ๐Ÿ“ Generate changelog from main id: changelog run: | # Get the latest tag on main (most recent stable release) LATEST_TAG=$(git describe --tags --abbrev=0 origin/main 2>/dev/null || echo "") if [ -z "$LATEST_TAG" ]; then echo "No tags found, using full develop history" COMPARE_REF="origin/main" SINCE_TEXT="main branch" else COMPARE_REF="$LATEST_TAG" SINCE_TEXT="$LATEST_TAG" fi echo "Generating changelog comparing to: $COMPARE_REF" # Generate changelog grouped by type { echo "CHANGELOG</dev/null || true) if [ -n "$FEATURES" ]; then echo "### โœจ Features" echo "" echo "$FEATURES" echo "" fi # Bug fixes FIXES=$(git log "$COMPARE_REF"..HEAD --pretty=format:"* %s ([%h](https://github.com/${{ github.repository }}/commit/%H))" --grep="^fix" --regexp-ignore-case 2>/dev/null || true) if [ -n "$FIXES" ]; then echo "### ๐Ÿ› Bug Fixes" echo "" echo "$FIXES" echo "" fi # Refactoring REFACTOR=$(git log "$COMPARE_REF"..HEAD --pretty=format:"* %s ([%h](https://github.com/${{ github.repository }}/commit/%H))" --grep="^refactor" --regexp-ignore-case 2>/dev/null || true) if [ -n "$REFACTOR" ]; then echo "### โ™ป๏ธ Code Refactoring" echo "" echo "$REFACTOR" echo "" fi # Documentation DOCS=$(git log "$COMPARE_REF"..HEAD --pretty=format:"* %s ([%h](https://github.com/${{ github.repository }}/commit/%H))" --grep="^docs" --regexp-ignore-case 2>/dev/null || true) if [ -n "$DOCS" ]; then echo "### ๐Ÿ“ Documentation" echo "" echo "$DOCS" echo "" fi # Other changes (chore, style, test, etc.) # Get all commits, then exclude the ones we already captured OTHER=$(git log "$COMPARE_REF"..HEAD --pretty=format:"%s|%h|%H" 2>/dev/null | grep -v -i -E "^(feat|fix|refactor|docs)" | while IFS='|' read -r subject short full; do if [ -n "$subject" ]; then echo "* $subject ([$short](https://github.com/${{ github.repository }}/commit/$full))" fi done || true) if [ -n "$OTHER" ]; then echo "### ๐Ÿ”ง Other Changes" echo "" echo "$OTHER" echo "" fi echo "CHANGELOG_EOF" } >> $GITHUB_OUTPUT - name: ๐Ÿ“ฅ Download all artifacts uses: actions/download-artifact@v8 with: path: artifacts - name: ๐Ÿ“‹ List available artifacts run: | echo "Available artifacts:" find artifacts -type f \( -name "*.zip" -o -name "*.tar.gz" \) | sort - name: ๐Ÿ“ Create/Update Latest Release uses: softprops/action-gh-release@v3 with: tag_name: latest name: Latest Development Build # Use glob to upload only artifacts that exist files: | artifacts/**/*.zip artifacts/**/*.tar.gz fail_on_unmatched_files: false draft: false prerelease: true body: | ๐Ÿš€ **Latest development build from develop branch** This is an automated build from the latest commit on develop. These builds may be unstable. For stable releases, use versioned releases. --- ${{ steps.changelog.outputs.CHANGELOG }} --- ### โšก Prerequisites - [Ollama](https://ollama.com/download) (all platforms) ### ๐Ÿ“ฆ Downloads | Platform | File | Notes | |----------|------|-------| | **Windows** | `Jarvis-Windows-x64.zip` | Extract โ†’ Run `Jarvis.exe` | | **macOS (Apple Silicon)** | `Jarvis-macOS-arm64.zip` | Extract โ†’ Move to Applications โ†’ Right-click โ†’ Open | | **macOS (Intel)** | `Jarvis-macOS-x64.zip` | Extract โ†’ Move to Applications โ†’ Right-click โ†’ Open | | **Linux** | `Jarvis-Linux-x64.tar.gz` | `tar -xzf` โ†’ Run `./Jarvis/Jarvis` | **Branch**: develop **Commit**: ${{ github.sha }} **Date**: ${{ github.event.head_commit.timestamp }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}