UNPKG

@opentelemetry/instrumentation

Version:
514 lines 18.5 kB
/* * Copyright The OpenTelemetry Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // This is a custom semantic versioning implementation compatible with the // `satisfies(version, range, options?)` function from the `semver` npm package; // with the exception that the `loose` option is not supported. // // The motivation for the custom semver implementation is that // `semver` package has some initialization delay (lots of RegExp init and compile) // and this leads to coldstart overhead for the OTEL Lambda Node.js layer. // Hence, we have implemented lightweight version of it internally with required functionalities. import { diag } from '@opentelemetry/api'; const VERSION_REGEXP = /^(?:v)?(?<version>(?<major>0|[1-9]\d*)\.(?<minor>0|[1-9]\d*)\.(?<patch>0|[1-9]\d*))(?:-(?<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; const RANGE_REGEXP = /^(?<op><|>|=|==|<=|>=|~|\^|~>)?\s*(?:v)?(?<version>(?<major>x|X|\*|0|[1-9]\d*)(?:\.(?<minor>x|X|\*|0|[1-9]\d*))?(?:\.(?<patch>x|X|\*|0|[1-9]\d*))?)(?:-(?<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?<build>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; const operatorResMap = { '>': [1], '>=': [0, 1], '=': [0], '<=': [-1, 0], '<': [-1], '!=': [-1, 1], }; /** * Checks given version whether it satisfies given range expression. * @param version the [version](https://github.com/npm/node-semver#versions) to be checked * @param range the [range](https://github.com/npm/node-semver#ranges) expression for version check * @param options options to configure semver satisfy check */ export function satisfies(version, range, options) { // Strict semver format check if (!_validateVersion(version)) { diag.error(`Invalid version: ${version}`); return false; } // If range is empty, satisfy check succeeds regardless what version is if (!range) { return true; } // Cleanup range range = range.replace(/([<>=~^]+)\s+/g, '$1'); // Parse version const parsedVersion = _parseVersion(version); if (!parsedVersion) { return false; } const allParsedRanges = []; // Check given version whether it satisfies given range expression const checkResult = _doSatisfies(parsedVersion, range, allParsedRanges, options); // If check result is OK, // do another final check for pre-release, if pre-release check is included by option if (checkResult && !options?.includePrerelease) { return _doPreleaseCheck(parsedVersion, allParsedRanges); } return checkResult; } function _validateVersion(version) { return typeof version === 'string' && VERSION_REGEXP.test(version); } function _doSatisfies(parsedVersion, range, allParsedRanges, options) { if (range.includes('||')) { // A version matches a range if and only if // every comparator in at least one of the ||-separated comparator sets is satisfied by the version const ranges = range.trim().split('||'); for (const r of ranges) { if (_checkRange(parsedVersion, r, allParsedRanges, options)) { return true; } } return false; } else if (range.includes(' - ')) { // Hyphen ranges: https://github.com/npm/node-semver#hyphen-ranges-xyz---abc range = replaceHyphen(range, options); } else if (range.includes(' ')) { // Multiple separated ranges and all needs to be satisfied for success const ranges = range .trim() .replace(/\s{2,}/g, ' ') .split(' '); for (const r of ranges) { if (!_checkRange(parsedVersion, r, allParsedRanges, options)) { return false; } } return true; } // Check given parsed version with given range return _checkRange(parsedVersion, range, allParsedRanges, options); } function _checkRange(parsedVersion, range, allParsedRanges, options) { range = _normalizeRange(range, options); if (range.includes(' ')) { // If there are multiple ranges separated, satisfy each of them return _doSatisfies(parsedVersion, range, allParsedRanges, options); } else { // Validate and parse range const parsedRange = _parseRange(range); allParsedRanges.push(parsedRange); // Check parsed version by parsed range return _satisfies(parsedVersion, parsedRange); } } function _satisfies(parsedVersion, parsedRange) { // If range is invalid, satisfy check fails (no error throw) if (parsedRange.invalid) { return false; } // If range is empty or wildcard, satisfy check succeeds regardless what version is if (!parsedRange.version || _isWildcard(parsedRange.version)) { return true; } // Compare version segment first let comparisonResult = _compareVersionSegments(parsedVersion.versionSegments || [], parsedRange.versionSegments || []); // If versions segments are equal, compare by pre-release segments if (comparisonResult === 0) { const versionPrereleaseSegments = parsedVersion.prereleaseSegments || []; const rangePrereleaseSegments = parsedRange.prereleaseSegments || []; if (!versionPrereleaseSegments.length && !rangePrereleaseSegments.length) { comparisonResult = 0; } else if (!versionPrereleaseSegments.length && rangePrereleaseSegments.length) { comparisonResult = 1; } else if (versionPrereleaseSegments.length && !rangePrereleaseSegments.length) { comparisonResult = -1; } else { comparisonResult = _compareVersionSegments(versionPrereleaseSegments, rangePrereleaseSegments); } } // Resolve check result according to comparison operator return operatorResMap[parsedRange.op]?.includes(comparisonResult); } function _doPreleaseCheck(parsedVersion, allParsedRanges) { if (parsedVersion.prerelease) { return allParsedRanges.some(r => r.prerelease && r.version === parsedVersion.version); } return true; } function _normalizeRange(range, options) { range = range.trim(); range = replaceCaret(range, options); range = replaceTilde(range); range = replaceXRange(range, options); range = range.trim(); return range; } function isX(id) { return !id || id.toLowerCase() === 'x' || id === '*'; } function _parseVersion(versionString) { const match = versionString.match(VERSION_REGEXP); if (!match) { diag.error(`Invalid version: ${versionString}`); return undefined; } const version = match.groups.version; const prerelease = match.groups.prerelease; const build = match.groups.build; const versionSegments = version.split('.'); const prereleaseSegments = prerelease?.split('.'); return { op: undefined, version, versionSegments, versionSegmentCount: versionSegments.length, prerelease, prereleaseSegments, prereleaseSegmentCount: prereleaseSegments ? prereleaseSegments.length : 0, build, }; } function _parseRange(rangeString) { if (!rangeString) { return {}; } const match = rangeString.match(RANGE_REGEXP); if (!match) { diag.error(`Invalid range: ${rangeString}`); return { invalid: true, }; } let op = match.groups.op; const version = match.groups.version; const prerelease = match.groups.prerelease; const build = match.groups.build; const versionSegments = version.split('.'); const prereleaseSegments = prerelease?.split('.'); if (op === '==') { op = '='; } return { op: op || '=', version, versionSegments, versionSegmentCount: versionSegments.length, prerelease, prereleaseSegments, prereleaseSegmentCount: prereleaseSegments ? prereleaseSegments.length : 0, build, }; } function _isWildcard(s) { return s === '*' || s === 'x' || s === 'X'; } function _parseVersionString(v) { const n = parseInt(v, 10); return isNaN(n) ? v : n; } function _normalizeVersionType(a, b) { if (typeof a === typeof b) { if (typeof a === 'number') { return [a, b]; } else if (typeof a === 'string') { return [a, b]; } else { throw new Error('Version segments can only be strings or numbers'); } } else { return [String(a), String(b)]; } } function _compareVersionStrings(v1, v2) { if (_isWildcard(v1) || _isWildcard(v2)) { return 0; } const [parsedV1, parsedV2] = _normalizeVersionType(_parseVersionString(v1), _parseVersionString(v2)); if (parsedV1 > parsedV2) { return 1; } else if (parsedV1 < parsedV2) { return -1; } return 0; } function _compareVersionSegments(v1, v2) { for (let i = 0; i < Math.max(v1.length, v2.length); i++) { const res = _compareVersionStrings(v1[i] || '0', v2[i] || '0'); if (res !== 0) { return res; } } return 0; } //////////////////////////////////////////////////////////////////////////////// // The rest of this file is adapted from portions of https://github.com/npm/node-semver/tree/868d4bb // License: /* * The ISC License * * Copyright (c) Isaac Z. Schlueter and Contributors * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ const LETTERDASHNUMBER = '[a-zA-Z0-9-]'; const NUMERICIDENTIFIER = '0|[1-9]\\d*'; const NONNUMERICIDENTIFIER = `\\d*[a-zA-Z-]${LETTERDASHNUMBER}*`; const GTLT = '((?:<|>)?=?)'; const PRERELEASEIDENTIFIER = `(?:${NUMERICIDENTIFIER}|${NONNUMERICIDENTIFIER})`; const PRERELEASE = `(?:-(${PRERELEASEIDENTIFIER}(?:\\.${PRERELEASEIDENTIFIER})*))`; const BUILDIDENTIFIER = `${LETTERDASHNUMBER}+`; const BUILD = `(?:\\+(${BUILDIDENTIFIER}(?:\\.${BUILDIDENTIFIER})*))`; const XRANGEIDENTIFIER = `${NUMERICIDENTIFIER}|x|X|\\*`; const XRANGEPLAIN = `[v=\\s]*(${XRANGEIDENTIFIER})` + `(?:\\.(${XRANGEIDENTIFIER})` + `(?:\\.(${XRANGEIDENTIFIER})` + `(?:${PRERELEASE})?${BUILD}?` + `)?)?`; const XRANGE = `^${GTLT}\\s*${XRANGEPLAIN}$`; const XRANGE_REGEXP = new RegExp(XRANGE); const HYPHENRANGE = `^\\s*(${XRANGEPLAIN})` + `\\s+-\\s+` + `(${XRANGEPLAIN})` + `\\s*$`; const HYPHENRANGE_REGEXP = new RegExp(HYPHENRANGE); const LONETILDE = '(?:~>?)'; const TILDE = `^${LONETILDE}${XRANGEPLAIN}$`; const TILDE_REGEXP = new RegExp(TILDE); const LONECARET = '(?:\\^)'; const CARET = `^${LONECARET}${XRANGEPLAIN}$`; const CARET_REGEXP = new RegExp(CARET); // Borrowed from https://github.com/npm/node-semver/blob/868d4bbe3d318c52544f38d5f9977a1103e924c2/classes/range.js#L285 // // ~, ~> --> * (any, kinda silly) // ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0-0 // ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0-0 // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0 // ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0 // ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0 // ~0.0.1 --> >=0.0.1 <0.1.0-0 function replaceTilde(comp) { const r = TILDE_REGEXP; return comp.replace(r, (_, M, m, p, pr) => { let ret; if (isX(M)) { ret = ''; } else if (isX(m)) { ret = `>=${M}.0.0 <${+M + 1}.0.0-0`; } else if (isX(p)) { // ~1.2 == >=1.2.0 <1.3.0-0 ret = `>=${M}.${m}.0 <${M}.${+m + 1}.0-0`; } else if (pr) { ret = `>=${M}.${m}.${p}-${pr} <${M}.${+m + 1}.0-0`; } else { // ~1.2.3 == >=1.2.3 <1.3.0-0 ret = `>=${M}.${m}.${p} <${M}.${+m + 1}.0-0`; } return ret; }); } // Borrowed from https://github.com/npm/node-semver/blob/868d4bbe3d318c52544f38d5f9977a1103e924c2/classes/range.js#L329 // // ^ --> * (any, kinda silly) // ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0-0 // ^2.0, ^2.0.x --> >=2.0.0 <3.0.0-0 // ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0 // ^1.2.3 --> >=1.2.3 <2.0.0-0 // ^1.2.0 --> >=1.2.0 <2.0.0-0 // ^0.0.1 --> >=0.0.1 <0.0.2-0 // ^0.1.0 --> >=0.1.0 <0.2.0-0 function replaceCaret(comp, options) { const r = CARET_REGEXP; const z = options?.includePrerelease ? '-0' : ''; return comp.replace(r, (_, M, m, p, pr) => { let ret; if (isX(M)) { ret = ''; } else if (isX(m)) { ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0`; } else if (isX(p)) { if (M === '0') { ret = `>=${M}.${m}.0${z} <${M}.${+m + 1}.0-0`; } else { ret = `>=${M}.${m}.0${z} <${+M + 1}.0.0-0`; } } else if (pr) { if (M === '0') { if (m === '0') { ret = `>=${M}.${m}.${p}-${pr} <${M}.${m}.${+p + 1}-0`; } else { ret = `>=${M}.${m}.${p}-${pr} <${M}.${+m + 1}.0-0`; } } else { ret = `>=${M}.${m}.${p}-${pr} <${+M + 1}.0.0-0`; } } else { if (M === '0') { if (m === '0') { ret = `>=${M}.${m}.${p}${z} <${M}.${m}.${+p + 1}-0`; } else { ret = `>=${M}.${m}.${p}${z} <${M}.${+m + 1}.0-0`; } } else { ret = `>=${M}.${m}.${p} <${+M + 1}.0.0-0`; } } return ret; }); } // Borrowed from https://github.com/npm/node-semver/blob/868d4bbe3d318c52544f38d5f9977a1103e924c2/classes/range.js#L390 function replaceXRange(comp, options) { const r = XRANGE_REGEXP; return comp.replace(r, (ret, gtlt, M, m, p, pr) => { const xM = isX(M); const xm = xM || isX(m); const xp = xm || isX(p); const anyX = xp; if (gtlt === '=' && anyX) { gtlt = ''; } // if we're including prereleases in the match, then we need // to fix this to -0, the lowest possible prerelease value pr = options?.includePrerelease ? '-0' : ''; if (xM) { if (gtlt === '>' || gtlt === '<') { // nothing is allowed ret = '<0.0.0-0'; } else { // nothing is forbidden ret = '*'; } } else if (gtlt && anyX) { // we know patch is an x, because we have any x at all. // replace X with 0 if (xm) { m = 0; } p = 0; if (gtlt === '>') { // >1 => >=2.0.0 // >1.2 => >=1.3.0 gtlt = '>='; if (xm) { M = +M + 1; m = 0; p = 0; } else { m = +m + 1; p = 0; } } else if (gtlt === '<=') { // <=0.7.x is actually <0.8.0, since any 0.7.x should // pass. Similarly, <=7.x is actually <8.0.0, etc. gtlt = '<'; if (xm) { M = +M + 1; } else { m = +m + 1; } } if (gtlt === '<') { pr = '-0'; } ret = `${gtlt + M}.${m}.${p}${pr}`; } else if (xm) { ret = `>=${M}.0.0${pr} <${+M + 1}.0.0-0`; } else if (xp) { ret = `>=${M}.${m}.0${pr} <${M}.${+m + 1}.0-0`; } return ret; }); } // Borrowed from https://github.com/npm/node-semver/blob/868d4bbe3d318c52544f38d5f9977a1103e924c2/classes/range.js#L488 // // 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 // 1.2.3 - 3.4 => >=1.2.0 <3.5.0-0 Any 3.4.x will do // 1.2 - 3.4 => >=1.2.0 <3.5.0-0 function replaceHyphen(comp, options) { const r = HYPHENRANGE_REGEXP; return comp.replace(r, (_, from, fM, fm, fp, fpr, fb, to, tM, tm, tp, tpr) => { if (isX(fM)) { from = ''; } else if (isX(fm)) { from = `>=${fM}.0.0${options?.includePrerelease ? '-0' : ''}`; } else if (isX(fp)) { from = `>=${fM}.${fm}.0${options?.includePrerelease ? '-0' : ''}`; } else if (fpr) { from = `>=${from}`; } else { from = `>=${from}${options?.includePrerelease ? '-0' : ''}`; } if (isX(tM)) { to = ''; } else if (isX(tm)) { to = `<${+tM + 1}.0.0-0`; } else if (isX(tp)) { to = `<${tM}.${+tm + 1}.0-0`; } else if (tpr) { to = `<=${tM}.${tm}.${tp}-${tpr}`; } else if (options?.includePrerelease) { to = `<${tM}.${tm}.${+tp + 1}-0`; } else { to = `<=${to}`; } return `${from} ${to}`.trim(); }); } //# sourceMappingURL=semver.js.map