UNPKG

npm-upgrade

Version:

Interactive CLI utility to easily update outdated NPM dependencies

335 lines (320 loc) 12.5 kB
"use strict"; 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); } } }