Files
ferro-ta/.github/workflows/CI.yml
T
Pratik Bhadane 682bf063ca release: cut v1.0.1
Prepare the first patch release after v1.0.0 by finalizing the outstanding release, packaging, and workflow fixes.

This release keeps PyPI and crates.io publishing anchored to CI.yml, splits the large CI workflow into focused rust/python/wasm/docs suites, fixes the ci-complete gate, and updates the release SBOM action pin.

It also switches the npm publish workflow to GitHub OIDC, installs the wasm32 target required for packaging, ensures the WASM npm tarball includes pkg/ artifacts during prepack, and adds a dedicated README plus docs.rs metadata for ferro_ta_core.

Finally, bump all published package versions to 1.0.1 and record the patch release notes in CHANGELOG.md.
2026-03-24 00:38:50 +05:30

258 lines
8.3 KiB
YAML

name: CI
on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
release:
types: [published]
permissions:
contents: read
jobs:
# -------------------------------------------------------------------------
# Version consistency — Cargo.toml and pyproject.toml must have same version
# -------------------------------------------------------------------------
changelog-check:
name: CHANGELOG has [Unreleased] entry
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v6
- name: Check CHANGELOG.md
run: python3 scripts/check_changelog.py
version-check:
name: Version consistency (Cargo.toml == pyproject.toml)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Check version parity
run: |
CARGO_VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
PYPROJECT_VERSION=$(grep '^version' pyproject.toml | head -1 | sed 's/version = "\(.*\)"/\1/')
echo "Cargo.toml version: $CARGO_VERSION"
echo "pyproject.toml version: $PYPROJECT_VERSION"
if [ "$CARGO_VERSION" != "$PYPROJECT_VERSION" ]; then
echo "ERROR: Version mismatch! Update both files before releasing."
exit 1
fi
echo "OK: versions match ($CARGO_VERSION)"
# -------------------------------------------------------------------------
# Modularized CI suites.
# Keep release publishing jobs in this file so trusted-publisher bindings
# remain anchored to CI.yml for PyPI and crates.io.
# -------------------------------------------------------------------------
rust-suite:
name: Rust and audit
uses: ./.github/workflows/ci-rust.yml
python-suite:
name: Python quality and tests
uses: ./.github/workflows/ci-python.yml
wasm:
name: WASM binding
uses: ./.github/workflows/ci-wasm.yml
docs:
name: Documentation
uses: ./.github/workflows/ci-docs.yml
with:
upload-pages-artifact: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
# -------------------------------------------------------------------------
# Deploy docs to GitHub Pages (on push to main only)
# -------------------------------------------------------------------------
deploy-docs:
name: Deploy docs to GitHub Pages
runs-on: ubuntu-latest
needs: docs
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
permissions:
pages: write
id-token: write
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
# -------------------------------------------------------------------------
# CI gate — all required jobs must pass before this job succeeds.
# Set "ci-complete" as a required status check in branch protection rules
# to block merging of PRs that fail any required job.
# -------------------------------------------------------------------------
ci-complete:
name: CI complete (required gate)
runs-on: ubuntu-latest
needs:
- changelog-check
- version-check
- rust-suite
- python-suite
- wasm
- docs
if: always()
steps:
- name: Check all required jobs passed
run: |
results='${{ toJSON(needs) }}'
echo "Job results: $results"
failed="$(
RESULTS_JSON="$results" python3 - <<'PY'
import json
import os
needs = json.loads(os.environ["RESULTS_JSON"])
failed = [
name
for name, data in needs.items()
if data["result"] not in ("success", "skipped")
]
if failed:
print(" ".join(failed))
PY
)"
if [ -n "$failed" ]; then
echo "FAILED jobs: $failed"
exit 1
fi
echo "All required CI jobs passed."
# -------------------------------------------------------------------------
# Build wheels for all platforms and publish to PyPI on release
# Keep release jobs here because trusted-publisher configuration points to
# CI.yml specifically.
# -------------------------------------------------------------------------
build-wheels:
name: Build wheels (${{ matrix.os }})
runs-on: ${{ matrix.os }}
if: github.event_name == 'release' && github.event.action == 'published'
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v6
- name: Build wheels
uses: PyO3/maturin-action@v1
with:
command: build
args: --release --out dist
manylinux: auto
# Build for both Intel and Apple Silicon on macOS
target: ${{ matrix.os == 'macos-latest' && 'universal2-apple-darwin' || '' }}
- name: Upload wheels as artifact
uses: actions/upload-artifact@v7
with:
name: wheels-${{ matrix.os }}
path: dist
# -------------------------------------------------------------------------
# Publish to PyPI
# -------------------------------------------------------------------------
publish:
name: Publish to PyPI
runs-on: ubuntu-latest
needs: build-wheels
if: github.event_name == 'release' && github.event.action == 'published'
environment:
name: pypi
url: https://pypi.org/p/ferro-ta
permissions:
id-token: write
steps:
- name: Download all wheels
uses: actions/download-artifact@v8
with:
pattern: wheels-*
merge-multiple: true
path: dist
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
# -------------------------------------------------------------------------
# Publish ferro_ta_core to crates.io (requires CARGO_REGISTRY_TOKEN secret)
# -------------------------------------------------------------------------
publish-cratesio:
name: Publish to crates.io
runs-on: ubuntu-latest
if: github.event_name == 'release' && github.event.action == 'published'
steps:
- uses: actions/checkout@v6
- name: Install Rust
uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
- name: Publish ferro_ta_core to crates.io
run: cargo publish -p ferro_ta_core --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
# -------------------------------------------------------------------------
# SBOM generation — Software Bill of Materials for supply-chain transparency
# Generates SBOMs for both Python (syft/CycloneDX) and Rust (cargo-cyclonedx)
# and uploads them as GitHub Release assets.
# -------------------------------------------------------------------------
sbom:
name: Generate SBOM (Python + Rust)
runs-on: ubuntu-latest
needs: build-wheels
if: github.event_name == 'release' && github.event.action == 'published'
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v6
- name: Set up Python 3.12
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install maturin
run: pip install maturin numpy
- name: Build and install ferro_ta wheel
run: |
maturin build --release --out dist
pip install dist/*.whl
- name: Generate Python SBOM (CycloneDX via anchore/sbom-action)
uses: anchore/sbom-action@v0.24.0
with:
artifact-name: ferro-ta-python-sbom.spdx.json
output-file: ferro-ta-python-sbom.spdx.json
format: spdx-json
- name: Install Rust stable
uses: dtolnay/rust-toolchain@v1
with:
toolchain: stable
- name: Install cargo-cyclonedx
run: cargo install cargo-cyclonedx --locked
- name: Generate Rust SBOM (CycloneDX)
run: cargo cyclonedx --format json --output-cdx ferro-ta-rust-sbom.cdx.json
- name: Upload Python SBOM to release
uses: softprops/action-gh-release@v2
with:
files: ferro-ta-python-sbom.spdx.json
- name: Upload Rust SBOM to release
uses: softprops/action-gh-release@v2
with:
files: ferro-ta-rust-sbom.cdx.json