dependabot-nuget 0.383.0 → 0.384.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 (33) 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 +3 -0
  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/RunWorker.cs +51 -26
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/CreateSecurityUpdatePullRequestHandler.cs +3 -3
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/GroupUpdateAllVersionsHandler.cs +6 -6
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshGroupUpdatePullRequestHandler.cs +3 -3
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshSecurityUpdatePullRequestHandler.cs +3 -3
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Run/UpdateHandlers/RefreshVersionUpdatePullRequestHandler.cs +3 -3
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/FileWriterWorker.cs +1 -1
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/IFileWriter.cs +2 -1
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/FileWriters/XmlFileWriter.cs +254 -5
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +22 -1
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +1 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.Project.cs +1 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +60 -0
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -0
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/SdkProjectDiscoveryTests.cs +90 -2
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/ApiModel/JobTests.cs +116 -0
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/ModifiedFilesTrackerTests.cs +378 -10
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Run/UpdatedDependencyListTests.cs +2 -2
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/FileWriterTestsBase.cs +6 -4
  30. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/TestFileWriterReturnsConstantResult.cs +1 -1
  31. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/FileWriters/XmlFileWriterTests.cs +714 -0
  32. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Utilities/MSBuildHelperTests.cs +24 -0
  33. 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: b77dedc63031536f0a500698857e46afbc14f3f5ea80a0e8300a52e27d4d3d46
4
+ data.tar.gz: 5794be271db14c64c9d3e72adb259bbd52e78fa79ed796943595598395fe3ed6
5
5
  SHA512:
6
- metadata.gz: d792e017824a47f5e0ff713b8ced7b76ae459c833d815139ccc98bb45ab97b3f4af96f77b035536a3c28237ac26afad0c6d9a3103897651d7ed9070898dc788f
7
- data.tar.gz: 899acdbff1b55fda3bd97c2b1591c282dcd659b872ce46ef8901b69430bb74e1188c2fb4211e1b6dc402918fe5e497048eaa17442fa553f753a678f5fec5f593
6
+ metadata.gz: bf66e0d54541539ac5fe228db99426b8f15b8a7e7253e2a62b61d25cee3ddf6a86c9ce1a51a2206f01b4ba3559ed69191bcf8ca7e81eb9b71950576c79002b5b
7
+ data.tar.gz: 5227b2387092e94a359eff0c3a0f08608d6a559b007b59caeb6c9016ff760712c6ab684dbc455ced2211835000ed2d8a2fc3b4f53f4638828d1ee01b705c66a0
@@ -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))]
@@ -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)
@@ -44,7 +44,7 @@ public class RunWorker
44
44
  public async Task<int> RunAsync(FileInfo jobFilePath, DirectoryInfo repoContentsPath, DirectoryInfo? caseInsensitiveRepoContentsPath, string baseCommitSha)
45
45
  {
46
46
  var jobFileContent = await File.ReadAllTextAsync(jobFilePath.FullName);
47
- var jobWrapper = Deserialize(jobFileContent);
47
+ var jobWrapper = Deserialize(jobFileContent, _logger);
48
48
  var experimentsManager = ExperimentsManager.GetExperimentsManager(jobWrapper.Job.Experiments);
49
49
  var result = await RunAsync(jobWrapper.Job, repoContentsPath, caseInsensitiveRepoContentsPath, baseCommitSha, experimentsManager);
50
50
  return result;
@@ -96,6 +96,13 @@ public class RunWorker
96
96
 
97
97
  try
98
98
  {
99
+ // Register MSBuild up front so that the `Microsoft.Build` assembly is loadable for
100
+ // the rest of the job. This is required even on the error-handling path:
101
+ // `JobErrorBase.ErrorFromException` references `Microsoft.Build` exception types, so
102
+ // if a failure occurs before MSBuild is registered, JIT-compiling the error handler
103
+ // would itself throw `FileNotFoundException` for `Microsoft.Build` and mask the real
104
+ // error.
105
+ MSBuildHelper.RegisterMSBuild(repoContentsPath.FullName, repoContentsPath.FullName, _logger);
99
106
  await PatchNuGetConfigFilesAsync(repoContentsPath);
100
107
  var handler = GetUpdateHandler(job);
101
108
  _logger.Info($"Starting update job of type {handler.TagName}");
@@ -264,7 +271,7 @@ public class RunWorker
264
271
  return relativeResolvedName;
265
272
  }
266
273
 
267
- internal static UpdatedDependencyList GetUpdatedDependencyListFromDiscovery(WorkspaceDiscoveryResult discoveryResult, string repoRoot, ILogger logger, HashSet<string> initiallyExistingLockFiles)
274
+ internal static UpdatedDependencyList GetUpdatedDependencyListFromDiscovery(WorkspaceDiscoveryResult discoveryResult, string repoRoot, ILogger logger, HashSet<string> initiallyExistingFiles)
268
275
  {
269
276
  string GetFullRepoPath(string path)
270
277
  {
@@ -272,12 +279,17 @@ public class RunWorker
272
279
  return Path.Join(discoveryResult.Path, path).FullyNormalizedRootedPath();
273
280
  }
274
281
 
282
+ bool IsInitiallyPresent(string filePath)
283
+ {
284
+ return !ModifiedFilesTracker.IsFileNotInitiallyPresent(Path.Join(discoveryResult.Path, filePath).NormalizePathToUnix(), initiallyExistingFiles);
285
+ }
286
+
275
287
  var auxiliaryFiles = new List<string>();
276
- if (discoveryResult.GlobalJson is not null)
288
+ if (discoveryResult.GlobalJson is not null && IsInitiallyPresent(discoveryResult.GlobalJson.FilePath))
277
289
  {
278
290
  auxiliaryFiles.Add(GetFullRepoPath(discoveryResult.GlobalJson.FilePath));
279
291
  }
280
- if (discoveryResult.DotNetToolsJson is not null)
292
+ if (discoveryResult.DotNetToolsJson is not null && IsInitiallyPresent(discoveryResult.DotNetToolsJson.FilePath))
281
293
  {
282
294
  auxiliaryFiles.Add(GetFullRepoPath(discoveryResult.DotNetToolsJson.FilePath));
283
295
  }
@@ -288,7 +300,7 @@ public class RunWorker
288
300
  foreach (var extraFile in project.ImportedFiles.Concat(project.AdditionalFiles))
289
301
  {
290
302
  var extraFileFullPath = Path.Join(projectDirectory, extraFile);
291
- if (ModifiedFilesTracker.IsLockFileNotInitiallyPresent(Path.Join(discoveryResult.Path, extraFileFullPath).NormalizePathToUnix(), initiallyExistingLockFiles))
303
+ if (!IsInitiallyPresent(extraFileFullPath))
292
304
  {
293
305
  continue;
294
306
  }
@@ -298,28 +310,30 @@ public class RunWorker
298
310
  }
299
311
  }
300
312
 
301
- var allDependenciesWithFilePath = discoveryResult.Projects.SelectMany(p =>
302
- {
303
- return p.Dependencies
304
- .Where(d => d.Version is not null)
305
- .Select(d =>
306
- (p.FilePath, new ReportedDependency()
307
- {
308
- Name = d.Name,
309
- Requirements = [new ReportedRequirement()
310
- {
311
- File = GetFullRepoPath(p.FilePath),
312
- Requirement = d.Version!,
313
- Groups = ["dependencies"],
314
- }],
315
- Version = d.Version,
316
- }));
317
- }).ToList();
313
+ var allDependenciesWithFilePath = discoveryResult.Projects
314
+ .Where(p => IsInitiallyPresent(p.FilePath))
315
+ .SelectMany(p =>
316
+ {
317
+ return p.Dependencies
318
+ .Where(d => d.Version is not null)
319
+ .Select(d =>
320
+ (p.FilePath, new ReportedDependency()
321
+ {
322
+ Name = d.Name,
323
+ Requirements = [new ReportedRequirement()
324
+ {
325
+ File = GetFullRepoPath(p.FilePath),
326
+ Requirement = d.Version!,
327
+ Groups = ["dependencies"],
328
+ }],
329
+ Version = d.Version,
330
+ }));
331
+ }).ToList();
318
332
 
319
333
  var nonProjectDependencySet = new (string?, IEnumerable<Dependency>)[]
320
334
  {
321
- (discoveryResult.GlobalJson?.FilePath, discoveryResult.GlobalJson?.Dependencies ?? []),
322
- (discoveryResult.DotNetToolsJson?.FilePath, discoveryResult.DotNetToolsJson?.Dependencies ?? []),
335
+ (discoveryResult.GlobalJson is not null && IsInitiallyPresent(discoveryResult.GlobalJson.FilePath) ? discoveryResult.GlobalJson.FilePath : null, discoveryResult.GlobalJson?.Dependencies ?? []),
336
+ (discoveryResult.DotNetToolsJson is not null && IsInitiallyPresent(discoveryResult.DotNetToolsJson.FilePath) ? discoveryResult.DotNetToolsJson.FilePath : null, discoveryResult.DotNetToolsJson?.Dependencies ?? []),
323
337
  };
324
338
 
325
339
  foreach (var (filePath, dependencies) in nonProjectDependencySet)
@@ -352,6 +366,7 @@ public class RunWorker
352
366
  .ToArray();
353
367
 
354
368
  var dependencyFiles = discoveryResult.Projects
369
+ .Where(p => IsInitiallyPresent(p.FilePath))
355
370
  .Select(p => GetFullRepoPath(p.FilePath))
356
371
  .Concat(auxiliaryFiles)
357
372
  .Select(p => EnsureCorrectFileCasing(p, repoRoot, logger))
@@ -367,9 +382,12 @@ public class RunWorker
367
382
  return updatedDependencyList;
368
383
  }
369
384
 
370
- public static JobFile Deserialize(string json)
385
+ public static JobFile Deserialize(string json) => Deserialize(json, new ConsoleLogger());
386
+
387
+ public static JobFile Deserialize(string json, ILogger logger)
371
388
  {
372
- var jobFile = JsonSerializer.Deserialize<JobFile>(json, SerializerOptions);
389
+ var options = GetDeserializerOptions(logger);
390
+ var jobFile = JsonSerializer.Deserialize<JobFile>(json, options);
373
391
  if (jobFile is null)
374
392
  {
375
393
  throw new InvalidOperationException("Unable to deserialize job wrapper.");
@@ -383,6 +401,13 @@ public class RunWorker
383
401
  return jobFile;
384
402
  }
385
403
 
404
+ internal static JsonSerializerOptions GetDeserializerOptions(ILogger logger)
405
+ {
406
+ var options = new JsonSerializerOptions(SerializerOptions);
407
+ options.Converters.Insert(0, new JobCommandConverter(logger));
408
+ return options;
409
+ }
410
+
386
411
  internal static string AddInsecureConnectionsAttribute(string nugetConfigContents)
387
412
  {
388
413
  try
@@ -31,7 +31,7 @@ internal class CreateSecurityUpdatePullRequestHandler : IUpdateHandler
31
31
  {
32
32
  var repoContentsPath = caseInsensitiveRepoContentsPath ?? originalRepoContentsPath;
33
33
  var jobDependencies = job.Dependencies.ToHashSet(StringComparer.OrdinalIgnoreCase);
34
- var initialLockFiles = ModifiedFilesTracker.GetExistingLockFiles(repoContentsPath);
34
+ var initialFiles = ModifiedFilesTracker.GetInitiallyExistingFiles(repoContentsPath);
35
35
  foreach (var directory in job.GetAllDirectories(repoContentsPath.FullName))
36
36
  {
37
37
  var discoveryResult = await discoveryWorker.RunAsync(repoContentsPath.FullName, directory);
@@ -42,7 +42,7 @@ internal class CreateSecurityUpdatePullRequestHandler : IUpdateHandler
42
42
  return;
43
43
  }
44
44
 
45
- var updatedDependencyList = RunWorker.GetUpdatedDependencyListFromDiscovery(discoveryResult, originalRepoContentsPath.FullName, logger, initialLockFiles);
45
+ var updatedDependencyList = RunWorker.GetUpdatedDependencyListFromDiscovery(discoveryResult, originalRepoContentsPath.FullName, logger, initialFiles);
46
46
  await apiHandler.UpdateDependencyList(updatedDependencyList);
47
47
  await this.ReportUpdaterStarted(apiHandler);
48
48
 
@@ -62,7 +62,7 @@ internal class CreateSecurityUpdatePullRequestHandler : IUpdateHandler
62
62
 
63
63
  logger.Info($"Updating dependencies: {string.Join(", ", groupedUpdateOperationsToPerform.Select(g => g.Key).Distinct().OrderBy(d => d, StringComparer.OrdinalIgnoreCase))}");
64
64
 
65
- var tracker = new ModifiedFilesTracker(originalRepoContentsPath, initialLockFiles, logger);
65
+ var tracker = new ModifiedFilesTracker(originalRepoContentsPath, initialFiles, logger);
66
66
  await tracker.StartTrackingAsync(discoveryResult);
67
67
  foreach (var dependencyGroupToUpdate in groupedUpdateOperationsToPerform)
68
68
  {