Skip to content

dashboard: bump version to 0.9.1 #45

dashboard: bump version to 0.9.1

dashboard: bump version to 0.9.1 #45

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