Skip to content

Commit 4351103

Browse files
committed
Rework support for @latest version
Currently, unpkg and jsdelivr support using 'latest' as a version. When files are requested by libman, they redirect (HTTP302) to the URL for the current latest files. We put those files into the cache like any other version (in a folder name 'latest') and all is well. However, when a new version is released, libman continues to use the now-stale downloaded assets from the cache. There isn't a signal to realize when to purge the latest version from the cache. This change instead simply turns 'latest' into the latest version we determine from the provider. There is a small risk that we are out of sync with the CDN: for example, since we get the version data from NPM, it might not match the version that e.g. jsdelivr redirects to. But this should be extremely rare. Doing it this way also allows cdnjs to support 'latest'. I didn't add it to the completion items, but I did verify that it works (CLI install, restore, and the file list in VS all support foo@latest for cdnjs now too).
1 parent 4589d14 commit 4351103

File tree

6 files changed

+47
-13
lines changed

6 files changed

+47
-13
lines changed

src/LibraryManager/ManifestConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,10 @@ public static class ManifestConstants
4848
/// libman.json files element
4949
/// </summary>
5050
public const string Files = "files";
51+
52+
/// <summary>
53+
/// For providers that support versioned libraries, this represents the evergreen latest version
54+
/// </summary>
55+
public const string LatestVersion = "latest";
5156
}
5257
}

