sharepoint-cli 0.0.10

Agent-friendly SharePoint Online CLI with JSON output, structured exit codes, and schema introspection
Documentation
name: Release

on:
  push:
    tags:
      - "v*"
  workflow_dispatch:
    inputs:
      version:
        description: "Version to rehearse (defaults to Cargo.toml)"
        required: false
        type: string
      dry_run:
        description: "Build and validate without publishing"
        required: true
        default: true
        type: boolean

permissions:
  contents: read

jobs:
  metadata:
    name: Validate release metadata
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.release.outputs.version }}
      tag: ${{ steps.release.outputs.tag }}
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
      - name: Resolve and validate release metadata
        id: release
        shell: bash
        env:
          REQUESTED_VERSION: ${{ inputs.version }}
          DRY_RUN: ${{ inputs.dry_run }}
        run: |
          manifest_version=$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
          version="${REQUESTED_VERSION#v}"
          version="${version:-$manifest_version}"
          [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+([+-][0-9A-Za-z.-]+)?$ ]]
          test "$manifest_version" = "$version"
          tag="v${version}"
          if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then
            test "$GITHUB_REF_NAME" = "$tag"
          elif [[ "$DRY_RUN" != "true" ]]; then
            test "$GITHUB_REF_TYPE" = "tag"
            test "$GITHUB_REF_NAME" = "$tag"
          fi
          echo "version=$version" >> "$GITHUB_OUTPUT"
          echo "tag=$tag" >> "$GITHUB_OUTPUT"

  check:
    needs: metadata
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
      - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
        with:
          toolchain: 1.96.0
          components: rustfmt, clippy
      - uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6
      - uses: taiki-e/install-action@82cd3e7658a6f96c86c0234aeeda1748937cb0a1
        with:
          tool: nextest
      - run: make fmt-check
      - run: make lint
      - run: make test

  build:
    needs: [metadata, check]
    if: github.event_name != 'pull_request'
    strategy:
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
            maturin_args: --compatibility manylinux_2_28 --zig
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-latest
            maturin_args: --compatibility manylinux_2_28 --zig
          - target: x86_64-apple-darwin
            os: macos-latest
            maturin_args: ""
          - target: aarch64-apple-darwin
            os: macos-latest
            maturin_args: ""
          - target: x86_64-pc-windows-msvc
            os: windows-latest
            maturin_args: ""
    runs-on: ${{ matrix.os }}
    permissions:
      contents: read
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1

      - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
        with:
          toolchain: 1.96.0
          components: rustfmt, clippy
          targets: ${{ matrix.target }}

      - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d

      - name: Install maturin and zig
        shell: bash
        run: |
          uv venv "${RUNNER_TEMP}/build-venv"
          if [[ "$RUNNER_OS" == "Windows" ]]; then
            VENV_BIN="${RUNNER_TEMP}/build-venv/Scripts"
            PYTHON="${VENV_BIN}/python.exe"
          else
            VENV_BIN="${RUNNER_TEMP}/build-venv/bin"
            PYTHON="${VENV_BIN}/python"
          fi
          uv pip install --python "$PYTHON" maturin ziglang
          echo "$VENV_BIN" >> "$GITHUB_PATH"

      - name: Build wheel and binary
        run: maturin build --release --target ${{ matrix.target }} ${{ matrix.maturin_args }}
        shell: bash

      - name: Package binary (unix)
        if: matrix.os != 'windows-latest'
        run: |
          cd target/${{ matrix.target }}/release
          tar czf ../../../sharepoint-${{ github.ref_name }}-${{ matrix.target }}.tar.gz sharepoint
        shell: bash

      - name: Package binary (windows)
        if: matrix.os == 'windows-latest'
        run: |
          cd target/${{ matrix.target }}/release
          7z a ../../../sharepoint-${{ github.ref_name }}-${{ matrix.target }}.zip sharepoint.exe
        shell: bash

      - name: Compute SHA256 (unix)
        if: matrix.os != 'windows-latest'
        run: |
          if [[ "${{ runner.os }}" == "macOS" ]]; then
            shasum -a 256 sharepoint-${{ github.ref_name }}-${{ matrix.target }}.tar.gz > sharepoint-${{ github.ref_name }}-${{ matrix.target }}.tar.gz.sha256
          else
            sha256sum sharepoint-${{ github.ref_name }}-${{ matrix.target }}.tar.gz > sharepoint-${{ github.ref_name }}-${{ matrix.target }}.tar.gz.sha256
          fi
        shell: bash

      - name: Upload binary artifact
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
        with:
          name: binary-${{ matrix.target }}
          path: sharepoint-${{ github.ref_name }}-${{ matrix.target }}.*

      - name: Upload wheel artifact
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
        with:
          name: wheel-${{ matrix.target }}
          path: target/wheels/*.whl

  sdist:
    needs: [metadata, check]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
      - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d
      - name: Install maturin
        shell: bash
        run: |
          uv venv "${RUNNER_TEMP}/build-venv"
          uv pip install --python "${RUNNER_TEMP}/build-venv/bin/python" maturin
          echo "${RUNNER_TEMP}/build-venv/bin" >> "$GITHUB_PATH"
      - run: maturin sdist
      - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
        with:
          name: sdist
          path: target/wheels/*.tar.gz

  release:
    needs: [metadata, build]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          pattern: binary-*
          merge-multiple: true

      - name: Create GitHub Release
        if: ${{ inputs.dry_run != true }}
        uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228
        with:
          generate_release_notes: true
          files: |
            sharepoint-*.tar.gz
            sharepoint-*.tar.gz.sha256
            sharepoint-*.zip

  publish-crates:
    needs: [metadata, release]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
      - uses: dtolnay/rust-toolchain@4360b52568e2003a75bf9bc1d59f33a8e3fc893c
        with:
          toolchain: 1.96.0
          components: rustfmt, clippy
      - name: Publish to crates.io
        if: ${{ inputs.dry_run != true }}
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
        run: |
          if ! output=$(cargo publish --locked 2>&1); then
            if echo "$output" | grep -q "already uploaded"; then
              echo "Crate already published to crates.io, skipping"
            else
              echo "$output"
              exit 1
            fi
          fi
      - name: Validate crates.io package
        if: ${{ inputs.dry_run == true }}
        run: cargo publish --locked --dry-run

  publish-pypi:
    needs: [metadata, release, build, sdist]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          pattern: wheel-*
          path: artifacts/wheels
      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          name: sdist
          path: artifacts/sdist
      - uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d
      - name: Publish to PyPI
        if: ${{ inputs.dry_run != true }}
        env:
          UV_PUBLISH_TOKEN: ${{ secrets.PYPI_API_TOKEN }}
        run: uv publish artifacts/wheels/wheel-*/*.whl artifacts/sdist/*.tar.gz
      - name: Validate PyPI artifacts
        if: ${{ inputs.dry_run == true }}
        run: uvx --from twine==6.2.0 twine check --strict artifacts/wheels/wheel-*/*.whl artifacts/sdist/*.tar.gz

  attest-release:
    name: Attest release artifacts
    needs: release
    if: ${{ inputs.dry_run != true }}
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
      attestations: write
    steps:
      - name: Download release artifacts
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
        with:
          path: artifacts
      - name: Attest release artifacts
        uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6
        with:
          subject-path: artifacts/**/*