name: Official Frends Task Pipeline on: workflow_call: inputs: workflow_mode: required: true type: string description: 'Workflow mode: "work" builds, tests and publishes to the test feed, "main" builds and tests, "release" builds, publishes to the main feed and creates a GitHub release.' os: required: false type: string default: 'all' description: 'Which OS(es) to test on: "win" (windows-latest), "lin" (ubuntu-latest) or "all" (both). The single build runs on windows-latest for "win", ubuntu-latest otherwise.' workdir: required: false type: string default: '.' pretest_command: required: false type: string description: 'Optional PowerShell command run in the test jobs before tests are executed (e.g. starting test containers).' dotnet_version: required: false type: string default: '8.0.x' description: 'Version of .NET SDK to use, e.g. 6.0.x, 8.0.x' strict_analyzers: required: false type: boolean default: true secrets: badge_service_api_key: required: true source_nuget_feed_url: required: false source_nuget_feed_api_key: required: false target_feed_api_key: required: false env_vars: required: false env: BUILD_ARTIFACT_NAME: build-output NUGET_ARTIFACT_NAME: nuget-packages jobs: build: name: Build on ${{ inputs.os == 'win' && 'windows-latest' || 'ubuntu-latest' }} runs-on: ${{ inputs.os == 'win' && 'windows-latest' || 'ubuntu-latest' }} outputs: workspace: ${{ steps.workspace.outputs.path }} steps: - id: workspace name: Record build workspace shell: pwsh run: '"path=$env:GITHUB_WORKSPACE" >> $env:GITHUB_OUTPUT' - name: Checkout uses: actions/checkout@v7.0.1 - name: Setup uses: FrendsPlatform/FrendsTasks/.github/actions/dotnet-setup@main with: dotnet_version: ${{ inputs.dotnet_version }} feed_url: ${{ secrets.source_nuget_feed_url }} api_key: ${{ secrets.source_nuget_feed_api_key }} - name: Build shell: pwsh working-directory: ${{ inputs.workdir }} run: dotnet build - name: Pack release version of task shell: pwsh working-directory: ${{ inputs.workdir }} run: dotnet pack --configuration Release - name: Upload build output uses: actions/upload-artifact@v7.0.1 with: name: ${{ env.BUILD_ARTIFACT_NAME }} path: | ${{ inputs.workdir }}/**/bin/ ${{ inputs.workdir }}/**/obj/ if-no-files-found: error retention-days: 1 - name: Upload NuGet package(s) uses: actions/upload-artifact@v7.0.1 with: name: ${{ env.NUGET_ARTIFACT_NAME }} path: ${{ inputs.workdir }}/**/*.nupkg if-no-files-found: error code_quality: name: Check Code Quality needs: build runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v7.0.1 with: fetch-depth: 0 - name: Super-Linter uses: super-linter/super-linter@v8.7.0 env: GITHUB_TOKEN: ${{ github.token }} VALIDATE_CSHARP: true IGNORE_GITIGNORED_FILES: true FILTER_REGEX_INCLUDE: "${{ inputs.workdir }}/.*" - name: Setup uses: FrendsPlatform/FrendsTasks/.github/actions/dotnet-setup@main with: dotnet_version: ${{ inputs.dotnet_version }} feed_url: ${{ secrets.source_nuget_feed_url }} api_key: ${{ secrets.source_nuget_feed_api_key }} - name: Download build output uses: actions/download-artifact@v8.0.1 with: name: ${{ env.BUILD_ARTIFACT_NAME }} path: ${{ inputs.workdir }} # The build may come from a different OS, so restore regenerates obj/ with paths valid on this runner. - name: Restore shell: pwsh working-directory: ${{ inputs.workdir }} run: dotnet restore - name: Enable global .NET tools shell: pwsh run: | (Join-Path $HOME '.dotnet' 'tools') >> $env:GITHUB_PATH - name: Install Roslynator shell: pwsh run: | dotnet tool install --global Roslynator.DotNet.Cli --version 0.10.1 - name: Download FrendsTaskAnalyzers shell: pwsh working-directory: ${{ inputs.workdir }} env: GITHUB_TOKEN: ${{ github.token }} run: | New-Item -Path '.analyzers/tmp' -ItemType Directory -Force | Out-Null $latest = gh release list ` --repo 'FrendsPlatform/FrendsTasks' ` --json tagName ` --exclude-drafts ` --exclude-pre-releases ` --jq '[.[] | select(.tagName | startswith("analyzers-")) | .tagName] | first' gh release download "$latest" ` --repo 'FrendsPlatform/FrendsTasks' ` --dir '.analyzers' ` --pattern '*.nupkg' Expand-Archive -Path '.analyzers/*.nupkg' -DestinationPath '.analyzers/tmp' Get-ChildItem -Path '.analyzers/tmp' -Filter '*.dll' -Recurse | Move-Item -Destination '.analyzers' -Force Remove-Item -Path '.analyzers/tmp' -Recurse -Force - name: Write .editorconfig shell: pwsh working-directory: ${{ inputs.workdir }} run: | $file = Get-ChildItem -Path . -Include 'FrendsTaskMetadata.json' -Recurse | select -First 1 $contents = Get-Content $file -Raw -Encoding UTF8 $contents = $contents.Trim() $json = $contents | ConvertFrom-Json $methods = $json.Tasks | Join-String -Property TaskMethod -Separator ';' $lines = @" [*.cs] frends_task_analyzers.task_methods = $methods "@ Add-Content -Path .editorconfig -Value $lines - name: Format shell: pwsh working-directory: ${{ inputs.workdir }} continue-on-error: ${{ !inputs.strict_analyzers }} run: | dotnet format ` --no-restore ` --verify-no-changes ` --verbosity detailed - name: Lint shell: pwsh working-directory: ${{ inputs.workdir }} continue-on-error: ${{ !inputs.strict_analyzers }} run: | $projects = Get-ChildItem -Recurse -Filter *.csproj | Where-Object { $_.Name -notlike '*.Tests.csproj' } foreach ($proj in $projects) { roslynator analyze ` --analyzer-assemblies '.analyzers' ` --verbosity detailed ` $proj.FullName } test_windows: name: Test on windows-latest needs: build if: ${{ (inputs.os == 'win' || inputs.os == 'all') && inputs.workflow_mode != 'release' }} runs-on: windows-latest steps: - name: Checkout uses: actions/checkout@v7.0.1 - name: Setup uses: FrendsPlatform/FrendsTasks/.github/actions/dotnet-setup@main with: dotnet_version: ${{ inputs.dotnet_version }} feed_url: ${{ secrets.source_nuget_feed_url }} api_key: ${{ secrets.source_nuget_feed_api_key }} - name: Test uses: FrendsPlatform/FrendsTasks/.github/actions/dotnet-test@main with: workdir: ${{ inputs.workdir }} build_artifact_name: ${{ env.BUILD_ARTIFACT_NAME }} build_workspace: ${{ needs.build.outputs.workspace }} pretest_command: ${{ inputs.pretest_command }} env_vars: ${{ secrets.env_vars }} badge_service_api_key: ${{ secrets.badge_service_api_key }} test_linux: name: Test on ubuntu-latest needs: build if: ${{ (inputs.os == 'lin' || inputs.os == 'all') && inputs.workflow_mode != 'release' }} runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v7.0.1 - name: Setup uses: FrendsPlatform/FrendsTasks/.github/actions/dotnet-setup@main with: dotnet_version: ${{ inputs.dotnet_version }} feed_url: ${{ secrets.source_nuget_feed_url }} api_key: ${{ secrets.source_nuget_feed_api_key }} - name: Test uses: FrendsPlatform/FrendsTasks/.github/actions/dotnet-test@main with: workdir: ${{ inputs.workdir }} build_artifact_name: ${{ env.BUILD_ARTIFACT_NAME }} build_workspace: ${{ needs.build.outputs.workspace }} pretest_command: ${{ inputs.pretest_command }} env_vars: ${{ secrets.env_vars }} badge_service_api_key: ${{ secrets.badge_service_api_key }} publish: name: Publish NuGet package needs: [ build, code_quality, test_windows, test_linux ] # Test jobs are skipped in "release" mode or for an OS not selected via inputs.os, # so "skipped" is accepted for them, but any failure/cancellation blocks publishing. if: >- ${{ always() && (inputs.workflow_mode == 'work' || inputs.workflow_mode == 'release') && needs.build.result == 'success' && needs.code_quality.result == 'success' && (needs.test_windows.result == 'success' || needs.test_windows.result == 'skipped') && (needs.test_linux.result == 'success' || needs.test_linux.result == 'skipped') }} runs-on: ubuntu-latest env: TARGET_FEED_URL: >- ${{ inputs.workflow_mode == 'release' && 'https://pkgs.dev.azure.com/frends-platform/frends-tasks/_packaging/main/nuget/v3/index.json' || 'https://pkgs.dev.azure.com/frends-platform/frends-tasks/_packaging/test/nuget/v3/index.json' }} steps: - name: Validate target NuGet feed configuration shell: pwsh env: TARGET_FEED_API_KEY: ${{ secrets.target_feed_api_key }} WORKFLOW_MODE: ${{ inputs.workflow_mode }} run: | if ([string]::IsNullOrEmpty($env:TARGET_FEED_API_KEY)) { throw "target_feed_api_key is required when workflow_mode is '$env:WORKFLOW_MODE'." } - name: Setup .NET SDK with target NuGet feed uses: FrendsPlatform/FrendsTasks/.github/actions/dotnet-setup@main with: dotnet_version: ${{ inputs.dotnet_version }} feed_url: ${{ env.TARGET_FEED_URL }} api_key: ${{ secrets.target_feed_api_key }} - name: Download NuGet package(s) uses: actions/download-artifact@v8.0.1 with: name: ${{ env.NUGET_ARTIFACT_NAME }} path: packages - name: Push NuGet package id: push_package shell: pwsh env: TARGET_FEED_URL: ${{ env.TARGET_FEED_URL }} TARGET_FEED_API_KEY: ${{ secrets.target_feed_api_key }} run: | $package = Get-ChildItem -Path packages -Filter *.nupkg -Recurse | Select-Object -First 1 if ($null -eq $package) { throw "The build did not produce a NuGet package." } "package_name=$($package.Name)" >> $env:GITHUB_OUTPUT dotnet nuget push $package.FullName ` --api-key $env:TARGET_FEED_API_KEY ` --source $env:TARGET_FEED_URL ` --skip-duplicate if ($LASTEXITCODE -ne 0) { throw "Failed to publish $($package.FullName)." } - name: Get current date if: ${{ inputs.workflow_mode == 'release' }} id: date shell: bash run: echo "date=$(date +'%Y-%m-%d')" >> "$GITHUB_OUTPUT" - name: Create release if: ${{ inputs.workflow_mode == 'release' }} uses: softprops/action-gh-release@v3.0.3 with: draft: false prerelease: false tag_name: release-${{ steps.push_package.outputs.package_name }}-${{ steps.date.outputs.date }} name: Release ${{ steps.push_package.outputs.package_name }} (${{ steps.date.outputs.date }}) body: | The task was released at https://pkgs.dev.azure.com/frends-platform/frends-tasks/_packaging/main/nuget/v2 You can install the task via Frends UI Task View, or browse released packages at https://dev.azure.com/frends-platform/frends-tasks/_artifacts/feed/main