chore: release v0.0.76 #90
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: Releaser | |
| on: | |
| push: | |
| tags: | |
| - 'v*' | |
| workflow_dispatch: | |
| defaults: | |
| run: | |
| shell: bash | |
| jobs: | |
| pantry: | |
| name: pantry (${{ matrix.platform.name }}) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| platform: | |
| - { os: macos-15, name: darwin-arm64, deps: '' } | |
| - { os: ubuntu-latest, name: linux-x86-64, deps: 'sudo apt-get update && sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev' } | |
| runs-on: ${{ matrix.platform.os }} | |
| permissions: | |
| contents: write | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Pantry (provides Bun, Zig, etc.) | |
| uses: pantry-pm/pantry/packages/action@main | |
| - name: Install System Dependencies | |
| if: matrix.platform.deps != '' | |
| run: ${{ matrix.platform.deps }} | |
| - name: Install Dependencies | |
| run: bun install | |
| - name: Extract Version | |
| id: version | |
| run: echo "version=$(jq -r .version package.json)" >> "$GITHUB_OUTPUT" | |
| - name: First-party Zig dependencies | |
| uses: ./.github/actions/first-party-zig-deps | |
| - name: Build Native Binary | |
| run: bun run build:core | |
| # ── Cross-compile all targets ──────────────────────────────── | |
| - name: First-party Zig dependencies | |
| uses: ./.github/actions/first-party-zig-deps | |
| - name: Cross-compile additional targets (macOS) | |
| if: runner.os == 'macOS' | |
| working-directory: packages/zig | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| SDK_PATH="$(xcrun --show-sdk-path)" | |
| mkdir -p zig-out/cross | |
| echo "::group::Cross-compile darwin-x64" | |
| eval "$(pantry env | sed -n '/^export /,$p')" && zig build -Doptimize=ReleaseSafe -Dversion="$VERSION" -Dtarget=x86_64-macos -Dmacos-sdk="$SDK_PATH" | |
| mkdir -p zig-out/cross/darwin-x64 && cp zig-out/bin/craft zig-out/cross/darwin-x64/craft | |
| echo "::endgroup::" | |
| # Restore native ARM64 binary. `zig-out/bin` is cleared first because | |
| # the publish step zips that directory whole and unfiltered, so | |
| # anything a cross build left in it ships to users under the host's | |
| # archive name. | |
| rm -rf zig-out/bin | |
| eval "$(pantry env | sed -n '/^export /,$p')" && zig build -Doptimize=ReleaseSafe -Dversion="$VERSION" | |
| - name: First-party Zig dependencies | |
| uses: ./.github/actions/first-party-zig-deps | |
| - name: Cross-compile additional targets (Linux) | |
| if: runner.os == 'Linux' | |
| working-directory: packages/zig | |
| run: | | |
| VERSION="${{ steps.version.outputs.version }}" | |
| mkdir -p zig-out/cross | |
| # Only cross-compile targets that do not require a target sysroot. | |
| # Linux/FreeBSD GUI builds link GTK/WebKit system libraries, so | |
| # non-host triples need dedicated sysroots before they are reliable. | |
| for target_spec in \ | |
| "x86_64-windows:windows-x64:craft.exe"; do | |
| IFS=: read -r zig_target dir_name bin_name <<< "$target_spec" | |
| echo "::group::Cross-compile $dir_name" | |
| eval "$(pantry env | sed -n '/^export /,$p')" | |
| zig build -Doptimize=ReleaseSafe -Dversion="$VERSION" -Dtarget="$zig_target" | |
| mkdir -p "zig-out/cross/$dir_name" && cp "zig-out/bin/$bin_name" "zig-out/cross/$dir_name/$bin_name" | |
| echo "Built $dir_name" | |
| echo "::endgroup::" | |
| done | |
| # Restore native linux-x64 binary. Clearing `zig-out/bin` first is | |
| # load-bearing: the windows cross build above leaves craft.exe there, | |
| # nothing removes it, and the publish step zips the whole directory — | |
| # so craft-linux-x64.zip shipped a Windows PE binary alongside the | |
| # Linux one. | |
| rm -rf zig-out/bin | |
| eval "$(pantry env | sed -n '/^export /,$p')" && zig build -Doptimize=ReleaseSafe -Dversion="$VERSION" | |
| # ── macOS Code Signing & Notarization ────────────────────────── | |
| # The signing credentials live encrypted in `.env.production`, not as | |
| # seven repository secrets. | |
| # | |
| # One key instead of seven values: the ciphertext is committed and | |
| # reviewable, rotating a certificate is a commit rather than seven trips | |
| # through a settings page, and the same file works locally — `buddy | |
| # env:get` reads a value on a laptop with no GitHub involved. | |
| # What that trades away | |
| # is that the ciphertext is public, so the whole of the secrecy is | |
| # `DOTENV_PRIVATE_KEY_PRODUCTION`, which is the one thing that stays a | |
| # repository secret. | |
| # | |
| # Decrypted into `$GITHUB_ENV` so every step below reads `$APPLE_ID` the | |
| # way it always did. Each value is masked first: `$GITHUB_ENV` does *not* | |
| # mask on its own, and an unmasked notarization password is one `set -x` | |
| # away from the log. | |
| - name: Load macOS release credentials | |
| if: runner.os == 'macOS' | |
| env: | |
| DOTENV_PRIVATE_KEY_PRODUCTION: ${{ secrets.DOTENV_PRIVATE_KEY_PRODUCTION }} | |
| run: | | |
| set -euo pipefail | |
| if [ -z "${DOTENV_PRIVATE_KEY_PRODUCTION:-}" ]; then | |
| echo "::error::DOTENV_PRIVATE_KEY_PRODUCTION is not set. It is the only" | |
| echo "::error::secret this release needs; see .env.production for what it opens." | |
| exit 1 | |
| fi | |
| for name in APPLE_CERTIFICATE_BASE64 APPLE_CERTIFICATE_PASSWORD APPLE_SIGNING_IDENTITY KEYCHAIN_PASSWORD APPLE_ID APPLE_APP_PASSWORD APPLE_TEAM_ID; do | |
| value="$(bunx --bun buddy env:get "$name" --file .env.production 2>/dev/null || true)" | |
| if [ -z "$value" ]; then | |
| echo "::error::$name is empty in .env.production." | |
| echo "::error::Set it with: bunx buddy env:set $name '<value>' --file .env.production" | |
| exit 1 | |
| fi | |
| echo "::add-mask::$value" | |
| printf '%s=%s\n' "$name" "$value" >> "$GITHUB_ENV" | |
| done | |
| - name: Import signing certificate | |
| if: runner.os == 'macOS' | |
| run: | | |
| security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain | |
| security default-keychain -s build.keychain | |
| security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain | |
| echo "$APPLE_CERTIFICATE_BASE64" | base64 --decode > certificate.p12 | |
| security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain | |
| rm certificate.p12 | |
| - name: Sign macOS binary | |
| if: runner.os == 'macOS' | |
| env: | |
| # Default to the strict distribution entitlements; CI can opt in to | |
| # the dev plist (sandbox off, library validation off) by setting | |
| # CRAFT_ENTITLEMENTS=dev for local debugging — never for release. | |
| ENTITLEMENTS_VARIANT: ${{ vars.CRAFT_ENTITLEMENTS || 'distribution' }} | |
| run: | | |
| set -euo pipefail | |
| ENTITLEMENTS="scripts/entitlements-${ENTITLEMENTS_VARIANT}.plist" | |
| if [ ! -f "$ENTITLEMENTS" ]; then | |
| echo "::error file=$ENTITLEMENTS::missing — required for hardened-runtime signing" | |
| exit 1 | |
| fi | |
| echo "Using entitlements: $ENTITLEMENTS" | |
| for binary in \ | |
| packages/zig/zig-out/bin/craft \ | |
| packages/zig/zig-out/cross/darwin-x64/craft; do | |
| codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" \ | |
| --entitlements "$ENTITLEMENTS" "$binary" | |
| codesign --verify --deep --strict "$binary" | |
| done | |
| echo "macOS ARM64 and x64 binaries signed successfully" | |
| - name: Notarize macOS binaries | |
| if: runner.os == 'macOS' | |
| run: | | |
| set -euo pipefail | |
| for tuple in \ | |
| "packages/zig/zig-out/bin/craft:arm64" \ | |
| "packages/zig/zig-out/cross/darwin-x64/craft:x64"; do | |
| IFS=: read -r binary architecture <<< "$tuple" | |
| archive="craft-${architecture}.zip" | |
| ditto -c -k --keepParent "$binary" "$archive" | |
| # A raw Mach-O binary has no stapler-supported container. Submit | |
| # an archive containing the exact signed binary and require | |
| # Apple's final Accepted result; Gatekeeper retrieves that ticket | |
| # online for the binary distributed by the release. | |
| xcrun notarytool submit "$archive" \ | |
| --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_APP_PASSWORD" \ | |
| --team-id "$APPLE_TEAM_ID" \ | |
| --wait \ | |
| --timeout 30m | |
| codesign --verify --deep --strict "$binary" | |
| rm "$archive" | |
| done | |
| # ── Publish & Release ────────────────────────────── | |
| # Pantry action handles everything: | |
| # 1. Publishes to pantry registry (zig package) | |
| # 2. Auto-packages binaries from zig-out/ into platform-named zips | |
| # 3. Creates/updates GitHub release with all artifacts + changelog | |
| - name: Publish & Release | |
| uses: pantry-pm/pantry/packages/action@main | |
| with: | |
| install: 'false' | |
| publish: 'zig' | |
| package-dir: packages/zig | |
| token: ${{ secrets.PANTRY_TOKEN }} | |
| release: 'true' | |
| release-changelog: CHANGELOG.md | |
| release-token: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }} | |
| # Replaces the old `notify` job, which pointed at | |
| # .github/actions/discord-notify — a path that has never existed in | |
| # this repository. Its `hashFiles` guard skipped the only real step, | |
| # so the job checked out the repo, did nothing and reported success: | |
| # a failed release looked notified. The action does this natively. | |
| discord-webhook: ${{ secrets.DISCORD_WEBHOOK_URL }} | |
| notification-title: craft ${{ github.ref_name }} | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.PAT_TOKEN || secrets.GITHUB_TOKEN }} | |
| npm: | |
| name: npm | |
| # Ordered after the binaries, not gated on them. | |
| # | |
| # `needs:` alone waits for every matrix leg to *succeed*, and the | |
| # darwin-arm64 leg exits early when the Apple signing secrets are absent — | |
| # so a missing certificate has been silently blocking every npm publish | |
| # since v0.0.55. Fifteen releases where the tag, the changelog and the | |
| # GitHub release all landed and not one package reached the registry. | |
| # | |
| # Nothing here consumes the binary job's artifacts: this checks out, runs | |
| # `bun install`, and publishes packages built from TypeScript. Signing a Zig | |
| # binary and publishing an SDK are independent, and the workflow now says | |
| # so. `!cancelled()` rather than `always()` so a cancelled run still stops. | |
| needs: [pantry] | |
| if: ${{ !cancelled() }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Setup Pantry (provides Bun, Zig, etc.) | |
| uses: pantry-pm/pantry/packages/action@main | |
| - name: Install Dependencies | |
| run: bun install | |
| - name: Publish to npm | |
| run: pantry publish --npm --access public | |
| env: | |
| NPM_TOKEN: ${{ secrets.NPM_TOKEN }} |