renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
256 lines • 12.8 kB
JavaScript
;
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