UNPKG

6.03 kBJavaScriptView Raw
1'use strict';
2
3const _ = require('lodash');
4const inquirer = require('inquirer');
5const chalk = require('chalk');
6const table = require('text-table');
7const installPackages = require('./install-packages');
8const emoji = require('./emoji');
9const stripAnsi = require('strip-ansi');
10
11const UI_GROUPS = [
12 {
13 title: chalk.bold.underline.green('Update package.json to match version installed.'),
14 filter: {mismatch: true, bump: null}
15 },
16 {
17 title: `${chalk.bold.underline.green('Missing.')} ${chalk.green('You probably want these.')}`,
18 filter: {notInstalled: true, bump: null}
19 },
20 {
21 title: `${chalk.bold.underline.green('Patch Update')} ${chalk.green('Backwards-compatible bug fixes.')}`,
22 filter: {bump: 'patch'}
23 },
24 {
25 title: `${chalk.yellow.underline.bold('Minor Update')} ${chalk.yellow('New backwards-compatible features.')}`,
26 bgColor: 'yellow',
27 filter: {bump: 'minor'}
28 },
29 {
30 title: `${chalk.red.underline.bold('Major Update')} ${chalk.red('Potentially breaking API changes. Use caution.')}`,
31 filter: {bump: 'major'}
32 },
33 {
34 title: `${chalk.magenta.underline.bold('Non-Semver')} ${chalk.magenta('Versions less than 1.0.0, caution.')}`,
35 filter: {bump: 'nonSemver'}
36 }
37];
38
39function label(pkg) {
40 const bumpInstalled = pkg.bump ? pkg.installed : '';
41 const installed = pkg.mismatch ? pkg.packageJson : bumpInstalled;
42 const name = chalk.yellow(pkg.moduleName);
43 const type = pkg.devDependency ? chalk.green(' devDep') : '';
44 const missing = pkg.notInstalled ? chalk.red(' missing') : '';
45 const homepage = pkg.homepage ? chalk.blue.underline(pkg.homepage) : '';
46 return [
47 name + type + missing,
48 installed,
49 installed && '❯',
50 chalk.bold(pkg.latest || ''),
51 pkg.latest ? homepage : pkg.regError || pkg.pkgError
52 ];
53}
54
55function short(pkg) {
56 return `${pkg.moduleName}@${pkg.latest}`;
57}
58
59function choice(pkg) {
60 if (!pkg.mismatch && !pkg.bump && !pkg.notInstalled) {
61 return false;
62 }
63
64 return {
65 value: pkg,
66 name: label(pkg),
67 short: short(pkg)
68 };
69}
70
71function unselectable(options) {
72 return new inquirer.Separator(chalk.reset(options ? options.title : ' '));
73}
74
75function createChoices(packages, options) {
76 const filteredChoices = _.filter(packages, options.filter);
77
78 const choices = filteredChoices.map(choice)
79 .filter(Boolean);
80
81 const choicesAsATable = table(_.map(choices, 'name'), {
82 align: ['l', 'l', 'l'],
83 stringLength: function (str) {
84 return stripAnsi(str).length;
85 }
86 }).split('\n');
87
88 const choicesWithTableFormating = _.map(choices, (choice, i) => {
89 choice.name = choicesAsATable[i];
90 return choice;
91 });
92
93 if (choicesWithTableFormating.length) {
94 choices.unshift(unselectable(options));
95 choices.unshift(unselectable());
96 return choices;
97 }
98}
99
100function buildPackageToUpdate(moduleName, version, isYarn, saveExact) {
101 // handle adding ^ for yarn, npm seems to handle this if not exact
102 return (isYarn && !saveExact) ? moduleName + '@^' + version : moduleName + '@' + version;
103}
104
105function interactive(currentState) {
106 const packages = currentState.get('packages');
107
108 if (currentState.get('debug')) {
109 console.log('packages', packages);
110 }
111
112 const choicesGrouped = UI_GROUPS.map(group => createChoices(packages, group))
113 .filter(Boolean);
114
115 const choices = _.flatten(choicesGrouped);
116
117 if (!choices.length) {
118 console.log(`${emoji(':heart: ')}Your modules look ${chalk.bold('amazing')}. Keep up the great work.${emoji(' :heart:')}`);
119 return;
120 }
121
122 choices.push(unselectable());
123 choices.push(unselectable({title: 'Space to select. Enter to start upgrading. Control-C to cancel.'}));
124
125 const questions = [
126 {
127 name: 'packages',
128 message: 'Choose which packages to update.',
129 type: 'checkbox',
130 choices: choices.concat(unselectable()),
131 pageSize: process.stdout.rows - 2
132 }
133 ];
134
135 return inquirer.prompt(questions).then(answers => {
136 const packagesToUpdate = answers.packages;
137 const isYarn = currentState.get('installer') === 'yarn';
138 const saveExact = currentState.get('saveExact');
139
140 if (!packagesToUpdate || !packagesToUpdate.length) {
141 console.log('No packages selected for update.');
142 return false;
143 }
144
145 const saveDependencies = packagesToUpdate
146 .filter(pkg => !pkg.devDependency)
147 .map(pkg => buildPackageToUpdate(pkg.moduleName, pkg.latest, isYarn, saveExact));
148
149 const saveDevDependencies = packagesToUpdate
150 .filter(pkg => pkg.devDependency)
151 .map(pkg => buildPackageToUpdate(pkg.moduleName, pkg.latest, isYarn, saveExact));
152
153 const updatedPackages = packagesToUpdate
154 .map(pkg => buildPackageToUpdate(pkg.moduleName, pkg.latest, isYarn, saveExact)).join(', ');
155
156 if (!currentState.get('global')) {
157 if (saveDependencies.length) {
158 !isYarn && saveDependencies.push('--save');
159 }
160
161 if (saveDevDependencies.length) {
162 isYarn ? saveDevDependencies.push('--dev') : saveDevDependencies.push('--save-dev');
163 }
164 }
165
166 return installPackages(saveDependencies, currentState)
167 .then(currentState => installPackages(saveDevDependencies, currentState))
168 .then(currentState => {
169 console.log('');
170 console.log(chalk.green(`[npm-check] Update complete!`));
171 console.log(chalk.green('[npm-check] ' + updatedPackages));
172 console.log(chalk.green(`[npm-check] You should re-run your tests to make sure everything works with the updates.`));
173 return currentState;
174 });
175 });
176}
177
178module.exports = interactive;