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 }}
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: 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/gitmind/${version}" >/dev/null; then
crates_exists=true
fi
npm_exists=false
if npm view "gitmind@${version}" version >/dev/null 2>&1; then
npm_exists=true
fi
pypi_exists=false
if curl -fsS "https://pypi.org/pypi/gitmind/${python_version}/json" >/dev/null; then
pypi_exists=true
fi
{
echo "crates_exists=$crates_exists"
echo "npm_exists=$npm_exists"
echo "pypi_exists=$pypi_exists"
} >> "$GITHUB_OUTPUT"
echo "Already published?"
echo " crates.io: $crates_exists"
echo " npm: $npm_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"
release_assets:
name: Publish GitHub release assets
needs: meta
if: needs.meta.outputs.release_assets_exist != 'true'
runs-on: macos-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
with:
targets: x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,x86_64-apple-darwin,aarch64-apple-darwin,x86_64-pc-windows-gnu
- name: Install Zig
run: brew install zig mingw-w64
- name: Build and publish GitHub release assets
uses: goreleaser/goreleaser-action@v7
with:
distribution: goreleaser
version: "~> v2"
args: release --clean --skip=validate
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
HOMEBREW_TAP_GITHUB_TOKEN: ${{ secrets.HOMEBREW_TOKEN }}
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
if: needs.meta.outputs.npm_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 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_pypi:
name: Publish PyPI
needs: meta
if: needs.meta.outputs.pypi_exists != 'true'
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