doctor.js
6.63 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
"use strict";
const fs = require('fs');
let chalk = require('chalk');
const rimraf = require('rimraf');
const upgradePackageData = require('./lib/upgradePackageData').default;
const { printUpgrades } = require('./logging');
const spawnYarn = require('./package-managers/yarn').default;
const spawnNpm = require('./package-managers/npm').default;
/** Run the npm CLI in CI mode. */
const npm = (args, options, print) => {
if (options.color) {
chalk = new chalk.Instance({ level: 1 });
}
if (print) {
console.log(chalk.blue([options.packageManager, ...args].join(' ')));
}
const spawnOptions = {
cwd: options.cwd || process.cwd(),
env: { ...process.env, CI: '1' },
...options
};
return (options.packageManager === 'yarn' ? spawnYarn : spawnNpm)(args, options, spawnOptions);
};
/** Load and validate package file and tests. */
const loadPackageFile = async (options) => {
let pkg, pkgFile;
// assert no --packageData or --packageFile
if (options.packageData || options.packageFile) {
throw new Error('--packageData and --packageFile are not allowed with --doctor. You must execute "ncu --doctor" in a directory with a package file so it can install dependencies and test them.');
}
// assert package.json
try {
pkgFile = fs.readFileSync('package.json', 'utf-8');
pkg = JSON.parse(pkgFile);
}
catch (e) {
throw new Error('Missing or invalid package.json');
}
// assert npm script "test"
if (!pkg.scripts || !pkg.scripts.test) {
throw new Error('No npm "test" script defined. You must define a "test" script in the "scripts" section of your package.json to use --doctor.');
}
return { pkg, pkgFile };
};
/** Iteratively installs upgrades and runs tests to identify breaking upgrades. */
// we have to pass run directly since it would be a circular require if doctor included this file
const doctor = async (run, options) => {
const lockFileName = options.packageManager === 'yarn' ? 'yarn.lock' : 'package-lock.json';
const { pkg, pkgFile } = await loadPackageFile(options);
const allDependencies = {
...pkg.dependencies,
...pkg.devDependencies,
...pkg.optionalDependencies,
...pkg.bundleDependencies,
};
console.log(`Running tests before upgrading`);
// install and load lock file
await npm(['install'], { packageManager: options.packageManager }, true);
let lockFile;
try {
lockFile = fs.readFileSync(lockFileName, 'utf-8');
}
catch (e) {
}
// make sure current tests pass before we begin
try {
await npm(['run', 'test'], {
packageManager: options.packageManager,
stderr: data => console.error(chalk.red(data.toString()))
}, true);
}
catch (e) {
throw new Error('Tests failed before we even got started!');
}
console.log(`Upgrading all dependencies and re-running tests`);
// upgrade all dependencies
// save upgrades for later in case we need to iterate
console.log(chalk.blue('ncu ' + process.argv.slice(2).filter(arg => arg !== '--doctor').join(' ')));
const upgrades = await run({
...options,
silent: true,
doctor: false, // --doctor triggers the initial call to doctor, but the internal call executes npm-check-updates normally in order to upgrade the dependencies
});
if (Object.keys(upgrades).length === 0) {
console.log('All dependencies are up-to-date ' + chalk.green.bold(':)'));
return;
}
// npm install
await npm(['install'], { packageManager: options.packageManager }, true);
// run tests on all upgrades
try {
await npm(['run', 'test'], {
packageManager: options.packageManager
}, true);
console.log(`${chalk.green('✓')} Tests pass`);
const numUpgraded = Object.keys(upgrades).length;
printUpgrades(options, {
current: allDependencies,
upgraded: upgrades,
numUpgraded,
total: numUpgraded
});
console.log('\nAll dependencies upgraded and installed ' + chalk.green(':)'));
}
catch (e) {
console.error(chalk.red('Tests failed'));
console.log(`Identifying broken dependencies`);
// restore package file, lockFile and re-install
fs.writeFileSync('package.json', pkgFile);
if (lockFile) {
fs.writeFileSync(lockFileName, lockFile);
}
else {
rimraf.sync(lockFileName);
}
// save the last package file with passing tests
let lastPkgFile = pkgFile;
await npm(['install'], { packageManager: options.packageManager }, true);
// iterate upgrades
for (const [name, version] of Object.entries(upgrades)) { // eslint-disable-line fp/no-loops
// install single dependency
await npm([...options.packageManager === 'yarn' ? ['add'] : ['install', '--no-save'], `${name}@${version}`], { packageManager: options.packageManager }, true);
try {
await npm(['run', 'test'], { packageManager: options.packageManager }, true);
console.log(` ${chalk.green('✓')} ${name} ${allDependencies[name]} → ${version}`);
// save upgraded package data so that passing versions can still be saved even when there is a failure
lastPkgFile = (await upgradePackageData(lastPkgFile, { [name]: allDependencies[name] }, { [name]: version })).newPkgData;
// save working lock file
lockFile = fs.readFileSync(lockFileName, 'utf-8');
}
catch (e) {
// print failing package
console.error(` ${chalk.red('✗')} ${name} ${allDependencies[name]} → ${version}\n`);
console.error(chalk.red(e));
// restore last good lock file
fs.writeFileSync(lockFileName, lockFile);
// restore package.json since yarn doesn't have --no-save option
if (options.packageManager === 'yarn') {
fs.writeFileSync('package.json', lastPkgFile);
}
}
}
// silently restore last passing package file and lock file
// only print message if package file is updated
if (lastPkgFile !== pkgFile) {
console.log('Saving partially upgraded package.json');
fs.writeFileSync('package.json', lastPkgFile);
}
// re-install from restored package.json and lockfile
await npm(['install'], { packageManager: options.packageManager }, true);
}
};
module.exports = doctor;
//# sourceMappingURL=doctor.js.map