Skip to content

Release 0.9.16

Release 0.9.16 #41

Workflow file for this run

name: Release
# Tag-driven, because a release is a deliberate act. Pushing `v0.2.0` builds the
# binaries that `install.sh` and `install.ps1` download; nothing else publishes.
on:
push:
tags: ["v*"]
# Runnable by hand to rebuild the binaries without cutting a version. It
# deliberately does not publish: on a branch `github.ref_name` is the branch
# name, so publishing would create a GitHub release called "main" and point
# /releases/latest at it - which is the exact URL every published install
# command reads from.
workflow_dispatch:
permissions:
contents: write
jobs:
build:
runs-on: ubuntu-latest
strategy:
# One failing target must not cancel the rest: a missing linux build is
# better than a release where three of four platforms silently vanished.
fail-fast: false
matrix:
include:
- target: bun-darwin-arm64
asset: pew2-darwin-arm64
- target: bun-darwin-x64
asset: pew2-darwin-x64
- target: bun-linux-x64
asset: pew2-linux-x64
- target: bun-linux-arm64
asset: pew2-linux-arm64
- target: bun-windows-x64
asset: pew2-windows-x64.exe
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: latest
- run: bun install --frozen-lockfile
# Cross-compiled from one runner: bun downloads the target runtime itself,
# so there is no need for a mac and a windows machine in the matrix.
- name: Compile
run: |
bun build --compile --minify \
--target=${{ matrix.target }} \
packages/daemon/src/cli/index.ts \
--outfile ${{ matrix.asset }}
# The provider manifests are compiled in rather than read from disk, and a
# binary that finds none of them looks exactly like a machine with no
# agents installed. Prove the shipped artifact knows its agents before it
# is published, not after someone reports it as broken.
#
# Only the native target can be executed here; the others are checked by
# the same assertion on their own platform when someone runs them.
- name: Verify the binary finds its agents
if: matrix.target == 'bun-linux-x64'
run: |
# The manifests are compiled into the binary rather than read from
# disk, and a build that lost them reports zero agents - which on a
# user's machine is indistinguishable from "you have nothing
# installed". Catch that here rather than in a bug report.
#
# Uses --json, not the human output: the screen is a designed surface
# that gets reworded, and an earlier version of this check broke
# simply because the list started showing display names instead of
# ids. A release must not fail over a copy change.
found=$(./${{ matrix.asset }} providers list --json | bun -e \
'const d = await Bun.stdin.json(); console.log(d.length)')
total=$(bun -e \
'import { BUNDLED_MANIFESTS } from "./packages/daemon/src/providers/bundled.ts"; console.log(BUNDLED_MANIFESTS.length)')
echo "providers found: $found of $total"
if [ "$found" -ne "$total" ]; then
echo "::error::The compiled binary reports $found of $total providers."
echo "The bundled manifests did not survive compilation - see packages/daemon/src/providers/bundled.ts"
./${{ matrix.asset }} providers list
exit 1
fi
# A binary that cannot be the daemon is not a release, it is a crash loop
# with a version number.
#
# `pew2 service install` wrote a plist naming `pew2 run /$bunfs/server.ts`
# - a subcommand that did not exist, pointing at a path inside the
# executable's own virtual filesystem. launchd started it, pew2 printed
# its help and exited 1, KeepAlive restarted it for ever, and `pew2 setup`
# reported the daemon unreachable with no hint why. Every binary install
# shipped that way; only people running from a source checkout, whose
# plist named a real server.ts, ever had a working daemon.
#
# `providers list` passed the whole time, because listing manifests never
# touches the server module - so this asks the one question that failed:
# does the daemon actually come up and answer.
- name: Verify the binary can run the daemon
if: matrix.target == 'bun-linux-x64'
run: |
set -u
PEW2_HOME=$(mktemp -d) PEW2_PORT=8791 ./${{ matrix.asset }} serve > serve.log 2>&1 &
pid=$!
ok=0
for _ in $(seq 1 30); do
if curl -fsS -o /dev/null http://127.0.0.1:8791/health; then ok=1; break; fi
# A dead process will never start answering, so stop waiting on one.
kill -0 "$pid" 2>/dev/null || break
sleep 1
done
kill "$pid" 2>/dev/null || true
if [ "$ok" -ne 1 ]; then
echo "::error::The compiled binary cannot run the daemon."
echo "'pew2 serve' never answered /health - a plist pointing at it would crash-loop."
cat serve.log
exit 1
fi
echo "daemon answered /health"
- name: Checksum
run: sha256sum ${{ matrix.asset }} > ${{ matrix.asset }}.sha256
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: ${{ matrix.asset }}
path: |
${{ matrix.asset }}
${{ matrix.asset }}.sha256
publish:
needs: build
# `always()` so a single target cannot hold the release hostage.
#
# `needs` waits for every matrix job whatever `fail-fast` says, and the jobs
# do not fail evenly: during a GitHub Actions incident the scarcer runner
# pools (linux-arm64 especially) sit queued for hours while the rest build in
# fifteen seconds. That blocked `curl | sh` on Mac and Windows for an entire
# evening over a Linux ARM binary that almost nobody downloads.
#
# The guard against publishing rubble is the explicit check below, which is a
# better rule than "every target succeeded" anyway: what matters is that the
# platforms the install scripts fetch are actually present.
if: always() && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
path: dist
merge-multiple: true
# Which assets a release cannot go out without.
#
# These are the ones `install.sh` and `install.ps1` resolve by name, so a
# release missing any of them is not a partial release, it is a broken
# install command for everyone on that platform. Linux ARM is deliberately
# absent: it has no installer path of its own and is a download people
# reach for by hand.
- name: Require the platforms the installers fetch
run: |
missing=""
for asset in pew2-darwin-arm64 pew2-darwin-x64 pew2-linux-x64 pew2-windows-x64.exe; do
if [ ! -f "dist/$asset" ] || [ ! -f "dist/$asset.sha256" ]; then
missing="$missing $asset"
fi
done
if [ -n "$missing" ]; then
echo "::error::Refusing to publish without:$missing"
echo "The install scripts fetch these by name, so a release without one"
echo "is a broken install command rather than a smaller release."
exit 1
fi
echo "Publishing:"
ls -1 dist
# The install scripts read from `/releases/latest/download/<name>`, so the
# asset names are effectively an API: renaming one breaks every install
# command already published.
- name: Publish
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${{ github.ref_name }}" \
--title "${{ github.ref_name }}" \
--generate-notes \
dist/*