diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5d721ab..be7be61 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,8 @@ -# Builds the Windows app and the Android APK, then publishes them as a single -# GitHub Release. +# Builds the Windows app, macOS app, and Android APK, then publishes them as a +# single GitHub Release. # # It runs only when you push a version tag like "v0.1.0" — see how to do that -# in the README's "Cutting a release" section. The two build jobs upload their +# in the README's "Cutting a release" section. The build jobs upload their # artifacts; a final release job attaches them to one release and generates the # notes once (so they aren't duplicated). @@ -112,8 +112,45 @@ jobs: name: android path: "intervalssync-${{ github.ref_name }}-android.apk" + macos: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: astral-sh/setup-uv@v6 + + - name: Compute version from tag + run: echo "APP_VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV" + + - name: Stamp version into the app + run: echo "__version__ = \"$APP_VERSION\"" > src/intervalssync/_version.py + + - name: Stamp Dropbox app key into the app + env: + IGPSYNC_DROPBOX_APP_KEY: ${{ secrets.IGPSYNC_DROPBOX_APP_KEY }} + run: | + python -c "from pathlib import Path; import os; Path('src/intervalssync/_dropbox_app_key.py').write_text('DROPBOX_APP_KEY = ' + repr(os.getenv('IGPSYNC_DROPBOX_APP_KEY', '')) + '\n', encoding='utf-8')" + + - name: Build macOS app + run: uv run flet build macos --yes --verbose --build-version "${{ env.APP_VERSION }}" + + - name: Package into a versioned zip + run: | + for app in build/macos/*.app; do + if [ -d "$app" ]; then + ditto -c -k --sequesterRsrc --keepParent "$app" "intervalssync-${{ github.ref_name }}-macos.zip" + exit 0 + fi + done + echo "No macOS .app bundle found in build/macos" >&2 + exit 1 + + - uses: actions/upload-artifact@v4 + with: + name: macos + path: "intervalssync-${{ github.ref_name }}-macos.zip" + release: - needs: [windows, android] + needs: [windows, android, macos] runs-on: ubuntu-latest steps: - name: Download build artifacts @@ -121,7 +158,7 @@ jobs: with: path: dist - # Create (or update) the Release for this tag, attach both files, and + # Create (or update) the Release for this tag, attach all files, and # generate the notes a single time (avoids the duplicate notes you get # when each build job generates them). # diff --git a/README.md b/README.md index 596da91..5b9b485 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@

License: MIT GitHub release - Platforms + Platforms GitHub stars

@@ -24,7 +24,7 @@ Enter your credentials once, press Sync, and your latest rides from iGPSPORT or Bryton Active land on intervals.icu. You can also push planned workouts from intervals.icu back to iGPSPORT or Bryton Active. - Free and open source, for Windows and Android. + Free and open source, for Windows, macOS, and Android.

@@ -61,7 +61,7 @@ - **Workout upload window** in Settings — how many calendar days to upload (default: today only) - **Sets the intervals.icu sport type** after upload (e.g. Mountain Bike Ride / Gravel Ride) — iGPSPORT exports everything as a generic "Ride" - Optionally deletes the local `.fit` files after a successful upload -- Stores your credentials in the **OS secure vault** (Windows Credential Manager / Android Keystore), never in a file +- Stores your credentials in the **OS secure vault** (Windows Credential Manager / macOS Keychain / Android Keystore), never in a file - Lets you know when a newer version is available - **Sync zones to iGPSPORT** — push FTP, LTHR, max HR, and power/HR zones from intervals.icu into your iGPSPORT profile (Settings → iGPSPORT profile, or `intervalssync sync-zones`; the app prompts on launch when thresholds differ) - **Headless CLI** — activity sync, workout upload, and iGPSPORT zone/threshold sync from the terminal, with JSON output and exit codes for automation and AI agents (see [CLI & automation](#cli--automation-ai-agents)) @@ -82,6 +82,16 @@ Your password and API key are stored in your operating system's **secure credential store** — Windows Credential Manager on Windows (the same vault Windows uses for its own logins) — never in a plain text file. +## Download & run (macOS) + +1. Go to the [Releases](../../releases) page and download the latest `macos.zip`. +2. Unzip it and move the app to **Applications** if you want. +3. Open the app, fill in **Settings** (same fields as above), then **Sync** or **Upload to iGPSPORT** / **Upload to Bryton**. + +On macOS your credentials are stored in Keychain. Release builds are not +notarized yet, so macOS may require right-clicking the app and choosing +**Open** the first time. + ## Download & run (Android) 1. On the [Releases](../../releases) page, download the latest `.apk`. @@ -106,14 +116,15 @@ cp .env.example .env # optional: credentials for CLI; IGPSYNC_DROPBOX_APP_KEY uv run --env-file .env intervalssync-gui ``` -## Build the Windows executable +## Build the desktop apps ```bash uv run flet build windows +uv run flet build macos ``` -The distributable lands in `build/windows/`. (Flet downloads the Flutter -toolchain on the first build.) +The distributables land in `build/windows/` and `build/macos/`. (Flet downloads +the Flutter toolchain on the first build; macOS builds require a macOS host.) ## Roadmap