dependabot-nuget 0.383.0 → 0.385.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Clone/CloneWorker.cs +1 -1
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +35 -6
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/SdkProjectDiscovery.cs +13 -2
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +4 -0
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Graph/GraphWorker.cs +1 -1
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/Job.cs +5 -1
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobCommand.cs +11 -0
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ApiModel/JobCommandConverter.cs +61 -0
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/ModifiedFilesTracker.cs +83 -16
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/AzurePackageDetailFinder.cs +1 -1
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/DetailedPullRequestBodyGenerator.cs +1 -1
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/GitHubPackageDetailFinder.cs +39 -8
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/GitLabPackageDetailFinder.cs +39 -8
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/PullRequestBodyGenerator/IPackageDetailFinder.cs +80 -3
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/RunWorker.cs +51 -26
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandler.cs +3 -3
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/GroupUpdateAllVersionsHandler.cs +11 -6
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandler.cs +3 -3
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandler.cs +3 -3
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandler.cs +3 -3
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/FileWriterWorker.cs +1 -1
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/IFileWriter.cs +2 -1
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/XmlFileWriter.cs +254 -5
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +22 -1
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +1 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +1 -0
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +60 -0
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -0
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +90 -2
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/ApiModel/JobTests.cs +148 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/ModifiedFilesTrackerTests.cs +378 -10
  33. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestBodyGenerator/DetailedPullRequestBodyGeneratorTests.cs +176 -0
  34. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/PullRequestBodyGenerator/IPackageDetailFinderTests.cs +34 -0
  35. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdateHandlers/UpdateHandlerSelectionTests.cs +18 -0
  36. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +2 -2
  37. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterTestsBase.cs +6 -4
  38. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/TestFileWriterReturnsConstantResult.cs +1 -1
  39. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/XmlFileWriterTests.cs +714 -0
  40. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +24 -0
  41. metadata +7 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9faecb2b58ddfb0ef2f7199364aaf92d894dc30cb222b649688278f03ad7db23
4
- data.tar.gz: a41a8ebdd8d8301739d30b922971bd3492be935aec49c6fe09a2f1d97deb6b98
3
+ metadata.gz: a881b72688cc9a18a791c07aaa05ca3bc0b3d1e2da426553116cb90e9e8545ae
4
+ data.tar.gz: 69578367499f994702f926128e1297e8b3b38fcc43a3bb5236606a515be19de8
5
5
  SHA512:
6
- metadata.gz: d792e017824a47f5e0ff713b8ced7b76ae459c833d815139ccc98bb45ab97b3f4af96f77b035536a3c28237ac26afad0c6d9a3103897651d7ed9070898dc788f
7
- data.tar.gz: 899acdbff1b55fda3bd97c2b1591c282dcd659b872ce46ef8901b69430bb74e1188c2fb4211e1b6dc402918fe5e497048eaa17442fa553f753a678f5fec5f593
6
+ metadata.gz: bdb9aa2d2d6de546e097b37bdea0eaf16c077c30e370e491a0c6defbabf3d5d176a99de91309202daaca3714de09b41b1035f46757402acbba7a9c66d9233f24
7
+ data.tar.gz: d425d717e8d673a7b64a559c6e65a1ef9bacfee8c3de37719eac3406af8d869e0be7157a835b5b6078b71db0aa173518ec52ad86e35a95eddf942f803b449d1f
@@ -34,7 +34,7 @@ public class CloneWorker
34
34
  JobErrorBase? parseError = null;
35
35
  try
36
36
  {
37
- jobFile = RunWorker.Deserialize(jobFileContent);
37
+ jobFile = RunWorker.Deserialize(jobFileContent, _logger);
38
38
  if (jobFile is null)
39
39
  {
40
40
  parseError = new UnknownError(new Exception("Job file could not be deserialized"), _jobId);
@@ -88,10 +88,20 @@ public partial class DiscoveryWorker : IDiscoveryWorker
88
88
  ImmutableArray<ProjectDiscoveryResult> projectResults = [];
89
89
  WorkspaceDiscoveryResult result;
90
90
 
91
+ // if the workspace directory directly contains a solution file, capture its directory so that the MSBuild
92
+ // `SolutionDir` property can be faked during discovery; this allows project files that reference
93
+ // `$(SolutionDir)` to be evaluated correctly
94
+ string? solutionDir = null;
95
+
91
96
  if (Directory.Exists(workspacePath))
92
97
  {
93
98
  _logger.Info($"Discovering build files in workspace [{workspacePath}].");
94
99
 
100
+ if (DirectoryContainsSolutionFile(workspacePath))
101
+ {
102
+ solutionDir = workspacePath;
103
+ }
104
+
95
105
  dotNetToolsJsonDiscovery = DotNetToolsJsonDiscovery.Discover(repoRootPath, workspacePath, _logger);
96
106
  globalJsonDiscovery = GlobalJsonDiscovery.Discover(repoRootPath, workspacePath, _logger);
97
107
 
@@ -101,7 +111,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
101
111
  }
102
112
 
103
113
  // this next line should throw or something
104
- projectResults = await RunForDirectoryAsync(repoRootPath, workspacePath);
114
+ projectResults = await RunForDirectoryAsync(repoRootPath, workspacePath, solutionDir);
105
115
  }
106
116
  else
107
117
  {
@@ -149,6 +159,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
149
159
  DotNetToolsJson = null,
150
160
  GlobalJson = null,
151
161
  Projects = projectResults.Where(p => p.IsSuccess).OrderBy(p => p.FilePath).ToImmutableArray(),
162
+ SolutionDirectory = GetRelativeSolutionDirectory(repoRootPath, solutionDir),
152
163
  Error = failedProjectResult.Error,
153
164
  IsSuccess = false,
154
165
  };
@@ -162,6 +173,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
162
173
  DotNetToolsJson = dotNetToolsJsonDiscovery,
163
174
  GlobalJson = globalJsonDiscovery,
164
175
  Projects = projectResults.OrderBy(p => p.FilePath).ToImmutableArray(),
176
+ SolutionDirectory = GetRelativeSolutionDirectory(repoRootPath, solutionDir),
165
177
  };
