NowAround.DevOps.Versioning.Cli 1.2.0

NowAround.DevOps.Versioning.Cli

NowAround.DevOps.Versioning.Cli is a .NET tool for Azure DevOps semantic version calculation. It reads git release tags and Azure DevOps pull request metadata, then emits a package version for PR, main, and release pipelines.

Design

The tool separates version decision from release execution:

  • PR validation decides a bump and persists it as a PR label.
  • Main and release pipelines read only persisted semver:* labels.
  • Stable release state is represented by git tags such as v1.2.3.
  • Package versions are emitted as Azure Pipeline variables.

This makes main and release builds deterministic: they do not infer from titles or branches after the PR has merged.

Modes

PR mode:

nowaround-versioning calculate --mode pr --output azure-pipelines

Output shape:

X.Y.Z-pr.<pullRequestId>.<buildId>

If the current PR is missing a semver:* label, PR mode infers a bump from description markers, branch prefixes, title prefixes, or --unknown-bump-policy, then adds the matching PR label.

Main mode:

nowaround-versioning calculate --mode main --output azure-pipelines

Output shape:

X.Y.Z-ci.<buildId>

Main mode scans completed PRs since the latest stable tag and fails if an included PR has no semver:* label.

Release mode:

nowaround-versioning calculate --mode release --output azure-pipelines

Output shape:

X.Y.Z

Release mode uses the same strict label-only behavior as main mode.

Local mode:

nowaround-versioning calculate --mode local --output text

Output shape:

X.Y.Z-dev.<headSha>

Local mode uses the same completed-PR scan as main mode, then appends a prerelease label and the current HEAD commit. It is intended for developer builds and can cache the calculated value in a gitignored file.

Inputs

The tool reads Azure Pipelines variables by default:

SYSTEM_COLLECTIONURI
SYSTEM_TEAMPROJECT
BUILD_REPOSITORY_ID
SYSTEM_ACCESSTOKEN
SYSTEM_PULLREQUEST_PULLREQUESTID
BUILD_BUILDID

Equivalent CLI options are available:

nowaround-versioning calculate \
  --mode pr \
  --collection-uri "https://dev.azure.com/ORG/" \
  --project "PROJECT" \
  --repository-id "GUID" \
  --access-token "$SYSTEM_ACCESSTOKEN" \
  --pull-request-id "123" \
  --build-id "4567"

Azure Pipelines System.AccessToken uses bearer authentication. For local testing with a PAT:

nowaround-versioning calculate \
  --mode pr \
  --access-token "$AZURE_DEVOPS_PAT" \
  --access-token-type pat

Output

With --output azure-pipelines, the tool writes:

##vso[task.setvariable variable=BaseVersion]...
##vso[task.setvariable variable=NextVersion]...
##vso[task.setvariable variable=PackageVersion]...
##vso[task.setvariable variable=VersionBump]...
##vso[task.setvariable variable=LastStableTag]...

With --output json, it emits the full calculation result, including included PR decisions.

With --output value, it emits only the calculated package version.

Local Build Cache

Developer builds can avoid repeated Azure DevOps API calls by using local mode with --cache-file:

git fetch --tags

nowaround-versioning calculate \
  --mode local \
  --output text \
  --cache-file obj/nowaround/version.json \
  --write-msbuild-props obj/nowaround/NowAround.Version.props \
  --collection-uri "https://dev.azure.com/ORG/" \
  --project "PROJECT" \
  --repository-id "GUID" \
  --access-token "$AZURE_DEVOPS_PAT" \
  --access-token-type pat

When the cache exists and still matches HEAD, branch, target branch, and latest stable tag, the tool uses it without contacting Azure DevOps. If the cache is missing or stale, the tool recalculates the version and writes a fresh cache. Use --refresh-cache to force recalculation.

The generated props file can be imported from Directory.Build.props by projects that should embed the version:

<Project>
  <Import Project="$(MSBuildThisFileDirectory)obj/nowaround/NowAround.Version.props"
          Condition="Exists('$(MSBuildThisFileDirectory)obj/nowaround/NowAround.Version.props')" />
</Project>

If you import from an individual .csproj, write the cache under that project's directory or adjust the import path accordingly.

To let normal local builds calculate the version automatically when the cache is missing or stale, add a local-only target in Directory.Build.targets:

<Project>
  <Target Name="ResolveNowAroundLocalVersion"
          BeforeTargets="GetAssemblyVersion"
          Condition="'$(TF_BUILD)' != 'True'">
    <Exec Command="nowaround-versioning calculate --mode local --output value --cache-file &quot;$(MSBuildThisFileDirectory)obj/nowaround/version.json&quot; --collection-uri &quot;$(NowAroundCollectionUri)&quot; --project &quot;$(NowAroundProject)&quot; --repository-id &quot;$(NowAroundRepositoryId)&quot; --access-token &quot;$(AZURE_DEVOPS_PAT)&quot; --access-token-type pat"
          ConsoleToMsBuild="true">
      <Output TaskParameter="ConsoleOutput" PropertyName="NowAroundPackageVersion" />
    </Exec>
    <PropertyGroup>
      <Version>$(NowAroundPackageVersion)</Version>
      <PackageVersion>$(NowAroundPackageVersion)</PackageVersion>
      <InformationalVersion>$(NowAroundPackageVersion)</InformationalVersion>
    </PropertyGroup>
  </Target>
</Project>

Set NowAroundCollectionUri, NowAroundProject, and NowAroundRepositoryId as MSBuild properties or environment variables. Set AZURE_DEVOPS_PAT in your local environment. The target still starts the CLI on each local build, but valid cache hits do not call Azure DevOps.

The props file sets Version, PackageVersion, InformationalVersion, AssemblyVersion, and FileVersion. Runtime code can read AssemblyInformationalVersionAttribute and publish that value as service.version.

Keep local outputs out of git:

obj/nowaround/

Files under obj are removed by dotnet clean, so clean rebuilds recalculate the local version while normal builds can reuse the cache.

The PAT used for local cache refresh must be able to read the repository and pull request labels. Label write permission is only needed for pr mode when the tool creates missing semver:* labels.

Artifact Publish Decisions

Artifact publish decisions are separate from SemVer calculation. semver:* labels decide the version bump. artifact:* labels decide whether NuGet packages and/or Docker images should be published with the calculated PackageVersion.

Enable artifact decision variables with:

nowaround-versioning calculate \
  --mode main \
  --output azure-pipelines \
  --unknown-bump-policy fail \
  --include-artifact-decisions \
  --artifact-default none

--artifact-default accepts none, nuget, docker, or all. If omitted, the default is none for PR/main mode and all for release mode. In main mode, --artifact-triggering-pr-required fails the pipeline when the triggering PR cannot be found; otherwise publishing is disabled with ArtifactDecisionSource=no-triggering-pr.

Supported artifact labels:

artifact:publish
artifact:nuget
artifact:docker
artifact:none

Use at most one artifact:* decision label on a PR. Multiple artifact decision labels fail the run.

Main mode artifact decision looks only at the completed PR that produced the current main run's BUILD_SOURCEVERSION merge commit. It does not use the full unreleased PR set scanned for SemVer calculation.

When enabled, Azure Pipelines output includes:

PublishArtifacts
PublishNuGet
PublishDockerImage
ArtifactDecision
ArtifactDecisionSource
ArtifactDecisionLabels
TriggeringPullRequestId

Publish conditions:

condition: and(succeeded(), eq(variables['PublishNuGet'], 'true'))
condition: and(succeeded(), eq(variables['PublishDockerImage'], 'true'))

Supported Labels

semver:major
semver:minor
semver:patch
semver:none

Only one semver:* label is allowed per PR. Multiple labels fail the run.

Git Requirements

Use full history and tags:

- checkout: self
  fetchDepth: 0
  persistCredentials: true

Existing Repositories

Before enabling the tool on an existing repository, create one bootstrap stable tag on the current main commit or on the last released commit:

git checkout main
git pull
git tag -a v1.2.3 -m "Bootstrap versioning from v1.2.3"
git push origin v1.2.3

Use the version that represents the current released package. The tag must match the configured stable tag pattern, which defaults to v[0-9]*.[0-9]*.[0-9]*.

Without this tag, pr, main, and release modes treat all reachable completed PRs as unreleased history. Existing completed PRs that do not already have exactly one semver:* label can then fail the pipeline.

The latest stable tag defaults to:

v[0-9]*.[0-9]*.[0-9]*

The default tag prefix is:

v

Both can be customized with --stable-tag-pattern and --tag-prefix.

Implementation Notes

The tool uses:

  • git tag, git rev-list, git rev-parse, and git merge-base for local repository history.
  • Azure DevOps Git REST API for pull requests and PR labels.
  • System.Text.Json and HttpClient instead of the large Azure DevOps SDK dependency, keeping the package small.

The package does not create release tags or push NuGet packages. Pipelines should use $(PackageVersion) for those steps.

This package has no dependencies.

Version Downloads Last updated
2.0.0 24 06/15/2026
1.2.1 4 06/15/2026
1.2.0 42 06/01/2026
1.2.0-ci.251 1 06/01/2026
1.2.0-ci.216 1 05/23/2026
1.1.0 42 05/23/2026
1.1.0-ci.211 1 05/23/2026
1.1.0-ci.209 1 05/23/2026
1.0.3 5 05/23/2026
1.0.2 3 05/23/2026
1.0.1 33 05/15/2026
1.0.0 6 05/14/2026
0.1.1 5 05/14/2026
0.1.0 5 05/14/2026
0.0.3 8 05/14/2026
0.0.2 3 05/14/2026
0.0.1 4 05/14/2026