src/LibraryManager/Providers/BaseProvider.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,18 @@ public virtual async Task<ILibraryOperationResult> UpdateStateAsync(ILibraryInst
9898
try
9999
{
100100
ILibraryCatalog catalog = GetCatalog();
101+
102+
if (string.Equals(desiredState.Version, ManifestConstants.LatestVersion, StringComparison.Ordinal))
103+
{
104+
// replace the @latest version with the latest version from the catalog. This redirect
105+
// ensures that as new versions are released, we will not reuse stale "latest" assets
106+
// from the cache.
107+
string latestVersion = await catalog.GetLatestVersion(libraryId, includePreReleases: false, cancellationToken).ConfigureAwait(false);
108+
LibraryInstallationState newState = LibraryInstallationState.FromInterface(desiredState);
109+
newState.Version = latestVersion;
110+
desiredState = newState;
111+
}
112+
101113
ILibrary library = await catalog.GetLibraryAsync(desiredState.Name, desiredState.Version, cancellationToken).ConfigureAwait(false);
102114

103115
if (library == null)

src/LibraryManager/Providers/Cdnjs/CdnjsCatalog.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ internal class CdnjsCatalog : ILibraryCatalog
2020
// TO DO: These should become Provider properties to be passed to CacheService
2121
private const string FileName = "cache.json";
2222
public const string CatalogUrl = "https://api.cdnjs.com/libraries?fields=name,description,version";
23-
public const string MetaPackageUrlFormat = "https://api.cdnjs.com/libraries/{0}?fields=filename,versions"; // {libraryName}
23+
public const string MetaPackageUrlFormat = "https://api.cdnjs.com/libraries/{0}?fields=filename,versions"; // {libraryName}
2424
public const string PackageVersionUrlFormat = "https://api.cdnjs.com/libraries/{0}/{1}?fields=files"; // {libraryName}/{version}
2525

2626
private readonly string _cacheFile;
@@ -150,7 +150,13 @@ public async Task<ILibrary> GetLibraryAsync(string libraryName, string version,
150150
throw new InvalidLibraryException(libraryId, _provider.Id);
151151
}
152152

153-
if (!(await GetLibraryVersionsAsync(libraryName, cancellationToken)).Contains(version))
153+
if (string.Equals(version, ManifestConstants.LatestVersion, StringComparison.Ordinal))
154+
{
155+
version = await GetLatestVersion(libraryName, includePreReleases: false, cancellationToken);
156+
}
157+
158+
IEnumerable<string> enumerable = await GetLibraryVersionsAsync(libraryName, cancellationToken);
159+
if (!enumerable.Contains(version))
154160
{
155161
throw new InvalidLibraryException(libraryId, _provider.Id);
156162
}
@@ -160,7 +166,7 @@ public async Task<ILibrary> GetLibraryAsync(string libraryName, string version,
160166
JObject groupMetadata = await GetLibraryGroupMetadataAsync(libraryName, cancellationToken);
161167
string defaultFile = groupMetadata?["filename"].Value<string>() ?? string.Empty;
162168

163-
IEnumerable<string> libraryFiles= await GetLibraryFilesAsync(libraryName, version, cancellationToken).ConfigureAwait(false);
169+
IEnumerable<string> libraryFiles = await GetLibraryFilesAsync(libraryName, version, cancellationToken).ConfigureAwait(false);
164170

165171
return new CdnjsLibrary
166172
{

src/LibraryManager/Providers/Unpkg/UnpkgCatalog.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ internal class UnpkgCatalog : ILibraryCatalog
2020
public const string CacheFileName = "cache.json";
2121
public const string LibraryFileListUrlFormat = "https://unpkg.com/{0}@{1}/?meta"; // e.g. https://unpkg.com/[email protected]/?meta
2222
public const string LatestLibraryVersonUrl = "https://unpkg.com/{0}/package.json"; // e.g. https://unpkg.com/jquery/package.json
23-
public const string LatestVersionTag = "latest";
2423

2524
private readonly INpmPackageInfoFactory _packageInfoFactory;
2625
private readonly INpmPackageSearch _packageSearch;
@@ -58,7 +57,7 @@ public async Task<string> GetLatestVersion(string libraryName, bool includePreRe
5857
try
5958
{
6059
string latestLibraryVersionUrl = string.Format(LatestLibraryVersonUrl, libraryName);
61-
string latestCacheFile = Path.Combine(_cacheFolder, libraryName, $"{LatestVersionTag}.json");
60+
string latestCacheFile = Path.Combine(_cacheFolder, libraryName, $"{ManifestConstants.LatestVersion}.json");
6261

6362
string latestJson = await _cacheService.GetContentsFromUriWithCacheFallbackAsync(latestLibraryVersionUrl,
6463
latestCacheFile,
@@ -88,7 +87,7 @@ public async Task<ILibrary> GetLibraryAsync(string libraryName, string version,
8887
}
8988

9089
string libraryId = _libraryNamingScheme.GetLibraryId(libraryName, version);
91-
if (string.Equals(version, LatestVersionTag, StringComparison.Ordinal))
90+
if (string.Equals(version, ManifestConstants.LatestVersion, StringComparison.Ordinal))
9291
{
9392
string latestVersion = await GetLatestVersion(libraryId, includePreReleases: false, cancellationToken).ConfigureAwait(false);
9493
libraryId = _libraryNamingScheme.GetLibraryId(libraryName, latestVersion);
@@ -263,8 +262,8 @@ public async Task<CompletionSet> GetLibraryCompletionSetAsync(string libraryName
263262
// support @latest version
264263
completions.Add(new CompletionItem
265264
{
266-
DisplayText = LatestVersionTag,
267-
InsertionText = _libraryNamingScheme.GetLibraryId(name, LatestVersionTag),
265+
DisplayText = ManifestConstants.LatestVersion,
266+
InsertionText = _libraryNamingScheme.GetLibraryId(name, ManifestConstants.LatestVersion),
268267
});
269268

270269
completionSet.CompletionType = CompletionSortOrder.Version;

src/LibraryManager/Providers/jsDelivr/JsDelivrCatalog.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ internal class JsDelivrCatalog : ILibraryCatalog
2323
public const string LatestLibraryVersionUrl = "https://data.jsdelivr.com/v1/package/npm/{0}";
2424
public const string LibraryFileListUrlFormatGH = "https://data.jsdelivr.com/v1/package/gh/{0}/flat";
2525
public const string LatestLibraryVersionUrlGH = "https://data.jsdelivr.com/v1/package/gh/{0}";
26-
public const string LatestVersionTag = "latest";
2726

2827
private readonly INpmPackageInfoFactory _packageInfoFactory;
2928
private readonly INpmPackageSearch _packageSearch;
@@ -61,7 +60,7 @@ public async Task<string> GetLatestVersion(string libraryId, bool includePreRele
6160
bool isGitHub = IsGitHub(libraryId);
6261
string latestLibraryVersionUrl = string.Format(isGitHub ? LatestLibraryVersionUrlGH : LatestLibraryVersionUrl, name);
6362
string cacheFileType = isGitHub ? "github" : "npm";
64-
string latestLibraryVersionCacheFile = Path.Combine(_cacheFolder, name, $"{cacheFileType}-{LatestVersionTag}.json");
63+
string latestLibraryVersionCacheFile = Path.Combine(_cacheFolder, name, $"{cacheFileType}-{ManifestConstants.LatestVersion}.json");
6564

6665
string latestVersionContent = await _cacheService.GetContentsFromUriWithCacheFallbackAsync(latestLibraryVersionUrl,
6766
latestLibraryVersionCacheFile,
@@ -116,7 +115,7 @@ public async Task<ILibrary> GetLibraryAsync(string name, string version, Cancell
116115
}
117116

118117
string libraryId = _libraryNamingScheme.GetLibraryId(name, version);
119-
if (string.Equals(version, LatestVersionTag, StringComparison.Ordinal))
118+
if (string.Equals(version, ManifestConstants.LatestVersion, StringComparison.Ordinal))
120119
{
121120
string latestVersion = await GetLatestVersion(libraryId, includePreReleases: false, cancellationToken).ConfigureAwait(false);
122121
libraryId = _libraryNamingScheme.GetLibraryId(name, latestVersion);
@@ -288,8 +287,8 @@ public async Task<CompletionSet> GetLibraryCompletionSetAsync(string libraryName
288287
// support @latest version
289288
completions.Add(new CompletionItem
290289
{
291-
DisplayText = LatestVersionTag,
292-
InsertionText = _libraryNamingScheme.GetLibraryId(name, LatestVersionTag),
290+
DisplayText = ManifestConstants.LatestVersion,
291+
InsertionText = _libraryNamingScheme.GetLibraryId(name, ManifestConstants.LatestVersion),
293292
});
294293

295294
completionSet.CompletionType = CompletionSortOrder.Version;

test/LibraryManager.Test/Providers/Cdnjs/CdnjsCatalogTest.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,19 @@ public async Task GetLibraryAsync_Success()
134134
Assert.IsNotNull(library.Files);
135135
}
136136

137+
[TestMethod]
138+
public async Task GetLibraryAsync_LatestVersion_Success()
139+
{
140+
CdnjsCatalog sut = SetupCatalog();
141+
142+
ILibrary library = await sut.GetLibraryAsync("sampleLibrary", "latest", CancellationToken.None);
143+
144+
Assert.IsNotNull(library);
145+
Assert.AreEqual("sampleLibrary", library.Name);
146+
Assert.AreEqual("3.1.4", library.Version);
147+
Assert.IsNotNull(library.Files);
148+
}
149+
137150
[TestMethod]
138151
public async Task GetLibraryAsync_InvalidLibraryId()
139152
{

0 commit comments

Comments
 (0)