dependabot-nuget 0.265.0 → 0.266.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/lib/NuGetUpdater/NuGetUpdater.Cli/Commands/UpdateCommand.cs +6 -6
  3. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalysisResult.cs +1 -1
  4. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/AnalyzeWorker.cs +73 -77
  5. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/CompatabilityChecker.cs +21 -8
  6. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/NuGetContext.cs +24 -8
  7. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Analyze/VersionFinder.cs +33 -16
  8. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/DiscoveryWorker.cs +44 -25
  9. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Discover/WorkspaceDiscoveryResult.cs +1 -1
  10. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/ErrorType.cs +8 -0
  11. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Files/ProjectBuildFile.cs +2 -2
  12. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/NativeResult.cs +8 -0
  13. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/PackagesConfigUpdater.cs +18 -1
  14. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/SdkPackageUpdater.cs +2 -1
  15. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdateOperationResult.cs +5 -0
  16. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Updater/UpdaterWorker.cs +62 -22
  17. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core/Utilities/MSBuildHelper.cs +19 -1
  18. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTestBase.cs +6 -2
  19. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/AnalyzeWorkerTests.cs +448 -0
  20. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Analyze/CompatibilityCheckerTests.cs +23 -0
  21. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTestBase.cs +2 -0
  22. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/DiscoveryWorkerTests.cs +148 -0
  23. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Discover/ExpectedDiscoveryResults.cs +1 -1
  24. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/MockNuGetPackage.cs +1 -1
  25. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/TestHttpServer.cs +81 -0
  26. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTestBase.cs +27 -7
  27. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Mixed.cs +32 -0
  28. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.PackagesConfig.cs +87 -2
  29. data/helpers/lib/NuGetUpdater/NuGetUpdater.Core.Test/Update/UpdateWorkerTests.Sdk.cs +88 -0
  30. data/lib/dependabot/nuget/analysis/dependency_analysis.rb +3 -0
  31. data/lib/dependabot/nuget/file_fetcher.rb +1 -1
  32. data/lib/dependabot/nuget/metadata_finder.rb +160 -2
  33. data/lib/dependabot/nuget/native_discovery/native_workspace_discovery.rb +3 -0
  34. data/lib/dependabot/nuget/native_helpers.rb +34 -3
  35. data/lib/dependabot/nuget/native_update_checker/native_update_checker.rb +1 -0
  36. data/lib/dependabot/nuget/nuget_config_credential_helpers.rb +3 -0
  37. metadata +11 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0c8985bae397230599aee116240d6569573ea126c70bc39a37a568935aaf3649
4
- data.tar.gz: b137e9983b1ce14c86235b898a70039c53aadf9eb7a4389b94631bc787ba7914
3
+ metadata.gz: 4669e522f1ce196d5657096bdf738e6ab353c5a697ea1b0acb1f0c87f9815a44
4
+ data.tar.gz: 33657445578fcd52b0370490b3c1f4b2fe165ad9f00e22d831d6a216b89458f5
5
5
  SHA512:
6
- metadata.gz: bbcf0d5509e6bad8ea0af998c7ee6ea6047c13153c4111b2f9e46d568b559bcbe93eb21e8a200ee9deabb4c41bad10e789d236bb6fb30a34ed8c072a47537ced
7
- data.tar.gz: 21ffbb1f7c7ff6a40d8db845b6e175c0f9478d5b99cadd969b127bbed07171b2b120bb3c6a107a1a41d59245906a7276f4006f0cdef4a2f01105d218596bfc6e
6
+ metadata.gz: 0ca40a2a42e55b376edc0694253f81a54eb30d75748c3a5572e3a754b64aab96fe86b34f753d0f4566bdf66816da3318aed0eba31fc195581cb2072f89c0d8cb
7
+ data.tar.gz: 115640e60d1b2a59edb5b6956c9e8d3c35105dafa6ce00d60aa5de52561da96670606af7862d1a24ab1f24bc4fd91651b4856eeb4b7fc545ca230cc72a529493
@@ -1,6 +1,4 @@
1
- using System;
2
1
  using System.CommandLine;
3
- using System.IO;
4
2
 
5
3
  using NuGetUpdater.Core;
