bgpkit-parser 0.20.0

MRT/BGP/BMP data processing library
Documentation
name: Release

permissions:
  contents: write
  id-token: write

on:
  push:
    tags:
      - v[0-9]+.*
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to validate (e.g. 0.20.0); publish steps are always skipped in dispatch mode'
        required: false
        default: ''

env:
  DRY_RUN: ${{ github.event_name == 'workflow_dispatch' }}

jobs:
  check-version:
    name: Preflight (version + changelog consistency)
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Check tag, Cargo, npm, and CHANGELOG versions agree
        run: |
          set -eu
          CARGO_VERSION="$(grep -m1 '^version' Cargo.toml | sed 's/version = "\(.*\)"/\1/')"
          WASM_VERSION="$(grep -m1 '"version"' src/wasm/js/package.json | sed 's/.*"version": "\(.*\)".*/\1/')"
          echo "cargo=${CARGO_VERSION} wasm=${WASM_VERSION}"

          if [ "${DRY_RUN}" = "true" ]; then
            TARGET_VERSION="${{ github.event.inputs.version }}"
            if [ -z "${TARGET_VERSION}" ]; then
              TARGET_VERSION="${CARGO_VERSION}"
            fi
          else
            TARGET_VERSION="${GITHUB_REF_NAME#v}"
          fi
          echo "target=${TARGET_VERSION}"

          if [ "${CARGO_VERSION}" != "${TARGET_VERSION}" ]; then
            echo "::error::Cargo.toml version ${CARGO_VERSION} != tag/dispatch ${TARGET_VERSION}" && exit 1
          fi
          if [ "${WASM_VERSION}" != "${TARGET_VERSION}" ]; then
            echo "::error::src/wasm/js/package.json version ${WASM_VERSION} != ${TARGET_VERSION}" && exit 1
          fi
          if ! grep -qE "^## v${TARGET_VERSION} - [0-9]{4}-[0-9]{2}-[0-9]{2}$" CHANGELOG.md; then
            echo "::error::CHANGELOG.md has no '## v${TARGET_VERSION} - YYYY-MM-DD' section" && exit 1
          fi
          echo "versions consistent: v${TARGET_VERSION}"

  release-test:
    name: Build & Test (release gate)
    needs: check-version
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: Swatinem/rust-cache@v2
      - name: Run tests
        run: cargo test --all-features

  release-format-check:
    name: Format + README check
    needs: check-version
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run format check
        run: cargo fmt --check
      - name: Run cargo-readme check
        run: cargo install cargo-readme --locked && cargo readme > TMP_README.md && diff -b TMP_README.md README.md

  security-audit:
    name: Security audit
    needs: check-version
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install cargo-audit
        run: cargo install cargo-audit --locked
      - name: Generate lockfile
        run: cargo generate-lockfile
      - name: Run cargo audit
        run: cargo audit

  cargo-publish:
    name: Publish to crates.io
    needs: [release-test, release-format-check, security-audit]
    if: needs.release-test.result == 'success' && needs.release-format-check.result == 'success' && needs.security-audit.result == 'success' && github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Publish to crates.io
        continue-on-error: true
        id: crates_publish
        run: >
          cargo publish
          --all-features
          --verbose
          --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
      - name: Fail loudly if publication did not succeed on a tag push
        if: steps.crates_publish.outcome == 'failure'
        run: |
          echo "::error::cargo publish failed"
          echo "::error::If this crate version was already published to crates.io in a previous attempt of this tag, re-run this failed job only (Re-run failed jobs). Do NOT re-push the tag: crates.io versions are immutable and re-publishing always fails."
          exit 1

  npm-publish:
    name: Publish @bgpkit/parser to npm
    needs: [release-test, release-format-check, security-audit]
    if: needs.release-test.result == 'success' && needs.release-format-check.result == 'success' && needs.security-audit.result == 'success' && github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          registry-url: https://registry.npmjs.org
          scope: '@bgpkit'
      - name: Install wasm-pack
        run: cargo install wasm-pack --locked
      - name: Add wasm target
        run: rustup target add wasm32-unknown-unknown
      - name: Build WASM npm package
        run: bash src/wasm/build.sh
      - name: Upgrade npm for trusted publishing
        run: npm install -g npm@latest
      - name: Publish @bgpkit/parser to npm
        continue-on-error: true
        id: npm_publish
        working-directory: pkg
        run: npm publish --access public --provenance
      - name: Fail loudly if publication did not succeed on a tag push
        if: steps.npm_publish.outcome == 'failure'
        run: |
          echo "::error::npm publish failed"
          echo "::error::If this version was already published to npm in a previous attempt of this tag, re-run this failed job only (Re-run failed jobs). Do NOT re-push the tag: npm versions are immutable."
          exit 1

  create-release:
    name: Create GitHub release
    needs: [cargo-publish, npm-publish]
    if: always() && github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Confirm both registries succeeded
        run: |
          if [ "${{ needs.cargo-publish.result }}" != "success" ] || [ "${{ needs.npm-publish.result }}" != "success" ]; then
            echo "::error::Refusing to create GitHub release: cargo-publish=${{ needs.cargo-publish.result }} npm-publish=${{ needs.npm-publish.result }}"
            exit 1
          fi
      - uses: taiki-e/create-gh-release-action@v1
        with:
          changelog: CHANGELOG.md
          token: ${{ secrets.GITHUB_TOKEN }}

  upload-assets:
    name: Upload binary assets
    needs: create-release
    if: github.event_name == 'push'
    strategy:
      matrix:
        include:
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-latest
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
          - target: universal-apple-darwin
            os: macos-latest
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: taiki-e/upload-rust-binary-action@v1
        with:
          bin: bgpkit-parser
          checksum: sha256
          features: cli
          target: ${{ matrix.target }}
          token: ${{ secrets.GITHUB_TOKEN }}