data-modelling-sdk 2.4.0

Shared SDK for model operations across platforms (API, WASM, Native)
Documentation
name: Release SDK to crates.io

on:
  workflow_dispatch: # Allow manual triggering
  workflow_call: # Allow being called from publish.yml
    inputs:
      version:
        description: "Version to release (without v prefix)"
        required: false
        type: string
    outputs:
      version:
        description: "The published version"
        value: ${{ jobs.publish.outputs.version }}

env:
  CARGO_TERM_COLOR: always

permissions:
  contents: write
  issues: write

jobs:
  version-check:
    name: Version Check
    uses: ./.github/workflows/version-check.yml

  publish:
    name: Publish to crates.io
    needs: version-check
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.version }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # Required for git tag operations
          token: ${{ secrets.GITHUB_TOKEN }}

      - name: Install Rust
        uses: dtolnay/rust-toolchain@stable

      - name: Cache cargo registry
        uses: actions/cache@v4
        with:
          path: |
            ~/.cargo/bin/
            ~/.cargo/registry/index/
            ~/.cargo/registry/cache/
            ~/.cargo/git/db/
            target/
          key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.toml') }}
          restore-keys: |
            ${{ runner.os }}-cargo-

      - name: Get version
        id: version
        env:
          INPUT_VERSION: ${{ inputs.version }}
          VERIFIED_VERSION: ${{ needs.version-check.outputs.version }}
        run: |
          if [ -n "$INPUT_VERSION" ]; then
            VERSION="$INPUT_VERSION"
          elif [ -n "$VERIFIED_VERSION" ]; then
            VERSION="$VERIFIED_VERSION"
          else
            VERSION=$(grep -A 10 '^\[package\]' Cargo.toml | grep '^version = ' | head -1 | sed 's/version = "\(.*\)"/\1/')
          fi
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "Version: $VERSION"

      - name: Check if tag already exists
        id: check-tag
        run: |
          TAG_NAME="v${{ steps.version.outputs.version }}"
          if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
            echo "exists=true" >> $GITHUB_OUTPUT
            echo "Tag $TAG_NAME already exists"
          else
            echo "exists=false" >> $GITHUB_OUTPUT
            echo "Tag $TAG_NAME does not exist"
          fi

      - name: Create git tag
        if: steps.check-tag.outputs.exists == 'false'
        run: |
          TAG_NAME="v${{ steps.version.outputs.version }}"
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git tag -a "$TAG_NAME" -m "Release $TAG_NAME"
          git push origin "$TAG_NAME"
          echo "Created and pushed tag: $TAG_NAME"

      - name: Extract referenced issues from commits
        id: extract-issues
        run: |
          # Get commits since last tag (or all commits if no tag exists)
          LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
          if [ -z "$LAST_TAG" ]; then
            COMMITS=$(git log --pretty=format:"%s %b" --no-merges)
          else
            COMMITS=$(git log ${LAST_TAG}..HEAD --pretty=format:"%s %b" --no-merges)
          fi

          # Extract issue numbers from commit messages
          ISSUES=$(echo "$COMMITS" | grep -oE '(closes|fixes|resolves|implements|addresses)?\s*#?[0-9]+' | grep -oE '[0-9]+' | sort -u | tr '\n' ' ' || echo "")

          if [ -z "$ISSUES" ]; then
            echo "No referenced issues found in commits"
            echo "issues=" >> $GITHUB_OUTPUT
          else
            echo "Found referenced issues: $ISSUES"
            echo "issues=$ISSUES" >> $GITHUB_OUTPUT
          fi

      - name: Close referenced issues
        if: steps.extract-issues.outputs.issues != ''
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          VERSION="${{ steps.version.outputs.version }}"
          ISSUES="${{ steps.extract-issues.outputs.issues }}"

          for issue in $ISSUES; do
            echo "Checking issue #$issue..."
            ISSUE_STATE=$(gh issue view $issue --repo ${{ github.repository }} --json state -q .state 2>/dev/null || echo "notfound")

            if [ "$ISSUE_STATE" = "notfound" ]; then
              echo "  Issue #$issue not found, skipping"
              continue
            elif [ "$ISSUE_STATE" = "closed" ]; then
              echo "  Issue #$issue is already closed, skipping"
              continue
            elif [ "$ISSUE_STATE" = "open" ]; then
              echo "  Closing issue #$issue..."
              gh issue close $issue --repo ${{ github.repository }} --comment "Closed automatically as part of release v$VERSION." || echo "  Failed to close issue #$issue"
            fi
          done

      - name: Login to crates.io
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: |
          echo "$CARGO_REGISTRY_TOKEN" | cargo login

      # Publish crates in dependency order: core first, then root SDK
      - name: Publish data-modelling-core to crates.io
        run: |
          cd crates/core
          cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
          echo "Waiting for crates.io to index the package..."
          sleep 60

      - name: Publish data-modelling-sdk to crates.io
        run: |
          cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}