sspi 0.21.1

A Rust implementation of the Security Support Provider Interface (SSPI) API
Documentation
name: Publish NuGet package

on:
  workflow_dispatch:
    inputs:
      dry-run:
        description: 'Dry run'
        required: true
        type: boolean
        default: true
      version:
        description: 'Package version'
        default: "latest"
        required: true
  schedule:
    - cron: '21 3 * * 1' # 3:21 AM UTC every Monday

jobs:
  preflight:
    name: Preflight
    runs-on: ubuntu-latest
    outputs:
      dry-run: ${{ steps.get-dry-run.outputs.dry-run }}
      package-version: ${{ steps.info.outputs.package-version }}

    steps:
      - name: Get dry run
        id: get-dry-run
        shell: pwsh
        run: |
          Set-PSDebug -Trace 1

          $IsDryRun = '${{ github.event.inputs.dry-run }}' -Eq 'true' -Or '${{ github.event_name }}' -Eq 'schedule'

          if ($IsDryRun) {
            echo "dry-run=true" >> $Env:GITHUB_OUTPUT
          } else {
            echo "dry-run=false" >> $Env:GITHUB_OUTPUT
          }

      - name: Package information
        id: info
        shell: pwsh
        run: |
          $PackageVersion = '${{ inputs.version }}'
          if ([string]::IsNullOrEmpty($PackageVersion) -or $PackageVersion -eq 'latest') {
            $PackageVersion = (Get-Date -Format "yyyy.MM.dd") + ".0"
          }

          if ($PackageVersion -NotMatch '^\d+\.\d+\.\d+\.\d+$') {
            throw "invalid version format: $PackageVersion, expected: 1.2.3.4"
          }

          echo "package-version=$PackageVersion" >> $Env:GITHUB_OUTPUT
          echo "::notice::Version: $PackageVersion"

  build-native:
    uses: ./.github/workflows/build-native.yml

  build-managed:
    name: Managed build
    runs-on: windows-2022
    needs: [ preflight, build-native ] 

    steps:
      - name: Check out ${{ github.repository }}
        uses: actions/checkout@v6

      - name: Prepare dependencies
        shell: pwsh
        run: |
          New-Item -ItemType Directory -Path "dependencies/runtimes" | Out-Null

      - name: Download native components
        uses: actions/download-artifact@v8
        with:
          path: dependencies/runtimes

      - name: Rename dependencies
        shell: pwsh
        run: |
          Set-PSDebug -Trace 1

          Set-Location "dependencies/runtimes"

          $(Get-Item ".\sspi-*-release") | ForEach-Object { Rename-Item $_ $_.Name.Replace("-release", "") }
          $(Get-Item ".\sspi-*") | ForEach-Object { Rename-Item $_ $_.Name.Replace("sspi-", "") }
          Get-ChildItem * -Recurse

      - name: Set package version
        shell: pwsh
        run: |
          $PackageVersion = '${{ needs.preflight.outputs.package-version }}'
          $csprojPath = "ffi\dotnet\Devolutions.Sspi\Devolutions.Sspi.csproj"
          $csprojContent = Get-Content $csprojPath -Raw
          $csprojContent = $csprojContent -Replace '(<Version>).*?(</Version>)', "<Version>$PackageVersion</Version>"
          Set-Content -Path $csprojPath -Value $csprojContent -Encoding UTF8

      - name: Build sspi (managed)
        shell: pwsh
        run: |
          dotnet build .\ffi\dotnet\Devolutions.Sspi\Devolutions.Sspi.csproj -o package

      - name: Upload managed components
        uses: actions/upload-artifact@v7
        with:
          name: sspi-nupkg
          path: package/*.nupkg

  publish:
    name: Publish NuGet package
    runs-on: ubuntu-latest
    environment: nuget-publish
    if: needs.preflight.outputs.dry-run == 'false'
    needs:
      - preflight
      - build-managed
    permissions:
      id-token: write

    steps:
      - name: Download NuGet package artifact
        uses: actions/download-artifact@v8
        with:
          name: sspi-nupkg
          path: package

      - name: NuGet login (OIDC)
        uses: NuGet/login@v1
        id: nuget-login
        with:
          user: ${{ secrets.NUGET_BOT_USERNAME }}

      - name: Publish to nuget.org
        shell: pwsh
        run: |
          Set-PSDebug -Trace 1

          $Files = Get-ChildItem -Recurse package/*.nupkg

          foreach ($File in $Files) {
            $PushCmd = @(
              'dotnet',
              'nuget',
              'push',
              "$File",
              '--api-key',
              '${{ steps.nuget-login.outputs.NUGET_API_KEY }}',
              '--source',
              'https://api.nuget.org/v3/index.json',
              '--skip-duplicate'
            )

            Write-Host "Publishing $($File.Name)..."
            $PushCmd = $PushCmd -Join ' '
            Invoke-Expression $PushCmd
          }

  package-symbols:
    name: Package debug symbols
    runs-on: ubuntu-latest
    needs: [ preflight, build-native ]

    steps:
      - name: Download all symbol artifacts
        uses: actions/download-artifact@v8
        with:
          path: artifacts
          pattern: sspi-*-release-symbols

      - name: Package symbols into zip
        shell: pwsh
        run: |
          Set-PSDebug -Trace 1

          $PackageVersion = '${{ needs.preflight.outputs.package-version }}'
          $SymbolsDir = "symbols"
          New-Item -ItemType Directory -Path $SymbolsDir -Force | Out-Null

          # Organize symbols by RID
          $SymbolArtifacts = Get-ChildItem -Directory "artifacts" -Filter "sspi-*-release-symbols"
          foreach ($Artifact in $SymbolArtifacts) {
            # Extract RID from artifact name (e.g., sspi-win-x64-release-symbols -> win-x64)
            $Rid = $Artifact.Name -Replace '^sspi-(.+)-release-symbols$', '$1'
            $DestDir = Join-Path $SymbolsDir $Rid
            New-Item -ItemType Directory -Path $DestDir -Force | Out-Null

            # Copy all contents from the artifact
            Get-ChildItem -Path $Artifact.FullName -Recurse | ForEach-Object {
              if (-not $_.PSIsContainer) {
                $RelativePath = $_.FullName.Substring($Artifact.FullName.Length + 1)
                $DestPath = Join-Path $DestDir $RelativePath
                $DestParent = Split-Path $DestPath -Parent
                if (-not (Test-Path $DestParent)) {
                  New-Item -ItemType Directory -Path $DestParent -Force | Out-Null
                }
                Copy-Item $_.FullName $DestPath
              }
            }
          }

          Write-Host "Symbol directory structure:"
          Get-ChildItem -Recurse $SymbolsDir | ForEach-Object { Write-Host $_.FullName }

          # Create the zip file
          $ZipName = "Devolutions.Sspi.$PackageVersion.symbols.zip"
          Compress-Archive -Path "$SymbolsDir/*" -DestinationPath $ZipName -Force
          Write-Host "Created symbols archive: $ZipName"

      - name: Upload symbols zip
        uses: actions/upload-artifact@v7
        with:
          name: sspi-symbols-zip
          path: Devolutions.Sspi.*.symbols.zip

  create-release:
    name: Create GitHub Release
    runs-on: ubuntu-latest
    if: needs.preflight.outputs.dry-run == 'false'
    needs:
      - preflight
      - build-managed
      - package-symbols
      - publish
    permissions:
      contents: write

    steps:
      - name: Download NuGet package
        uses: actions/download-artifact@v8
        with:
          name: sspi-nupkg
          path: release-assets

      - name: Download symbols zip
        uses: actions/download-artifact@v8
        with:
          name: sspi-symbols-zip
          path: release-assets

      - name: Create GitHub Release
        env:
          GH_TOKEN: ${{ github.token }}
          GH_REPO: ${{ github.repository }}
        shell: pwsh
        run: |
          Set-PSDebug -Trace 1

          $PackageVersion = '${{ needs.preflight.outputs.package-version }}'
          $TagName = "v$PackageVersion"
          $ReleaseName = "Devolutions.Sspi $PackageVersion"

          # Get the list of assets to upload
          $Assets = Get-ChildItem -Path "release-assets" -File
          Write-Host "Release assets:"
          $Assets | ForEach-Object { Write-Host "  - $($_.Name)" }

          # Build the release notes
          $ReleaseNotes = @"
          ## Devolutions.Sspi $PackageVersion

          ### NuGet Package
          - [Devolutions.Sspi on NuGet.org](https://www.nuget.org/packages/Devolutions.Sspi/$PackageVersion)

          ### Included Platforms
          - Windows (x64, arm64)
          - macOS (x64, arm64, universal)
          - Linux (x64, arm64)
          - iOS device (arm64)
          - iOS Simulator (arm64)
          - Android (x86, x64, arm, arm64)

          ### Debug Symbols
          The `Devolutions.Sspi.$PackageVersion.symbols.zip` archive contains debug symbols for all platforms:
          - Windows: `.pdb` files
          - macOS/iOS: `.dSYM` bundles
          - Linux/Android: `.debug` or `.dwp` files
          "@

          # Create the release with assets
          $AssetArgs = @()
          foreach ($Asset in $Assets) {
            $AssetArgs += $Asset.FullName
          }

          gh release create $TagName `
            --title $ReleaseName `
            --notes $ReleaseNotes `
            --target ${{ github.sha }} `
            @AssetArgs

          Write-Host "Created release: $ReleaseName with tag $TagName"

  notify:
    name: Notify failure
    runs-on: ubuntu-latest
    if: ${{ always() && contains(needs.*.result, 'failure') && github.event_name == 'schedule' }}
    needs:
      - preflight
      - build-native
      - build-managed
      - package-symbols
    env:
      SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_ARCHITECTURE }}
      SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
    steps:
      - name: Send slack notification
        id: slack
        uses: slackapi/slack-github-action@v1.26.0
        with:
          payload: |
            {
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*${{ github.repository }}* :fire::fire::fire::fire::fire: \n The scheduled build for *${{ github.repository }}* is <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|broken>"
                  }
                }
              ]
            }