name: Release
on:
push:
tags: ["v*"]
workflow_dispatch:
permissions:
contents: read
jobs:
release:
if: github.repository_owner == 'adonm'
name: Release (${{ matrix.target }})
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
archive: tar.gz
- os: ubuntu-24.04-arm
target: aarch64-unknown-linux-gnu
archive: tar.gz
- os: macos-14
target: aarch64-apple-darwin
archive: tar.gz
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 with:
persist-credentials: false
- uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d - name: Add cross-compilation target
run: rustup target add ${{ matrix.target }}
- name: Build size-optimized release binary
env:
RUSTFLAGS: -C debuginfo=0
run: cargo build --release --locked --target ${{ matrix.target }}
- name: Strip binary
shell: bash
run: |
if [[ "${{ runner.os }}" == "macOS" ]]; then
strip target/${{ matrix.target }}/release/oy
else
strip --strip-all target/${{ matrix.target }}/release/oy
fi
- name: Package
shell: bash
env:
REF_NAME: ${{ github.ref_name }}
run: |
mkdir -p dist
cp target/${{ matrix.target }}/release/oy dist/oy
tar -C dist -czf "oy-${REF_NAME}-${{ matrix.target }}.tar.gz" oy
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a with:
name: oy-${{ github.ref_name }}-${{ matrix.target }}
path: oy-${{ github.ref_name }}-${{ matrix.target }}.${{ matrix.archive }}
if-no-files-found: error
publish-release:
if: github.repository_owner == 'adonm'
needs: [release, publish-npm]
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
attestations: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 with:
persist-credentials: false
- name: Download artifacts
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c with:
path: dist
merge-multiple: true
- name: Extract changelog release notes
env:
REF_NAME: ${{ github.ref_name }}
run: |
python3 - <<'PY'
import os
import re
import sys
from pathlib import Path
version = os.environ["REF_NAME"].removeprefix("v")
changelog = Path("CHANGELOG.md").read_text(encoding="utf-8")
pattern = re.compile(
rf"^## \[{re.escape(version)}\][^\n]*\n(?P<body>.*?)(?=^## |\Z)",
re.MULTILINE | re.DOTALL,
)
match = pattern.search(changelog)
if match is None:
sys.exit(f"CHANGELOG.md has no section for {version}")
notes = match.group("body").strip()
if not notes:
sys.exit(f"CHANGELOG.md section for {version} is empty")
Path("release-notes.md").write_text(notes + "\n", encoding="utf-8")
PY
- name: Attest release assets
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 with:
subject-path: dist/*
- name: Create draft GitHub release with assets
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }}
REF_NAME: ${{ github.ref_name }}
REPOSITORY: ${{ github.repository }}
SHA: ${{ github.sha }}
run: |
prerelease_args=()
if [[ "${REF_NAME}" == *-* ]]; then
prerelease_args+=(--prerelease)
fi
if gh release view "${REF_NAME}" --repo "${REPOSITORY}" >/dev/null 2>&1; then
gh release edit "${REF_NAME}" \
--repo "${REPOSITORY}" \
--draft=true \
"${prerelease_args[@]}" \
--target "${SHA}" \
--title "${REF_NAME}" \
--notes-file release-notes.md
gh release upload "${REF_NAME}" dist/* --repo "${REPOSITORY}" --clobber
else
gh release create "${REF_NAME}" dist/* \
--repo "${REPOSITORY}" \
--draft \
"${prerelease_args[@]}" \
--target "${SHA}" \
--title "${REF_NAME}" \
--notes-file release-notes.md
fi
- name: Publish GitHub release
env:
GH_TOKEN: ${{ secrets.RELEASE_TOKEN || github.token }}
REF_NAME: ${{ github.ref_name }}
REPOSITORY: ${{ github.repository }}
run: gh release edit "${REF_NAME}" --repo "${REPOSITORY}" --draft=false
publish-crate:
if: github.repository_owner == 'adonm'
needs: release
runs-on: ubuntu-latest
environment: crates-io
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 with:
persist-credentials: false
- uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d - name: Verify crate package
run: cargo package --locked
- name: Authenticate with crates.io
id: auth
uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 - name: Publish crate to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: |
output=$(cargo publish --locked 2>&1) && exit_code=0 || exit_code=$?
echo "$output"
if [ "$exit_code" -ne 0 ]; then
if echo "$output" | grep -q "already exists"; then
echo "::notice::Version already published on crates.io, skipping"
else
exit "$exit_code"
fi
fi
publish-npm:
if: github.repository_owner == 'adonm'
needs: release
runs-on: ubuntu-latest
environment: npm
permissions:
contents: read
id-token: write
defaults:
run:
working-directory: packages/opencode
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e with:
node-version: "24"
registry-url: https://registry.npmjs.org
package-manager-cache: false
- name: Install OIDC-capable npm
run: npm install --global npm@12.0.1
- name: Require package version to match tag
env:
REF_NAME: ${{ github.ref_name }}
run: test "v$(node -p 'require("./package.json").version')" = "$REF_NAME"
- name: Install, build, and test
run: |
npm ci --ignore-scripts
npm run build
npm test
- name: Publish public package with npm trusted publishing
env:
EXPECTED_SHA: ${{ github.sha }}
run: |
package=$(node -p 'require("./package.json").name')
version=$(node -p 'require("./package.json").version')
if npm view "$package@$version" version >/dev/null 2>&1; then
published_sha=$(npm view "$package@$version" gitHead)
if [ "$published_sha" != "$EXPECTED_SHA" ]; then
echo "::error::$package@$version already exists from unexpected commit $published_sha"
exit 1
fi
echo "::notice::$package@$version is already published from $published_sha; skipping"
else
npm publish --access public
fi