feat(opentelemetry-instrumentation): replace `semver` package with internal semantic versioning check implementation (#5305)

Co-authored-by: Trent Mick <trentm@gmail.com>
This commit is contained in:
Serkan ÖZAL 2025-01-29 01:57:04 +03:00 committed by GitHub
parent 199fd8ddd3
commit e42fbb985f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 969 additions and 8 deletions

View File

@ -74,6 +74,7 @@ For semantic convention package changes, see the [semconv CHANGELOG](packages/se
* refactor(sdk-trace-base): rename `BasicTracerProvider.activeSpanProcessor` private property. [#5211](https://github.com/open-telemetry/opentelemetry-js/pull/5211) @david-luna
* chore(selenium-tests): remove internal selenium-tests/ package, it wasn't being used @trentm
* chore: update typescript `module` compiler option to `node16`. [#5347](https://github.com/open-telemetry/opentelemetry-js/pull/5347) @david-luna
* feat(opentelemetry-instrumentation): replace `semver` package with internal semantic versioning check implementation to get rid of `semver` package initialization overhead especially in the AWS Lambda environment during coldstart [#5305](https://github.com/open-telemetry/opentelemetry-js/pull/5305) @serkan-ozal
## 1.30.0

View File

@ -74,7 +74,6 @@
"@types/shimmer": "^1.2.0",
"import-in-the-middle": "^1.8.1",
"require-in-the-middle": "^7.1.1",
"semver": "^7.5.2",
"shimmer": "^1.2.1"
},
"peerDependencies": {

View File

@ -17,7 +17,7 @@
import * as types from '../../types';
import * as path from 'path';
import { types as utilTypes } from 'util';
import { satisfies } from 'semver';
import { satisfies } from '../../semver';
import { wrap, unwrap, massWrap, massUnwrap } from 'shimmer';
import { InstrumentationAbstract } from '../../instrumentation';
import {

View File

@ -0,0 +1,618 @@
/*
* 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: { [op: string]: number[] } = {
'>': [1],
'>=': [0, 1],
'=': [0],
'<=': [-1, 0],
'<': [-1],
'!=': [-1, 1],
};
/** Interface for the options to configure semantic versioning satisfy check. */
export interface SatisfiesOptions {
/**
* If set to true, the pre-release checks will be included
* as described [here](https://github.com/npm/node-semver#prerelease-tags).
*/
includePrerelease?: boolean;
}
interface ParsedVersion {
op?: string;
version?: string;
versionSegments?: string[];
versionSegmentCount?: number;
prerelease?: string;
prereleaseSegments?: string[];
prereleaseSegmentCount?: number;
build?: string;
invalid?: boolean;
}
/**
* 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: string,
range: string,
options?: SatisfiesOptions
): boolean {
// 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: ParsedVersion | undefined = _parseVersion(version);
if (!parsedVersion) {
return false;
}
const allParsedRanges: ParsedVersion[] = [];
// Check given version whether it satisfies given range expression
const checkResult: boolean = _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: unknown): boolean {
return typeof version === 'string' && VERSION_REGEXP.test(version);
}
function _doSatisfies(
parsedVersion: ParsedVersion,
range: string,
allParsedRanges: ParsedVersion[],
options?: SatisfiesOptions
): boolean {
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: string[] = 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: string[] = 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: ParsedVersion,
range: string,
allParsedRanges: ParsedVersion[],
options?: SatisfiesOptions
): boolean {
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: ParsedVersion = _parseRange(range);
allParsedRanges.push(parsedRange);
// Check parsed version by parsed range
return _satisfies(parsedVersion, parsedRange);
}
}
function _satisfies(
parsedVersion: ParsedVersion,
parsedRange: ParsedVersion
): boolean {
// 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: number = _compareVersionSegments(
parsedVersion.versionSegments || [],
parsedRange.versionSegments || []
);
// If versions segments are equal, compare by pre-release segments
if (comparisonResult === 0) {
const versionPrereleaseSegments: string[] =
parsedVersion.prereleaseSegments || [];
const rangePrereleaseSegments: string[] =
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: ParsedVersion,
allParsedRanges: ParsedVersion[]
): boolean {
if (parsedVersion.prerelease) {
return allParsedRanges.some(
r => r.prerelease && r.version === parsedVersion.version
);
}
return true;
}
function _normalizeRange(range: string, options?: SatisfiesOptions): string {
range = range.trim();
range = replaceCaret(range, options);
range = replaceTilde(range);
range = replaceXRange(range, options);
range = range.trim();
return range;
}
function isX(id?: string): boolean {
return !id || id.toLowerCase() === 'x' || id === '*';
}
function _parseVersion(versionString: string): ParsedVersion | undefined {
const match: RegExpMatchArray | null = versionString.match(VERSION_REGEXP);
if (!match) {
diag.error(`Invalid version: ${versionString}`);
return undefined;
}
const version: string = match!.groups!.version;
const prerelease: string = match!.groups!.prerelease;
const build: string = match!.groups!.build;
const versionSegments: string[] = version.split('.');
const prereleaseSegments: string[] | undefined = prerelease?.split('.');
return {
op: undefined,
version,
versionSegments,
versionSegmentCount: versionSegments.length,
prerelease,
prereleaseSegments,
prereleaseSegmentCount: prereleaseSegments ? prereleaseSegments.length : 0,
build,
};
}
function _parseRange(rangeString: string): ParsedVersion {
if (!rangeString) {
return {};
}
const match: RegExpMatchArray | null = rangeString.match(RANGE_REGEXP);
if (!match) {
diag.error(`Invalid range: ${rangeString}`);
return {
invalid: true,
};
}
let op: string = match!.groups!.op;
const version: string = match!.groups!.version;
const prerelease: string = match!.groups!.prerelease;
const build: string = match!.groups!.build;
const versionSegments: string[] = version.split('.');
const prereleaseSegments: string[] | undefined = prerelease?.split('.');
if (op === '==') {
op = '=';
}
return {
op: op || '=',
version,
versionSegments,
versionSegmentCount: versionSegments.length,
prerelease,
prereleaseSegments,
prereleaseSegmentCount: prereleaseSegments ? prereleaseSegments.length : 0,
build,
};
}
function _isWildcard(s: string | undefined): boolean {
return s === '*' || s === 'x' || s === 'X';
}
function _parseVersionString(v: string): string | number {
const n: number = parseInt(v, 10);
return isNaN(n) ? v : n;
}
function _normalizeVersionType(
a: string | number,
b: string | number
): [string, string] | [number, number] {
if (typeof a === typeof b) {
if (typeof a === 'number') {
return [a as number, b as number];
} else if (typeof a === 'string') {
return [a as string, b as string];
} else {
throw new Error('Version segments can only be strings or numbers');
}
} else {
return [String(a), String(b)];
}
}
function _compareVersionStrings(v1: string, v2: string): number {
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: string[], v2: string[]): number {
for (let i = 0; i < Math.max(v1.length, v2.length); i++) {
const res: number = _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: string): string {
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: string, options?: SatisfiesOptions): string {
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: string, options?: SatisfiesOptions): string {
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: string, options?: SatisfiesOptions): string {
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();
}
);
}

View File

@ -84,8 +84,8 @@ export interface InstrumentationModuleFile {
/** Supported versions for the file.
*
* A module version is supported if one of the supportedVersions in the array satisfies the module version.
* The syntax of the version is checked with the `satisfies` function of "The semantic versioner for npm", see
* [`semver` package](https://www.npmjs.com/package/semver)
* The syntax of the version is checked with a function compatible
* with [node-semver's `satisfies()` function](https://github.com/npm/node-semver#ranges-1).
* If the version is not supported, we won't apply instrumentation patch.
* If omitted, all versions of the module will be patched.
*
@ -116,8 +116,8 @@ export interface InstrumentationModuleDefinition {
/** Supported version of module.
*
* A module version is supported if one of the supportedVersions in the array satisfies the module version.
* The syntax of the version is checked with the `satisfies` function of "The semantic versioner for npm", see
* [`semver` package](https://www.npmjs.com/package/semver)
* The syntax of the version is checked with the `satisfies` function of
* "The [semantic versioner](https://semver.org) for npm".
* If the version is not supported, we won't apply instrumentation patch (see `enable` method).
* If omitted, all versions of the module will be patched.
*

View File

@ -0,0 +1,96 @@
/*
* 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.
*/
/*
* These tests are adapted from semver's tests for `semver.satisfies()`.
* https://github.com/npm/node-semver/blob/868d4bbe3d318c52544f38d5f9977a1103e924c2/test/functions/satisfies.js
* 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.
*/
import * as assert from 'assert';
import { satisfies, SatisfiesOptions } from '../../src/semver';
const rangeInclude = require('./third-party/node-semver/range-include.js');
const rangeExclude = require('./third-party/node-semver/range-exclude.js');
describe('SemVer', () => {
describe('satisfies', () => {
function isOptionsSupported(options: any): boolean {
// We don't support
// - boolean typed options
// - 'loose' option parameter
if (options && (typeof options === 'boolean' || options.loose)) {
return false;
}
return true;
}
it('when range is included', () => {
rangeInclude.forEach(([range, ver, options]: [string, string, any]) => {
if (!isOptionsSupported(options)) {
return;
}
assert.ok(
satisfies(ver, range, options),
`${range} satisfied by ${ver}`
);
});
});
it('when range is not included', () => {
rangeExclude.forEach(([range, ver, options]: [string, string, any]) => {
if (!isOptionsSupported(options)) {
return;
}
assert.ok(
!satisfies(ver, range, options as SatisfiesOptions),
`${range} not satisfied by ${ver}`
);
});
});
it('invalid ranges never satisfied (but do not throw)', () => {
const cases = [
['blerg', '1.2.3'],
['git+https://user:password0123@github.com/foo', '123.0.0', true],
['^1.2.3', '2.0.0-pre'],
['0.x', undefined],
['*', undefined],
];
cases.forEach(([range, ver]) => {
assert.ok(
!satisfies(ver as any, range as any),
`${range} not satisfied because invalid`
);
});
});
});
});

View File

@ -0,0 +1,15 @@
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.

View File

@ -0,0 +1,107 @@
// [range, version, options]
// version should not be included by range
module.exports = [
['1.0.0 - 2.0.0', '2.2.3'],
['1.2.3+asdf - 2.4.3+asdf', '1.2.3-pre.2'],
['1.2.3+asdf - 2.4.3+asdf', '2.4.3-alpha'],
['^1.2.3+build', '2.0.0'],
['^1.2.3+build', '1.2.0'],
['^1.2.3', '1.2.3-pre'],
['^1.2', '1.2.0-pre'],
['>1.2', '1.3.0-beta'],
['<=1.2.3', '1.2.3-beta'],
['^1.2.3', '1.2.3-beta'],
['=0.7.x', '0.7.0-asdf'],
['>=0.7.x', '0.7.0-asdf'],
['<=0.7.x', '0.7.0-asdf'],
['1', '1.0.0beta', { loose: 420 }],
['<1', '1.0.0beta', true],
['< 1', '1.0.0beta', true],
['1.0.0', '1.0.1'],
['>=1.0.0', '0.0.0'],
['>=1.0.0', '0.0.1'],
['>=1.0.0', '0.1.0'],
['>1.0.0', '0.0.1'],
['>1.0.0', '0.1.0'],
['<=2.0.0', '3.0.0'],
['<=2.0.0', '2.9999.9999'],
['<=2.0.0', '2.2.9'],
['<2.0.0', '2.9999.9999'],
['<2.0.0', '2.2.9'],
['>=0.1.97', 'v0.1.93', true],
['>=0.1.97', '0.1.93'],
['0.1.20 || 1.2.4', '1.2.3'],
['>=0.2.3 || <0.0.1', '0.0.3'],
['>=0.2.3 || <0.0.1', '0.2.2'],
['2.x.x', '1.1.3', { loose: NaN }],
['2.x.x', '3.1.3'],
['1.2.x', '1.3.3'],
['1.2.x || 2.x', '3.1.3'],
['1.2.x || 2.x', '1.1.3'],
['2.*.*', '1.1.3'],
['2.*.*', '3.1.3'],
['1.2.*', '1.3.3'],
['1.2.* || 2.*', '3.1.3'],
['1.2.* || 2.*', '1.1.3'],
['2', '1.1.2'],
['2.3', '2.4.1'],
['~0.0.1', '0.1.0-alpha'],
['~0.0.1', '0.1.0'],
['~2.4', '2.5.0'], // >=2.4.0 <2.5.0
['~2.4', '2.3.9'],
['~>3.2.1', '3.3.2'], // >=3.2.1 <3.3.0
['~>3.2.1', '3.2.0'], // >=3.2.1 <3.3.0
['~1', '0.2.3'], // >=1.0.0 <2.0.0
['~>1', '2.2.3'],
['~1.0', '1.1.0'], // >=1.0.0 <1.1.0
['<1', '1.0.0'],
['>=1.2', '1.1.1'],
['1', '2.0.0beta', true],
['~v0.5.4-beta', '0.5.4-alpha'],
['=0.7.x', '0.8.2'],
['>=0.7.x', '0.6.2'],
['<0.7.x', '0.7.2'],
['<1.2.3', '1.2.3-beta'],
['=1.2.3', '1.2.3-beta'],
['>1.2', '1.2.8'],
['^0.0.1', '0.0.2-alpha'],
['^0.0.1', '0.0.2'],
['^1.2.3', '2.0.0-alpha'],
['^1.2.3', '1.2.2'],
['^1.2', '1.1.9'],
['*', 'v1.2.3-foo', true],
// invalid versions never satisfy, but shouldn't throw
['*', 'not a version'],
['>=2', 'glorp'],
['>=2', false],
['2.x', '3.0.0-pre.0', { includePrerelease: true }],
['^1.0.0', '1.0.0-rc1', { includePrerelease: true }],
['^1.0.0', '2.0.0-rc1', { includePrerelease: true }],
['^1.2.3-rc2', '2.0.0', { includePrerelease: true }],
['^1.0.0', '2.0.0-rc1'],
['1 - 2', '3.0.0-pre', { includePrerelease: true }],
['1 - 2', '2.0.0-pre'],
['1 - 2', '1.0.0-pre'],
['1.0 - 2', '1.0.0-pre'],
['1.1.x', '1.0.0-a'],
['1.1.x', '1.1.0-a'],
['1.1.x', '1.2.0-a'],
['1.1.x', '1.2.0-a', { includePrerelease: true }],
['1.1.x', '1.0.0-a', { includePrerelease: true }],
['1.x', '1.0.0-a'],
['1.x', '1.1.0-a'],
['1.x', '1.2.0-a'],
['1.x', '0.0.0-a', { includePrerelease: true }],
['1.x', '2.0.0-a', { includePrerelease: true }],
['>=1.0.0 <1.1.0', '1.1.0'],
['>=1.0.0 <1.1.0', '1.1.0', { includePrerelease: true }],
['>=1.0.0 <1.1.0', '1.1.0-pre'],
['>=1.0.0 <1.1.0-pre', '1.1.0-pre'],
['== 1.0.0 || foo', '2.0.0', { loose: true }],
];

View File

@ -0,0 +1,127 @@
// [range, version, options]
// version should be included by range
module.exports = [
['1.0.0 - 2.0.0', '1.2.3'],
['^1.2.3+build', '1.2.3'],
['^1.2.3+build', '1.3.0'],
['1.2.3-pre+asdf - 2.4.3-pre+asdf', '1.2.3'],
['1.2.3pre+asdf - 2.4.3-pre+asdf', '1.2.3', true],
['1.2.3-pre+asdf - 2.4.3pre+asdf', '1.2.3', true],
['1.2.3pre+asdf - 2.4.3pre+asdf', '1.2.3', true],
['1.2.3-pre+asdf - 2.4.3-pre+asdf', '1.2.3-pre.2'],
['1.2.3-pre+asdf - 2.4.3-pre+asdf', '2.4.3-alpha'],
['1.2.3+asdf - 2.4.3+asdf', '1.2.3'],
['1.0.0', '1.0.0'],
['>=*', '0.2.4'],
['', '1.0.0'],
['*', '1.2.3', {}],
['*', 'v1.2.3', { loose: 123 }],
['>=1.0.0', '1.0.0', /asdf/],
['>=1.0.0', '1.0.1', { loose: null }],
['>=1.0.0', '1.1.0', { loose: 0 }],
['>1.0.0', '1.0.1', { loose: undefined }],
['>1.0.0', '1.1.0'],
['<=2.0.0', '2.0.0'],
['<=2.0.0', '1.9999.9999'],
['<=2.0.0', '0.2.9'],
['<2.0.0', '1.9999.9999'],
['<2.0.0', '0.2.9'],
['>= 1.0.0', '1.0.0'],
['>= 1.0.0', '1.0.1'],
['>= 1.0.0', '1.1.0'],
['> 1.0.0', '1.0.1'],
['> 1.0.0', '1.1.0'],
['<= 2.0.0', '2.0.0'],
['<= 2.0.0', '1.9999.9999'],
['<= 2.0.0', '0.2.9'],
['< 2.0.0', '1.9999.9999'],
['<\t2.0.0', '0.2.9'],
['>=0.1.97', 'v0.1.97', true],
['>=0.1.97', '0.1.97'],
['0.1.20 || 1.2.4', '1.2.4'],
['>=0.2.3 || <0.0.1', '0.0.0'],
['>=0.2.3 || <0.0.1', '0.2.3'],
['>=0.2.3 || <0.0.1', '0.2.4'],
['||', '1.3.4'],
['2.x.x', '2.1.3'],
['1.2.x', '1.2.3'],
['1.2.x || 2.x', '2.1.3'],
['1.2.x || 2.x', '1.2.3'],
['x', '1.2.3'],
['2.*.*', '2.1.3'],
['1.2.*', '1.2.3'],
['1.2.* || 2.*', '2.1.3'],
['1.2.* || 2.*', '1.2.3'],
['*', '1.2.3'],
['2', '2.1.2'],
['2.3', '2.3.1'],
['~0.0.1', '0.0.1'],
['~0.0.1', '0.0.2'],
['~x', '0.0.9'], // >=2.4.0 <2.5.0
['~2', '2.0.9'], // >=2.4.0 <2.5.0
['~2.4', '2.4.0'], // >=2.4.0 <2.5.0
['~2.4', '2.4.5'],
['~>3.2.1', '3.2.2'], // >=3.2.1 <3.3.0,
['~1', '1.2.3'], // >=1.0.0 <2.0.0
['~>1', '1.2.3'],
['~> 1', '1.2.3'],
['~1.0', '1.0.2'], // >=1.0.0 <1.1.0,
['~ 1.0', '1.0.2'],
['~ 1.0.3', '1.0.12'],
['~ 1.0.3alpha', '1.0.12', { loose: true }],
['>=1', '1.0.0'],
['>= 1', '1.0.0'],
['<1.2', '1.1.1'],
['< 1.2', '1.1.1'],
['~v0.5.4-pre', '0.5.5'],
['~v0.5.4-pre', '0.5.4'],
['=0.7.x', '0.7.2'],
['<=0.7.x', '0.7.2'],
['>=0.7.x', '0.7.2'],
['<=0.7.x', '0.6.2'],
['~1.2.1 >=1.2.3', '1.2.3'],
['~1.2.1 =1.2.3', '1.2.3'],
['~1.2.1 1.2.3', '1.2.3'],
['~1.2.1 >=1.2.3 1.2.3', '1.2.3'],
['~1.2.1 1.2.3 >=1.2.3', '1.2.3'],
['>=1.2.1 1.2.3', '1.2.3'],
['1.2.3 >=1.2.1', '1.2.3'],
['>=1.2.3 >=1.2.1', '1.2.3'],
['>=1.2.1 >=1.2.3', '1.2.3'],
['>=1.2', '1.2.8'],
['^1.2.3', '1.8.1'],
['^0.1.2', '0.1.2'],
['^0.1', '0.1.2'],
['^0.0.1', '0.0.1'],
['^1.2', '1.4.2'],
['^1.2 ^1', '1.4.2'],
['^1.2.3-alpha', '1.2.3-pre'],
['^1.2.0-alpha', '1.2.0-pre'],
['^0.0.1-alpha', '0.0.1-beta'],
['^0.0.1-alpha', '0.0.1'],
['^0.1.1-alpha', '0.1.1-beta'],
['^x', '1.2.3'],
['x - 1.0.0', '0.9.7'],
['x - 1.x', '0.9.7'],
['1.0.0 - x', '1.9.7'],
['1.x - x', '1.9.7'],
['<=7.x', '7.9.9'],
['2.x', '2.0.0-pre.0', { includePrerelease: true }],
['2.x', '2.1.0-pre.0', { includePrerelease: true }],
['1.1.x', '1.1.0-a', { includePrerelease: true }],
['1.1.x', '1.1.1-a', { includePrerelease: true }],
['*', '1.0.0-rc1', { includePrerelease: true }],
['^1.0.0-0', '1.0.1-rc1', { includePrerelease: true }],
['^1.0.0-rc2', '1.0.1-rc1', { includePrerelease: true }],
['^1.0.0', '1.0.1-rc1', { includePrerelease: true }],
['^1.0.0', '1.1.0-rc1', { includePrerelease: true }],
['1 - 2', '2.0.0-pre', { includePrerelease: true }],
['1 - 2', '1.0.0-pre', { includePrerelease: true }],
['1.0 - 2', '1.0.0-pre', { includePrerelease: true }],
['=0.7.x', '0.7.0-asdf', { includePrerelease: true }],
['>=0.7.x', '0.7.0-asdf', { includePrerelease: true }],
['<=0.7.x', '0.7.0-asdf', { includePrerelease: true }],
['>=1.0.0 <=1.1.0', '1.1.0-pre', { includePrerelease: true }],
];

2
package-lock.json generated
View File

@ -2277,7 +2277,6 @@
"@types/shimmer": "^1.2.0",
"import-in-the-middle": "^1.8.1",
"require-in-the-middle": "^7.1.1",
"semver": "^7.5.2",
"shimmer": "^1.2.1"
},
"devDependencies": {
@ -34838,7 +34837,6 @@
"mocha": "10.8.2",
"nyc": "17.1.0",
"require-in-the-middle": "^7.1.1",
"semver": "^7.5.2",
"shimmer": "^1.2.1",
"sinon": "15.1.2",
"ts-loader": "9.5.2",