winx-code-agent 0.2.336

Remote-first Rust MCP runtime with Streamable HTTP, durable PTYs, and guarded agent tools
Documentation
name: Release

on:
  push:
    tags:
      - "v*"

permissions:
  contents: write
  id-token: write
  attestations: write
  artifact-metadata: write

env:
  CARGO_TERM_COLOR: always
  CARGO_INCREMENTAL: "0"
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

jobs:
  quality:
    uses: ./.github/workflows/ci.yml

  build:
    name: Build ${{ matrix.target }}
    needs: quality
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu-latest
            target: x86_64-unknown-linux-gnu
            asset_name: winx-linux-amd64.tar.gz
            bundle: true
          - os: macos-26
            target: aarch64-apple-darwin
            asset_name: winx-macos-arm64.tar.gz
            bundle: true
          - os: windows-latest
            target: x86_64-pc-windows-msvc
            asset_name: winx-windows-amd64.exe
            bundle: false

    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

      - name: Setup Rust target
        uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
        with:
          toolchain: stable
          targets: ${{ matrix.target }}

      - name: Rust cache
        uses: Swatinem/rust-cache@401aff9a7a08acb9d27b64936a90db81024cff97 # v2.8.2
        with:
          key: ${{ matrix.target }}-release

      - name: Build Unix release bundle
        if: matrix.bundle
        run: cargo build --release --locked --target ${{ matrix.target }} --bins

      - name: Build Windows standalone binary
        if: ${{ !matrix.bundle }}
        run: cargo build --release --locked --target ${{ matrix.target }} --bin winx-code-agent

      - name: Stage Unix release bundle
        if: matrix.bundle
        shell: bash
        run: |
          set -euo pipefail
          mkdir -p dist/stage
          release_dir="target/${{ matrix.target }}/release"
          cp "$release_dir/winx-code-agent" "$release_dir/winxd" "$release_dir/winx-guardian" dist/stage/
          tar -C dist/stage -czf "dist/${{ matrix.asset_name }}" winx-code-agent winxd winx-guardian

      - name: Stage Windows standalone binary
        if: ${{ !matrix.bundle }}
        shell: bash
        run: |
          set -euo pipefail
          mkdir -p dist
          cp "target/${{ matrix.target }}/release/winx-code-agent.exe" "dist/${{ matrix.asset_name }}"

      - name: Generate artifact checksum
        shell: bash
        run: |
          set -euo pipefail
          cd dist
          if command -v sha256sum >/dev/null 2>&1; then
            sha256sum "${{ matrix.asset_name }}" > "${{ matrix.asset_name }}.sha256"
          else
            shasum -a 256 "${{ matrix.asset_name }}" > "${{ matrix.asset_name }}.sha256"
          fi

      - name: Attest build artifact provenance
        uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1
        with:
          subject-path: dist/${{ matrix.asset_name }}

      - name: Upload release artifact
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: release-${{ matrix.target }}
          path: |
            dist/${{ matrix.asset_name }}
            dist/${{ matrix.asset_name }}.sha256
          if-no-files-found: error
          retention-days: 5

  sbom:
    name: Generate CycloneDX SBOM
    needs: quality
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
        with:
          toolchain: stable

      - name: Rust cache
        uses: Swatinem/rust-cache@401aff9a7a08acb9d27b64936a90db81024cff97 # v2.8.2
        with:
          key: sbom-tool
          cache-bin: true

      - name: Install cargo-cyclonedx
        run: cargo install cargo-cyclonedx --locked --version 0.5.9

      - name: Generate reproducible JSON SBOM
        shell: bash
        run: |
          set -euo pipefail
          export SOURCE_DATE_EPOCH="$(git log -1 --format=%ct)"
          cargo cyclonedx --format json --all-features
          mkdir -p dist
          sbom="$(find . -maxdepth 2 -type f \( -name '*.cdx.json' -o -name 'bom.json' \) -print -quit)"
          test -n "$sbom"
          # actions/attest requires bomFormat + specVersion + serialNumber, but
          # cargo-cyclonedx omits serialNumber. Inject a deterministic UUIDv5
          # derived from repo+commit so the SBOM stays reproducible.
          serial="urn:uuid:$(uuidgen --sha1 --namespace @url --name "https://github.com/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}")"
          jq --arg serial "$serial" '.serialNumber = $serial' "$sbom" > dist/winx-code-agent.cdx.json
          (cd dist && sha256sum winx-code-agent.cdx.json > winx-code-agent.cdx.json.sha256)

      - name: Upload SBOM
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: release-sbom
          path: |
            dist/winx-code-agent.cdx.json
            dist/winx-code-agent.cdx.json.sha256
          if-no-files-found: error
          retention-days: 5

  publish:
    name: Publish to crates.io
    needs: [build, sbom]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

      - name: Setup Rust
        uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c # stable
        with:
          toolchain: stable

      - name: Rust cache
        uses: Swatinem/rust-cache@401aff9a7a08acb9d27b64936a90db81024cff97 # v2.8.2
        with:
          key: publish

      - name: Verify tag matches Cargo.toml
        shell: bash
        run: |
          set -euo pipefail
          cargo_version="$(awk -F '"' '/^version = / { print $2; exit }' Cargo.toml)"
          tag_version="${GITHUB_REF#refs/tags/v}"
          if [[ "$cargo_version" != "$tag_version" ]]; then
            echo "::error::Cargo.toml version ($cargo_version) != tag ($tag_version)"
            exit 1
          fi
          echo "version=$cargo_version" >> "$GITHUB_ENV"

      - name: Skip if version already exists on crates.io
        id: check
        shell: bash
        run: |
          status="$(curl -sf -A 'winx-ci/1.0' \
            "https://crates.io/api/v1/crates/winx-code-agent/${{ env.version }}" \
            -o /dev/null -w '%{http_code}' || true)"
          if [[ "$status" == "200" ]]; then
            echo "winx-code-agent ${{ env.version }} already exists on crates.io — skipping"
            echo "skip=true" >> "$GITHUB_OUTPUT"
          else
            echo "skip=false" >> "$GITHUB_OUTPUT"
          fi

      - name: Cargo publish
        if: steps.check.outputs.skip != 'true'
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }}
        run: cargo publish --locked

  release:
    name: Publish GitHub Release
    needs: [build, sbom, publish]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

      - name: Download all release artifacts
        uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
        with:
          path: dist
          merge-multiple: true

      - name: Verify individual checksums and generate aggregate checksum
        shell: bash
        run: |
          set -euo pipefail
          cd dist
          for checksum in *.sha256; do
            sha256sum --check "$checksum"
          done
          sha256sum winx-*.tar.gz winx-*.exe winx-code-agent.cdx.json > SHA256SUMS

      - name: Attest release artifacts with their SBOM
        uses: actions/attest@508db95dd578ae2727ebd6217d5ba78e4fbda05d # v4.2.1
        with:
          subject-path: |
            dist/winx-*.tar.gz
            dist/winx-*.exe
          sbom-path: dist/winx-code-agent.cdx.json

      - name: Publish release
        uses: softprops/action-gh-release@62c96d0c4e8a889135c1f3a25910db8dbe0e85f7 # v2.3.4
        with:
          tag_name: ${{ github.ref_name }}
          name: ${{ github.ref_name }}
          generate_release_notes: true
          fail_on_unmatched_files: true
          files: dist/*