multicalc 0.6.0

Rust scientific computing for single and multi-variable calculus
Documentation
name: Release

# Publishes to crates.io and cuts a GitHub release when a Cargo.toml version bump
# lands on main. Non-version edits and re-runs are no-ops (guarded on the tag).
on:
  push:
    branches: [main]
    paths: ['Cargo.toml']
  workflow_dispatch:

# never cancel an in-flight publish
concurrency: release

permissions:
  contents: write # create the tag + GitHub release

jobs:
  # Cheap, unguarded: work out whether this push is a new version and that it has
  # a changelog entry. Requesting manual approval only makes sense once we know
  # there is actually something to publish, so that lives in the `publish` job.
  prepare:
    runs-on: ubuntu-latest
    outputs:
      new: ${{ steps.tag.outputs.new }}
      version: ${{ steps.v.outputs.version }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # need tags to detect an already-released version

      - uses: dtolnay/rust-toolchain@stable

      - name: Resolve version from Cargo.toml
        id: v
        run: echo "version=$(cargo metadata --no-deps --format-version=1 | jq -r '.packages[0].version')" >> "$GITHUB_OUTPUT"

      - name: Is this version already tagged?
        id: tag
        run: |
          if git rev-parse "v${{ steps.v.outputs.version }}" >/dev/null 2>&1; then
            echo "new=false" >> "$GITHUB_OUTPUT"
            echo "v${{ steps.v.outputs.version }} already exists - nothing to release"
          else
            echo "new=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Require a changelog entry
        if: steps.tag.outputs.new == 'true'
        run: grep -q "^## \[${{ steps.v.outputs.version }}\]" CHANGELOG.md

  # Runs only for a genuinely new version. The `crates-io` environment is where a
  # manual-approval gate lives: add required reviewers to it in the repo settings and
  # this job pauses for sign-off before publishing. Until then it runs unguarded.
  publish:
    needs: prepare
    if: needs.prepare.outputs.new == 'true'
    runs-on: ubuntu-latest
    environment: crates-io
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: dtolnay/rust-toolchain@stable

      # write the notes OUTSIDE the repo so `cargo publish`'s clean-tree check still passes
      - name: Extract release notes for this version
        run: |
          awk -v ver="${{ needs.prepare.outputs.version }}" '
            $0 ~ ("^## \\[" ver "\\]") { capture=1; next }
            capture && /^## \[/ { exit }
            capture && /^\[.*\]:/ { exit }
            capture { print }
          ' CHANGELOG.md > "$RUNNER_TEMP/RELEASE_NOTES.md"
          cat "$RUNNER_TEMP/RELEASE_NOTES.md"

      # publish BEFORE tagging: a crates.io version is immutable, so the tag should
      # only appear once the publish has succeeded
      - name: Publish to crates.io
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: cargo publish

      - name: Tag and create the GitHub release
        uses: softprops/action-gh-release@v2
        with:
          tag_name: v${{ needs.prepare.outputs.version }}
          name: v${{ needs.prepare.outputs.version }}
          body_path: ${{ runner.temp }}/RELEASE_NOTES.md