chore: bump version to 0.4.7 #43
Workflow file for this run
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: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: 0 | |
| TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }} | |
| TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }} | |
| jobs: | |
| # Create the draft release before platform builds so parallel jobs can upload without racing | |
| # ("release not found" when Linux finished before macOS ran gh release create). | |
| prepare-release: | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Create draft GitHub release (if missing) | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(python3 -c "import json; print(json.load(open('apps/desktop/src-tauri/tauri.conf.json'))['version'])") | |
| if gh release view "v${VERSION}" --repo "$GITHUB_REPOSITORY" &>/dev/null; then | |
| echo "Release v${VERSION} already exists; skipping create." | |
| else | |
| gh release create "v${VERSION}" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --title "Max Video Player v${VERSION}" \ | |
| --notes "See the [CHANGELOG](https://github.com/MaxMB15/MaxVideoPlayer/blob/main/CHANGELOG.md) for details." \ | |
| --draft | |
| fi | |
| release-macos: | |
| needs: [prepare-release] | |
| runs-on: macos-latest | |
| env: | |
| CARGO_HOME: /Users/runner/.cargo | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Rust cache | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: ". -> target" | |
| cache-on-failure: true | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| cache: npm | |
| - name: Install frontend deps | |
| run: npm ci | |
| - name: Install system deps | |
| run: | | |
| # ffmpeg@7 required: mpv 0.40.0 uses FF_PROFILE_* macros removed in ffmpeg 8.x | |
| brew install meson ninja pkg-config ffmpeg@7 libass libplacebo dylibbundler | |
| # Make ffmpeg@7 visible to pkg-config (it is keg-only, not linked by default) | |
| echo "PKG_CONFIG_PATH=$(brew --prefix ffmpeg@7)/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" >> $GITHUB_ENV | |
| - name: Build libmpv | |
| run: ./scripts/build-libmpv.sh macos | |
| - name: Prepare signing key | |
| run: | | |
| # Tauri CLI requires the raw minisign format (starting with "untrusted comment:"). | |
| # The secret may be stored as: | |
| # (a) base64-encoded raw key, or | |
| # (b) raw key with actual newlines, or | |
| # (c) raw key with escaped \n (two chars) instead of real newlines. | |
| # We normalize to raw format and always write to GITHUB_ENV so the key | |
| # is guaranteed to have proper newlines when Tauri reads it. | |
| python3 - << 'EOF' | |
| import base64, os, sys | |
| key = os.environ.get("TAURI_SIGNING_PRIVATE_KEY", "") | |
| print(f"[signing] Key length: {len(key)}, has newlines: {'yes' if chr(10) in key else 'no'}") | |
| if not key.strip(): | |
| print("[signing] ERROR: no signing key set.") | |
| sys.exit(1) | |
| # Normalize literal \n (two chars) to actual newlines | |
| key = key.replace("\\n", "\n") | |
| def try_b64(s): | |
| try: | |
| return base64.b64decode(s.strip(), validate=True).decode("utf-8") | |
| except Exception: | |
| return None | |
| if "untrusted comment:" in key: | |
| final_key = key | |
| print("[signing] Raw minisign key detected.") | |
| else: | |
| # Try up to 2 levels of base64 decoding to handle both | |
| # single-encoded and accidentally double-encoded secrets. | |
| current = key | |
| final_key = None | |
| for depth in range(1, 3): | |
| decoded = try_b64(current) | |
| if decoded is None: | |
| print(f"[signing] ERROR: base64 decode failed at depth {depth}.") | |
| sys.exit(1) | |
| if "untrusted comment:" in decoded: | |
| final_key = decoded | |
| print(f"[signing] Key decoded at depth {depth}.") | |
| break | |
| current = decoded | |
| if final_key is None: | |
| print("[signing] ERROR: key is not a valid minisign private key.") | |
| sys.exit(1) | |
| with open(os.environ["GITHUB_ENV"], "a") as f: | |
| f.write("TAURI_SIGNING_PRIVATE_KEY<<__ENDKEY__\n") | |
| f.write(final_key.rstrip("\n") + "\n") | |
| f.write("__ENDKEY__\n") | |
| print("[signing] Key written to GITHUB_ENV.") | |
| EOF | |
| - name: Build Tauri app | |
| # Build without updater artifacts so bundle_dmg.sh doesn't race with | |
| # hdiutil over the .app bundle. We create the updater artifacts manually below. | |
| run: cd apps/desktop && npx tauri build --config '{"bundle":{"createUpdaterArtifacts":false}}' | |
| - name: Create updater artifacts | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(python3 -c "import json; print(json.load(open('apps/desktop/src-tauri/tauri.conf.json'))['version'])") | |
| echo "VERSION=$VERSION" >> "$GITHUB_ENV" | |
| BUNDLE_DIR="target/release/bundle/macos" | |
| UPDATER="$BUNDLE_DIR/MaxVideoPlayer.app.tar.gz" | |
| # Create the .tar.gz updater archive from the built .app | |
| cd "$BUNDLE_DIR" | |
| tar czf MaxVideoPlayer.app.tar.gz MaxVideoPlayer.app | |
| cd - | |
| # tauri signer sign -k always base64-decodes its argument (no raw-format | |
| # detection). Re-encode the raw key as base64 so Tauri can decode it. | |
| B64_KEY=$(python3 -c "import base64,os; print(base64.b64encode(os.environ['TAURI_SIGNING_PRIVATE_KEY'].encode()).decode())") | |
| if [[ -n "${TAURI_SIGNING_PRIVATE_KEY_PASSWORD:-}" ]]; then | |
| cd apps/desktop && npx tauri signer sign \ | |
| -k "$B64_KEY" \ | |
| -p "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" \ | |
| "../../$UPDATER" | |
| cd - | |
| else | |
| cd apps/desktop && npx tauri signer sign \ | |
| -k "$B64_KEY" \ | |
| --no-password \ | |
| "../../$UPDATER" | |
| cd - | |
| fi | |
| - name: Upload macOS updater signature | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: macos-updater-sig | |
| path: target/release/bundle/macos/MaxVideoPlayer.app.tar.gz.sig | |
| - name: Upload macOS assets to draft release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| VERSION="$VERSION" | |
| DMG="target/release/bundle/dmg/MaxVideoPlayer_${VERSION}_aarch64.dmg" | |
| UPDATER="target/release/bundle/macos/MaxVideoPlayer.app.tar.gz" | |
| UPDATER_VERSIONED="target/release/bundle/macos/MaxVideoPlayer_${VERSION}_aarch64.app.tar.gz" | |
| # Rename updater files so the download URL matches what latest.json expects | |
| cp "$UPDATER" "$UPDATER_VERSIONED" | |
| cp "${UPDATER}.sig" "${UPDATER_VERSIONED}.sig" | |
| gh release upload "v${VERSION}" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --clobber \ | |
| "$DMG" \ | |
| "$UPDATER_VERSIONED" \ | |
| "${UPDATER_VERSIONED}.sig" | |
| release-linux: | |
| needs: [prepare-release] | |
| runs-on: ubuntu-latest | |
| env: | |
| CARGO_HOME: /home/runner/.cargo | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install Rust | |
| uses: dtolnay/rust-toolchain@stable | |
| - name: Rust cache | |
| uses: Swatinem/rust-cache@v2 | |
| with: | |
| workspaces: ". -> target" | |
| cache-on-failure: true | |
| - name: Setup Node | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: 20 | |
| - name: Install frontend deps | |
| run: rm -f package-lock.json && npm install | |
| - name: Install system deps | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libgtk-3-dev libwebkit2gtk-4.1-dev \ | |
| libjavascriptcoregtk-4.1-dev libsoup-3.0-dev \ | |
| libayatana-appindicator3-dev \ | |
| libegl-dev libssl-dev pkg-config librsvg2-dev \ | |
| patchelf rpm \ | |
| meson ninja-build \ | |
| libavcodec-dev libavformat-dev libavutil-dev libswscale-dev libavfilter-dev \ | |
| libass-dev libpulse-dev libasound2-dev \ | |
| libplacebo-dev \ | |
| libx11-dev libxss-dev libxext-dev libxpresent-dev libxrandr-dev \ | |
| libwayland-dev | |
| - name: Build libmpv from source | |
| run: | | |
| ./scripts/build-libmpv.sh linux | |
| echo "LD_LIBRARY_PATH=$(pwd)/libs/linux:${LD_LIBRARY_PATH:-}" >> "$GITHUB_ENV" | |
| - name: Prepare signing key | |
| run: | | |
| python3 - << 'EOF' | |
| import base64, os, sys | |
| key = os.environ.get("TAURI_SIGNING_PRIVATE_KEY", "") | |
| if not key.strip(): | |
| print("[signing] ERROR: no signing key set.") | |
| sys.exit(1) | |
| key = key.replace("\\n", "\n") | |
| def try_b64(s): | |
| try: return base64.b64decode(s.strip(), validate=True).decode("utf-8") | |
| except Exception: return None | |
| if "untrusted comment:" in key: | |
| final_key = key | |
| else: | |
| current = key | |
| final_key = None | |
| for depth in range(1, 3): | |
| decoded = try_b64(current) | |
| if decoded is None: | |
| print(f"[signing] ERROR: base64 decode failed at depth {depth}.") | |
| sys.exit(1) | |
| if "untrusted comment:" in decoded: | |
| final_key = decoded | |
| break | |
| current = decoded | |
| if final_key is None: | |
| print("[signing] ERROR: key is not a valid minisign private key.") | |
| sys.exit(1) | |
| with open(os.environ["GITHUB_ENV"], "a") as f: | |
| f.write("TAURI_SIGNING_PRIVATE_KEY<<__ENDKEY__\n") | |
| f.write(final_key.rstrip("\n") + "\n") | |
| f.write("__ENDKEY__\n") | |
| print("[signing] Key written to GITHUB_ENV.") | |
| EOF | |
| - name: Build Tauri app | |
| run: cd apps/desktop && npx tauri build --config '{"bundle":{"createUpdaterArtifacts":false}}' | |
| - name: Create updater artifacts | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(python3 -c "import json; print(json.load(open('apps/desktop/src-tauri/tauri.conf.json'))['version'])") | |
| echo "VERSION=$VERSION" >> "$GITHUB_ENV" | |
| BUNDLE_DIR="target/release/bundle" | |
| APPIMAGE=$(find "$BUNDLE_DIR/appimage" -name "*.AppImage" | head -1) | |
| if [[ -n "$APPIMAGE" ]]; then | |
| UPDATER="$BUNDLE_DIR/MaxVideoPlayer_linux.tar.gz" | |
| tar czf "$UPDATER" -C "$(dirname "$APPIMAGE")" "$(basename "$APPIMAGE")" | |
| B64_KEY=$(python3 -c "import base64,os; print(base64.b64encode(os.environ['TAURI_SIGNING_PRIVATE_KEY'].encode()).decode())") | |
| if [[ -n "${TAURI_SIGNING_PRIVATE_KEY_PASSWORD:-}" ]]; then | |
| cd apps/desktop && npx tauri signer sign -k "$B64_KEY" -p "$TAURI_SIGNING_PRIVATE_KEY_PASSWORD" "../../$UPDATER" | |
| cd - | |
| else | |
| cd apps/desktop && npx tauri signer sign -k "$B64_KEY" --no-password "../../$UPDATER" | |
| cd - | |
| fi | |
| fi | |
| - name: Upload Linux updater signature | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: linux-updater-sig | |
| path: target/release/bundle/MaxVideoPlayer_linux.tar.gz.sig | |
| if-no-files-found: warn | |
| - name: Publish to GitHub release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| VERSION="$VERSION" | |
| BUNDLE_DIR="target/release/bundle" | |
| # Collect all Linux artifacts | |
| ARTIFACTS=() | |
| for deb in "$BUNDLE_DIR"/deb/*.deb; do | |
| [[ -f "$deb" ]] && ARTIFACTS+=("$deb") | |
| done | |
| for rpm in "$BUNDLE_DIR"/rpm/*.rpm; do | |
| [[ -f "$rpm" ]] && ARTIFACTS+=("$rpm") | |
| done | |
| for appimage in "$BUNDLE_DIR"/appimage/*.AppImage; do | |
| [[ -f "$appimage" ]] && ARTIFACTS+=("$appimage") | |
| done | |
| # Include updater tar.gz if it exists | |
| UPDATER="$BUNDLE_DIR/MaxVideoPlayer_linux.tar.gz" | |
| if [[ -f "$UPDATER" ]]; then | |
| ARTIFACTS+=("$UPDATER") | |
| [[ -f "${UPDATER}.sig" ]] && ARTIFACTS+=("${UPDATER}.sig") | |
| fi | |
| # Upload to draft release (created by prepare-release job before builds) | |
| if [[ ${#ARTIFACTS[@]} -eq 0 ]]; then | |
| echo "::error::No Linux artifacts found under $BUNDLE_DIR (deb/rpm/appimage)." | |
| exit 1 | |
| fi | |
| for artifact in "${ARTIFACTS[@]}"; do | |
| gh release upload "v${VERSION}" "$artifact" \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --clobber | |
| done | |
| # ── Publish latest.json (depends on both platform jobs) ─────────────── | |
| publish-latest-json: | |
| runs-on: ubuntu-latest | |
| needs: [release-macos, release-linux] | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Download updater signatures | |
| uses: actions/download-artifact@v4 | |
| with: | |
| path: sigs | |
| - name: Build and upload latest.json | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| set -euo pipefail | |
| VERSION=$(python3 -c "import json; print(json.load(open('apps/desktop/src-tauri/tauri.conf.json'))['version'])") | |
| MACOS_SIG="" | |
| if [[ -f sigs/macos-updater-sig/MaxVideoPlayer.app.tar.gz.sig ]]; then | |
| MACOS_SIG=$(cat sigs/macos-updater-sig/MaxVideoPlayer.app.tar.gz.sig) | |
| fi | |
| LINUX_SIG="" | |
| if [[ -f sigs/linux-updater-sig/MaxVideoPlayer_linux.tar.gz.sig ]]; then | |
| LINUX_SIG=$(cat sigs/linux-updater-sig/MaxVideoPlayer_linux.tar.gz.sig) | |
| fi | |
| python3 - "$VERSION" "$MACOS_SIG" "$LINUX_SIG" << 'PYEOF' | |
| import json, sys | |
| from datetime import datetime, timezone | |
| version, macos_sig, linux_sig = sys.argv[1], sys.argv[2], sys.argv[3] | |
| platforms = {} | |
| if macos_sig: | |
| platforms["darwin-aarch64"] = { | |
| "signature": macos_sig, | |
| "url": f"https://github.com/MaxMB15/MaxVideoPlayer/releases/download/v{version}/MaxVideoPlayer_{version}_aarch64.app.tar.gz" | |
| } | |
| if linux_sig: | |
| platforms["linux-x86_64"] = { | |
| "signature": linux_sig, | |
| "url": f"https://github.com/MaxMB15/MaxVideoPlayer/releases/download/v{version}/MaxVideoPlayer_linux.tar.gz" | |
| } | |
| base_url = f"https://github.com/MaxMB15/MaxVideoPlayer/releases/download/v{version}" | |
| packages = { | |
| "linux-deb-x86_64": f"{base_url}/MaxVideoPlayer_{version}_amd64.deb", | |
| "linux-rpm-x86_64": f"{base_url}/MaxVideoPlayer-{version}-1.x86_64.rpm", | |
| } | |
| data = { | |
| "version": version, | |
| "notes": "See the CHANGELOG for details.", | |
| "pub_date": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), | |
| "platforms": platforms, | |
| "packages": packages | |
| } | |
| with open("latest.json", "w") as f: | |
| json.dump(data, f, indent=2) | |
| print(f"latest.json written with platforms: {list(platforms.keys())}") | |
| PYEOF | |
| gh release upload "v${VERSION}" latest.json \ | |
| --repo "$GITHUB_REPOSITORY" \ | |
| --clobber | |
| # ── Sync main back to dev after release ───────────────────────────────── | |
| sync-dev: | |
| runs-on: ubuntu-latest | |
| needs: [publish-latest-json] | |
| if: always() && needs.publish-latest-json.result == 'success' | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| token: ${{ secrets.RELEASE_AUTOMATION_PAT || github.token }} | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "41898282+github-actions[bot]@users.noreply.github.com" | |
| - name: Merge main into dev | |
| env: | |
| GH_TOKEN: ${{ secrets.RELEASE_AUTOMATION_PAT || github.token }} | |
| run: | | |
| set -euo pipefail | |
| # Check if dev branch exists before fetching (fetch fails if branch is missing) | |
| if ! git ls-remote --heads origin dev | grep -q .; then | |
| echo "::notice::No dev branch found — skipping sync." | |
| exit 0 | |
| fi | |
| git fetch origin main dev | |
| git checkout dev | |
| git reset --hard origin/dev | |
| # Check if main has anything new for dev | |
| AHEAD=$(git rev-list --count origin/dev..origin/main) | |
| if [ "$AHEAD" = "0" ]; then | |
| echo "dev is already up to date with main." | |
| exit 0 | |
| fi | |
| # Try fast-forward merge first, fall back to merge commit | |
| if git merge origin/main --ff-only 2>/dev/null; then | |
| echo "Fast-forwarded dev to main." | |
| git push origin dev | |
| elif git merge origin/main --no-edit -m "chore: sync main back to dev after release"; then | |
| echo "Merged main into dev (merge commit)." | |
| git push origin dev | |
| else | |
| echo "::warning::Auto-merge of main into dev failed (conflicts). Creating PR instead." | |
| # Create a PR for manual resolution | |
| BRANCH="chore/sync-main-to-dev-$(date -u +%Y%m%d-%H%M%S)-${GITHUB_RUN_ID}" | |
| git merge --abort || true | |
| git checkout -b "$BRANCH" origin/main | |
| git push -u origin "$BRANCH" | |
| gh pr create \ | |
| --base dev \ | |
| --head "$BRANCH" \ | |
| --title "chore: sync main back to dev after release" \ | |
| --body "Automated post-release sync. Main has changes (version bump + release tag) that need to be merged back into dev. Resolve any conflicts and merge." | |
| fi | |
| - name: Summary | |
| run: echo "### main synced back to dev" >> "$GITHUB_STEP_SUMMARY" |