From de44e89821d3b1c5352ef8968749799b0a41dda1 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Mon, 30 Dec 2019 15:19:32 -0800 Subject: [PATCH] Teach update-dependency about more range specifiers and make it adopt the current range for any tag. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently there is a special case to adopt the existing range specifier if the upgraded range is ‘latest’. This extends this special case for any upgrade to a tag version. This also allows for range specifiers of =, <, >, <=, and >=. This fixes a bug where updating to ^latest generates a spec of ^^latest in the current version. --- buildutils/src/update-dependency.ts | 65 +++++++++++++++++++---------- 1 file changed, 44 insertions(+), 21 deletions(-) diff --git a/buildutils/src/update-dependency.ts b/buildutils/src/update-dependency.ts index 553b6aa48536..c3895bf90f45 100755 --- a/buildutils/src/update-dependency.ts +++ b/buildutils/src/update-dependency.ts @@ -12,32 +12,55 @@ import commander from 'commander'; import semver from 'semver'; let versionCache = new Map(); -const tags = /^([~^]?)([\w\-.]*)$/; +/** + * Matches a simple semver range, where the version number could be an npm tag. + */ +const SEMVER_RANGE = /^(~|\^|=|<|>|<=|>=)?([\w\-.]*)$/; + +/** + * Get the specifier we should use + * + * @param currentSpecifier - The current package version. + * @param suggestedSpecifier - The package version we would like to use. + * + * #### Notes + * If the suggested specifier is not a valid range, we assume it is of the + * form ${RANGE}${TAG}, where TAG is an npm tag (such as 'latest') and RANGE + * is either a semver range indicator (one of `~, ^, >, <, =, >=, <=`), or is + * not given (in which case the current specifier range prefix is used). + */ async function getSpecifier( currentSpecifier: string, suggestedSpecifier: string ) { - // we have an special case where `latest` will use the same current specifier - // e.g. when using latest: - // ^current should result in ^new - // ~current should result in ~new + if (semver.validRange(suggestedSpecifier)) { + return suggestedSpecifier; + } - if (semver.validRange(suggestedSpecifier) === null) { - // We have a tag, with possibly a range specifier, such as ^latest - let specifier = suggestedSpecifier; - if (specifier === 'latest') { - specifier = currentSpecifier; - } - let match = specifier.match(tags); + // The suggested specifier is not a valid range, so we assume it + // references a tag + let [, suggestedSigil, suggestedTag] = + suggestedSpecifier.match(SEMVER_RANGE) ?? []; + + if (!suggestedTag) { + throw Error(`Invalid version specifier: ${suggestedSpecifier}`); + } + + // A tag with no sigil adopts the sigil from the current specification + if (!suggestedSigil) { + let match = currentSpecifier.match(SEMVER_RANGE); if (match === null) { - throw Error(`Invalid version specifier: ${specifier}`); + throw Error( + `Current version range is not recognized: ${currentSpecifier}` + ); + } + let [, currentSigil] = match; + if (currentSigil) { + suggestedSigil = currentSigil; } - specifier = match[1] + suggestedSpecifier; - return specifier; - } else { - return suggestedSpecifier; } + return `${suggestedSigil}${suggestedTag}`; } async function getVersion(pkg: string, specifier: string) { @@ -47,7 +70,7 @@ async function getVersion(pkg: string, specifier: string) { } if (semver.validRange(specifier) === null) { // We have a tag, with possibly a range specifier, such as ^latest - let match = specifier.match(tags); + let match = specifier.match(SEMVER_RANGE); if (match === null) { throw Error(`Invalid version specifier: ${specifier}`); } @@ -70,10 +93,10 @@ async function getVersion(pkg: string, specifier: string) { */ function subset(range1: string, range2: string): boolean { try { - const [, r1, version1] = range1.match(tags)!; - const [, r2] = range2.match(tags)!; + const [, r1, version1] = range1.match(SEMVER_RANGE) ?? []; + const [, r2] = range2.match(SEMVER_RANGE) ?? []; return ( - ['', '~', '^'].indexOf(r1) >= 0 && + ['', '>=', '=', '~', '^'].includes(r1) && r1 === r2 && !!semver.valid(version1) && semver.satisfies(version1, range2)