Skip to content

Bump version to 0.2.2 #44

Bump version to 0.2.2

Bump version to 0.2.2 #44

Workflow file for this run

name: Release Binaries
on:
push:
tags:
- "v*"
branches:
- "version/**"
workflow_dispatch:
inputs:
dry_run_note:
description: Optional context for manual non-publishing release validation.
required: false
type: string
permissions:
contents: read
jobs:
web-ui-gates:
name: Web UI Gates
runs-on: ubuntu-latest
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: "22.x"
cache: npm
cache-dependency-path: crates/orchestrator-web-server/web-ui/package-lock.json
- name: Setup Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Install web-ui dependencies
working-directory: crates/orchestrator-web-server/web-ui
run: npm ci
- name: Run web-ui tests
working-directory: crates/orchestrator-web-server/web-ui
run: npm run test
- name: Build web-ui bundle
working-directory: crates/orchestrator-web-server/web-ui
run: npm run build
- name: Run web-ui smoke E2E
id: smoke
working-directory: crates/orchestrator-web-server/web-ui
env:
SMOKE_ARTIFACT_DIR: ${{ github.workspace }}/crates/orchestrator-web-server/web-ui/.smoke-artifacts
run: npm run test:e2e:smoke
- name: Upload smoke diagnostics
if: failure()
uses: actions/upload-artifact@v4
with:
name: release-web-ui-smoke-failure-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ github.workspace }}/crates/orchestrator-web-server/web-ui/.smoke-artifacts
if-no-files-found: ignore
retention-days: 7
build:
name: Build (${{ matrix.target }})
runs-on: ${{ matrix.os }}
needs: web-ui-gates
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
archive_ext: tar.gz
- os: macos-15-intel
target: x86_64-apple-darwin
archive_ext: tar.gz
- os: macos-14
target: aarch64-apple-darwin
archive_ext: tar.gz
- os: windows-latest
target: x86_64-pc-windows-msvc
archive_ext: zip
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- name: Cache Cargo build artifacts
uses: Swatinem/rust-cache@v2
- name: Compute release version
id: version
shell: bash
run: |
if [[ "${GITHUB_REF}" == refs/tags/* ]]; then
VERSION="${GITHUB_REF_NAME}"
else
BRANCH_SANITIZED="$(echo "${GITHUB_REF_NAME}" | sed -E 's/[^A-Za-z0-9._-]+/-/g; s/^-+//; s/-+$//')"
if [[ -z "${BRANCH_SANITIZED}" ]]; then
BRANCH_SANITIZED="ref"
fi
VERSION="${BRANCH_SANITIZED}-${GITHUB_SHA::7}"
fi
echo "value=${VERSION}" >> "${GITHUB_OUTPUT}"
echo "build_number=${GITHUB_RUN_NUMBER}" >> "${GITHUB_OUTPUT}"
echo "build_id=${GITHUB_RUN_ID}" >> "${GITHUB_OUTPUT}"
- name: Enforce Cargo.toml version matches tag
if: startsWith(github.ref, 'refs/tags/v')
shell: bash
run: |
set -euo pipefail
TAG_VERSION="${GITHUB_REF_NAME#v}"
CARGO_VERSION="$(grep '^version' crates/orchestrator-cli/Cargo.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')"
if [[ "${TAG_VERSION}" != "${CARGO_VERSION}" ]]; then
echo "::error::Version mismatch: tag is v${TAG_VERSION} but Cargo.toml has ${CARGO_VERSION}"
echo "Update crates/orchestrator-cli/Cargo.toml version to ${TAG_VERSION} before tagging."
exit 1
fi
echo "Version verified: v${CARGO_VERSION}"
- name: Build release binaries
shell: bash
run: |
cargo ao-bin-build-release --locked --target ${{ matrix.target }}
- name: Verify binary version
if: startsWith(github.ref, 'refs/tags/v') && !contains(matrix.target, 'windows')
shell: bash
run: |
set -euo pipefail
TAG_VERSION="${GITHUB_REF_NAME#v}"
BIN_VERSION="$(target/${{ matrix.target }}/release/ao --version 2>&1 | awk '{print $2}')"
if [[ "${BIN_VERSION}" != "${TAG_VERSION}" ]]; then
echo "::error::Binary reports version ${BIN_VERSION} but tag is v${TAG_VERSION}"
exit 1
fi
echo "Binary version verified: ${BIN_VERSION}"
- name: Package artifact (unix)
if: runner.os != 'Windows'
shell: bash
env:
DRY_RUN_NOTE: ${{ github.event.inputs.dry_run_note || '' }}
run: |
set -euo pipefail
VERSION="${{ steps.version.outputs.value }}"
TARGET="${{ matrix.target }}"
STAGE_DIR="ao-${VERSION}-${TARGET}"
mkdir -p "${STAGE_DIR}"
BINARIES=(ao agent-runner llm-cli-wrapper ao-oai-runner ao-workflow-runner)
for binary in "${BINARIES[@]}"; do
cp "target/${TARGET}/release/${binary}" "${STAGE_DIR}/"
done
export VERSION TARGET STAGE_DIR BUILD_NUMBER BUILD_ID
BUILD_NUMBER="${{ steps.version.outputs.build_number }}"
BUILD_ID="${{ steps.version.outputs.build_id }}"
python3 - <<'PY'
import json
import os
from pathlib import Path
binaries = ["ao", "agent-runner", "llm-cli-wrapper", "ao-oai-runner", "ao-workflow-runner"]
metadata = {
"schema": "ao.release.v1",
"version": os.environ["VERSION"],
"target": os.environ["TARGET"],
"build_number": int(os.environ.get("BUILD_NUMBER", "0")),
"build_id": os.environ.get("BUILD_ID", ""),
"git_ref": os.environ["GITHUB_REF"],
"git_sha": os.environ["GITHUB_SHA"],
"event_name": os.environ["GITHUB_EVENT_NAME"],
"dry_run_note": os.environ.get("DRY_RUN_NOTE", ""),
"binaries": binaries,
"files": binaries,
}
output = Path(os.environ["STAGE_DIR"]) / "release-metadata.json"
output.write_text(json.dumps(metadata, indent=2, sort_keys=True) + "\n", encoding="utf-8")
PY
tar -czf "${STAGE_DIR}.tar.gz" "${STAGE_DIR}"
- name: Validate staged artifact contract (unix)
if: runner.os != 'Windows'
shell: bash
run: |
set -euo pipefail
VERSION="${{ steps.version.outputs.value }}"
TARGET="${{ matrix.target }}"
STAGE_DIR="ao-${VERSION}-${TARGET}"
ARCHIVE_PATH="${STAGE_DIR}.tar.gz"
REQUIRED_FILES=(ao agent-runner llm-cli-wrapper ao-oai-runner ao-workflow-runner release-metadata.json)
for file in "${REQUIRED_FILES[@]}"; do
if [[ ! -f "${STAGE_DIR}/${file}" ]]; then
echo "missing required staged file: ${STAGE_DIR}/${file}" >&2
exit 1
fi
done
if [[ ! -f "${ARCHIVE_PATH}" ]]; then
echo "missing expected archive: ${ARCHIVE_PATH}" >&2
exit 1
fi
export VERSION TARGET STAGE_DIR
python3 - <<'PY'
import json
import os
from pathlib import Path
stage_dir = Path(os.environ["STAGE_DIR"])
metadata_path = stage_dir / "release-metadata.json"
metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
expected_binaries = ["ao", "agent-runner", "llm-cli-wrapper", "ao-oai-runner", "ao-workflow-runner"]
required_pairs = {
"schema": "ao.release.v1",
"version": os.environ["VERSION"],
"target": os.environ["TARGET"],
"binaries": expected_binaries,
"files": expected_binaries,
}
for key, expected in required_pairs.items():
if metadata.get(key) != expected:
raise SystemExit(
f"release metadata mismatch for {key}: expected {expected!r}, got {metadata.get(key)!r}"
)
for key in ("git_ref", "git_sha", "event_name", "dry_run_note"):
if key not in metadata:
raise SystemExit(f"release metadata missing key: {key}")
PY
ARCHIVE_LIST="$(tar -tzf "${ARCHIVE_PATH}")"
for file in "${REQUIRED_FILES[@]}"; do
if ! printf '%s\n' "${ARCHIVE_LIST}" | grep -Fqx "${STAGE_DIR}/${file}"; then
echo "archive missing required entry: ${STAGE_DIR}/${file}" >&2
exit 1
fi
done
- name: Package artifact (windows)
if: runner.os == 'Windows'
shell: pwsh
env:
DRY_RUN_NOTE: ${{ github.event.inputs.dry_run_note || '' }}
run: |
$Version = "${{ steps.version.outputs.value }}"
$Target = "${{ matrix.target }}"
$StageDir = "ao-$Version-$Target"
$Binaries = @("ao", "agent-runner", "llm-cli-wrapper", "ao-oai-runner", "ao-workflow-runner")
$BinaryFiles = $Binaries | ForEach-Object { "$_.exe" }
New-Item -ItemType Directory -Path $StageDir -Force | Out-Null
foreach ($Binary in $BinaryFiles) {
Copy-Item "target/$Target/release/$Binary" "$StageDir/"
}
$Metadata = @{
schema = "ao.release.v1"
version = $Version
target = $Target
git_ref = $env:GITHUB_REF
git_sha = $env:GITHUB_SHA
event_name = $env:GITHUB_EVENT_NAME
dry_run_note = $env:DRY_RUN_NOTE
binaries = $Binaries
files = $BinaryFiles
}
$Metadata | ConvertTo-Json -Depth 4 | Set-Content -Path "$StageDir/release-metadata.json" -Encoding utf8NoBOM
Compress-Archive -Path $StageDir -DestinationPath "$StageDir.zip" -Force
- name: Validate staged artifact contract (windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
$Version = "${{ steps.version.outputs.value }}"
$Target = "${{ matrix.target }}"
$StageDir = "ao-$Version-$Target"
$ArchivePath = "$StageDir.zip"
$Binaries = @("ao", "agent-runner", "llm-cli-wrapper", "ao-oai-runner", "ao-workflow-runner")
$BinaryFiles = $Binaries | ForEach-Object { "$_.exe" }
$RequiredFiles = $BinaryFiles + "release-metadata.json"
foreach ($File in $RequiredFiles) {
if (-not (Test-Path "$StageDir/$File")) {
throw "Missing required staged file: $StageDir/$File"
}
}
if (-not (Test-Path $ArchivePath)) {
throw "Missing expected archive: $ArchivePath"
}
$Metadata = Get-Content "$StageDir/release-metadata.json" -Raw | ConvertFrom-Json
if ($Metadata.schema -ne "ao.release.v1") {
throw "release metadata mismatch for schema"
}
if ($Metadata.version -ne $Version) {
throw "release metadata mismatch for version"
}
if ($Metadata.target -ne $Target) {
throw "release metadata mismatch for target"
}
if ((@($Metadata.binaries) -join ",") -ne ($Binaries -join ",")) {
throw "release metadata mismatch for binaries"
}
if ((@($Metadata.files) -join ",") -ne ($BinaryFiles -join ",")) {
throw "release metadata mismatch for files"
}
foreach ($Key in @("git_ref", "git_sha", "event_name", "dry_run_note")) {
if ($null -eq $Metadata.$Key) {
throw "release metadata missing key: $Key"
}
}
Add-Type -AssemblyName System.IO.Compression.FileSystem
$Zip = [System.IO.Compression.ZipFile]::OpenRead($ArchivePath)
try {
$ZipEntries = $Zip.Entries | ForEach-Object { $_.FullName.TrimEnd("/") }
}
finally {
$Zip.Dispose()
}
foreach ($File in $RequiredFiles) {
$EntryPath = "$StageDir/$File"
if ($ZipEntries -notcontains $EntryPath) {
throw "Archive missing required entry: $EntryPath"
}
}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ao-${{ steps.version.outputs.value }}-${{ matrix.target }}
path: ao-${{ steps.version.outputs.value }}-${{ matrix.target }}.${{ matrix.archive_ext }}
if-no-files-found: error
publish:
name: Publish GitHub release (tags only)
runs-on: ubuntu-latest
needs: build
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
permissions:
contents: write
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
path: dist
- name: Generate checksums
shell: bash
run: |
set -euo pipefail
RELEASE_ASSETS_DIR="dist/release-assets"
rm -rf "${RELEASE_ASSETS_DIR}"
mapfile -d '' ARCHIVES < <(find dist -type f \( -name '*.tar.gz' -o -name '*.zip' \) ! -path "${RELEASE_ASSETS_DIR}/*" -print0 | LC_ALL=C sort -z)
if [[ "${#ARCHIVES[@]}" -eq 0 ]]; then
echo "no release archives found after download-artifact step" >&2
exit 1
fi
mkdir -p "${RELEASE_ASSETS_DIR}"
BASENAMES=()
for archive in "${ARCHIVES[@]}"; do
base_name="$(basename "${archive}")"
if [[ -e "${RELEASE_ASSETS_DIR}/${base_name}" ]]; then
echo "duplicate archive basename detected: ${base_name}" >&2
exit 1
fi
cp "${archive}" "${RELEASE_ASSETS_DIR}/${base_name}"
BASENAMES+=("${base_name}")
done
(
cd "${RELEASE_ASSETS_DIR}"
mapfile -t BASENAMES_SORTED < <(printf '%s\n' "${BASENAMES[@]}" | LC_ALL=C sort)
sha256sum "${BASENAMES_SORTED[@]}" > SHA256SUMS.txt
)
- name: Publish release (private repo)
uses: softprops/action-gh-release@v2
with:
files: |
dist/release-assets/*.tar.gz
dist/release-assets/*.zip
dist/release-assets/SHA256SUMS.txt
generate_release_notes: true
publish-public:
name: Publish to public repo (tags only)
runs-on: ubuntu-latest
needs: publish
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
steps:
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
path: dist
- name: Collect release assets
shell: bash
run: |
set -euo pipefail
RELEASE_ASSETS_DIR="dist/release-assets"
rm -rf "${RELEASE_ASSETS_DIR}"
mkdir -p "${RELEASE_ASSETS_DIR}"
mapfile -d '' ARCHIVES < <(find dist -type f \( -name '*.tar.gz' -o -name '*.zip' \) ! -path "${RELEASE_ASSETS_DIR}/*" -print0 | LC_ALL=C sort -z)
for archive in "${ARCHIVES[@]}"; do
cp "${archive}" "${RELEASE_ASSETS_DIR}/$(basename "${archive}")"
done
(
cd "${RELEASE_ASSETS_DIR}"
sha256sum *.tar.gz *.zip 2>/dev/null | LC_ALL=C sort -k2 > SHA256SUMS.txt
)
- name: Create release on public repo
shell: bash
env:
GH_TOKEN: ${{ secrets.AO_PUBLIC_RELEASE_TOKEN }}
run: |
set -euo pipefail
VERSION="${GITHUB_REF_NAME}"
gh release create "${VERSION}" \
--repo launchapp-dev/ao \
--title "AO ${VERSION}" \
--notes "Release ${VERSION} — download the archive for your platform and run install.sh, or use:
\`\`\`bash
curl -fsSL https://raw.githubusercontent.com/launchapp-dev/ao/main/install.sh | bash
\`\`\`" \
dist/release-assets/*