2017-01-07 07:22:48 +00:00
|
|
|
const semver = require('semver');
|
|
|
|
const stable = require('semver-stable');
|
2017-03-26 04:08:01 +00:00
|
|
|
const _ = require('lodash');
|
2017-04-13 19:22:24 +00:00
|
|
|
const semverUtils = require('semver-utils');
|
2017-08-06 13:38:10 +00:00
|
|
|
const moment = require('moment');
|
2017-01-07 07:22:48 +00:00
|
|
|
|
2017-01-11 13:33:32 +00:00
|
|
|
module.exports = {
|
2017-01-15 22:56:09 +00:00
|
|
|
determineUpgrades,
|
2017-01-12 16:04:25 +00:00
|
|
|
isRange,
|
|
|
|
isValidVersion,
|
2017-01-17 11:46:32 +00:00
|
|
|
isPastLatest,
|
2017-01-11 12:19:59 +00:00
|
|
|
};
|
|
|
|
|
2017-06-27 11:44:03 +00:00
|
|
|
function determineUpgrades(npmDep, config) {
|
2017-07-04 10:39:28 +00:00
|
|
|
const result = {
|
2017-07-05 05:12:25 +00:00
|
|
|
type: 'warning',
|
2017-07-04 10:39:28 +00:00
|
|
|
};
|
2017-09-15 17:46:25 +00:00
|
|
|
const { currentVersion } = config;
|
|
|
|
const { versions } = npmDep;
|
2017-01-17 11:46:32 +00:00
|
|
|
if (!versions || Object.keys(versions).length === 0) {
|
2017-07-04 10:39:28 +00:00
|
|
|
result.message = `No versions returned from registry for this package`;
|
|
|
|
logger.warn(result.message);
|
|
|
|
return [result];
|
2017-01-12 16:04:25 +00:00
|
|
|
}
|
2017-01-17 09:26:36 +00:00
|
|
|
const versionList = Object.keys(versions);
|
2017-01-12 16:04:25 +00:00
|
|
|
const allUpgrades = {};
|
2017-04-13 10:44:34 +00:00
|
|
|
let changeLogFromVersion = currentVersion;
|
2017-01-12 16:04:25 +00:00
|
|
|
// Check for a current range and pin it
|
|
|
|
if (isRange(currentVersion)) {
|
|
|
|
// Pin ranges to their maximum satisfying version
|
2017-01-17 09:26:36 +00:00
|
|
|
const maxSatisfying = semver.maxSatisfying(versionList, currentVersion);
|
2017-01-13 09:06:38 +00:00
|
|
|
allUpgrades.pin = {
|
2017-07-05 05:12:25 +00:00
|
|
|
type: 'pin',
|
2017-06-25 05:36:13 +00:00
|
|
|
isPin: true,
|
2017-01-13 09:06:38 +00:00
|
|
|
newVersion: maxSatisfying,
|
|
|
|
newVersionMajor: semver.major(maxSatisfying),
|
|
|
|
};
|
2017-04-13 10:44:34 +00:00
|
|
|
changeLogFromVersion = maxSatisfying;
|
2017-07-18 18:01:00 +00:00
|
|
|
} else if (versionList.indexOf(currentVersion) === -1) {
|
|
|
|
const rollbackVersion = semver.maxSatisfying(
|
|
|
|
versionList,
|
|
|
|
`<${currentVersion}`
|
|
|
|
);
|
|
|
|
allUpgrades.rollback = {
|
|
|
|
type: 'rollback',
|
|
|
|
isRollback: true,
|
|
|
|
newVersion: rollbackVersion,
|
|
|
|
newVersionMajor: semver.major(rollbackVersion),
|
2017-11-24 06:14:58 +00:00
|
|
|
semanticCommitType: 'fix',
|
2017-10-02 09:38:42 +00:00
|
|
|
branchName:
|
|
|
|
'{{branchPrefix}}rollback-{{depNameSanitized}}-{{newVersionMajor}}.x',
|
2017-07-18 18:01:00 +00:00
|
|
|
};
|
2017-01-12 16:04:25 +00:00
|
|
|
}
|
2017-03-26 04:08:01 +00:00
|
|
|
_(versionList)
|
|
|
|
// Filter out older versions as we can't upgrade to those
|
2017-04-13 10:44:34 +00:00
|
|
|
.filter(version => semver.gt(version, changeLogFromVersion))
|
2017-03-26 04:08:01 +00:00
|
|
|
// Ignore unstable versions, unless the current version is unstable
|
2017-04-21 08:12:41 +00:00
|
|
|
.reject(
|
|
|
|
version =>
|
|
|
|
config.ignoreUnstable &&
|
|
|
|
stable.is(changeLogFromVersion) &&
|
2017-04-21 08:25:49 +00:00
|
|
|
!stable.is(version)
|
2017-04-21 08:12:41 +00:00
|
|
|
)
|
2017-03-26 04:08:01 +00:00
|
|
|
// Ignore versions newer than "latest", unless current version is newer than the "latest"
|
2017-04-21 08:12:41 +00:00
|
|
|
.reject(
|
|
|
|
version =>
|
|
|
|
config.respectLatest &&
|
2017-06-27 11:44:03 +00:00
|
|
|
isPastLatest(npmDep, version) &&
|
|
|
|
!isPastLatest(npmDep, changeLogFromVersion)
|
2017-04-21 08:12:41 +00:00
|
|
|
)
|
2017-03-26 04:08:01 +00:00
|
|
|
// Loop through all possible versions
|
2017-04-21 08:12:41 +00:00
|
|
|
.forEach(newVersion => {
|
2017-01-12 16:04:25 +00:00
|
|
|
// Group by major versions
|
|
|
|
const newVersionMajor = semver.major(newVersion);
|
2017-08-01 15:10:53 +00:00
|
|
|
const newVersionMinor = semver.minor(newVersion);
|
2017-08-21 11:41:48 +00:00
|
|
|
const hasPatchAutomerge = config.patch && config.patch.automerge === true;
|
2017-08-01 15:10:53 +00:00
|
|
|
let type;
|
|
|
|
if (newVersionMajor > semver.major(changeLogFromVersion)) {
|
|
|
|
type = 'major';
|
|
|
|
} else if (
|
|
|
|
newVersionMinor === semver.minor(changeLogFromVersion) &&
|
2017-08-21 11:41:48 +00:00
|
|
|
(config.separatePatchReleases || hasPatchAutomerge)
|
2017-08-01 15:10:53 +00:00
|
|
|
) {
|
|
|
|
// Only use patch if configured to
|
|
|
|
type = 'patch';
|
|
|
|
} else {
|
|
|
|
type = 'minor';
|
|
|
|
}
|
|
|
|
let upgradeKey;
|
|
|
|
if (
|
|
|
|
!config.separateMajorReleases ||
|
|
|
|
config.groupName ||
|
2017-08-24 10:39:23 +00:00
|
|
|
config.major.automerge === true
|
2017-08-01 15:10:53 +00:00
|
|
|
) {
|
|
|
|
// If we're not separating releases then we use a common lookup key
|
|
|
|
upgradeKey = 'latest';
|
2017-11-10 03:57:07 +00:00
|
|
|
} else if (!config.multipleMajorPrs && type === 'major') {
|
|
|
|
upgradeKey = 'major';
|
2017-08-01 15:10:53 +00:00
|
|
|
} else if (type === 'patch') {
|
|
|
|
upgradeKey = `{{newVersionMajor}}.{{newVersionMinor}}`;
|
|
|
|
} else {
|
|
|
|
// Use major version as lookup key
|
|
|
|
upgradeKey = newVersionMajor;
|
|
|
|
}
|
2017-01-12 16:04:25 +00:00
|
|
|
// Save this, if it's a new major version or greater than the previous greatest
|
2017-04-21 08:12:41 +00:00
|
|
|
if (
|
|
|
|
!allUpgrades[upgradeKey] ||
|
|
|
|
semver.gt(newVersion, allUpgrades[upgradeKey].newVersion)
|
|
|
|
) {
|
2017-04-13 10:44:34 +00:00
|
|
|
const changeLogToVersion = newVersion;
|
2017-04-15 10:44:50 +00:00
|
|
|
allUpgrades[upgradeKey] = {
|
2017-07-05 05:12:25 +00:00
|
|
|
type,
|
2017-01-12 16:04:25 +00:00
|
|
|
newVersion,
|
|
|
|
newVersionMajor,
|
2017-07-27 20:17:28 +00:00
|
|
|
newVersionMinor,
|
2017-04-13 10:44:34 +00:00
|
|
|
changeLogFromVersion,
|
|
|
|
changeLogToVersion,
|
2017-01-12 16:04:25 +00:00
|
|
|
};
|
2017-07-05 05:12:25 +00:00
|
|
|
if (type === 'major') {
|
2017-06-25 05:36:13 +00:00
|
|
|
allUpgrades[upgradeKey].isMajor = true;
|
2017-07-05 05:12:25 +00:00
|
|
|
} else if (type === 'minor') {
|
2017-06-25 05:36:13 +00:00
|
|
|
allUpgrades[upgradeKey].isMinor = true;
|
2017-07-27 20:17:28 +00:00
|
|
|
} else if (type === 'patch') {
|
|
|
|
allUpgrades[upgradeKey].isPatch = true;
|
2017-06-25 05:36:13 +00:00
|
|
|
}
|
2017-01-07 21:00:32 +00:00
|
|
|
}
|
2017-03-26 04:08:01 +00:00
|
|
|
});
|
2017-01-12 16:04:25 +00:00
|
|
|
// Return only the values - we don't need the keys anymore
|
2017-04-13 19:22:24 +00:00
|
|
|
const upgrades = Object.keys(allUpgrades).map(key => allUpgrades[key]);
|
2017-08-06 13:38:10 +00:00
|
|
|
for (const upgrade of upgrades) {
|
|
|
|
const elapsed = moment().diff(
|
|
|
|
moment(versions[upgrade.newVersion].time),
|
|
|
|
'days'
|
|
|
|
);
|
|
|
|
upgrade.unpublishable = elapsed > 0;
|
|
|
|
}
|
2017-04-13 19:22:24 +00:00
|
|
|
|
|
|
|
// Return now if array is empty, or we can keep pinned version upgrades
|
|
|
|
if (upgrades.length === 0 || config.pinVersions || !isRange(currentVersion)) {
|
|
|
|
return upgrades;
|
|
|
|
}
|
|
|
|
|
|
|
|
// The user prefers to maintain ranges, so we need to unpin our upgrades
|
|
|
|
const semverParsed = semverUtils.parseRange(currentVersion);
|
|
|
|
if (semverParsed.length > 1) {
|
|
|
|
// We don't know how to support complex semver ranges, so don't upgrade
|
2017-11-07 10:52:15 +00:00
|
|
|
result.message = `Complex semver ranges such as "${
|
|
|
|
currentVersion
|
|
|
|
}" are not yet supported so won't ever be upgraded`;
|
2017-07-04 10:39:28 +00:00
|
|
|
logger.warn(result.message);
|
|
|
|
return [result];
|
2017-04-13 19:22:24 +00:00
|
|
|
}
|
|
|
|
// We know we have a simple semver, now check which operator it is
|
|
|
|
const currentSemver = semverParsed[0];
|
|
|
|
// Loop through all upgrades and convert to ranges
|
2017-07-04 10:39:28 +00:00
|
|
|
const rangedUpgrades = _(upgrades)
|
2017-07-05 05:12:25 +00:00
|
|
|
.reject(upgrade => upgrade.type === 'pin')
|
2017-08-03 06:01:20 +00:00
|
|
|
.map(upgrade => ({ ...upgrade, ...{ isRange: true } }))
|
2017-04-21 08:12:41 +00:00
|
|
|
.map(upgrade => {
|
|
|
|
const { major, minor } = semverUtils.parse(upgrade.newVersion);
|
|
|
|
if (currentSemver.operator === '~') {
|
|
|
|
// Utilise that a.b is the same as ~a.b.0
|
|
|
|
const minSatisfying = semver.minSatisfying(
|
|
|
|
versionList,
|
2017-04-21 08:25:49 +00:00
|
|
|
`${major}.${minor}`
|
2017-04-21 08:12:41 +00:00
|
|
|
);
|
|
|
|
// Add a tilde before that version number
|
2017-08-03 06:01:20 +00:00
|
|
|
return { ...upgrade, ...{ newVersion: `~${minSatisfying}` } };
|
2017-04-21 08:12:41 +00:00
|
|
|
} else if (currentSemver.operator === '^') {
|
2017-04-28 19:55:14 +00:00
|
|
|
// Special case where major and minor are 0
|
|
|
|
if (major === '0' && minor === '0') {
|
2017-08-03 06:01:20 +00:00
|
|
|
return {
|
|
|
|
...upgrade,
|
|
|
|
...{
|
|
|
|
newVersion: `^${upgrade.newVersion}`,
|
|
|
|
},
|
|
|
|
};
|
2017-04-28 19:55:14 +00:00
|
|
|
}
|
2017-04-21 08:12:41 +00:00
|
|
|
// If version is < 1, then semver treats ^ same as ~
|
|
|
|
const newRange = major === '0' ? `${major}.${minor}` : `${major}`;
|
|
|
|
const minSatisfying = semver.minSatisfying(versionList, newRange);
|
|
|
|
// Add in the caret
|
2017-08-03 06:01:20 +00:00
|
|
|
return { ...upgrade, ...{ newVersion: `^${minSatisfying}` } };
|
2017-04-21 08:12:41 +00:00
|
|
|
} else if (currentSemver.operator === '<=') {
|
|
|
|
// Example: <= 1.2.0
|
2017-08-03 06:01:20 +00:00
|
|
|
return {
|
|
|
|
...upgrade,
|
|
|
|
...{
|
|
|
|
newVersion: `<= ${upgrade.newVersion}`,
|
|
|
|
},
|
|
|
|
};
|
2017-04-21 08:12:41 +00:00
|
|
|
} else if (currentSemver.minor === undefined) {
|
|
|
|
// Example: 1
|
2017-08-03 06:01:20 +00:00
|
|
|
return { ...upgrade, ...{ newVersion: `${major}` } };
|
2017-04-21 08:12:41 +00:00
|
|
|
} else if (currentSemver.minor === 'x') {
|
|
|
|
// Example: 1.x
|
2017-08-03 06:01:20 +00:00
|
|
|
return { ...upgrade, ...{ newVersion: `${major}.x` } };
|
2017-04-21 08:12:41 +00:00
|
|
|
} else if (currentSemver.patch === undefined) {
|
|
|
|
// Example: 1.2
|
2017-08-03 06:01:20 +00:00
|
|
|
return { ...upgrade, ...{ newVersion: `${major}.${minor}` } };
|
2017-04-21 08:12:41 +00:00
|
|
|
} else if (currentSemver.patch === 'x') {
|
|
|
|
// Example: 1.2.x
|
2017-08-03 06:01:20 +00:00
|
|
|
return { ...upgrade, ...{ newVersion: `${major}.${minor}.x` } };
|
2017-04-21 08:12:41 +00:00
|
|
|
}
|
2017-11-07 10:52:15 +00:00
|
|
|
result.message = `The current semver range "${
|
|
|
|
currentVersion
|
|
|
|
}" is not supported so won't ever be upgraded`;
|
2017-07-04 10:39:28 +00:00
|
|
|
logger.warn(result.message);
|
2017-04-21 08:12:41 +00:00
|
|
|
return null;
|
|
|
|
})
|
|
|
|
.compact()
|
|
|
|
.value();
|
2017-07-04 10:39:28 +00:00
|
|
|
if (result.message) {
|
|
|
|
// There must have been an error converting to ranges
|
|
|
|
return [result];
|
|
|
|
}
|
|
|
|
return rangedUpgrades;
|
2017-01-07 21:00:32 +00:00
|
|
|
}
|
2017-01-09 21:44:36 +00:00
|
|
|
|
|
|
|
function isRange(input) {
|
|
|
|
// Pinned versions also return true for semver.validRange
|
2017-01-10 22:06:25 +00:00
|
|
|
// We need to check first that they're not 'valid' to get only ranges
|
2017-04-21 08:12:41 +00:00
|
|
|
return semver.valid(input) === null && semver.validRange(input) !== null;
|
2017-01-09 21:44:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function isValidVersion(input) {
|
2017-01-12 16:00:54 +00:00
|
|
|
return (semver.valid(input) || semver.validRange(input)) !== null;
|
2017-01-09 21:44:36 +00:00
|
|
|
}
|
2017-01-17 11:46:32 +00:00
|
|
|
|
2017-06-27 11:44:03 +00:00
|
|
|
function isPastLatest(npmDep, version) {
|
|
|
|
if (npmDep['dist-tags'] && npmDep['dist-tags'].latest) {
|
|
|
|
return semver.gt(version, npmDep['dist-tags'].latest);
|
2017-01-17 11:46:32 +00:00
|
|
|
}
|
2017-06-27 11:44:03 +00:00
|
|
|
logger.warn(`No dist-tags.latest for ${npmDep.name}`);
|
2017-01-17 11:46:32 +00:00
|
|
|
return false;
|
|
|
|
}
|