dashboard: bump version to 0.9.1 #45
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: Dashboard Release | |
| on: | |
| push: | |
| tags: | |
| - "dashboard-v*" | |
| workflow_dispatch: | |
| permissions: | |
| contents: write | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| jobs: | |
| # Create the draft release ONCE, up front, and hand its id to every build leg. | |
| # The previous design let each matrix leg create-or-find the release by tagName; | |
| # when two legs started simultaneously they both saw "no release yet" and each | |
| # created its own draft, splitting the platform assets (and the per-arch | |
| # latest.json) across two drafts for the same tag. Creating it here first means | |
| # every leg uploads into the same release, so tauri-action merges one complete | |
| # latest.json across all architectures. | |
| create-release: | |
| name: Create draft release | |
| runs-on: ubuntu-latest | |
| outputs: | |
| release_id: ${{ steps.create.outputs.result }} | |
| steps: | |
| - id: create | |
| uses: actions/github-script@v7 | |
| with: | |
| result-encoding: string | |
| script: | | |
| const ref = process.env.GITHUB_REF_NAME; | |
| const { owner, repo } = context.repo; | |
| // Reuse an existing release for this tag (re-runs), else create a draft. | |
| try { | |
| const ex = await github.rest.repos.getReleaseByTag({ owner, repo, tag: ref }); | |
| core.info(`Reusing existing release ${ex.data.id} for ${ref}`); | |
| return String(ex.data.id); | |
| } catch (e) { | |
| if (e.status !== 404) throw e; | |
| } | |
| const created = await github.rest.repos.createRelease({ | |
| owner, repo, | |
| tag_name: ref, | |
| name: `Dashboard ${ref}`, | |
| draft: true, | |
| prerelease: false, | |
| }); | |
| core.info(`Created draft release ${created.data.id} for ${ref}`); | |
| return String(created.data.id); | |
| build: | |
| name: Build Dashboard (${{ matrix.settings.label }}) | |
| needs: create-release | |
| # Harden cargo against the transient crates.io drops we hit in the wild | |
| # ("download of … failed / curl … Connection reset by peer" mid-build, which | |
| # killed a single matrix leg ~23s in despite the code being fine). Retry hard | |
| # and disable HTTP/2 multiplexing — the documented workaround for spurious | |
| # connection resets against the cargo download CDN on hosted runners. | |
| env: | |
| CARGO_NET_RETRY: "10" | |
| CARGO_NET_GIT_FETCH_WITH_CLI: "true" | |
| CARGO_HTTP_MULTIPLEXING: "false" | |
| strategy: | |
| # Keep building every platform leg even if one fails: a release wants to | |
| # surface ALL broken platforms in one run, not cancel the survivors. The | |
| # release SCRIPT fail-fasts instead (aborts the moment the run concludes | |
| # failure), so a single failed leg no longer hides behind the slow ones. | |
| fail-fast: false | |
| matrix: | |
| settings: | |
| - host: macos-latest | |
| target: aarch64-apple-darwin | |
| label: macOS ARM64 | |
| arch: arm64 | |
| - host: macos-latest | |
| target: x86_64-apple-darwin | |
| label: macOS Intel | |
| arch: x64 | |
| - host: ubuntu-22.04 | |
| target: x86_64-unknown-linux-gnu | |
| label: Linux x64 | |
| arch: x64 | |
| - host: ubuntu-22.04-arm | |
| target: aarch64-unknown-linux-gnu | |
| label: Linux ARM64 | |
| arch: arm64 | |
| - host: windows-latest | |
| target: x86_64-pc-windows-msvc | |
| label: Windows x64 | |
| arch: x64 | |
| - host: windows-11-arm | |
| target: aarch64-pc-windows-msvc | |
| label: Windows ARM64 | |
| arch: arm64 | |
| # WiX has no ARM64 support — NSIS is the only Windows ARM installer | |
| # Tauri can produce. Without this override the default bundle set | |
| # ("all") fails the leg at the .msi step. | |
| bundles: nsis | |
| runs-on: ${{ matrix.settings.host }} | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - uses: oven-sh/setup-bun@v2 | |
| with: | |
| bun-version: latest | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.settings.target }} | |
| - name: Install Linux dependencies | |
| if: contains(matrix.settings.host, 'ubuntu') | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf | |
| - name: Install dependencies | |
| run: bun install | |
| # macOS: import Apple certificate for code signing | |
| - name: Import Apple certificate | |
| if: contains(matrix.settings.host, 'macos') | |
| env: | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| run: | | |
| CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12 | |
| KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db | |
| KEYCHAIN_PASSWORD=$(openssl rand -base64 32) | |
| echo -n "$APPLE_CERTIFICATE" | base64 --decode -o $CERTIFICATE_PATH | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security set-keychain-settings -lut 21600 $KEYCHAIN_PATH | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH | |
| security set-key-partition-list -S apple-tool:,apple: -k "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH | |
| security list-keychain -d user -s $KEYCHAIN_PATH | |
| # Extract signing identity — MUST be "Developer ID Application", not "Apple Development" | |
| IDENTITY=$(security find-identity -v -p codesigning $KEYCHAIN_PATH | grep "Developer ID Application" | head -1 | sed 's/.*"\(.*\)".*/\1/') | |
| if [ -z "$IDENTITY" ]; then | |
| echo "::error::No 'Developer ID Application' certificate found in keychain. Check APPLE_CERTIFICATE secret." | |
| security find-identity -v -p codesigning $KEYCHAIN_PATH | |
| exit 1 | |
| fi | |
| echo "Found signing identity: $IDENTITY" | |
| echo "APPLE_SIGNING_IDENTITY=$IDENTITY" >> $GITHUB_ENV | |
| # macOS: write App Store Connect API key for notarization | |
| - name: Write Apple API key | |
| if: contains(matrix.settings.host, 'macos') | |
| env: | |
| APPLE_API_KEY_CONTENT: ${{ secrets.APPLE_API_KEY_CONTENT }} | |
| run: | | |
| mkdir -p $RUNNER_TEMP | |
| echo "$APPLE_API_KEY_CONTENT" > $RUNNER_TEMP/apple-api-key.p8 | |
| - name: Build and upload artifacts | |
| uses: tauri-apps/tauri-action@v0 | |
| timeout-minutes: 60 | |
| with: | |
| projectPath: packages/dashboard | |
| tauriScript: bunx tauri | |
| args: --target ${{ matrix.settings.target }}${{ matrix.settings.bundles && format(' --bundles {0}', matrix.settings.bundles) || '' }} | |
| updaterJsonPreferNsis: true | |
| # Upload into the single pre-created draft (see create-release). Using | |
| # releaseId (not tagName) guarantees every leg targets the same release, | |
| # so there is exactly one release and one merged latest.json per tag. | |
| releaseId: ${{ needs.create-release.outputs.release_id }} | |
| assetNamePattern: magic-context-dashboard-[platform]-${{ matrix.settings.arch }}[ext] | |
| 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 }} | |
| # macOS code signing + notarization | |
| APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} | |
| APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | |
| APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }} | |
| APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} | |
| APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} | |
| APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8 | |
| # Deploy latest.json to gh-pages for the updater endpoint. | |
| # | |
| # Tauri's updater endpoint lives at https://cortexkit.github.io/magic-context/latest.json. | |
| # That URL must keep serving the latest signed manifest after every dashboard | |
| # release. We deploy ONLY latest.json by staging it in an isolated directory | |
| # and pointing publish_dir at that directory — previous configs used | |
| # `publish_dir: .` together with the (non-existent) `include_files` input, | |
| # which silently published the entire repo checkout to gh-pages on every | |
| # release. | |
| deploy-updater: | |
| name: Deploy updater manifest | |
| needs: build | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Download latest.json from release (with retry) | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| TAG="${{ github.ref_name }}" | |
| mkdir -p _updater_publish | |
| OUT=_updater_publish/latest.json | |
| # Retry up to 20×15s (5 minutes) — first-time uploads of large | |
| # platform binaries can take longer than a single fixed sleep, | |
| # and tauri-action publishes assets in parallel. | |
| for attempt in $(seq 1 20); do | |
| if gh release download "$TAG" --pattern "latest.json" --output "$OUT" --clobber 2>/dev/null; then | |
| echo "✓ downloaded latest.json on attempt $attempt" | |
| cat "$OUT" | |
| exit 0 | |
| fi | |
| echo "attempt $attempt: latest.json not yet available, sleeping 15s…" | |
| sleep 15 | |
| done | |
| echo "::error::latest.json never became available on release $TAG" | |
| exit 1 | |
| # Pin every asset URL to THIS tag's release. tauri-action writes | |
| # /releases/latest/download/ URLs, but in this repo GitHub's "latest | |
| # release" pointer belongs to the plugin train (v*), which carries no | |
| # dashboard assets — so /latest/ URLs 404 and Desktop auto-update breaks. | |
| # Rewrite to /releases/download/<TAG>/, which is immune to the pointer. | |
| - name: Pin updater URLs to this release tag | |
| run: | | |
| TAG="${{ github.ref_name }}" | |
| FILE=_updater_publish/latest.json | |
| python3 - "$TAG" "$FILE" <<'PY' | |
| import json, sys | |
| tag, path = sys.argv[1], sys.argv[2] | |
| old = "/releases/latest/download/" | |
| new = f"/releases/download/{tag}/" | |
| d = json.load(open(path)) | |
| n = 0 | |
| for info in d.get("platforms", {}).values(): | |
| u = info.get("url", "") | |
| if old in u: | |
| info["url"] = u.replace(old, new); n += 1 | |
| json.dump(d, open(path, "w"), indent=2) | |
| print(f"pinned {n} url(s) to {new}") | |
| # Fail closed: a manifest still carrying /latest/ URLs would ship broken. | |
| assert not any(old in i.get("url", "") for i in d.get("platforms", {}).values()), \ | |
| "latest.json still has /releases/latest/download/ URLs after rewrite" | |
| PY | |
| cat "$FILE" | |
| - name: Deploy to gh-pages | |
| uses: peaceiris/actions-gh-pages@v4 | |
| with: | |
| github_token: ${{ secrets.GITHUB_TOKEN }} | |
| # Publish ONLY the staging dir, which contains exactly one file: | |
| # latest.json. Anything else in the workspace stays out of gh-pages. | |
| publish_dir: ./_updater_publish | |
| publish_branch: gh-pages | |
| # keep_files: true preserves any other files that already exist | |
| # on gh-pages so we don't wipe the branch on each release. | |
| keep_files: true | |
| # force_orphan would discard gh-pages history; we keep history so | |
| # the branch acts as a normal append-only artifact log. | |
| force_orphan: false |