NuGet client library and CLI for Go with protocol parity to the official .NET NuGet.Client.
Library: 77% complete (68/88 milestones)
CLI: Command restructure complete (noun-first hierarchy with package, source, config, restore, and version namespaces)
Interop tests passing against NuGet.Client for feature parity validation.
Core Operations
- Package search, metadata retrieval, version resolution
- Package download with content verification
- Full dependency graph resolution with conflict detection
- Transitive dependency resolution with parallel processing
- Solution file support (.sln, .slnx, .slnf) for multi-project operations
Version System
- SemVer 2.0 parsing and comparison (<20ns/op)
- Legacy 4-part version support (Major.Minor.Build.Revision)
- Version range evaluation with floating version support
Framework Support
- Target Framework Moniker (TFM) parsing and compatibility checking
- Framework-specific dependency selection
- Portable Class Library (PCL) profile mapping
Protocol Implementation
- NuGet V3 (service index, registration, search, download, autocomplete)
- NuGet V2 (OData feeds with XML/Atom parsing)
- Automatic protocol detection
- Multi-source repository management
Package Operations
- Package reading from .nupkg files (ZIP + nuspec parsing)
- Package creation with OPC (Open Packaging Conventions) compliance
- PKCS#7 signature creation, verification, and RFC 3161 timestamping
- Asset selection with Runtime Identifier (RID) resolution
Infrastructure
- Multi-tier caching (LRU memory + disk persistence with ETag validation)
- HTTP/2 and HTTP/3 support with automatic fallback
- Retry logic with exponential backoff and Retry-After header support
- Circuit breaker pattern for fault tolerance
- Per-source rate limiting with token bucket algorithm
Observability
- OpenTelemetry tracing with distributed context propagation
- Prometheus metrics for operation monitoring
- Structured logging via mtlog integration
Authentication
- API key authentication (X-NuGet-ApiKey header)
- Bearer token authentication
- HTTP basic authentication
go get github.com/willibrandon/gonuget
git clone https://github.com/willibrandon/gonuget
cd gonuget
make build
./gonuget --versionSee cmd/gonuget/README.md for CLI documentation.
# Add a package source gonuget source add https://api.nuget.org/v3/index.json --name "NuGet.org" # List configured sources gonuget source list # Add a package to a project gonuget package add Newtonsoft.Json --project MyProject.csproj --version 13.0.3 # List packages from a solution file gonuget package list --project MySolution.sln # List packages (auto-detects project or solution in current directory) gonuget package list # Search for packages gonuget package search Newtonsoft --format json # Get configuration value gonuget config get repositoryPath # Enable shell completion (bash, zsh, powershell) gonuget completion bash > /etc/bash_completion.d/gonuget
Command Structure: Noun-first hierarchy matching dotnet CLI (e.g., gonuget package add, gonuget source list)
Solution File Support:
- Lists packages from all projects in
.sln,.slnx, and.slnffiles - Auto-detects solution files in the current directory
- Cross-platform path handling (Windows/Unix compatibility)
- UTF-8 with/without BOM support
- Shows warnings for missing project files
Performance: 15-17x faster than dotnet nuget for CLI operations
package main import ( "context" "fmt" "log" "github.com/willibrandon/gonuget/core" "github.com/willibrandon/gonuget/http" ) func main() { // Create HTTP client httpClient := http.NewClient(nil) // Create repository manager repoManager := core.NewRepositoryManager() // Add NuGet.org as source repo := core.NewSourceRepository(core.RepositoryConfig{ Name: "nuget.org", SourceURL: "https://api.nuget.org/v3/index.json", HTTPClient: httpClient, }) repoManager.AddRepository(repo) // Create client client := core.NewClient(core.ClientConfig{ RepositoryManager: repoManager, }) // Search for packages ctx := context.Background() results, err := client.SearchPackages(ctx, "newtonsoft", core.SearchOptions{ Take: 10, IncludePrerelease: false, }) if err != nil { log.Fatal(err) } for repoName, pkgs := range results { fmt.Printf("Repository: %s\n", repoName) for _, pkg := range pkgs { fmt.Printf(" %s %s\n", pkg.ID, pkg.Version) } } }
import "github.com/willibrandon/gonuget/version" v1, _ := version.Parse("1.2.3-beta.1") v2, _ := version.Parse("1.2.3") if v1.Compare(v2) < 0 { fmt.Println("v1 is less than v2") } // Version range evaluation vr, _ := version.ParseVersionRange("[1.0.0,2.0.0)") if vr.IsSatisfiedBy(v2) { fmt.Println("v2 satisfies range") }
import ( "github.com/willibrandon/gonuget/core/resolver" "github.com/willibrandon/gonuget/protocol/v3" ) // Create metadata client httpClient := http.NewClient(nil) serviceIndexClient := v3.NewServiceIndexClient(httpClient) metadataClient := v3.NewMetadataClient(httpClient, serviceIndexClient) // Create resolver res := resolver.NewResolver( metadataClient, []string{"https://api.nuget.org/v3/index.json"}, "net8.0", ) // Resolve dependencies result, err := res.Resolve(ctx, "Newtonsoft.Json", "[13.0.1]") if err != nil { log.Fatal(err) } fmt.Printf("Resolved %d packages\n", len(result.Packages)) for _, pkg := range result.Packages { fmt.Printf(" %s %s\n", pkg.ID, pkg.Version) }
import "github.com/willibrandon/gonuget/packaging" reader, err := packaging.OpenPackageReader("package.nupkg") if err != nil { log.Fatal(err) } defer reader.Close() nuspec := reader.GetNuspec() fmt.Printf("Package: %s %s\n", nuspec.Metadata.ID, nuspec.Metadata.Version) // List files files := reader.GetFiles() for _, f := range files { fmt.Printf(" %s (%d bytes)\n", f, reader.GetEntry(f).UncompressedSize64) }
import ( "github.com/willibrandon/gonuget/packaging" "github.com/willibrandon/gonuget/version" ) builder := packaging.NewPackageBuilder() ver, _ := version.Parse("1.0.0") builder. SetID("MyPackage"). SetVersion(ver). SetDescription("My package description"). SetAuthors("Author Name"). AddFile("lib/net8.0/MyLibrary.dll", "MyLibrary.dll") if err := builder.Save("MyPackage.1.0.0.nupkg"); err != nil { log.Fatal(err) }
import "github.com/willibrandon/gonuget/frameworks" net80, _ := frameworks.Parse("net8.0") net70, _ := frameworks.Parse("net7.0") netstandard20, _ := frameworks.Parse("netstandard2.0") // Check compatibility if net80.IsCompatibleWith(netstandard20) { fmt.Println("net8.0 apps can use netstandard2.0 packages") } // Get portable framework name fmt.Println(net80.GetShortFolderName()) // "net8.0"
import ( "github.com/willibrandon/gonuget/cache" "time" ) // Create memory cache memCache := cache.NewMemoryCache(100 * 1024 * 1024) // 100MB // Create disk cache diskCache, _ := cache.NewDiskCache("/tmp/nuget-cache", 1*1024*1024*1024) // 1GB // Create multi-tier cache mtCache := cache.NewMultiTierCache(memCache, diskCache) // Use with client repo := core.NewSourceRepository(core.RepositoryConfig{ Name: "nuget.org", SourceURL: "https://api.nuget.org/v3/index.json", HTTPClient: httpClient, Cache: mtCache, })
# All tests make test # Go unit tests only (skip integration) make test-go-unit # Interop tests (validate parity with NuGet.Client) make test-interop # Specific package go test ./version go test ./core/resolver
The project includes C# interop tests that validate exact behavioral parity with NuGet.Client by running identical operations in both implementations and comparing results.
# Run CLI tests go test ./cmd/gonuget/... -v # Run CLI benchmarks go test -tags=benchmark -bench=. ./cmd/gonuget
- Version comparison: <20ns/op (zero allocations)
- Framework compatibility checks: optimized for hot path operations
- HTTP/2 connection pooling and multiplexing
- HTTP/3 support for reduced latency
- Multi-tier caching with efficient TTL and ETag validation
- 15-17x faster than dotnet nuget for common operations
- 30-35% less memory per command invocation
- Startup time: ~6-7ms (vs ~100-120ms for dotnet nuget)
- Zero runtime overhead (native binary)
See cmd/gonuget/benchmarks/README.md for detailed benchmarks.
- Go 1.25.2 or later
- For interop tests: .NET 9.0 SDK
# Build make build # Format code make fmt # Run linter make lint # Clean build artifacts make clean
MIT License - See LICENSE file