166
178
 
167
179
  _logger.Info("Discovery complete.");
@@ -196,7 +208,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
196
208
  return await NuGetHelper.DownloadNuGetPackagesAsync(repoRootPath, workspacePath, msbuildSdks, logger);
197
209
  }
198
210
 
199
- private async Task<ImmutableArray<ProjectDiscoveryResult>> RunForDirectoryAsync(string repoRootPath, string workspacePath)
211
+ private async Task<ImmutableArray<ProjectDiscoveryResult>> RunForDirectoryAsync(string repoRootPath, string workspacePath, string? solutionDir)
200
212
  {
201
213
  _logger.Info($" Discovering projects beneath [{Path.GetRelativePath(repoRootPath, workspacePath)}].");
202
214
  var entryPoints = FindEntryPoints(workspacePath);
@@ -227,7 +239,24 @@ public partial class DiscoveryWorker : IDiscoveryWorker
227
239
  return [];
228
240
  }
229
241
 
230
- return await RunForProjectPathsAsync(repoRootPath, workspacePath, projects);
242
+ return await RunForProjectPathsAsync(repoRootPath, workspacePath, projects, solutionDir);
243
+ }
244
+
245
+ private static bool DirectoryContainsSolutionFile(string directoryPath)
246
+ {
247
+ return Directory.EnumerateFiles(directoryPath)
248
+ .Any(path =>
249
+ {
250
+ string extension = Path.GetExtension(path).ToLowerInvariant();
251
+ return extension is ".sln" or ".slnx";
252
+ });
253
+ }
254
+
255
+ private static string? GetRelativeSolutionDirectory(string repoRootPath, string? solutionDir)
256
+ {
257
+ return solutionDir is null
258
+ ? null
259
+ : Path.GetRelativePath(repoRootPath, solutionDir).NormalizePathToUnix();
231
260
  }
232
261
 
233
262
  private static ImmutableArray<string> FindEntryPoints(string workspacePath)
@@ -378,7 +407,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
378
407
  return foundItems;
379
408
  }
380
409
 
381
- private async Task<ImmutableArray<ProjectDiscoveryResult>> RunForProjectPathsAsync(string repoRootPath, string workspacePath, IEnumerable<string> projectPaths)
410
+ private async Task<ImmutableArray<ProjectDiscoveryResult>> RunForProjectPathsAsync(string repoRootPath, string workspacePath, IEnumerable<string> projectPaths, string? solutionDir)
382
411
  {
383
412
  var normalizedProjectPaths = projectPaths.SelectMany(p => PathHelper.ResolveCaseInsensitivePathsInsideRepoRoot(p, repoRootPath) ?? []).Distinct().ToImmutableArray();
384
413
 
@@ -437,7 +466,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
437
466
  _processedProjectPaths.Add(projectPath);
438
467
 
439
468
  var relativeProjectPath = Path.GetRelativePath(workspacePath, projectPath).NormalizePathToUnix();
440
- var projectResults = await SdkProjectDiscovery.DiscoverAsync(repoRootPath, workspacePath, projectPath, _experimentsManager, _logger);
469
+ var projectResults = await SdkProjectDiscovery.DiscoverAsync(repoRootPath, workspacePath, projectPath, _experimentsManager, solutionDir, _logger);
441
470
 
442
471
  // Determine if there were unrestored MSBuildSdks
443
472
  var msbuildSdks = projectResults.SelectMany(p => p.Dependencies.Where(d => d.Type == DependencyType.MSBuildSdk)).ToImmutableArray();
@@ -446,7 +475,7 @@ public partial class DiscoveryWorker : IDiscoveryWorker
446
475
  // If new SDKs were restored, then we need to rerun SdkProjectDiscovery.
447
476
  if (await TryRestoreMSBuildSdksAsync(repoRootPath, workspacePath, msbuildSdks, _logger))
448
477
  {
449
- projectResults = await SdkProjectDiscovery.DiscoverAsync(repoRootPath, workspacePath, projectPath, _experimentsManager, _logger);
478
+ projectResults = await SdkProjectDiscovery.DiscoverAsync(repoRootPath, workspacePath, projectPath, _experimentsManager, solutionDir, _logger);
450
479
  }
451
480
  }
452
481
 
@@ -71,7 +71,7 @@ internal static class SdkProjectDiscovery
71
71
  // this seems to be the maximum number of TFMs that can be restored in parallel without running into race conditions
72
72
  private const int MaximumParallelTargetFrameworkRestores = 2;
73
73
 
74
- public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverAsync(string repoRootPath, string workspacePath, string startingProjectPath, ExperimentsManager experimentsManager, ILogger logger)
74
+ public static async Task<ImmutableArray<ProjectDiscoveryResult>> DiscoverAsync(string repoRootPath, string workspacePath, string startingProjectPath, ExperimentsManager experimentsManager, string? solutionDir, ILogger logger)
75
75
  {
76
76
  var extension = Path.GetExtension(startingProjectPath)?.ToLowerInvariant();
77
77
  switch (extension)
@@ -190,6 +190,15 @@ internal static class SdkProjectDiscovery
190
190
  args.Add($"/p:TargetFramework={tfm}");
191
191
  }
192
192
 
193
+ // if the project lives alongside a solution file, fake the MSBuild `SolutionDir` property so that
194
+ // project files referencing `$(SolutionDir)` can be evaluated correctly; MSBuild expects this value
195
+ // to end with a directory separator so that `$(SolutionDir)foo` concatenations form valid paths
196
+ if (solutionDir is not null)
197
+ {
198
+ var normalizedSolutionDir = $"{solutionDir.TrimEnd('/', Path.DirectorySeparatorChar)}/";
199
+ args.Add($"/p:SolutionDir={normalizedSolutionDir}");
200
+ }
201
+
193
202
  // if using CPM and a project also sets TreatWarningsAsErrors to true, this can cause discovery to fail; explicitly don't allow that
194
203
  args.Add("/p:TreatWarningsAsErrors=false");
195
204
  args.Add("/p:MSBuildTreatWarningsAsErrors=false");
@@ -446,6 +455,7 @@ internal static class SdkProjectDiscovery
446
455
  packagesPerProject,
447
456
  explicitPackageVersionsPerProject,
448
457
  experimentsManager,
458
+ solutionDir,
449
459
  logger
450
460
  );
451
461
  }
@@ -809,6 +819,7 @@ internal static class SdkProjectDiscovery
809
819
  Dictionary<string, Dictionary<string, Dictionary<string, string>>> packagesPerProject,
810
820
  Dictionary<string, Dictionary<string, Dictionary<string, string>>> explicitPackageVersionsPerProject,
811
821
  ExperimentsManager experimentsManager,
822
+ string? solutionDir,
812
823
  ILogger logger
813
824
  )
