name: Release
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
tag:
description: 'Tag to release (e.g. v0.2.0)'
required: true
concurrency:
group: release-${{ github.ref_name }}
cancel-in-progress: false
permissions:
contents: write
id-token: write
attestations: write
env:
CARGO_TERM_COLOR: always
jobs:
validate:
name: Validate and gate
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.tag.outputs.tag }}
version: ${{ steps.tag.outputs.version }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
targets: wasm32-unknown-unknown
- name: Resolve and validate tag
id: tag
run: |
TAG="${{ github.event.inputs.tag || github.ref_name }}"
if [[ ! "$TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$ ]]; then
echo "::error::invalid tag format: $TAG (expected vX.Y.Z)"; exit 1
fi
VERSION="${TAG#v}"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
- name: Verify Cargo.toml version matches tag
run: |
CARGO_VERSION=$(grep -m1 '^version' Cargo.toml | sed -E 's/.*"([^"]+)".*/\1/')
if [ "$CARGO_VERSION" != "${{ steps.tag.outputs.version }}" ]; then
echo "::error::Cargo.toml version ($CARGO_VERSION) != tag (${{ steps.tag.outputs.version }})"
exit 1
fi
- name: Format
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --all-targets -- -D warnings
- name: Clippy (Python bindings, check-only)
run: cargo clippy --lib --features python -- -D warnings
- name: Test (includes c2pa-rs round-trip conformance)
run: cargo test
- name: Verify wasm32 build
run: cargo build --target wasm32-unknown-unknown
crates-io:
name: Publish to crates.io
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- name: cargo publish
run: cargo publish --token "$CARGO_REGISTRY_TOKEN"
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
npm:
name: Publish wasm package to npm
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- uses: jetli/wasm-pack-action@v0.4.0
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Build wasm package (c2pa-text-binding)
run: wasm-pack build --release --target web --out-dir pkg
- name: Publish to npm
run: npm publish
working-directory: pkg
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Stage package tarball for the release
run: |
npm pack --pack-destination "$GITHUB_WORKSPACE" >/dev/null
working-directory: pkg
- uses: actions/upload-artifact@v4
with:
name: npm-package
path: '*.tgz'
pypi-wheels:
name: Build wheel (${{ matrix.platform.runner }} ${{ matrix.platform.target }})
needs: validate
runs-on: ${{ matrix.platform.runner }}
strategy:
fail-fast: false
matrix:
platform:
- { runner: ubuntu-latest, target: x86_64 }
- { runner: ubuntu-latest, target: aarch64 }
- { runner: macos-latest, target: aarch64 }
- { runner: macos-13, target: x86_64 }
- { runner: windows-latest, target: x64 }
steps:
- uses: actions/checkout@v4
- name: Build wheel
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist
manylinux: auto
sccache: 'true'
- uses: actions/upload-artifact@v4
with:
name: wheels-${{ matrix.platform.runner }}-${{ matrix.platform.target }}
path: dist/*.whl
pypi-sdist:
name: Build sdist
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist
- uses: actions/upload-artifact@v4
with:
name: sdist
path: dist/*.tar.gz
pypi-publish:
name: Publish to PyPI (trusted publishing)
needs: [pypi-wheels, pypi-sdist]
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: wheels-*
path: dist
merge-multiple: true
- uses: actions/download-artifact@v4
with:
name: sdist
path: dist
- name: Publish
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist
github-release:
name: Create GitHub release
needs: [validate, crates-io, npm, pypi-publish]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/download-artifact@v4
with:
name: npm-package
path: dist
- name: Generate release notes
id: notes
uses: orhun/git-cliff-action@v4
with:
config: cliff.toml
args: --latest --strip header
env:
OUTPUT: RELEASE_NOTES.md
- name: Attest package provenance
uses: actions/attest-build-provenance@v2
with:
subject-path: dist/*.tgz
- name: Create release
uses: softprops/action-gh-release@v2
with:
name: c2pa-text-binding ${{ needs.validate.outputs.tag }}
tag_name: ${{ needs.validate.outputs.tag }}
draft: false
prerelease: ${{ contains(needs.validate.outputs.tag, '-') }}
body_path: RELEASE_NOTES.md
files: dist/*.tgz