6
4
 
@@ -15,6 +13,7 @@ internal static class UpdateCommand
15
13
  internal static readonly Option<string> PreviousVersionOption = new("--previous-version") { IsRequired = true };
16
14
  internal static readonly Option<bool> IsTransitiveOption = new("--transitive", getDefaultValue: () => false);
17
15
  internal static readonly Option<bool> VerboseOption = new("--verbose", getDefaultValue: () => false);
16
+ internal static readonly Option<string?> ResultOutputPathOption = new("--result-output-path", getDefaultValue: () => null);
18
17
 
19
18
  internal static Command GetCommand(Action<int> setExitCode)
20
19
  {
@@ -26,17 +25,18 @@ internal static class UpdateCommand
26
25
  NewVersionOption,
27
26
  PreviousVersionOption,
28
27
  IsTransitiveOption,
29
- VerboseOption
28
+ VerboseOption,
29
+ ResultOutputPathOption
30
30
  };
31
31
 
32
32
  command.TreatUnmatchedTokensAsErrors = true;
33
33
 
34
- command.SetHandler(async (repoRoot, solutionOrProjectFile, dependencyName, newVersion, previousVersion, isTransitive, verbose) =>
34
+ command.SetHandler(async (repoRoot, solutionOrProjectFile, dependencyName, newVersion, previousVersion, isTransitive, verbose, resultOutputPath) =>
35
35
  {
36
36
  var worker = new UpdaterWorker(new Logger(verbose));
37
- await worker.RunAsync(repoRoot.FullName, solutionOrProjectFile.FullName, dependencyName, previousVersion, newVersion, isTransitive);
37
+ await worker.RunAsync(repoRoot.FullName, solutionOrProjectFile.FullName, dependencyName, previousVersion, newVersion, isTransitive, resultOutputPath);
38
38
  setExitCode(0);
39
- }, RepoRootOption, SolutionOrProjectFileOption, DependencyNameOption, NewVersionOption, PreviousVersionOption, IsTransitiveOption, VerboseOption);
39
+ }, RepoRootOption, SolutionOrProjectFileOption, DependencyNameOption, NewVersionOption, PreviousVersionOption, IsTransitiveOption, VerboseOption, ResultOutputPathOption);
40
40
 
41
41
  return command;
42
42
  }
@@ -2,7 +2,7 @@ using System.Collections.Immutable;
2
2
 
3
3
  namespace NuGetUpdater.Core.Analyze;
4
4
 
5
- public record AnalysisResult
5
+ public record AnalysisResult : NativeResult
6
6
  {
7
7
  public required string UpdatedVersion { get; init; }
8
8
  public bool CanUpdate { get; init; }
@@ -1,8 +1,8 @@
1
1
  using System.Collections.Immutable;
2
+ using System.Net;
2
3
  using System.Text.Json;
3
4
  using System.Text.Json.Serialization;
4
5
 
5
- using NuGet.Configuration;
6
6
  using NuGet.Frameworks;
7
7
  using NuGet.Versioning;
8
8
 
@@ -57,70 +57,87 @@ public partial class AnalyzeWorker
57
57
  ImmutableArray<Dependency> updatedDependencies = [];
58
58
 
59
59
  bool isUpdateNecessary = IsUpdateNecessary(dependencyInfo, projectsWithDependency);
60
- if (isUpdateNecessary)
60
+ using var nugetContext = new NuGetContext(startingDirectory);
61
+ AnalysisResult result;
62
+ try
61
63
  {
62
- var nugetContext = new NuGetContext(startingDirectory);
63
- if (!Directory.Exists(nugetContext.TempPackageDirectory))
64
+ if (isUpdateNecessary)
64
65
  {
65
- Directory.CreateDirectory(nugetContext.TempPackageDirectory);
66
- }
67
-
68
- _logger.Log($" Determining multi-dependency property.");
69
- var multiDependencies = DetermineMultiDependencyDetails(
70
- discovery,
71
- dependencyInfo.Name,
72
- propertyBasedDependencies);
73
-
74
- usesMultiDependencyProperty = multiDependencies.Any(md => md.DependencyNames.Count > 1);
75
- var dependenciesToUpdate = usesMultiDependencyProperty
76
- ? multiDependencies
77
- .SelectMany(md => md.DependencyNames)
78
- .ToImmutableHashSet(StringComparer.OrdinalIgnoreCase)
79
- : [dependencyInfo.Name];
80
- var applicableTargetFrameworks = usesMultiDependencyProperty
81
- ? multiDependencies
82
- .SelectMany(md => md.TargetFrameworks)
83
- .ToImmutableHashSet(StringComparer.OrdinalIgnoreCase)
84
- .Select(NuGetFramework.Parse)
85
- .ToImmutableArray()
86
- : projectFrameworks;
87
-
88
- _logger.Log($" Finding updated version.");
89
- updatedVersion = await FindUpdatedVersionAsync(
90
- startingDirectory,
91
- dependencyInfo,
92
- dependenciesToUpdate,
93
- applicableTargetFrameworks,
94
- nugetContext,
95
- _logger,
96
- CancellationToken.None);
66
+ if (!Directory.Exists(nugetContext.TempPackageDirectory))
67
+ {
68
+ Directory.CreateDirectory(nugetContext.TempPackageDirectory);
69
+ }
97
70
 
98
- _logger.Log($" Finding updated peer dependencies.");
99
- updatedDependencies = updatedVersion is not null
100
- ? await FindUpdatedDependenciesAsync(
101
- repoRoot,
71
+ _logger.Log($" Determining multi-dependency property.");
72
+ var multiDependencies = DetermineMultiDependencyDetails(
102
73
  discovery,
74
+ dependencyInfo.Name,
75
+ propertyBasedDependencies);
76
+
77
+ usesMultiDependencyProperty = multiDependencies.Any(md => md.DependencyNames.Count > 1);
78
+ var dependenciesToUpdate = usesMultiDependencyProperty
79
+ ? multiDependencies
80
+ .SelectMany(md => md.DependencyNames)
81
+ .ToImmutableHashSet(StringComparer.OrdinalIgnoreCase)
82
+ : [dependencyInfo.Name];
83
+ var applicableTargetFrameworks = usesMultiDependencyProperty
84
+ ? multiDependencies
85
+ .SelectMany(md => md.TargetFrameworks)
86
+ .ToImmutableHashSet(StringComparer.OrdinalIgnoreCase)
87
+ .Select(NuGetFramework.Parse)
88
+ .ToImmutableArray()
89
+ : projectFrameworks;
90
+
91
+ _logger.Log($" Finding updated version.");
92
+ updatedVersion = await FindUpdatedVersionAsync(
93
+ startingDirectory,
94
+ dependencyInfo,
103
95
  dependenciesToUpdate,
104
- updatedVersion,
96
+ applicableTargetFrameworks,
105
97
  nugetContext,
106
98
  _logger,
107
- CancellationToken.None)
108
- : [];
109
-
110
- //TODO: At this point we should add the peer dependencies to a queue where
111
- // we will analyze them one by one to see if they themselves are part of a
112
- // multi-dependency property. Basically looping this if-body until we have
113
- // emptied the queue and have a complete list of updated dependencies. We
114
- // should track the dependenciesToUpdate as they have already been analyzed.
115
- }
99
+ CancellationToken.None);
100
+
101
+ _logger.Log($" Finding updated peer dependencies.");
102
+ updatedDependencies = updatedVersion is not null
103
+ ? await FindUpdatedDependenciesAsync(
104
+ repoRoot,
105
+ discovery,
106
+ dependenciesToUpdate,
107
+ updatedVersion,
108
+ nugetContext,
109
+ _logger,
110
+ CancellationToken.None)
111
+ : [];
112
+
113
+ //TODO: At this point we should add the peer dependencies to a queue where
114
+ // we will analyze them one by one to see if they themselves are part of a
115
+ // multi-dependency property. Basically looping this if-body until we have
116
+ // emptied the queue and have a complete list of updated dependencies. We
117
+ // should track the dependenciesToUpdate as they have already been analyzed.
118
+ }
116
119
 
117
- var result = new AnalysisResult
120
+ result = new AnalysisResult
121
+ {
122
+ UpdatedVersion = updatedVersion?.ToNormalizedString() ?? dependencyInfo.Version,
123
+ CanUpdate = updatedVersion is not null,
124
+ VersionComesFromMultiDependencyProperty = usesMultiDependencyProperty,
125
+ UpdatedDependencies = updatedDependencies,
126
+ };
127
+ }
128
+ catch (HttpRequestException ex)
129
+ when (ex.StatusCode == HttpStatusCode.Unauthorized || ex.StatusCode == HttpStatusCode.Forbidden)
118
130
  {
119
- UpdatedVersion = updatedVersion?.ToNormalizedString() ?? dependencyInfo.Version,
120
- CanUpdate = updatedVersion is not null,
121
- VersionComesFromMultiDependencyProperty = usesMultiDependencyProperty,
122
- UpdatedDependencies = updatedDependencies,
123
- };
131
+ // TODO: consolidate this error handling between AnalyzeWorker, DiscoveryWorker, and UpdateWorker
132
+ result = new AnalysisResult
133
+ {
134
+ ErrorType = ErrorType.AuthenticationFailure,
135
+ ErrorDetails = "(" + string.Join("|", nugetContext.PackageSources.Select(s => s.Source)) + ")",
136
+ UpdatedVersion = string.Empty,
137
+ CanUpdate = false,
138
+ UpdatedDependencies = [],
139
+ };
140
+ }
124
141
 
125
142
  await WriteResultsAsync(analysisDirectory, dependencyInfo.Name, result, _logger);
126
143
 
@@ -314,27 +331,6 @@ public partial class AnalyzeWorker
314
331
  return true;
315
332
  }
316
333
 
317
- internal static async Task<ImmutableDictionary<NuGetFramework, ImmutableArray<Dependency>>> GetDependenciesAsync(
318
- string workspacePath,
319
- string projectPath,
320
- IEnumerable<NuGetFramework> frameworks,
321
- Dependency package,
322
- Logger logger)
323
- {
324
- var result = ImmutableDictionary.CreateBuilder<NuGetFramework, ImmutableArray<Dependency>>();
325
- foreach (var framework in frameworks)
326
- {
327
- var dependencies = await MSBuildHelper.GetAllPackageDependenciesAsync(
328
- workspacePath,
329
- projectPath,
330
- framework.ToString(),
331
- [package],
332
- logger);
333
- result.Add(framework, [.. dependencies]);
334
- }
335
- return result.ToImmutable();
336
- }
337
-
338
334
  internal static async Task<ImmutableArray<Dependency>> FindUpdatedDependenciesAsync(
339
335
  string repoRoot,
340
336
  WorkspaceDiscoveryResult discovery,
@@ -54,6 +54,11 @@ internal static class CompatibilityChecker
54
54
 
55
55
  var compatibilityService = new FrameworkCompatibilityService();
56
56
  var compatibleFrameworks = compatibilityService.GetCompatibleFrameworks(packageFrameworks);
57
+ var packageSupportsAny = compatibleFrameworks.Any(f => f.IsAny);
58
+ if (packageSupportsAny)
59
+ {
60
+ return true;
61
+ }
57
62
 
58
63
  var incompatibleFrameworks = projectFrameworks.Where(f => !compatibleFrameworks.Contains(f)).ToArray();
59
64
  if (incompatibleFrameworks.Length > 0)
@@ -141,15 +146,23 @@ internal static class CompatibilityChecker
141
146
  throw new NotSupportedException($"Failed to get FindPackageByIdResource for {source.SourceUri}");
142
147
  }
143
148
 
144
- var exists = await feed.DoesPackageExistAsync(
145
- package.Id,
146
- package.Version,
147
- context.SourceCacheContext,
148
- NullLogger.Instance,
149
- cancellationToken);
150
-
151
- if (!exists)
149
+ try
150
+ {
151
+ // a non-compliant v2 API returning 404 can cause this to throw
152
+ var exists = await feed.DoesPackageExistAsync(
153
+ package.Id,
154
+ package.Version,
155
+ context.SourceCacheContext,
156
+ NullLogger.Instance,
157
+ cancellationToken);
158
+ if (!exists)
159
+ {
160
+ continue;
161
+ }
162
+ }
163
+ catch (FatalProtocolException)
152
164
  {
165
+ // if anything goes wrong here, the package source obviously doesn't contain the requested package
153
166
  continue;
154
167
  }
