NuVersions 0.3.0
dotnet tool install --global NuVersions --version 0.3.0
dotnet new tool-manifest
dotnet tool install --local NuVersions --version 0.3.0
#tool dotnet:?package=NuVersions&version=0.3.0
nuke :add-package NuVersions --version 0.3.0
NuVersions (formerly PreReleaseDelist)
A CLI to show how a NuGet package's versions are listed on a NuGet server, and to unlist versions of the package.
Features
- Show every version of a NuGet package together with whether each one is listed or unlisted (
show). - Unlist specific versions of a NuGet package by name, pre-release and stable versions alike (
unlist). - Unlist all pre-release versions of a package in one go (
unlist --unlist-all). - Distinguishes a version that is already unlisted from a version the server has never heard of, so a mistyped version string is reported as a failure rather than a silent success.
- Preview an unlist with
--dry-run. No delete request is sent and no API key is required. - Machine-readable stdout with
--output json, plus a small documented exit-code contract for CI. - Configure NuGet API Key and Server URL via environment variables.
Renamed from PrereleaseDelist: the
prerelease-delisttool is nownuversions, with today's unlist behaviour available asnuversions unlist. The environment variable prefix is nowNUVERSIONS_, the oldNUGET_*generic fallbacks are unchanged, andshowis the first sibling command. See the0.3.0entry in the changelog.
Installation
NuVersions ships through two distribution channels.
The dotnet tool package (primary)
The primary distribution is the dotnet tool package on NuGet:
dotnet tool install --global NuVersions
After installing, the nuversions command will be available on your PATH.
Prerequisites
- The .NET 10 Runtime, required by the
dotnet toolchannel. - A .NET SDK on the host, but only for the
sdkbackend (--backend sdk), which drives SDK tooling that packaging cannot bundle. The standalone binaries below cannot remove this requirement.
This channel runs both backends (http and sdk) and serves musl/Alpine users, since no standalone linux-musl-* binary is published.
Standalone binaries (GitHub Releases)
Self-contained, single-file binaries are attached to each GitHub Release for exactly six RIDs:
win-x64, win-arm64, linux-x64, linux-arm64, osx-x64, osx-arm64
- No .NET installation is required. These binaries run the HTTP backend out of the box.
- The
sdkbackend still requires a host .NET SDK installation, which packaging cannot change; use thedotnet toolchannel with an SDK installed if you need--backend sdk. - Each release ships SHA-256 checksums: one checksum file per artefact plus a combined
sha256.txtlisting every pair, verifiable withsha256sum -c. - musl/Alpine users are served by the
dotnet toolchannel; nolinux-musl-*RID is built.
Build
dotnet build -c Release
Trim and Native AOT compatibility
NuVersions is not trimming-safe or Native AOT compatible, due to its use of NuGet.Protocol:
- NuGet.Protocol marks several code paths this tool exercises (V3 service index parsing, repository signature resources, plugin serialization) with
[RequiresUnreferencedCode], because they deserialize JSON via the reflection-based Newtonsoft.Json library. The reflection makes the code paths unverifiable for the trimmer and the Native AOT compiler, so trimmed or AOT-published builds surface IL2026/IL2104/IL3053 warnings and may fail at runtime. - NuVersions' own code avoids reflection-based serialization (the
--output jsonNDJSON (Newline delimited JSON) output uses System.Text.Json source generation), so nothing on the tool's side contributes to these warnings.
Both distribution channels avoid the problem by design: the dotnet tool package is framework-dependent (never trimmed), and the standalone GitHub Release binaries are self-contained, untrimmed JIT builds. This may be revisited if a future NuGet.Protocol release removes the Newtonsoft.Json dependency.
Usage
The nuversions command has subcommands, one per operation.
nuversions [subcommand] [options]
| Subcommand | Reads or writes | What it does |
|---|---|---|
show |
read-only | Print every version of a package with whether it is listed or unlisted. No API key needed. |
unlist |
write | Unlist specified versions, or all pre-release versions, of a package. |
Running nuversions with no subcommand prints the command hierarchy.
Shared options
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
--package-id |
string | yes | — | The ID of the package, e.g. MyPackage. |
--server-url |
string | no | https://api.nuget.org/v3/index.json |
The NuGet server's V3 service index URL. Useful for third-party NuGet servers. Falls back to the NUVERSIONS_NuGetServerUrl or NUGET_SERVER_URL environment variables. |
--output |
text | json |
no | text |
Output format for stdout: human-readable text, or newline-delimited JSON (one object per version). stderr is unaffected in both modes. |
show
Prints every version the server knows for the package, including unlisted versions, with one status word per version:
Version=1.0.0 Status=listed
Version=1.0.1 Status=unlisted
listed: the server lists the version in search results.unlisted: the server knows the version but does not list it.
The output is sorted by version, ascending. show is a pure read: it sends no delete request
and needs no API key. It exits 0 when the package exists, 1 when a feed-side error occurs,
and 2 when the package does not exist on the server.
To unlist a version, use unlist with that version. The show statuses are deliberately
different from the unlist statuses: show describes the current state; unlist describes the
attempt it just made.
unlist
nuversions unlist --package-id "MyPackage" --api-key "myApiKey" "1.0.0-alpha.1" "1.0.0-alpha.2"
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
versions (argument) |
string[] | Yes (unless --unlist-all is used) |
— | One or more version strings to unlist, passed positionally. There is no --versions option. Only version strings starting with a digit are considered; others are ignored, except a token starting with -, which is rejected as an unknown option (see Exit codes). Pre-release and stable versions are both accepted; to unlist every pre-release version at once, see --unlist-all. |
--package-id |
string | Yes | — | The ID of the package to unlist versions of, e.g. MyPackage. |
--api-key |
string | Yes* | — | The NuGet API key to authenticate with. If omitted, the CLI falls back to the NUVERSIONS_NuGetApiKey or NUGET_API_KEY environment variables. |
--server-url |
string | No | https://api.nuget.org/v3/index.json |
The NuGet server's V3 service index URL. Useful for third-party NuGet servers. Falls back to the NUVERSIONS_NuGetServerUrl or NUGET_SERVER_URL environment variables. |
--unlist-all |
boolean | No | false |
Unlist all pre-release versions of the package instead of the explicitly listed versions. |
--include-zero-major |
boolean | No | false |
With --unlist-all, also include stable 0.x (Major == 0) versions. |
--use-strict-parsing |
boolean | No | true |
When true, an invalid version string causes an error. When false, invalid version strings are silently skipped. |
--backend |
http | sdk |
No | http |
Which unlisting backend to use: the V3 http API, or the .NET SDK's package deprecation/unlist support (sdk). |
--non-interactive |
boolean | No | false |
Print one machine-friendly Version=<version> Status=<kebab-case> line per version on stdout and exit non-zero if any version failed. Useful in CI. |
--dry-run |
boolean | No | false |
Print the unlist plan without sending any delete request. Works without an API key. Exits only with 0, 1, or 2. See Dry runs. |
--output |
text | json |
No | text |
Output format for stdout: human-readable text, or newline-delimited JSON (one object per version). stderr is unaffected in both modes. |
* Required either on the command line or via environment variable (see Configuration below), except under --dry-run, where no API key is needed at all.
Unlist version states
Every unlist run resolves each requested version to exactly one member of a closed status vocabulary. The same kebab-case word appears in --non-interactive lines, in --output json payloads, and in the exit-code mapping:
| State | --non-interactive output |
JSON status |
Exit code contribution |
|---|---|---|---|
| Listed on the server (now unlisted) | Status=unlisted |
unlisted |
0 |
| On the server but already unlisted | Status=already-unlisted |
already-unlisted |
0 |
| Not known to the server at all | Status=not-on-server |
not-on-server |
1 |
| Unlist attempt failed | Status=failed |
failed |
1 |
| Server rate-limited the run | Status=rate-limited |
rate-limited |
3 |
| Not attempted; the run stopped first | Status=not-attempted |
not-attempted |
4 |
Status renames: the unlist vocabulary was
delisted/already-delistedunder theprerelease-delistname; the same two states areunlisted/already-unlistednow, matching nuget.org's terminology. The all-pre-release option is now--unlist-all(was--delist-all).
A version the server has never heard of, a typo or a version that was never published, is reported as a failure (not-on-server), not as "already unlisted". Versions that do need unlisting are still processed, so one bad entry in a batch does not prevent the rest from being unlisted.
Dry runs
--dry-run resolves and prints the unlist plan without sending any delete request. Network reads still happen; nothing is written.
Each requested version lands in one of three buckets, printed as Version=<version> Bucket=<bucket>:
| Bucket | Meaning |
|---|---|
would-unlist |
The version exists and is listed, so a real run would unlist it. |
already-unlisted |
The version exists but is unlisted. |
not-on-server |
The server has no record of the version (a typo, or a version never published). |
Exit is bucket-aware: 0 only when every requested version exists on the server, 1 when any version is not-on-server, and 2 when the package itself does not exist on the server. Dry runs exit only with 0, 1, or 2, never 3 or 4.
A dry run does not require an API key: delete requests are the only credential-bearing path, and a dry run sends none. With --output json, each plan line is a JSON object using the same closed status vocabulary: a would-unlist version renders as not-attempted, because the delete attempt is never made.
nuversions unlist --package-id "MyPackage" --dry-run "1.0.0-alpha.1" "1.0.0-alpha.2"
Version=1.0.0-alpha.1 Bucket=would-unlist
Version=1.0.0-alpha.2 Bucket=already-unlisted
JSON output
--output json switches stdout to newline-delimited JSON (NDJSON): one object per requested version, with exactly three fields in a fixed order. No envelope, no array wrapper, no additional fields:
{"package":"MyPackage","version":"1.0.0-alpha.1","status":"unlisted"}
{"package":"MyPackage","version":"1.0.0-alpha.2","status":"not-on-server"}
package: the package id exactly as supplied to the run.version: the normalised version string.status: a kebab-case member of the subcommand's status vocabulary in Unlist version states or inshow.showreports the listed/unlisted facts;unlistreports what its attempt did.
stderr is unchanged by this mode. Every error and diagnostic still prints there as human-readable text. In all modes, results, summaries, dry-run plans, and show listings print to stdout; errors and diagnostics print to stderr; the API key is never written to any stream. When both are set, --output json takes precedence over --non-interactive for stdout.
Exit codes
The CLI exits only with these codes:
| Code | Meaning |
|---|---|
0 |
Success: every requested version was unlisted or was already unlisted. For show, the package exists and every version's state was reported. |
1 |
At least one version failed, including a version the server does not have (not-on-server); also command-line errors the argument parser rejects (see below). |
2 |
Validation failure detected by the CLI itself: an unknown option, an empty --package-id, an invalid --backend or --output value, no version strings without --unlist-all, a missing API key on a real run, a package that does not exist on the server, or an invalid version string under strict parsing. For show, the package does not exist on the server. |
3 |
The server rate-limited the run; the run stopped fail-fast and reported the results so far. |
4 |
The run was cancelled; the remaining versions were not attempted. |
130 |
Interrupted with Ctrl-C. |
Command lines that the argument parser rejects outright, a missing required option such as --package-id or an option missing its value, never reach that validation: the parser writes its message to stderr and the process exits 1 without contacting the server. An unknown option is a different case: the parser hands it to the versions position, where the CLI detects it, writes Error: Unrecognized option: '--typo'. to stderr and exits 2. A typo-ed option is never silently dropped as a non-version string.
When a run produces more than one of these codes, the most severe wins: 3 beats 4, which beats 1, which beats 0. unlist --dry-run reuses 0, 1, and 2 only.
Environment variables
The CLI enables DotMake command-line directives, so you can set environment variables inline for a single run using the DotMake [env:...] directive:
nuversions unlist --package-id "MyPackage" --api-key "myApiKey" [env:NuGetServerUrl=https://my-server/api/v3/index.json]
Configuration
Options can also be satisfied from environment variables instead of the command line. Precedence is: explicit --api-key/--server-url command-line options first, then NUVERSIONS_-prefixed environment variables, then generic NUGET_* environment variables, then built-in defaults.
| Option | NUVERSIONS_ env var |
Generic env var |
|---|---|---|
--api-key |
NUVERSIONS_NuGetApiKey |
NUGET_API_KEY |
--server-url |
NUVERSIONS_NuGetServerUrl |
NUGET_SERVER_URL |
# bash
export NUGET_API_KEY="myApiKey"
export NUGET_SERVER_URL="https://api.nuget.org/v3/index.json"
# PowerShell
$env:NUGET_API_KEY = "myApiKey"
$env:NUGET_SERVER_URL = "https://api.nuget.org/v3/index.json"
The --api-key lookup order is therefore: --api-key option → NUVERSIONS_NuGetApiKey → NUGET_API_KEY → error (the CLI exits with a message if no key is found). If neither server-URL variable is set, the CLI falls back to https://api.nuget.org/v3/index.json.
Examples
Show how every version of a package is listed, stable and pre-release, listed and unlisted:
nuversions show --package-id "MyPackage"
Unlist specific versions of a package (any pre-release or stable version):
nuversions unlist --package-id "MyPackage" --api-key "myApiKey" "1.0.0-alpha.1" "1.0.0-alpha.2"
Unlist a broken or vulnerable stable release. Stable versions are unlisted the same way:
nuversions unlist --package-id "MyPackage" --api-key "myApiKey" "1.4.2"
Unlist all pre-release versions of a package:
nuversions unlist --package-id "MyPackage" --unlist-all true --api-key "myApiKey"
Unlist all pre-release versions, including stable 0.x versions:
nuversions unlist --package-id "MyPackage" --unlist-all true --include-zero-major true --api-key "myApiKey"
Unlist from a third-party NuGet server:
nuversions unlist --package-id "MyPackage" --api-key "myApiKey" --server-url "https://my-server/api/v3/index.json" "2.0.0-beta.4"
Use the .NET SDK backend instead of the HTTP API:
nuversions unlist --package-id "MyPackage" --api-key "myApiKey" --backend sdk "1.2.0-pre.1"
Run non-interactively (e.g. in CI), turning on lenient version parsing:
nuversions unlist --package-id "MyPackage" --api-key "myApiKey" --non-interactive true --use-strict-parsing false "0.9.0-beta" "1.0.0"
Preview what an unlist would do, with no API key and no delete request:
nuversions unlist --package-id "MyPackage" --dry-run "1.0.0-alpha.1" "1.0.0-alpha.2"
Emit newline-delimited JSON on stdout for machine consumption:
nuversions show --package-id "MyPackage" --output json
nuversions unlist --package-id "MyPackage" --api-key "myApiKey" --output json "1.0.0-alpha.1"
Unlisting and deprecation
Unlisting hides a version from search and from the nuget.org package details page. Existing consumers can still restore it, and unlisting tells them nothing by itself. Deprecation metadata is what surfaces warnings to dotnet list package --deprecated and IDE package managers. If you are unlisting a vulnerable or broken release, consider also deprecating the version (or a replacement) on nuget.org so consumers get the reason.
Rate limits
NuGet.org's NuGet server implementation has an API rate limit for unlisting packages of 250 package versions per hour per API Key.
Third party NuGet servers may have their own rate limits. Please check your NuGet server's documentation for more information.
When the server rate-limits the run, this CLI fails fast: it stops sending further delete requests, reports the results collected so far (versions not yet attempted are reported as not-attempted), and exits with code 3. It does not retry and does not wait for the rate-limit window to reset. Rerun once the window has elapsed. Rate-limit detection is authoritative by HTTP status code on the http backend and best-effort on the sdk backend.
License
The CLI project is licensed under the GNU GPL v3.0 or later. See the LICENSE file for details.
The library powering the CLI's NuGet-related functionality is licensed under the GNU LGPL v3.0 or later.
| 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.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.3.0 | 0 | 10/20/2026 |
## Changes since 0.2.0
**NOTE**: PreReleaseDelist has been renamed to **NuVersions**.
### Added
- The `nuversions show` subcommand: a pure read that prints every version of a package, stable
and pre-release, listed and unlisted, with one `Status=listed|unlisted` line per version.
It sends no delete request and needs no API key. With `--output json`, same one-object-per-line
NDJSON schema as `unlist`; a missing package exits `2`.
### Changed
- The project is renamed: the project name `PreReleaseDelist` becomes `NuVersions`, the NuGet id
`PreReleaseDelist` becomes `NuVersions`, and the `prerelease-delist` command becomes
`nuversions`. The single command is now a hierarchy: `nuversions unlist` (prior delist
behavior) and `nuversions show`. The old package id will be deprecated on NuGet.org pointing
at the new one.
- The unlist status vocabulary now matches nuget.org's own wording: the outcome formerly rendered
`delisted`/`already-delisted` is `unlisted`/`already-unlisted`, and dry-run buckets render
`would-unlist`. Everything about the run is otherwise identical: same statuses for
`not-on-server`, `failed`, `rate-limited`, `not-attempted`, same exit codes, same NDJSON shape.
- The domain vocabulary is unlist end to end, per four mappings: Delist becomes Unlist,
Delisting becomes Unlisting, Delisted becomes Unlisted, and Delete becomes Unlist at the
contract level. The all-pre-release option is now `--unlist-all` (was `--delist-all`).
Transport command names keep their wire spelling (HTTP DELETE, `dotnet nuget delete`).
What each term means is defined by the Unlist action entry in
[GLOSSARY.md](GLOSSARY.md).
- The environment-variable fallback prefix is `NUVERSIONS_` (was `PRERELEASEDELIST_`); the
generic `NUGET_API_KEY`/`NUGET_SERVER_URL` fallbacks are unchanged.
- The library's outcome enum members follow the same rename (`PackageVersionStatus.Unlisted`,
`PackageVersionStatus.AlreadyUnlisted`); the assembly is not published on NuGet, so no external
consumer impact is expected.
### Removed
- The `prerelease-delist` command name and the `PreReleaseDelist` package id.