UNPKG

npm-check-updates

Version:

Find newer versions of dependencies than what your package.json allows

290 lines 13.5 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const promises_1 = __importDefault(require("fs/promises")); const rimraf_1 = require("rimraf"); const spawn_please_1 = __importDefault(require("spawn-please")); const logging_1 = require("../lib/logging"); const bun_1 = __importDefault(require("../package-managers/bun")); const npm_1 = __importDefault(require("../package-managers/npm")); const pnpm_1 = __importDefault(require("../package-managers/pnpm")); const yarn_1 = __importDefault(require("../package-managers/yarn")); const chalk_1 = __importStar(require("./chalk")); const loadPackageInfoFromFile_1 = __importDefault(require("./loadPackageInfoFromFile")); const upgradePackageData_1 = __importDefault(require("./upgradePackageData")); /** Run npm, yarn, pnpm, or bun. */ const npm = (args, options, print, { spawnOptions } = {}) => { if (print) { console.log(chalk_1.default.blue([options.packageManager, ...args].join(' '))); } const spawnOptionsMerged = { cwd: options.cwd || process.cwd(), env: { ...process.env, // TODO: Why does CI break pnpm install? ...(options.packageManager !== 'pnpm' ? { CI: '1' } : null), FORCE_COLOR: '1', ...spawnOptions === null || spawnOptions === void 0 ? void 0 : spawnOptions.env, }, ...spawnOptions, }; const npmOptions = { ...(options.global ? { location: 'global' } : null), ...(options.prefix ? { prefix: options.prefix } : null), }; return (options.packageManager === 'pnpm' ? pnpm_1.default : options.packageManager === 'yarn' ? yarn_1.default : options.packageManager === 'bun' ? bun_1.default : npm_1.default)(args, npmOptions, spawnOptionsMerged); }; /** Load and validate package file and tests. */ const loadPackageFileForDoctor = async (options) => { var _a; // assert no --packageData or --packageFile if (options.packageData || options.packageFile) { console.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.'); process.exit(1); } let packageInfo; // assert package.json try { packageInfo = await (0, loadPackageInfoFromFile_1.default)(options, 'package.json'); } catch (e) { console.error('Missing or invalid package.json'); process.exit(1); } // assert npm script "test" (unless a custom test script is specified) if (!options.doctorTest && !((_a = packageInfo.pkg.scripts) === null || _a === void 0 ? void 0 : _a.test)) { console.error('No npm "test" script defined. You must define a "test" script in the "scripts" section of your package.json to use --doctor.'); process.exit(1); } return packageInfo; }; /** 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) => { var _a; await (0, chalk_1.chalkInit)(); const lockFileName = options.packageManager === 'yarn' ? 'yarn.lock' : options.packageManager === 'pnpm' ? 'pnpm-lock.yaml' : options.packageManager === 'bun' ? 'bun.lockb' : 'package-lock.json'; const { pkg, pkgFile } = await loadPackageFileForDoctor(options); // flatten all deps into one so we can iterate over them const allDependencies = { ...pkg.dependencies, ...pkg.devDependencies, ...pkg.optionalDependencies, }; /** Install dependencies using "npm run install" or a custom script given by --doctorInstall. */ const runInstall = async () => { if (options.doctorInstall) { const [installCommand, ...testArgs] = options.doctorInstall.split(' '); console.log(chalk_1.default.blue(options.doctorInstall)); await (0, spawn_please_1.default)(installCommand, testArgs); } else { await npm(['install'], { packageManager: options.packageManager }, true); } }; /** Run the tests using "npm run test" or a custom script given by --doctorTest. */ const runTests = async () => { const spawnOptions = { stderr: (data) => { console.error(chalk_1.default.red(data.toString())); }, // Test runners typically write to stdout, so we need to print stdout. // Otherwise test failures will be silenced. stdout: (data) => { process.stdout.write(data.toString()); }, }; if (options.doctorTest) { const regexp = /"(.+?)"|'(.+?)'|[^ ]+/g; const matches = options.doctorTest.matchAll(regexp); let groups = []; // eslint-disable-next-line fp/no-loops for (const match of matches) { groups = [...groups, match[2] || match[1] || match[0]]; } const [testCommand, ...testArgs] = groups; console.log(chalk_1.default.blue(options.doctorTest)); await (0, spawn_please_1.default)(testCommand, testArgs, spawnOptions); } else { await npm(['run', 'test'], { packageManager: options.packageManager, }, true, { spawnOptions }); } }; console.log(`Running tests before upgrading`); // initial install await runInstall(); // save lock file if there is one let lockFile = ''; try { lockFile = await promises_1.default.readFile(lockFileName, 'utf-8'); } catch (e) { } // make sure current tests pass before we begin try { await runTests(); } catch (e) { console.error('Tests failed before we even got started!'); process.exit(1); } if (!options.interactive) { 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_1.default.blue('ncu ' + process.argv .slice(2) .filter(arg => arg !== '--doctor') .join(' '))); process.env.NCU_DOCTOR = '1'; const upgrades = (await run({ ...options, silent: true, // --doctor triggers the initial call to doctor, but the internal call needs to executes npm-check-updates normally in order to upgrade the dependencies doctor: false, })); if (Object.keys(upgrades || {}).length === 0) { console.log('All dependencies are up-to-date ' + chalk_1.default.green.bold(':)')); return; } // track if installing dependencies was successful // this allows us to skip re-installing when it fails and proceed straight to installing individual dependencies let installAllSuccess = false; // run tests on all upgrades try { // install after all upgrades await runInstall(); installAllSuccess = true; // run tests after all upgrades await runTests(); console.log(`${chalk_1.default.green('✓')} Tests pass`); await (0, logging_1.printUpgrades)(options, { current: allDependencies, upgraded: upgrades, total: Object.keys(upgrades || {}).length, }); console.log(`\n${options.interactive ? 'Chosen' : 'All'} dependencies upgraded and installed ${chalk_1.default.green(':)')}`); } catch { console.error(chalk_1.default.red(installAllSuccess ? 'Tests failed' : 'Install failed')); console.log(`Identifying broken dependencies`); // restore package file, lockFile and re-install await promises_1.default.writeFile('package.json', pkgFile); if (lockFile) { await promises_1.default.writeFile(lockFileName, lockFile); } else { await (0, rimraf_1.rimraf)(lockFileName); } // save the last package file with passing tests let lastPkgFile = pkgFile; // re-install after restoring package file and lock file // only re-install if the tests failed, not if npm install failed if (installAllSuccess) { try { await runInstall(); } catch (e) { const installCommand = (options.packageManager || 'npm') + ' install'; throw new Error(`Error: Doctor mode was about to test individual upgrades, but ${chalk_1.default.cyan(installCommand)} failed after rolling back to your existing package and lock files. This is unexpected since the initial install before any upgrades succeeded. Either npm failed to revert a partial install, or failed anomalously on the second run. Please check your internet connection and retry. If doctor mode fails consistently, report a bug with your complete list of dependency versions at https://github.com/raineorshine/npm-check-updates/issues.`); } } // iterate upgrades let name, version; // eslint-disable-next-line fp/no-loops for ([name, version] of Object.entries(upgrades)) { try { // install single dependency await npm([ ...(options.packageManager === 'yarn' || options.packageManager === 'pnpm' || options.packageManager === 'bun' ? ['add'] : ['install', '--no-save']), `${name}@${version}`, ], { packageManager: options.packageManager }, true); // if there is a prepare script, we need to run it manually since --no-save does not run prepare automatically // https://github.com/raineorshine/npm-check-updates/issues/1170 if ((_a = pkg.scripts) === null || _a === void 0 ? void 0 : _a.prepare) { try { await npm(['run', 'prepare'], { packageManager: options.packageManager }, true); } catch (e) { console.error(chalk_1.default.red('Prepare script failed')); throw e; } } // run tests after individual upgrade await runTests(); console.log(` ${chalk_1.default.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 (0, upgradePackageData_1.default)(lastPkgFile, { [name]: allDependencies[name] }, { [name]: version }, options); // save working lock file lockFile = await promises_1.default.readFile(lockFileName, 'utf-8'); } catch (e) { // print failing package console.error(` ${chalk_1.default.red('✗')} ${name} ${allDependencies[name]}${version}\n`); console.error(chalk_1.default.red(e)); // restore last good lock file await promises_1.default.writeFile(lockFileName, lockFile); // restore package.json since yarn and pnpm do not have the --no-save option if (options.packageManager === 'yarn' || options.packageManager === 'pnpm' || options.packageManager === 'bun') { await promises_1.default.writeFile('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'); await promises_1.default.writeFile('package.json', lastPkgFile); } // re-install from restored package.json and lockfile await runInstall(); } }; exports.default = doctor; //# sourceMappingURL=doctor.js.map