Verify.Cli GitHub Action

I’ve previously written about the command line tool I created for snapshot testing files without needing a unit test framework - Verify.Cli.

I’ve been making good use of this tool as I continue to make changes and improvements to my blog. The underlying “engine” of my blog lives in my astro-blog-engine repo, and it contains a small subset of my blog posts that I use for testing any changes before merging to my full blog repo. The latter repo is private.

Up until now I’ve had these wired up in my blog engine repo’s GitHub Actions workflow like this:

      - name: Verify post
        id: verify_post
        if: github.event_name == 'pull_request' || github.repository == 'flcdrg/astro-blog-engine'
        run: docker run --rm --user "$(id -u):$(id -g)" -v $PWD:/tmp flcdrg/verify-cli --file /tmp/dist/2025/07/azure-pipeline-template-expression.html --verified-dir /tmp/verified --scrub-inline-pattern '(?<prefix>")/_astro/[^"]+(?<suffix>")' --scrub-inline-pattern '(?<prefix>title=")[^"]+(?<suffix>")' --scrub-inline-remove ' data-image-component="true"' --scrub-inline-pattern '(?<prefix>meta name="generator" content="Astro v)[\d\.]+(?<suffix>")' --verbosity detailed

      - name: Diff post if error
        if: failure() && steps.verify_post.outcome == 'failure'
        run: |
          ls -al verified/
          echo ""
          diff -u verified/azure-pipeline-template-expression.html.verified.html verified/azure-pipeline-template-expression.html.received.html

Just this week I discovered I’d accidentally messed up the Tags page on my blog. A recent change had mixed up the tag name and the count for the number of pages with that tag, which caused all the links to be broken.

I realised that page didn’t have a covering snapshot test in the engine repo, so some copy/paste ensued. In doing that it occurred to me that I really should extract out the common logic and make it a reusable GitHub Action. So that’s what I’ve done.

verify-cli is now listed in the GitHub Actions marketplace, and the source is at https://github.com/flcdrg/verify-cli-action

I can now simplify my blog’s workflow file by using this new action:

      - name: Verify post
        if: github.event_name == 'pull_request' || github.repository == 'flcdrg/astro-blog-engine'
        uses: flcdrg/verify-cli-action@v1
        with:
          file: dist/2025/07/azure-pipeline-template-expression.html
          verified-directory: verified
          working-directory: .
          scrub-inline-pattern: |
            (?<prefix>")/_astro/[^"]+(?<suffix>")
            (?<prefix>title=")[^"]+(?<suffix>")
            (?<prefix>meta name="generator" content="Astro v)[\d\.]+(?<suffix>")
          scrub-inline-remove: ' data-image-component="true"'
          verbosity: detailed

Failing example

Here’s an example (based on the test workflow in the verify-cli-action repo) to show what it looks like if the received file doesn’t match the verified file

The workflow step is similar to this:

      - name: "Match in separate verified directory - not matching"
        uses: flcdrg/verify-cli-action@v1
        with:
          file: "data/notMatching.json"
          working-directory: "samples/separate-verified"
          verified-directory: "verified"

With this result:

Screenshot of failing GitHub Actions workflow

If you need to ensure that a file’s contents match a known state (and need to ignore specific text like timestamps), but be alerted by a failing GitHub Actions workflow if it changes (and see what those changes are), then this action might be just the thing.

Troubleshooting GitHub Package NuGet feeds with GitHub Actions

If you’re building .NET applications with GitHub Actions, then you want to ensure the correct .NET SDK is in place. If you’re using GitHub Packages to access a private NuGet feed then you’ll also need to be able to authenticate to that feed. The actions/setup-dotnet action is great for doing both of these tasks, but there are some gotchas I’ve encountered when using it.

Using the action is pretty straight forward:

- uses: actions/setup-dotnet@v6

If you have a global.json file present in the root of your repo it will read it by default. If you don’t then it doesn’t know which version to install and you’ll see this logged in the workflow run:

The global.json wasn't found in the root directory. No .NET version will be installed.

Better to use dotnet-version to indicate one (or more) SDK version ranges to install:

- uses: actions/setup-dotnet@v6
  with:
    dotnet-version: 10.0.x

That is fine, but the best approach in my opinion is to have a global.json file with an sdk property set like this:

{
  "sdk": {
    "version": "10.0.401",
    "rollForward": "latestFeature"
  },
  "test": {
    "runner": "Microsoft.Testing.Platform"
  }
}

The action will read the SDK version from there. This is often a good thing as it means you just need to set the version in one place rather than all through your workflow files.

Using GitHub Packages feeds

Assuming you’ve already configured your GitHub packages feed in your NuGet.Config file (and note the use of the newer packageSourceMapping block):

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <packageSources>
        <add key="nuget" value="https://api.nuget.org/v3/index.json" />
        <add key="github" value="https://nuget.pkg.github.com/flcdrg/index.json" />
    </packageSources>

    <packageSourceMapping>
        <packageSource key="nuget">
            <package pattern="*" />
        </packageSource>
        <packageSource key="github">
            <package pattern="Verify.MongoDB" />
        </packageSource>
    </packageSourceMapping>
</configuration>

Then you can use the action to wire up authentication for the Packages feed with the source-url input parameter:

- uses: actions/setup-dotnet@v6
  with:
    source-url: https://nuget.pkg.github.com/flcdrg/index.json
  env:
    NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}

Note that you must grant the GitHub token access to read packages!

permissions:
  contents: read
  packages: read

And you’d think that would be it. But there are some edge cases to be aware of.

Building from a subdirectory

The action defaults to looking in the root of the repository for global.json and NuGet.Config files. If your .NET source code resides in a subdirectory (often the case if you are using a monorepo approach), then make sure you tell the action where they are:

- uses: actions/setup-dotnet@v6
  with:
    global-json-file: globaljson-and-nugetconfig/global.json
    source-url: https://nuget.pkg.github.com/flcdrg/index.json
    config-file: globaljson-and-nugetconfig/nuget.config

If you forget to set global-json-file and/or config-file then it will only look in the root of the repository, and either load the wrong files or load nothing if those files don’t exist at the root (as we saw earlier).

When the action/setup-dotnet action runs with source-url, it actually generates an additional temporary nuget.config file. You can see this in the workflow run output:

dotnet-auth: Finding any source references in /home/runner/work/using-setup-dotnet-action/using-setup-dotnet-action/nuget.config, writing a new temporary configuration file with credentials to /home/runner/work/using-setup-dotnet-action/nuget.config

That temporary file contains the authentication details for the feed.

But the restore fails:

Determining projects to restore...
/usr/share/dotnet/sdk/10.0.401/NuGet.targets(198,5): warning : Your request could not be authenticated by the GitHub Packages service. Please ensure your access token is valid and has the appropriate scopes configured. [/home/runner/work/using-setup-dotnet-action/using-setup-dotnet-action/globaljson-and-nugetconfig/tests/tests.csproj]
  Retrying 'FindPackagesByIdAsync' for source 'https://nuget.pkg.github.com/flcdrg/download/verify.mongodb/index.json'.

If you take a look at the contents of the temporary nuget.config file you might see the problem:

<configuration>
  <config>
    <add key="defaultPushSource" value="https://nuget.pkg.github.com/flcdrg/index.json"/>
  </config>
  <packageSources>
    <add key="Source" value="https://nuget.pkg.github.com/flcdrg/index.json"/>
  </packageSources>
  <packageSourceCredentials>
    <Source>
      <add key="Username" value="flcdrg"/>
      <add key="ClearTextPassword" value="***"/>
    </Source>
  </packageSourceCredentials>
</configuration>

The action knows the feed URL, but it has defaulted to using Source as the source name.

The issue here is that when dotnet restore runs, it finds the nuget.config file in the application directory and combines that with the temporary file.

The packageSourceMapping defines the github source as the only place to look for Verify.MongoDB package. But the temporary nuget.config file didn’t use that name. So you end up that error.

The solution is to set the config-file input parameter so that the action can use the correct source name from your nuget.config file. With the names aligned, everything should work correctly.

Summary

  • If global.json is not in the root of the repository, use global-json-file
  • If NuGet.Config is not in the root of the repository, use config-file

More recent posts

Older posts

All posts (currently 1192) can be found in the archive. Jump to recent years in 2026 , 2025 and 2024 .