dependabot-nuget 0.265.0 → 0.266.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 (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
+ }