feat: implement offline preview feature and update UI components #3
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
| # Ship portable binaries (Windows .exe portable, macOS .dmg, Linux AppImage) on each push to main. | |
| # Release version is the next MINOR (MAJOR.(MINOR+1).0) from the latest Release tag or from package.json as baseline. | |
| name: Release Electron builds | |
| on: | |
| push: | |
| branches: | |
| - main | |
| permissions: | |
| contents: write | |
| concurrency: | |
| group: electron-release-${{ github.repository }} | |
| cancel-in-progress: false | |
| env: | |
| # Desktop app renders via Electron/Chromium at runtime; skip huge Playwright browser download in CI install | |
| PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' | |
| jobs: | |
| resolve-version: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| release_version: ${{ steps.calc.outputs.release_version }} | |
| tag_v: ${{ steps.calc.outputs.tag_v }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Compute next MINOR version | |
| id: calc | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| REPO="${{ github.repository }}" | |
| # Latest published release (REST /releases/latest ignores drafts/prereleases in many cases — see GitHub API docs) | |
| LATEST_TAG="" | |
| RESP=$(gh api "repos/${REPO}/releases/latest" 2>/dev/null || true) | |
| if printf '%s' "$RESP" | jq -e .tag_name >/dev/null 2>&1; then | |
| LATEST_TAG="$(printf '%s' "$RESP" | jq -r .tag_name)" | |
| fi | |
| if [[ -n "$LATEST_TAG" && "$LATEST_TAG" != "null" ]]; then | |
| VERSION="${LATEST_TAG#v}" | |
| else | |
| VERSION="$(jq -r .version package.json)" | |
| fi | |
| # bash read splits on IFS=. — MINOR +1 and PATCH resets to 0 (e.g. 2.3.8 → 2.4.0) | |
| old_IFS="$IFS" | |
| IFS=. | |
| read -r MAJOR MINOR PATCH <<<"${VERSION}" | |
| IFS="$old_IFS" | |
| NEXT="${MAJOR}.$((10#${MINOR} + 1)).0" | |
| printf 'release_version=%s\n' "$NEXT" >> "$GITHUB_OUTPUT" | |
| printf 'tag_v=v%s\n' "$NEXT" >> "$GITHUB_OUTPUT" | |
| electron-build: | |
| needs: resolve-version | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - os: windows-latest | |
| artifact: dist-win | |
| eb_flag: --win | |
| - os: macos-latest | |
| artifact: dist-mac | |
| eb_flag: --mac | |
| - os: ubuntu-latest | |
| artifact: dist-linux | |
| eb_flag: --linux | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| cache: npm | |
| - name: Linux dependencies for AppImage (FUSE) | |
| if: runner.os == 'Linux' | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libfuse2 | |
| - run: npm ci | |
| - name: Align package.json version with release | |
| shell: bash | |
| run: | | |
| node -e " | |
| const fs=require('fs'); | |
| const p=JSON.parse(fs.readFileSync('package.json','utf8')); | |
| p.version='${{ needs.resolve-version.outputs.release_version }}'; | |
| fs.writeFileSync('package.json', JSON.stringify(p,null,2)+'\n'); | |
| " | |
| - name: Package frontend + Electron (${{ matrix.artifact }}) | |
| run: npx vite build && npx electron-builder ${{ matrix.eb_flag }} --publish never | |
| - name: Stage shipping binaries only (skip unpacked dirs) | |
| shell: bash | |
| env: | |
| TARGET: ${{ matrix.eb_flag }} | |
| run: | | |
| set -euo pipefail | |
| mkdir -p upload | |
| case "${TARGET}" in | |
| --win) | |
| find dist-electron -maxdepth 1 -type f \( -iname '*.exe' \) -exec cp {} upload/ \; | |
| ;; | |
| --mac) | |
| find dist-electron -maxdepth 1 -type f \( -iname '*.dmg' \) -exec cp {} upload/ \; | |
| ;; | |
| --linux) | |
| find dist-electron -maxdepth 1 -type f \( -iname '*.AppImage' \) -exec cp {} upload/ \; | |
| ;; | |
| *) | |
| echo "Unknown TARGET=${TARGET}" | |
| exit 1 | |
| ;; | |
| esac | |
| if [[ -z "$(ls -A upload)" ]]; then | |
| echo "No shipping binaries found under dist-electron" | |
| ls -la dist-electron || true | |
| exit 1 | |
| fi | |
| ls -la upload | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ matrix.artifact }} | |
| path: upload/ | |
| release: | |
| needs: | |
| - resolve-version | |
| - electron-build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ github.sha }} | |
| - name: Download desktop artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: dist-* | |
| merge-multiple: true | |
| path: release-assets | |
| - name: Add source archive (tracked files at this commit) | |
| run: | | |
| set -euo pipefail | |
| VER="${{ needs.resolve-version.outputs.release_version }}" | |
| OUT="release-assets/AnyDownload-${VER}-source.zip" | |
| git archive --format=zip --output="$OUT" HEAD | |
| ls -la release-assets | |
| - uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.resolve-version.outputs.tag_v }} | |
| target_commitish: ${{ github.sha }} | |
| files: release-assets/* | |
| fail_on_unmatched_files: true | |
| generate_release_notes: true | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |