dependabot-nuget 0.400.0 → 0.401.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a29d38c1ed416c5fc9c810f3815f241789054754ff92ac4982bf65e9ef221488
4
- data.tar.gz: 75c1ce1d121fc582d1ad2edbd1e3a35bb79e6955501984585643e455a85e4934
3
+ metadata.gz: 1aacd71e67c731e943f8d2ccb69697af27574e92eea1863d2abfc5c0ab2e8021
4
+ data.tar.gz: bb4d30f0ca2f0470f1aa45ff2d077fc934e79b86a709efd99a0bf4ed3a435c7a
5
5
  SHA512:
6
- metadata.gz: 525d56946331f53dabd8705aa5a903b2489b89e2ebf40a5cc3b514dbf4c28d93633e09a46330d8d0b0509e7feb2439a988a408dd4f01e5ce71cf4b4c6a7cbb41
7
- data.tar.gz: 2bdbf0b8a2c5302646e50fa666a14b91567dfecc96713472a419f15407c1c7a7628470adec534c50edf342ac4f3356f90840bf1d8c65526f24660d6b005fd9c1
6
+ metadata.gz: 4e5815e70d41634f1cce453b0f3309b6b601da2fbf177e5d7147fedaa71b458c2c17cd670e0f97a96e789d12653fdfe81a22fc98ccb9c1154d0d9ebac4244bac
7
+ data.tar.gz: ca65ed03d6d0440e4ac70d537a41caf0ad25438d6e716d2d3e3cbeee969a8071623bf264e4b7befb7fb19308b25c479d1bfb23dc7dbee425cdb8b96c0ed593d6
@@ -4,6 +4,7 @@ using System.Text.Json;
4
4
  using System.Text.Json.Serialization;
5
5
 
6
6
  using NuGet.Frameworks;
7
+ using NuGet.Packaging.Core;
7
8
  using NuGet.Versioning;
8
9
 
9
10
  using NuGetUpdater.Core.Discover;
@@ -258,20 +259,21 @@ public partial class AnalyzeWorker : IAnalyzeWorker
258
259
  Func<NuGetVersion, Task<bool>>? candidateValidator,
259
260
  CancellationToken cancellationToken)
