version-util.js
16.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.upgradeGithubUrl = exports.upgradeDependencyDeclaration = exports.getGithubUrlTag = exports.isGithubUrl = exports.upgradeNpmAlias = exports.isNpmAlias = exports.parseNpmAlias = exports.createNpmAlias = exports.v = exports.fixPseudoVersion = exports.isPre = exports.findGreatestByLevel = exports.compareVersions = exports.colorizeDiff = exports.isWildPart = exports.isWildCard = exports.addWildCard = exports.setPrecision = exports.getPrecision = exports.stringify = exports.precisionAdd = exports.numParts = exports.WILDCARDS = exports.DEFAULT_WILDCARD = void 0;
const lodash_1 = __importDefault(require("lodash"));
const util_1 = __importDefault(require("util"));
const semver_utils_1 = __importDefault(require("semver-utils"));
const chalk_1 = __importDefault(require("chalk"));
const cint_1 = __importDefault(require("cint"));
const semver_1 = __importDefault(require("semver"));
const parse_github_url_1 = __importDefault(require("parse-github-url"));
const VERSION_BASE_PARTS = ['major', 'minor', 'patch'];
const VERSION_ADDED_PARTS = ['release', 'build'];
const VERSION_PARTS = [...VERSION_BASE_PARTS, ...VERSION_ADDED_PARTS];
const VERSION_PART_DELIM = {
major: '',
minor: '.',
patch: '.',
release: '-',
build: '+',
};
exports.DEFAULT_WILDCARD = '^';
exports.WILDCARDS = ['^', '~', '.*', '.x'];
const WILDCARDS_PURE = ['^', '~', '^*', '*', 'x', 'x.x', 'x.x.x'];
const WILDCARD_PURE_REGEX = new RegExp(`^(${WILDCARDS_PURE.join('|').replace(/\^/g, '\\^').replace(/\*/g, '\\*')})$`);
/** Matches an npm alias version declaration. */
const NPM_ALIAS_REGEX = /^npm:(.*)@(.*)/;
/**
* @param version
* @returns The number of parts in the version
*/
function numParts(version) {
const [semver] = semver_utils_1.default.parseRange(version);
if (!semver) {
throw new Error(util_1.default.format('semverutils.parseRange returned null when trying to parse "%s". This is probably a problem with the "semver-utils" dependency. Please report an issue at https://github.com/raineorshine/npm-check-updates/issues.', version));
}
return lodash_1.default.intersection(VERSION_PARTS, Object.keys(semver)).length;
}
exports.numParts = numParts;
/**
* Increases or decreases the a precision by the given amount, e.g. major+1 -> minor
*
* @param precision
* @param n
* @returns
*/
function precisionAdd(precision, n) {
if (n === 0)
return precision;
const index = VERSION_BASE_PARTS.includes(precision)
? VERSION_BASE_PARTS.indexOf(precision) + n
: VERSION_ADDED_PARTS.includes(precision)
? VERSION_BASE_PARTS.length + n
: null;
if (index === null || !VERSION_PARTS[index]) {
throw new Error(`Invalid precision: ${precision}`);
}
return VERSION_PARTS[index];
}
exports.precisionAdd = precisionAdd;
/**
* Joins the major, minor, patch, release, and build parts (controlled by an
* optional precision arg) of a semver object into a dot-delimited string.
*
* @param semver
* @param [precision]
* @returns
*/
function stringify(semver, precision) {
// get a list of the parts up until (and including) the given precision
// or all of them, if no precision is specified
const parts = precision ? VERSION_PARTS.slice(0, VERSION_PARTS.indexOf(precision) + 1) : VERSION_PARTS;
// pair each part with its delimiter and join together
return parts
.filter(part => (precision && VERSION_BASE_PARTS.includes(precision)) || semver[part])
.map(part => VERSION_PART_DELIM[part] + (semver[part] || '0'))
.join('');
}
exports.stringify = stringify;
/**
* Gets how precise this version number is (major, minor, patch, release, or build)
*
* @param version
* @returns
*/
function getPrecision(version) {
const [semver] = semver_utils_1.default.parseRange(version);
// expects VERSION_PARTS to be in correct order
// eslint-disable-next-line fp/no-mutating-methods
return VERSION_PARTS.slice().reverse().find(lodash_1.default.propertyOf(semver));
}
exports.getPrecision = getPrecision;
/**
* Sets the precision of a (loose) semver to the specified level: major, minor, etc.
*
* @param version
* @param [precision]
* @returns
*/
function setPrecision(version, precision) {
const [semver] = semver_utils_1.default.parseRange(version);
return stringify(semver, precision);
}
exports.setPrecision = setPrecision;
/**
* Adds a given wildcard (^,~,.*,.x) to a version number. Adds ^ and ~ to the
* beginning. Replaces everything after the major version number with .* or .x
*
* @param version
* @param wildcard
* @returns
*/
function addWildCard(version, wildcard) {
return wildcard === '^' || wildcard === '~' ? wildcard + version : setPrecision(version, 'major') + wildcard;
}
exports.addWildCard = addWildCard;
/**
* Returns true if the given string is one of the wild cards.
*
* @param version
* @returns
*/
function isWildCard(version) {
return WILDCARD_PURE_REGEX.test(version);
}
exports.isWildCard = isWildCard;
/**
* Returns true if the given digit is a wildcard for a part of a version.
*
* @param versionPart
* @returns
*/
function isWildPart(versionPartValue) {
return versionPartValue === '*' || versionPartValue === 'x';
}
exports.isWildPart = isWildPart;
/**
* Colorize the parts of a version string (to) that are different than
* another (from). Assumes that the two verson strings are in the same format.
*
* @param from
* @param to
* @returns
*/
function colorizeDiff(from, to) {
let leadingWildcard = '';
// separate out leading ^ or ~
if (/^[~^]/.test(to) && to[0] === from[0]) {
leadingWildcard = to[0];
to = to.slice(1);
from = from.slice(1);
}
// split into parts
const partsToColor = to.split('.');
const partsToCompare = from.split('.');
let i = partsToColor.findIndex((part, i) => part !== partsToCompare[i]);
i = i >= 0 ? i : partsToColor.length;
// major = red (or any change before 1.0.0)
// minor = cyan
// patch = green
const color = i === 0 || partsToColor[0] === '0' ? 'red' : i === 1 ? 'cyan' : 'green';
// if we are colorizing only part of the word, add a dot in the middle
const middot = i > 0 && i < partsToColor.length ? '.' : '';
return leadingWildcard + partsToColor.slice(0, i).join('.') + middot + chalk_1.default[color](partsToColor.slice(i).join('.'));
}
exports.colorizeDiff = colorizeDiff;
/** Comparator used to sort semver versions */
function compareVersions(a, b) {
const isValid = semver_1.default.valid(a) && semver_1.default.valid(b);
const isGreater = isValid ? semver_1.default.gt(a, b) : a > b;
return isGreater ? 1 : a === b ? 0 : -1;
}
exports.compareVersions = compareVersions;
/**
* Finds the greatest version at the given level (minor|patch).
*
* @param versions Unsorted array of all available versions
* @param current Current version or range
* @param level major|minor
* @returns String representation of the suggested version.
*/
function findGreatestByLevel(versions, current, level) {
if (!semver_1.default.validRange(current)) {
return null;
}
const cur = semver_1.default.minVersion(current);
const versionsSorted = [...versions] // eslint-disable-line fp/no-mutating-methods
.sort(compareVersions)
.filter(v => {
const parsed = semver_1.default.parse(v);
return (parsed &&
(level === 'major' || parsed.major === (cur === null || cur === void 0 ? void 0 : cur.major)) &&
(level === 'major' || level === 'minor' || parsed.minor === (cur === null || cur === void 0 ? void 0 : cur.minor)));
});
return lodash_1.default.last(versionsSorted) || null;
}
exports.findGreatestByLevel = findGreatestByLevel;
/**
* @param version
* @returns True if the version is any kind of prerelease: alpha, beta, rc, pre
*/
function isPre(version) {
return getPrecision(version) === 'release';
}
exports.isPre = isPre;
/** Checks if a string is a simple version in the format "v1". */
const isMissingMinorAndPatch = (s) => /^[vV]?\d+$/.test(s);
/** Checks if a version string is missing its match component, e.g. "1.0". */
const isMissingPatch = (s) => /^[vV]?\d+\.\d+$/.test(s);
/** Removes a leading 'v' or 'V' from a pseudo version.. */
const fixLeadingV = (s) => s.replace(/^[vV]/, '');
/** Converts a pseudo version that is missing its minor and patch components into a valid semver version. NOOP for valid semver versions. */
const fixMissingMinorAndPatch = (s) => (isMissingMinorAndPatch(s) ? s + '.0.0' : s);
/** Converts a pseudo version that is missing its patch component into a valid semver version. NOOP for valid semver versions. */
const fixMissingPatch = (s) => (isMissingPatch(s) ? s + '.0' : s);
/** Converts a pseudo version into a valid semver version. NOOP for valid semver versions. */
exports.fixPseudoVersion = lodash_1.default.flow(fixLeadingV, fixMissingMinorAndPatch, fixMissingPatch);
/**
* Returns 'v' if the string starts with a v, otherwise returns empty string.
*
* @param str
* @returns
*/
function v(str) {
return str && (str[0] === 'v' || str[1] === 'v') ? 'v' : '';
}
exports.v = v;
/**
* Constructs an npm alias from the name and version of the actual package.
*
* @param name Name of the actual package.
* @param version Version of the actual package.
* @returns "npm:package@x.y.z"
* @example createNpmAlias('chalk', '2.0.0') -> 'npm:chalk@2.0.0'
*/
const createNpmAlias = (name, version) => `npm:${name}@${version}`;
exports.createNpmAlias = createNpmAlias;
/**
* Parses an npm alias into a [name, version] 2-tuple.
*
* @returns [name, version] or null if the input is not an npm alias
* @example 'npm:chalk@1.0.0' -> ['chalk', '1.0.0']
*/
const parseNpmAlias = (alias) => {
const match = alias && alias.match && alias.match(NPM_ALIAS_REGEX);
return match && match.slice(1);
};
exports.parseNpmAlias = parseNpmAlias;
/**
* Returns true if a version declaration is an npm alias.
*/
const isNpmAlias = (declaration) => declaration && !!declaration.match(NPM_ALIAS_REGEX);
exports.isNpmAlias = isNpmAlias;
/**
* Replaces the version number embedded in an npm alias.
*/
const upgradeNpmAlias = (declaration, upgraded) => {
const npmAlias = (0, exports.parseNpmAlias)(declaration);
if (!npmAlias)
return null;
return (0, exports.createNpmAlias)(npmAlias[0], upgraded);
};
exports.upgradeNpmAlias = upgradeNpmAlias;
/**
* Returns true if a version declaration is a Github URL with a valid semver version.
*/
const isGithubUrl = (declaration) => {
if (!declaration)
return false;
const parsed = (0, parse_github_url_1.default)(declaration);
if (!parsed || !parsed.branch)
return false;
const version = decodeURIComponent(parsed.branch).replace(/^semver:/, '');
return !!semver_1.default.validRange(version);
};
exports.isGithubUrl = isGithubUrl;
/**
* Returns the embedded tag in a Github URL.
*/
const getGithubUrlTag = (declaration) => {
if (!declaration)
return null;
const parsed = (0, parse_github_url_1.default)(declaration);
if (!parsed || !parsed.branch)
return null;
const version = decodeURIComponent(parsed.branch).replace(/^semver:/, '');
return parsed && parsed.branch && semver_1.default.validRange(version) ? version : null;
};
exports.getGithubUrlTag = getGithubUrlTag;
/**
* Upgrade an existing dependency declaration to satisfy the latest version.
*
* @param declaration Current version declaration (e.g. "1.2.x")
* @param latestVersion Latest version (e.g "1.3.2")
* @param [options={}]
* @returns The upgraded dependency declaration (e.g. "1.3.x")
*/
function upgradeDependencyDeclaration(declaration, latestVersion, options = {}) {
options.wildcard = options.wildcard || exports.DEFAULT_WILDCARD;
if (!latestVersion) {
return declaration;
}
// parse the latestVersion
// return original declaration if latestSemver is invalid
const [latestSemver] = semver_utils_1.default.parseRange(latestVersion);
if (!latestSemver) {
return declaration;
}
// return global wildcards immediately
if (options.removeRange) {
return latestVersion;
}
else if (isWildCard(declaration)) {
return declaration;
}
// parse the declaration
// if multiple ranges, use the semver with the least number of parts
const parsedRange = (0, lodash_1.default)(semver_utils_1.default.parseRange(declaration))
// semver-utils includes empty entries for the || and - operators. We can remove them completely
.reject({ operator: '||' })
.reject({ operator: '-' })
.sortBy(lodash_1.default.ary(lodash_1.default.flow(stringify, numParts), 1))
.value();
const [declaredSemver] = parsedRange;
/**
* Chooses version parts between the declared version and the latest.
* Base parts (major, minor, patch) are only included if they are in the original declaration.
* Added parts (release, build) are always included. They are only present if we are checking --greatest versions
* anyway.
*/
function chooseVersion(part) {
return ((isWildPart(declaredSemver[part])
? declaredSemver[part]
: VERSION_BASE_PARTS.includes(part) && declaredSemver[part]
? latestSemver[part]
: VERSION_ADDED_PARTS.includes(part)
? latestSemver[part]
: null) || null);
}
// create a new semver object with major, minor, patch, build, and release parts
const newSemver = cint_1.default.toObject(VERSION_PARTS, (part) => ({
[part]: chooseVersion(part),
}));
const newSemverString = stringify(newSemver);
const version = v(declaredSemver.semver) + newSemverString;
// determine the operator
// do not compact, because [undefined, '<'] must be differentiated from ['<']
const uniqueOperators = (0, lodash_1.default)(parsedRange)
.map(range => range.operator)
.uniq()
.value();
const operator = uniqueOperators[0] || '';
const hasWildCard = exports.WILDCARDS.some(wildcard => newSemverString.includes(wildcard));
const isLessThanOrEqual = uniqueOperators[0] === '<' || uniqueOperators[0] === '<=';
const isGreaterThan = uniqueOperators[0] === '>';
const isMixed = uniqueOperators.length > 1;
// convert versions with </<= or mixed operators into the preferred wildcard
// only do so if the new version does not already contain a wildcard
return !hasWildCard && (isLessThanOrEqual || isMixed)
? addWildCard(version, options.wildcard)
: // convert > to >= since there are likely no available versions > latest
// https://github.com/raineorshine/npm-check-updates/issues/957
(isGreaterThan ? '>=' : operator) + version;
}
exports.upgradeDependencyDeclaration = upgradeDependencyDeclaration;
/** Reverts a valid semver version to a pseudo version that is missing its minor and patch components. NOOP If the original version was a valid semver version. */
const revertMissingMinorAndPatch = lodash_1.default.curry((current, latest) => isMissingMinorAndPatch(current) ? latest.slice(0, latest.length - '.0.0'.length) : latest);
/** Reverts a valid semver version to a pseudo version that is missing its patch components. NOOP If the original version was a valid semver version. */
const revertMissingPatch = lodash_1.default.curry((current, latest) => isMissingPatch(current) ? latest.slice(0, latest.length - '.0'.length) : latest);
/** Reverts a valid semver version to a pseudo version with a leading 'v'. NOOP If the original version was a valid semver version. */
const revertLeadingV = lodash_1.default.curry((current, latest) => (v(current) ? v(current) + latest : latest));
/** Reverts a valid semver version to a pseudo version. NOOP If the original version was a valid semver version. */
const revertPseudoVersion = (current, latest) => lodash_1.default.flow(revertLeadingV(current), revertMissingMinorAndPatch(current), revertMissingPatch(current))(latest);
/**
* Replaces the version number embedded in a Github URL.
*/
const upgradeGithubUrl = (declaration, upgraded) => {
// convert upgraded to a proper semver version if it is a pseudo version, otherwise revertPseudoVersion will return an empty string
const upgradedNormalized = (0, exports.fixPseudoVersion)(upgraded);
const parsedUrl = (0, parse_github_url_1.default)(declaration);
if (!parsedUrl)
return declaration;
const tag = decodeURIComponent(parsedUrl.branch).replace(/^semver:/, '');
return declaration.replace(tag, upgradeDependencyDeclaration(tag, revertPseudoVersion(tag, upgradedNormalized)));
};
exports.upgradeGithubUrl = upgradeGithubUrl;
//# sourceMappingURL=version-util.js.map