feat: simpler icon-forward quit hint, drop grown-ups wording #47
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: Release | |
| on: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Existing release tag to (re)build & attach binaries to, e.g. v1.0.0. Leave blank for the normal Changesets release flow." | |
| required: false | |
| type: string | |
| # Changesets needs to push the version commit/tag and open the version PR; | |
| # the binary job needs to upload an asset to the release. | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| # Don't run two releases at once, but let an in-progress one finish. | |
| concurrency: | |
| group: release | |
| cancel-in-progress: false | |
| jobs: | |
| # 1. Changesets: open/update the "Version Packages" PR, or — once that PR is | |
| # merged — tag the new version and create the GitHub Release. | |
| release: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| published: ${{ steps.changesets.outputs.published }} | |
| publishedPackages: ${{ steps.changesets.outputs.publishedPackages }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - name: Install release tooling | |
| # Only Changesets is needed here; skip zero-native's postinstall (the | |
| # native CLI download) — the binary jobs do a full install for that. | |
| run: npm install --ignore-scripts | |
| - name: Create version PR or tag a release | |
| id: changesets | |
| uses: changesets/action@v1 | |
| with: | |
| version: npm run version | |
| publish: npm run release | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # 2. Build the macOS app and attach it to the release Changesets just created. | |
| binary-macos: | |
| needs: release | |
| # Normal flow: only when Changesets published a new version. Manual flow: | |
| # a workflow_dispatch with a `tag` input rebuilds/attaches binaries to that | |
| # existing release without needing a publish. | |
| if: ${{ needs.release.outputs.published == 'true' || inputs.tag != '' }} | |
| runs-on: macos-latest | |
| # Signing + notarization secrets. When unset, the signing steps below are | |
| # skipped and the build ships unsigned (first launch needs right-click → | |
| # Open). Set all of them to ship a signed, notarized .dmg with no Gatekeeper | |
| # prompt. See "Code signing" in README.md for how to obtain each. | |
| env: | |
| MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} | |
| MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} | |
| MACOS_SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }} | |
| APPLE_NOTARY_KEY: ${{ secrets.APPLE_NOTARY_KEY }} | |
| APPLE_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }} | |
| APPLE_NOTARY_ISSUER: ${{ secrets.APPLE_NOTARY_ISSUER }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - uses: mlugg/setup-zig@v2 | |
| with: | |
| version: 0.16.0 | |
| - name: Install dependencies (zero-native framework + CLI) | |
| run: | | |
| npm install | |
| echo "$GITHUB_WORKSPACE/node_modules/.bin" >> "$GITHUB_PATH" | |
| - name: Resolve the released version | |
| id: ver | |
| run: | | |
| # Manual dispatch with a tag attaches to that existing release; | |
| # otherwise use the version Changesets just published. Changesets tags | |
| # a single-package repo as v<version> (only monorepos use | |
| # <name>@<version>), so the normal release tag is "v$VER". | |
| if [ -n "${{ github.event.inputs.tag }}" ]; then | |
| TAG="${{ github.event.inputs.tag }}" | |
| VER="${TAG#v}" | |
| else | |
| VER=$(echo '${{ needs.release.outputs.publishedPackages }}' | jq -r '.[0].version') | |
| TAG="v$VER" | |
| fi | |
| echo "version=$VER" >> "$GITHUB_OUTPUT" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| - name: Build & package the macOS app | |
| env: | |
| # Embedded into the frontend by Next during `zig build` (which runs | |
| # npm run build); tags desktop telemetry as platform=macos. | |
| NEXT_PUBLIC_GA_ID: ${{ secrets.GA_MEASUREMENT_ID }} | |
| run: | | |
| zig build package \ | |
| -Doptimize=ReleaseFast \ | |
| -Dpackage-target=macos | |
| - name: Import Developer ID certificate | |
| if: ${{ env.MACOS_CERTIFICATE != '' }} | |
| run: | | |
| KEYCHAIN="$RUNNER_TEMP/signing.keychain-db" | |
| KEYCHAIN_PWD="$(openssl rand -base64 24)" | |
| security create-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN" | |
| security set-keychain-settings -lut 3600 "$KEYCHAIN" | |
| security unlock-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN" | |
| echo "$MACOS_CERTIFICATE" | base64 --decode > "$RUNNER_TEMP/cert.p12" | |
| security import "$RUNNER_TEMP/cert.p12" -k "$KEYCHAIN" \ | |
| -P "$MACOS_CERTIFICATE_PWD" -T /usr/bin/codesign | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: \ | |
| -s -k "$KEYCHAIN_PWD" "$KEYCHAIN" >/dev/null | |
| # Put the temp keychain on the search list so codesign finds the identity. | |
| security list-keychains -d user -s "$KEYCHAIN" \ | |
| $(security list-keychains -d user | sed s/\"//g) | |
| rm -f "$RUNNER_TEMP/cert.p12" | |
| # Diagnostics: list every imported identity (any policy) with its name — | |
| # the name reveals the cert TYPE, which must be "Developer ID Application" | |
| # (an Installer/Development cert won't satisfy the codesigning policy). | |
| echo "=== All identities in the temp keychain ===" | |
| security find-identity "$KEYCHAIN" || true | |
| echo "=== Imported certificate (subject + validity window) ===" | |
| security find-certificate -a -p "$KEYCHAIN" \ | |
| | openssl x509 -noout -subject -dates 2>/dev/null || true | |
| # Gate on what codesign actually sees: a VALID codesigning identity on the | |
| # full search list (temp keychain for the leaf+key, system keychain for the | |
| # Apple intermediate/root the chain needs). Checking the temp keychain alone | |
| # reports a false "0 valid" because the chain can't be built there. | |
| echo "=== Valid code-signing identities on the search list (codesign's view) ===" | |
| security find-identity -v -p codesigning | |
| if [ "$(security find-identity -v -p codesigning | grep -c '"')" -eq 0 ]; then | |
| echo "::error::No VALID code-signing identity. From the dump above, confirm the cert is a 'Developer ID Application' cert (not Installer/Development), is unexpired, and chains to Apple's intermediate. If the identity is listed but invalid, the Developer ID intermediate may be missing from the runner." | |
| exit 1 | |
| fi | |
| # Hand the keychain path to the signing steps so they target it explicitly. | |
| echo "KEYCHAIN=$KEYCHAIN" >> "$GITHUB_ENV" | |
| - name: Sign the app (Developer ID + Hardened Runtime) | |
| if: ${{ env.MACOS_CERTIFICATE != '' }} | |
| run: | | |
| APP="$(ls -d zig-out/package/*.app | head -n1)" | |
| codesign --force --deep --options runtime --timestamp \ | |
| --keychain "$KEYCHAIN" \ | |
| --entitlements assets/keyparty.entitlements \ | |
| --sign "$MACOS_SIGN_IDENTITY" "$APP" | |
| codesign --verify --strict --verbose=2 "$APP" | |
| - name: Build a .dmg (branded, drag-to-Applications) | |
| # Same script used for local testing (scripts/make-dmg.sh), so the | |
| # install window here is identical to what you preview on a Mac. It | |
| # picks up the already-signed .app from zig-out/package, lays it out | |
| # over the branded backdrop next to an Applications drop-link, and | |
| # sets the volume icon. Signing/notarization happens in the next step. | |
| run: scripts/make-dmg.sh | |
| - name: Sign & notarize the .dmg | |
| if: ${{ env.MACOS_CERTIFICATE != '' }} | |
| run: | | |
| codesign --force --timestamp --keychain "$KEYCHAIN" \ | |
| --sign "$MACOS_SIGN_IDENTITY" "KeyParty.dmg" | |
| if [ -n "$APPLE_NOTARY_KEY" ]; then | |
| echo "$APPLE_NOTARY_KEY" | base64 --decode > "$RUNNER_TEMP/notary.p8" | |
| # --wait blocks until Apple finishes; staple embeds the ticket so the | |
| # .dmg launches offline with no Gatekeeper prompt. | |
| xcrun notarytool submit "KeyParty.dmg" \ | |
| --key "$RUNNER_TEMP/notary.p8" \ | |
| --key-id "$APPLE_NOTARY_KEY_ID" \ | |
| --issuer "$APPLE_NOTARY_ISSUER" \ | |
| --wait | |
| xcrun stapler staple "KeyParty.dmg" | |
| rm -f "$RUNNER_TEMP/notary.p8" | |
| else | |
| echo "::warning::APPLE_NOTARY_KEY unset — .dmg is signed but NOT notarized; Gatekeeper will still warn." | |
| fi | |
| - name: Attach the .dmg to the release | |
| # Version-less name -> https://github.com/<owner>/<repo>/releases/latest/download/KeyParty.dmg | |
| # is a stable link the web build can point at. | |
| run: gh release upload "${{ steps.ver.outputs.tag }}" "KeyParty.dmg" --clobber | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| # 3. Build the Windows app (full-screen kiosk WebView2 host) and attach it. | |
| binary-windows: | |
| needs: release | |
| # Normal flow: only when Changesets published a new version. Manual flow: | |
| # a workflow_dispatch with a `tag` input rebuilds/attaches binaries to that | |
| # existing release without needing a publish. | |
| if: ${{ needs.release.outputs.published == 'true' || inputs.tag != '' }} | |
| runs-on: windows-latest | |
| env: | |
| # Where the WebView2 SDK headers land (NuGet, version stripped). | |
| NUGET_DIR: packages/Microsoft.Web.WebView2 | |
| # Code signing is scaffolded but inert. Set this secret AND drop a real | |
| # signing command into the "Sign the Windows exe" step to enable it (see | |
| # "Code signing" in README.md). Until then the exe ships unsigned. | |
| WINDOWS_SIGN_CERT: ${{ secrets.WINDOWS_SIGN_CERT }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: 24 | |
| - uses: mlugg/setup-zig@v2 | |
| with: | |
| version: 0.16.0 | |
| # Sets the MSVC environment (INCLUDE, WindowsSdkDir, WindowsSdkVersion, …) | |
| # so Zig's msvc target finds the toolchain and we can locate the winrt | |
| # headers (wrl.h) the WebView2 host needs. | |
| - uses: ilammy/msvc-dev-cmd@v1 | |
| - name: Install dependencies (zero-native framework + CLI) | |
| shell: bash | |
| run: | | |
| npm install | |
| echo "$GITHUB_WORKSPACE/node_modules/.bin" >> "$GITHUB_PATH" | |
| - name: Fetch the WebView2 SDK headers + loader | |
| shell: bash | |
| run: nuget install Microsoft.Web.WebView2 -OutputDirectory packages -ExcludeVersion | |
| - name: Resolve the released version | |
| id: ver | |
| shell: bash | |
| run: | | |
| # Manual dispatch with a tag attaches to that existing release; | |
| # otherwise use the version Changesets just published. Changesets tags | |
| # a single-package repo as v<version> (only monorepos use | |
| # <name>@<version>), so the normal release tag is "v$VER". | |
| if [ -n "${{ github.event.inputs.tag }}" ]; then | |
| TAG="${{ github.event.inputs.tag }}" | |
| VER="${TAG#v}" | |
| else | |
| VER=$(echo '${{ needs.release.outputs.publishedPackages }}' | jq -r '.[0].version') | |
| TAG="v$VER" | |
| fi | |
| echo "version=$VER" >> "$GITHUB_OUTPUT" | |
| echo "tag=$TAG" >> "$GITHUB_OUTPUT" | |
| - name: Build the single-file Windows exe | |
| shell: bash | |
| env: | |
| # Embedded into the frontend by Next during `zig build` (which runs | |
| # npm run build); tags desktop telemetry as platform=windows. | |
| NEXT_PUBLIC_GA_ID: ${{ secrets.GA_MEASUREMENT_ID }} | |
| run: | | |
| WV2_INC="$GITHUB_WORKSPACE/$NUGET_DIR/build/native/include" | |
| WV2_LIB="$GITHUB_WORKSPACE/$NUGET_DIR/build/native/x64" | |
| echo "WebView2 include: $WV2_INC" | |
| echo "WebView2 lib: $WV2_LIB" | |
| # Fail loudly if the SDK pieces are missing: without WebView2.h the host | |
| # silently compiles to a blank-window stub; without the static loader lib | |
| # it can't link a self-contained exe. | |
| test -f "$WV2_INC/WebView2.h" || { echo "ERROR: WebView2.h not found at $WV2_INC"; ls -R "$GITHUB_WORKSPACE/$NUGET_DIR" 2>/dev/null | head -40; exit 1; } | |
| test -f "$WV2_LIB/WebView2LoaderStatic.lib" || { echo "ERROR: WebView2LoaderStatic.lib not found at $WV2_LIB"; exit 1; } | |
| # The frontend is embedded into the exe (served from memory) and the | |
| # WebView2 loader is static-linked, so the release artifact is a single | |
| # self-contained keyparty.exe. cl.exe finds the Windows SDK + winrt via | |
| # %INCLUDE%. | |
| zig build \ | |
| -Dtarget=x86_64-windows-msvc \ | |
| -Doptimize=ReleaseFast \ | |
| -Dwebview2-include="$WV2_INC" \ | |
| -Dwebview2-lib-dir="$WV2_LIB" | |
| test -f zig-out/bin/keyparty.exe || { echo "ERROR: no exe produced"; ls -R zig-out; exit 1; } | |
| # Version-less name -> .../releases/latest/download/KeyParty.exe is stable. | |
| cp zig-out/bin/keyparty.exe "KeyParty.exe" | |
| - name: Sign the Windows exe (scaffold — skipped until a cert is configured) | |
| # Inert today (no WINDOWS_SIGN_CERT secret). Public-trust Windows certs now | |
| # require hardware/cloud key storage, so plug your provider in here: | |
| # • Microsoft Trusted Signing: azure/trusted-signing-action@v0 | |
| # • DigiCert KeyLocker / SSL.com eSigner: the provider's signtool dlib | |
| # • signtool: signtool sign /fd SHA256 /tr <timestamp-url> /td SHA256 KeyParty.exe | |
| # Replace the echo with the real command; the signed exe then flows through | |
| # to the existing upload step unchanged. | |
| if: ${{ env.WINDOWS_SIGN_CERT != '' }} | |
| shell: bash | |
| run: | | |
| echo "TODO: sign KeyParty.exe (see step comment for provider options)." | |
| - name: Attach the binary to the release | |
| shell: bash | |
| run: gh release upload "${{ steps.ver.outputs.tag }}" "KeyParty.exe" --clobber | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |