Eternet.ReleasePlanner 1.1.1

Prefix Reserved
dotnet tool install --global Eternet.ReleasePlanner --version 1.1.1
                    
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 Eternet.ReleasePlanner --version 1.1.1
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Eternet.ReleasePlanner&version=1.1.1
                    
nuke :add-package Eternet.ReleasePlanner --version 1.1.1
                    

Eternet.ReleasePlanner

Eternet.ReleasePlanner is a .NET global tool for planning and publishing NuGet releases from repositories with multiple related packages.

Install

dotnet tool install --global Eternet.ReleasePlanner

Commands

  • plan: writes release-plan.json, release-manifest.json, and generated package version targets.
  • validate-dependency-closure: verifies that local package dependencies are either in the current artifact set or already published to the feed.
  • push-waves: pushes planned packages to NuGet in dependency wave order.
  • write-tag-message: writes the annotated Git tag message containing the release manifest payload.
  • exchange-nuget-oidc: exchanges a GitHub Actions OIDC token for a NuGet API key when Trusted Publishing is configured.

Example

eternet-release-planner plan `
  --source-directory ./src `
  --project-list ./scripts/publishable-projects.txt `
  --plan-output ./artifacts/release-plan.json `
  --manifest-output ./artifacts/release-manifest.json `
  --generated-targets ./src/Generated.PackageVersions.targets `
  --feed-base-url https://api.nuget.org/v3-flatcontainer `
  --scope changed

For a bounded owner-package release, add --package-ids Package.A,Package.B. The planner retains the complete catalog and selects those IDs plus all outgoing internal dependencies, recursively. It does not include reverse dependents or unrelated changed packages. Unknown, empty and duplicate IDs fail. Review shared source owners and client/gateway compatibility separately before choosing this mode; it does not claim an ecosystem-wide release.

Add --expected-source-commit <full-sha> to reject a different checkout. In release mode, an insufficient bump or missing classifier-required explicit version intent fails before artifacts are written. --dry-run retains classification evidence for review, including required bumps, without enforcing publication readiness.

release-waves.yml requires package_ids, expected_package_ids (the exact reviewed set including dependencies), expected_sha, and pull_request. It checks the checkout, current same-repository PR head and exact plan set before building and again before staging/publication. A moved PR must be reviewed again. A normal dispatch still publishes automatically after checks; these guards are not a manual approval step. version_intent_json controls bumps, not package selection.

Version intent

--version-intent <path> (the version_intent_json workflow input) overrides the default patch bump per package:

{
  "packages": {
    "Package.A": { "bump": "minor", "reason": "Add public API" },
    "Package.B": { "bump": "patch", "baseVersion": "5.0", "reason": "Fluent UI v5 line" }
  }
}
  • bump (required): patch, minor or major.
  • reason (optional): free text for reviewers.
  • baseVersion (optional): pins the package to a version line. Accepts a stable X.Y or X.Y.Z (X.Y is normalized to X.Y.0); anything else fails with VersionIntentMalformed.

Without baseVersion, the release version comes from the project <Version> and the latest stable version published on the feed (any line), exactly as before.

With baseVersion B, the line is B.Major:

  • Only published stable versions with Major == B.Major are considered; the latest of them is L. Prerelease and higher/lower major lines are ignored, so a higher published major never raises ProjectVersionBehindPublishedLine.
  • The project <Version> is ignored for version calculation (the generated targets still stamp the planned version); B is the release floor.
  • The release baseline (change detection and public API classification) is the newest release manifest whose package version is on the line, falling back to the newest one on a lower major (a new line starts from the previous one). Manifests on a higher line are never used, so a 2.0 maintenance release is compared with the latest 2.0.x release, not with 5.0.x.
  • No L: release = B.
  • patch: release = max(B, L.Major.L.Minor.(L.Patch + 1)).
  • minor: release = max(B, L.Major.(L.Minor + 1).0).
  • major together with baseVersion is rejected (VersionIntentMalformed): a pinned line only takes patch or minor bumps. Start a new line by pinning a baseVersion on the new major instead.

Required-bump classification still runs. The step delivered by the line itself (lineBump: the patch release on the line compared with L, or, when the line has no published version, with the latest stable version on a lower major line) satisfies classifier requirements up to that step. Moving to a new major line therefore satisfies a breaking public API change without requesting major (VersionLineBaseline evidence), while a breaking change inside an existing line still fails release mode with ReleaseBumpIntentInsufficient. A base above the next patch of L records VersionLineFloor evidence.

release-plan.json exposes the effective line for pinned packages only (the field is omitted otherwise, so unpinned plans are unchanged):

"versionLine": {
  "baseVersion": "5.0.0",
  "latestPublishedVersion": null,
  "lineBump": "major",
  "major": 5,
  "previousVersion": "2.0.46"
}

latestPublishedVersion at package level keeps reporting the latest stable version on any line. There is no top-level baseVersion default: packages in one repository usually live on different lines, and a default would also pin dependencies pulled in by --package-ids.

Coexisting version lines

Pin a base version only while two or three lines are maintained in parallel, then drop it once a single line remains. Example: Eternet.Web.UI moves from 2.0.x (Fluent UI v4) to 5.0.x (Fluent UI v5, aligned with Fluent's major) while 2.0.x keeps receiving fixes from a maintenance branch. Feed: 2.0.46.

On main, the first 5.0 release:

{ "packages": { "Eternet.Web.UI": { "bump": "patch", "baseVersion": "5.0", "reason": "Fluent UI v5" } } }

plans 5.0.0 (no 5.x published yet; lineBump major). The next main releases with the same intent plan 5.0.1, 5.0.2, ... (or 5.1.0 with "bump": "minor").

On the maintenance branch, after 5.0.x exists on the feed:

{ "packages": { "Eternet.Web.UI": { "bump": "patch", "baseVersion": "2.0", "reason": "Fluent UI v4 fix" } } }

plans 2.0.47, ignoring the higher 5.0.x line and the branch's project version. When 2.0.x is retired, remove baseVersion. The unpinned planner then continues from the latest published line (5.0.x), provided the project <Version> is unset or already on that line; a project version still on 2.0 fails with ProjectVersionBehindPublishedLine.

Validate

Validate planner and publication guards locally:

dotnet test --project tools/Eternet.ReleasePlanner/tests/Eternet.ReleasePlanner.Tests/Eternet.ReleasePlanner.Tests.csproj -c Release
./scripts/Test-ReleasePublication.ps1
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
1.1.1 0 10/5/2026
1.1.0 63 10/1/2026
1.0.2 362 5/9/2026
1.0.1 125 5/8/2026
1.0.0 127 5/6/2026