UNPKG

@rushstack/lockfile-explorer

Version:

Rush Lockfile Explorer: The UI for solving version conflicts quickly in a large monorepo

113 lines 4.13 kB
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. import { homedir } from 'node:os'; import semver from 'semver'; import { JsonFile } from '@rushstack/node-core-library'; const REGISTRY_BASE_URL = 'https://registry.npmjs.org'; const FETCH_TIMEOUT_MS = 5000; const DEFAULT_CACHE_EXPIRY_MS = 24 * 60 * 60 * 1000; // 24 hours const CACHE_VERSION = 1; const CACHE_FOLDER = `${homedir()}/.rushstack/update-checks`; async function _tryFetchLatestVersionAsync(packageName) { const url = `${REGISTRY_BASE_URL}/${encodeURIComponent(packageName)}/latest`; const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS); try { const response = await fetch(url, { signal: controller.signal }); if (!response.ok) { return undefined; } const { version } = (await response.json()); return typeof version === 'string' ? version : undefined; } catch (_a) { // Network errors, timeouts, and parse failures are all silent. return undefined; } finally { clearTimeout(timeout); } } async function _readCacheAsync(filePath) { try { const data = await JsonFile.loadAsync(filePath); const { cacheVersion, ...rest } = data; if (cacheVersion === CACHE_VERSION) { return rest; } } catch (_a) { // Ignore } } async function _writeCacheAsync(filePath, cache) { try { const cacheData = { cacheVersion: CACHE_VERSION, checkedAt: Date.now(), ...cache }; await JsonFile.saveAsync(cacheData, filePath, { ensureFolderExists: true }); } catch (_a) { // Cache write failures are silent — a stale or missing cache just means // we'll re-fetch on the next invocation. } } /** * Checks npm for a newer version of a package and caches the result locally so that * the registry is not queried on every invocation. * * @internal */ export class PackageUpdateChecker { constructor(options) { const { packageName, currentVersion, skip = false, forceCheck = false, cacheExpiryMs = DEFAULT_CACHE_EXPIRY_MS } = options; this._packageName = packageName; this._currentVersion = currentVersion; this._skip = skip; this._forceCheck = forceCheck; this._cacheExpiryMs = cacheExpiryMs; } /** * Performs the update check and returns the result, or `undefined` if the check * was skipped or the registry could not be reached. */ async tryGetUpdateAsync() { if (this._skip) { return undefined; } const cacheFilePath = this._getCacheFilePath(); let latestVersion; if (!this._forceCheck) { const cached = await _readCacheAsync(cacheFilePath); if (cached !== undefined) { const { checkedAt, latestVersion: latestVersionFromCache } = cached; const ageMs = Date.now() - checkedAt; if (ageMs < this._cacheExpiryMs) { latestVersion = latestVersionFromCache; } } } if (latestVersion === undefined) { // Cache is missing or stale — fetch from the registry. latestVersion = await _tryFetchLatestVersionAsync(this._packageName); if (latestVersion === undefined) { return undefined; } await _writeCacheAsync(cacheFilePath, { latestVersion }); } return { latestVersion, isOutdated: semver.gt(latestVersion, this._currentVersion) }; } _getCacheFilePath() { // Replace characters that are unsafe in file names (e.g. the "/" in scoped package names). const sanitizedName = this._packageName.replace(/[^a-zA-Z0-9._-]/g, '_'); return `${CACHE_FOLDER}/${sanitizedName}.json`; } } //# sourceMappingURL=PackageUpdateChecker.js.map