Skip to content

Packaging & distribution

Phase 0 (developer-only)

No distribution. go build ./cmd/ofem produces a local binary for development. No Apple Developer artifacts required.

Phase 1+ — Homebrew cask of the macOS .app

Output artifact

A single signed and notarized DMG containing OneLake.app. The .app bundles: - The Swift host app (OneLake). - The Swift File Provider Extension (OneLakeFileProvider.appex). - The Go CLI binary (ofem), embedded at OneLake.app/Contents/Resources/bin/ofem. - The Go core library is statically linked into the Swift binaries; not shipped separately.

Build pipeline

        GitHub Actions (runs on macos-15-arm64)
        ────────────────────────────────────────
1. checkout
2. setup-go (1.26.x)
3. setup-xcode (latest 26.x)
4. cache go build, xcodebuild derived data
5. go build -buildmode=c-archive -o build/libofemcore.a ./core
6. go build -o build/ofem ./cmd/ofem
7. xcodebuild archive -scheme OneLake -archivePath build/OneLake.xcarchive
8. xcodebuild -exportArchive ... -exportPath build/Export
9. codesign --force --options runtime --sign "$DEVELOPER_ID_APPLICATION" \
       --entitlements apple/OneLake.entitlements build/Export/OneLake.app
10. create-dmg build/Export/OneLake.app build/OneLake-$VERSION.dmg
11. xcrun notarytool submit build/OneLake-$VERSION.dmg --wait \
        --key-id "$NOTARY_KEY_ID" --key "$NOTARY_API_KEY_P8" \
        --issuer "$NOTARY_ISSUER_ID"
12. xcrun stapler staple build/OneLake-$VERSION.dmg
13. goreleaser release (uploads DMG + checksums to GH Releases)
14. goreleaser bumps homebrew-ofem tap repo with new cask version

Homebrew cask

We maintain a separate tap repository homebrew-ofem. The cask:

cask "ofem" do
  arch arm: "arm64"
  version "2026.05.1"
  sha256 "abc123..."

  url "https://github.com/sdebruyn/onelake-explorer-macos/releases/download/v#{version}/OneLake-#{version}.dmg"
  name "OneLake"
  desc "OneLake File Explorer for macOS — Finder integration for Microsoft Fabric"
  homepage "https://github.com/sdebruyn/onelake-explorer-macos"

  depends_on macos: ">= :sonoma"
  depends_on arch: :arm64

  app "OneLake.app"
  binary "#{appdir}/OneLake.app/Contents/Resources/bin/ofem"

  postflight do
    # Trigger launchd registration on first install
    system_command "#{appdir}/OneLake.app/Contents/Resources/bin/ofem",
                   args: ["daemon", "install"],
                   sudo: false
  end

  uninstall launchctl: "dev.debruyn.ofem.daemon",
            quit:      "dev.debruyn.ofem.app",
            delete:    [
              "~/Library/LaunchAgents/dev.debruyn.ofem.daemon.plist",
            ]

  zap trash: [
    "~/Library/Application Support/dev.debruyn.ofem",
    "~/Library/Caches/dev.debruyn.ofem",
    "~/Library/Logs/dev.debruyn.ofem",
    "~/Library/Preferences/dev.debruyn.ofem.plist",
    "~/Library/Group Containers/group.dev.debruyn.ofem",
    "~/Library/CloudStorage/OneLake-*",
  ]
end

zap runs on brew uninstall --zap ofem and wipes user data; default brew uninstall preserves it.

Versioning

CalVer: YYYY.MM.PATCH (e.g. 2026.05.1). A git tag v2026.05.1 triggers the release pipeline.

Signing identity and notarization

We need: - Apple Developer Program enrollment (~$99/year). Required to obtain code signing certificates. - A Developer ID Application certificate, exported as a .p12 and stored in GitHub Actions secrets (DEVELOPER_ID_APPLICATION_P12_BASE64 + DEVELOPER_ID_APPLICATION_P12_PASSWORD). - An App Store Connect API key for notarytool (preferred over app-specific passwords because it doesn't expire and is per-team scoped): - NOTARY_KEY_ID — the 10-character key identifier. - NOTARY_ISSUER_ID — the issuer UUID. - NOTARY_API_KEY_P8 — the contents of the .p8 private key. - A Provisioning profile is not required for non-Mac-App-Store distribution; Developer ID signing is sufficient.

Entitlements

apple/OneLake.entitlements: - com.apple.security.app-sandbox = true. - com.apple.security.application-groups = [group.dev.debruyn.ofem]. - com.apple.security.network.client = true. - com.apple.security.files.user-selected.read-write = true. - com.apple.security.keychain-access-groups = [$(AppIdentifierPrefix)group.dev.debruyn.ofem].

apple/OneLakeFileProvider.entitlements adds: - com.apple.developer.file-provider.testing-mode = true (in dev builds only). - NSExtension plist with NSExtensionFileProviderSupportedItemActions enumerated.

GoReleaser config (.goreleaser.yaml)

GoReleaser handles the Go binary part (ofem CLI) and Release upload. The Xcode part is a separate workflow step that produces the DMG which GoReleaser then attaches as a release artifact.

project_name: ofem
before:
  hooks:
    - go mod tidy
builds:
  - id: ofem-cli
    main: ./cmd/ofem
    binary: ofem
    env:
      - CGO_ENABLED=1
    goos: [darwin]
    goarch: [arm64]
    ldflags:
      - -s -w
      - -X main.version={{.Version}}
      - -X main.appInsightsConnString={{.Env.OFEM_APPINSIGHTS_CONNSTRING}}
release:
  github:
    owner: sdebruyn
    name: onelake-explorer-macos
  prerelease: auto
  extra_files:
    - glob: ./build/OneLake-*.dmg
brews:
  - repository:
      owner: sdebruyn
      name: homebrew-ofem
    homepage: https://github.com/sdebruyn/onelake-explorer-macos
    description: OneLake File Explorer for macOS CLI
    test: |
      system "#{bin}/ofem", "--version"

Note: the brews block is for the CLI-only formula in homebrew-ofem (for users who want just the CLI without the full .app). The .app is shipped via the cask which is updated by a separate workflow step.

Pre-release / beta channel

No formal beta tap. Pre-release versions are tagged like v2026.05.0-rc.1 and GoReleaser marks the GitHub Release as "prerelease". Users who want to try them download the DMG directly from the Releases page. Stable installs via Homebrew see only stable tags.

Update mechanism

brew upgrade --cask ofem. No in-app update check; no Sparkle. The daemon logs the running version on startup so users can compare against brew info ofem output.

Uninstall

brew uninstall --cask ofem          # removes app, keeps data
brew uninstall --cask --zap ofem    # removes app + all user data

The uninstall launchctl: directive in the cask stops the daemon. The app directive removes OneLake.app. zap removes everything else.

Local development distribution

For pre-release dogfooding, developers can run ./scripts/build-local.sh which: 1. Builds the Go binary (no codesigning). 2. Builds the Xcode project with the locally available developer certificate (or ad-hoc signing). 3. Skips notarization. 4. Produces build/OneLake.app ready to drag into /Applications.

This won't pass Gatekeeper on other machines without manual override (xattr -d com.apple.quarantine), which is fine for personal dogfooding.