diff --git a/buildutils/src/update-dependency.ts b/buildutils/src/update-dependency.ts index 553b6aa48536..3e0b9236e641 100755 --- a/buildutils/src/update-dependency.ts +++ b/buildutils/src/update-dependency.ts @@ -12,32 +12,57 @@ 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 + console.log(`current: ${currentSpecifier}`); + console.log(`suggested: ${suggestedSpecifier}`); + 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 +72,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 +95,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)