Migrate NugetService to the official NuGet client libraries#629
Merged
Conversation
Replace the hand-rolled NuGet v3 HTTP/JSON/XML API calls in NugetService with the official NuGet client libraries (NuGet.Protocol / Packaging / Configuration / Versioning / Credentials) so that winapp init/restore/update honor the user's nuget.config hierarchy - custom and private package sources, credentials, and globalPackagesFolder - when downloading the Windows SDK packages. AOT-safe via the NuGet.UseSystemTextJsonDeserialization feature switch (Trim=true), which trims out the reflection-based Newtonsoft.Json path entirely. Verified clean Release Native AOT publish for both win-x64 and win-arm64 (0 warnings under TreatWarningsAsErrors, Newtonsoft.Json fully trimmed). NuGet.* is pinned to 7.9.0-rc from the public dnceng dotnet-tools feed (temporary, until a 7.8+ stable with the STJ deserializer ships to nuget.org). The dev nuget.config uses packageSourceMapping to pin NuGet.* to dotnet-tools and everything else to nuget.org (dependency-confusion mitigation); release-nuget.config stays single-source on pde-oss_Internal, which has dotnet-tools configured as an upstream. Also removes dead nuspec-parsing unit tests and updates docs/usage.md and the setup skill. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Migrates SDK package restoration to official NuGet client libraries so standard configuration, feeds, credentials, and package caches are supported.
Changes:
- Replaces manual NuGet HTTP/XML handling with NuGet client APIs.
- Adds AOT-compatible NuGet dependencies and feed configuration.
- Documents custom/private feed support.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
src/winapp-CLI/WinApp.Cli/WinApp.Cli.csproj |
Adds NuGet libraries and AOT switch. |
src/winapp-CLI/WinApp.Cli/Services/NugetService.cs |
Implements configured-source restoration. |
src/winapp-CLI/WinApp.Cli.Tests/NugetServiceTests.cs |
Updates dependency tests. |
src/winapp-CLI/Directory.Packages.props |
Pins NuGet library versions. |
nuget.config |
Adds mapped development feeds. |
docs/usage.md |
Documents private feeds. |
docs/fragments/skills/winapp-cli/setup.md |
Updates setup guidance. |
.pipelines/release-nuget.config |
Documents release feed upstream. |
.github/plugin/skills/winapp-cli/setup/SKILL.md |
Regenerates plugin guidance. |
.claude/skills/winapp-setup/SKILL.md |
Synchronizes Claude guidance. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…spose in NugetService Resolves the Copilot reviewer findings on the NuGet.Client migration: - Honor <packageSourceMapping>: add GetRepositoriesForPackage() so download, version listing and dependency resolution only query sources mapped to the package (prevents pulling SDK packages from an unmapped/unintended feed). - Fail over correctly: acquire the source resource inside the per-source try/catch so a FatalProtocolException loading an unreachable/unauthorized service index tries the next source instead of aborting. - Do not swallow cancellation: re-throw OperationCanceledException in GetListedVersionsAsync before the general catch. - Dispose the DownloadResourceResult returned by AddPackageAsync. - Normalize the global-packages path in GetNuGetPackageDir via VersionFolderPathResolver so the on-disk 'already installed' check matches what AddPackageAsync writes (e.g. '1.0' -> '1.0.0'). - Preserve the underlying source error: DownloadPackageAsync now throws a detailed InvalidOperationException (with inner exception and source name) instead of returning bool, so 401/403/network failures are distinguishable from a missing package. - Add isolated nuget.config tests (temp local-folder feeds, no network) covering packageSourceMapping selection, wildcard fallback, unmapped->empty, and globalPackagesFolder resolution. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- ResolveDependenciesAsync: re-throw OperationCanceledException on cancellation and surface dependency read/install failures as visible warnings instead of hiding them behind verbose-only logging (no more silent incomplete installs). - ReadDependenciesFromNuspec: apply the same IgnoredDependencyPrefixes filter as FetchDirectDependenciesAsync so framework/runtime refs aren't install-attempted. - CompareVersions: use NuGet SemVer 2.0 ordering (prerelease-aware) with a numeric fallback, fixing non-deterministic latest-version selection for preview/experimental. - GetLatestVersionAsync: clearer 'no matching versions' message distinguishing a channel filter from an empty/auth/source failure. - nuget.config: <clear /> inherited packageSourceMapping so only repo pins apply. - Tests: correct CompareVersions prerelease assertions; add GetNuGetPackageDir normalization and GetLatestVersionAsync cancellation tests. - Docs: security/trust note on honoring the working-dir nuget.config. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
Build Metrics ReportBinary Sizes
Test Results✅ 1734 passed, 1 skipped out of 1735 tests in 625.8s (+72 tests, +214.6s vs. baseline) Test Coverage❌ 49.3% line coverage, 54.5% branch coverage · ✅ +1.2% vs. baseline CLI Startup Time44ms median (x64, Updated 2026-07-15 20:31:17 UTC · commit |
- NugetService: add NormalizeVersion helper; store canonical version in the installed dict (InstallPackageRecursiveAsync) and normalize in PackageInstallationService so downstream cache-path builders (CppWinrtService, PackageLayoutService, MsixService) locate a shorthand pin like '1.0' under its on-disk '1.0.0' folder. - FetchDirectDependenciesAsync: track the last per-source protocol error and throw when no source produced dependency metadata, instead of silently returning an empty graph (which callers treat as 'no dependencies' and skip installing missing transitive packages). Empty is returned only on clean absence. - GetListedVersionsAsync: inline the underlying source error + source name in the thrown message so 401/403/network detail is visible (top-level handlers print only ex.Message). - docs/dotnet-run-support.md: mark the CLI AOT blocker resolved via the System.Text.Json feature switch (Newtonsoft trimmed out; 0-warning AOT publish). - Tests: add NormalizeVersion canonical-form coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…g, preserve download errors, add real install tests - FetchDirectDependenciesAsync: throw when packageSourceMapping leaves no eligible source (was silently returning empty deps -> caller reported success while transitive packages went uninstalled) - DownloadPackageAsync: capture per-source diagnostics via CollectingLogger so a 401/403 on the .nupkg content endpoint (CopyNupkgToStreamAsync returns false, does not throw) is preserved as the real error instead of misreported as 'not found' - NugetServiceTests: add real local-feed install tests exercising download/extract/nuspec/recursive-deps + actionable-error path - docs/usage.md + setup.md skill fragment: clarify packageSourceMapping restricts per-package source eligibility (regenerated .github/plugin + .claude skills) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…clarify empty-source errors - EnsureCredentialService: replace Interlocked.Exchange in-progress flag with Lazy<bool> (ExecutionAndPublication) so setup runs once AND every caller blocks until it completes; a concurrent NuGet op can no longer build HTTP resources against a half-initialized credential service and hit a private feed anonymously - Add DescribeNoEligibleSources helper: distinguish 'no enabled sources configured' (point at <packageSources>) from 'packageSourceMapping excludes this package' (point at <packageSourceMapping>); use it in both DownloadPackageAsync and FetchDirectDependenciesAsync empty-repos errors so users are sent to the right nuget.config section Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…d' from 'no mapping' DescribeNoEligibleSources now separates the two packageSourceMapping causes of an empty eligible-source set: (1) the package matches no <packageSourceMapping> pattern (tell the user to add a mapping) vs (2) the package IS mapped but to a source that isn't enabled/configured, e.g. a disabled or misspelled source name (tell the user to enable/fix that source in <packageSources>). Added two GetPackageDependenciesAsync tests covering both branches. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ngrades - GetListedVersionsAsync now throws when any eligible source cannot be queried, instead of silently returning a partial result. Because 'latest' is a MAX across sources, a partial result could make callers pick an older version (update could downgrade a pinned package). - Empty-eligible-sources in the version path now emits the same actionable DescribeNoEligibleSources guidance (missing mapping / disabled mapped source / no sources) as the download and dependency paths. - UpdateCommand only advances to a strictly-greater version (compared by value, not string inequality), preventing spurious '1.0'->'1.0.0' updates and any downgrade. - Refreshed INugetService XML docs to reflect nuget.config-based source resolution, packageSourceMapping, normalized versions, and fail-on-error. - Added tests for the strictly-greater update guard and the version-path missing-mapping message. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…sabled sources Threads O/P: the GetListedVersionsAsync summary and inline comment claimed unlisted versions are always excluded from "latest". That guarantee only holds for registration/metadata-backed sources; a flat-container-only feed (PackageBaseAddress with no registration) exposes no listed/unlisted flag, so the filter cannot be applied there. Reworded both comments to state the caveat and cross-reference GetSourceVersionsAsync / the INugetService remarks. Thread Q: the repo nuget.config cleared inherited <config>, <packageSources>, and <packageSourceMapping> but not <disabledPackageSources>, so a user/machine-level config could disable nuget.org or dotnet-tools by name and make restore non-deterministic across machines. Added <disabledPackageSources><clear /></...>. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…flict detection
Thread R (NugetService.cs): RangesHaveCommonVersion and the pre-check
VersionRange.Satisfies both ignored a floating range's implied ceiling. A float
(1.*) declares no explicit upper bound on the underlying VersionRange -- and
VersionRange.Satisfies("1.*", 2.0.0) even returns true -- so a diamond pinning a
shared dependency to disjoint floats (A -> C 1.*, B -> C 2.*) was treated as the
documented first-selected limitation and silently succeeded with a version that
violates one branch. Now derive the floated band (1.* => [1.0.0, 2.0.0), 1.2.* =>
[1.2.0, 1.3.0), 1.2.3.* => [1.2.3, 1.2.4)) via GetEffectiveBounds/
TryGetFloatUpperBound, use it in the interval intersection, and add
RangeSatisfiesWithFloat so the "already satisfied" short-circuit also honors the
ceiling (covers the order where the 2.* branch installs first). * / *-* still
float every component (no ceiling), and differing-lower-bound diamonds ([1.0,) +
[2.0,)) remain satisfiable, so no false conflict is introduced. Added
NugetServiceVersionRangeTests exercising both helpers with genuine floating
ranges (a PackageBuilder-authored nuspec normalizes the float away, so this is
unit-tested directly rather than through a feed).
Thread S (skill fragment): docs/usage.md already qualifies that flat-container-
only feeds can select an unlisted version, but the shipped setup skill still said
init/update always pick the highest listed version. Added the same caveat to
docs/fragments/skills/winapp-cli/setup.md and its generated GitHub/Claude mirrors
so agents aren't given a stronger guarantee than the implementation provides.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…d float resolution; harden nuspec/test/doc gaps Follow-up to the previous float-aware conflict fix, addressing six review findings: - FindBestMatch treats a float as a preference, not a hard constraint: for a dependency declared 1.* whose only available version is 2.0.0 it returns 2.0.0, installing an out-of-band transitive package. Re-check the selected version with the float-aware predicate in ResolveDependencyVersionAsync so an out-of-band match is rejected and surfaced as unresolved instead of silently installed. - RangeSatisfiesWithFloat now defers to FloatRange.Satisfies instead of a hand-rolled numeric ceiling. The numeric ceiling only approximated the band and ignored prerelease eligibility, so a stable 1.* wrongly accepted 1.5.0-preview. - RangesHaveCommonVersion no longer reduces floating constraints to numeric bounds (which lost prerelease/prefix semantics and reported disjoint 1.2.3-beta.* / 1.2.3-rc.* as satisfiable). It now tests each range's inclusive minimum against every range with the full float-aware predicate. Deletes the now-unused GetEffectiveBounds/TryGetFloatUpperBound helpers (net simpler). - An unreadable .nuspec is now recorded in dependencyFailures rather than swallowed. A package that declares no dependencies reads back as an empty set without throwing, so a throw means the dependency graph is genuinely unknown and must fail the install loudly (matching the resolution/install error paths) instead of reporting success with a possibly-incomplete graph. - The test host now sets NuGet.UseSystemTextJsonDeserialization=true so the NuGet integration tests exercise the same System.Text.Json/AOT deserialization path the published CLI uses, not the default Newtonsoft.Json reflection path. - Documented that Package Source Mapping governs where a package is downloaded from, not one already in the global packages folder (NuGet and winapp's cache check reuse a completed package regardless of origin feed), so mapping-for-trust needs a clean or repository-scoped globalPackagesFolder. Added regression rows for disjoint prerelease-prefix floats and the stable-float- excludes-prerelease case. NuGet-area tests 131/131 pass (skipped:0), 0 warnings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…exclusive bounds Addresses three review findings in the NuGet client migration: - PackageInstallationService: the completed-cache shortcut resolved transitive dependencies via the feed-based GetPackageDependenciesAsync, so a cached package that is absent/unmapped on the currently configured feed would fail restore (breaking documented cache reuse under a private nuget.config) or install a graph diverging from what is on disk. Delegate cache hits to NugetService.InstallPackageAsync, whose completion-marker path already resolves dependencies from the package's extracted local .nuspec. - ReadDependenciesFromNuspec: a missing root .nuspec in an already-accepted (marker-present) package directory now throws instead of returning an empty set. A valid extracted package always has a .nuspec, so its absence is cache corruption; returning empty let install report success while silently omitting required transitive packages. The caller records it as a dependency failure so the operation fails loudly. - RangesHaveCommonVersion: replace the minimum-version-only candidate test with an actual numeric-interval intersection (inclusivity-aware, folding a float's next-prefix ceiling into the upper bound). The old approach falsely reported a conflict for genuinely overlapping ranges with an exclusive lower bound ([1.0.0,3.0.0) and (2.0.0,4.0.0) share 2.1.0). Prerelease-prefix floats, which have no numeric ceiling, are confirmed with a concrete witness via RangeSatisfiesWithFloat. Adds exclusive-bound regression rows. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
nmetulev
approved these changes
Jul 15, 2026
azchohfi
added a commit
that referenced
this pull request
Jul 15, 2026
…gration) Resolution: - NugetService.cs: take main's #629 rewrite (official NuGet client libs); the branch's edits to the old hand-rolled NuGet API are obsolete. - BuildToolsService.cs: auto-merged cleanly - keeps #629's NormalizeVersion shorthand-pin fix inside this branch's FindPackagePath refactor. - Removed NugetServiceOfflineTests.cs (tested the removed Http-seam NuGet API). - Extracted the reusable StubWinappDirectoryService helper (previously defined in NugetServiceOfflineTests.cs) into its own file so MSStoreCLIServiceOfflineTests keeps compiling. - Kept all cert/sign/registration/lifecycle/DevMode/FirstRun/Status/BuildTools coverage. Debug + Release build 0W/0E; 109 affected tests pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
azchohfi
added a commit
that referenced
this pull request
Jul 15, 2026
Reconcile PR7 (UI-command logic coverage) with two changes that landed on main while it was in review: - #629 (NuGet migration): auto-merged. HostBuilderExtensions.cs keeps both #629's NugetSourceProvider/NugetPackageDownloader registrations and my IOwnedWindowFinder/IPollDelay/ISystemUiQuery seam registrations. BaseCommandTests.cs: #629 and this branch independently added the same 3-arg ParseAndInvokeWithCaptureAsync overload; collapsed to one (kept #629's 2-arg delegator + my doc-comment) to avoid a duplicate method. - #603 (--allow-system-keys on ui send-keys): reconciled both product and tests so the feature AND my IPollDelay/ISystemUiQuery seam both survive. UiSendKeysCommand.cs: kept #603's AllowSystemKeysOption, warnings list, never-bypassable/soft-combo guard rewrite and JSON Warnings, plus my ISystemUiQuery systemQuery ctor param and IsXamlClassName seam read. UiCommandTests.SendKeys.cs: manual conflict resolution keeps the exact union of both sides' test methods (41 total, no duplicates). Debug 0W/0E; Release both projects 0W/0E. UiCommandTests + UiSessionServiceTests + SystemKeyGuard: 310 passed / 0 failed / 0 skipped. All 32 UI-scope files remain >=95% (UiSendKeysCommand 232/232=100%). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
azchohfi
added a commit
that referenced
this pull request
Jul 15, 2026
Resolves the add/add conflict on WinApp.Cli.Tests/UpdateCommandTests.cs introduced when PR #629 (migrate NugetService to the official NuGet client libraries) merged to main and added its own UpdateCommandTests. Resolution keeps BOTH test sets in one class: - All 9 of this branch's control-flow tests (config handling, build-tools gating, runtime-install step, preview mode, unexpected-exception path) are kept. - All 5 of #629's version-decision-gate tests (LatestIsHigher/NormalizedEqual/ Lower, LatestVersionLookupFails => exit non-zero, CancelledDuringLookup) are added, adapted onto this branch's configurable fakes. - Dropped only this branch's Update_NuGetFailsForPackage_KeepsCurrentVersion AndSkipsInstall: #629 changed UpdateCommand so a lookup failure now exits non-zero and preserves the pin, which directly contradicts that test. It is superseded by #629's Update_LatestVersionLookupFails_ExitsNonZeroAndPreservesPin. - Added a computed InstalledPackages convenience to FakePackageInstallation Service so #629's ported assertions read naturally. UpdateCommandTests: 14/14 green (Debug). No product-code changes in the merge. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
azchohfi
added a commit
that referenced
this pull request
Jul 15, 2026
…t migration) Resolve conflicts from the merged #629 NuGet-client rewrite: - FakeNugetService.cs: union of both sides — keep #641's InstallReturns hook + add #629's CancelOnQuery/CancelOnQueryPackage + IsPackageInstalled (dir + .nupkg.metadata completion marker) + MarkInstalled helper. Removed the now-unused Dependencies/ThrowKeyNotFoundFor hooks (only the removed transitive-orchestration tests used them); GetPackageDependenciesAsync returns empty like main. - PackageInstallationServiceTests.cs: #629 re-architected InstallPackagesAsync to delegate the transitive graph to INugetService.InstallPackageAsync (returning the full installed set) and merge by max-version, instead of orchestrating deps via GetPackageDependenciesAsync. Dropped the 7 obsolete tests that asserted the removed orchestration; kept the version-resolution + merge tests; added a compare-not-greater merge test. MarkPresent now writes the completion marker so the new IsPackageInstalled gate recognizes simulated-present packages. Product PackageInstallationService.cs stays 100% covered. - PackageInstallationServiceCacheMarkerTests.cs: new file holding #629's real-NugetService cache-marker integration test (BaseCommandTests-derived; can't share #641's standalone fixture). - MsixServiceIdentityTests.cs: removed #641's inline FakeWorkspaceSetupService (main now ships a shared FakeWorkspaceSetupService.cs); repointed refs to the shared members (InstallResult->InstallRuntimeResult, InstallCalls->InstallRuntimeCalls). Debug build 0W/0E; affected classes (PackageInstallationService|UpdateCommand|MsixServiceIdentity) 71/71 pass; PackageInstallationService.cs 100% line coverage. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
azchohfi
added a commit
that referenced
this pull request
Jul 15, 2026
Resolve conflicts from main's advance (#629 NuGet-client migration and merged coverage PRs) in the workspace/setup service test suite: - FakePackageInstallationService: fold the native-path error-injection knobs (InstallResult / ReturnNull / LastRequestedPackages) into main's new shared fake and delete the duplicate class from FakeNativeSetupServices.cs, resolving the CS0101 collision. The added knobs default to main's prior echo behavior, so existing consumers (UpdateCommandTests) are unaffected. - FakeBuildToolsService (shared): additively add BuildToolsResult and forceLatest recording used by the pinned-BuildTools native-path test. - FakeDevModeService: keep the folded configurable fake and repoint main's two .Enabled references in MsixServiceIdentityTests to .IsEnabledResult (identical default behavior). - FakeDotNetService / MsixServiceBundleOrchestrationTests: take main's supersets. The dead-code deletions from d462b88 (HasFileWithExtension, duplicate no-config guard) are preserved. Debug and Release (both projects individually) build 0W/0E; affected test classes pass; scope files hold >=95% (WorkspaceSetupService 95.1%, DotNetService 95.5%, WinmdService 95.7%, WinmdsLockfileService 98.1%, CppWinrt/Gitignore/SlugGenerator 100%) with ProjectDetectionService 93.2% and DirectoryPackagesService 92.7% documented unreachable-defensive/dead-code ceilings. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
azchohfi
added a commit
that referenced
this pull request
Jul 15, 2026
…647) Adds unit coverage for the NuGet client migration (#629) so every rewritten file clears the 95% bar: NugetService 96.02%, NugetService.Dependencies 95.44%, NugetPackageDownloader 95.18%, NugetSourceProvider 100%. New tests (NugetServiceCoverageTests, NugetPackageDownloaderCoverageTests, NugetServiceTests, NugetServiceVersionRangeTests) exercise source/config resolution, version selection, download/failover, dependency resolution, and diagnostic error paths against local folder feeds and in-process feeds (no network). One behavior-preserving product seam (NugetPackageDownloader.DeleteTempFile, defaults to File.Delete) makes the best-effort temp-cleanup swallow path testable. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
azchohfi
added a commit
that referenced
this pull request
Jul 16, 2026
…629)" This reverts the NuGet-client migration (#629) to unblock ADO main builds, which fail on the prerelease NuGet.* 7.9.0-rc packages sourced from the dnceng dotnet-tools feed (not nuget.org). See #653. Conflict resolution for coverage PRs that landed on top of #629: - FakeNugetService.cs restored to its pre-#629 form. - BaseCommandTests.cs: reverted #629 changes but kept the generic ParseAndInvokeWithCaptureAsync(..., CancellationToken) overload, which is unrelated test infra used by UI-command cancellation tests (#645). - Deleted PackageInstallationServiceTests.cs and UpdateCommandTests.cs (new in #629; later expanded by #640/#641 for the migrated code). - Deleted PackageInstallationServiceCacheMarkerTests.cs (added by #641; exercised the migrated NugetService via now-removed NugetFeedTestHelpers and INugetService.IsPackageInstalled). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: d55408ba-4e7d-41aa-95a0-f7c801632eaf
azchohfi
added a commit
that referenced
this pull request
Jul 16, 2026
…#654) Reverts the NuGet-client migration (#629) and its stacked coverage PR (#647). Tracks #653. ## Why After migrating `NugetService` to the official NuGet client libraries, `main` builds fail on ADO due to a security issue: #629 pins the `NuGet.*` packages to the prerelease **`7.9.0-rc.36120`** sourced from the dnceng **dotnet-tools** public feed (not nuget.org). The security gate flags consuming a prerelease from a secondary public feed. This was always intended as temporary — the `NuGet.UseSystemTextJsonDeserialization` switch needed to keep the Native AOT publish clean isn't in a nuget.org stable yet. ## What this reverts - **#647** first (reverse-merge order, since it's stacked on #629): removes the added coverage tests and the `NugetPackageDownloader.cs` test seams. - **#629** next: restores the hand-rolled `NugetService`, and removes the `NuGet.*` RC pins, the `dotnet-tools` source + `packageSourceMapping` from `nuget.config`, the `.pipelines/release-nuget.config` upstream, and the `Directory.Packages.props` pins. ## Conflict resolution notes Several coverage PRs merged after #629 built on top of its surface, so a plain revert left conflicts. Resolved as follows: - `FakeNugetService.cs` — restored to its pre-#629 form. - `BaseCommandTests.cs` — reverted #629's NuGet-specific changes but **kept** the generic `ParseAndInvokeWithCaptureAsync(..., CancellationToken)` overload, which is unrelated test infrastructure used by UI-command cancellation tests from #645. - Deleted `PackageInstallationServiceTests.cs` and `UpdateCommandTests.cs` (both new in #629; later expanded by #640/#641 for the migrated code, which no longer exists). - Deleted `PackageInstallationServiceCacheMarkerTests.cs` (added by #641; exercised the migrated `NugetService` against a local folder feed via `NugetFeedTestHelpers` + `INugetService.IsPackageInstalled`, all of which are removed here). ## Validation - `dotnet build winapp.sln -c Debug` — **0 warnings / 0 errors** - Test project builds clean; UI-command and NuGet-service test suites pass (447 passed / 0 failed / 1 skipped). - No remaining references to `dotnet-tools`, `7.9.0`, or `packageSourceMapping` in `nuget.config` / `Directory.Packages.props` / `.pipelines/release-nuget.config`. ## Re-landing This migration should be reapplied **only once stable `NuGet.Protocol 7.9.0` and all related `NuGet.*` packages are published on nuget.org** (not on dnceng dotnet-tools or any other secondary public feed). At that point: repoint the pins to the nuget.org stable, and drop the `dotnet-tools` source, `packageSourceMapping`, and the release-nuget.config upstream. See #653. --------- Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This was referenced Jul 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
NugetServicepreviously talked to the NuGet v3 API by hand — building HTTP requests, parsing the service index / registration JSON, and reading.nuspecXML itself — with the package source hardcoded toapi.nuget.org. That meantwinapp init/restore/updateignored the user'snuget.config, so custom/private package sources, feed credentials, and a customglobalPackagesFolderwere never honored.This PR replaces that hand-rolled code with the official NuGet client libraries (
NuGet.Protocol/NuGet.Packaging/NuGet.Configuration/NuGet.Versioning/NuGet.Credentials). Package sources, authentication, and the global packages folder are now resolved from the user's standardnuget.confighierarchy (rooted at the current working directory), so private and custom feeds/mirrors work when downloading the Windows SDK packages.The public
INugetServicesurface, the staticParseMinimumVersion/CompareVersionshelpers, andSDK_PACKAGESare all preserved, so callers are unchanged. No breaking changes.Native AOT
The CLI is Native AOT + full-trim +
TreatWarningsAsErrors. To keep it AOT-clean, the NuGet libraries are consumed with theNuGet.UseSystemTextJsonDeserializationfeature switch (Trim=true), which lets the linker trim out the reflection-basedNewtonsoft.Jsonpath entirely. Verified: clean Release AOT publish for both win-x64 and win-arm64 (0 warnings,Newtonsoft.Jsonfully trimmed out of the published output).Usage Example
winapp restore(andinit/update) now pick up anuget.configlike this and restore the SDK packages from a private mirror:Credentials from
<packageSourceCredentials>, environment variables, and NuGet credential-provider plugins are used automatically; interactive prompts appear only on interactive terminals (CI stays non-interactive).Related Issue
Type of Change
Checklist
docs/fragments/skills/(if CLI commands/workflows changed)Additional Notes
Package feeds
NuGet.*is pinned to7.9.0-rc.36120from the public dnceng dotnet-tools feed. This is temporary: theNuGet.UseSystemTextJsonDeserializationswitch isn't in a nuget.org stable yet (latest stable there is7.6.0, which still rootsNewtonsoft.Jsonand fails the AOT gate — verified). Once a 7.8+ stable ships to nuget.org, bump the pins and drop the dotnet-tools feed.nuget.configtherefore has two sources (nuget.org+dotnet-tools) withpackageSourceMappingpinningNuGet.*→ dotnet-tools and everything else → nuget.org. Package Source Mapping is the recommended dependency-confusion mitigation, so the multi-source config is deterministic/safe..pipelines/release-nuget.configstays single-source onpde-oss_Internal, which has the dnceng dotnet-tools feed configured as an upstream, so the7.9.0-rcpackages resolve through it during CI/release.Behavioral notes
.nupkgs are streamed to a temp file (SDK packages are large) then added to the global packages folder; dependency metadata is read from the extracted.nuspecviaNuspecReader.SdkInstallModeversion filtering (stable / preview / experimental) and theIgnoredDependencyPrefixesfiltering are preserved.Testing
NugetServiceTestspass against live feeds; 16EndToEndTestspass; full unit suite green.