Skip to content

test(aft-bridge): skip findBinarySync env-mutation tests on Linux CI #127

test(aft-bridge): skip findBinarySync env-mutation tests on Linux CI

test(aft-bridge): skip findBinarySync env-mutation tests on Linux CI #127

Workflow file for this run

name: Release
on:
push:
tags:
- "v*"
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write
id-token: write
env:
CARGO_TERM_COLOR: always
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
# All unit-level coverage (Linux, macOS, Windows cargo, Windows bash e2e).
# Defined as a reusable workflow so tests.yml shares the exact same
# workload. Release mode (strict=true) makes EVERY job blocking — including
# the Windows jobs that are non-blocking at PR time. If a Windows-only
# regression slipped past PR-time CI, this gate catches it before publish.
unit:
name: Unit
uses: ./.github/workflows/_unit-suite.yml
with:
strict: true
# Run the full E2E matrix (Linux Docker + Pi RPC + Windows native +
# macOS native) at release time. Reuses the same workflow tests.yml runs
# at PR time — single source of truth, so PR-time and release-time e2e
# can never drift. Build/publish jobs below `needs:` this job, so an e2e
# regression blocks the release.
e2e:
name: E2E
needs: unit
uses: ./.github/workflows/_e2e-suite.yml
publish-crates:
name: Publish to crates.io
runs-on: ubuntu-latest
needs: [unit, e2e]
steps:
- uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: dtolnay/rust-toolchain@stable
# Sync Cargo.toml version to match the tag. Without this, `cargo publish`
# tries to publish whatever version is in HEAD's Cargo.toml — which is
# last-released-version on tag pushes (the tag does NOT carry a separate
# version commit; release.sh commits the version bump locally, but if
# someone retags HEAD without running release.sh, Cargo.toml stays
# stale). Mirrors the same step in publish-npm-platforms.
- name: Sync versions from tag
run: node scripts/version-sync.mjs --from-tag
- name: Publish to crates.io
# Allow re-runs of the same tag: treat "already exists" as success
# ONLY if the version we're publishing matches the tag. Otherwise the
# fallback silently masks the version-mismatch bug (we try to publish
# vN, it already exists, we say success even though we wanted vN+1).
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
CARGO_VERSION=$(grep '^version' crates/aft/Cargo.toml | head -1 | sed -E 's/version = "([^"]+)"/\1/')
if [ "$TAG_VERSION" != "$CARGO_VERSION" ]; then
echo "::error::Tag $GITHUB_REF_NAME wants $TAG_VERSION but Cargo.toml has $CARGO_VERSION after version-sync. version-sync.mjs broken?"
exit 1
fi
cargo publish --package agent-file-tools \
|| { ec=$?; cargo publish --package agent-file-tools --dry-run 2>&1 | grep -q "already exists" && exit 0 || exit $ec; }
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
build-darwin-arm64:
name: Build macOS ARM64
runs-on: macos-latest
needs: [unit, e2e]
steps:
- uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-apple-darwin
# Safety net: if someone retags HEAD without running release.sh,
# Cargo.toml.version stays stale. Bake the tag's version into the
# binary before cargo build so `aft --version` matches what the
# plugin expects.
- name: Sync versions from tag
run: node scripts/version-sync.mjs --from-tag
- name: Build
run: cargo build --release --target aarch64-apple-darwin
- name: Strip binary
run: strip target/aarch64-apple-darwin/release/aft
- uses: actions/upload-artifact@v4
with:
name: darwin-arm64
path: target/aarch64-apple-darwin/release/aft
if-no-files-found: error
build-darwin-x64:
name: Build macOS x64
runs-on: macos-latest
needs: [unit, e2e]
steps:
- uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-apple-darwin
- name: Sync versions from tag
run: node scripts/version-sync.mjs --from-tag
- name: Build
run: cargo build --release --target x86_64-apple-darwin
- name: Strip binary
run: strip target/x86_64-apple-darwin/release/aft
- uses: actions/upload-artifact@v4
with:
name: darwin-x64
path: target/x86_64-apple-darwin/release/aft
if-no-files-found: error
build-linux-arm64:
name: Build Linux ARM64
runs-on: ubuntu-latest
needs: [unit, e2e]
steps:
- uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: dtolnay/rust-toolchain@stable
with:
targets: aarch64-unknown-linux-gnu
- name: Sync versions from tag
run: node scripts/version-sync.mjs --from-tag
- name: Install cross
run: cargo install cross --version 0.2.5 --locked
# Use gnu target (not musl) so dlopen works for ONNX Runtime loading.
# musl produces static binaries where dlopen is a stub that always fails.
- name: Build
run: cross build --release --target aarch64-unknown-linux-gnu
- uses: actions/upload-artifact@v4
with:
name: linux-arm64
path: target/aarch64-unknown-linux-gnu/release/aft
if-no-files-found: error
build-linux-x64:
name: Build Linux x64
# Build on Ubuntu 22.04 to link against GLIBC 2.35 — compatible with
# Ubuntu 22.04 LTS (supported until 2027), Pop!OS 22.04, Debian 12+,
# and any distro with GLIBC >= 2.35. Using ubuntu-latest (24.04) would
# require GLIBC 2.39 and break older LTS users.
runs-on: ubuntu-22.04
needs: [unit, e2e]
steps:
- uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: dtolnay/rust-toolchain@stable
- name: Sync versions from tag
run: node scripts/version-sync.mjs --from-tag
# Native x64 build on Ubuntu runner — no cross-compilation needed.
# Uses gnu target (not musl) so dlopen works for ONNX Runtime loading.
- name: Build
run: cargo build --release
- uses: actions/upload-artifact@v4
with:
name: linux-x64
path: target/release/aft
if-no-files-found: error
build-win32-x64:
name: Build Windows x64
runs-on: windows-latest
needs: [unit, e2e]
steps:
- uses: actions/checkout@v5
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: dtolnay/rust-toolchain@stable
with:
targets: x86_64-pc-windows-msvc
- name: Sync versions from tag
run: node scripts/version-sync.mjs --from-tag
shell: pwsh
- name: Build
run: cargo build --release --target x86_64-pc-windows-msvc
- uses: actions/upload-artifact@v4
with:
name: win32-x64
path: target/x86_64-pc-windows-msvc/release/aft.exe
if-no-files-found: error
# npm publish runs AFTER github-release so that checksums.sha256 is already
# available on the GitHub release page when ensureBinary() runs during
# @cortexkit/aft-opencode@latest install.
publish-npm:
name: Publish to npm
runs-on: ubuntu-latest
needs:
- build-darwin-arm64
- build-darwin-x64
- build-linux-arm64
- build-linux-x64
- build-win32-x64
- github-release
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
- name: Ensure latest npm (for trusted publishing)
run: npm install -g npm@latest
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Download darwin-arm64 binary
uses: actions/download-artifact@v4
with:
name: darwin-arm64
path: packages/npm/darwin-arm64/bin
- name: Download darwin-x64 binary
uses: actions/download-artifact@v4
with:
name: darwin-x64
path: packages/npm/darwin-x64/bin
- name: Download linux-arm64 binary
uses: actions/download-artifact@v4
with:
name: linux-arm64
path: packages/npm/linux-arm64/bin
- name: Download linux-x64 binary
uses: actions/download-artifact@v4
with:
name: linux-x64
path: packages/npm/linux-x64/bin
- name: Download win32-x64 binary
uses: actions/download-artifact@v4
with:
name: win32-x64
path: packages/npm/win32-x64/bin
- name: Set binary permissions
run: |
chmod +x packages/npm/darwin-arm64/bin/aft
chmod +x packages/npm/darwin-x64/bin/aft
chmod +x packages/npm/linux-arm64/bin/aft
chmod +x packages/npm/linux-x64/bin/aft
- name: Sync versions from tag
run: node scripts/version-sync.mjs --from-tag
- name: Validate packages
run: node scripts/validate-packages.mjs
- name: Install bridge dependencies
run: bun install
working-directory: packages/aft-bridge
- name: Build aft-bridge
run: bun run build
working-directory: packages/aft-bridge
- name: Install plugin dependencies
run: bun install
working-directory: packages/opencode-plugin
- name: Build OpenCode plugin
run: bun run build
working-directory: packages/opencode-plugin
- name: Install Pi plugin dependencies
run: bun install
working-directory: packages/pi-plugin
- name: Build Pi plugin
run: bun run build
working-directory: packages/pi-plugin
- name: Install CLI dependencies
run: bun install
working-directory: packages/aft-cli
- name: Build CLI
run: bun run build
working-directory: packages/aft-cli
# Uses npm Trusted Publishing (OIDC) — configured per-package on npmjs.com
- name: Publish platform packages
run: |
set -euo pipefail
publish_or_skip() {
local pkg_dir="$1"
local pkg_name
local version
pkg_name=$(node -p "require('./${pkg_dir}/package.json').name")
version=$(node -p "require('./${pkg_dir}/package.json').version")
if npm view "${pkg_name}@${version}" version >/dev/null 2>&1; then
echo "::notice::${pkg_name}@${version} already published; skipping"
return 0
fi
npm publish --access public --provenance "$pkg_dir"
}
for pkg in darwin-arm64 darwin-x64 linux-arm64 linux-x64 win32-x64; do
publish_or_skip "packages/npm/$pkg"
done
# aft-bridge MUST publish before the plugins, because @cortexkit/aft-opencode
# and @cortexkit/aft-pi depend on it. Plugins publish from local builds, so
# there's no install-time race here, but consumers installing @latest will
# need aft-bridge resolvable on npm.
#
# Tolerate `already published` so a re-run of the same tag (or the
# initial bootstrap publish that claims the package name with a
# token before Trusted Publishing is configured) doesn't fail the
# whole release. Mirrors the crates.io publish step above.
- name: Publish @cortexkit/aft-bridge
run: |
set -euo pipefail
PKG_NAME=$(node -p "require('./package.json').name")
VERSION=$(node -p "require('./package.json').version")
if npm view "${PKG_NAME}@${VERSION}" version >/dev/null 2>&1; then
echo "::notice::${PKG_NAME}@${VERSION} already published; skipping"
exit 0
fi
npm publish --access public --provenance
working-directory: packages/aft-bridge
- name: Publish @cortexkit/aft-opencode
run: |
set -euo pipefail
PKG_NAME=$(node -p "require('./package.json').name")
VERSION=$(node -p "require('./package.json').version")
if npm view "${PKG_NAME}@${VERSION}" version >/dev/null 2>&1; then
echo "::notice::${PKG_NAME}@${VERSION} already published; skipping"
exit 0
fi
npm publish --access public --provenance
working-directory: packages/opencode-plugin
- name: Publish @cortexkit/aft-pi
run: |
set -euo pipefail
PKG_NAME=$(node -p "require('./package.json').name")
VERSION=$(node -p "require('./package.json').version")
if npm view "${PKG_NAME}@${VERSION}" version >/dev/null 2>&1; then
echo "::notice::${PKG_NAME}@${VERSION} already published; skipping"
exit 0
fi
npm publish --access public --provenance
working-directory: packages/pi-plugin
- name: Publish @cortexkit/aft (unified CLI)
run: |
set -euo pipefail
PKG_NAME=$(node -p "require('./package.json').name")
VERSION=$(node -p "require('./package.json').version")
if npm view "${PKG_NAME}@${VERSION}" version >/dev/null 2>&1; then
echo "::notice::${PKG_NAME}@${VERSION} already published; skipping"
exit 0
fi
npm publish --access public --provenance
working-directory: packages/aft-cli
# GitHub release must complete before npm publish so that checksums.sha256
# is available when freshly-installed @cortexkit/aft-opencode@latest triggers
# ensureBinary(). crates.io publish is also gated so that a failed crate
# publish blocks the release — no point shipping binaries if the Rust crate
# isn't available yet.
github-release:
name: Create GitHub Release
runs-on: ubuntu-latest
needs:
- build-darwin-arm64
- build-darwin-x64
- build-linux-arm64
- build-linux-x64
- build-win32-x64
- publish-crates
steps:
- uses: actions/checkout@v5
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Prepare release assets
run: |
mkdir -p release-assets
cp artifacts/darwin-arm64/aft release-assets/aft-darwin-arm64
cp artifacts/darwin-x64/aft release-assets/aft-darwin-x64
cp artifacts/linux-arm64/aft release-assets/aft-linux-arm64
cp artifacts/linux-x64/aft release-assets/aft-linux-x64
cp artifacts/win32-x64/aft.exe release-assets/aft-win32-x64.exe
chmod +x release-assets/aft-*
- name: Generate checksums
run: |
cd release-assets
sha256sum aft-* > checksums.sha256
cat checksums.sha256
- name: Verify curated release notes exist
run: |
notes_file=".alfonso/release-notes/${GITHUB_REF_NAME}.md"
if [ ! -f "$notes_file" ]; then
echo "::error::Curated release notes missing: $notes_file"
echo "Draft them under .alfonso/release-notes/ before tagging the release."
exit 1
fi
echo "Using $notes_file as the release body."
wc -l "$notes_file"
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
body_path: .alfonso/release-notes/${{ github.ref_name }}.md
files: release-assets/*