name: ASF-publish

on: [push, pull_request]

env:
  CONFIGURATION: Release
  DOTNET_CLI_TELEMETRY_OPTOUT: true
  DOTNET_NOLOGO: true
  DOTNET_SDK_VERSION: 10.0
  NODE_JS_VERSION: 'lts/*'
  PLUGINS_BUNDLED: ArchiSteamFarm.OfficialPlugins.ItemsMatcher ArchiSteamFarm.OfficialPlugins.MobileAuthenticator ArchiSteamFarm.OfficialPlugins.SteamTokenDumper
  PLUGINS_INCLUDED: ArchiSteamFarm.OfficialPlugins.Monitoring # Apart from declaring them here, there is certain amount of hardcoding needed below for uploading

permissions: {}

jobs:
  publish-asf-ui:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        show-progress: false
        submodules: recursive

    - name: Setup Node.js with npm
      uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
      with:
        check-latest: true
        node-version: ${{ env.NODE_JS_VERSION }}

    - name: Verify Node.js
      run: node -v

    - name: Verify npm
      run: npm -v

    - name: Install npm modules for ASF-ui
      run: npm ci --no-progress --prefix ASF-ui

    - name: Publish ASF-ui
      run: npm run-script deploy --no-progress --prefix ASF-ui

    - name: Upload ASF-ui
      uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
      with:
        compression-level: 1
        if-no-files-found: error
        name: ASF-ui
        path: ASF-ui/dist

  publish-asf:
    needs: publish-asf-ui

    strategy:
      fail-fast: false
      matrix:
        include:
        - os: ubuntu-latest
          variant: generic
        - os: ubuntu-latest
          variant: linux-arm
        - os: ubuntu-latest
          variant: linux-arm64
        - os: ubuntu-latest
          variant: linux-x64
        - os: macos-latest
          variant: osx-arm64
        - os: macos-latest
          variant: osx-x64
        - os: windows-latest
          variant: win-arm64
        - os: windows-latest
          variant: win-x64

    runs-on: ${{ matrix.os }}

    permissions:
      attestations: write
      id-token: write

    steps:
    - name: Checkout code
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        show-progress: false

    - name: Setup .NET Core
      uses: actions/setup-dotnet@baa11fbfe1d6520db94683bd5c7a3818018e4309 # v5.1.0
      with:
        dotnet-version: ${{ env.DOTNET_SDK_VERSION }}

    - name: Verify .NET Core
      run: dotnet --info

    - name: Download previously built ASF-ui
      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
      with:
        name: ASF-ui
        path: ASF-ui/dist

    - name: Prepare private key for signing on Unix
      if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
      env:
        ASF_PRIVATE_SNK: ${{ secrets.ASF_PRIVATE_SNK }}
      shell: sh
      run: |
        set -eu

        if [ -n "${ASF_PRIVATE_SNK-}" ]; then
            echo "$ASF_PRIVATE_SNK" | base64 -d > "resources/ArchiSteamFarm.snk"
        fi

    - name: Prepare private key for signing on Windows
      if: startsWith(matrix.os, 'windows-')
      env:
        ASF_PRIVATE_SNK: ${{ secrets.ASF_PRIVATE_SNK }}
      shell: pwsh
      run: |
        Set-StrictMode -Version Latest
        $ErrorActionPreference = 'Stop'
        $ProgressPreference = 'SilentlyContinue'

        if ((Test-Path env:ASF_PRIVATE_SNK) -and ($env:ASF_PRIVATE_SNK)) {
            echo "$env:ASF_PRIVATE_SNK" > "resources\ArchiSteamFarm.snk"

            certutil -f -decode "resources\ArchiSteamFarm.snk" "resources\ArchiSteamFarm.snk"

            if ($LastExitCode -ne 0) {
                throw "Last command failed."
            }
        }

    - name: Prepare ArchiSteamFarm.OfficialPlugins.SteamTokenDumper on Unix
      if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
      env:
        STEAM_TOKEN_DUMPER_TOKEN: ${{ secrets.STEAM_TOKEN_DUMPER_TOKEN }}
      shell: sh
      run: |
        set -eu

        if [ -n "${STEAM_TOKEN_DUMPER_TOKEN-}" ] && [ -f "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs" ]; then
            sed "s/STEAM_TOKEN_DUMPER_TOKEN/${STEAM_TOKEN_DUMPER_TOKEN}/g" "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs" > "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs.new"
            mv "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs.new" "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper/SharedInfo.cs"
        fi

    - name: Prepare ArchiSteamFarm.OfficialPlugins.SteamTokenDumper on Windows
      if: startsWith(matrix.os, 'windows-')
      env:
        STEAM_TOKEN_DUMPER_TOKEN: ${{ secrets.STEAM_TOKEN_DUMPER_TOKEN }}
      shell: pwsh
      run: |
        Set-StrictMode -Version Latest
        $ErrorActionPreference = 'Stop'
        $ProgressPreference = 'SilentlyContinue'

        if ((Test-Path env:STEAM_TOKEN_DUMPER_TOKEN) -and ($env:STEAM_TOKEN_DUMPER_TOKEN) -and (Test-Path "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper\SharedInfo.cs" -PathType Leaf)) {
            (Get-Content "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper\SharedInfo.cs").Replace('STEAM_TOKEN_DUMPER_TOKEN', "$env:STEAM_TOKEN_DUMPER_TOKEN") | Set-Content "ArchiSteamFarm.OfficialPlugins.SteamTokenDumper\SharedInfo.cs"
        }

    - name: Publish ASF-${{ matrix.variant }} on Unix
      if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
      env:
        VARIANT: ${{ matrix.variant }}
      shell: bash
      run: |
        set -euo pipefail

        if [ "$VARIANT" = 'generic' ]; then
            variantArgs="-p:TargetLatestRuntimePatch=false -p:UseAppHost=false"
        else
            variantArgs="-p:PublishSingleFile=true -p:PublishTrimmed=true -r $VARIANT --self-contained"
        fi

        dotnet publish ArchiSteamFarm -c "$CONFIGURATION" -o "out/${VARIANT}" "-p:ASFVariant=${VARIANT}" -p:ContinuousIntegrationBuild=true --nologo $variantArgs

        # Include .ico file for all platforms, since only Windows script can bundle it inside the exe
        cp "resources/ASF.ico" "out/${VARIANT}/ArchiSteamFarm.ico"

        # Include extra logic for builds marked for release
        case "$GITHUB_REF" in
            "refs/tags/"*)
                # Update link in Changelog.html accordingly
                if [ -f "out/${VARIANT}/Changelog.html" ]; then
                    tag="$(echo "$GITHUB_REF" | cut -c 11-)"

                    sed "s/ArchiSteamFarm\/commits\/main/ArchiSteamFarm\/releases\/tag\/${tag}/g" "out/${VARIANT}/Changelog.html" > "out/${VARIANT}/Changelog.html.new"
                    mv "out/${VARIANT}/Changelog.html.new" "out/${VARIANT}/Changelog.html"
                fi
                ;;
        esac

    - name: Publish ASF-${{ matrix.variant }} on Windows
      if: startsWith(matrix.os, 'windows-')
      env:
        VARIANT: ${{ matrix.variant }}
      shell: pwsh
      run: |
        Set-StrictMode -Version Latest
        $ErrorActionPreference = 'Stop'
        $ProgressPreference = 'SilentlyContinue'

        if ($env:VARIANT -like 'generic*') {
            $variantArgs = '-p:TargetLatestRuntimePatch=false', '-p:UseAppHost=false'
        } else {
            $variantArgs = '-p:PublishSingleFile=true', '-p:PublishTrimmed=true', '-r', "$env:VARIANT", '--self-contained'
        }

        dotnet publish ArchiSteamFarm -c "$env:CONFIGURATION" -o "out\$env:VARIANT" "-p:ASFVariant=$env:VARIANT" -p:ContinuousIntegrationBuild=true --nologo $variantArgs

        if ($LastExitCode -ne 0) {
            throw "Last command failed."
        }

        # Icon is available only in .exe Windows builds, we'll bundle the .ico file for other flavours
        if (!(Test-Path "out\$env:VARIANT\ArchiSteamFarm.exe" -PathType Leaf)) {
            Copy-Item 'resources\ASF.ico' "out\$env:VARIANT\ArchiSteamFarm.ico"
        }

        # Include extra logic for builds marked for release
        if ($env:GITHUB_REF -like 'refs/tags/*') {
            # Update link in Changelog.html accordingly
            if (Test-Path "out\$env:VARIANT\Changelog.html" -PathType Leaf) {
                $tag = $env:GITHUB_REF.Substring(10)

                (Get-Content "out\$env:VARIANT\Changelog.html").Replace('ArchiSteamFarm/commits/main', "ArchiSteamFarm/releases/tag/$tag") | Set-Content "out\$env:VARIANT\Changelog.html"
            }
        }

    - name: Publish bundled plugins on Unix
      if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
      env:
        VARIANT: ${{ matrix.variant }}
      shell: bash
      run: |
        set -euo pipefail

        if [ "$VARIANT" = 'generic' ]; then
            variantArgs="-p:TargetLatestRuntimePatch=false -p:UseAppHost=false"
        else
            variantArgs="-r $VARIANT"
        fi

        for plugin in $PLUGINS_BUNDLED; do
            dotnet publish "$plugin" -c "$CONFIGURATION" -o "out/${VARIANT}/plugins/${plugin}" "-p:ASFVariant=${VARIANT}" -p:ContinuousIntegrationBuild=true --nologo $variantArgs
        done

    - name: Publish bundled plugins on Windows
      if: startsWith(matrix.os, 'windows-')
      env:
        VARIANT: ${{ matrix.variant }}
      shell: pwsh
      run: |
        Set-StrictMode -Version Latest
        $ErrorActionPreference = 'Stop'
        $ProgressPreference = 'SilentlyContinue'

        if ($env:VARIANT -like 'generic*') {
            $variantArgs = '-p:TargetLatestRuntimePatch=false', '-p:UseAppHost=false'
        } else {
            $variantArgs = '-r', "$env:VARIANT"
        }

        foreach ($plugin in $env:PLUGINS_BUNDLED.Split([char[]] $null, [System.StringSplitOptions]::RemoveEmptyEntries)) {
            dotnet publish "$plugin" -c "$env:CONFIGURATION" -o "out\$env:VARIANT\plugins\$plugin" "-p:ASFVariant=$env:VARIANT" -p:ContinuousIntegrationBuild=true --nologo $variantArgs

            if ($LastExitCode -ne 0) {
                throw "Last command failed."
            }
        }

    - name: Zip ASF-${{ matrix.variant }} on Unix
      if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
      env:
        VARIANT: ${{ matrix.variant }}
      shell: bash
      run: |
        set -euo pipefail

        # By default use fastest compression
        seven_zip_args="-mx=1"
        zip_args="-1"

        # Tweak compression args for release publishing
        case "$GITHUB_REF" in
            "refs/tags/"*)
                seven_zip_args="-mx=9 -mfb=258 -mpass=15"
                zip_args="-9"
                ;;
        esac

        # Create the final zip file
        case "$(uname -s)" in
            "Darwin")
                # We prefer to use zip on macOS as 7z implementation on that OS doesn't handle file permissions (chmod +x)
                if command -v zip >/dev/null; then
                    (
                        cd "${GITHUB_WORKSPACE}/out/${VARIANT}"
                        zip -q -r $zip_args "../ASF-${VARIANT}.zip" .
                    )
                else
                    7z a -bd -slp -tzip -mm=Deflate $seven_zip_args "out/ASF-${VARIANT}.zip" "${GITHUB_WORKSPACE}/out/${VARIANT}/*"
                fi
                ;;
            *)
                if command -v 7z >/dev/null; then
                    7z a -bd -slp -tzip -mm=Deflate $seven_zip_args "out/ASF-${VARIANT}.zip" "${GITHUB_WORKSPACE}/out/${VARIANT}/*"
                else
                    (
                        cd "${GITHUB_WORKSPACE}/out/${VARIANT}"
                        zip -q -r $zip_args "../ASF-${VARIANT}.zip" .
                    )
                fi
                ;;
        esac

    - name: Zip ASF-${{ matrix.variant }} on Windows
      if: startsWith(matrix.os, 'windows-')
      env:
        VARIANT: ${{ matrix.variant }}
      shell: pwsh
      run: |
        Set-StrictMode -Version Latest
        $ErrorActionPreference = 'Stop'
        $ProgressPreference = 'SilentlyContinue'

        # By default use fastest compression
        $compressionArgs = '-mx=1'

        # Tweak compression args for release publishing
        if ($env:GITHUB_REF -like 'refs/tags/*') {
            $compressionArgs = '-mx=9', '-mfb=258', '-mpass=15'
        }

        # Create the final zip file
        7z a -bd -slp -tzip -mm=Deflate $compressionArgs "out\ASF-$env:VARIANT.zip" "$env:GITHUB_WORKSPACE\out\$env:VARIANT\*"

        if ($LastExitCode -ne 0) {
            throw "Last command failed."
        }

        # We can aid non-windows users by adding chmod +x flag to appropriate executables directly in the zip file
        # This is ALMOST a hack, but works reliably enough
        if (Test-Path "tools\zip_exec\zip_exec.exe" -PathType Leaf) {
            $executableFiles = @()

            if ($env:VARIANT -like 'generic*') {
                $executableFiles += 'ArchiSteamFarm.sh', 'ArchiSteamFarm-Service.sh'
            } elseif (($env:VARIANT -like 'linux*') -or ($env:VARIANT -like 'osx*')) {
                $executableFiles += 'ArchiSteamFarm', 'ArchiSteamFarm-Service.sh'
            }

            foreach ($executableFile in $executableFiles) {
                tools\zip_exec\zip_exec.exe "out\ASF-$env:VARIANT.zip" "$executableFile"

                if ($LastExitCode -ne 0) {
                    throw "Last command failed."
                }
            }
        }

    - name: Generate artifact attestation for ASF-${{ matrix.variant }}.zip
      if: ${{ github.event_name == 'push' }}
      uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
      with:
        subject-path: out/ASF-${{ matrix.variant }}.zip

    - name: Upload ASF-${{ matrix.variant }}
      uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
      with:
        compression-level: 1
        if-no-files-found: error
        name: ${{ matrix.os }}_ASF-${{ matrix.variant }}
        path: out/ASF-${{ matrix.variant }}.zip

    - name: Publish included plugins on Unix
      if: ${{ matrix.os == 'ubuntu-latest' && matrix.variant == 'generic' }}
      env:
        VARIANT: ${{ matrix.variant }}
      shell: bash
      run: |
        set -euo pipefail

        # By default use fastest compression
        seven_zip_args="-mx=1"
        zip_args="-1"

        # Tweak compression args for release publishing
        case "$GITHUB_REF" in
            "refs/tags/"*)
                seven_zip_args="-mx=9 -mfb=258 -mpass=15"
                zip_args="-9"
                ;;
        esac

        for plugin in $PLUGINS_INCLUDED; do
            dotnet publish "$plugin" -c "$CONFIGURATION" -o "out/${plugin}" "-p:ASFVariant=${VARIANT}" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --nologo

            # Create the final zip file
            if command -v 7z >/dev/null; then
                7z a -bd -slp -tzip -mm=Deflate $seven_zip_args "out/${plugin}.zip" "${GITHUB_WORKSPACE}/out/${plugin}/*"
            else
                (
                    cd "${GITHUB_WORKSPACE}/out/${plugin}"
                    zip -q -r $zip_args "../${plugin}.zip" .
                )
            fi
        done

    - name: Generate artifact attestation for ArchiSteamFarm.OfficialPlugins.Monitoring
      if: ${{ github.event_name == 'push' && matrix.os == 'ubuntu-latest' && matrix.variant == 'generic' }}
      uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
      with:
        subject-path: out/ArchiSteamFarm.OfficialPlugins.Monitoring.zip

    - name: Upload ArchiSteamFarm.OfficialPlugins.Monitoring
      if: ${{ matrix.os == 'ubuntu-latest' && matrix.variant == 'generic' }}
      uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
      with:
        compression-level: 1
        if-no-files-found: error
        name: ArchiSteamFarm.OfficialPlugins.Monitoring
        path: out/ArchiSteamFarm.OfficialPlugins.Monitoring.zip

  release:
    if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/') }}
    needs: publish-asf
    environment: release-github
    runs-on: ubuntu-latest

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

    steps:
    - name: Checkout code
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
      with:
        show-progress: false

    - name: Download ASF-generic artifact from ubuntu-latest
      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
      with:
        name: ubuntu-latest_ASF-generic
        path: out

    - name: Download ASF-linux-arm artifact from ubuntu-latest
      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
      with:
        name: ubuntu-latest_ASF-linux-arm
        path: out

    - name: Download ASF-linux-arm64 artifact from ubuntu-latest
      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
      with:
        name: ubuntu-latest_ASF-linux-arm64
        path: out

    - name: Download ASF-linux-x64 artifact from ubuntu-latest
      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
      with:
        name: ubuntu-latest_ASF-linux-x64
        path: out

    - name: Download ASF-osx-arm64 artifact from macos-latest
      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
      with:
        name: macos-latest_ASF-osx-arm64
        path: out

    - name: Download ASF-osx-x64 artifact from macos-latest
      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
      with:
        name: macos-latest_ASF-osx-x64
        path: out

    - name: Download ASF-win-arm64 artifact from windows-latest
      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
      with:
        name: windows-latest_ASF-win-arm64
        path: out

    - name: Download ASF-win-x64 artifact from windows-latest
      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
      with:
        name: windows-latest_ASF-win-x64
        path: out

    - name: Download ArchiSteamFarm.OfficialPlugins.Monitoring artifact
      uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
      with:
        name: ArchiSteamFarm.OfficialPlugins.Monitoring
        path: out

    - name: Import GPG key for signing
      uses: crazy-max/ghaction-import-gpg@e89d40939c28e39f97cf32126055eeae86ba74ec # v6.3.0
      with:
        gpg_private_key: ${{ secrets.ARCHIBOT_GPG_PRIVATE_KEY }}

    - name: Generate SHA-512 checksums and signature
      shell: sh
      working-directory: out
      run: |
        set -eu

        sha512sum *.zip > SHA512SUMS
        gpg -a -b -o SHA512SUMS.sign SHA512SUMS

    - name: Generate artifact attestation for SHA512SUMS
      uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
      with:
        subject-path: out/SHA512SUMS

    - name: Upload SHA512SUMS
      uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
      with:
        compression-level: 1
        if-no-files-found: error
        name: SHA512SUMS
        path: out/SHA512SUMS

    - name: Generate artifact attestation for SHA512SUMS.sign
      uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
      with:
        subject-path: out/SHA512SUMS.sign

    - name: Upload SHA512SUMS.sign
      uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
      with:
        compression-level: 1
        if-no-files-found: error
        name: SHA512SUMS.sign
        path: out/SHA512SUMS.sign

    - name: Create ArchiSteamFarm GitHub release
      uses: ncipollo/release-action@b7eabc95ff50cbeeedec83973935c8f306dfcd0b # v1.20.0
      with:
        allowUpdates: true
        artifactErrorsFailBuild: true
        artifacts: "out/*"
        bodyFile: .github/RELEASE_TEMPLATE.md
        immutableCreate: true
        makeLatest: false
        name: ArchiSteamFarm V${{ github.ref_name }}
        prerelease: true
        token: ${{ secrets.ARCHIBOT_GITHUB_TOKEN }}
        updateOnlyUnreleased: true