814
825
  {
@@ -831,7 +842,7 @@ internal static class SdkProjectDiscovery
831
842
 
832
843
  var tempProjectPath = await MSBuildHelper.CreateTempProjectAsync(tempDirectory, repoRootPath, projectPath, targetFrameworks, topLevelDependencies, logger);
833
844
  var tempProjectDirectory = Path.GetDirectoryName(tempProjectPath)!;
834
- var rediscoveredDependencies = await DiscoverAsync(tempProjectDirectory, tempProjectDirectory, tempProjectPath, experimentsManager, logger);
845
+ var rediscoveredDependencies = await DiscoverAsync(tempProjectDirectory, tempProjectDirectory, tempProjectPath, experimentsManager, solutionDir, logger);
835
846
  var tempProjectFileName = Path.GetFileName(tempProjectPath);
836
847
  var rediscoveredDependenciesForThisProject = rediscoveredDependencies.FirstOrDefault(r => PathComparer.Instance.Equals(r.FilePath, tempProjectFileName));
837
848
  if (rediscoveredDependenciesForThisProject is null)
@@ -10,6 +10,10 @@ public sealed record WorkspaceDiscoveryResult : NativeResult
10
10
  public GlobalJsonDiscoveryResult? GlobalJson { get; init; }
11
11
  public DotNetToolsJsonDiscoveryResult? DotNetToolsJson { get; init; }
12
12
 
13
+ // when the workspace directly contains a solution file, this is the directory that was used to fake the MSBuild
14
+ // `SolutionDir` property during discovery; it is `null` when no solution file was present
15
+ public string? SolutionDirectory { get; init; }
16
+
13
17
  public ProjectDiscoveryResult? GetProjectDiscoveryFromPath(string repoPath)
14
18
  {
15
19
  var projectDiscovery = Projects.FirstOrDefault(p => System.IO.Path.Join(Path, p.FilePath).FullyNormalizedRootedPath().Equals(repoPath, StringComparison.OrdinalIgnoreCase));
@@ -28,7 +28,7 @@ public class GraphWorker : IGraphWorker
28
28
  {
29
29
  // Deserialize the job file
30
30
  var jobFileContent = await File.ReadAllTextAsync(jobFilePath.FullName);
31
- var jobWrapper = RunWorker.Deserialize(jobFileContent);
31
+ var jobWrapper = RunWorker.Deserialize(jobFileContent, _logger);
32
32
  var job = jobWrapper.Job;
33
33
 
34
34
  // Use the case-insensitive repo contents path if provided, otherwise use the original
@@ -14,6 +14,9 @@ namespace NuGetUpdater.Core.Run.ApiModel;
14
14
  public sealed record Job
15
15
  {
16
16
  public string PackageManager { get; init; } = "nuget";
17
+
18
+ public JobCommand Command { get; init; } = JobCommand.None;
19
+
17
20
  public ImmutableArray<AllowedUpdate> AllowedUpdates { get; init; } = [new AllowedUpdate()];
18
21
 
19
22
  [JsonConverter(typeof(NullAsBoolConverter))]
@@ -34,6 +37,7 @@ public sealed record Job
34
37
  public required JobSource Source { get; init; }
35
38
  public bool UpdateSubdependencies { get; init; } = false;
36
39
  public bool UpdatingAPullRequest { get; init; } = false;
40
+ public bool MultiEcosystemUpdate { get; init; } = false;
37
41
  public bool VendorDependencies { get; init; } = false;
38
42
  public bool RejectExternalCode { get; init; } = false;
39
43
  public bool RepoPrivate { get; init; } = false;
@@ -50,7 +54,7 @@ public sealed record Job
50
54
  builder.Add(Source.Directory);
51
55
  }
52
56
 
53
- builder.AddRange(Source.Directories ?? []);
57
+ builder.AddRange((Source.Directories ?? []).Where(d => d is not null));
54
58
  if (builder.Count == 0)
55
59
  {
56
60
  builder.Add("/");
@@ -0,0 +1,11 @@
1
+ namespace NuGetUpdater.Core.Run.ApiModel;
2
+
3
+ public enum JobCommand
4
+ {
5
+ None,
6
+ Version,
7
+ Update,
8
+ Recreate,
9
+ Security,
10
+ Graph,
11
+ }
@@ -0,0 +1,61 @@
1
+ using System.Text.Json;
2
+ using System.Text.Json.Serialization;
3
+
4
+ namespace NuGetUpdater.Core.Run.ApiModel;
5
+
6
+ public class JobCommandConverter : JsonConverter<JobCommand>
7
+ {
8
+ private readonly ILogger _logger;
9
+
10
+ public JobCommandConverter(ILogger logger)
11
+ {
12
+ _logger = logger;
13
+ }
14
+
15
+ public override JobCommand Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
16
+ {
17
+ if (reader.TokenType == JsonTokenType.Null)
18
+ {
19
+ return JobCommand.None;
20
+ }
21
+
22
+ if (reader.TokenType != JsonTokenType.String)
23
+ {
24
+ _logger.Warn($"Unexpected JSON token type for job command: {reader.TokenType}; defaulting to None.");
25
+ reader.Skip();
26
+ return JobCommand.None;
27
+ }
28
+
29
+ var value = reader.GetString();
30
+ return value switch
31
+ {
32
+ "" => JobCommand.None,
33
+ "version" => JobCommand.Version,
34
+ "update" => JobCommand.Update,
35
+ "recreate" => JobCommand.Recreate,
36
+ "security" => JobCommand.Security,
37
+ "graph" => JobCommand.Graph,
38
+ _ => LogAndDefault(value),
39
+ };
40
+ }
41
+
42
+ private JobCommand LogAndDefault(string? value)
43
+ {
44
+ _logger.Warn($"Unknown job command value: \"{value}\"; defaulting to None.");
45
+ return JobCommand.None;
46
+ }
47
+
48
+ public override void Write(Utf8JsonWriter writer, JobCommand value, JsonSerializerOptions options)
49
+ {
50
+ var str = value switch
51
+ {
52
+ JobCommand.Version => "version",
53
+ JobCommand.Update => "update",
54
+ JobCommand.Recreate => "recreate",
55
+ JobCommand.Security => "security",
56
+ JobCommand.Graph => "graph",
57
+ _ => "",
58
+ };
59
+ writer.WriteStringValue(str);
60
+ }
61
+ }
@@ -18,27 +18,56 @@ public class ModifiedFilesTracker
18
18
  private readonly Dictionary<string, EOLType> _originalDependencyFileEOFs = [];
19
19
  private readonly Dictionary<string, bool> _originalDependencyFileBOMs = [];
20
20
  private string[] _nonProjectFiles = [];
21
- private readonly HashSet<string> _initiallyExistingLockFiles;
21
+ private readonly HashSet<string> _initiallyExistingFiles;
22
+
23
+ /// <summary>
24
+ /// The set of file name patterns (case-insensitive) that are allowed to be edited during an update run.
25
+ /// Files matching these patterns are tracked for pre-existence; any file not present before discovery
26
+ /// will not be reported as modified.
27
+ /// </summary>
28
+ internal static readonly string[] AllowedEditableFilePatterns =
29
+ [
30
+ "global.json",
31
+ "dotnet-tools.json",
32
+ "*.csproj",
33
+ "*.fsproj",
34
+ "*.vbproj",
35
+ "*.props",
36
+ "*.targets",
37
+ "app.config",
38
+ "web.config",
39
+ "packages.config",
40
+ "packages.lock.json",
41
+ ];
22
42
 
23
43
  public IReadOnlyDictionary<string, string> OriginalDependencyFileContents => _originalDependencyFileContents;
24
44
  //public IReadOnlyDictionary<string, EOLType> OriginalDependencyFileEOFs => _originalDependencyFileEOFs;
25
45
  public IReadOnlyDictionary<string, bool> OriginalDependencyFileBOMs => _originalDependencyFileBOMs;
26
46
 
27
- public ModifiedFilesTracker(DirectoryInfo repoContentsPath, HashSet<string> initiallyExistingLockFiles, ILogger logger)
47
+ public ModifiedFilesTracker(DirectoryInfo repoContentsPath, HashSet<string> initiallyExistingFiles, ILogger logger)
28
48
  {
29
49
  RepoContentsPath = repoContentsPath;
30
- _initiallyExistingLockFiles = initiallyExistingLockFiles;
50
+ _initiallyExistingFiles = initiallyExistingFiles;
31
51
  _logger = logger;
32
52
  }
33
53
 
34
54
  /// <summary>
35
- /// Returns the set of lock file paths (relative to repo root, unix-style) that currently exist on disk.
55
+ /// Returns the set of editable file paths (relative to repo root, unix-style) that currently exist on disk
56
+ /// and match the allowed editable file patterns.
36
57
  /// </summary>
37
- public static HashSet<string> GetExistingLockFiles(DirectoryInfo repoContentsPath)
58
+ public static HashSet<string> GetInitiallyExistingFiles(DirectoryInfo repoContentsPath)
38
59
  {
39
- return Directory.EnumerateFiles(repoContentsPath.FullName, ProjectHelper.PackagesLockJsonFileName, SearchOption.AllDirectories)
40
- .Select(f => Path.GetRelativePath(repoContentsPath.FullName, f).NormalizePathToUnix())
41
- .ToHashSet(StringComparer.OrdinalIgnoreCase);
60
+ var result = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
61
+ foreach (var file in Directory.EnumerateFiles(repoContentsPath.FullName, "*", SearchOption.AllDirectories))
62
+ {
63
+ var fileName = Path.GetFileName(file);
64
+ if (MatchesAllowedEditablePattern(fileName))
65
+ {
66
+ result.Add(Path.GetRelativePath(repoContentsPath.FullName, file).NormalizePathToUnix());
67
+ }
68
+ }
69
+
70
+ return result;
42
71
  }
43
72
 
44
73
  public async Task StartTrackingAsync(WorkspaceDiscoveryResult discoveryResult)
@@ -65,11 +94,16 @@ public class ModifiedFilesTracker
65
94
  foreach (var project in _currentDiscoveryResult.Projects)
66
95
  {
67
96
  var projectDirectory = Path.GetDirectoryName(project.FilePath);
97
+ if (IsFileNotInitiallyPresent(Path.Join(_currentDiscoveryResult.Path, project.FilePath).NormalizePathToUnix()))
98
+ {
99
+ continue;
100
+ }
101
+
68
102
  await TrackOriginalContentsAsync(_currentDiscoveryResult.Path, project.FilePath);
69
103
  foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
70
104
  {
71
105
  var extraFilePath = Path.Join(projectDirectory, extraFile);
72
- if (IsLockFileNotInitiallyPresent(Path.Join(_currentDiscoveryResult.Path, extraFilePath).NormalizePathToUnix()))
106
+ if (IsFileNotInitiallyPresent(Path.Join(_currentDiscoveryResult.Path, extraFilePath).NormalizePathToUnix()))
73
107
  {
74
108
  continue;
75
109
  }
@@ -82,7 +116,9 @@ public class ModifiedFilesTracker
82
116
  {
83
117
  _currentDiscoveryResult.GlobalJson?.FilePath,
84
118
  _currentDiscoveryResult.DotNetToolsJson?.FilePath,
85
- }.Where(f => f is not null).Cast<string>().ToArray();
119
+ }.Where(f => f is not null).Cast<string>()
120
+ .Where(f => !IsFileNotInitiallyPresent(Path.Join(_currentDiscoveryResult.Path, f).NormalizePathToUnix()))
121
+ .ToArray();
86
122
  foreach (var nonProjectFile in _nonProjectFiles)
87
123
  {
88
124
  await TrackOriginalContentsAsync(_currentDiscoveryResult.Path, nonProjectFile);
@@ -138,12 +174,17 @@ public class ModifiedFilesTracker
138
174
 
139
175
  foreach (var project in _currentDiscoveryResult.Projects)
140
176
  {
177
+ if (IsFileNotInitiallyPresent(Path.Join(_currentDiscoveryResult.Path, project.FilePath).NormalizePathToUnix()))
178
+ {
179
+ continue;
180
+ }
181
+
141
182
  await AddUpdatedFileIfDifferentAsync(_currentDiscoveryResult.Path, project.FilePath);
142
183
  var projectDirectory = Path.GetDirectoryName(project.FilePath);
143
184
  foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
144
185
  {
145
186
  var extraFilePath = Path.Join(projectDirectory, extraFile);
146
- if (IsLockFileNotInitiallyPresent(Path.Join(_currentDiscoveryResult.Path, extraFilePath).NormalizePathToUnix()))
187
+ if (IsFileNotInitiallyPresent(Path.Join(_currentDiscoveryResult.Path, extraFilePath).NormalizePathToUnix()))
147
188
  {
148
189
  continue;
149
190
  }
@@ -173,20 +214,46 @@ public class ModifiedFilesTracker
173
214
  return correctedRepoFullPath;
174
215
  }
175
216
 
176
- private bool IsLockFileNotInitiallyPresent(string repoRelativePath)
217
+ private bool IsFileNotInitiallyPresent(string repoRelativePath)
177
218
  {
178
- return IsLockFileNotInitiallyPresent(repoRelativePath, _initiallyExistingLockFiles);
219
+ return IsFileNotInitiallyPresent(repoRelativePath, _initiallyExistingFiles);
179
220
  }
180
221
 
181
- public static bool IsLockFileNotInitiallyPresent(string repoRelativePath, HashSet<string> initiallyExistingLockFiles)
222
+ public static bool IsFileNotInitiallyPresent(string repoRelativePath, HashSet<string> initiallyExistingFiles)
182
223
  {
183
224
  var normalizedPath = repoRelativePath.NormalizePathToUnix().NormalizeUnixPathParts().TrimStart('/');
184
- if (!Path.GetFileName(normalizedPath).Equals(ProjectHelper.PackagesLockJsonFileName, StringComparison.OrdinalIgnoreCase))
225
+ var fileName = Path.GetFileName(normalizedPath);
226
+
227
+ if (!MatchesAllowedEditablePattern(fileName))
185
228
  {
186
229
  return false;
187
230
  }
188
231
 
189
- return !initiallyExistingLockFiles.Contains(normalizedPath);
232
+ return !initiallyExistingFiles.Contains(normalizedPath);
233
+ }
234
+
235
+ internal static bool MatchesAllowedEditablePattern(string fileName)
236
+ {
237
+ foreach (var pattern in AllowedEditableFilePatterns)
238
+ {
239
+ if (pattern.StartsWith("*"))
240
+ {
241
+ var extension = pattern[1..]; // e.g., ".csproj"
242
+ if (fileName.EndsWith(extension, StringComparison.OrdinalIgnoreCase))
243
+ {
244
+ return true;
245
+ }
246
+ }
247
+ else
248
+ {
249
+ if (fileName.Equals(pattern, StringComparison.OrdinalIgnoreCase))
250
+ {
251
+ return true;
252
+ }
253
+ }
254
+ }
255
+
256
+ return false;
190
257
  }
191
258
 
192
259
  public static ImmutableArray<DependencyFile> MergeUpdatedFileSet(ImmutableArray<DependencyFile> setA, ImmutableArray<DependencyFile> setB)
@@ -20,7 +20,7 @@ internal class AzurePackageDetailFinder : IPackageDetailFinder
20
20
  return "commits";
21
21
  }
22
22
 
23
- public Task<Dictionary<NuGetVersion, (string TagName, string? Details)>> GetReleaseDataForVersionsAsync(string repoName, NuGetVersion oldVersion, NuGetVersion newVersion)
23
+ public Task<Dictionary<NuGetVersion, (string TagName, string? Details)>> GetReleaseDataForVersionsAsync(string repoName, string dependencyName, NuGetVersion oldVersion, NuGetVersion newVersion)
24
24
  {
25
25
  // azure devops doesn't support direct tag listing
26
26
  return Task.FromResult(new Dictionary<NuGetVersion, (string TagName, string? Details)>());
@@ -91,7 +91,7 @@ internal class DetailedPullRequestBodyGenerator : IPullRequestBodyGenerator, IDi
91
91
  updateOperation.OldVersion is not null)
92
92
  {
93
93
  var repoName = uri.LocalPath.TrimStart('/');
94
- var versionsAndDetails = await packageDetailFinder.GetReleaseDataForVersionsAsync(repoName, updateOperation.OldVersion, updateOperation.NewVersion);
94
+ var versionsAndDetails = await packageDetailFinder.GetReleaseDataForVersionsAsync(repoName, updateOperation.DependencyName, updateOperation.OldVersion, updateOperation.NewVersion);
95
95
  var ordered = versionsAndDetails
96
96
  .Where(kv => kv.Key != updateOperation.OldVersion)
97
97
  .OrderByDescending(kv => kv.Key)
@@ -28,25 +28,27 @@ internal class GitHubPackageDetailFinder : IPackageDetailFinder
28
28
  return "commits";
29
29
  }
30
30
 
31
- public async Task<Dictionary<NuGetVersion, (string TagName, string? Details)>> GetReleaseDataForVersionsAsync(string repoName, NuGetVersion oldVersion, NuGetVersion newVersion)
31
+ public async Task<Dictionary<NuGetVersion, (string TagName, string? Details)>> GetReleaseDataForVersionsAsync(string repoName, string dependencyName, NuGetVersion oldVersion, NuGetVersion newVersion)
32
32
  {
33
- var result = new Dictionary<NuGetVersion, (string TagName, string? Details)>();
33
+ var versionReleaseData = new Dictionary<NuGetVersion, (string TagName, string? Details)>();
34
+ var packageScopedVersionReleaseData = new Dictionary<NuGetVersion, (string TagName, string? Details)>();
35
+ var otherPackageScopedVersionReleaseData = new Dictionary<NuGetVersion, (string TagName, string? Details)>();
34
36
  var url = $"https://api.github.com/repos/{repoName}/releases?per_page=100";
35
37
  var jsonOption = await _httpFetcher.GetJsonElementAsync(url);
36
38
  if (jsonOption is null)
37
39
  {
38
- return result;
40
+ return versionReleaseData;
39
41
  }
40
42
 
41
43
  var json = jsonOption.Value;
42
44
  if (json.ValueKind != JsonValueKind.Array)
43
45
  {
44
- return result;
46
+ return versionReleaseData;
45
47
  }
46
48
 
47
49
  if (json.GetArrayLength() == 0)
48
50
  {
49
- return result;
51
+ return versionReleaseData;
50
52
  }
51
53
 
52
54
  foreach (var releaseObject in json.EnumerateArray())
@@ -75,7 +77,10 @@ internal class GitHubPackageDetailFinder : IPackageDetailFinder
75
77
  var tagName = tagNameElement.GetString()!;
76
78
 
77
79
  // find matching version
78
- var correspondingVersion = IPackageDetailFinder.GetVersionFromNames(releaseName, tagName);
80
+ var packageScopedVersion = IPackageDetailFinder.GetPackageScopedVersionFromNames(releaseName, tagName, dependencyName);
81
+ var correspondingVersion = packageScopedVersion ?? IPackageDetailFinder.GetVersionFromNames(releaseName, tagName);
82
+ var isOtherPackageScopedVersion = packageScopedVersion is null &&
83
+ IPackageDetailFinder.HasPackageScopedVersionForOtherDependency(releaseName, tagName, dependencyName);
79
84
  if (correspondingVersion is null)
80
85
  {
81
86
  continue;
@@ -90,11 +95,37 @@ internal class GitHubPackageDetailFinder : IPackageDetailFinder
90
95
  }
91
96
 
92
97
  var body = bodyElement.GetString()!;
93
- result[correspondingVersion] = (tagName, body);
98
+ if (packageScopedVersion is not null)
99
+ {
100
+ packageScopedVersionReleaseData[correspondingVersion] = (tagName, body);
101
+ }
102
+ else if (isOtherPackageScopedVersion)
103
+ {
104
+ otherPackageScopedVersionReleaseData[correspondingVersion] = (tagName, body);
105
+ }
106
+ else
107
+ {
108
+ versionReleaseData[correspondingVersion] = (tagName, body);
109
+ }
94
110
  }
95
111
  }
96
112
 
97
- return result;
113
+ if (packageScopedVersionReleaseData.Count > 0)
114
+ {
115
+ foreach (var packageScopedDetails in packageScopedVersionReleaseData)
116
+ {
117
+ versionReleaseData[packageScopedDetails.Key] = packageScopedDetails.Value;
118
+ }
119
+
120
+ return versionReleaseData;
121
+ }
122
+
123
+ foreach (var otherPackageScopedDetails in otherPackageScopedVersionReleaseData)
124
+ {
125
+ versionReleaseData[otherPackageScopedDetails.Key] = otherPackageScopedDetails.Value;
126
+ }
127
+
128
+ return versionReleaseData;
98
129
  }
99
130
 
100
131
  public string GetReleasesUrlPath() => "releases";
@@ -28,25 +28,27 @@ internal class GitLabPackageDetailFinder : IPackageDetailFinder
28
28
  return "-/commits";
29
29
  }
30
30
 
31
- public async Task<Dictionary<NuGetVersion, (string TagName, string? Details)>> GetReleaseDataForVersionsAsync(string repoName, NuGetVersion oldVersion, NuGetVersion newVersion)
31
+ public async Task<Dictionary<NuGetVersion, (string TagName, string? Details)>> GetReleaseDataForVersionsAsync(string repoName, string dependencyName, NuGetVersion oldVersion, NuGetVersion newVersion)
32
32
  {
33
- var result = new Dictionary<NuGetVersion, (string TagName, string? Details)>();
33
+ var versionReleaseData = new Dictionary<NuGetVersion, (string TagName, string? Details)>();
34
+ var packageScopedVersionReleaseData = new Dictionary<NuGetVersion, (string TagName, string? Details)>();
35
+ var otherPackageScopedVersionReleaseData = new Dictionary<NuGetVersion, (string TagName, string? Details)>();
34
36
  var url = $"https://gitlab.com/api/v4/projects/{Uri.EscapeDataString(repoName)}/repository/tags";
35
37
  var jsonOption = await _httpFetcher.GetJsonElementAsync(url);
36
38
  if (jsonOption is null)
37
39
  {
38
- return result;
40
+ return versionReleaseData;
39
41
  }
40
42
 
41
43
  var json = jsonOption.Value;
42
44
  if (json.ValueKind != JsonValueKind.Array)
43
45
  {
44
- return result;
46
+ return versionReleaseData;
45
47
  }
46
48
 
47
49
  if (json.GetArrayLength() == 0)
48
50
  {
49
- return result;
51
+ return versionReleaseData;
50
52
  }
51
53
 
52
54
  foreach (var responseObject in json.EnumerateArray())
@@ -85,7 +87,10 @@ internal class GitLabPackageDetailFinder : IPackageDetailFinder
85
87
  }
86
88
 
87
89
  // find matching version
88
- var correspondingVersion = IPackageDetailFinder.GetVersionFromNames(releaseName, tagName);
90
+ var packageScopedVersion = IPackageDetailFinder.GetPackageScopedVersionFromNames(releaseName, tagName, dependencyName);
91
+ var correspondingVersion = packageScopedVersion ?? IPackageDetailFinder.GetVersionFromNames(releaseName, tagName);
92
+ var isOtherPackageScopedVersion = packageScopedVersion is null &&
93
+ IPackageDetailFinder.HasPackageScopedVersionForOtherDependency(releaseName, tagName, dependencyName);
89
94
  if (correspondingVersion is null)
90
95
  {
91
96
  continue;
@@ -96,11 +101,37 @@ internal class GitLabPackageDetailFinder : IPackageDetailFinder
96
101
  correspondingVersion >= oldVersion &&
97
102
  correspondingVersion <= newVersion)
98
103
  {
99
- result[correspondingVersion] = (resultTag, description);
104
+ if (packageScopedVersion is not null)
105
+ {
106
+ packageScopedVersionReleaseData[correspondingVersion] = (resultTag, description);
107
+ }
108
+ else if (isOtherPackageScopedVersion)
109
+ {
110
+ otherPackageScopedVersionReleaseData[correspondingVersion] = (resultTag, description);
111
+ }
112
+ else
113
+ {
114
+ versionReleaseData[correspondingVersion] = (resultTag, description);
115
+ }
100
116
  }
101
117
  }
102
118
 
103
- return result;
119
+ if (packageScopedVersionReleaseData.Count > 0)
120
+ {
121
+ foreach (var packageScopedDetails in packageScopedVersionReleaseData)
122
+ {
123
+ versionReleaseData[packageScopedDetails.Key] = packageScopedDetails.Value;
124
+ }
125
+
126
+ return versionReleaseData;
127
+ }
128
+
129
+ foreach (var otherPackageScopedDetails in otherPackageScopedVersionReleaseData)
130
+ {
131
+ versionReleaseData[otherPackageScopedDetails.Key] = otherPackageScopedDetails.Value;
132
+ }
133
+
134
+ return versionReleaseData;
104
135
  }
105
136
 
106
137
  public string GetReleasesUrlPath() => "-/releases";