Skip to main content

Desktop Distribution

This project ships Vued for macOS as a signed and notarized Electron DMG.

Required Apple Setup

Install an AIRCAPS Developer ID Application certificate/private key in the local login keychain:
Developer ID Application: AIRCAPS, PBC (29RMTVJVXL)
Create a notarytool profile:
xcrun notarytool store-credentials "aircaps-notary" \
  --apple-id "<apple-id>" \
  --team-id 29RMTVJVXL \
  --password "<app-specific-password>"
If the certificate name differs, set:
export CSC_NAME="AIRCAPS, PBC (29RMTVJVXL)"
Electron Builder chooses the Developer ID Application certificate type automatically; CSC_NAME should be the identity qualifier without the Developer ID Application: prefix. If the notary profile differs, set:
export APPLE_KEYCHAIN_PROFILE="aircaps-notary"

Build

npm run desktop:dist:preflight
npm run desktop:dist
The signed, notarized DMG/ZIP are written to dist/, along with dist/desktop-release-manifest.json. Verify manually when needed:
codesign -dv --verbose=4 "dist/mac-arm64/Vued.app"
spctl --assess --type execute --verbose=4 "dist/mac-arm64/Vued.app"
spctl --assess --type open --context context:primary-signature --verbose=4 "dist/Vued-0.1.0-arm64.dmg"
xcrun stapler validate "dist/mac-arm64/Vued.app"
xcrun stapler validate "dist/Vued-0.1.0-arm64.dmg"

Upload To Supabase Storage

Create a public Supabase Storage bucket. The default scripts assume:
bucket: downloads
prefix: desktop/macos
Recommended bucket layout:
downloads/
  desktop/macos/0.1.0/Vued-0.1.0-arm64.dmg
  desktop/macos/0.1.0/Vued-0.1.0-arm64-mac.zip
  desktop/macos/0.1.0/Vued-0.1.0-arm64-mac.zip.blockmap
  desktop/macos/0.1.0/Vued-0.1.0-arm64.dmg.blockmap
  desktop/macos/0.1.0/desktop-release-manifest.json
  desktop/macos/latest.json
  desktop/macos/latest-mac.yml
Use immutable versioned paths for release history. The only mutable object is desktop/macos/latest.json, which points the website to the current versioned DMG URL. latest-mac.yml is the Electron auto-update feed. It stays at the prefix root and points to the current versioned ZIP. The DMG is for website downloads; the ZIP is for in-app updates. The upload script writes desktop-release-manifest.json and latest.json before large files. It writes latest-mac.yml after the versioned ZIP so open apps never see a new updater feed before the update artifact exists.
export SUPABASE_URL="https://<project-ref>.supabase.co"
export SUPABASE_SERVICE_ROLE_KEY="<service-role-key>"
export VUED_RELEASE_BUCKET="downloads"
export VUED_RELEASE_PREFIX="desktop/macos"
VUED_UPLOAD_DRY_RUN=1 npm run desktop:dist:upload
npm run desktop:dist:upload
npm run desktop:dist bumps the current patch version by default and writes the new version to package.json and package-lock.json. To choose the version explicitly:
npm run desktop:dist -- --version 0.1.1
To bump another semver part:
npm run desktop:dist -- --bump minor
Files larger than 6 MB use Supabase’s resumable Storage endpoint on the direct storage hostname. The chunk size is fixed at 6 MB to match Supabase’s TUS upload requirements. The default large-file transport is macOS curl; set VUED_UPLOAD_TRANSPORT=node only when debugging Node transport behavior. To upload only the DMG and release manifests for the website:
npm run desktop:dist:upload:dmg
The bucket should be public for direct browser downloads. Keep SUPABASE_SERVICE_ROLE_KEY server-only. If an upload fails with fetch failed, verify:
  • SUPABASE_URL has no braces or path suffix, e.g. https://<project-ref>.supabase.co
  • SUPABASE_SERVICE_ROLE_KEY is a current service-role key
  • the downloads bucket exists and accepts objects around 200 MB
  • local network/DNS can reach the Supabase project
  • VUED_SUPABASE_STORAGE_URL points to https://<project-ref>.storage.supabase.co if you use a nonstandard Supabase URL
Rotate the service-role key immediately if it is pasted into terminal logs, issue trackers, or chats.

Automatic Updates

Production builds use electron-updater with a generic provider pointed at:
https://<project-ref>.supabase.co/storage/v1/object/public/downloads/desktop/macos
The updater reads latest-mac.yml, downloads the versioned ZIP, verifies the update, and stages installation through quitAndInstall(). The app checks on startup, periodically, and when the desktop SSE stream receives desktop.release.available, desktop.update.available, or desktop_release_available. Users can approve automatic installs from the macOS app menu:
Vued > Install Updates Automatically
After approval, a downloaded update calls quitAndInstall() automatically. Without approval, Vued shows a native “Restart and Update” prompt when the download is ready. The renderer can also call desktopBridge().updater.install() when the state is ready, for example from the Integrations settings page. Plain app quit does not install updates; this avoids a macOS race where Squirrel can still be staging the downloaded ZIP.

Website Context For vued.ai/app

The landing page should fetch:
https://<project-ref>.supabase.co/storage/v1/object/public/downloads/desktop/macos/latest.json
Use downloadUrl from that manifest for the primary macOS install button. Suggested page behavior:
  • Primary CTA: Download for Mac
  • Secondary text: Open the DMG, then drag Vued into Applications.
  • Show file metadata from latest.json: version, updated date, and optionally SHA-256.
  • If navigator.platform or UA is not macOS, keep the button visible but label it Download for Mac.
  • Link directly to the DMG, not the raw .app.
  • Do not expose Supabase service-role credentials in the page; reads should use the public bucket URL only.
Supabase Storage public buckets can serve public download URLs, and Supabase stores assets behind a CDN. Use low cache control for latest.json and higher cache control for versioned artifacts.
Last modified on June 27, 2026