diff --git a/Directory.Build.props b/Directory.Build.props
index 187d51109e3..d212ce253ae 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -32,7 +32,7 @@
true
true
- true
+ true
runtime
@@ -194,9 +194,9 @@
.prebuilt.xml
- $(PackageReportDir)poison-catalog.xml
- $(PackageReportDir)poisoned.txt
- $(PackageReportDir)poison-usage.xml
+ $(ArtifactsLogDir)poison-catalog.xml
+ $(ArtifactsLogDir)poisoned.txt
+ $(ArtifactsLogDir)poison-usage.xml
diff --git a/eng/init-poison.proj b/eng/init-poison.proj
index b04716e4680..5937223b510 100644
--- a/eng/init-poison.proj
+++ b/eng/init-poison.proj
@@ -15,7 +15,7 @@
Outputs="$(BaseIntermediateOutputPath)PoisonPrebuiltPackages.complete">
.source-built.xml
- $(PackageReportDir)poison-source-built-catalog.xml
+ $(ArtifactsLogDir)poison-source-built-catalog.xml
diff --git a/eng/pipelines/templates/jobs/vmr-build.yml b/eng/pipelines/templates/jobs/vmr-build.yml
index cdc7160e04a..729fdd5acb7 100644
--- a/eng/pipelines/templates/jobs/vmr-build.yml
+++ b/eng/pipelines/templates/jobs/vmr-build.yml
@@ -606,16 +606,6 @@ jobs:
continueOnError: true
condition: succeededOrFailed()
- - ${{ if eq(parameters.buildSourceOnly, 'True') }}:
- - task: CopyFiles@2
- displayName: Copy prebuilt-report to BuildLogs
- inputs:
- SourceFolder: '$(sourcesPath)'
- Contents: artifacts/prebuilt-report/**
- TargetFolder: '$(Build.ArtifactStagingDirectory)/BuildLogs'
- continueOnError: true
- condition: succeededOrFailed()
-
- ${{ if or(ne(variables['System.TeamProject'], 'internal'), eq(variables['Build.Reason'], 'PullRequest')) }}:
- publish: $(Build.ArtifactStagingDirectory)/BuildLogs
artifact: $(Agent.JobName)_BuildLogs_Attempt$(System.JobAttempt)
diff --git a/eng/pipelines/templates/stages/vmr-verticals.yml b/eng/pipelines/templates/stages/vmr-verticals.yml
index 00221eb954f..906d2172696 100644
--- a/eng/pipelines/templates/stages/vmr-verticals.yml
+++ b/eng/pipelines/templates/stages/vmr-verticals.yml
@@ -210,7 +210,7 @@ stages:
crossRootFs: '/crossrootfs/x64'
targetOS: browser
targetArchitecture: wasm
- extraProperties: /p:DotNetBuildRuntimeWasmEnableThreads=true
+ extraProperties: /p:WasmEnableThreads=true
- template: ../jobs/vmr-build.yml
parameters:
@@ -329,7 +329,7 @@ stages:
useMonoRuntime: true
targetOS: osx
targetArchitecture: x64
- extraProperties: /p:DotNetBuildMonoEnableLLVM=true /p:DotNetBuildMonoAOTEnableLLVM=true /p:DotNetBuildMonoBundleLLVMOptimizer=true
+ extraProperties: /p:MonoEnableLLVM=true /p:MonoAOTEnableLLVM=true /p:MonoBundleLLVMOptimizer=true
- template: ../jobs/vmr-build.yml
parameters:
@@ -368,7 +368,7 @@ stages:
useMonoRuntime: true
targetOS: linux
targetArchitecture: x64
- extraProperties: /p:DotNetBuildMonoEnableLLVM=true /p:DotNetBuildMonoAOTEnableLLVM=true /p:DotNetBuildMonoBundleLLVMOptimizer=true
+ extraProperties: /p:MonoEnableLLVM=true /p:MonoAOTEnableLLVM=true /p:MonoBundleLLVMOptimizer=true
- template: ../jobs/vmr-build.yml
parameters:
@@ -458,7 +458,7 @@ stages:
useMonoRuntime: true
targetOS: linux
targetArchitecture: arm64
- extraProperties: /p:DotNetBuildMonoEnableLLVM=true /p:DotNetBuildMonoAOTEnableLLVM=true /p:DotNetBuildMonoBundleLLVMOptimizer=true
+ extraProperties: /p:MonoEnableLLVM=true /p:MonoAOTEnableLLVM=true /p:MonoBundleLLVMOptimizer=true
- template: ../jobs/vmr-build.yml
parameters:
diff --git a/prereqs/git-info/cecil.props b/prereqs/git-info/cecil.props
index 09461395a29..fa78d329b2b 100644
--- a/prereqs/git-info/cecil.props
+++ b/prereqs/git-info/cecil.props
@@ -1,8 +1,8 @@
- b861ffe40adc95775897af7f77cdae5292ac8c35
- 20250429.1
- 0.11.5-alpha.25229.1
+ 8f5ef5bf825aed91bb15039b02f7426f165b5eb8
+ 20250501.1
+ 0.11.5-alpha.25251.1
\ No newline at end of file
diff --git a/prereqs/git-info/deployment-tools.props b/prereqs/git-info/deployment-tools.props
index af957d16bbc..40dbd8c76c4 100644
--- a/prereqs/git-info/deployment-tools.props
+++ b/prereqs/git-info/deployment-tools.props
@@ -1,8 +1,8 @@
- 0d838d1284cb95c963ac49987dfc2fa5d93a11e7
- 20250428.1
- 9.0.0-preview.1.25228.1
+ 150d38efe20041a832482eb69e0d61b5d795e167
+ 20250501.1
+ 9.0.0-preview.1.25251.1
\ No newline at end of file
diff --git a/prereqs/git-info/efcore.props b/prereqs/git-info/efcore.props
index eeac697c23a..289ac8ac001 100644
--- a/prereqs/git-info/efcore.props
+++ b/prereqs/git-info/efcore.props
@@ -1,8 +1,8 @@
- c481582e500624eb9b60bba55e196c2da356be9f
- 20250430.1
- 10.0.0-preview.5.25230.1
+ 4ff9270f21135dba86d678b8515ed2ba2e15089d
+ 20250501.4
+ 10.0.0-preview.5.25251.4
\ No newline at end of file
diff --git a/prereqs/git-info/nuget-client.props b/prereqs/git-info/nuget-client.props
index 4cc0db37ab2..322994843a9 100644
--- a/prereqs/git-info/nuget-client.props
+++ b/prereqs/git-info/nuget-client.props
@@ -1,8 +1,8 @@
- d535886475b1eeda1813eb1947af23bd046abb25
- 20250416.1
- 6.15.0-preview.1.7
+ 86c695c5bdd02ca0aa735eed42508b57f695baa7
+ 6.15.0.31
+ 6.15.0-preview.1.31
\ No newline at end of file
diff --git a/prereqs/git-info/runtime.props b/prereqs/git-info/runtime.props
index 0dc0f6da516..284dff843ae 100644
--- a/prereqs/git-info/runtime.props
+++ b/prereqs/git-info/runtime.props
@@ -1,8 +1,8 @@
- 6ee6d40609b14602c20e762fac07c0da544ea1b8
- 20250430.7
- 10.0.0-preview.5.25230.7
+ 27604b57bd2e9c9aa46d005db7c4e387d461b5b6
+ 20250501.7
+ 10.0.0-preview.5.25251.7
\ No newline at end of file
diff --git a/prereqs/git-info/sdk.props b/prereqs/git-info/sdk.props
index c90ed8e96aa..7c58cc4ca62 100644
--- a/prereqs/git-info/sdk.props
+++ b/prereqs/git-info/sdk.props
@@ -1,8 +1,8 @@
- 2e6a71352a5312f8b136f6530891f4d208c2a83b
- 20250424.4
- 10.0.100-preview.5.25224.4
+ 7601b9cae2f49701609df537fe81ae73425109ed
+ 20250501.4
+ 10.0.100-preview.5.25251.4
\ No newline at end of file
diff --git a/prereqs/git-info/source-build-reference-packages.props b/prereqs/git-info/source-build-reference-packages.props
index 5dbf9dfd8cb..01f85e6fe51 100644
--- a/prereqs/git-info/source-build-reference-packages.props
+++ b/prereqs/git-info/source-build-reference-packages.props
@@ -1,8 +1,8 @@
- 64b59155edd234b5352eead067a2b005410061c7
- 20250428.1
- 10.0.622801
+ d1ab3b44c05f314be6885d1933d7e48fffa0cfc2
+ 20250501.1
+
\ No newline at end of file
diff --git a/prereqs/git-info/sourcelink.props b/prereqs/git-info/sourcelink.props
index 1afadc6438c..667fe10d29e 100644
--- a/prereqs/git-info/sourcelink.props
+++ b/prereqs/git-info/sourcelink.props
@@ -1,8 +1,8 @@
- fe14310ca1da9c2d63c377fe590dcf5f6a2d013e
- 20250430.1
- 10.0.0-beta.25230.1
+ 25716402f74dfeb1f4137601d013e158628d5196
+ 20250501.2
+ 10.0.0-beta.25251.2
\ No newline at end of file
diff --git a/prereqs/git-info/templating.props b/prereqs/git-info/templating.props
index 1a5db18dacf..7b443e349f8 100644
--- a/prereqs/git-info/templating.props
+++ b/prereqs/git-info/templating.props
@@ -1,8 +1,8 @@
- 2bb998b46268d8c38222a709ecf9fe8140df8393
- 20250430.1
- 10.0.100-preview.5.25230.1
+ 03eef3b9429ba0975982ca7427274e5986ef328e
+ 20250501.2
+ 10.0.100-preview.5.25251.2
\ No newline at end of file
diff --git a/prereqs/git-info/windowsdesktop.props b/prereqs/git-info/windowsdesktop.props
index cd13f69e888..0f6ce4308a4 100644
--- a/prereqs/git-info/windowsdesktop.props
+++ b/prereqs/git-info/windowsdesktop.props
@@ -1,8 +1,8 @@
- 70420a1fc04cf9a1efd42570a2873fa5bdb18ccb
- 20250429.1
- 10.0.0-preview.5.25229.1
+ 2ebb7d189eccf908c7121a5c1e5e71da0085c4dc
+ 20250501.1
+ 10.0.0-preview.5.25251.1
\ No newline at end of file
diff --git a/prereqs/git-info/winforms.props b/prereqs/git-info/winforms.props
index 4c95910ff7b..af62b9f81d2 100644
--- a/prereqs/git-info/winforms.props
+++ b/prereqs/git-info/winforms.props
@@ -1,8 +1,8 @@
- a345d2c077aad9aad8346aef79938c26ca029857
- 20250429.4
- 10.0.0-preview.5.25229.4
+ 43ffa433a31ca3a6af20cefadf2b13ba2a7e537d
+ 20250501.4
+ 10.0.0-preview.5.25251.4
\ No newline at end of file
diff --git a/repo-projects/Directory.Build.props b/repo-projects/Directory.Build.props
index d9f792eca96..c17a695a8cc 100644
--- a/repo-projects/Directory.Build.props
+++ b/repo-projects/Directory.Build.props
@@ -19,6 +19,11 @@
https://github.com/dotnet/source-build/issues/4922 -->
+
+
+ $([System.DateTime]::Now.ToString("yyyyMMdd")).1
+
+
netstandard2.0
@@ -56,7 +61,6 @@
$([MSBuild]::ValueOrDefault('$(ARCADE_BOOTSTRAP_VERSION)', '$(ArcadeSdkVersion)'))
$([MSBuild]::NormalizeDirectory('$(ArtifactsLogDir)', '$(RepositoryName)'))
- $([MSBuild]::NormalizeDirectory('$(PackageReportDir)', '$(RepositoryName)'))
Windows_x64
diff --git a/repo-projects/Directory.Build.targets b/repo-projects/Directory.Build.targets
index 45eab3f2e44..39a1eb7e66e 100644
--- a/repo-projects/Directory.Build.targets
+++ b/repo-projects/Directory.Build.targets
@@ -303,12 +303,12 @@
-
+
-
+
-
@@ -701,23 +701,15 @@
+ Condition="'$(IsUtilityProject)' != 'true'">
-
-
-
+
-
-
-
diff --git a/repo-projects/runtime.proj b/repo-projects/runtime.proj
index 38f76647dc6..65057174f78 100644
--- a/repo-projects/runtime.proj
+++ b/repo-projects/runtime.proj
@@ -24,10 +24,10 @@
$(BuildArgs) /p:DotNetBuildAllRuntimePacks=true
- $(BuildArgs) /p:DotNetBuildRuntimeWasmEnableThreads=true
- $(BuildArgs) /p:DotNetBuildMonoEnableLLVM=$(DotNetBuildMonoEnableLLVM)
- $(BuildArgs) /p:DotNetBuildMonoAOTEnableLLVM=$(DotNetBuildMonoAOTEnableLLVM)
- $(BuildArgs) /p:DotNetBuildMonoBundleLLVMOptimizer=$(DotNetBuildMonoBundleLLVMOptimizer)
+ $(BuildArgs) /p:WasmEnableThreads=true
+ $(BuildArgs) /p:MonoEnableLLVM=$(MonoEnableLLVM)
+ $(BuildArgs) /p:MonoAOTEnableLLVM=$(MonoAOTEnableLLVM)
+ $(BuildArgs) /p:MonoBundleLLVMOptimizer=$(MonoBundleLLVMOptimizer)
$(BuildArgs) $(FlagParameterPrefix)pgoinstrument
$(BuildArgs) /p:UseSystemLibs=$(UseSystemLibs)
diff --git a/src/arcade/eng/common/core-templates/steps/source-build.yml b/src/arcade/eng/common/core-templates/steps/source-build.yml
index c6b9ef51ac6..9292c9b67a3 100644
--- a/src/arcade/eng/common/core-templates/steps/source-build.yml
+++ b/src/arcade/eng/common/core-templates/steps/source-build.yml
@@ -46,11 +46,6 @@ steps:
buildConfig='$(_BuildConfig)'
fi
- officialBuildArgs=
- if [ '${{ and(ne(variables['System.TeamProject'], 'public'), notin(variables['Build.Reason'], 'PullRequest')) }}' = 'True' ]; then
- officialBuildArgs='/p:DotNetPublishUsingPipelines=true /p:OfficialBuildId=$(BUILD.BUILDNUMBER)'
- fi
-
targetRidArgs=
if [ '${{ parameters.platform.targetRID }}' != '' ]; then
targetRidArgs='/p:TargetRid=${{ parameters.platform.targetRID }}'
@@ -66,16 +61,6 @@ steps:
baseOsArgs='/p:BaseOS=${{ parameters.platform.baseOS }}'
fi
- publishArgs=
- if [ '${{ parameters.platform.skipPublishValidation }}' != 'true' ]; then
- publishArgs='--publish'
- fi
-
- assetManifestFileName=SourceBuild_RidSpecific.xml
- if [ '${{ parameters.platform.name }}' != '' ]; then
- assetManifestFileName=SourceBuild_${{ parameters.platform.name }}.xml
- fi
-
portableBuildArgs=
if [ '${{ parameters.platform.portableBuild }}' != '' ]; then
portableBuildArgs='/p:PortableBuild=${{ parameters.platform.portableBuild }}'
@@ -83,9 +68,8 @@ steps:
${{ coalesce(parameters.platform.buildScript, './build.sh') }} --ci \
--configuration $buildConfig \
- --restore --build --pack $publishArgs -bl \
+ --restore --build --pack -bl \
${{ parameters.platform.buildArguments }} \
- $officialBuildArgs \
$internalRuntimeDownloadArgs \
$internalRestoreArgs \
$targetRidArgs \
@@ -94,7 +78,6 @@ steps:
$portableBuildArgs \
/p:DotNetBuildSourceOnly=true \
/p:DotNetBuildRepo=true \
- /p:AssetManifestFileName=$assetManifestFileName
displayName: Build
# Upload build logs for diagnosis.
diff --git a/src/cecil/eng/Version.Details.xml b/src/cecil/eng/Version.Details.xml
index 4fa76d855f8..42e6c55b41a 100644
--- a/src/cecil/eng/Version.Details.xml
+++ b/src/cecil/eng/Version.Details.xml
@@ -1,12 +1,12 @@
-
+
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
diff --git a/src/cecil/eng/common/core-templates/jobs/source-build.yml b/src/cecil/eng/common/core-templates/jobs/source-build.yml
index a10ccfbee6d..df24c948ba1 100644
--- a/src/cecil/eng/common/core-templates/jobs/source-build.yml
+++ b/src/cecil/eng/common/core-templates/jobs/source-build.yml
@@ -14,7 +14,7 @@ parameters:
# This is the default platform provided by Arcade, intended for use by a managed-only repo.
defaultManagedPlatform:
name: 'Managed'
- container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream9'
+ container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream-10-amd64'
# Defines the platforms on which to run build jobs. One job is created for each platform, and the
# object in this array is sent to the job template as 'platform'. If no platforms are specified,
diff --git a/src/cecil/eng/common/core-templates/steps/source-build.yml b/src/cecil/eng/common/core-templates/steps/source-build.yml
index 8c88ccd7b08..c6b9ef51ac6 100644
--- a/src/cecil/eng/common/core-templates/steps/source-build.yml
+++ b/src/cecil/eng/common/core-templates/steps/source-build.yml
@@ -121,14 +121,3 @@ steps:
continueOnError: true
condition: succeededOrFailed()
sbomEnabled: false # we don't need SBOM for logs
-
-# Manually inject component detection so that we can ignore the source build upstream cache, which contains
-# a nupkg cache of input packages (a local feed).
-# This path must match the upstream cache path in property 'CurrentRepoSourceBuiltNupkgCacheDir'
-# in src\Microsoft.DotNet.Arcade.Sdk\tools\SourceBuild\SourceBuildArcade.targets
-- template: /eng/common/core-templates/steps/component-governance.yml
- parameters:
- displayName: Component Detection (Exclude upstream cache)
- is1ESPipeline: ${{ parameters.is1ESPipeline }}
- componentGovernanceIgnoreDirectories: '$(Build.SourcesDirectory)/artifacts/sb/src/artifacts/obj/source-built-upstream-cache'
- disableComponentGovernance: ${{ eq(variables['System.TeamProject'], 'public') }}
diff --git a/src/cecil/global.json b/src/cecil/global.json
index 88bdc536c7f..62e0ff9c5e0 100644
--- a/src/cecil/global.json
+++ b/src/cecil/global.json
@@ -4,6 +4,6 @@
},
"msbuild-sdks": {
"Microsoft.Build.NoTargets": "3.7.0",
- "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25229.109"
+ "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25251.102"
}
}
diff --git a/src/deployment-tools/eng/Version.Details.xml b/src/deployment-tools/eng/Version.Details.xml
index eb2d795aca7..fa7e8568896 100644
--- a/src/deployment-tools/eng/Version.Details.xml
+++ b/src/deployment-tools/eng/Version.Details.xml
@@ -1,11 +1,11 @@
-
+
-
+
https://github.com/dotnet/dotnet
- efbfb137a317fb339a6b7be36af7188cc508dc95
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
diff --git a/src/deployment-tools/eng/common/core-templates/jobs/source-build.yml b/src/deployment-tools/eng/common/core-templates/jobs/source-build.yml
index a10ccfbee6d..df24c948ba1 100644
--- a/src/deployment-tools/eng/common/core-templates/jobs/source-build.yml
+++ b/src/deployment-tools/eng/common/core-templates/jobs/source-build.yml
@@ -14,7 +14,7 @@ parameters:
# This is the default platform provided by Arcade, intended for use by a managed-only repo.
defaultManagedPlatform:
name: 'Managed'
- container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream9'
+ container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream-10-amd64'
# Defines the platforms on which to run build jobs. One job is created for each platform, and the
# object in this array is sent to the job template as 'platform'. If no platforms are specified,
diff --git a/src/deployment-tools/eng/common/core-templates/steps/source-build.yml b/src/deployment-tools/eng/common/core-templates/steps/source-build.yml
index 8c88ccd7b08..c6b9ef51ac6 100644
--- a/src/deployment-tools/eng/common/core-templates/steps/source-build.yml
+++ b/src/deployment-tools/eng/common/core-templates/steps/source-build.yml
@@ -121,14 +121,3 @@ steps:
continueOnError: true
condition: succeededOrFailed()
sbomEnabled: false # we don't need SBOM for logs
-
-# Manually inject component detection so that we can ignore the source build upstream cache, which contains
-# a nupkg cache of input packages (a local feed).
-# This path must match the upstream cache path in property 'CurrentRepoSourceBuiltNupkgCacheDir'
-# in src\Microsoft.DotNet.Arcade.Sdk\tools\SourceBuild\SourceBuildArcade.targets
-- template: /eng/common/core-templates/steps/component-governance.yml
- parameters:
- displayName: Component Detection (Exclude upstream cache)
- is1ESPipeline: ${{ parameters.is1ESPipeline }}
- componentGovernanceIgnoreDirectories: '$(Build.SourcesDirectory)/artifacts/sb/src/artifacts/obj/source-built-upstream-cache'
- disableComponentGovernance: ${{ eq(variables['System.TeamProject'], 'public') }}
diff --git a/src/deployment-tools/global.json b/src/deployment-tools/global.json
index 5982d5d3906..4ae23dd54d1 100644
--- a/src/deployment-tools/global.json
+++ b/src/deployment-tools/global.json
@@ -8,7 +8,7 @@
"dotnet": "10.0.100-preview.3.25201.16"
},
"msbuild-sdks": {
- "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25228.104",
+ "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25251.102",
"Microsoft.Build.NoTargets": "3.7.0",
"Microsoft.Build.Traversal": "3.4.0"
}
diff --git a/src/efcore/Directory.Packages.props b/src/efcore/Directory.Packages.props
index a367f152413..ff78d09a621 100644
--- a/src/efcore/Directory.Packages.props
+++ b/src/efcore/Directory.Packages.props
@@ -40,7 +40,7 @@
-
+
diff --git a/src/efcore/eng/Version.Details.xml b/src/efcore/eng/Version.Details.xml
index 860be0492c1..e65d9e17619 100644
--- a/src/efcore/eng/Version.Details.xml
+++ b/src/efcore/eng/Version.Details.xml
@@ -1,84 +1,84 @@
-
+
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
-
+
https://github.com/dotnet/dotnet
- 78c5fa9a48d469a19ab5a61c16c955c1f370b5be
+ dab45dead83a6cf3e5fa9df1396c51e6a6b61c07
diff --git a/src/efcore/eng/Versions.props b/src/efcore/eng/Versions.props
index 6dc3e330955..3fdb61b5ed5 100644
--- a/src/efcore/eng/Versions.props
+++ b/src/efcore/eng/Versions.props
@@ -16,24 +16,24 @@
False
- 10.0.0-preview.5.25229.109
- 10.0.0-preview.5.25229.109
- 10.0.0-preview.5.25229.109
- 10.0.0-preview.5.25229.109
- 10.0.0-preview.5.25229.109
- 10.0.0-preview.5.25229.109
- 10.0.0-preview.5.25229.109
- 10.0.0-preview.5.25229.109
- 10.0.0-preview.5.25229.109
- 10.0.0-preview.5.25229.109
- 10.0.0-preview.5.25229.109
- 10.0.0-preview.5.25229.109
- 10.0.0-preview.5.25229.109
- 10.0.0-preview.5.25229.109
- 10.0.0-preview.5.25229.109
+ 10.0.0-preview.5.25251.102
+ 10.0.0-preview.5.25251.102
+ 10.0.0-preview.5.25251.102
+ 10.0.0-preview.5.25251.102
+ 10.0.0-preview.5.25251.102
+ 10.0.0-preview.5.25251.102
+ 10.0.0-preview.5.25251.102
+ 10.0.0-preview.5.25251.102
+ 10.0.0-preview.5.25251.102
+ 10.0.0-preview.5.25251.102
+ 10.0.0-preview.5.25251.102
+ 10.0.0-preview.5.25251.102
+ 10.0.0-preview.5.25251.102
+ 10.0.0-preview.5.25251.102
+ 10.0.0-preview.5.25251.102
- 10.0.0-beta.25229.109
+ 10.0.0-beta.25251.102
17.13.9
@@ -42,7 +42,7 @@
4.13.0
1.1.3-beta1.24423.1
1.1.3-beta1.24352.1
- 1.13.1
+ 1.13.2
1.3.2
1.8.1
2.1.10
diff --git a/src/efcore/eng/common/core-templates/jobs/source-build.yml b/src/efcore/eng/common/core-templates/jobs/source-build.yml
index a10ccfbee6d..df24c948ba1 100644
--- a/src/efcore/eng/common/core-templates/jobs/source-build.yml
+++ b/src/efcore/eng/common/core-templates/jobs/source-build.yml
@@ -14,7 +14,7 @@ parameters:
# This is the default platform provided by Arcade, intended for use by a managed-only repo.
defaultManagedPlatform:
name: 'Managed'
- container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream9'
+ container: 'mcr.microsoft.com/dotnet-buildtools/prereqs:centos-stream-10-amd64'
# Defines the platforms on which to run build jobs. One job is created for each platform, and the
# object in this array is sent to the job template as 'platform'. If no platforms are specified,
diff --git a/src/efcore/eng/common/core-templates/steps/source-build.yml b/src/efcore/eng/common/core-templates/steps/source-build.yml
index 8c88ccd7b08..c6b9ef51ac6 100644
--- a/src/efcore/eng/common/core-templates/steps/source-build.yml
+++ b/src/efcore/eng/common/core-templates/steps/source-build.yml
@@ -121,14 +121,3 @@ steps:
continueOnError: true
condition: succeededOrFailed()
sbomEnabled: false # we don't need SBOM for logs
-
-# Manually inject component detection so that we can ignore the source build upstream cache, which contains
-# a nupkg cache of input packages (a local feed).
-# This path must match the upstream cache path in property 'CurrentRepoSourceBuiltNupkgCacheDir'
-# in src\Microsoft.DotNet.Arcade.Sdk\tools\SourceBuild\SourceBuildArcade.targets
-- template: /eng/common/core-templates/steps/component-governance.yml
- parameters:
- displayName: Component Detection (Exclude upstream cache)
- is1ESPipeline: ${{ parameters.is1ESPipeline }}
- componentGovernanceIgnoreDirectories: '$(Build.SourcesDirectory)/artifacts/sb/src/artifacts/obj/source-built-upstream-cache'
- disableComponentGovernance: ${{ eq(variables['System.TeamProject'], 'public') }}
diff --git a/src/efcore/global.json b/src/efcore/global.json
index 10c39de481f..7497505d190 100644
--- a/src/efcore/global.json
+++ b/src/efcore/global.json
@@ -13,7 +13,7 @@
}
},
"msbuild-sdks": {
- "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25229.109",
- "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.25229.109"
+ "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25251.102",
+ "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.25251.102"
}
}
diff --git a/src/nuget-client/.gdn/.gdnsuppress b/src/nuget-client/.gdn/.gdnsuppress
new file mode 100644
index 00000000000..3febf51caa7
--- /dev/null
+++ b/src/nuget-client/.gdn/.gdnsuppress
@@ -0,0 +1,35 @@
+{
+ "version": "latest",
+ "suppressionSets": {
+ "default": {
+ "name": "default",
+ "createdDate": "2025-04-14T13:30:47Z",
+ "lastUpdatedDate": "2025-04-14T13:30:47Z"
+ }
+ },
+ "results": {
+ "94b262a20d21544c9527803ee2109eec43f13edee79db6408106a7bf15ffaa0b": {
+ "signature": "94b262a20d21544c9527803ee2109eec43f13edee79db6408106a7bf15ffaa0b",
+ "target": "artifacts/NuGet.VisualStudio.Implementation/bin/release/net472/Microsoft.VisualStudio.ManagedInterfaces.dll",
+ "memberOf": ["default"],
+ "tool": "BinSkim",
+ "ruleId": "BA2009",
+ "justification": "This DLL is not shipped and is not built by our team.",
+ "createdDate": "2025-04-15 17:38:54Z",
+ "expirationDate": null,
+ "type": null
+ },
+ "dadf10d994d221e4d43842f89921d1eca4a2185ef8afa6c6894e5a8faf96afc6": {
+ "signature": "dadf10d994d221e4d43842f89921d1eca4a2185ef8afa6c6894e5a8faf96afc6",
+ "target": "artifacts/NuGet.VisualStudio.Implementation/bin/release/net472/Microsoft.VisualStudio.ManagedInterfaces.dll",
+ "memberOf": ["default"],
+ "tool": "BinSkim",
+ "ruleId": "BA2016",
+ "justification": "This DLL is not shipped and is not built by our team.",
+ "createdDate": "2025-04-15 17:38:54Z",
+ "expirationDate": null,
+ "type": null
+ }
+ }
+ }
+
\ No newline at end of file
diff --git a/src/nuget-client/docs/workflow.md b/src/nuget-client/docs/workflow.md
index f52c60bf147..0897656bbc1 100644
--- a/src/nuget-client/docs/workflow.md
+++ b/src/nuget-client/docs/workflow.md
@@ -29,6 +29,7 @@ These are guidelines to follow but unless indicated, they are not required.
- Try your best not to take feedback personally, we're all working towards the same goals.
- When conflict arises try to address it directly with the commentor offline, preferably synchronously.
- Provide an explanation if you decide not to implement a recommendation.
+ - Feel free to resolve conversations for which you've made the suggested code changes.
### Merging Pull Requests
diff --git a/src/nuget-client/eng/Publishing.props b/src/nuget-client/eng/Publishing.props
index 21cd8b66441..28c55419b1d 100644
--- a/src/nuget-client/eng/Publishing.props
+++ b/src/nuget-client/eng/Publishing.props
@@ -7,4 +7,10 @@
+
+
+
+
+
diff --git a/src/nuget-client/eng/dotnet-build/build.ps1 b/src/nuget-client/eng/dotnet-build/build.ps1
index b0278404450..ee845b44586 100644
--- a/src/nuget-client/eng/dotnet-build/build.ps1
+++ b/src/nuget-client/eng/dotnet-build/build.ps1
@@ -1,11 +1,12 @@
# The VMR orchestrator passes a number of stnadar
param (
- [string]$Configuration,
- [switch]$ci,
- [switch]$bl,
- [Parameter(ValueFromRemainingArguments = $true)]
- [string[]]$AdditionalArgs
+ [string][Alias('c')]$configuration = "Release",
+ [string][Alias('v')]$verbosity = "minimal",
+ [switch]$ci,
+ [switch]$bl,
+ [Parameter(ValueFromRemainingArguments = $true)]
+ [string[]]$AdditionalArgs
)
# This will exec a process using the console and return it's exit code.
@@ -55,6 +56,8 @@ $env:NUGET_PACKAGES=$nugetPackagesRoot
# MSBuild arguments
# Add the dotnet tool...
$dotnetArguments += $dotnetTool
+# Add the verbosity flag
+$dotnetArguments += "-v:$verbosity"
# Then project file...
$dotnetArguments += "$PSScriptRoot/dotnet-build.proj"
# Then remaining arguments.
diff --git a/src/nuget-client/eng/dotnet-build/build.sh b/src/nuget-client/eng/dotnet-build/build.sh
index 8bb636266cc..908ebd5b70a 100755
--- a/src/nuget-client/eng/dotnet-build/build.sh
+++ b/src/nuget-client/eng/dotnet-build/build.sh
@@ -6,6 +6,7 @@ git config --global protocol.file.allow always
source="${BASH_SOURCE[0]}"
scriptroot="$( cd -P "$( dirname "$source" )" && pwd )"
configuration='Release'
+verbosity='minimal'
source_build=false
properties=''
@@ -25,6 +26,10 @@ repo_root="${repo_root}/"
while [[ $# > 0 ]]; do
lowerI="$(echo $1 | awk '{print tolower($0)}')"
case $lowerI in
+ --verbosity|-v)
+ verbosity=$2
+ shift
+ ;;
--configuration|-c)
configuration=$2
shift
@@ -91,4 +96,4 @@ properties="$properties /p:Configuration=$configuration"
properties="$properties /p:DotNetBuildRepo=true"
properties="$properties /p:RepoRoot=$repo_root"
-"$DOTNET" msbuild "$scriptroot/dotnet-build.proj" "/bl:${repo_root}artifacts/sb/log/source-inner-build.binlog" $properties $args
+"$DOTNET" msbuild -v:$verbosity "$scriptroot/dotnet-build.proj" "/bl:${repo_root}artifacts/sb/log/source-inner-build.binlog" $properties $args
diff --git a/src/nuget-client/eng/pipelines/official.yml b/src/nuget-client/eng/pipelines/official.yml
index b23dc644672..932abcdea32 100644
--- a/src/nuget-client/eng/pipelines/official.yml
+++ b/src/nuget-client/eng/pipelines/official.yml
@@ -71,6 +71,8 @@ extends:
enabled: true
credscan:
enabled: false
+ suppression:
+ suppressionFile: $(Build.SourcesDirectory)\.gdn\.gdnsuppress
pool:
name: AzurePipelines-EO
image: 1ESPT-Windows2022
diff --git a/src/nuget-client/eng/pipelines/pull_request.yml b/src/nuget-client/eng/pipelines/pull_request.yml
index 9c58cc9b796..d7343171474 100644
--- a/src/nuget-client/eng/pipelines/pull_request.yml
+++ b/src/nuget-client/eng/pipelines/pull_request.yml
@@ -75,6 +75,8 @@ extends:
enabled: true
credscan:
enabled: false
+ suppression:
+ suppressionFile: $(Build.SourcesDirectory)\.gdn\.gdnsuppress
pool:
name: AzurePipelines-EO
image: 1ESPT-Windows2022
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/Commands/RestoreCommand.cs b/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/Commands/RestoreCommand.cs
index 60d0e59967d..0ae7b05d750 100644
--- a/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/Commands/RestoreCommand.cs
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/Commands/RestoreCommand.cs
@@ -834,8 +834,7 @@ private void GetInputsFromFile(string projectFilePath, PackageRestoreInputs pack
{
packageRestoreInputs.RestoreV3Context.Inputs.Add(projectFilePath);
}
- else if (projectFileName.EndsWith(".sln", StringComparison.OrdinalIgnoreCase)
- || projectFileName.EndsWith(".slnf", StringComparison.OrdinalIgnoreCase))
+ else if (projectFileName.IsSolutionFile())
{
ProcessSolutionFile(projectFilePath, packageRestoreInputs);
}
@@ -859,9 +858,7 @@ private void GetInputsFromDirectory(string directory, PackageRestoreInputs packa
var topLevelFiles = Directory.GetFiles(directory, "*.*", SearchOption.TopDirectoryOnly);
// Solution files
- var solutionFiles = topLevelFiles.Where(file =>
- file.EndsWith(".sln", StringComparison.OrdinalIgnoreCase))
- .ToArray();
+ var solutionFiles = topLevelFiles.Where(file => file.IsSolutionFile()).ToArray();
if (solutionFiles.Length > 0)
{
@@ -925,7 +922,7 @@ private static bool IsSolutionOrProjectFile(string fileName)
lastFourCharacters = extension.Substring(length - 4);
}
- return (string.Equals(extension, ".sln", StringComparison.OrdinalIgnoreCase)
+ return (fileName.IsSolutionFile()
|| string.Equals(lastFourCharacters, "proj", StringComparison.OrdinalIgnoreCase));
}
return false;
@@ -998,6 +995,16 @@ private string GetPackageReferenceFile(string projectFile)
private void ProcessSolutionFile(string solutionFileFullPath, PackageRestoreInputs restoreInputs)
{
+ var msBuildToolset = MsBuildDirectory.Value;
+ if (Path.GetExtension(solutionFileFullPath).Equals(".slnx", StringComparison.OrdinalIgnoreCase)
+ && msBuildToolset.ParsedVersion < new Version(17, 13))
+ {
+ throw new InvalidOperationException(string.Format(
+ CultureInfo.InvariantCulture,
+ LocalizedResourceManager.GetString(nameof(NuGetResources.Error_UnsupportedMsBuildForSlnx)),
+ msBuildToolset.Version));
+ }
+
restoreInputs.DirectoryOfSolutionFile = Path.GetDirectoryName(solutionFileFullPath);
restoreInputs.NameOfSolutionFile = Path.GetFileNameWithoutExtension(solutionFileFullPath);
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/Commands/UpdateCommand.cs b/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/Commands/UpdateCommand.cs
index 19e09ab5bbd..8c7b449f4a0 100644
--- a/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/Commands/UpdateCommand.cs
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/Commands/UpdateCommand.cs
@@ -238,7 +238,7 @@ private string GetInputFile()
return GetPackagesConfigPath(path);
}
- if (extension.Equals(".sln", StringComparison.OrdinalIgnoreCase))
+ if (path.IsSolutionFile())
{
return Path.GetFullPath(path);
}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/Common/ProjectHelper.cs b/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/Common/ProjectHelper.cs
index 6c661af6097..e0614f58740 100644
--- a/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/Common/ProjectHelper.cs
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/Common/ProjectHelper.cs
@@ -27,6 +27,10 @@ public static HashSet SupportedProjectExtensions
}
}
+ public static bool IsSolutionFile(this string filepath) => filepath.EndsWith(".sln", StringComparison.OrdinalIgnoreCase)
+ || filepath.EndsWith(".slnf", StringComparison.OrdinalIgnoreCase)
+ || filepath.EndsWith(".slnx", StringComparison.OrdinalIgnoreCase);
+
public static bool TryGetProjectFile(string directory, out string projectFile)
{
projectFile = null;
@@ -70,7 +74,7 @@ public static string GetSolutionDir(string projectDirectory)
private static bool SolutionFileExists(string path)
{
- return Directory.GetFiles(path, "*.sln").Any();
+ return Directory.EnumerateFiles(path, "*.*").Any(f => f.IsSolutionFile());
}
}
}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.Designer.cs b/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.Designer.cs
index 130818274b4..7429fed3318 100644
--- a/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.Designer.cs
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.Designer.cs
@@ -1,4 +1,4 @@
-//------------------------------------------------------------------------------
+//------------------------------------------------------------------------------
//
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
@@ -629,6 +629,15 @@ public static string Error_UnsupportedMsbuild {
}
}
+ ///
+ /// Looks up a localized string similar to This version of MSBuild not support the slnx format: '{0}'. Please use a MSBuild version greater or equal to 17.13 to use this feature..
+ ///
+ public static string Error_UnsupportedMsBuildForSlnx {
+ get {
+ return ResourceManager.GetString("Error_UnsupportedMsBuildForSlnx", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to This version of MSBuild not support Solution filter: '{0}'. Please use a MSBuild version greater or equal to 16.7 to use this feature..
///
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.resx b/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.resx
index f5370aea3ed..3a659370c8b 100644
--- a/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.resx
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.CommandLine/NuGetResources.resx
@@ -726,6 +726,9 @@ Do not localize `SDK`, `dotnet pack`, msbuild -t:pack` and 'true'.
This version of MSBuild not support Solution filter: '{0}'. Please use a MSBuild version greater or equal to 16.7 to use this feature.
+
+ This version of MSBuild not support the slnx format: '{0}'. Please use a MSBuild version greater or equal to 17.13 to use this feature.
+
Invalid culture identifier in {0} environment variable. Value read is '{1}'
0 - Environment variable name, 1 - value set in environment variable
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Design/PackageItemListViewSampleData.xaml b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Design/PackageItemListViewSampleData.xaml
deleted file mode 100644
index 7178fe65920..00000000000
--- a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Design/PackageItemListViewSampleData.xaml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailControlModel.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailControlModel.cs
index 35d03d52789..df121b73364 100644
--- a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailControlModel.cs
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/DetailControlModel.cs
@@ -9,6 +9,7 @@
using System.Threading.Tasks;
using System.Windows.Media.Imaging;
using Microsoft.ServiceHub.Framework;
+using NuGet.PackageManagement.UI.Models.Package;
using NuGet.PackageManagement.UI.ViewModels;
using NuGet.PackageManagement.VisualStudio;
using NuGet.Packaging.Core;
@@ -474,9 +475,9 @@ public string ExplainPackageDeprecationReasons(IReadOnlyCollection reaso
{
return Resources.Label_DeprecationReasons_Unknown;
}
- else if (reasons.Contains(PackageDeprecationReason.CriticalBugs, StringComparer.OrdinalIgnoreCase))
+ else if (reasons.Contains(PackageDeprecationReasonConstants.CriticalBugs, StringComparer.OrdinalIgnoreCase))
{
- if (reasons.Contains(PackageDeprecationReason.Legacy, StringComparer.OrdinalIgnoreCase))
+ if (reasons.Contains(PackageDeprecationReasonConstants.Legacy, StringComparer.OrdinalIgnoreCase))
{
return Resources.Label_DeprecationReasons_LegacyAndCriticalBugs;
}
@@ -485,7 +486,7 @@ public string ExplainPackageDeprecationReasons(IReadOnlyCollection reaso
return Resources.Label_DeprecationReasons_CriticalBugs;
}
}
- else if (reasons.Contains(PackageDeprecationReason.Legacy, StringComparer.OrdinalIgnoreCase))
+ else if (reasons.Contains(PackageDeprecationReasonConstants.Legacy, StringComparer.OrdinalIgnoreCase))
{
return Resources.Label_DeprecationReasons_Legacy;
}
@@ -495,12 +496,6 @@ public string ExplainPackageDeprecationReasons(IReadOnlyCollection reaso
}
}
- private static class PackageDeprecationReason
- {
- public const string CriticalBugs = nameof(CriticalBugs);
- public const string Legacy = nameof(Legacy);
- }
-
private DetailedPackageMetadata _packageMetadata;
public DetailedPackageMetadata PackageMetadata
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/DeprecationPackageMetadataCapability.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/DeprecationPackageMetadataCapability.cs
new file mode 100644
index 00000000000..10c86652023
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/DeprecationPackageMetadataCapability.cs
@@ -0,0 +1,77 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.VisualStudio.Internal.Contracts;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal class DeprecationPackageMetadataCapability : IDeprecationCapable
+ {
+ private readonly IPackageMetadataRetrievalAdapter _packageMetadataRetrievalAdapter;
+ private PackageDeprecationMetadataContextInfo? _deprecationMetadata;
+
+ public DeprecationPackageMetadataCapability(IPackageMetadataRetrievalAdapter packageMetadataRetrievalAdapter)
+ {
+ _packageMetadataRetrievalAdapter = packageMetadataRetrievalAdapter ?? throw new ArgumentNullException(nameof(packageMetadataRetrievalAdapter));
+ }
+
+ public AlternatePackageMetadataContextInfo? AlternatePackage => _deprecationMetadata?.AlternatePackage;
+
+ public bool IsDeprecated => _deprecationMetadata != null;
+
+ public PackageDeprecationReason PackageDeprecationReasons
+ {
+ get
+ {
+ if (_deprecationMetadata?.Reasons == null || _deprecationMetadata.Reasons.Count == 0)
+ {
+ return PackageDeprecationReason.Unknown;
+ }
+
+ bool hasCriticalBugs = false;
+ bool hasLegacy = false;
+
+ foreach (var reason in _deprecationMetadata.Reasons)
+ {
+ if (string.Equals(reason, PackageDeprecationReasonConstants.CriticalBugs, StringComparison.OrdinalIgnoreCase))
+ {
+ hasCriticalBugs = true;
+ }
+ else if (string.Equals(reason, PackageDeprecationReasonConstants.Legacy, StringComparison.OrdinalIgnoreCase))
+ {
+ hasLegacy = true;
+ }
+
+ if (hasCriticalBugs && hasLegacy)
+ {
+ return PackageDeprecationReason.LegacyAndCriticalBugs;
+ }
+ }
+
+ if (hasCriticalBugs)
+ {
+ return PackageDeprecationReason.CriticalBugs;
+ }
+
+ if (hasLegacy)
+ {
+ return PackageDeprecationReason.Legacy;
+ }
+
+ return PackageDeprecationReason.Unknown;
+ }
+ }
+
+ public PackageDeprecationMetadataContextInfo? DeprecationMetadata => _deprecationMetadata;
+
+ public async Task PopulateDataAsync(CancellationToken cancellationToken)
+ {
+ _deprecationMetadata = await _packageMetadataRetrievalAdapter.GetPackageDeprecationInfoAsync(cancellationToken);
+ }
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/DirectlyReferencedPackageModel.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/DirectlyReferencedPackageModel.cs
new file mode 100644
index 00000000000..9c8cf4ecb7e
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/DirectlyReferencedPackageModel.cs
@@ -0,0 +1,88 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.Packaging;
+using NuGet.Packaging.Core;
+using NuGet.Protocol;
+using NuGet.VisualStudio.Internal.Contracts;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal class DirectlyReferencedPackageModel : PackageModel, IDeprecationCapable, IVulnerableCapable
+ {
+ private readonly IDeprecationCapable _deprecationCapability;
+ private readonly IVulnerableCapable _vulnerableCapability;
+
+ public DirectlyReferencedPackageModel(
+ PackageIdentity identity,
+ string packagePath,
+ IVulnerableCapable vulnerableCapability,
+ IDeprecationCapable deprecationCapability,
+ IEmbeddedResourcesCapable embeddedResources,
+ string? title,
+ string? description,
+ string? authors,
+ Uri? projectUrl,
+ string[]? tags,
+ IReadOnlyList? ownersList,
+ IReadOnlyCollection? packageDependencyGroups,
+ string? summary,
+ DateTimeOffset? publishedDate,
+ LicenseMetadata? licenseMetadata,
+ Uri? licenseUrl,
+ bool requireLicenseAcceptance,
+ string? reportAbuseUrl,
+ Uri? iconUrl)
+ : base(identity,
+ embeddedResources,
+ title,
+ description,
+ authors,
+ projectUrl,
+ tags,
+ ownersList,
+ packageDependencyGroups,
+ summary,
+ publishedDate,
+ licenseMetadata,
+ licenseUrl,
+ requireLicenseAcceptance,
+ iconUrl)
+ {
+ ReportAbuseUrl = reportAbuseUrl;
+ _deprecationCapability = deprecationCapability ?? throw new ArgumentNullException(nameof(deprecationCapability));
+ _vulnerableCapability = vulnerableCapability ?? throw new ArgumentNullException(nameof(vulnerableCapability));
+ PackagePath = packagePath;
+ }
+
+ public string PackagePath { get; }
+
+ public string? ReportAbuseUrl { get; }
+
+ public bool IsDeprecated => _deprecationCapability.IsDeprecated;
+
+ public PackageDeprecationReason PackageDeprecationReasons => _deprecationCapability.PackageDeprecationReasons;
+
+ public AlternatePackageMetadataContextInfo? AlternatePackage => _deprecationCapability.AlternatePackage;
+
+ public IReadOnlyList? Vulnerabilities => _vulnerableCapability.Vulnerabilities;
+
+ public bool IsVulnerable => _vulnerableCapability.IsVulnerable;
+
+ public PackageVulnerabilitySeverity VulnerabilityMaxSeverity => _vulnerableCapability.VulnerabilityMaxSeverity;
+
+ public PackageDeprecationMetadataContextInfo? DeprecationMetadata => _deprecationCapability.DeprecationMetadata;
+
+ public override async Task PopulateDataAsync(CancellationToken cancellationToken)
+ {
+ await _vulnerableCapability.PopulateDataAsync(cancellationToken);
+ await _deprecationCapability.PopulateDataAsync(cancellationToken);
+ }
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/EmbeddedResourcesCapability.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/EmbeddedResourcesCapability.cs
new file mode 100644
index 00000000000..e17e0063940
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/EmbeddedResourcesCapability.cs
@@ -0,0 +1,48 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.Packaging.Core;
+using NuGet.VisualStudio.Internal.Contracts;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal class EmbeddedResourcesCapability : IEmbeddedResourcesCapable
+ {
+ private readonly INuGetPackageFileService _nugetPackageFileService;
+ private readonly PackageIdentity _packageIdentity;
+
+ public EmbeddedResourcesCapability(INuGetPackageFileService nugetPackageFileService, PackageIdentity packageIdentity, Uri? readmeUri)
+ {
+ _nugetPackageFileService = nugetPackageFileService ?? throw new ArgumentNullException(nameof(nugetPackageFileService));
+ _packageIdentity = packageIdentity ?? throw new ArgumentNullException(nameof(packageIdentity));
+ ReadmeUri = readmeUri;
+ }
+
+ public Uri? ReadmeUri { get; }
+
+ public ValueTask GetIconAsync(CancellationToken cancellationToken)
+ {
+ return _nugetPackageFileService.GetPackageIconAsync(_packageIdentity, cancellationToken);
+ }
+
+ public ValueTask GetLicenseAsync(CancellationToken cancellationToken)
+ {
+ return _nugetPackageFileService.GetEmbeddedLicenseAsync(_packageIdentity, cancellationToken);
+ }
+
+ public async ValueTask GetReadmeAsync(CancellationToken cancellationToken)
+ {
+ if (ReadmeUri != null)
+ {
+ return await _nugetPackageFileService.GetReadmeAsync(ReadmeUri, cancellationToken);
+ }
+ return null;
+ }
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IDeprecationCapable.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IDeprecationCapable.cs
new file mode 100644
index 00000000000..fb714dbbb64
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IDeprecationCapable.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.VisualStudio.Internal.Contracts;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal interface IDeprecationCapable
+ {
+ public bool IsDeprecated { get; }
+
+ public PackageDeprecationReason PackageDeprecationReasons { get; }
+
+ public AlternatePackageMetadataContextInfo? AlternatePackage { get; }
+
+ public Task PopulateDataAsync(CancellationToken cancellationToken);
+
+ public PackageDeprecationMetadataContextInfo? DeprecationMetadata { get; }
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IEmbeddedResourcesCapable.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IEmbeddedResourcesCapable.cs
new file mode 100644
index 00000000000..f2670d5c076
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IEmbeddedResourcesCapable.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System;
+using System.IO;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal interface IEmbeddedResourcesCapable
+ {
+ Uri? ReadmeUri { get; }
+
+ ValueTask GetIconAsync(CancellationToken cancellationToken);
+
+ ValueTask GetLicenseAsync(CancellationToken cancellationToken);
+
+ ValueTask GetReadmeAsync(CancellationToken cancellationToken);
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IPackageMetadataRetrievalAdapter.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IPackageMetadataRetrievalAdapter.cs
new file mode 100644
index 00000000000..0af87d8455e
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IPackageMetadataRetrievalAdapter.cs
@@ -0,0 +1,20 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.VisualStudio.Internal.Contracts;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal interface IPackageMetadataRetrievalAdapter
+ {
+ public Task GetPackageMetadataAsync(
+ CancellationToken cancellationToken);
+
+ public Task GetPackageDeprecationInfoAsync(
+ CancellationToken cancellationToken);
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IVulnerableCapable.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IVulnerableCapable.cs
new file mode 100644
index 00000000000..d31b1e99e8c
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IVulnerableCapable.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.Protocol;
+using NuGet.VisualStudio.Internal.Contracts;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal interface IVulnerableCapable
+ {
+ public IReadOnlyList? Vulnerabilities { get; }
+
+ public bool IsVulnerable { get; }
+
+ public PackageVulnerabilitySeverity VulnerabilityMaxSeverity { get; }
+
+ public Task PopulateDataAsync(CancellationToken cancellationToken);
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/LocalPackageModel.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/LocalPackageModel.cs
new file mode 100644
index 00000000000..7c3e20d9824
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/LocalPackageModel.cs
@@ -0,0 +1,46 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.Packaging;
+using NuGet.Packaging.Core;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal class LocalPackageModel : PackageModel
+ {
+ public LocalPackageModel(PackageIdentity identity,
+ string packagePath,
+ IEmbeddedResourcesCapable embeddedResources,
+ string? title,
+ string? description,
+ string? authors,
+ Uri? projectUrl,
+ string[]? tags,
+ IReadOnlyList? ownersList,
+ IReadOnlyCollection? packageDependencyGroups,
+ string? summary,
+ DateTimeOffset? publishedDate,
+ LicenseMetadata? licenseMetadata,
+ Uri? licenseUrl,
+ bool requireLicenseAcceptance,
+ Uri? iconUrl)
+ : base(identity, embeddedResources, title, description, authors, projectUrl, tags, ownersList, packageDependencyGroups, summary, publishedDate, licenseMetadata, licenseUrl, requireLicenseAcceptance, iconUrl)
+ {
+ PackagePath = packagePath;
+ }
+
+ public string PackagePath { get; }
+
+ public override Task PopulateDataAsync(CancellationToken cancellationToken)
+ {
+ // LocalPackageModel does not need to populate any additional data.
+ return Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/PackageDeprecationReason.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/PackageDeprecationReason.cs
new file mode 100644
index 00000000000..8baa8eb92fb
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/PackageDeprecationReason.cs
@@ -0,0 +1,21 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ public enum PackageDeprecationReason
+ {
+ Unknown = -1,
+ Legacy = 0,
+ CriticalBugs = 1,
+ LegacyAndCriticalBugs = 2
+ }
+
+ public static class PackageDeprecationReasonConstants
+ {
+ public const string CriticalBugs = nameof(CriticalBugs);
+ public const string Legacy = nameof(Legacy);
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/PackageMetadataRetrievalAdapter.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/PackageMetadataRetrievalAdapter.cs
new file mode 100644
index 00000000000..c0e50b7e561
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/PackageMetadataRetrievalAdapter.cs
@@ -0,0 +1,64 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.Packaging.Core;
+using NuGet.VisualStudio.Internal.Contracts;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal class PackageMetadataRetrievalAdapter : IPackageMetadataRetrievalAdapter
+ {
+ private readonly INuGetSearchService _nugetSearchService;
+ private readonly PackageIdentity _packageIdentity;
+ private readonly IReadOnlyCollection _packageSources;
+ private readonly bool _includePrerelease;
+ private Task<(PackageSearchMetadataContextInfo, PackageDeprecationMetadataContextInfo?)>? _packageMetadataTask;
+ private readonly object _lock = new();
+
+ public PackageMetadataRetrievalAdapter(INuGetSearchService nugetSearchService, PackageIdentity packageIdentity, IReadOnlyCollection packageSources, bool includePrerelease)
+ {
+ _nugetSearchService = nugetSearchService ?? throw new ArgumentNullException(nameof(nugetSearchService));
+ _packageIdentity = packageIdentity ?? throw new ArgumentNullException(nameof(packageIdentity));
+ _packageSources = packageSources ?? throw new ArgumentNullException(nameof(packageSources));
+ _includePrerelease = includePrerelease;
+ }
+
+ public async Task GetPackageMetadataAsync(
+ CancellationToken cancellationToken)
+ {
+ var packageMetadata = await FetchMetadataAsync(cancellationToken);
+ return packageMetadata.Item1;
+ }
+
+ public async Task GetPackageDeprecationInfoAsync(
+ CancellationToken cancellationToken)
+ {
+ var packageMetadata = await FetchMetadataAsync(cancellationToken);
+ return packageMetadata.Item2;
+ }
+
+ private Task<(PackageSearchMetadataContextInfo, PackageDeprecationMetadataContextInfo?)> FetchMetadataAsync(
+ CancellationToken cancellationToken)
+ {
+ if (_packageMetadataTask == null)
+ {
+ lock (_lock)
+ {
+ _packageMetadataTask ??= _nugetSearchService.GetPackageMetadataAsync(
+ _packageIdentity,
+ _packageSources,
+ _includePrerelease,
+ cancellationToken).AsTask();
+ }
+ }
+
+ return _packageMetadataTask;
+ }
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/PackageModel.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/PackageModel.cs
new file mode 100644
index 00000000000..e95fb77e829
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/PackageModel.cs
@@ -0,0 +1,101 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.Packaging;
+using NuGet.Packaging.Core;
+using NuGet.Versioning;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ public abstract class PackageModel : IEmbeddedResourcesCapable
+ {
+ private readonly IEmbeddedResourcesCapable _embeddedResources;
+
+ internal PackageModel(PackageIdentity identity,
+ IEmbeddedResourcesCapable embeddedResources,
+ string? title,
+ string? description,
+ string? authors,
+ Uri? projectUrl,
+ string[]? tags,
+ IReadOnlyList? ownersList,
+ IReadOnlyCollection? packageDependencyGroups,
+ string? summary,
+ DateTimeOffset? publishedDate,
+ LicenseMetadata? licenseMetadata,
+ Uri? licenseUrl,
+ bool requireLicenseAcceptance,
+ Uri? iconUrl)
+ {
+ _embeddedResources = embeddedResources ?? throw new ArgumentNullException(nameof(embeddedResources));
+ Identity = identity ?? throw new ArgumentNullException(nameof(identity));
+ Title = title;
+ Description = description;
+ Authors = authors;
+ ProjectUrl = projectUrl;
+ Tags = tags;
+ OwnersList = ownersList;
+ Summary = summary;
+ PublishedDate = publishedDate;
+ LicenseMetadata = licenseMetadata;
+ LicenseUrl = licenseUrl;
+ RequireLicenseAcceptance = requireLicenseAcceptance;
+ IconUrl = iconUrl;
+
+ if (packageDependencyGroups != null && packageDependencyGroups.Count > 0)
+ {
+ DependencySets = [.. packageDependencyGroups.Select(e => new PackageDependencySetMetadata(e))];
+ }
+ }
+
+ public PackageIdentity Identity { get; }
+
+ public string Id => Identity.Id;
+
+ public NuGetVersion Version => Identity.Version;
+
+ public string? Title { get; }
+
+ public string? Description { get; }
+
+ public string? Authors { get; }
+
+ public IReadOnlyList? OwnersList { get; }
+
+ public IReadOnlyCollection? DependencySets { get; }
+
+ public Uri? ProjectUrl { get; }
+
+ public string[]? Tags { get; }
+
+ public string? Summary { get; }
+
+ public LicenseMetadata? LicenseMetadata { get; }
+
+ public Uri? LicenseUrl { get; }
+
+ public bool RequireLicenseAcceptance { get; }
+
+ public DateTimeOffset? PublishedDate { get; }
+
+ public Uri? IconUrl { get; }
+
+ public Uri? ReadmeUri => _embeddedResources.ReadmeUri;
+
+ public abstract Task PopulateDataAsync(CancellationToken cancellationToken);
+
+ public ValueTask GetIconAsync(CancellationToken cancellationToken) => _embeddedResources.GetIconAsync(cancellationToken);
+
+ public ValueTask GetLicenseAsync(CancellationToken cancellationToken) => _embeddedResources.GetLicenseAsync(cancellationToken);
+
+ public ValueTask GetReadmeAsync(CancellationToken cancellationToken) => _embeddedResources.GetReadmeAsync(cancellationToken);
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/PackageModelFactory.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/PackageModelFactory.cs
new file mode 100644
index 00000000000..ac45aadf63c
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/PackageModelFactory.cs
@@ -0,0 +1,195 @@
+// Updated PackageModelFactory class
+using System;
+using System.Collections.Generic;
+using NuGet.PackageManagement.VisualStudio;
+using NuGet.VisualStudio.Internal.Contracts;
+using ContractItemFilter = NuGet.VisualStudio.Internal.Contracts.ItemFilter;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal class PackageModelFactory
+ {
+ private readonly INuGetSearchService _searchService;
+ private readonly INuGetPackageFileService _packageFileService;
+ private readonly IPackageVulnerabilityService _packageVulnerabilityService;
+ private readonly bool _includePrerelease;
+ private IReadOnlyCollection _packageSources;
+
+ public PackageModelFactory(INuGetSearchService searchService, INuGetPackageFileService packageFileService, IPackageVulnerabilityService packageVulnerabilityService, bool includePrerelease, IReadOnlyCollection packageSources)
+ {
+ _searchService = searchService ?? throw new ArgumentNullException(nameof(_searchService));
+ _packageFileService = packageFileService ?? throw new ArgumentNullException(nameof(_packageFileService));
+ _packageVulnerabilityService = packageVulnerabilityService ?? throw new ArgumentNullException(nameof(_packageVulnerabilityService));
+ _includePrerelease = includePrerelease;
+ _packageSources = packageSources ?? throw new ArgumentNullException(nameof(_packageSources));
+ }
+
+ public PackageModel Create(PackageSearchMetadataContextInfo metadata, ContractItemFilter itemFilter)
+ {
+ if (metadata == null)
+ {
+ throw new ArgumentNullException(nameof(metadata));
+ }
+
+ EmbeddedResourcesCapability embeddedResources = new EmbeddedResourcesCapability(_packageFileService, metadata.Identity!, metadata.ReadmeUrl);
+
+ if (metadata.TransitiveOrigins != null)
+ {
+ IVulnerableCapable vulnerableDatabaseCapability = new VulnerableDatabaseCapability(_packageVulnerabilityService, metadata.Identity!);
+ return CreateTransitivelyReferencedPackageModel(metadata, vulnerableDatabaseCapability, embeddedResources);
+ }
+ else
+ {
+ if (metadata.PackagePath != null)
+ {
+ PackageMetadataRetrievalAdapter packageMetadataRetrievalAdapter = new PackageMetadataRetrievalAdapter(_searchService, metadata.Identity!, _packageSources, _includePrerelease);
+ IVulnerableCapable vulnerableCapability = new VulnerablePackageMetadataCapability(packageMetadataRetrievalAdapter);
+
+ if (itemFilter.Equals(ContractItemFilter.All))
+ {
+ return CreateLocalPackageModel(metadata, vulnerableCapability, embeddedResources);
+ }
+
+ IDeprecationCapable deprecationCapable = new DeprecationPackageMetadataCapability(packageMetadataRetrievalAdapter);
+ return CreateDirectlyReferencedPackageModel(metadata, vulnerableCapability, deprecationCapable, embeddedResources);
+ }
+ else
+ {
+ PackageMetadataRetrievalAdapter packageMetadataRetrievalAdapter = new PackageMetadataRetrievalAdapter(_searchService, metadata.Identity!, _packageSources, _includePrerelease);
+ IDeprecationCapable deprecationCapable = new DeprecationPackageMetadataCapability(packageMetadataRetrievalAdapter);
+ VulnerablePackageMetadataCapability vulnerableCapability = new VulnerablePackageMetadataCapability(packageMetadataRetrievalAdapter);
+
+ if (metadata.IsRecommended)
+ {
+ return CreateRecommendedPackageModel(metadata, vulnerableCapability, deprecationCapable, embeddedResources);
+ }
+
+ return CreateRemotePackageModel(metadata, vulnerableCapability, deprecationCapable, embeddedResources);
+ }
+ }
+ }
+
+ private static LocalPackageModel CreateLocalPackageModel(PackageSearchMetadataContextInfo metadata, IVulnerableCapable vulnerableCapability, EmbeddedResourcesCapability embeddedResources)
+ {
+ return new LocalPackageModel(
+ metadata.Identity!,
+ metadata.PackagePath!,
+ embeddedResources,
+ metadata.Title,
+ metadata.Description,
+ metadata.Authors,
+ metadata.ProjectUrl,
+ metadata.Tags?.Split(','),
+ metadata.OwnersList,
+ metadata.DependencySets,
+ metadata.Summary,
+ metadata.Published,
+ metadata.LicenseMetadata,
+ metadata.LicenseUrl,
+ metadata.RequireLicenseAcceptance,
+ metadata.IconUrl);
+ }
+
+ private static DirectlyReferencedPackageModel CreateDirectlyReferencedPackageModel(PackageSearchMetadataContextInfo metadata, IVulnerableCapable vulnerableCapability, IDeprecationCapable deprecationCapable, EmbeddedResourcesCapability embeddedResources)
+ {
+ return new DirectlyReferencedPackageModel(
+ metadata.Identity!,
+ metadata.PackagePath!,
+ vulnerableCapability,
+ deprecationCapable,
+ embeddedResources,
+ metadata.Title,
+ metadata.Description,
+ metadata.Authors,
+ metadata.ProjectUrl,
+ metadata.Tags?.Split(','),
+ metadata.OwnersList,
+ metadata.DependencySets,
+ metadata.Summary,
+ metadata.Published,
+ metadata.LicenseMetadata,
+ metadata.LicenseUrl,
+ metadata.RequireLicenseAcceptance,
+ metadata.ReportAbuseUrl?.ToString(),
+ metadata.IconUrl);
+ }
+
+ private static TransitivelyReferencedPackageModel CreateTransitivelyReferencedPackageModel(PackageSearchMetadataContextInfo metadata, IVulnerableCapable vulnerableDatabaseCapability, EmbeddedResourcesCapability embeddedResources)
+ {
+ return new TransitivelyReferencedPackageModel(
+ metadata.Identity!,
+ vulnerableDatabaseCapability,
+ embeddedResources,
+ metadata.TransitiveOrigins!,
+ metadata.Title,
+ metadata.Description,
+ metadata.Authors,
+ metadata.ProjectUrl,
+ metadata.Tags?.Split(','),
+ metadata.OwnersList,
+ metadata.DependencySets,
+ metadata.Summary,
+ metadata.Published,
+ metadata.LicenseMetadata,
+ metadata.LicenseUrl,
+ metadata.RequireLicenseAcceptance,
+ metadata.ReportAbuseUrl?.ToString(),
+ metadata.IconUrl);
+ }
+
+ private static RecommendedPackageModel CreateRecommendedPackageModel(PackageSearchMetadataContextInfo metadata, IVulnerableCapable vulnerableCapability, IDeprecationCapable deprecationCapable, EmbeddedResourcesCapability embeddedResources)
+ {
+ var recommenderVersion = metadata.RecommenderVersion ?? throw new ArgumentNullException(nameof(metadata.RecommenderVersion));
+
+ return new RecommendedPackageModel(
+ metadata.Identity!,
+ vulnerableCapability,
+ deprecationCapable,
+ embeddedResources,
+ recommenderVersion,
+ metadata.Title,
+ metadata.Description,
+ metadata.Authors,
+ metadata.ProjectUrl,
+ metadata.Tags?.Split(','),
+ metadata.OwnersList,
+ metadata.DependencySets,
+ metadata.Summary,
+ metadata.Published,
+ metadata.LicenseMetadata,
+ metadata.LicenseUrl,
+ metadata.RequireLicenseAcceptance,
+ metadata.IsListed,
+ metadata.PackageDetailsUrl,
+ metadata.DownloadCount,
+ metadata.ReadmeUrl,
+ metadata.IconUrl);
+ }
+
+ private static RemotePackageModel CreateRemotePackageModel(PackageSearchMetadataContextInfo metadata, IVulnerableCapable vulnerableCapability, IDeprecationCapable deprecationCapable, EmbeddedResourcesCapability embeddedResources)
+ {
+ return new RemotePackageModel(
+ metadata.Identity!,
+ vulnerableCapability,
+ deprecationCapable,
+ embeddedResources,
+ metadata.Title,
+ metadata.Description,
+ metadata.Authors,
+ metadata.ProjectUrl,
+ metadata.Tags?.Split(','),
+ metadata.OwnersList,
+ metadata.DependencySets,
+ metadata.Summary,
+ metadata.Published,
+ metadata.LicenseMetadata,
+ metadata.LicenseUrl,
+ metadata.RequireLicenseAcceptance,
+ metadata.IsListed,
+ metadata.PackageDetailsUrl,
+ metadata.DownloadCount,
+ metadata.ReadmeUrl,
+ metadata.IconUrl);
+ }
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/RecommendedPackageModel.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/RecommendedPackageModel.cs
new file mode 100644
index 00000000000..36b6687a392
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/RecommendedPackageModel.cs
@@ -0,0 +1,46 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using NuGet.Packaging;
+using NuGet.Packaging.Core;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal class RecommendedPackageModel : RemotePackageModel
+ {
+ public RecommendedPackageModel(
+ PackageIdentity identity,
+ IVulnerableCapable vulnerableCapability,
+ IDeprecationCapable deprecationCapability,
+ IEmbeddedResourcesCapable embeddedResources,
+ (string modelVersion, string vsixVersion) recommenderVersion,
+ string? title,
+ string? description,
+ string? authors,
+ Uri? projectUrl,
+ string[]? tags,
+ IReadOnlyList? ownersList,
+ IReadOnlyCollection? packageDependencyGroups,
+ string? summary,
+ DateTimeOffset? publishedDate,
+ LicenseMetadata? licenseMetadata,
+ Uri? licenseUrl,
+ bool requireLicenseAcceptance,
+ bool isListed,
+ Uri? packageDetailsUrl,
+ long? downloadCount,
+ Uri? readmeUrl,
+ Uri? iconUrl)
+ : base(identity, vulnerableCapability, deprecationCapability, embeddedResources, title, description, authors, projectUrl, tags, ownersList, packageDependencyGroups, summary, publishedDate, licenseMetadata, licenseUrl, requireLicenseAcceptance, isListed, packageDetailsUrl, downloadCount, readmeUrl, iconUrl)
+ {
+ RecommenderVersion = recommenderVersion;
+ }
+
+ public (string modelVersion, string vsixVersion) RecommenderVersion { get; }
+ }
+}
+
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/RemotePackageModel.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/RemotePackageModel.cs
new file mode 100644
index 00000000000..bec87bb87f1
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/RemotePackageModel.cs
@@ -0,0 +1,79 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.Packaging;
+using NuGet.Packaging.Core;
+using NuGet.Protocol;
+using NuGet.VisualStudio.Internal.Contracts;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal class RemotePackageModel : PackageModel, IDeprecationCapable, IVulnerableCapable
+ {
+ private readonly IDeprecationCapable _deprecationCapability;
+ private readonly IVulnerableCapable _vulnerableCapability;
+
+ public RemotePackageModel(
+ PackageIdentity identity,
+ IVulnerableCapable vulnerableCapability,
+ IDeprecationCapable deprecationCapability,
+ IEmbeddedResourcesCapable embeddedResources,
+ string? title,
+ string? description,
+ string? authors,
+ Uri? projectUrl,
+ string[]? tags,
+ IReadOnlyList? ownersList,
+ IReadOnlyCollection? packageDependencyGroups,
+ string? summary,
+ DateTimeOffset? publishedDate,
+ LicenseMetadata? licenseMetadata,
+ Uri? licenseUrl,
+ bool requireLicenseAcceptance,
+ bool isListed,
+ Uri? packageDetailsUrl,
+ long? downloadCount,
+ Uri? readmeUrl,
+ Uri? iconUrl)
+ : base(identity, embeddedResources, title, description, authors, projectUrl, tags, ownersList, packageDependencyGroups, summary, publishedDate, licenseMetadata, licenseUrl, requireLicenseAcceptance, iconUrl)
+ {
+ IsListed = isListed;
+ PackageDetailsUrl = packageDetailsUrl;
+ DownloadCount = downloadCount;
+ _deprecationCapability = deprecationCapability ?? throw new ArgumentNullException(nameof(deprecationCapability));
+ _vulnerableCapability = vulnerableCapability ?? throw new ArgumentNullException(nameof(vulnerableCapability));
+ ReadmeUrl = readmeUrl;
+ }
+
+ public bool IsListed { get; }
+ public Uri? PackageDetailsUrl { get; }
+ public long? DownloadCount { get; }
+ public Uri? ReadmeUrl { get; }
+
+ public bool IsDeprecated => _deprecationCapability.IsDeprecated;
+
+ public PackageDeprecationReason PackageDeprecationReasons => _deprecationCapability.PackageDeprecationReasons;
+
+ public AlternatePackageMetadataContextInfo? AlternatePackage => _deprecationCapability.AlternatePackage;
+
+ public IReadOnlyList? Vulnerabilities => _vulnerableCapability.Vulnerabilities;
+
+ public bool IsVulnerable => _vulnerableCapability.IsVulnerable;
+
+ public PackageVulnerabilitySeverity VulnerabilityMaxSeverity => _vulnerableCapability.VulnerabilityMaxSeverity;
+
+ public PackageDeprecationMetadataContextInfo? DeprecationMetadata => _deprecationCapability.DeprecationMetadata;
+
+ public override async Task PopulateDataAsync(CancellationToken cancellationToken)
+ {
+ await _vulnerableCapability.PopulateDataAsync(cancellationToken);
+ await _deprecationCapability.PopulateDataAsync(cancellationToken);
+ }
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/TransitivelyReferencedPackageModel.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/TransitivelyReferencedPackageModel.cs
new file mode 100644
index 00000000000..9cbc507e663
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/TransitivelyReferencedPackageModel.cs
@@ -0,0 +1,73 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.Packaging;
+using NuGet.Packaging.Core;
+using NuGet.Protocol;
+using NuGet.VisualStudio.Internal.Contracts;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal class TransitivelyReferencedPackageModel : PackageModel, IVulnerableCapable
+ {
+ private readonly IVulnerableCapable _vulnerableCapability;
+
+ public TransitivelyReferencedPackageModel(
+ PackageIdentity identity,
+ IVulnerableCapable vulnerableCapability,
+ IEmbeddedResourcesCapable embeddedResources,
+ IReadOnlyCollection transitiveOrigins,
+ string? title,
+ string? description,
+ string? authors,
+ Uri? projectUrl,
+ string[]? tags,
+ IReadOnlyList? ownersList,
+ IReadOnlyCollection? packageDependencyGroups,
+ string? summary,
+ DateTimeOffset? publishedDate,
+ LicenseMetadata? licenseMetadata,
+ Uri? licenseUrl,
+ bool requireLicenseAcceptance,
+ string? reportAbuseUrl,
+ Uri? iconUrl)
+ : base(identity,
+ embeddedResources,
+ title,
+ description,
+ authors,
+ projectUrl,
+ tags,
+ ownersList,
+ packageDependencyGroups,
+ summary,
+ publishedDate,
+ licenseMetadata,
+ licenseUrl,
+ requireLicenseAcceptance,
+ iconUrl)
+ {
+ _vulnerableCapability = vulnerableCapability ?? throw new ArgumentNullException(nameof(vulnerableCapability));
+ TransitiveOrigins = transitiveOrigins;
+ }
+
+ public IReadOnlyCollection TransitiveOrigins { get; }
+
+ public IReadOnlyList? Vulnerabilities => _vulnerableCapability.Vulnerabilities;
+
+ public bool IsVulnerable => _vulnerableCapability.IsVulnerable;
+
+ public PackageVulnerabilitySeverity VulnerabilityMaxSeverity => _vulnerableCapability.VulnerabilityMaxSeverity;
+
+ public override async Task PopulateDataAsync(CancellationToken cancellationToken)
+ {
+ await _vulnerableCapability.PopulateDataAsync(cancellationToken);
+ }
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerableCapabilityBase.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerableCapabilityBase.cs
new file mode 100644
index 00000000000..432d467678e
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerableCapabilityBase.cs
@@ -0,0 +1,61 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.Protocol;
+using NuGet.VisualStudio.Internal.Contracts;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal abstract class VulnerableCapabilityBase : IVulnerableCapable
+ {
+ private IReadOnlyList? _vulnerabilities;
+
+ public IReadOnlyList? Vulnerabilities
+ {
+ get => _vulnerabilities;
+ protected set
+ {
+ List? sortedList = null;
+ if (value != null)
+ {
+ sortedList = [.. value];
+ // Sort the list in descending order.
+ sortedList.Sort((b, a) => a.Severity.CompareTo(b.Severity));
+ }
+ _vulnerabilities = sortedList;
+ }
+ }
+
+ public bool IsVulnerable => Vulnerabilities?.Count > 0;
+
+ public PackageVulnerabilitySeverity VulnerabilityMaxSeverity
+ {
+ get
+ {
+ if (!IsVulnerable || Vulnerabilities is null)
+ {
+ return PackageVulnerabilitySeverity.Unknown;
+ }
+
+ // Vulnerabilities are ordered on set so the first element is always the highest severity
+ int severity = Vulnerabilities[0].Severity;
+ if (Enum.IsDefined(typeof(PackageVulnerabilitySeverity), severity))
+ {
+ return (PackageVulnerabilitySeverity)severity;
+ }
+ else
+ {
+ return PackageVulnerabilitySeverity.Unknown;
+ }
+ }
+ }
+
+ public abstract Task PopulateDataAsync(CancellationToken cancellationToken);
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerableDatabaseCapability.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerableDatabaseCapability.cs
new file mode 100644
index 00000000000..d97bcc639fd
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerableDatabaseCapability.cs
@@ -0,0 +1,30 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using NuGet.PackageManagement.VisualStudio;
+using NuGet.Packaging.Core;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal class VulnerableDatabaseCapability : VulnerableCapabilityBase
+ {
+ private readonly IPackageVulnerabilityService _vulnerabilityService;
+ private readonly PackageIdentity _packageIdentity;
+
+ public VulnerableDatabaseCapability(IPackageVulnerabilityService vulnerabilityService, PackageIdentity packageIdentity)
+ {
+ _vulnerabilityService = vulnerabilityService ?? throw new ArgumentNullException(nameof(vulnerabilityService));
+ _packageIdentity = packageIdentity ?? throw new ArgumentNullException(nameof(packageIdentity));
+ }
+
+ public override async Task PopulateDataAsync(CancellationToken cancellationToken)
+ {
+ Vulnerabilities = await _vulnerabilityService.GetVulnerabilityInfoAsync(_packageIdentity, cancellationToken);
+ }
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerablePackageMetadataCapability.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerablePackageMetadataCapability.cs
new file mode 100644
index 00000000000..ea7e96c3f31
--- /dev/null
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerablePackageMetadataCapability.cs
@@ -0,0 +1,28 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#nullable enable
+
+using System;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace NuGet.PackageManagement.UI.Models.Package
+{
+ internal class VulnerablePackageMetadataCapability : VulnerableCapabilityBase
+ {
+ private readonly IPackageMetadataRetrievalAdapter _packageMetadataRetrievalAdapter;
+
+ public VulnerablePackageMetadataCapability(IPackageMetadataRetrievalAdapter packageMetadataRetrievalAdapter)
+ {
+ _packageMetadataRetrievalAdapter = packageMetadataRetrievalAdapter ?? throw new ArgumentNullException(nameof(packageMetadataRetrievalAdapter));
+ }
+
+ public async override Task PopulateDataAsync(CancellationToken cancellationToken)
+ {
+ var packageMetadata = await _packageMetadataRetrievalAdapter.GetPackageMetadataAsync(cancellationToken);
+ Vulnerabilities = packageMetadata.Vulnerabilities?.ToList() ?? [];
+ }
+ }
+}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/PackageItemLoader.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/PackageItemLoader.cs
index e33247df808..ed087ac03cd 100644
--- a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/PackageItemLoader.cs
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/PackageItemLoader.cs
@@ -4,7 +4,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
-using System.Globalization;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -13,6 +12,7 @@
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
using NuGet.Common;
+using NuGet.PackageManagement.UI.Models.Package;
using NuGet.PackageManagement.UI.ViewModels;
using NuGet.PackageManagement.VisualStudio;
using NuGet.Protocol.Core.Types;
@@ -42,6 +42,7 @@ internal class PackageItemLoader : IPackageItemLoader, IDisposable
public IItemLoaderState State => _state;
private IServiceBroker _serviceBroker;
private INuGetPackageFileService _packageFileService;
+ private PackageModelFactory _packageModelFactory;
public bool IsMultiSource => _packageSources.Count > 1;
@@ -274,7 +275,7 @@ public IEnumerable GetCurrent()
{
if (packageLevel == PackageLevel.Transitive)
{
- UpdateTransitiveInfo(existingListItem, metadataContextInfo);
+ existingListItem.UpdateTransitiveInfo(metadataContextInfo);
}
existingListItem.UpdateInstalledPackagesVulnerabilities(metadataContextInfo.Identity);
@@ -318,25 +319,16 @@ public IEnumerable GetCurrent()
knownOwnerViewModels = LoadKnownOwnerViewModels(metadataContextInfo);
}
- var listItem = new PackageItemViewModel(_searchService, _packageVulnerabilityService)
+ _packageModelFactory ??= new PackageModelFactory(_searchService, _packageFileService, _packageVulnerabilityService, _includePrerelease, _packageSources);
+ PackageModel packageModel = _packageModelFactory.Create(metadataContextInfo, _itemFilter);
+
+ var listItem = new PackageItemViewModel(_searchService, packageModel, _packageVulnerabilityService)
{
- Id = metadataContextInfo.Identity.Id,
- Version = metadataContextInfo.Identity.Version,
- IconUrl = metadataContextInfo.IconUrl,
- Owner = metadataContextInfo.Owners,
KnownOwnerViewModels = knownOwnerViewModels,
- Author = metadataContextInfo.Authors,
- DownloadCount = metadataContextInfo.DownloadCount,
- Summary = metadataContextInfo.Summary,
AllowedVersions = allowedVersions,
VersionOverride = versionOverride,
PrefixReserved = metadataContextInfo.PrefixReserved && !IsMultiSource,
- Recommended = metadataContextInfo.IsRecommended,
- RecommenderVersion = metadataContextInfo.RecommenderVersion,
- Vulnerabilities = metadataContextInfo.Vulnerabilities,
Sources = _packageSources,
- PackagePath = metadataContextInfo.PackagePath,
- PackageFileService = _packageFileService,
IncludePrerelease = _includePrerelease,
PackageLevel = packageLevel,
AutoReferenced = autoReferenced,
@@ -344,12 +336,14 @@ public IEnumerable GetCurrent()
if (listItem.PackageLevel == PackageLevel.TopLevel)
{
- listItem.UpdatePackageStatus(_installedPackages);
+ listItem.UpdatePackageStatusAsync(_installedPackages)
+ .PostOnFailure(nameof(PackageItemLoader), nameof(GetCurrent));
}
else
{
- UpdateTransitiveInfo(listItem, metadataContextInfo);
- listItem.UpdateTransitivePackageStatus(metadataContextInfo.Identity.Version);
+ listItem.UpdateTransitiveInfo(metadataContextInfo);
+ listItem.UpdateTransitivePackageStatusAsync()
+ .PostOnFailure(nameof(PackageItemLoader), nameof(GetCurrent));
}
listItemViewModels[packageId] = listItem;
@@ -359,13 +353,6 @@ public IEnumerable GetCurrent()
return listItemViewModels.Values.ToArray();
}
- private static void UpdateTransitiveInfo(PackageItemViewModel listItem, PackageSearchMetadataContextInfo metadataContextInfo)
- {
- listItem.TransitiveInstalledVersions.Add(metadataContextInfo.Identity.Version);
- listItem.TransitiveOrigins.AddRange(metadataContextInfo.TransitiveOrigins);
- listItem.TransitiveToolTipMessage = string.Format(CultureInfo.CurrentCulture, Resources.PackageVersionWithTransitiveOrigins, string.Join(", ", listItem.TransitiveInstalledVersions), string.Join(", ", listItem.TransitiveOrigins));
- }
-
private static ImmutableList LoadKnownOwnerViewModels(PackageSearchMetadataContextInfo metadataContextInfo)
{
ImmutableList knownOwnerViewModels = null;
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/PackageItemViewModel.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/PackageItemViewModel.cs
index b27af2a9170..f6004fa20bf 100644
--- a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/PackageItemViewModel.cs
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/ViewModels/PackageItemViewModel.cs
@@ -18,6 +18,7 @@
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Threading;
+using NuGet.PackageManagement.UI.Models.Package;
using NuGet.PackageManagement.UI.ViewModels;
using NuGet.PackageManagement.VisualStudio;
using NuGet.Packaging.Core;
@@ -37,12 +38,18 @@ public sealed class PackageItemViewModel : INotifyPropertyChanged, ISelectableIt
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly IPackageVulnerabilityService _vulnerabilityService;
+ private readonly PackageModel _packageModel;
+ private List _transitiveInstalledVersions;
+ private List _transitiveOrigins;
- public PackageItemViewModel(INuGetSearchService searchService, IPackageVulnerabilityService vulnerabilityService = default)
+ public PackageItemViewModel(INuGetSearchService searchService, PackageModel packageModel, IPackageVulnerabilityService vulnerabilityService = default)
{
_cancellationTokenSource = new CancellationTokenSource();
_searchService = searchService;
_vulnerabilityService = vulnerabilityService;
+ _packageModel = packageModel;
+ _transitiveInstalledVersions = [];
+ _transitiveOrigins = [];
}
// same URIs can reuse the bitmapImage that we've already used.
@@ -56,9 +63,9 @@ public PackageItemViewModel(INuGetSearchService searchService, IPackageVulnerabi
public event PropertyChangedEventHandler PropertyChanged;
- public string Id { get; set; }
+ public string Id => _packageModel.Id;
- public NuGetVersion Version { get; set; }
+ public NuGetVersion Version => _packageModel.Version;
public VersionRange AllowedVersions { get; set; }
@@ -70,22 +77,9 @@ public PackageItemViewModel(INuGetSearchService searchService, IPackageVulnerabi
public ImmutableList KnownOwnerViewModels { get; internal set; }
- public string Owner { get; internal set; }
+ public string Owner => string.Join(",", _packageModel.OwnersList ?? []);
- private string _author;
- public string Author
- {
- get
- {
- return _author;
- }
- set
- {
- _author = value;
- OnPropertyChanged(nameof(Author));
- OnPropertyChanged(nameof(ByAuthor));
- }
- }
+ public string Author => _packageModel.Authors;
///
/// When a collection of is available, this property returns the
@@ -119,32 +113,14 @@ private string ByOwner
}
}
- public string ByAuthor
- {
- get
- {
- return !string.IsNullOrWhiteSpace(_author) ? string.Format(CultureInfo.CurrentCulture, Resx.Text_ByAuthor, _author) : null;
- }
- }
+ public string ByAuthor => !string.IsNullOrWhiteSpace(_packageModel.Authors) ? string.Format(CultureInfo.CurrentCulture, Resx.Text_ByAuthor, _packageModel.Authors) : null;
///
/// Fallback to only when is null.
///
- public string ByOwnerOrAuthor
- {
- get
- {
- return ByOwner ?? ByAuthor;
- }
- }
+ public string ByOwnerOrAuthor => ByOwner ?? ByAuthor;
- public string VulnerableVersionsString
- {
- get
- {
- return string.Join(", ", VulnerableVersions.Keys);
- }
- }
+ public string VulnerableVersionsString => string.Join(", ", VulnerableVersions.Keys);
private readonly Dictionary _vulnerableVersions = [];
public Dictionary VulnerableVersions => _vulnerableVersions;
@@ -241,34 +217,6 @@ public bool AutoReferenced
}
}
- private List _transitiveInstalledVersions;
- public List TransitiveInstalledVersions
- {
- get
- {
- if (_transitiveInstalledVersions == null)
- {
- _transitiveInstalledVersions = new();
- }
-
- return _transitiveInstalledVersions;
- }
- }
-
- private List _transitiveOrigins;
- public List TransitiveOrigins
- {
- get
- {
- if (_transitiveOrigins == null)
- {
- _transitiveOrigins = new();
- }
-
- return _transitiveOrigins;
- }
- }
-
private string _installedVersionToolTip;
public string InstalledVersionToolTip
@@ -329,22 +277,9 @@ private bool VersionEquals(NuGetVersion v1, NuGetVersion v2)
return v1.Equals(v2, VersionComparison.Default);
}
- private long? _downloadCount;
-
- public long? DownloadCount
- {
- get
- {
- return _downloadCount;
- }
- set
- {
- _downloadCount = value;
- OnPropertyChanged(nameof(DownloadCount));
- }
- }
+ public long? DownloadCount => (_packageModel as RemotePackageModel)?.DownloadCount;
- public string Summary { get; set; }
+ public string Summary => _packageModel.Summary;
private PackageStatus _status;
public PackageStatus Status
@@ -415,30 +350,9 @@ public bool IsUpdateAvailable
}
}
- private bool _recommended;
- public bool Recommended
- {
- get { return _recommended; }
- set
- {
- if (_recommended != value)
- {
- _recommended = value;
- OnPropertyChanged(nameof(Recommended));
- }
- }
- }
+ public bool Recommended => (_packageModel as RecommendedPackageModel) is not null;
- private (string modelVersion, string vsixVersion)? _recommenderVersion;
- public (string modelVersion, string vsixVersion)? RecommenderVersion
- {
- get { return _recommenderVersion; }
- set
- {
- _recommenderVersion = value;
- OnPropertyChanged(nameof(RecommenderVersion));
- }
- }
+ public (string modelVersion, string vsixVersion)? RecommenderVersion => (_packageModel as RecommendedPackageModel)?.RecommenderVersion;
private bool _prefixReserved;
public bool PrefixReserved
@@ -454,38 +368,22 @@ public bool PrefixReserved
}
}
- private bool _isPackageDeprecated;
- public bool IsPackageDeprecated
- {
- get { return _isPackageDeprecated; }
- set
- {
- if (_isPackageDeprecated != value)
- {
- _isPackageDeprecated = value;
- OnPropertyChanged(nameof(IsPackageDeprecated));
- OnPropertyChanged(nameof(IsPackageWithWarnings));
- }
- }
- }
+ public PackageDeprecationMetadataContextInfo DeprecationMetadata => (_packageModel as IDeprecationCapable)?.DeprecationMetadata;
+ public bool IsPackageDeprecated => (_packageModel as IDeprecationCapable)?.IsDeprecated ?? false;
- public bool IsPackageVulnerable
- {
- get => VulnerabilityMaxSeverity > -1;
- }
+ public bool IsPackageVulnerable => (_packageModel as IVulnerableCapable)?.IsVulnerable ?? false || VulnerableVersions.Count > 0;
- private int _vulnerabilityMaxSeverity = -1;
public int VulnerabilityMaxSeverity
{
- get { return _vulnerabilityMaxSeverity; }
- set
+ get
{
- if (_vulnerabilityMaxSeverity != value)
+ if (VulnerableVersions.Count > 0)
{
- _vulnerabilityMaxSeverity = value;
- OnPropertyChanged(nameof(VulnerabilityMaxSeverity));
- OnPropertyChanged(nameof(IsPackageVulnerable));
- OnPropertyChanged(nameof(IsPackageWithWarnings));
+ return VulnerableVersions.Values.Max();
+ }
+ else
+ {
+ return -1;
}
}
}
@@ -509,16 +407,7 @@ public bool IsPackageWithNetworkErrors
}
}
- private Uri _iconUrl;
- public Uri IconUrl
- {
- get { return _iconUrl; }
- set
- {
- _iconUrl = value;
- OnPropertyChanged(nameof(IconUrl));
- }
- }
+ public Uri IconUrl => _packageModel.IconUrl;
private IconBitmapStatus _bitmapStatus;
@@ -633,21 +522,22 @@ public async Task> GetVersionsAsync(
#pragma warning restore VSTHRD003 // Avoid awaiting foreign Tasks
}
- private PackageDeprecationMetadataContextInfo _deprecationMetadata;
- public PackageDeprecationMetadataContextInfo DeprecationMetadata
+ public AlternatePackageMetadataContextInfo AlternatePackage => (_packageModel as IDeprecationCapable)?.AlternatePackage;
+
+ public IEnumerable Vulnerabilities => (_packageModel as IVulnerableCapable)?.Vulnerabilities ?? [];
+
+ public void UpdateTransitiveInfo(PackageSearchMetadataContextInfo metadataContextInfo)
{
- get => _deprecationMetadata;
- set
+ if (metadataContextInfo.TransitiveOrigins == null)
{
- if (_deprecationMetadata != value)
- {
- _deprecationMetadata = value;
- OnPropertyChanged(nameof(DeprecationMetadata));
- }
+ return;
}
- }
- public IEnumerable Vulnerabilities { get; set; }
+ _transitiveInstalledVersions.Add(metadataContextInfo.Identity.Version);
+ _transitiveOrigins.AddRange(metadataContextInfo.TransitiveOrigins);
+ TransitiveToolTipMessage = string.Format(CultureInfo.CurrentCulture, Resources.PackageVersionWithTransitiveOrigins, string.Join(", ", _transitiveInstalledVersions), string.Join(", ", _transitiveOrigins));
+ OnPropertyChanged(nameof(TransitiveToolTipMessage));
+ }
private (BitmapSource, IconBitmapStatus) GetInitialIconBitmapAndStatus()
{
@@ -711,7 +601,7 @@ private async Task FetchIconAsync()
Assumes.NotNull(IconUrl);
- using (Stream stream = await PackageFileService.GetPackageIconAsync(new PackageIdentity(Id, Version), CancellationToken.None))
+ using (Stream stream = await _packageModel.GetIconAsync(CancellationToken.None))
{
if (stream != null)
{
@@ -795,7 +685,7 @@ private static void AddToCache(string cacheKey, BitmapSource iconBitmapImage)
BitmapImageCache.Set(cacheKey, iconBitmapImage, policy);
}
- private async System.Threading.Tasks.Task ReloadPackageVersionsAsync()
+ private async Task ReloadPackageVersionsAsync()
{
CancellationToken cancellationToken = _cancellationTokenSource.Token;
try
@@ -833,54 +723,31 @@ private async System.Threading.Tasks.Task ReloadPackageVersionsAsync()
}
}
- private async Task ReloadTopLevelPackageMetadataAsync()
+ private async Task ReloadPackageMetadataAsync()
{
- CancellationToken cancellationToken = _cancellationTokenSource.Token;
- try
+ await RunOperationAsync(async (cancellationToken) =>
{
- var identity = new PackageIdentity(Id, Version);
-
- (PackageSearchMetadataContextInfo packageMetadata, PackageDeprecationMetadataContextInfo deprecationMetadata) =
- await _searchService.GetPackageMetadataAsync(identity, Sources, IncludePrerelease, cancellationToken);
-
- await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
+ await _packageModel.PopulateDataAsync(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
- DeprecationMetadata = deprecationMetadata;
- IsPackageDeprecated = deprecationMetadata != null;
-
- SetVulnerabilityMaxSeverity(identity.Version, packageMetadata?.Vulnerabilities?.FirstOrDefault()?.Severity ?? -1);
- }
- catch (OperationCanceledException)
- {
- // UI requested cancellation.
- }
- catch (TimeoutException)
- {
- // Our code throws a TimeoutException for HTTP timeouts
- try
+ if (_packageModel is IDeprecationCapable deprecationCapable)
{
- await NuGetUIThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
- IsPackageWithNetworkErrors = true;
+ UpdateDeprecationInfo(deprecationCapable);
}
- catch (OperationCanceledException)
+
+ if (_packageModel is IVulnerableCapable vulnerableCapable)
{
- // if cancellationToken cancelled before the above is scheduled on UI thread, don't log fault telemetry
+ UpdateVulnerabilityInfo(vulnerableCapable);
}
- }
+ });
}
- private async Task ReloadTransitivePackageMetadataAsync()
+ private async Task RunOperationAsync(Func func)
{
CancellationToken cancellationToken = _cancellationTokenSource.Token;
-
try
{
- if (_vulnerabilityService != null)
- {
- var identity = new PackageIdentity(Id, Version);
- await UpdatePackageMaxVulnerabilityAsync(identity, cancellationToken);
- }
+ await func(cancellationToken);
}
catch (OperationCanceledException)
{
@@ -903,13 +770,15 @@ private async Task ReloadTransitivePackageMetadataAsync()
private async Task UpdatePackageMaxVulnerabilityAsync(PackageIdentity packageIdentity, CancellationToken cancellationToken)
{
- cancellationToken.ThrowIfCancellationRequested();
-
- // Use ShutdownToken to ensure the operation is canceled if it's still running when VS shuts down.
- IEnumerable vulnerabilityInfoList =
- await _vulnerabilityService.GetVulnerabilityInfoAsync(packageIdentity, VsShellUtilities.ShutdownToken);
+ await RunOperationAsync(async (cancellationToken) =>
+ {
+ IVulnerableCapable vulnerabilityDatabaseCapability = new VulnerableDatabaseCapability(_vulnerabilityService, packageIdentity);
+ // Use ShutdownToken to ensure the operation is canceled if it's still running when VS shuts down.
+ await vulnerabilityDatabaseCapability.PopulateDataAsync(VsShellUtilities.ShutdownToken);
+ cancellationToken.ThrowIfCancellationRequested();
- SetVulnerabilityMaxSeverity(packageIdentity.Version, vulnerabilityInfoList?.FirstOrDefault()?.Severity ?? -1);
+ UpdateVulnerabilityInfo(vulnerabilityDatabaseCapability);
+ });
}
private void SetVulnerabilityMaxSeverity(NuGetVersion version, int maxSeverity)
@@ -918,16 +787,40 @@ private void SetVulnerabilityMaxSeverity(NuGetVersion version, int maxSeverity)
{
if (VulnerableVersions.TryAdd(version, maxSeverity))
{
+ OnPropertyChanged(nameof(VulnerabilityMaxSeverity));
OnPropertyChanged(nameof(VulnerableVersions));
OnPropertyChanged(nameof(VulnerableVersionsString));
}
- VulnerabilityMaxSeverity = Math.Max(VulnerabilityMaxSeverity, maxSeverity);
-
OnPropertyChanged(nameof(Status));
}
}
+ private void UpdateDeprecationInfo(IDeprecationCapable deprecationCapable)
+ {
+ if (deprecationCapable == null)
+ {
+ return;
+ }
+
+ OnPropertyChanged(nameof(IsPackageDeprecated));
+ OnPropertyChanged(nameof(DeprecationMetadata));
+ OnPropertyChanged(nameof(AlternatePackage));
+ }
+
+ private void UpdateVulnerabilityInfo(IVulnerableCapable vulnerableCapable)
+ {
+ if (vulnerableCapable == null)
+ {
+ return;
+ }
+
+ SetVulnerabilityMaxSeverity(Version, (int)vulnerableCapable.VulnerabilityMaxSeverity);
+ OnPropertyChanged(nameof(IsPackageVulnerable));
+ OnPropertyChanged(nameof(IsPackageWithWarnings));
+ OnPropertyChanged(nameof(Vulnerabilities));
+ }
+
public void UpdateInstalledPackagesVulnerabilities(PackageIdentity packageIdentity)
{
CancellationToken cancellationToken = _cancellationTokenSource.Token;
@@ -937,7 +830,7 @@ public void UpdateInstalledPackagesVulnerabilities(PackageIdentity packageIdenti
.PostOnFailure(nameof(PackageItemViewModel), nameof(UpdatePackageMaxVulnerabilityAsync));
}
- public void UpdatePackageStatus(IEnumerable installedPackages, bool clearCache = false)
+ public async Task UpdatePackageStatusAsync(IEnumerable installedPackages, bool clearCache = false)
{
// Get the maximum version installed in any target project/solution
InstalledVersion = installedPackages
@@ -949,27 +842,20 @@ public void UpdatePackageStatus(IEnumerable installedPack
_searchService.ClearFromCache(Id, Sources, IncludePrerelease);
}
- NuGetUIThreadHelper.JoinableTaskFactory
- .RunAsync(ReloadPackageVersionsAsync)
- .PostOnFailure(nameof(PackageItemViewModel), nameof(ReloadPackageVersionsAsync));
-
- NuGetUIThreadHelper.JoinableTaskFactory
- .RunAsync(ReloadTopLevelPackageMetadataAsync)
- .PostOnFailure(nameof(PackageItemViewModel), nameof(ReloadTopLevelPackageMetadataAsync));
+ await ReloadPackageVersionsAsync();
+ await ReloadPackageMetadataAsync();
OnPropertyChanged(nameof(Status));
}
- public void UpdateTransitivePackageStatus(NuGetVersion installedVersion)
+ public async Task UpdateTransitivePackageStatusAsync()
{
- InstalledVersion = installedVersion ?? throw new ArgumentNullException(nameof(installedVersion));
+ InstalledVersion = Version;
// Transitive packages cannot be updated and can only be installed as top-level packages with their currently installed version.
- LatestVersion = installedVersion;
+ LatestVersion = InstalledVersion;
- NuGetUIThreadHelper.JoinableTaskFactory
- .RunAsync(ReloadTransitivePackageMetadataAsync)
- .PostOnFailure(nameof(PackageItemViewModel), nameof(ReloadTransitivePackageMetadataAsync));
+ await ReloadPackageMetadataAsync();
OnPropertyChanged(nameof(Status));
}
@@ -1003,8 +889,7 @@ private void OnPropertyChanged(string propertyName)
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
- public string PackagePath { get; set; }
- public INuGetPackageFileService PackageFileService { get; internal set; }
+ public string PackagePath => (_packageModel as LocalPackageModel)?.PackagePath;
public override string ToString()
{
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/InfiniteScrollList.xaml.cs b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/InfiniteScrollList.xaml.cs
index 0f722ff7526..3ef8cc7801d 100644
--- a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/InfiniteScrollList.xaml.cs
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/InfiniteScrollList.xaml.cs
@@ -604,7 +604,7 @@ private void ClearPackageList()
_loadingStatusBar.ItemsLoaded = 0;
}
- public void UpdatePackageStatus(PackageCollectionItem[] installedPackages, bool clearCache = false)
+ public async Task UpdatePackageStatusAsync(PackageCollectionItem[] installedPackages, bool clearCache = false)
{
// in this case, we only need to update PackageStatus of
// existing items in the package list
@@ -612,11 +612,11 @@ public void UpdatePackageStatus(PackageCollectionItem[] installedPackages, bool
{
if (package.PackageLevel == PackageLevel.TopLevel)
{
- package.UpdatePackageStatus(installedPackages, clearCache);
+ await package.UpdatePackageStatusAsync(installedPackages, clearCache);
}
else
{
- package.UpdateTransitivePackageStatus(package.InstalledVersion);
+ await package.UpdateTransitivePackageStatusAsync();
}
}
}
@@ -751,9 +751,7 @@ private void ScrollViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
var last = _scrollViewer.ViewportHeight + first;
if (_scrollViewer.ViewportHeight > 0 && last >= packagesCount)
{
- NuGetUIThreadHelper.JoinableTaskFactory.RunAsync(() =>
- LoadItemsAsync(selectedPackageItem: null, token: CancellationToken.None)
- ).PostOnFailure(nameof(InfiniteScrollList));
+ _ = LoadItemsAsync(selectedPackageItem: null, token: CancellationToken.None);
}
}
}
diff --git a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageItemControl.xaml b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageItemControl.xaml
index 2f8c4094901..50a12d4e7fe 100644
--- a/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageItemControl.xaml
+++ b/src/nuget-client/src/NuGet.Clients/NuGet.PackageManagement.UI/Xamls/PackageItemControl.xaml
@@ -256,17 +256,17 @@
-
+
-
+