UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

256 lines • 12.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateLockFile = generateLockFile; exports.divideWorkspaceAndRootDeps = divideWorkspaceAndRootDeps; const tslib_1 = require("tslib"); // TODO: types (#22198) const is_1 = tslib_1.__importDefault(require("@sindresorhus/is")); const semver_1 = tslib_1.__importDefault(require("semver")); const shlex_1 = require("shlex"); const upath_1 = tslib_1.__importDefault(require("upath")); const global_1 = require("../../../../config/global"); const error_messages_1 = require("../../../../constants/error-messages"); const logger_1 = require("../../../../logger"); const exec_1 = require("../../../../util/exec"); const fs_1 = require("../../../../util/fs"); const minimatch_1 = require("../../../../util/minimatch"); const result_1 = require("../../../../util/result"); const url_1 = require("../../../../util/url"); const schema_1 = require("../schema"); const utils_1 = require("../utils"); const node_version_1 = require("./node-version"); const utils_2 = require("./utils"); async function getNpmConstraintFromPackageLock(lockFileDir, filename) { const packageLockFileName = upath_1.default.join(lockFileDir, filename); const packageLockContents = await (0, fs_1.readLocalFile)(packageLockFileName, 'utf8'); const packageLockJson = result_1.Result.parse(packageLockContents, schema_1.PackageLock).unwrapOrNull(); if (!packageLockJson) { logger_1.logger.debug(`Could not parse ${packageLockFileName}`); return null; } const { lockfileVersion } = packageLockJson; if (lockfileVersion === 1) { logger_1.logger.debug(`Using npm constraint <7 for lockfileVersion=1`); return `<7`; } if (lockfileVersion === 2) { logger_1.logger.debug(`Using npm constraint <9 for lockfileVersion=2`); return `<9`; } logger_1.logger.debug(`Using npm constraint >=9 for lockfileVersion=${lockfileVersion}`); return `>=9`; } async function generateLockFile(lockFileDir, env, filename, config = {}, upgrades = []) { // TODO: don't assume package-lock.json is in the same directory const lockFileName = upath_1.default.join(lockFileDir, filename); logger_1.logger.debug(`Spawning npm install to create ${lockFileDir}/${filename}`); const { skipInstalls, postUpdateOptions } = config; let lockFile = null; try { const lazyPkgJson = (0, utils_2.lazyLoadPackageJson)(lockFileDir); const npmToolConstraint = { toolName: 'npm', constraint: config.constraints?.npm ?? (0, utils_2.getPackageManagerVersion)('npm', await lazyPkgJson.getValue()) ?? (await getNpmConstraintFromPackageLock(lockFileDir, filename)) ?? null, }; const supportsPreferDedupeFlag = !npmToolConstraint.constraint || semver_1.default.intersects('>=7.0.0', npmToolConstraint.constraint); let commands = []; let cmdOptions = ''; if ((postUpdateOptions?.includes('npmDedupe') === true && !supportsPreferDedupeFlag) || skipInstalls === false) { logger_1.logger.debug('Performing node_modules install'); cmdOptions += '--no-audit'; } else { logger_1.logger.debug('Updating lock file only'); cmdOptions += '--package-lock-only --no-audit'; } if (postUpdateOptions?.includes('npmDedupe') && supportsPreferDedupeFlag) { logger_1.logger.debug('Deduplicate dependencies on installation'); cmdOptions += ' --prefer-dedupe'; } if (!global_1.GlobalConfig.get('allowScripts') || config.ignoreScripts) { cmdOptions += ' --ignore-scripts'; } const extraEnv = { NPM_CONFIG_CACHE: env.NPM_CONFIG_CACHE, npm_config_store: env.npm_config_store, }; const execOptions = { cwdFile: lockFileName, extraEnv, toolConstraints: [ await (0, node_version_1.getNodeToolConstraint)(config, upgrades, lockFileDir, lazyPkgJson), npmToolConstraint, ], docker: {}, }; /* v8 ignore next 4 -- needs test */ if (global_1.GlobalConfig.get('exposeAllEnv')) { extraEnv.NPM_AUTH = env.NPM_AUTH; extraEnv.NPM_EMAIL = env.NPM_EMAIL; } if (!upgrades.every((upgrade) => upgrade.isLockfileUpdate)) { // This command updates the lock file based on package.json commands.push(`npm install ${cmdOptions}`.trim()); } // rangeStrategy = update-lockfile const lockUpdates = upgrades.filter((upgrade) => upgrade.isLockfileUpdate); // divide the deps in two categories: workspace and root const { lockRootUpdates, lockWorkspacesUpdates, workspaces, rootDeps } = divideWorkspaceAndRootDeps(lockFileDir, lockUpdates); if (workspaces.size && lockWorkspacesUpdates.length) { logger_1.logger.debug('Performing lockfileUpdate (npm-workspaces)'); for (const workspace of workspaces) { const currentWorkspaceUpdates = lockWorkspacesUpdates .filter((update) => update.workspace === workspace) .map((update) => update.managerData?.packageKey) .filter((packageKey) => !rootDeps.has(packageKey)); if (currentWorkspaceUpdates.length) { const updateCmd = `npm install ${cmdOptions} --workspace=${(0, shlex_1.quote)(workspace)} ${currentWorkspaceUpdates .map(shlex_1.quote) .join(' ')}`; commands.push(updateCmd); } } } if (lockRootUpdates.length) { logger_1.logger.debug('Performing lockfileUpdate (npm)'); const updateCmd = `npm install ${cmdOptions} ${lockRootUpdates .map((update) => update.managerData?.packageKey) .map(shlex_1.quote) .join(' ')}`; commands.push(updateCmd); } if (upgrades.some((upgrade) => upgrade.isRemediation)) { // We need to run twice to get the correct lock file commands.push(`npm install ${cmdOptions}`.trim()); } // postUpdateOptions if (config.postUpdateOptions?.includes('npmDedupe') && !supportsPreferDedupeFlag) { logger_1.logger.debug('Performing npm dedupe after installation'); commands.push('npm dedupe'); } if (upgrades.find((upgrade) => upgrade.isLockFileMaintenance)) { logger_1.logger.debug(`Removing ${lockFileName} first due to lock file maintenance upgrade`); try { await (0, fs_1.deleteLocalFile)(lockFileName); /* v8 ignore start -- needs test */ } catch (err) { logger_1.logger.debug({ err, lockFileName }, 'Error removing `package-lock.json` for lock file maintenance'); } /* v8 ignore stop -- needs test */ } if (postUpdateOptions?.includes('npmInstallTwice')) { logger_1.logger.debug('Running npm install twice'); // Run the install command twice to ensure the lock file is up to date // iterate through commands and if any command starts with `npm install`, add it again const existingCommands = [...commands]; commands = []; for (const command of existingCommands) { commands.push(command); if (command.startsWith('npm install')) { commands.push(command); } } } // Run the commands await (0, exec_1.exec)(commands, execOptions); // massage to shrinkwrap if necessary if (filename === 'npm-shrinkwrap.json' && (await (0, fs_1.localPathExists)(upath_1.default.join(lockFileDir, 'package-lock.json')))) { await (0, fs_1.renameLocalFile)(upath_1.default.join(lockFileDir, 'package-lock.json'), upath_1.default.join(lockFileDir, 'npm-shrinkwrap.json')); } // Read the result // TODO #22198 lockFile = (await (0, fs_1.readLocalFile)(upath_1.default.join(lockFileDir, filename), 'utf8')); // Massage lockfile counterparts of package.json that were modified // because npm install was called with an explicit version for rangeStrategy=update-lockfile if (lockUpdates.length) { const { detectedIndent, lockFileParsed } = (0, utils_1.parseLockFile)(lockFile); if (lockFileParsed?.lockfileVersion === 2 || lockFileParsed?.lockfileVersion === 3) { lockUpdates.forEach((lockUpdate) => { const depType = lockUpdate.depType; // TODO #22198 if (lockFileParsed.packages?.['']?.[depType]?.[lockUpdate.packageName]) { lockFileParsed.packages[''][depType][lockUpdate.packageName] = lockUpdate.newValue; } }); lockFile = (0, utils_1.composeLockFile)(lockFileParsed, detectedIndent); } } /* v8 ignore start -- needs test */ } catch (err) { if (err.message === error_messages_1.TEMPORARY_ERROR) { throw err; } logger_1.logger.debug({ err, type: 'npm', }, 'lock file error'); if (err.stderr?.includes('ENOSPC: no space left on device')) { throw new Error(error_messages_1.SYSTEM_INSUFFICIENT_DISK_SPACE); } return { error: true, stderr: err.stderr }; } /* v8 ignore stop -- needs test */ return { error: !lockFile, lockFile }; } function divideWorkspaceAndRootDeps(lockFileDir, lockUpdates) { const lockRootUpdates = []; // stores all upgrades which are present in root package.json const lockWorkspacesUpdates = []; // stores all upgrades which are present in workspaces package.json const workspaces = new Set(); // name of all workspaces const rootDeps = new Set(); // packageName of all upgrades in root package.json (makes it check duplicate deps in root) // divide the deps in two categories: workspace and root for (const upgrade of lockUpdates) { upgrade.managerData ??= {}; upgrade.managerData.packageKey = generatePackageKey(upgrade.packageName, upgrade.newVersion); if (upgrade.managerData.workspacesPackages?.length && is_1.default.string(upgrade.packageFile)) { const workspacePatterns = upgrade.managerData.workspacesPackages; // glob pattern or directory name/path const packageFileDir = (0, url_1.trimSlashes)(upgrade.packageFile.replace('package.json', '')); // workspaceDir = packageFileDir - lockFileDir const workspaceDir = (0, url_1.trimSlashes)(packageFileDir.startsWith(lockFileDir) ? packageFileDir.slice(lockFileDir.length) : packageFileDir); if (is_1.default.nonEmptyString(workspaceDir)) { let workspaceName; // compare workspaceDir to workspace patterns // stop when the first match is found and // add workspaceDir to workspaces set and upgrade object for (const workspacePattern of workspacePatterns) { const massagedPattern = workspacePattern.replace(/^\.\//, ''); if ((0, minimatch_1.minimatch)(massagedPattern).match(workspaceDir)) { workspaceName = workspaceDir; break; } } if (workspaceName) { if (!rootDeps.has(upgrade.managerData.packageKey) // prevent same dep from existing in root and workspace ) { workspaces.add(workspaceName); upgrade.workspace = workspaceName; lockWorkspacesUpdates.push(upgrade); } } else { logger_1.logger.warn({ workspacePatterns, workspaceDir }, 'workspaceDir not found'); } continue; } } lockRootUpdates.push(upgrade); rootDeps.add(upgrade.managerData.packageKey); } return { lockRootUpdates, lockWorkspacesUpdates, workspaces, rootDeps }; } function generatePackageKey(packageName, version) { return `${packageName}@${version}`; } //# sourceMappingURL=npm.js.map