Skip to content

Fix #54: Add possibility to pin versions via a new property "IsPinned" #55

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,18 @@ Shared package references, e.g. in the `Directory.Build.props` file, are handled

CentralPackageManagement (`PackageVersion` entries) are supported as well.

A version can be pinned by adding the `IsPinned` property to `PackageReference` or `PackageVersion` entries, to stop NuGetMonitor
from offering to update this version, if e.g. updating the package might break some functionaility.
This is an alternate approach to using the package range notation `[13.0.1]`, that avoids side effects on dependent projects, e.g. when creating a NuGet package.
```xml
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" IsPinned="true">
```
A justification property can be added to `PackageReference` or `PackageVersion` entries, to e.g. document why a reference is pinned and can't be updated
```xml
<PackageReference Include="Newtonsoft.Json" Version="[13.0.1]" Justification="Can't update due to Visual Studio extension limitations">
```

A mitigation element can be added to suppress warnings for transitive dependencies that can't be updated due to project limitations but have been evaluated to not affect the product security.
A mitigation element can be added to suppress warnings for transitive dependencies that can't be updated due to project limitations
but have been evaluated to not affect the product security.
```xml
<PackageMitigation Include="Newtonsoft.Json" Version="13.0.1" Justification="Can't update due to Visual Studio extension limitations">
```
Expand Down
16 changes: 15 additions & 1 deletion src/NuGetMonitor.Model/ExtensionMethods.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
namespace NuGetMonitor.Model;
using Microsoft.Build.Evaluation;

namespace NuGetMonitor.Model;

public static class ExtensionMethods
{
Expand All @@ -22,4 +24,16 @@ public static class ExtensionMethods
}
}
}

public static bool GetIsPinned(this ProjectItem projectItem)
{
var metadataValue = projectItem.GetMetadataValue("IsPinned");

return bool.TryParse(metadataValue, out var value) && value;
}

public static string GetJustification(this ProjectItem projectItem)
{
return projectItem.GetMetadataValue("Justification");
}
}
2 changes: 1 addition & 1 deletion src/NuGetMonitor.Model/Models/PackageReference.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace NuGetMonitor.Model.Models;

[DebuggerDisplay("{Id}, {VersionRange}")]
public sealed record PackageReference(string Id, VersionRange VersionRange)
public sealed record PackageReference(string Id, VersionRange VersionRange, bool IsPinned)
{
public PackageIdentity? FindBestMatch(IEnumerable<NuGetVersion>? versions)
{
Expand Down
9 changes: 6 additions & 3 deletions src/NuGetMonitor.Model/Models/PackageReferenceEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ public enum VersionKind
[DebuggerDisplay("{Identity}, {ProjectItemInTargetFramework}")]
public sealed record PackageReferenceEntry
{
public PackageReferenceEntry(string id, VersionRange versionRange, VersionKind versionKind, ProjectItem versionSource, ProjectItemInTargetFramework projectItemInTargetFramework, string justification, bool isPrivateAsset)
public PackageReferenceEntry(string id, VersionRange versionRange, VersionKind versionKind, ProjectItem versionSource, ProjectItemInTargetFramework projectItemInTargetFramework, bool isPrivateAsset)
{
VersionKind = versionKind;
VersionSource = versionSource;
ProjectItemInTargetFramework = projectItemInTargetFramework;
Justification = justification;
Justification = versionSource.GetJustification();
IsPrivateAsset = isPrivateAsset;
Identity = new(id, versionRange);
IsPinned = versionSource.GetIsPinned();
Identity = new(id, versionRange, IsPinned);
}

public PackageReference Identity { get; }
Expand All @@ -36,4 +37,6 @@ public PackageReferenceEntry(string id, VersionRange versionRange, VersionKind v
public string Justification { get; }

public bool IsPrivateAsset { get; }

public bool IsPinned { get; }
}
2 changes: 1 addition & 1 deletion src/NuGetMonitor.Model/Models/ProjectInTargetFramework.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ private static ReadOnlyDictionary<PackageIdentity, string> GetPackageMitigations
.Select(item => new
{
Identity = new PackageIdentity(item.EvaluatedInclude, NuGetVersion.Parse(item.GetMetadataValue("Version"))),
Justification = item.GetMetadataValue("Justification")
Justification = item.GetJustification()
}
)
.ToDictionary(item => item.Identity, item => item.Justification);
Expand Down
7 changes: 4 additions & 3 deletions src/NuGetMonitor.Model/Services/ProjectService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,9 +217,10 @@ private static IEnumerable<ProjectItemInTargetFramework> GetPackageReferenceItem
}
}

return version is null
? null
: new PackageReferenceEntry(id, version, versionKind, versionSource, projectItemInTargetFramework, versionSource.GetMetadataValue("Justification"), projectItem.GetIsPrivateAsset());
if (version is null)
return null;

return new(id, version, versionKind, versionSource, projectItemInTargetFramework, projectItem.GetIsPrivateAsset());
}