260
261
  {
261
- var versionResult = await VersionFinder.GetVersionsAsync(
262
- projectFrameworks,
262
+ var currentTime = DateTimeOffset.UtcNow;
263
+ var versionResult = await VersionFinder.GetCandidateVersionsAsync(
263
264
  dependencyInfo,
264
- DateTimeOffset.UtcNow,
265
+ currentTime,
265
266
  nugetContext,
266
267
  logger,
267
268
  cancellationToken);
268
269
 
269
270
  return await FindUpdatedVersionAsync(
270
271
  packageIds,
271
- dependencyInfo.Version,
272
+ dependencyInfo,
272
273
  versionResult,
273
274
  projectFrameworks,
274
275
  findLowestVersion: dependencyInfo.IsVulnerable,
276
+ currentTime,
275
277
  nugetContext,
276
278
  logger,
277
279
  candidateValidator,
@@ -280,10 +282,11 @@ public partial class AnalyzeWorker : IAnalyzeWorker
280
282
 
281
283
  internal static async Task<NuGetVersion?> FindUpdatedVersionAsync(
282
284
  ImmutableHashSet<string> packageIds,
283
- string versionString,
285
+ DependencyInfo dependencyInfo,
284
286
  VersionResult versionResult,
285
287
  ImmutableArray<NuGetFramework> projectFrameworks,
286
288
  bool findLowestVersion,
289
+ DateTimeOffset currentTime,
287
290
  NuGetContext nugetContext,
288
291
  ILogger logger,
289
292
  Func<NuGetVersion, Task<bool>>? candidateValidator,
@@ -302,10 +305,11 @@ public partial class AnalyzeWorker : IAnalyzeWorker
302
305
 
303
306
  return await FindFirstCompatibleVersion(
304
307
  packageIds,
305
- versionString,
308
+ dependencyInfo,
306
309
  versionResult,
307
310
  orderedVersions,
308
311
  projectFrameworks,
312
+ currentTime,
309
313
  nugetContext,
310
314
  logger,
311
315
  candidateValidator,
@@ -314,58 +318,70 @@ public partial class AnalyzeWorker : IAnalyzeWorker
314
318
 
315
319
  internal static async Task<NuGetVersion?> FindFirstCompatibleVersion(
316
320
  ImmutableHashSet<string> packageIds,
317
- string versionString,
321
+ DependencyInfo dependencyInfo,
318
322
  VersionResult versionResult,
319
323
  IEnumerable<NuGetVersion> orderedVersions,
320
324
  ImmutableArray<NuGetFramework> projectFrameworks,
325
+ DateTimeOffset currentTime,
321
326
  NuGetContext nugetContext,
322
327
  ILogger logger,
323
328
  Func<NuGetVersion, Task<bool>>? candidateValidator,
324
329
  CancellationToken cancellationToken)
325
330
  {
326
- if (NuGetVersion.TryParse(versionString, out var currentVersion))
331
+ var checkSiblingCompatibility = true;
332
+ if (NuGetVersion.TryParse(dependencyInfo.Version, out var currentVersion))
327
333
  {
328
- var isCompatible = await AreAllPackagesCompatibleAsync(
334
+ checkSiblingCompatibility = await AreAllPackagesCompatibleAsync(
329
335
  packageIds,
330
336
  currentVersion,
331
337
  projectFrameworks,
332
338
  nugetContext,
333
339
  logger,
334
340
  cancellationToken);
335
-
336
- if (!isCompatible)
337
- {
338
- // If the current package is incompatible, then don't check for compatibility.
339
- foreach (var version in orderedVersions)
340
- {
341
- if (candidateValidator is null || await candidateValidator(version))
342
- {
343
- return version;
344
- }
345
- }
346
-
347
- return null;
348
- }
349
341
  }
350
342
 
351
343
  foreach (var version in orderedVersions)
352
344
  {
353
- var existsForAll = await VersionFinder.DoVersionsExistAsync(packageIds, version, nugetContext, logger, cancellationToken);
354
- if (!existsForAll)
345
+ var primaryIdentity = new PackageIdentity(dependencyInfo.Name, version);
346
+ if (!await CompatibilityChecker.IsAvailableAndCompatibleAsync(
347
+ primaryIdentity,
348
+ projectFrameworks,
349
+ nugetContext,
350
+ logger,
351
+ cancellationToken))
355
352
  {
356
353
  continue;
357
354
  }
358
355
 
359
- var isCompatible = await AreAllPackagesCompatibleAsync(
360
- packageIds,
356
+ if (!await AreSiblingPackagesAcceptableAsync(
357
+ dependencyInfo.Name,
358
+ packageIds,
359
+ version,
360
+ projectFrameworks,
361
+ checkSiblingCompatibility,
362
+ nugetContext,
363
+ logger,
364
+ cancellationToken))
365
+ {
366
+ continue;
367
+ }
368
+
369
+ var allowedSources = await VersionFinder.GetCooldownAllowedSourcesAsync(
370
+ dependencyInfo,
371
+ currentTime,
372
+ versionResult.CurrentVersion,
361
373
  version,
362
- projectFrameworks,
374
+ versionResult.GetPackageSources(version),
363
375
  nugetContext,
364
376
  logger,
365
377
  cancellationToken);
378
+ if (allowedSources.IsEmpty)
379
+ {
380
+ continue;
381
+ }
366
382
 
367
- if (isCompatible &&
368
- (candidateValidator is null || await candidateValidator(version)))
383
+ versionResult.SetPackageSources(version, allowedSources);
384
+ if (candidateValidator is null || await candidateValidator(version))
369
385
  {
370
386
  return version;
371
387
  }
@@ -385,7 +401,7 @@ public partial class AnalyzeWorker : IAnalyzeWorker
385
401
  {
386
402
  foreach (var packageId in packageIds)
387
403
  {
388
- var isCompatible = await CompatibilityChecker.CheckAsync(
404
+ var isCompatible = await CompatibilityChecker.ExistsAndIsCompatibleAsync(
389
405
  new(packageId, currentVersion),
390
406
  projectFrameworks,
391
407
  nugetContext,
@@ -400,6 +416,40 @@ public partial class AnalyzeWorker : IAnalyzeWorker
400
416
  return true;
401
417
  }
402
418
 
419
+ internal static async Task<bool> AreSiblingPackagesAcceptableAsync(
420
+ string primaryPackageId,
421
+ ImmutableHashSet<string> packageIds,
422
+ NuGetVersion version,
423
+ ImmutableArray<NuGetFramework> projectFrameworks,
424
+ bool checkCompatibility,
425
+ NuGetContext nugetContext,
426
+ ILogger logger,
427
+ CancellationToken cancellationToken)
428
+ {
429
+ foreach (var packageId in packageIds.Where(
430
+ packageId => !packageId.Equals(primaryPackageId, StringComparison.OrdinalIgnoreCase)))
431
+ {
432
+ var packageIdentity = new PackageIdentity(packageId, version);
433
+ var isAcceptable = checkCompatibility
434
+ ? await CompatibilityChecker.IsAvailableAndCompatibleAsync(
435
+ packageIdentity,
436
+ projectFrameworks,
437
+ nugetContext,
438
+ logger,
439
+ cancellationToken)
440
+ : await CompatibilityChecker.ExistsAsync(
441
+ packageIdentity,
442
+ nugetContext,
443
+ cancellationToken);
444
+ if (!isAcceptable)
445
+ {
446
+ return false;
447
+ }
448
+ }
449
+
450
+ return true;
451
+ }
452
+
403
453
  internal static async Task<ImmutableArray<Dependency>> FindUpdatedDependenciesAsync(
404
454
  string repoRoot,
405
455
  WorkspaceDiscoveryResult discovery,
@@ -0,0 +1,403 @@
1
+ using System.Collections.Immutable;
2
+
3
+ using NuGet.Common;
4
+ using NuGet.Configuration;
5
+ using NuGet.Frameworks;
6
+ using NuGet.Packaging;
7
+ using NuGet.Packaging.Core;
8
+ using NuGet.Protocol;
9
+ using NuGet.Protocol.Core.Types;
10
+
11
+ using NuGetUpdater.Core.FrameworkChecker;
12
+
13
+ namespace NuGetUpdater.Core.Analyze;
14
+
15
+ using PackageInfo = (bool IsDevDependency, ImmutableArray<NuGetFramework> Frameworks);
16
+ using PackageReaders = (IAsyncPackageCoreReader CoreReader, IAsyncPackageContentReader ContentReader);
17
+
18
+ internal static class CompatibilityChecker
19
+ {
20
+ public static async Task<bool> CheckAsync(
21
+ PackageIdentity package,
22
+ ImmutableArray<NuGetFramework> projectFrameworks,
23
+ NuGetContext nugetContext,
24
+ ILogger logger,
25
+ CancellationToken cancellationToken)
26
+ {
27
+ var packageInfo = await GetPackageInfoAsync(
28
+ package,
29
+ nugetContext,
30
+ cancellationToken);
31
+ if (packageInfo is null)
32
+ {
33
+ return false;
34
+ }
35
+
36
+ var (isDevDependency, packageFrameworks) = packageInfo.GetValueOrDefault();
37
+ return PerformCheck(package, projectFrameworks, isDevDependency, packageFrameworks, logger);
38
+ }
39
+
40
+ public static async Task<bool> ExistsAndIsCompatibleAsync(
41
+ PackageIdentity package,
42
+ ImmutableArray<NuGetFramework> projectFrameworks,
43
+ NuGetContext nugetContext,
44
+ ILogger logger,
45
+ CancellationToken cancellationToken)
46
+ {
47
+ var packageInfo = await GetPackageInfoAsync(
48
+ package,
49
+ nugetContext,
50
+ cancellationToken);
51
+ if (packageInfo is null)
52
+ {
53
+ return false;
54
+ }
55
+
56
+ // dotnet-tools.json and global.json packages won't specify a TFM, so existence is sufficient
57
+ if (projectFrameworks.IsEmpty)
58
+ {
59
+ return true;
60
+ }
61
+
62
+ var (isDevDependency, packageFrameworks) = packageInfo.GetValueOrDefault();
63
+ return PerformCheck(package, projectFrameworks, isDevDependency, packageFrameworks, logger);
64
+ }
65
+
66
+ public static async Task<bool> IsAvailableAndCompatibleAsync(
67
+ PackageIdentity package,
68
+ ImmutableArray<NuGetFramework> projectFrameworks,
69
+ NuGetContext nugetContext,
70
+ ILogger logger,
71
+ CancellationToken cancellationToken)
72
+ {
73
+ var packageInfo = await GetPackageInfoAsync(
74
+ package,
75
+ nugetContext,
76
+ requireSourceAvailability: true,
77
+ cancellationToken);
78
+ if (packageInfo is null)
79
+ {
80
+ return false;
81
+ }
82
+
83
+ if (projectFrameworks.IsEmpty)
84
+ {
85
+ return true;
86
+ }
87
+
88
+ var (isDevDependency, packageFrameworks) = packageInfo.GetValueOrDefault();
89
+ return PerformCheck(package, projectFrameworks, isDevDependency, packageFrameworks, logger);
90
+ }
91
+
92
+ public static async Task<bool> ExistsAsync(
93
+ PackageIdentity package,
94
+ NuGetContext nugetContext,
95
+ CancellationToken cancellationToken)
96
+ {
97
+ var readersOption = await GetPackageReadersAsync(
98
+ package,
99
+ nugetContext,
100
+ requireSourceAvailability: true,
101
+ cancellationToken);
102
+ if (readersOption is null)
103
+ {
104
+ return false;
105
+ }
106
+
107
+ var readers = readersOption.GetValueOrDefault();
108
+ DisposeReaders(readers);
109
+ return true;
110
+ }
111
+
112
+ internal static bool PerformCheck(
113
+ PackageIdentity package,
114
+ ImmutableArray<NuGetFramework> projectFrameworks,
115
+ bool isDevDependency,
116
+ ImmutableArray<NuGetFramework> packageFrameworks,
117
+ ILogger logger)
118
+ {
119
+ // development dependencies are packages such as analyzers which need to be compatible with the compiler not the
120
+ // project itself, but some packages that report themselves as development dependencies still contain target
121
+ // framework dependencies and should be checked for compatibility through the regular means
122
+ if (isDevDependency && packageFrameworks.Length == 0)
123
+ {
124
+ return true;
125
+ }
126
+
127
+ if (packageFrameworks.Length == 0 || projectFrameworks.Length == 0)
128
+ {
129
+ return false;
130
+ }
131
+
132
+ var compatibilityService = new FrameworkCompatibilityService();
133
+ var compatibleFrameworks = compatibilityService.GetCompatibleFrameworks(packageFrameworks);
134
+ var packageSupportsAny = compatibleFrameworks.Any(f => f.IsAny);
135
+ if (packageSupportsAny)
136
+ {
137
+ return true;
138
+ }
139
+
140
+ var incompatibleFrameworks = projectFrameworks.Where(f => !compatibleFrameworks.Contains(f)).ToArray();
141
+ if (incompatibleFrameworks.Length > 0)
142
+ {
143
+ logger.Info($"The package {package} is not compatible. Incompatible project frameworks: {string.Join(", ", incompatibleFrameworks.Select(f => f.GetShortFolderName()))}");
144
+ return false;
145
+ }
146
+
147
+ return true;
148
+ }
149
+
150
+ internal static async Task<PackageReaders?> GetPackageReadersAsync(
151
+ PackageIdentity package,
152
+ NuGetContext nugetContext,
153
+ CancellationToken cancellationToken)
154
+ {
155
+ return await GetPackageReadersAsync(
156
+ package,
157
+ nugetContext,
158
+ requireSourceAvailability: false,
159
+ cancellationToken);
160
+ }
161
+
162
+ private static async Task<PackageReaders?> GetPackageReadersAsync(
163
+ PackageIdentity package,
164
+ NuGetContext nugetContext,
165
+ bool requireSourceAvailability,
166
+ CancellationToken cancellationToken)
167
+ {
168
+ var packagePath = GetPackagePath(package, nugetContext);
169
+ if (!requireSourceAvailability && File.Exists(packagePath))
170
+ {
171
+ return ReadPackage(packagePath);
172
+ }
173
+
174
+ if (File.Exists(packagePath))
175
+ {
176
+ return await IsAvailableAsync(package, nugetContext, cancellationToken)
177
+ ? ReadPackage(packagePath)
178
+ : null;
179
+ }
180
+
181
+ return await DownloadPackageAsync(package, nugetContext, cancellationToken);
182
+ }
183
+
184
+ internal static async Task<PackageInfo?> GetPackageInfoAsync(
185
+ PackageIdentity package,
186
+ NuGetContext nugetContext,
187
+ CancellationToken cancellationToken)
188
+ {
189
+ var readersOption = await GetPackageReadersAsync(package, nugetContext, cancellationToken);
190
+ return await ReadPackageInfoAsync(readersOption, cancellationToken);
191
+ }
192
+
193
+ private static async Task<PackageInfo?> GetPackageInfoAsync(
194
+ PackageIdentity package,
195
+ NuGetContext nugetContext,
196
+ bool requireSourceAvailability,
197
+ CancellationToken cancellationToken)
198
+ {
199
+ var readersOption = await GetPackageReadersAsync(
200
+ package,
201
+ nugetContext,
202
+ requireSourceAvailability,
203
+ cancellationToken);
204
+ return await ReadPackageInfoAsync(readersOption, cancellationToken);
205
+ }
206
+
207
+ private static async Task<PackageInfo?> ReadPackageInfoAsync(
208
+ PackageReaders? readersOption,
209
+ CancellationToken cancellationToken)
210
+ {
211
+ if (readersOption is null)
212
+ {
213
+ return null;
214
+ }
215
+
216
+ var readers = readersOption.GetValueOrDefault();
217
+ try
218
+ {
219
+ var nuspecStream = await readers.CoreReader.GetNuspecAsync(cancellationToken);
220
+ var reader = new NuspecReader(nuspecStream);
221
+
222
+ var isDevDependency = reader.GetDevelopmentDependency();
223
+ var tfms = new HashSet<NuGetFramework>();
224
+ var dependencyGroups = reader.GetDependencyGroups().ToArray();
225
+
226
+ foreach (var d in dependencyGroups)
227
+ {
228
+ if (!d.TargetFramework.IsAny)
229
+ {
230
+ tfms.Add(d.TargetFramework);
231
+ }
232
+ }
233
+
234
+ var refItems = (await readers.ContentReader.GetReferenceItemsAsync(cancellationToken)).ToArray();
235
+ foreach (var refItem in refItems)
236
+ {
237
+ tfms.Add(refItem.TargetFramework);
238
+ }
239
+
240
+ var libItems = (await readers.ContentReader.GetLibItemsAsync(cancellationToken)).ToArray();
241
+ foreach (var libItem in libItems)
242
+ {
243
+ tfms.Add(libItem.TargetFramework);
244
+ }
245
+
246
+ // if a package contains no assemblies that need to be referenced, we can consider it compatible with all frameworks
247
+ if (refItems.Length == 0 && libItems.Length == 0)
248
+ {
249
+ tfms.Clear();
250
+ }
251
+
252
+ if (!tfms.Any())
253
+ {
254
+ tfms.Add(NuGetFramework.AnyFramework);
255
+ }
256
+
257
+ return (isDevDependency, tfms.ToImmutableArray());
258
+ }
259
+ finally
260
+ {
261
+ DisposeReaders(readers);
262
+ }
263
+ }
264
+
265
+ private static void DisposeReaders(PackageReaders readers)
266
+ {
267
+ // The interfaces aren't disposable, but their implementations can be. The same object often implements both.
268
+ var coreReader = readers.CoreReader as IDisposable;
269
+ var contentReader = readers.ContentReader as IDisposable;
270
+ try
271
+ {
272
+ coreReader?.Dispose();
273
+ }
274
+ finally
275
+ {
276
+ if (!ReferenceEquals(coreReader, contentReader))
277
+ {
278
+ contentReader?.Dispose();
279
+ }
280
+ }
281
+ }
282
+
283
+ internal static PackageReaders ReadPackage(string packagePath)
284
+ {
285
+ var stream = new FileStream(
286
+ packagePath,
287
+ FileMode.Open,
288
+ FileAccess.Read,
289
+ FileShare.Read,
290
+ bufferSize: 4096);
291
+ PackageArchiveReader archiveReader = new(stream);
292
+ return (archiveReader, archiveReader);
293
+ }
294
+
295
+ internal static async Task<PackageReaders?> DownloadPackageAsync(
296
+ PackageIdentity package,
297
+ NuGetContext context,
298
+ CancellationToken cancellationToken)
299
+ {
300
+ foreach (var source in GetAllowedSources(package.Id, context))
301
+ {
302
+ FindPackageByIdResource feed;
303
+ try
304
+ {
305
+ var sourceRepository = context.GetSourceRepository(source);
306
+ feed = await sourceRepository.GetResourceAsync<FindPackageByIdResource>(cancellationToken)
307
+ ?? throw new NotSupportedException($"Failed to get FindPackageByIdResource for {source.SourceUri}");
308
+
309
+ // a non-compliant v2 API returning 404 can cause this to throw
310
+ var exists = await feed.DoesPackageExistAsync(
311
+ package.Id,
312
+ package.Version,
313
+ context.SourceCacheContext,
314
+ NullLogger.Instance,
315
+ cancellationToken);
316
+ if (!exists)
317
+ {
318
+ continue;
319
+ }
320
+ }
321
+ catch (FatalProtocolException)
322
+ {
323
+ // if anything goes wrong here, the package source obviously doesn't contain the requested package
324
+ continue;
325
+ }
326
+
327
+ var downloader = await feed.GetPackageDownloaderAsync(
328
+ package,
329
+ context.SourceCacheContext,
330
+ context.Logger,
331
+ cancellationToken);
332
+
333
+ var packagePath = GetPackagePath(package, context);
334
+ var isDownloaded = await downloader.CopyNupkgFileToAsync(packagePath, cancellationToken);
335
+ if (isDownloaded)
336
+ {
337
+ return (downloader.CoreReader, downloader.ContentReader);
338
+ }
339
+ }
340
+
341
+ return null;
342
+ }
343
+
344
+ private static async Task<bool> IsAvailableAsync(
345
+ PackageIdentity package,
346
+ NuGetContext context,
347
+ CancellationToken cancellationToken)
348
+ {
349
+ foreach (var source in GetAllowedSources(package.Id, context))
350
+ {
351
+ try
352
+ {
353
+ var sourceRepository = context.GetSourceRepository(source);
354
+ var feed = await sourceRepository.GetResourceAsync<FindPackageByIdResource>(cancellationToken)
355
+ ?? throw new NotSupportedException($"Failed to get FindPackageByIdResource for {source.SourceUri}");
356
+ if (await feed.DoesPackageExistAsync(
357
+ package.Id,
358
+ package.Version,
359
+ context.SourceCacheContext,
360
+ NullLogger.Instance,
361
+ cancellationToken))
362
+ {
363
+ return true;
364
+ }
365
+ }
366
+ catch (FatalProtocolException)
367
+ {
368
+ // Try the next allowed source.
369
+ }
370
+ }
371
+
372
+ return false;
373
+ }
374
+
375
+ private static ImmutableArray<PackageSource> GetAllowedSources(string packageId, NuGetContext context)
376
+ {
377
+ var sourceMapping = PackageSourceMapping.GetPackageSourceMapping(context.Settings);
378
+ var packageSources = sourceMapping.GetConfiguredPackageSources(packageId).ToHashSet();
379
+ return packageSources.Count == 0
380
+ ? context.PackageSources
381
+ : context.PackageSources
382
+ .Where(source => packageSources.Contains(source.Name))
383
+ .ToImmutableArray();
384
+ }
385
+
386
+ internal static string GetPackagePath(PackageIdentity package, NuGetContext context)
387
+ {
388
+ // https://learn.microsoft.com/en-us/nuget/consume-packages/managing-the-global-packages-and-cache-folders
389
+ var nugetPackagesPath = Environment.GetEnvironmentVariable("NUGET_PACKAGES");
390
+ if (nugetPackagesPath is null)
391
+ {
392
+ // n.b., this path should never be hit during a unit test
393
+ nugetPackagesPath = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
394
+ }
395
+
396
+ var normalizedName = package.Id.ToLowerInvariant();
397
+ var normalizedVersion = package.Version.ToNormalizedString().ToLowerInvariant();
398
+ var packageDirectory = Path.Join(nugetPackagesPath, normalizedName, normalizedVersion);
399
+ Directory.CreateDirectory(packageDirectory);
400
+ var packagePath = Path.Join(packageDirectory, $"{normalizedName}.{normalizedVersion}.nupkg");
401
+ return packagePath;
402
+ }
403
+ }