Lib.Release 10.4.0

dotnet tool install --global Lib.Release --version 10.4.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Lib.Release --version 10.4.0
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Lib.Release&version=10.4.0
                    
nuke :add-package Lib.Release --version 10.4.0
                    

Lib.Release

A .NET global tool that packs one or more projects in a target repository and publishes them to nuget.org. What to release is declared in a lib.release.json manifest that lives in the target repo. The tool only publishes a project when its declared version differs from the latest version already on nuget.org, so re-running it is safe and idempotent.

This README is written to help you (human or agent) author a lib.release.json for a repo you want to release — see The release manifest. The rest documents the CLI and the release pipeline.

Install

dotnet tool install --global Lib.Release

Requires the dotnet CLI on PATH (build/pack/push), and git on PATH when the target is a git repository (for the working-tree check). A non-git target skips the working-tree check and does not need git. The tool has no other runtime dependencies.

Usage

lib.release <Lib> [--ApiKey <key>] [--SkipTests] [-v|-d]
Argument / Option Required Description
<Lib> yes The target repo. Either a bare name (resolved under the projects dir — env LIB_RELEASE_PROJECTS_DIR, default c:\projects) or a fully rooted path. Must exist.
--ApiKey <key> yes* nuget.org API key with push permission. Falls back to the NUGET_API_KEY environment variable; an explicit --ApiKey overrides the env var.
--SkipTests no Skip the test step. Default false.
-v, --verbose no Verbose logging (trace level).
-d, --debug no Debug logging.

Options are case-insensitive. \* Required as either the flag or the NUGET_API_KEY env var — the run exits with InvalidArguments if neither is set.

Setup commands (no API key needed)

These modes are self-contained and non-interactive — an agent with only the installed tool (no source access) can discover and bootstrap everything from them:

Command What it does
lib.release --help Usage, the getting-started sequence, and the manifest essentials.
lib.release --check [dir] Pre-flight (read-only): reports whether the repo is releasable and lists what would be released / tested / skipped. Exits Incompatible (13) if nothing is releasable. Run this before authoring a manifest. Default dir: current.
lib.release --init <dir> Scaffolds lib.release.json into <dir>. Greedy: includes every releasable project and every test project found. --exclude <names> (comma/space list) leaves projects out; --force overwrites an existing file.
lib.release --migrate <dir> Converts a legacy (name-based) lib.release.json to the path-based format in place, preserving versions. Writes nothing unless every entry resolves to a real .csproj.
lib.release --schema Prints the JSON Schema for lib.release.json to stdout (e.g. lib.release --schema > lib.release.schema.json). Every field self-describes.

Typical agent flow in a target repo: --check (is it compatible?) → --init (write the manifest) → review versions → release.


The release manifest: lib.release.json

lib.release.json is the only thing you author to make a repo releasable. It lists the projects to publish and the test projects to run. The tool reads the manifest in the target repo — not its own.

Where it goes

Put it anywhere in the repo; the tool searches recursively under <Lib> and uses the first match, so keep exactly one. The repository root is the conventional location.

Schema

A JSON Schema ships with the tool's source at lib.release.schema.json. Reference it from your manifest for editor validation and inline field docs — the tool ignores the $schema key:

{
  "$schema": "https://raw.githubusercontent.com/rzmoz/lib.release/main/lib.release.schema.json",
  "releases": [ { "project": "src/MyLib/MyLib.csproj", "version": "1.0.0", "prerelease": "" } ],
  "tests": [ "test/MyLib.Tests/MyLib.Tests.csproj" ]
}

Projects are referenced by their .csproj path relative to the repo root — there is no file-naming or folder-layout convention.

Migrating from the old format: earlier manifests identified projects by name (resolved by a <repo>/<name>/<name>.csproj convention). That format is no longer supported — the tool detects it and exits StaleManifest (14), and --check flags it. Run lib.release --migrate <dir> to convert it in place, preserving versions (it maps each name to its .csproj path), then commit. (To rebuild from scratch instead, use lib.release --init <dir> --force.)

Field Type Required Description
releases array yes One entry per project to pack and publish.
releases[].project string yes Path to the project's .csproj, relative to the repo root (forward slashes), e.g. src/Foo/Foo.csproj. The nuget package id is read from the csproj (<PackageId><AssemblyName> → file name).
releases[].version string yes Major.Minor.Patch (e.g. 1.4.0). Bump it to publish a new version.
releases[].prerelease string no SemVer 2.0 pre-release label without the leading - (e.g. beta, rc.1). "" or omitted = stable.
tests array of string no Paths (relative to the repo root) of test-project .csproj files to run before packing. Omit or [] for none.

Unknown keys are ignored by the tool, but the schema rejects them so authoring mistakes surface in your editor.

Generating a manifest

The fastest path is lib.release --init <dir>, which applies the rules below greedily and writes the file (preview first with lib.release --check <dir>). To produce or review one by hand, work through these steps — they are exactly what --init automates:

  1. Find the packable projects. Look at every *.csproj in the repo (any folder, any depth) and keep only those that produce a NuGet package:
    • Include class libraries with packaging metadata, and tools with <PackAsTool>true</PackAsTool>.
    • Exclude test projects (*.Tests, or those with <IsPackable>false</IsPackable>), executable/sample apps (<OutputType>Exe</OutputType> console projects you don't ship), and anything else not meant for nuget.org.
  2. Emit one releases entry per packable project. Set project to the csproj path relative to the repo root (e.g. src/Foo/Foo.csproj). No naming convention — the package id is taken from the csproj.
  3. Set the version. Use the project's intended next Major.Minor.Patch. Set prerelease only for pre-release builds. (The committed .csproj version can stay a placeholder like 0.0.0; the tool injects the manifest version at release time — see pipeline.)
  4. List test projects in tests[] by csproj path.
  5. Add the $schema line so editors validate the file.
  6. Save it at the repo root.

Then verify, before committing:

  • every releases[].project path points to an existing .csproj;
  • each release project carries packable metadata and ships its README (see packable csproj);
  • every tests[] path points to an existing .csproj;
  • every version is Major.Minor.Patch.

Examples

Multi-package library repo — DotNet.Basics

Releases three libraries and runs one test project. Note that the repo also contains a DotNet.Basics.Cli.Console sample app — it is deliberately not in releases because it isn't a published package.

{
  "$schema": "https://raw.githubusercontent.com/rzmoz/lib.release/main/lib.release.schema.json",
  "releases": [
    { "project": "DotNet.Basics/DotNet.Basics.csproj",                     "version": "12.12.3", "prerelease": "" },
    { "project": "DotNet.Basics.Pipelines/DotNet.Basics.Pipelines.csproj", "version": "12.11.1", "prerelease": "" },
    { "project": "DotNet.Basics.Cli/DotNet.Basics.Cli.csproj",             "version": "12.15.1", "prerelease": "" }
  ],
  "tests": [
    "DotNet.Basics.Tests/DotNet.Basics.Tests.csproj"
  ]
}

Single-package tool repo — this repo (Lib.Release)

One PackAsTool package, releasing itself, with one test project:

{
  "$schema": "./lib.release.schema.json",
  "releases": [
    { "project": "Lib.Release/Lib.Release.csproj", "version": "10.3.0", "prerelease": "" }
  ],
  "tests": [
    "Lib.Release.Tests/Lib.Release.Tests.csproj"
  ]
}

Project requirements

Referencing projects

A release entry's project is just the .csproj path relative to the repo root (forward slashes), e.g. src/Foo/Foo.csproj. Any layout works — flat, src/, nested, whatever. The package id is whatever the csproj produces: <PackageId> if set, else <AssemblyName>, else the file name. That package id is what the tool looks up on nuget.org (case-insensitively) to decide whether the version already exists.

Packable csproj metadata

Each release project should carry standard NuGet packaging metadata so it produces a valid package. The version fields can stay as placeholders (e.g. 0.0.0) — the tool overwrites them transiently at release time. Example from DotNet.Basics:

<PropertyGroup>
  <TargetFramework>netstandard2.1</TargetFramework>
  <Description>...</Description>
  <Authors>rzmoz</Authors>
  <RepositoryUrl>https://github.com/rzmoz/DotNet.Basics</RepositoryUrl>
  <Version>0.0.0</Version>
  <AssemblyVersion>0.0.0</AssemblyVersion>
  <FileVersion>0.0.0</FileVersion>
  <PackageLicenseExpression>MIT</PackageLicenseExpression>
  <PackageReadmeFile>README.md</PackageReadmeFile>
  <Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
  <None Remove="**/*.tmp" />
  <None Include="README.md" Pack="true" PackagePath="\" />
</ItemGroup>
  • <None Include="README.md" Pack="true" .../> together with <PackageReadmeFile>README.md</PackageReadmeFile> ships the per-project README into the package (required by nuget.org for a README to render).
  • <None Remove="**/*.tmp" /> keeps the transient *.csproj.tmp backups the tool creates out of the package.

Test projects

Each entry in tests[] is a .csproj path (relative to the repo root). The tool runs each in release configuration and fails the release if any test fails.

Clean git working tree

If the target is a git repo, all changes must be committed before releasing — a dirty working tree aborts the run. A non-git target skips this check.


What it does (pipeline)

The tool runs these steps in order against the target repo. The first step to fail stops the run and the tool exits with the matching code from the exit-codes table — it reports the reason and never dumps a stack trace for a known failure.

# Step Behavior Failure code
1 Init for release If <Lib> is a git repo, asserts the working tree is clean (a non-git directory skips this check and proceeds); loads lib.release.json (first match found recursively under <Lib>); then resolves each releases[].project path to an existing csproj. DirtyWorkingTree, GitNotAvailable, ReleaseManifestNotFound, ProjectFileNotFound
2 Init versions For each declared release, queries nuget.org for the latest published version. If the declared version+prerelease equals what is already published, that release is dropped. NoReleaseCandidates (when nothing is left to release)
3 Apply version For each release, backs up the csproj at the project path and patches <Version> (SemVer 2.0) plus <AssemblyVersion> and <FileVersion> (Major.Minor.Patch) into the first <PropertyGroup>, creating the nodes if absent. (The path is resolved and existence-checked in step 1.) ProjectFileFormatNotRecognized
4 Run tests Unless --SkipTests, runs dotnet test "<project>" -c release for each tests[] path. TestProjectNotResolved, TestsFailed
5 Pack nugets Cleans each project's output dir, then runs dotnet pack "<csproj>" -c release --force -o <projDir>/bin/.nuget. PackFailed
6 Push nugets Runs dotnet nuget push <pkg>.nupkg --api-key <key> --source https://api.nuget.org/v3/index.json --skip-duplicate for each produced package. --skip-duplicate makes re-pushing an existing version succeed, which is what keeps the tool idempotent. PushFailed

Important: the csproj version edits from step 3 are transient. When the run finishes (success or failure), every patched .csproj is restored from its backup. The release version is sourced from lib.release.json, applied only for the duration of the pack/push, then reverted — your committed csproj files keep their original (placeholder) version.

Exit codes

Every code is in the 0–255 range. 0 is success; anything else is a distinct failure reason.

Code Name Meaning
0 Success All requested packages released (or nothing needed releasing on a re-run).
1 UnexpectedError An unhandled error, or an exit code the host produced outside 0–255.
2 InvalidArguments Missing/invalid CLI args (e.g. no <Lib>, or no API key).
3 DirtyWorkingTree Uncommitted changes in the target git repo.
4 ReleaseManifestNotFound No lib.release.json found under <Lib>.
5 NoReleaseCandidates Everything declared is already published. Logged as a warning.
6 ProjectFileNotFound A releases[].project path does not point to an existing .csproj.
7 ProjectFileFormatNotRecognized The csproj has no <Project>/<PropertyGroup>.
8 TestProjectNotResolved A tests[] path does not point to an existing .csproj.
9 TestsFailed dotnet test returned non-zero.
10 PackFailed dotnet pack returned non-zero.
11 PushFailed dotnet nuget push returned non-zero.
12 GitNotAvailable <Lib> is a git repo but git was not found on PATH.
13 Incompatible --check/--init found no packable .csproj anywhere in the repo.
14 StaleManifest The lib.release.json uses the old name-based format. Regenerate with --init --force.

Release workflow

  1. Bump version (and/or prerelease) in lib.release.json for the projects you want to publish.
  2. Commit the change (if the repo is under git, the working tree must be clean).
  3. Run lib.release <repo> --ApiKey <key>.

Only projects whose manifest version differs from what is already on nuget.org are packed and pushed; everything else is skipped. If no project needs releasing, the run exits early.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
10.4.0 133 5/27/2026
10.3.0 106 5/27/2026
10.2.0 115 5/27/2026
10.1.31 112 5/8/2026
10.1.30 103 5/8/2026
10.1.23 99 5/6/2026
10.1.22 105 5/6/2026
10.1.21 91 5/6/2026
10.1.20 110 5/6/2026
10.1.12 87 5/6/2026
10.1.11 89 5/6/2026
10.1.10 93 5/6/2026
10.1.9 101 5/4/2026
10.1.8 112 5/4/2026
10.1.7 92 5/4/2026
10.1.6 92 5/4/2026
10.1.5 95 5/4/2026
10.1.4 98 5/4/2026
10.1.3 104 5/4/2026
10.1.2 93 5/4/2026
Loading failed