internal static bool IsTrue(this ProjectProperty? property)
Expand Down
5 changes: 1 addition & 4 deletions src/NuGetMonitor/Services/InfoBarService.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
using System.IO;
using System.Text;
using System.Windows;
using Community.VisualStudio.Toolkit;
using Community.VisualStudio.Toolkit;
using Microsoft.VisualStudio.Imaging;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
Expand Down
5 changes: 5 additions & 0 deletions src/NuGetMonitor/View/Monitor/NuGetMonitorControl.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,11 @@
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridCheckBoxColumn IsReadOnly="True"
Header="Pinned"
Width="50"
Binding="{Binding IsPinned, FallbackValue=false, Mode=OneWay}"
dgx:DataGridFilterColumn.IsFilterVisible="False" />
<DataGridTextColumn Header="Issues"
Width="120"
Binding="{Binding PackageInfo.Issues, FallbackValue=''}"
Expand Down
4 changes: 2 additions & 2 deletions src/NuGetMonitor/View/Monitor/NugetMonitorViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,10 @@ private async void Load()
.SelectMany(item => item.Items)
.Select(item => item.ProjectItemInTargetFramework)
.Where(item => item.Project.IsTransitivePinningEnabled)
.SelectMany(project => project.Project.CentralVersionMap.Values.Select(item => new PackageReferenceEntry(item.EvaluatedInclude, item.GetVersion() ?? VersionRange.None, VersionKind.CentralDefinition, item, project, item.GetMetadataValue("Justification"), false)))
.SelectMany(project => project.Project.CentralVersionMap.Values.Select(item => new PackageReferenceEntry(item.EvaluatedInclude, item.GetVersion() ?? VersionRange.None, VersionKind.CentralDefinition, item, project, false)))
.Where(item => !packageIds.Contains(item.Identity.Id))
.GroupBy(item => item.Identity)
.Select(item => new PackageViewModel(this, item, PackageItemType.PackageVersion, _solutionService))
.Select(group => new PackageViewModel(this, group, PackageItemType.PackageVersion, _solutionService))
.ToArray();

Packages = packages.Concat(transitivePins).ToArray();
Expand Down
5 changes: 4 additions & 1 deletion src/NuGetMonitor/ViewModels/PackageViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public PackageViewModel(NuGetMonitorViewModel parent, IGrouping<PackageReference
Projects = items.GroupBy(item => (itemType == PackageItemType.PackageVersion ? item.VersionSource : item.ProjectItemInTargetFramework.ProjectItem).GetContainingProject()).Select(item => new ProjectViewModel(item.Key, solutionService)).ToArray();
ActiveVersion = NuGetVersion.TryParse(PackageReference.VersionRange.OriginalString, out var simpleVersion) ? simpleVersion : PackageReference.VersionRange;
Justifications = string.Join(", ", Items.Select(reference => reference.Justification).Distinct());
IsPinned = items.Key.IsPinned;
}

public IGrouping<PackageReference, PackageReferenceEntry> Items { get; }
Expand Down Expand Up @@ -53,6 +54,8 @@ public PackageViewModel(NuGetMonitorViewModel parent, IGrouping<PackageReference

public string Justifications { get; }

public bool IsPinned { get; }

public async Task Load()
{
try
Expand Down Expand Up @@ -86,7 +89,7 @@ public void ApplySelectedVersion()

private void OnSelectedVersionChanged()
{
IsUpdateAvailable = (SelectedVersion is not null) && ActiveVersion switch
IsUpdateAvailable = !IsPinned && (SelectedVersion is not null) && ActiveVersion switch
{
NuGetVersion version => version != SelectedVersion,
VersionRange versionRange => versionRange.FindBestMatch(Package?.Versions) != SelectedVersion,
Expand Down