name: release

# Triggered by pushing a tag like `v0.1.0`. Publishes the Rust crate to
# crates.io, the Node native package and the WASM package to npm, and Python
# wheels to PyPI.
#
# Secrets / setup required (see .local/ci-config.md):
#   * CARGO_REGISTRY_TOKEN  -> https://crates.io settings (or switch to Trusted Publishing)
#   * NPM_TOKEN             -> https://npmjs.com access token (Automation / Granular)
#   * PyPI Trusted Publishing: register this workflow + `pypi` environment at
#     https://pypi.org/manage/project/eregex/settings/publishing/
#
# All four manifests must report the same version as the tag:
#   Cargo.toml, crates/eregex-node/package.json, crates/eregex-wasm/package.json,
#   crates/eregex-python/pyproject.toml

on:
  push:
    tags:
      - "v*.*.*"

permissions:
  contents: read
  id-token: write

env:
  CARGO_TERM_COLOR: always

jobs:
  check-version:
    name: Check version consistency
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Verify versions match tag
        shell: bash
        run: |
          set -euo pipefail
          TAG="${GITHUB_REF_NAME#v}"

          # The single source of truth is `[workspace.package].version` in the
          # root Cargo.toml; every member crate inherits it via
          # `version.workspace = true`. We extract that one value and confirm
          # each crate is actually inheriting (no literal version overriding it).
          WS_VERSION=$(awk '
            /^\[workspace\.package\]/ { in_ws=1; next }
            /^\[/                      { in_ws=0 }
            in_ws && /^version *=/      { gsub(/version *= *"|"$/, ""); print; exit }
          ' Cargo.toml)

          NODE_VERSION=$(grep -m1 '"version"' crates/eregex-node/package.json | sed 's/.*: "\(.*\)".*/\1/' | tr -d ',')
          WASM_VERSION=$(grep -m1 '"version"' crates/eregex-wasm/package.json | sed 's/.*: "\(.*\)".*/\1/' | tr -d ',')
          PY_VERSION=$(grep -m1 '^version =' crates/eregex-python/pyproject.toml | sed 's/version = "\(.*\)"/\1/')

          echo "tag=$TAG"
          echo "cargo (workspace) = $WS_VERSION"
          echo "npm (native)      = $NODE_VERSION"
          echo "npm (wasm)        = $WASM_VERSION"
          echo "pypi              = $PY_VERSION"

          # Every Cargo crate must inherit the workspace version (no literals).
          grep -q '^version\.workspace = true$' Cargo.toml
          grep -q '^version\.workspace = true$' crates/eregex-node/Cargo.toml
          grep -q '^version\.workspace = true$' crates/eregex-python/Cargo.toml
          grep -q '^version\.workspace = true$' crates/eregex-wasm/Cargo.toml

          test "$WS_VERSION" = "$TAG"
          test "$NODE_VERSION" = "$TAG"
          test "$WASM_VERSION" = "$TAG"
          test "$PY_VERSION" = "$TAG"

  test:
    name: Test Rust core
    runs-on: ubuntu-latest
    needs: check-version
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      - name: Test workspace
        run: cargo test --workspace --all-features

  publish-crate:
    name: Publish crate to crates.io
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable

      - uses: Swatinem/rust-cache@v2

      - name: Publish
        run: cargo publish -p eregex
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

  build-node:
    name: Build Node (${{ matrix.target }})
    needs: test
    strategy:
      fail-fast: false
      matrix:
        include:
          - { os: ubuntu-latest,   target: x86_64-unknown-linux-gnu  }
          - { os: ubuntu-24.04-arm, target: aarch64-unknown-linux-gnu }
          - { os: macos-latest,    target: aarch64-apple-darwin      }
          - { os: macos-13,        target: x86_64-apple-darwin       }
          - { os: windows-latest,  target: x86_64-pc-windows-msvc    }
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
          cache-dependency-path: crates/eregex-node/package-lock.json

      - name: Install dependencies
        working-directory: crates/eregex-node
        run: npm ci

      - name: Build native addon
        working-directory: crates/eregex-node
        run: npx napi build --release --platform --target ${{ matrix.target }}

      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: node-${{ matrix.target }}
          path: |
            crates/eregex-node/*.node
            crates/eregex-node/index.js
            crates/eregex-node/index.d.ts

  publish-node:
    name: Publish to npm
    runs-on: ubuntu-latest
    needs: build-node
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 22
          registry-url: https://registry.npmjs.org
          cache: npm
          cache-dependency-path: crates/eregex-node/package-lock.json

      - name: Install dependencies
        working-directory: crates/eregex-node
        run: npm ci

      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          pattern: node-*
          path: crates/eregex-node
          merge-multiple: true

      - name: Publish
        working-directory: crates/eregex-node
        run: npm publish --access public --provenance
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

  build-wasm:
    name: Build WASM
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: wasm32-unknown-unknown

      - uses: Swatinem/rust-cache@v2

      - uses: actions/setup-node@v4
        with:
          node-version: 22

      - name: Install wasm-pack
        run: cargo install wasm-pack --locked

      # `npm run build` runs `wasm-pack build --target nodejs --release` and
      # then `scripts/assemble-pkg.cjs`, which drops the hand-written `index.js`
      # entry into `pkg/` and sets it as `main` (materializing the flag
      # constants that wasm-bindgen cannot export as `const`).
      - name: Build + assemble WASM package
        working-directory: crates/eregex-wasm
        run: npm run build

      - name: Test WASM package
        working-directory: crates/eregex-wasm
        run: npm test

      - name: Upload WASM artifact
        uses: actions/upload-artifact@v4
        with:
          name: wasm-pkg
          path: crates/eregex-wasm/pkg

  publish-wasm:
    name: Publish WASM to npm
    runs-on: ubuntu-latest
    needs: build-wasm
    steps:
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          registry-url: https://registry.npmjs.org

      - uses: actions/download-artifact@v4
        with:
          name: wasm-pkg
          path: pkg

      - name: Publish
        working-directory: pkg
        run: npm publish --access public --provenance
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

  build-python:
    name: Build Python (${{ matrix.os }})
    needs: test
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest, macos-13]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4

      - uses: dtolnay/rust-toolchain@stable

      - uses: PyO3/maturin-action@v1
        with:
          command: build
          args: --release --manifest-path crates/eregex-python/Cargo.toml --out dist
          manylinux: auto

      - name: Upload wheels
        uses: actions/upload-artifact@v4
        with:
          name: python-wheels-${{ matrix.os }}
          path: dist

  publish-python:
    name: Publish to PyPI
    runs-on: ubuntu-latest
    needs: build-python
    environment:
      name: pypi
    steps:
      - name: Download wheels
        uses: actions/download-artifact@v4
        with:
          pattern: python-wheels-*
          path: dist
          merge-multiple: true

      - name: Publish
        uses: pypa/gh-action-pypi-publish@release/v1
