name: Publish Packages
on:
push:
tags:
- v[0-9]+.*
workflow_dispatch:
inputs:
tag:
description: "Release tag to publish (e.g., v0.1.0)"
required: true
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: write
id-token: write
jobs:
meta:
name: Resolve versions
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
python_version: ${{ steps.version.outputs.python_version }}
tag: ${{ steps.version.outputs.tag }}
crates_exists: ${{ steps.published.outputs.crates_exists }}
npm_exists: ${{ steps.published.outputs.npm_exists }}
opencode_exists: ${{ steps.published.outputs.opencode_exists }}
pypi_exists: ${{ steps.published.outputs.pypi_exists }}
release_assets_exist: ${{ steps.release_assets.outputs.release_assets_exist }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "20"
- name: Extract version from tag
id: version
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
tag="${{ github.event.inputs.tag }}"
else
tag="${GITHUB_REF#refs/tags/}"
fi
version="${tag#v}"
python_version="${version//-rc./rc}"
{
echo "version=$version"
echo "python_version=$python_version"
echo "tag=$tag"
} >> "$GITHUB_OUTPUT"
echo "Git tag: $tag"
echo "Cargo/npm version: $version"
echo "Python version: $python_version"
- name: Verify version consistency across all surfaces
run: |
set -euo pipefail
target_version="${{ steps.version.outputs.version }}"
target_python_version="${{ steps.version.outputs.python_version }}"
echo "Verifying version consistency for: $target_version (Python form: $target_python_version)"
# Check Cargo.toml
cargo_version="$(grep -E '^version = "' Cargo.toml | head -1 | cut -d'"' -f2)"
if [ "$cargo_version" != "$target_version" ]; then
echo "::error::Cargo.toml version mismatch: expected $target_version, got $cargo_version"
exit 1
fi
# Check npm-package/package.json
if [ -f npm-package/package.json ]; then
npm_version="$(jq -r '.version' npm-package/package.json)"
if [ "$npm_version" != "$target_version" ]; then
echo "::error::npm-package/package.json version mismatch: expected $target_version, got $npm_version"
exit 1
fi
fi
# Check pip-package/pyproject.toml (PyPI form)
if [ -f pip-package/pyproject.toml ]; then
pypi_version="$(grep -E '^version = "' pip-package/pyproject.toml | head -1 | cut -d'"' -f2)"
if [ "$pypi_version" != "$target_python_version" ]; then
echo "::error::pip-package/pyproject.toml version mismatch: expected $target_python_version, got $pypi_version"
exit 1
fi
fi
# Check pip-package/basemind/__init__.py
if [ -f pip-package/basemind/__init__.py ]; then
init_version="$(grep -E '^__version__ = "' pip-package/basemind/__init__.py | cut -d'"' -f2)"
if [ "$init_version" != "$target_python_version" ]; then
echo "::error::pip-package/basemind/__init__.py version mismatch: expected $target_python_version, got $init_version"
exit 1
fi
fi
# Check root package.json
if [ -f package.json ]; then
root_version="$(jq -r '.version' package.json)"
if [ "$root_version" != "$target_version" ]; then
echo "::error::package.json (root) version mismatch: expected $target_version, got $root_version"
exit 1
fi
fi
# Check opencode-plugin/package.json
if [ -f opencode-plugin/package.json ]; then
opencode_version="$(jq -r '.version' opencode-plugin/package.json)"
if [ "$opencode_version" != "$target_version" ]; then
echo "::error::opencode-plugin/package.json version mismatch: expected $target_version, got $opencode_version"
exit 1
fi
fi
# Check .claude-plugin/plugin.json
if [ -f .claude-plugin/plugin.json ]; then
claude_version="$(jq -r '.version' .claude-plugin/plugin.json)"
if [ "$claude_version" != "$target_version" ]; then
echo "::error::.claude-plugin/plugin.json version mismatch: expected $target_version, got $claude_version"
exit 1
fi
fi
# Check .claude-plugin/marketplace.json
if [ -f .claude-plugin/marketplace.json ]; then
marketplace_version="$(jq -r '.plugins[0].version' .claude-plugin/marketplace.json)"
if [ "$marketplace_version" != "$target_version" ]; then
echo "::error::.claude-plugin/marketplace.json version mismatch: expected $target_version, got $marketplace_version"
exit 1
fi
fi
# Check .codex-plugin/plugin.json
if [ -f .codex-plugin/plugin.json ]; then
codex_version="$(jq -r '.version' .codex-plugin/plugin.json)"
if [ "$codex_version" != "$target_version" ]; then
echo "::error::.codex-plugin/plugin.json version mismatch: expected $target_version, got $codex_version"
exit 1
fi
fi
# Check .cursor-plugin/plugin.json
if [ -f .cursor-plugin/plugin.json ]; then
cursor_version="$(jq -r '.version' .cursor-plugin/plugin.json)"
if [ "$cursor_version" != "$target_version" ]; then
echo "::error::.cursor-plugin/plugin.json version mismatch: expected $target_version, got $cursor_version"
exit 1
fi
fi
# Check gemini-extension.json
if [ -f gemini-extension.json ]; then
gemini_version="$(jq -r '.version' gemini-extension.json)"
if [ "$gemini_version" != "$target_version" ]; then
echo "::error::gemini-extension.json version mismatch: expected $target_version, got $gemini_version"
exit 1
fi
fi
echo "✓ All version surfaces match: $target_version"
- name: Detect already-published versions
id: published
run: |
version="${{ steps.version.outputs.version }}"
python_version="${{ steps.version.outputs.python_version }}"
crates_exists=false
if curl -fsS "https://crates.io/api/v1/crates/basemind/${version}" >/dev/null; then
crates_exists=true
fi
npm_exists=false
if npm view "basemind@${version}" version >/dev/null 2>&1; then
npm_exists=true
fi
opencode_exists=false
if npm view "basemind-opencode@${version}" version >/dev/null 2>&1; then
opencode_exists=true
fi
pypi_exists=false
if curl -fsS "https://pypi.org/pypi/basemind/${python_version}/json" >/dev/null; then
pypi_exists=true
fi
{
echo "crates_exists=$crates_exists"
echo "npm_exists=$npm_exists"
echo "opencode_exists=$opencode_exists"
echo "pypi_exists=$pypi_exists"
} >> "$GITHUB_OUTPUT"
echo "Already published?"
echo " crates.io: $crates_exists"
echo " npm: $npm_exists"
echo " npm (opencode): $opencode_exists"
echo " PyPI: $pypi_exists"
- name: Detect existing GitHub release assets
id: release_assets
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag="${{ steps.version.outputs.tag }}"
release_assets_exist=false
if gh release view "$tag" >/dev/null 2>&1; then
count="$(gh release view "$tag" --json assets --jq '.assets | length')"
if [ "${count:-0}" -gt 0 ]; then
release_assets_exist=true
fi
fi
echo "release_assets_exist=$release_assets_exist" >> "$GITHUB_OUTPUT"
echo "GitHub release assets exist? $release_assets_exist"
create_release:
name: Create GitHub release
needs: meta
if: needs.meta.outputs.release_assets_exist != 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
- name: Ensure the GitHub release exists
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag="${{ needs.meta.outputs.tag }}"
if gh release view "$tag" >/dev/null 2>&1; then
echo "Release $tag already exists."
else
prerelease=""
case "$tag" in
*-rc.* | *-beta* | *-alpha*) prerelease="--prerelease" ;;
esac
# Create as a DRAFT. The build matrix uploads binaries to it, but it
# stays invisible (assets not publicly downloadable) until the
# finalize_release job promotes it — and that job only runs once
# checksums (which requires all 4 platform binaries) succeeds. A failed
# platform build therefore leaves a hidden draft, not a live release
# with missing binaries and no checksums (the v0.10.0 failure mode that
# broke the plugin launcher's checksum-verified download).
gh release create "$tag" --title "$tag" --notes "Release $tag" --verify-tag --draft $prerelease
fi
build-binaries:
name: Build binaries / ${{ matrix.triple }}
needs: [meta, create_release]
if: needs.meta.outputs.release_assets_exist != 'true'
strategy:
fail-fast: false
matrix:
include:
- triple: aarch64-apple-darwin
os: macos-latest
- triple: x86_64-pc-windows-msvc
os: windows-latest
runs-on: ${{ matrix.os }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.triple }}
- name: Install system dependencies
uses: ./.github/actions/install-system-deps
- name: Setup cache
uses: Swatinem/rust-cache@v2
with:
key: ${{ matrix.triple }}
- name: Build release binary
shell: bash
run: |
cargo build --release --features full --bin basemind \
--target "${{ matrix.triple }}"
- name: Package release
shell: bash
run: ./scripts/package-release.sh ${{ matrix.triple }}
- name: Upload to GitHub release
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag="${{ needs.meta.outputs.tag }}"
triple="${{ matrix.triple }}"
if [ "$triple" = "x86_64-pc-windows-msvc" ]; then
archive="basemind-${triple}.zip"
else
archive="basemind-${triple}.tar.gz"
fi
if [ ! -f "$archive" ]; then
echo "::error::Archive not found: $archive"
exit 1
fi
gh release upload "$tag" "$archive" --clobber
build-linux-binaries:
name: Build Linux binaries / ${{ matrix.triple }}
needs: [meta, create_release]
if: needs.meta.outputs.release_assets_exist != 'true'
runs-on: ${{ matrix.os }}
container: ${{ matrix.image }}
strategy:
fail-fast: false
matrix:
include:
- triple: x86_64-unknown-linux-gnu
os: ubuntu-latest
image: quay.io/pypa/manylinux_2_28_x86_64:latest
protoc_arch: linux-x86_64
- triple: aarch64-unknown-linux-gnu
os: ubuntu-24.04-arm
image: quay.io/pypa/manylinux_2_28_aarch64:latest
protoc_arch: linux-aarch_64
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
- name: Install Rust (in container)
shell: bash
run: |
set -euo pipefail
# Pin CARGO_HOME/RUSTUP_HOME to absolute paths and export them to
# GITHUB_ENV. Container steps do not share $HOME reliably, so rustup
# installs its default toolchain under one HOME and a later step cannot
# find it ("no default is configured"). Fixed paths remove the ambiguity.
export CARGO_HOME=/opt/cargo
export RUSTUP_HOME=/opt/rustup
curl -fsSL https://sh.rustup.rs | \
sh -s -- -y --profile minimal --default-toolchain stable
export PATH="$CARGO_HOME/bin:$PATH"
rustup default stable
rustup target add "${{ matrix.triple }}"
echo "CARGO_HOME=/opt/cargo" >> "$GITHUB_ENV"
echo "RUSTUP_HOME=/opt/rustup" >> "$GITHUB_ENV"
echo "/opt/cargo/bin" >> "$GITHUB_PATH"
- name: Install system dependencies (AlmaLinux 8)
shell: bash
run: |
set -euo pipefail
dnf install -y epel-release
dnf install -y dnf-plugins-core
# gh CLI for the release-asset upload from inside the container.
dnf config-manager --add-repo https://cli.github.com/packages/rpm/gh-cli.repo
# Core build + image/text libraries. Some codecs (libde265, x265, dav1d)
# and libmagic-devel are unavailable on AlmaLinux 8; libaom is via EPEL.
# tesseract-langpack-eng conflicts with tesseract, so install tesseract only.
dnf install -y \
gcc gcc-c++ make cmake pkgconfig \
openssl-devel \
tesseract \
libaom-devel \
patchelf gh \
git curl wget unzip
echo "gcc: $(gcc --version | head -1)"
- name: Install protoc from official release
shell: bash
run: |
set -euo pipefail
ver="28.2"
zip="protoc-${ver}-${{ matrix.protoc_arch }}.zip"
mkdir -p /usr/local/protoc
cd /tmp
curl -fsSL -o "$zip" \
"https://github.com/protocolbuffers/protobuf/releases/download/v${ver}/${zip}"
unzip -q "$zip" -d /usr/local/protoc
ln -sf /usr/local/protoc/bin/protoc /usr/local/bin/protoc
echo "PROTOC=/usr/local/bin/protoc" >> "$GITHUB_ENV"
protoc --version
- name: Build libheif from source (1.23.0)
shell: bash
run: |
set -euo pipefail
ver="1.23.0"
d="$(mktemp -d)"; trap 'rm -rf "$d"' EXIT
cd "$d"
curl -fsSL -o libheif.tar.gz \
"https://github.com/strukturag/libheif/releases/download/v${ver}/libheif-${ver}.tar.gz"
tar xzf libheif.tar.gz
cd "libheif-${ver}"
mkdir build && cd build
# Disable codecs unavailable on AlmaLinux 8 (libde265, x265, dav1d);
# libaom is present via EPEL.
cmake .. \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/usr/local \
-DCMAKE_INSTALL_LIBDIR=lib \
-DWITH_EXAMPLES=OFF \
-DWITH_GDK_PIXBUF=OFF \
-DBUILD_TESTING=OFF \
-DWITH_LIBDE265=OFF \
-DWITH_X265=OFF \
-DWITH_DAV1D=OFF \
-DWITH_AOM=ON
make -j"$(nproc)"
make install
ldconfig
{
echo "PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:${PKG_CONFIG_PATH:-}"
echo "LD_LIBRARY_PATH=/usr/local/lib:${LD_LIBRARY_PATH:-}"
} >> "$GITHUB_ENV"
- name: Build release binary (native gcc + glibc-2.28 compat shim)
shell: bash
env:
TRIPLE: ${{ matrix.triple }}
run: |
set -euo pipefail
# Compile the glibc-2.28 compat shim and link it into the binary so
# ort's prebuilt ONNX Runtime (which references glibc 2.38 symbols)
# links against the 2.28 floor. Scoped to this target's RUSTFLAGS.
shim_obj="$PWD/glibc228_compat.o"
gcc -O2 -fPIC -c scripts/glibc228_compat.c -o "$shim_obj"
rustflags_var="CARGO_TARGET_$(echo "$TRIPLE" | tr 'a-z-' 'A-Z_')_RUSTFLAGS"
export "${rustflags_var}=-C link-arg=${shim_obj}"
cargo build --release --features full --bin basemind --target "$TRIPLE"
- name: Verify glibc floor (<= 2.28)
shell: bash
run: |
set -euo pipefail
bin="target/${{ matrix.triple }}/release/basemind"
max="$(objdump -T "$bin" | grep -oE 'GLIBC_[0-9]+\.[0-9]+' | sort -V | tail -1 || true)"
echo "highest glibc symbol required: ${max:-none}"
if [ -n "$max" ] && \
[ "$(printf '%s\nGLIBC_2.28\n' "$max" | sort -V | tail -1)" != "GLIBC_2.28" ]; then
echo "::error::basemind requires $max (> GLIBC_2.28); a dependency pulled a newer glibc symbol"
exit 1
fi
echo "✓ glibc floor verified: ${max:-none} <= GLIBC_2.28"
- name: Package release
shell: bash
run: ./scripts/package-release.sh ${{ matrix.triple }}
- name: Upload to GitHub release
shell: bash
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
tag="${{ needs.meta.outputs.tag }}"
archive="basemind-${{ matrix.triple }}.tar.gz"
if [ ! -f "$archive" ]; then
echo "::error::Archive not found: $archive"
exit 1
fi
# The container runs as root but the workspace is owned by the runner
# uid; gh shells out to git, which refuses with "dubious ownership".
# Mark it safe and pass --repo so gh never needs the local git remote.
git config --global --add safe.directory "${GITHUB_WORKSPACE:-/__w/basemind/basemind}"
gh release upload "$tag" "$archive" --clobber --repo "$GITHUB_REPOSITORY"
checksums:
name: Generate and upload checksums
needs: [meta, build-binaries, build-linux-binaries]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
- name: Verify all 4 expected archives are present
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
tag="${{ needs.meta.outputs.tag }}"
# Expected archives for the 4 build targets
expected=(
"basemind-x86_64-unknown-linux-gnu.tar.gz"
"basemind-aarch64-unknown-linux-gnu.tar.gz"
"basemind-aarch64-apple-darwin.tar.gz"
"basemind-x86_64-pc-windows-msvc.zip"
)
# Fetch the list of release assets
gh release view "$tag" --json assets -q '.assets[].name' > /tmp/release_assets.txt || true
missing_count=0
for expected_asset in "${expected[@]}"; do
if ! grep -q "^${expected_asset}$" /tmp/release_assets.txt 2>/dev/null; then
echo "::error::Missing expected archive: $expected_asset"
missing_count=$((missing_count + 1))
fi
done
if [ $missing_count -gt 0 ]; then
echo "::error::Release is incomplete: $missing_count of 4 platform archives missing. The release must have all 4 binaries before checksums can be generated."
exit 1
fi
echo "✓ All 4 expected archives present"
- name: Download archives and generate checksums
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
tag="${{ needs.meta.outputs.tag }}"
# Download all archives from the release
gh release view "$tag" --json assets -q '.assets[] | select(.name | test("basemind-.+\\.(tar\\.gz|zip)$")).name' | while read -r asset; do
echo "Downloading $asset..."
gh release download "$tag" -p "$asset" -O "$asset"
done
# Generate checksums file with explicit platform list for clarity
sha256sum basemind-*.tar.gz basemind-*.zip | sort > "basemind_${{ needs.meta.outputs.version }}_checksums.txt"
echo "✓ Generated checksums file"
cat "basemind_${{ needs.meta.outputs.version }}_checksums.txt"
- name: Upload checksums to release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
tag="${{ needs.meta.outputs.tag }}"
gh release upload "$tag" basemind_${{ needs.meta.outputs.version }}_checksums.txt --clobber
echo "✓ Uploaded checksums to release"
finalize_release:
name: Publish GitHub release
needs: [meta, checksums]
if: always() && needs.meta.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Promote draft to a published release once complete
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
tag="${{ needs.meta.outputs.tag }}"
version="${{ needs.meta.outputs.version }}"
# The full asset set a launcher / npm install.js / pip downloader needs:
# every platform archive plus the checksums file they verify against.
required=(
"basemind-x86_64-unknown-linux-gnu.tar.gz"
"basemind-aarch64-unknown-linux-gnu.tar.gz"
"basemind-aarch64-apple-darwin.tar.gz"
"basemind-x86_64-pc-windows-msvc.zip"
"basemind_${version}_checksums.txt"
)
gh release view "$tag" --repo "$GITHUB_REPOSITORY" --json assets -q '.assets[].name' \
> /tmp/assets.txt || true
missing=0
for asset in "${required[@]}"; do
if ! grep -qx "$asset" /tmp/assets.txt; then
echo "::error::release $tag is missing $asset"
missing=$((missing + 1))
fi
done
if [ "$missing" -gt 0 ]; then
echo "::error::release $tag is incomplete ($missing required assets missing); leaving it as a draft"
exit 1
fi
gh release edit "$tag" --draft=false --repo "$GITHUB_REPOSITORY"
echo "✓ Promoted $tag to a published release"
publish_crates:
name: Publish crates.io
needs: meta
if: needs.meta.outputs.crates_exists != 'true'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
- name: Authenticate to crates.io
uses: rust-lang/crates-io-auth-action@v1
id: crates_auth
- name: Publish to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ steps.crates_auth.outputs.token }}
run: |
set -euo pipefail
if cargo publish --allow-dirty 2>cargo-publish.err; then
exit 0
fi
if grep -Eq "crate .* already exists" cargo-publish.err; then
echo "crate already published; skipping"
exit 0
fi
cat cargo-publish.err >&2
exit 1
publish_npm:
name: Publish npm
needs: [meta, finalize_release]
if: needs.meta.outputs.npm_exists != 'true' && needs.finalize_release.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"
- name: Update npm
run: npm install -g npm@latest
- name: Publish to npm
env:
NODE_AUTH_TOKEN: ""
run: |
unset NODE_AUTH_TOKEN
cd npm-package
version="${{ needs.meta.outputs.version }}"
if [[ "$version" == *"-rc."* ]]; then
npm_tag="beta"
else
npm_tag="latest"
fi
npm publish --provenance --access public --tag "$npm_tag"
publish_opencode:
name: Publish npm (basemind-opencode)
needs: [meta, finalize_release]
if: needs.meta.outputs.opencode_exists != 'true' && needs.finalize_release.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"
- name: Update npm
run: npm install -g npm@latest
- name: Publish to npm
env:
NODE_AUTH_TOKEN: ""
run: |
unset NODE_AUTH_TOKEN
cd opencode-plugin
version="${{ needs.meta.outputs.version }}"
if [[ "$version" == *"-rc."* ]]; then
npm_tag="beta"
else
npm_tag="latest"
fi
npm publish --provenance --access public --tag "$npm_tag"
publish_pypi:
name: Publish PyPI
needs: [meta, finalize_release]
if: needs.meta.outputs.pypi_exists != 'true' && needs.finalize_release.result == 'success'
runs-on: ubuntu-latest
environment: pypi
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: "3.8"
- name: Build Python package
run: |
python -m pip install --upgrade pip
python -m pip install build
cd pip-package
python -m build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: pip-package/dist
skip-existing: true
publish_homebrew:
name: Update Homebrew formula
needs: [meta, finalize_release]
if: needs.finalize_release.result == 'success' && !contains(needs.meta.outputs.version, '-rc') && !contains(needs.meta.outputs.version, '-beta') && !contains(needs.meta.outputs.version, '-alpha')
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.tag || '' }}
- name: Generate formula and push to tap
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TOKEN: ${{ secrets.HOMEBREW_TOKEN }}
run: |
set -euo pipefail
if [ -z "${HOMEBREW_TOKEN:-}" ]; then
echo "HOMEBREW_TOKEN not set — skipping the Homebrew formula update."
exit 0
fi
tag="${{ needs.meta.outputs.tag }}"
version="${{ needs.meta.outputs.version }}"
gh release download "$tag" -p "basemind_${version}_checksums.txt" -O checksums.txt
./scripts/update-homebrew-formula.sh "$version" checksums.txt > basemind.rb
git clone --depth 1 "https://x-access-token:${HOMEBREW_TOKEN}@github.com/Goldziher/homebrew-tap.git" tap
cp basemind.rb tap/Formula/basemind.rb
cd tap
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/basemind.rb
if git diff --cached --quiet; then
echo "Formula already up to date."
else
git commit -m "basemind ${version}"
git push
fi