155
168
 
@@ -47,6 +47,13 @@ internal record NuGetContext : IDisposable
47
47
 
48
48
  private readonly Dictionary<PackageIdentity, string?> _packageInfoUrlCache = new();
49
49
 
50
+ public static string[] GetPackageSourceUrls(string currentDirectory)
51
+ {
52
+ using var context = new NuGetContext(currentDirectory);
53
+ var sourceUrls = context.PackageSources.Select(s => s.Source).ToArray();
54
+ return sourceUrls;
55
+ }
56
+
50
57
  public async Task<string?> GetPackageInfoUrlAsync(string packageId, string packageVersion, CancellationToken cancellationToken)
51
58
  {
52
59
  var packageIdentity = new PackageIdentity(packageId, NuGetVersion.Parse(packageVersion));
@@ -86,15 +93,24 @@ internal record NuGetContext : IDisposable
86
93
  continue;
87
94
  }
88
95
 
89
- var existsInFeed = await feed.Exists(
90
- packageIdentity,
91
- includeUnlisted: false,
92
- SourceCacheContext,
93
- NullLogger.Instance,
94
- cancellationToken);
95
- if (!existsInFeed)
96
+ try
97
+ {
98
+ // a non-compliant v2 API returning 404 can cause this to throw
99
+ var existsInFeed = await feed.Exists(
100
+ packageIdentity,
101
+ includeUnlisted: false,
102
+ SourceCacheContext,
103
+ NullLogger.Instance,
104
+ cancellationToken);
105
+ if (!existsInFeed)
106
+ {
107
+ message.AppendLine($" package {packageIdentity} does not exist in {source.Name}");
108
+ continue;
109
+ }
110
+ }
111
+ catch (FatalProtocolException)
96
112
  {
97
- message.AppendLine($" package {packageIdentity} does not exist in {source.Name}");
113
+ // if anything goes wrong here, the package source obviously doesn't contain the requested package
98
114
  continue;
99
115
  }
100
116
 
@@ -66,15 +66,24 @@ internal static class VersionFinder
66
66
  continue;
67
67
  }
68
68
 
69
- var existsInFeed = await feed.Exists(
70
- packageId,
71
- includePrerelease,
72
- includeUnlisted: false,
73
- nugetContext.SourceCacheContext,
74
- NullLogger.Instance,
75
- cancellationToken);
76
- if (!existsInFeed)
69
+ try
70
+ {
71
+ // a non-compliant v2 API returning 404 can cause this to throw
72
+ var existsInFeed = await feed.Exists(
73
+ packageId,
74
+ includePrerelease,
75
+ includeUnlisted: false,
76
+ nugetContext.SourceCacheContext,
77
+ NullLogger.Instance,
78
+ cancellationToken);
79
+ if (!existsInFeed)
80
+ {
81
+ continue;
82
+ }
83
+ }
84
+ catch (FatalProtocolException)
77
85
  {
86
+ // if anything goes wrong here, the package source obviously doesn't contain the requested package
78
87
  continue;
79
88
  }
80
89
 
@@ -162,15 +171,23 @@ internal static class VersionFinder
162
171
  continue;
163
172
  }
164
173
 
165
- var existsInFeed = await feed.Exists(
166
- new PackageIdentity(packageId, version),
167
- includeUnlisted: false,
168
- nugetContext.SourceCacheContext,
169
- NullLogger.Instance,
170
- cancellationToken);
171
- if (existsInFeed)
174
+ try
175
+ {
176
+ // a non-compliant v2 API returning 404 can cause this to throw
177
+ var existsInFeed = await feed.Exists(
178
+ new PackageIdentity(packageId, version),
179
+ includeUnlisted: false,
180
+ nugetContext.SourceCacheContext,
181
+ NullLogger.Instance,
182
+ cancellationToken);
183
+ if (existsInFeed)
184
+ {
185
+ return true;
186
+ }
187
+ }
188
+ catch (FatalProtocolException)
172
189
  {
173
- return true;
190
+ // if anything goes wrong here, the package source obviously doesn't contain the requested package
174
191
  }
175
192
  }
176
193
 
@@ -1,4 +1,5 @@
1
1
  using System.Collections.Immutable;
2
+ using System.Net;
2
3
  using System.Text.Json;
3
4
  using System.Text.Json.Serialization;
4
5
 
@@ -6,6 +7,7 @@ using Microsoft.Build.Construction;
6
7
  using Microsoft.Build.Definition;
7
8
  using Microsoft.Build.Evaluation;
8
9
 
10
+ using NuGetUpdater.Core.Analyze;
9
11
  using NuGetUpdater.Core.Utilities;
10
12
 
11
13
  namespace NuGetUpdater.Core.Discover;
@@ -47,43 +49,60 @@ public partial class DiscoveryWorker
47
49
  DirectoryPackagesPropsDiscoveryResult? directoryPackagesPropsDiscovery = null;
48
50
 
49
51
  ImmutableArray<ProjectDiscoveryResult> projectResults = [];
52
+ WorkspaceDiscoveryResult result;
50
53
 
51
- if (Directory.Exists(workspacePath))
54
+ try
52
55
  {
53
- _logger.Log($"Discovering build files in workspace [{workspacePath}].");
56
+ if (Directory.Exists(workspacePath))
57
+ {
58
+ _logger.Log($"Discovering build files in workspace [{workspacePath}].");
54
59
 
55
- dotNetToolsJsonDiscovery = DotNetToolsJsonDiscovery.Discover(repoRootPath, workspacePath, _logger);
56
- globalJsonDiscovery = GlobalJsonDiscovery.Discover(repoRootPath, workspacePath, _logger);
60
+ dotNetToolsJsonDiscovery = DotNetToolsJsonDiscovery.Discover(repoRootPath, workspacePath, _logger);
61
+ globalJsonDiscovery = GlobalJsonDiscovery.Discover(repoRootPath, workspacePath, _logger);
57
62
 
58
- if (globalJsonDiscovery is not null)
59
- {
60
- await TryRestoreMSBuildSdksAsync(repoRootPath, workspacePath, globalJsonDiscovery.Dependencies, _logger);
61
- }
63
+ if (globalJsonDiscovery is not null)
64
+ {
65
+ await TryRestoreMSBuildSdksAsync(repoRootPath, workspacePath, globalJsonDiscovery.Dependencies, _logger);
66
+ }
62
67
 
63
- projectResults = await RunForDirectoryAsnyc(repoRootPath, workspacePath);
68
+ // this next line should throw or something
69
+ projectResults = await RunForDirectoryAsnyc(repoRootPath, workspacePath);
64
70
 
65
- directoryPackagesPropsDiscovery = DirectoryPackagesPropsDiscovery.Discover(repoRootPath, workspacePath, projectResults, _logger);
71
+ directoryPackagesPropsDiscovery = DirectoryPackagesPropsDiscovery.Discover(repoRootPath, workspacePath, projectResults, _logger);
66
72
 
67
- if (directoryPackagesPropsDiscovery is not null)
73
+ if (directoryPackagesPropsDiscovery is not null)
74
+ {
75
+ projectResults = projectResults.Remove(projectResults.First(p => p.FilePath.Equals(directoryPackagesPropsDiscovery.FilePath, StringComparison.OrdinalIgnoreCase)));
76
+ }
77
+ }
78
+ else
68
79
  {
69
- projectResults = projectResults.Remove(projectResults.First(p => p.FilePath.Equals(directoryPackagesPropsDiscovery.FilePath, StringComparison.OrdinalIgnoreCase)));
80
+ _logger.Log($"Workspace path [{workspacePath}] does not exist.");
70
81
  }
82
+
83
+ result = new WorkspaceDiscoveryResult
84
+ {
85
+ Path = initialWorkspacePath,
86
+ DotNetToolsJson = dotNetToolsJsonDiscovery,
87
+ GlobalJson = globalJsonDiscovery,
88
+ DirectoryPackagesProps = directoryPackagesPropsDiscovery,
89
+ Projects = projectResults.OrderBy(p => p.FilePath).ToImmutableArray(),
90
+ };
71
91
  }
72
- else
92
+ catch (HttpRequestException ex)
93
+ when (ex.StatusCode == HttpStatusCode.Unauthorized || ex.StatusCode == HttpStatusCode.Forbidden)
73
94
  {
74
- _logger.Log($"Workspace path [{workspacePath}] does not exist.");
95
+ // TODO: consolidate this error handling between AnalyzeWorker, DiscoveryWorker, and UpdateWorker
96
+ result = new WorkspaceDiscoveryResult
97
+ {
98
+ ErrorType = ErrorType.AuthenticationFailure,
99
+ ErrorDetails = "(" + string.Join("|", NuGetContext.GetPackageSourceUrls(workspacePath)) + ")",
100
+ Path = initialWorkspacePath,
101
+ Projects = [],
102
+ };
75
103
  }
76
104
 
77
- var result = new WorkspaceDiscoveryResult
78
- {
79
- Path = initialWorkspacePath,
80
- DotNetToolsJson = dotNetToolsJsonDiscovery,
81
- GlobalJson = globalJsonDiscovery,
82
- DirectoryPackagesProps = directoryPackagesPropsDiscovery,
83
- Projects = projectResults.OrderBy(p => p.FilePath).ToImmutableArray(),
84
- };
85
-
86
- await WriteResults(repoRootPath, outputPath, result);
105
+ await WriteResultsAsync(repoRootPath, outputPath, result);
87
106
 
88
107
  _logger.Log("Discovery complete.");
89
108
 
@@ -293,7 +312,7 @@ public partial class DiscoveryWorker
293
312
  return [.. results.Values];
294
313
  }
295
314
 
296
- private static async Task WriteResults(string repoRootPath, string outputPath, WorkspaceDiscoveryResult result)
315
+ internal static async Task WriteResultsAsync(string repoRootPath, string outputPath, WorkspaceDiscoveryResult result)
297
316
  {
298
317
  var resultPath = Path.IsPathRooted(outputPath)
299
318
  ? outputPath
@@ -2,7 +2,7 @@ using System.Collections.Immutable;
2
2
 
3
3
  namespace NuGetUpdater.Core.Discover;
4
4
 
5
- public sealed record WorkspaceDiscoveryResult
5
+ public sealed record WorkspaceDiscoveryResult : NativeResult
6
6
  {
7
7
  public required string Path { get; init; }
8
8
  public bool IsSuccess { get; init; } = true;
@@ -0,0 +1,8 @@
1
+ namespace NuGetUpdater.Core;
2
+
3
+ public enum ErrorType
4
+ {
5
+ // TODO: add `Unknown` option to track all other failure types
6
+ None,
7
+ AuthenticationFailure,
8
+ }
@@ -93,11 +93,11 @@ internal sealed class ProjectBuildFile : XmlBuildFile
93
93
  {
94
94
  var isUpdate = false;
95
95
 
96
- var name = element.GetAttributeOrSubElementValue("Include", StringComparison.OrdinalIgnoreCase);
96
+ var name = element.GetAttributeOrSubElementValue("Include", StringComparison.OrdinalIgnoreCase)?.Trim();
97
97
  if (name is null)
98
98
  {
99
99
  isUpdate = true;
100
- name = element.GetAttributeOrSubElementValue("Update", StringComparison.OrdinalIgnoreCase);
100
+ name = element.GetAttributeOrSubElementValue("Update", StringComparison.OrdinalIgnoreCase)?.Trim();
101
101
  }
102
102
 
103
103
  if (name is null || name.StartsWith("@("))
@@ -0,0 +1,8 @@
1
+ namespace NuGetUpdater.Core;
2
+
3
+ public record NativeResult
4
+ {
5
+ // TODO: nullable not required, `ErrorType.None` is the default anyway
6
+ public ErrorType? ErrorType { get; init; }
7
+ public string? ErrorDetails { get; init; }
8
+ }
@@ -1,3 +1,4 @@
1
+ using System.Diagnostics;
1
2
  using System.Text;
2
3
  using System.Xml.Linq;
3
4
  using System.Xml.XPath;
@@ -102,9 +103,9 @@ internal static class PackagesConfigUpdater
102
103
  Console.SetError(writer);
103
104
 
104
105
  var currentDir = Environment.CurrentDirectory;
106
+ var existingSpawnedProcesses = GetLikelyNuGetSpawnedProcesses();
105
107
  try
106
108
  {
107
-
108
109
  Environment.CurrentDirectory = projectDirectory;
109
110
  var retryingAfterRestore = false;
110
111
 
@@ -144,6 +145,7 @@ internal static class PackagesConfigUpdater
144
145
  goto doRestore;
145
146
  }
146
147
 
148
+ MSBuildHelper.ThrowOnUnauthenticatedFeed(fullOutput);
147
149
  throw new Exception(fullOutput);
148
150
  }
149
151
  }
@@ -157,9 +159,24 @@ internal static class PackagesConfigUpdater
157
159
  Environment.CurrentDirectory = currentDir;
158
160
  Console.SetOut(originalOut);
159
161
  Console.SetError(originalError);
162
+
163
+ // NuGet.exe can spawn processes that hold on to the temporary directory, so we need to kill them
164
+ var currentSpawnedProcesses = GetLikelyNuGetSpawnedProcesses();
165
+ var deltaSpawnedProcesses = currentSpawnedProcesses.Except(existingSpawnedProcesses).ToArray();
166
+ foreach (var credProvider in deltaSpawnedProcesses)
167
+ {
168
+ logger.Log($"Ending spawned credential provider process");
169
+ credProvider.Kill();
170
+ }
160
171
  }
161
172
  }
162
173
 
174
+ private static Process[] GetLikelyNuGetSpawnedProcesses()
175
+ {
176
+ var processes = Process.GetProcesses().Where(p => p.ProcessName.StartsWith("CredentialProvider", StringComparison.OrdinalIgnoreCase) == true).ToArray();
177
+ return processes;
178
+ }
179
+
163
180
  internal static string? GetPathToPackagesDirectory(ProjectBuildFile projectBuildFile, string dependencyName, string dependencyVersion, string packagesConfigPath)
164
181
  {
165
182
  // the packages directory can be found from the hint path of the matching dependency, e.g., when given "Newtonsoft.Json", "7.0.1", and a project like this:
@@ -228,6 +228,7 @@ internal static class SdkPackageUpdater
228
228
 
229
229
  // see https://learn.microsoft.com/nuget/consume-packages/install-use-packages-dotnet-cli
230
230
  var (exitCode, stdout, stderr) = await ProcessEx.RunAsync("dotnet", $"add {projectPath} package {dependencyName} --version {newDependencyVersion}", workingDirectory: Path.GetDirectoryName(projectPath));
231
+ MSBuildHelper.ThrowOnUnauthenticatedFeed(stdout);
231
232
  if (exitCode != 0)
232
233
  {
233
234
  logger.Log($" Transitive dependency [{dependencyName}/{newDependencyVersion}] was not added.\nSTDOUT:\n{stdout}\nSTDERR:\n{stderr}");
@@ -594,7 +595,7 @@ internal static class SdkPackageUpdater
594
595
  string packageName)
595
596
  => buildFile.PackageItemNodes.Where(e =>
596
597
  string.Equals(
597
- e.GetAttributeOrSubElementValue("Include", StringComparison.OrdinalIgnoreCase) ?? e.GetAttributeOrSubElementValue("Update", StringComparison.OrdinalIgnoreCase),
598
+ (e.GetAttributeOrSubElementValue("Include", StringComparison.OrdinalIgnoreCase) ?? e.GetAttributeOrSubElementValue("Update", StringComparison.OrdinalIgnoreCase))?.Trim(),
598
599
  packageName,
599
600
  StringComparison.OrdinalIgnoreCase) &&
600
601
  (e.GetAttributeOrSubElementValue("Version", StringComparison.OrdinalIgnoreCase) ?? e.GetAttributeOrSubElementValue("VersionOverride", StringComparison.OrdinalIgnoreCase)) is not null);
@@ -0,0 +1,5 @@
1
+ namespace NuGetUpdater.Core.Updater;
2
+
3
+ public record UpdateOperationResult : NativeResult
4
+ {
5
+ }