cargo-readme 3.4.0

A cargo subcommand to generate README.md content from doc comments
Documentation
name: release

on:
  workflow_dispatch:
    inputs:
      version:
        description: "Version WITHOUT a leading v, e.g. 3.3.3 or 3.3.3-rc1 (must match Cargo.toml)"
        required: true
      sha:
        description: "Full commit SHA to release (must be on main)"
        required: true

# Serialize releases and let an active release finish.
concurrency:
  group: release
  cancel-in-progress: false

jobs:
  # Has no id-token or write access.
  verify:
    runs-on: ubuntu-latest
    timeout-minutes: 30
    permissions:
      contents: read

    steps:
      # Reject malformed versions before checking the repository.
      - name: Validate version input format
        env:
          VERSION: ${{ inputs.version }}
        run: |
          # Accept stable versions and versions with a prerelease suffix.
          if ! echo "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then
            echo "::error::version must be semver like 3.3.3 or 3.3.3-rc1 (no leading 'v')."
            exit 1
          fi

      # Ensure releases can only be initiated from main.
      - name: Restrict to main branch
        # Compare the full ref because branch names alone are ambiguous.
        if: github.ref != 'refs/heads/main'
        run: |
          echo "::error::This workflow must be run from main (ref was '${{ github.ref }}')."
          exit 1

      # Check out the exact commit requested for release.
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0, 2026-06-18
        with:
          # Preserve history for the ancestry and tag checks.
          fetch-depth: 0
          # Release the requested commit, not the workflow trigger commit.
          ref: ${{ inputs.sha }}

      # Confirm the requested commit is reachable from main.
      - name: Validate commit is on main
        env:
          RELEASE_SHA: ${{ inputs.sha }}
        run: |
          # An ancestor check prevents releasing an unrelated commit.
          if ! git merge-base --is-ancestor "$RELEASE_SHA" origin/main; then
            echo "::error::Input SHA '$RELEASE_SHA' is not an ancestor of main."
            exit 1
          fi

      # Install the Rust toolchain used by the project.
      - name: Install Rust
        uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0

      # Ensure the requested version matches the checked-out manifest.
      - name: Validate version matches Cargo.toml
        env:
          VERSION: ${{ inputs.version }}
        run: |
          # Read the package version from Cargo's normalized metadata.
          manifest_version=$(cargo metadata --no-deps --format-version=1 | jq -r '.packages[0].version')
          if [ "$manifest_version" != "$VERSION" ]; then
            echo "::error::Input version '$VERSION' does not match Cargo.toml version '$manifest_version'."
            exit 1
          fi

      # Prevent the release tag from being reused.
      - name: Validate tag does not already exist
        env:
          VERSION: ${{ inputs.version }}
        run: |
          # Query the remote tag directly without fetching every tag.
          if git ls-remote --tags origin "refs/tags/v$VERSION" | grep -q .; then
            echo "::error::Tag v$VERSION already exists."
            exit 1
          fi

      # Prevent publishing a version that crates.io already knows about.
      - name: Validate version not already published
        env:
          VERSION: ${{ inputs.version }}
        run: |
          # A successful API response means this exact version is published.
          if curl -sf "https://crates.io/api/v1/crates/cargo-readme/$VERSION" > /dev/null; then
            echo "::error::cargo-readme $VERSION is already published on crates.io."
            exit 1
          fi

      # Run the project's complete pre-release validation suite.
      - name: Run CI checks
        run: make ci

  # Privileged step: only runs after verify passes.
  publish:
    needs: verify
    runs-on: ubuntu-latest
    timeout-minutes: 30
    environment: release
    permissions:
      contents: write
      id-token: write

    steps:
      # Check out the exact commit that passed verification.
      - name: Checkout
        uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0, 2026-06-18
        with:
          # Preserve history for tagging and release creation.
          fetch-depth: 0
          # Publish the verified commit rather than the workflow trigger commit.
          ref: ${{ inputs.sha }}

      # Install the Rust toolchain needed for publishing.
      - name: Install Rust
        uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0

      # Create and push the version tag.
      - name: Tag release
        env:
          VERSION: ${{ inputs.version }}
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git tag -a "v$VERSION" -m "v$VERSION"
          git push origin "v$VERSION"

      # Create the GitHub release and generate its notes.
      - name: Create GitHub release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          VERSION: ${{ inputs.version }}
        run: |
          prerelease_flag=""
          # Treat any version with a suffix as a prerelease.
          case "$VERSION" in
            *-*) prerelease_flag="--prerelease" ;;
          esac
          # Pass the optional prerelease flag only when it is needed.
          gh release create "v$VERSION" \
            --title "v$VERSION" \
            $prerelease_flag \
            --generate-notes

      # Obtain a short-lived token for crates.io.
      - name: Authenticate with crates.io
        id: auth
        uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5

      # Publish only after all other release actions have succeeded.
      - name: Publish to crates.io
        run: cargo publish --locked
        env:
          # Use the token issued by the authentication step.
          CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}