Eternet.ReleasePlanner
1.1.1
Prefix Reserved
dotnet tool install --global Eternet.ReleasePlanner --version 1.1.1
dotnet new tool-manifest
dotnet tool install --local Eternet.ReleasePlanner --version 1.1.1
#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: writesrelease-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,minorormajor.reason(optional): free text for reviewers.baseVersion(optional): pins the package to a version line. Accepts a stableX.YorX.Y.Z(X.Yis normalized toX.Y.0); anything else fails withVersionIntentMalformed.
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.Majorare considered; the latest of them is L. Prerelease and higher/lower major lines are ignored, so a higher published major never raisesProjectVersionBehindPublishedLine. - 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).majortogether withbaseVersionis rejected (VersionIntentMalformed): a pinned line only takes patch or minor bumps. Start a new line by pinning abaseVersionon 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 | Versions 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. |
This package has no dependencies.