Skip to content

Migrate NugetService to the official NuGet client libraries#629

Merged
nmetulev merged 34 commits into
mainfrom
azchohfi-nuget-client-migration
Jul 15, 2026
Merged

Migrate NugetService to the official NuGet client libraries#629
nmetulev merged 34 commits into
mainfrom
azchohfi-nuget-client-migration

Conversation

@azchohfi

@azchohfi azchohfi commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

Description

NugetService previously talked to the NuGet v3 API by hand — building HTTP requests, parsing the service index / registration JSON, and reading .nuspec XML itself — with the package source hardcoded to api.nuget.org. That meant winapp init / restore / update ignored the user's nuget.config, so custom/private package sources, feed credentials, and a custom globalPackagesFolder were 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 standard nuget.config hierarchy (rooted at the current working directory), so private and custom feeds/mirrors work when downloading the Windows SDK packages.

The public INugetService surface, the static ParseMinimumVersion / CompareVersions helpers, and SDK_PACKAGES are 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 the NuGet.UseSystemTextJsonDeserialization feature switch (Trim=true), which lets the linker trim out the reflection-based Newtonsoft.Json path entirely. Verified: clean Release AOT publish for both win-x64 and win-arm64 (0 warnings, Newtonsoft.Json fully trimmed out of the published output).

Usage Example

winapp restore (and init / update) now pick up a nuget.config like this and restore the SDK packages from a private mirror:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <clear />
    <add key="contoso" value="https://pkgs.dev.azure.com/contoso/_packaging/winsdk-mirror/nuget/v3/index.json" />
  </packageSources>
</configuration>
winapp restore

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

  • ✨ New feature
  • ♻️ Refactoring
  • 🔧 Config/build

Checklist

  • Tested locally on Windows
  • docs/usage.md updated (if CLI commands changed)
  • Agent skill templates updated in docs/fragments/skills/ (if CLI commands/workflows changed)

Additional Notes

Package feeds

  • NuGet.* is pinned to 7.9.0-rc.36120 from the public dnceng dotnet-tools feed. This is temporary: the NuGet.UseSystemTextJsonDeserialization switch isn't in a nuget.org stable yet (latest stable there is 7.6.0, which still roots Newtonsoft.Json and fails the AOT gate — verified). Once a 7.8+ stable ships to nuget.org, bump the pins and drop the dotnet-tools feed.
  • The dev nuget.config therefore has two sources (nuget.org + dotnet-tools) with packageSourceMapping pinning NuGet.* → 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.config stays single-source on pde-oss_Internal, which has the dnceng dotnet-tools feed configured as an upstream, so the 7.9.0-rc packages resolve through it during CI/release.

Behavioral notes

  • Downloaded .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 .nuspec via NuspecReader. SdkInstallMode version filtering (stable / preview / experimental) and the IgnoredDependencyPrefixes filtering are preserved.
  • Removed the dead nuspec-XML-parsing unit tests (they covered a hand-rolled parser that no longer exists); the integration tests for dependency resolution and latest-version selection still pass.

Testing

  • 27 NugetServiceTests pass against live feeds; 16 EndToEndTests pass; full unit suite green.
  • Release Native AOT publish verified clean on win-x64 and win-arm64.

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>
Copilot AI review requested due to automatic review settings July 14, 2026 21:11
@github-actions github-actions Bot added the enhancement New feature or request label Jul 14, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
azchohfi and others added 3 commits July 14, 2026 14:28
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/WinApp.Cli.csproj
@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Build Metrics Report

Binary Sizes

Artifact Baseline Current Delta
CLI (ARM64) 31.90 MB 36.29 MB 📈 +4.38 MB (+13.74%)
CLI (x64) 32.23 MB 36.48 MB 📈 +4.25 MB (+13.19%)
MSIX (ARM64) 13.39 MB 15.18 MB 📈 +1.79 MB (+13.37%)
MSIX (x64) 14.23 MB 16.11 MB 📈 +1.87 MB (+13.17%)
NPM Package 27.93 MB 31.68 MB 📈 +3.75 MB (+13.42%)
NuGet Package 27.96 MB 31.72 MB 📈 +3.76 MB (+13.44%)

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 Time

44ms median (x64, winapp --version) · ✅ no change vs. baseline


Updated 2026-07-15 20:31:17 UTC · commit bfe0f81 · workflow run

azchohfi and others added 2 commits July 14, 2026 15:34
- 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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.

Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli.Tests/NugetServiceTests.cs Outdated
Comment thread docs/usage.md Outdated
Comment thread docs/fragments/skills/winapp-cli/setup.md Outdated
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated 3 comments.

Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs
Comment thread docs/fragments/skills/winapp-cli/setup.md Outdated
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 32 out of 32 changed files in this pull request and generated 6 comments.

Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.Dependencies.cs
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs
Comment thread src/winapp-CLI/WinApp.Cli/WinApp.Cli.csproj
Comment thread docs/usage.md Outdated
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 33 out of 33 changed files in this pull request and generated 3 comments.

Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.cs Outdated
Comment thread src/winapp-CLI/WinApp.Cli/Services/NugetService.Dependencies.cs
Comment thread src/winapp-CLI/WinApp.Cli/Services/PackageInstallationService.cs Outdated
azchohfi and others added 2 commits July 15, 2026 12:52
…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
nmetulev merged commit 5e408f9 into main Jul 15, 2026
22 checks passed
@nmetulev
nmetulev deleted the azchohfi-nuget-client-migration branch July 15, 2026 20:37
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants