Skip to content

chore: release v0.0.76 #90

chore: release v0.0.76

chore: release v0.0.76 #90

Workflow file for this run

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 }}