npm-upgrade
Version:
Interactive CLI utility to easily update outdated NPM dependencies
335 lines (320 loc) • 12.5 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.aliases = void 0;
exports.builder = builder;
exports.handler = exports.describe = exports.command = void 0;
var _partition2 = _interopRequireDefault(require("lodash/fp/partition"));
var _map3 = _interopRequireDefault(require("lodash/fp/map"));
var _flow2 = _interopRequireDefault(require("lodash/fp/flow"));
var _cloneDeep2 = _interopRequireDefault(require("lodash/cloneDeep"));
var _compact2 = _interopRequireDefault(require("lodash/compact"));
var _has2 = _interopRequireDefault(require("lodash/has"));
var _isEmpty2 = _interopRequireDefault(require("lodash/isEmpty"));
var _filter2 = _interopRequireDefault(require("lodash/filter"));
var _each2 = _interopRequireDefault(require("lodash/each"));
var _every2 = _interopRequireDefault(require("lodash/every"));
var _map4 = _interopRequireDefault(require("lodash/map"));
var _fs = require("fs");
var _open = _interopRequireDefault(require("open"));
var _semver = _interopRequireDefault(require("semver"));
var _detectIndent = _interopRequireDefault(require("detect-indent"));
var _npmCheckUpdates = _interopRequireDefault(require("npm-check-updates"));
var _shelljs = _interopRequireDefault(require("shelljs"));
var _versionUtil = require("npm-check-updates/lib/version-util");
var _catchAsyncError = _interopRequireDefault(require("../catchAsyncError"));
var _filterUtils = require("../filterUtils");
var _packageUtils = require("../packageUtils");
var _changelogUtils = require("../changelogUtils");
var _cliTable = require("../cliTable");
var _cliStyles = require("../cliStyles");
var _askUser = _interopRequireDefault(require("../askUser"));
var _stringUtils = require("../stringUtils");
var _ignore = require("./ignore");
var _Config = _interopRequireDefault(require("../Config"));
const pkg = require('../../package.json');
function createUpdatedModulesTable(modules) {
return (0, _cliTable.createSimpleTable)((0, _map4.default)(modules, ({
name,
from,
to
}) => [(0, _cliStyles.strong)(name), from, '→', (0, _versionUtil.colorizeDiff)(from, to)]));
}
const command = exports.command = 'check [filter]';
const aliases = exports.aliases = '*';
const describe = exports.describe = 'Check for outdated modules';
function builder(yargs) {
_packageUtils.DEPS_GROUPS.forEach(({
name,
field,
flag
}) => yargs.option(name, {
type: 'boolean',
alias: flag,
describe: `check only "${field}"`
}));
}
/* eslint complexity: "off" */
const handler = exports.handler = (0, _catchAsyncError.default)(async opts => {
const {
filter
} = opts;
// Checking all the deps if all of them are omitted
if ((0, _every2.default)(_packageUtils.DEPS_GROUPS, ({
name
}) => !opts[name])) {
(0, _each2.default)(_packageUtils.DEPS_GROUPS, ({
name
}) => opts[name] = true);
opts.global = false;
} else if (opts.global) {
// Make global flag mutually exclusive with other flags
(0, _each2.default)(_packageUtils.DEPS_GROUPS, ({
name
}) => {
opts[name] = false;
});
opts.global = true;
}
// Loading `package.json` from the current directory
const {
path: packageFile,
content: packageJson,
source: packageSource
} = opts.global ? (0, _packageUtils.loadGlobalPackages)() : (0, _packageUtils.loadPackageJson)();
// Fetching remote changelogs db in background
(0, _changelogUtils.fetchRemoteDb)();
const depsGroupsToCheck = (0, _filter2.default)(_packageUtils.DEPS_GROUPS, ({
name
}) => !!opts[name]);
const depsGroupsToCheckStr = depsGroupsToCheck.length === _packageUtils.DEPS_GROUPS.length ? '' : `${(0, _stringUtils.toSentence)((0, _map4.default)(depsGroupsToCheck, ({
name
}) => (0, _cliStyles.strong)(name)))} `;
const filteredWith = filter ? `filtered with ${(0, _cliStyles.strong)(filter)} ` : '';
console.log(`Checking for outdated ${depsGroupsToCheckStr}dependencies ${filteredWith}${opts.global ? '' : `for "${(0, _cliStyles.strong)(packageFile)}"`}...`);
const ncuDepGroups = _packageUtils.DEPS_GROUPS.filter(({
name
}) => opts[name]).map(({
ncuValue
}) => ncuValue).join(',');
const filteredPackageJson = filterDepsInPackageJson(packageJson, (0, _filterUtils.makeFilterFunction)(filter));
const currentVersions = _npmCheckUpdates.default.getCurrentDependencies(filteredPackageJson, {
dep: ncuDepGroups
});
const latestVersions = await _npmCheckUpdates.default.queryVersions(currentVersions, {
versionTarget: 'latest',
timeout: 0
});
const upgradedVersions = _npmCheckUpdates.default.upgradeDependencies(currentVersions, latestVersions);
if ((0, _isEmpty2.default)(upgradedVersions)) {
return console.log((0, _cliStyles.success)('All dependencies are up-to-date!'));
}
// Getting the list of ignored modules
const config = new _Config.default();
config.ignore = config.ignore || {};
// Making arrays of outdated modules
const [ignoredModules, modulesToUpdate] = (0, _flow2.default)(_map3.default.convert({
'cap': false
})((newVersion, moduleName) => ({
name: moduleName,
from: currentVersions[moduleName],
to: newVersion
})), (0, _partition2.default)(module => (0, _has2.default)(config.ignore, module.name) && _semver.default.satisfies(latestVersions[module.name], config.ignore[module.name].versions)))(upgradedVersions);
// Moving `@types/*` modules right below their original modules
sortModules(modulesToUpdate);
sortModules(ignoredModules);
// Creating pretty-printed CLI tables with update info
if ((0, _isEmpty2.default)(modulesToUpdate)) {
console.log((0, _cliStyles.success)('\nAll active modules are up-to-date!'));
} else {
console.log(`\n${(0, _cliStyles.strong)('New versions of active modules available:')}\n\n${createUpdatedModulesTable(modulesToUpdate)}`);
}
if (!(0, _isEmpty2.default)(ignoredModules)) {
const rows = (0, _map4.default)(ignoredModules, ({
name,
from,
to
}) => [(0, _cliStyles.strong)(name), from, '→', (0, _versionUtil.colorizeDiff)(from, to), (0, _cliStyles.attention)(config.ignore[name].versions), config.ignore[name].reason]);
// Adding table header
rows.unshift((0, _map4.default)(['', 'From', '', 'To', 'Ignored versions', 'Reason'], header => (0, _cliStyles.strong)(header)));
console.log(`\n${(0, _cliStyles.strong)('Ignored updates:')}\n\n${(0, _cliTable.createSimpleTable)(rows)}`);
}
const updatedModules = [];
let isUpdateFinished = false;
while (modulesToUpdate.length && !isUpdateFinished) {
const outdatedModule = modulesToUpdate.shift();
const {
name,
from,
to
} = outdatedModule;
let {
changelogUrl,
homepage
} = outdatedModule;
// Adds new line
console.log('');
const answer = await (0, _askUser.default)({
type: 'list',
message: `${changelogUrl === undefined ? 'U' : 'So, u'}pdate "${name}" ${opts.global ? 'globally' : 'in package.json'} from ${from} to ${(0, _versionUtil.colorizeDiff)(from, to)}?`,
choices: (0, _compact2.default)([{
name: 'Yes',
value: true
}, {
name: 'No',
value: false
},
// Don't show this option if we couldn't find module's changelog url
changelogUrl !== null && {
name: 'Show changelog',
value: 'changelog'
},
// Show this if we haven't found changelog
changelogUrl === null && homepage !== null && {
name: 'Open homepage',
value: 'homepage'
}, {
name: 'Ignore',
value: 'ignore'
}, {
name: 'Finish update process',
value: 'finish'
}]),
// Automatically setting cursor to "Open homepage" after we haven't found changelog
default: changelogUrl === null && homepage === undefined ? 2 : 0
});
switch (answer) {
case 'changelog':
// Ask user about this module again
modulesToUpdate.unshift(outdatedModule);
if (changelogUrl === undefined) {
console.log('Trying to find changelog URL...');
changelogUrl = outdatedModule.changelogUrl = await (0, _changelogUtils.findModuleChangelogUrl)(name);
}
if (changelogUrl) {
console.log(`Opening ${(0, _cliStyles.strong)(changelogUrl)}...`);
(0, _open.default)(changelogUrl);
} else {
console.log(`Sorry, we haven't found any changelog URL for ${(0, _cliStyles.strong)(name)} module.\n` + `It would be great if you could fill an issue about this here: ${(0, _cliStyles.strong)(pkg.bugs.url)}\n` + 'Thanks a lot!');
}
break;
case 'homepage':
// Ask user about this module again
modulesToUpdate.unshift(outdatedModule);
if (homepage === undefined) {
console.log('Trying to find homepage URL...');
homepage = outdatedModule.homepage = (0, _packageUtils.getModuleHomepage)(await (0, _packageUtils.getModuleInfo)(name));
}
if (homepage) {
console.log(`Opening ${(0, _cliStyles.strong)(homepage)}...`);
(0, _open.default)(homepage);
} else {
console.log(`Sorry, there is no info about homepage URL in the ${(0, _cliStyles.strong)(name)}'s package.json`);
}
break;
case 'ignore':
{
const {
versions,
reason
} = await (0, _ignore.askIgnoreFields)(latestVersions[name]);
config.ignore[name] = {
versions,
reason
};
break;
}
case 'finish':
isUpdateFinished = true;
break;
case true:
updatedModules.push(outdatedModule);
(0, _packageUtils.setModuleVersion)(name, to, packageJson);
delete config.ignore[name];
break;
}
}
// Adds new line
console.log('');
// Saving config
config.save();
if (!updatedModules.length) {
console.log('Nothing to update');
return;
}
// Showing the list of modules that are going to be updated
console.log(`\n${(0, _cliStyles.strong)('These packages will be updated:')}\n\n` + createUpdatedModulesTable(updatedModules) + '\n');
if (opts.global) {
const shouldUpdateGlobalPackages = await (0, _askUser.default)({
type: 'confirm',
message: 'Update global modules?',
default: true
});
if (!shouldUpdateGlobalPackages) {
return;
}
console.log(`Automatically upgrading ${updatedModules.length} module${updatedModules.length !== 1 ? 's' : ''}...`);
return _shelljs.default.exec(`npm install --global ${updatedModules.map(({
name,
to
}) => `${name}@${to}`).join(' ')}`);
}
const shouldUpdatePackageFile = await (0, _askUser.default)({
type: 'confirm',
message: 'Update package.json?',
default: true
});
if (shouldUpdatePackageFile) {
const {
indent
} = (0, _detectIndent.default)(packageSource);
(0, _fs.writeFileSync)(packageFile,
// Adding newline to the end of file
`${JSON.stringify(packageJson, null, indent)}\n`);
}
});
function filterDepsInPackageJson(packageJson, moduleNameFilterFn) {
const result = (0, _cloneDeep2.default)(packageJson);
for (const depsGroup of _packageUtils.DEPS_GROUPS) {
const deps = result[depsGroup.field];
if (deps) {
for (const moduleName of Object.keys(deps)) {
if (!moduleNameFilterFn(moduleName)) {
delete deps[moduleName];
}
}
}
}
return result;
}
function sortModules(modules) {
const processedModules = new Set();
for (let i = 0, len = modules.length; i < len; i++) {
const module = modules[i];
if (processedModules.has(module)) {
continue;
}
const normalizedName = module.name.replace(/^@types\//, '');
if (module.name === normalizedName) {
continue;
}
// Searching for corresponding module
const originalModuleIndex = modules.findIndex(({
name
}) => name === normalizedName);
if (originalModuleIndex === -1 || i === originalModuleIndex + 1) {
continue;
}
if (originalModuleIndex > i) {
modules.splice(originalModuleIndex + 1, 0, module);
modules.splice(i, 1);
processedModules.add(module);
i--;
} else {
modules.splice(i, 1);
modules.splice(originalModuleIndex + 1, 0, module);
}
}
}