UNPKG

npm-check-updates

Version:

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

316 lines 16.3 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 }); exports.run = void 0; const isString_1 = __importDefault(require("lodash/isString")); const path_1 = __importDefault(require("path")); const prompts_ncu_1 = __importDefault(require("prompts-ncu")); const spawn_please_1 = __importDefault(require("spawn-please")); const cli_options_1 = require("./cli-options"); const cache_1 = require("./lib/cache"); const chalk_1 = __importStar(require("./lib/chalk")); const determinePackageManager_1 = __importDefault(require("./lib/determinePackageManager")); const doctor_1 = __importDefault(require("./lib/doctor")); const exists_1 = __importDefault(require("./lib/exists")); const findPackage_1 = __importDefault(require("./lib/findPackage")); const getAllPackages_1 = __importDefault(require("./lib/getAllPackages")); const getNcuRc_1 = __importDefault(require("./lib/getNcuRc")); const initOptions_1 = __importDefault(require("./lib/initOptions")); const logging_1 = require("./lib/logging"); const mergeOptions_1 = __importDefault(require("./lib/mergeOptions")); const programError_1 = __importDefault(require("./lib/programError")); const runGlobal_1 = __importDefault(require("./lib/runGlobal")); const runLocal_1 = __importDefault(require("./lib/runLocal")); // allow prompt injection from environment variable for testing purposes if (process.env.INJECT_PROMPTS) { prompts_ncu_1.default.inject(JSON.parse(process.env.INJECT_PROMPTS)); } // Exit with non-zero error code when there is an unhandled promise rejection. // Use `node --trace-uncaught ...` to show where the exception was thrown. // See: https://nodejs.org/api/process.html#event-unhandledrejection process.on('unhandledRejection', (reason) => { // do not rethrow, as there may be other errors to print out console.error(reason); }); /** * Volta is a tool for managing JavaScript tooling like Node and npm. Volta has * its own system for installing global packages which circumvents npm, so * commands like `npm ls -g` do not accurately reflect what is installed. * * The ability to use `npm ls -g` is tracked in this Volta issue: https://github.com/volta-cli/volta/issues/1012 */ const noVolta = (options) => { var _a; // The first check is for macOS/Linux and the second check is for Windows if (options.global && (!!process.env.VOLTA_HOME || ((_a = process.env.PATH) === null || _a === void 0 ? void 0 : _a.includes('\\Volta')))) { const message = 'It appears you are using Volta. `npm-check-updates --global` ' + 'cannot be used with Volta because Volta has its own system for ' + 'managing global packages which circumvents npm.\n\n' + 'If you are still receiving this message after uninstalling Volta, ' + 'ensure your PATH does not contain an entry for Volta and your ' + 'shell profile does not define VOLTA_HOME. You may need to reboot ' + 'for changes to your shell profile to take effect.'; (0, logging_1.print)(options, message, 'error'); process.exit(1); } }; /** Returns the package manager that should be used to install packages after running "ncu -u". Detects pnpm via pnpm-lock.yaml. This is the one place that pnpm needs to be detected, since otherwise it is backwards compatible with npm. */ const getPackageManagerForInstall = async (options, pkgFile) => { var _a; // when packageManager is set to staticRegistry, we need to infer the package manager from lock files if (options.packageManager === 'staticRegistry') (0, determinePackageManager_1.default)({ ...options, packageManager: undefined }); else if (options.packageManager !== 'npm') return options.packageManager; const cwd = ((_a = options.cwd) !== null && _a !== void 0 ? _a : pkgFile) ? `${pkgFile}/..` : process.cwd(); const pnpmDetected = await (0, exists_1.default)(path_1.default.join(cwd, 'pnpm-lock.yaml')); return pnpmDetected ? 'pnpm' : 'npm'; }; /** Returns if analysis contains upgrades */ const someUpgraded = (pkgs, analysis) => { // deep mode analysis is of type Index<PackageFile> // non-deep mode analysis is of type <PackageFile>, so we normalize it to Index<PackageFile> const analysisNormalized = pkgs.length === 1 ? { [pkgs[0]]: analysis } : analysis; return Object.values(analysisNormalized).some(upgrades => Object.keys(upgrades).length > 0); }; /** Either suggest an install command based on the package manager, or in interactive mode, prompt to auto-install. */ const install = async (pkgs, analysis, options) => { var _a; if (options.install === 'never') { (0, logging_1.print)(options, ''); return; } // if no packages were upgraded (i.e. all dependencies deselected in interactive mode), then bail without suggesting an install. // normalize the analysis for one or many packages if (!someUpgraded(pkgs, analysis)) return; // for the purpose of the install hint, just use the package manager used in the first sub-project // if auto-installing, the actual package manager in each sub-project will be used const packageManager = await getPackageManagerForInstall(options, pkgs[0]); // by default, show an install hint after upgrading // this will be disabled in interactive mode if the user chooses to have npm-check-updates execute the install command const installHint = `Run ${chalk_1.default.cyan(packageManager + ' install')}${pkgs.length > 1 && !options.workspace && !options.workspaces ? ' in each project directory' : ''} to install new versions`; const isInteractive = options.interactive && !process.env.NCU_DOCTOR; // prompt the user if they want ncu to run "npm install" let response; if (isInteractive && options.install === 'prompt') { (0, logging_1.print)(options, ''); response = await (0, prompts_ncu_1.default)({ type: 'confirm', name: 'value', message: `${installHint}?`, initial: true, // allow Ctrl+C to kill the process onState: (state) => { if (state.aborted) { process.nextTick(() => process.exit(1)); } }, }); } // auto-install if (options.install === 'always' || (isInteractive && response.value)) { if (options.install === 'always') { (0, logging_1.print)(options, ''); } (0, logging_1.print)(options, 'Installing dependencies...'); // only run npm install once in the root when in workspace mode // npm install will install packages for all workspaces const isWorkspace = options.workspaces || !!((_a = options.workspace) === null || _a === void 0 ? void 0 : _a.length); const pkgsNormalized = isWorkspace ? ['package.json'] : pkgs; pkgsNormalized.forEach(async (pkgFile) => { const packageManager = await getPackageManagerForInstall(options, pkgFile); const cmd = packageManager + (process.platform === 'win32' && packageManager !== 'bun' ? '.cmd' : ''); const cwd = options.cwd || path_1.default.resolve(pkgFile, '..'); let stdout = ''; try { await (0, spawn_please_1.default)(cmd, ['install'], { cwd, env: { ...process.env, ...(options.color !== false ? { FORCE_COLOR: true } : null), // With spawn, pnpm install will fail with ERR_PNPM_PEER_DEP_ISSUES Unmet peer dependencies. // When pnpm install is run directly from the terminal, this error does not occur. // When pnpm install is run from a simple spawn script, this error does not occur. // The issue only seems to be when pnpm install is executed from npm-check-updates, but it's not clear what configuration or environmental factors are causing this. // For now, turn off strict-peer-dependencies on pnpm auto-install. // See: https://github.com/raineorshine/npm-check-updates/issues/1191 ...(packageManager === 'pnpm' ? { npm_config_strict_peer_dependencies: false } : null), }, stdout: (data) => { stdout += data; }, stderr: (data) => { console.error(chalk_1.default.red(data.toString())); }, }); (0, logging_1.print)(options, stdout); (0, logging_1.print)(options, 'Done'); } catch (err) { // sometimes packages print errors to stdout instead of stderr // if there is nothing on stderr, reject with stdout throw new Error((err === null || err === void 0 ? void 0 : err.message) || err || stdout); } }); } // show the install hint unless auto-install occurred else if (!isInteractive) { (0, logging_1.print)(options, `\n${installHint}.`); } }; /** Runs the dependency upgrades. Loads the ncurc, finds the package file, and handles --deep. */ async function runUpgrades(options, timeout) { var _a; const [selectedPackageInfos, workspacePackages] = await (0, getAllPackages_1.default)(options); const packageFilepaths = selectedPackageInfos.map((packageInfo) => packageInfo.filepath); // enable deep mode if --deep, --workspace, --workspaces, or if multiple package files are found const isWorkspace = options.workspaces || !!((_a = options.workspace) === null || _a === void 0 ? void 0 : _a.length); options.deep = options.deep || isWorkspace || selectedPackageInfos.length > 1; let analysis; if (options.global) { const analysis = await (0, runGlobal_1.default)(options); clearTimeout(timeout); return analysis; } else if (options.deep) { analysis = await selectedPackageInfos.reduce(async (previousPromise, packageInfo) => { const packages = await previousPromise; // copy object to prevent share .ncurc options between different packageFile, to prevent unpredictable behavior const rcResult = await (0, getNcuRc_1.default)({ packageFile: packageInfo.filepath, color: options.color }); let rcConfig = rcResult && rcResult.config ? rcResult.config : {}; if (options.mergeConfig && Object.keys(rcConfig).length) { // Merge config options. rcConfig = (0, mergeOptions_1.default)(options, rcConfig); } const pkgOptions = { ...options, ...rcConfig, packageFile: packageInfo.filepath, workspacePackages, }; const { pkgData, pkgFile } = await (0, findPackage_1.default)(pkgOptions); return { ...packages, // index by relative path if cwd was specified [pkgOptions.cwd ? path_1.default .relative(path_1.default.resolve(pkgOptions.cwd), pkgFile) // convert Windows path to *nix path for consistency .replace(/\\/g, '/') : pkgFile]: await (0, runLocal_1.default)(pkgOptions, pkgData, pkgFile), }; }, Promise.resolve({})); if (options.json) { (0, logging_1.printJson)(options, analysis); } } else { // mutate packageFile when glob pattern finds only single package if (selectedPackageInfos.length === 1 && selectedPackageInfos[0].filepath !== (options.packageFile || 'package.json')) { options.packageFile = selectedPackageInfos[0].filepath; } const { pkgData, pkgFile } = await (0, findPackage_1.default)(options); analysis = await (0, runLocal_1.default)(options, pkgData, pkgFile); } clearTimeout(timeout); if (options.errorLevel === 2 && someUpgraded(packageFilepaths, analysis)) { (0, programError_1.default)(options, '\nDependencies not up-to-date'); } // suggest install command or auto-install if (options.upgrade) { // deno does not have an install command // The closest equivalent is deno cache, but it is optional. // See: https://deno.land/manual@v1.30.3/references/cheatsheet#nodejs---deno-cheatsheet if (options.packageManager === 'deno') { (0, logging_1.print)(options, ''); } else { await install(packageFilepaths, analysis, options); } } return analysis; } /** Main entry point. * * @returns Promise< * PackageFile Default returns upgraded package file. * | Index<VersionSpec> --jsonUpgraded returns only upgraded dependencies. * | void --global upgrade returns void. * > */ async function run(runOptions = {}, { cli } = {}) { const options = await (0, initOptions_1.default)(runOptions, { cli }); // chalk may already have been initialized in cli.ts, but when imported as a module // chalkInit is idempotent await (0, chalk_1.chalkInit)(options.color); noVolta(options); (0, logging_1.print)(options, 'Initializing', 'verbose'); if (options.cacheClear) { await (0, cache_1.cacheClear)(options); } let timeout; let timeoutPromise = new Promise(() => null); if (options.timeout) { const timeoutMs = (0, isString_1.default)(options.timeout) ? Number.parseInt(options.timeout, 10) : options.timeout; timeoutPromise = new Promise((resolve, reject) => { timeout = setTimeout(() => { // must catch the error and reject explicitly since we are in a setTimeout const error = `Exceeded global timeout of ${timeoutMs}ms`; reject(error); try { (0, programError_1.default)(options, error); } catch (e) { /* noop */ } }, timeoutMs); }); } // doctor mode if (options.doctor) { // execute with -u if (options.upgrade) { // we have to pass run directly since it would be a circular require if doctor included this file return Promise.race([timeoutPromise, (0, doctor_1.default)(run, options)]); } // print help otherwise else { const help = typeof cli_options_1.cliOptionsMap.doctor.help === 'function' ? cli_options_1.cliOptionsMap.doctor.help({}) : cli_options_1.cliOptionsMap.doctor.help; (0, logging_1.print)(options, `Usage: ncu --doctor\n\n${help}`, 'warn'); } } // normal mode else { return Promise.race([timeoutPromise, runUpgrades(options, timeout)]); } } exports.run = run; exports.default = run; //# sourceMappingURL=index.js.map