Keeping Your NuGet Packages Sharp: Reporting Outdated Ones in Azure DevOps Pull Requests

September 09, 2023

Maintaining up-to-date dependencies can be a tedious task, often requiring significant concentration. When tackling such tasks, I aim to seamlessly integrate them into the regular workflow to make them more manageable. Considering this approach, it's evident that examining outdated NuGet packages during a pull request phase is a smart strategy!

Pull Request comment

We're going to set up a YAML pipeline that tracks our outdated dependencies using dotnet-outdated) within a pull request on Azure DevOps. This pipeline will be triggered automatically when a pull request is opened.

While we'll be creating a pipeline for Azure DevOps, you can also draw inspiration from this blog post for platforms like GitHub or others.

Please note that dotnet-outdated isn't compatible with .NET Framework projects. For more details about this tool, refer to its GitHub repository.

YAML pipeline

The YAML pipeline is installing the dotnet-outdated tool, creates an output to JSON file, read that file and create a comment in the open pull request.

We start with the basics of a YAML pipeline. We need to set this pipeline a branch validation within Azure Devops, so the trigger is going to be none. We are using powershell (pswh) step for installing and executing dotnet-outdated.

trigger: none

steps:
  - pwsh: |
      # ...

We need to install the dotnet-outdated and let it analyze our solution. We give arguments with it to output the results to a JSON file. If the analyzing is finished and there is no file found, there are no outdated dependencies so we can exit the script.

trigger: none

steps:
  - pwsh: |
      $solutionName = "ChangeThis.sln"

      # Install the tool
      dotnet tool install dotnet-outdated-tool

      # Analyzing solution and save output to a JSON file
      dotnet outdated "$solutionName" --output "outdated-packages.json"

      # No file? No outdated dependencies!
      if (!(Test-Path "./outdated-packages.json"))
      {
        Write-Host "All dependencies are up to date!"
        return 0;
      }

      #...

If there are outdated packages, we are gonna read the output file, remove all duplicates and create a markdown table for it.

      #...

      # Read the output file to a powershell structure which we can use to create a markdown table
      $output = Get-Content -Path "./outdated-packages.json" | ConvertFrom-Json
      $outdated = @();

      foreach ($project in $output.Projects) {
          foreach ($framework in $project.TargetFrameworks) {
              foreach ($dependency in $framework.Dependencies) {
                $outdated += $dependency
              }
          }
      }

      # Remove duplicates by Name (this is a trade-off to keep things simple)
      $outdated = $outdated | Sort-Object -Property Name -Unique

      # Create markdown table for the pull request comment
      $markdownTable = "| Name | Resolved Version | Latest Version | Upgrade Severity |`n"
      $markdownTable += "| --- | --- | --- | --- |`n"

      foreach ($dependency in $outdated) {
        $markdownTable += "| $($dependency.Name) | $($dependency.ResolvedVersion) | $($dependency.LatestVersion) | $($dependency.UpgradeSeverity) |`n"
      }

      #...

After creating the markdown table we can create a pull request comment by using the Azure Devops API. We are using predefined variables for the organization URL, project name, repository name and access token.

      # ...

      # Creating pull request comment
      $body = @"
      {
        "comments": [
          {
            "parentCommentId": 0,
            "content": "$markdownTable",
            "commentType": 1
          }
        ],
        "status": 1
      }
      "@

      Invoke-RestMethod `
        -Uri "$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_apis/git/repositories/$(Build.Repository.Name)/$(System.PullRequest.PullRequestId)/threads?api-version=5.1" `
        -Method POST -Headers @{Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"} `
        -Body $Body `
        -ContentType "application/json; charset=utf-8"
    displayName: Check outdated packages
    env:
      SYSTEM_ACCESSTOKEN: $(System.AccessToken)

For creating pull request comments, the Build Service user should have permission to contribute to pull requests. You can change those permissions in the security settings of the repository.

And that's it! That's the pipeline which creates a pull request comment when there are outdated dependencies in your solution.

If you don't know how to setup a pipeline for your pull request, you check check-out this blog post: Run the CI Pipeline during a Pull Request.

Complete YAML

trigger: none

steps:
  - pwsh: |
      $solutionName = "ChangeThis.sln"

      # Install the tool
      dotnet tool install dotnet-outdated-tool

      # Analyzing solution and save output to a JSON file
      dotnet outdated "$solutionName" --output "outdated-packages.json"

      # No file? No outdated dependencies!
      if (!(Test-Path "./outdated-packages.json"))
      {
        Write-Host "All dependencies are up to date!"
        return 0;
      }

      # Read the output file to a powershell structure which we can use to create a markdown table
      $output = Get-Content -Path "./outdated-packages.json" | ConvertFrom-Json
      $outdated = @();

      foreach ($project in $output.Projects) {
          foreach ($framework in $project.TargetFrameworks) {
              foreach ($dependency in $framework.Dependencies) {
                $outdated += $dependency
              }
          }
      }

      # Remove duplicates by Name (this is a trade-off to keep things simple)
      $outdated = $outdated | Sort-Object -Property Name -Unique

      # Create markdown table for the pull request comment
      $markdownTable = "| Name | Resolved Version | Latest Version | Upgrade Severity |`n"
      $markdownTable += "| --- | --- | --- | --- |`n"

      foreach ($dependency in $outdated) {
        $markdownTable += "| $($dependency.Name) | $($dependency.ResolvedVersion) | $($dependency.LatestVersion) | $($dependency.UpgradeSeverity) |`n"
      }

      # Creating pull request comment
      $body = @"
      {
        "comments": [
          {
            "parentCommentId": 0,
            "content": "$markdownTable",
            "commentType": 1
          }
        ],
        "status": 1
      }
      "@

      Invoke-RestMethod `
        -Uri "$(System.TeamFoundationCollectionUri)$(System.TeamProject)/_apis/git/repositories/$(Build.Repository.Name)/$(System.PullRequest.PullRequestId)/threads?api-version=5.1" `
        -Method POST -Headers @{Authorization = "Bearer $env:SYSTEM_ACCESSTOKEN"} `
        -Body $Body `
        -ContentType "application/json; charset=utf-8"
    displayName: Check outdated packages
    env:
      SYSTEM_ACCESSTOKEN: $(System.AccessToken)