flow-typed
Version:
A repository of high quality flow type definitions
172 lines (157 loc) • 4.72 kB
Flow
// @flow
import * as semver from 'semver';
export type VersionRange = '>=' | '<=';
export type Version = {|
range?: VersionRange,
major: number | 'x',
minor: number | 'x',
patch: number | 'x',
prerel: null | string,
upperBound?: Version, // TODO: rename to otherBound
|};
export function emptyVersion(): Version {
return {
range: '<=',
major: 'x',
minor: 'x',
patch: 'x',
prerel: null,
};
}
/**
* Find the lowest compatible explicit version based on a version range
* of a flow-typed definition
* ie: a type definition is 1.2.x, and the lower bound of that would be 1.2.0
*/
export function getRangeLowerBound(rangeRaw: string | semver.Range): string {
const range =
typeof rangeRaw === 'string' ? new semver.Range(rangeRaw) : rangeRaw;
// When the range only has one object in the set, it implicitly means
// there is a range of anything up to the upper bound.
// Therefore we return `'0.0.0'`.
if (range.set[0].length === 1) {
return '0.0.0';
}
return range.set[0][0].semver.version;
}
/**
* Find the highest compatible explicit version based on a version range
* of a flow-typed definition
* ie: a type definition is 1.2.x, and the upper bound of that would be 1.3.0
*/
export function getRangeUpperBound(rangeRaw: string | semver.Range): string {
const range =
typeof rangeRaw === 'string' ? new semver.Range(rangeRaw) : rangeRaw;
// When the range only has one object in the set, it implicitly means
// there is a range of anything up to the upper bound.
// So we'll return the first object version representing the upper bound.
if (range.set[0].length === 1) {
return range.set[0][0].semver.version;
}
return range.set[0][1].semver.version;
}
// TODO: This has some egregious duplication with
// libDef.getLocalLibDefFlowVersions(). Need to better consolidate logic
const VER = 'v([0-9]+).([0-9]+|x).([0-9]+|x)(-.*)?';
const VERSION_RE = new RegExp(`^([><]=?)?${VER}(_([><]=?)${VER})?$`);
export function stringToVersion(verStr: string): Version {
const versionParts = verStr.match(VERSION_RE);
if (versionParts == null) {
throw new Error(
`${verStr} is a malformed version string. Expected a version formatted ` +
'as `' +
VERSION_RE.toString() +
'`',
);
}
const [
_1,
range,
majorStr,
minorStr,
patchStr,
prerel,
_2,
upperRange,
upperMajor,
upperMinor,
upperPatch,
upperPrerel,
] = versionParts;
if (range != null && range !== '>=' && range !== '<=') {
throw new Error(`'${verStr}': Invalid version range: ${range}`);
}
if (upperRange != null && upperRange !== '>=' && upperRange !== '<=') {
throw new Error(
`'${verStr}': Invalid version upper-bound range: ${upperRange}`,
);
}
const major = _validateVersionNumberPart(verStr, 'major', majorStr);
const minor =
minorStr !== 'x'
? _validateVersionNumberPart(verStr, 'minor', minorStr)
: minorStr;
const patch =
patchStr !== 'x'
? _validateVersionNumberPart(verStr, 'patch', patchStr)
: patchStr;
const upperBound = upperMajor
? {
range: upperRange,
major: _validateVersionNumberPart(
verStr,
'upper-bound major',
upperMajor,
),
minor:
upperMinor !== 'x'
? _validateVersionNumberPart(
verStr,
'upper-bound minor',
upperMinor,
)
: upperMinor,
patch:
upperPatch !== 'x'
? _validateVersionNumberPart(
verStr,
'upper-bound patch',
upperPatch,
)
: upperPatch,
prerel: upperPrerel.substr(1),
}
: undefined;
if (range === '<=' && ((major === minor) === patch) === 0) {
throw new Error(
`It doesn't make sense to have a version range of '<=v0.0.0'!`,
);
}
return {
range,
major,
minor,
patch,
prerel: prerel != null ? prerel.substr(1) : prerel,
upperBound,
};
}
export function versionToString(ver: Version): string {
const rangeStr = ver.range ? ver.range : '';
const upperStr = ver.upperBound ? `_${versionToString(ver.upperBound)}` : '';
const prerel = ver.prerel == null ? '' : `-${ver.prerel}`;
return `${rangeStr}v${ver.major}.${ver.minor}.${ver.patch}${prerel}${upperStr}`;
}
function _validateVersionNumberPart(
context: string,
partName: string,
part: string,
) {
const num = parseInt(part, 10);
if (String(num) !== part) {
throw new Error(
`${context}: Invalid ${partName} number. Expected a number.`,
);
}
return num;
}