renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
488 lines • 23.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.determineLockFileDirs = determineLockFileDirs;
exports.writeExistingFiles = writeExistingFiles;
exports.writeUpdatedPackageFiles = writeUpdatedPackageFiles;
exports.updateYarnBinary = updateYarnBinary;
exports.getAdditionalFiles = getAdditionalFiles;
const tslib_1 = require("tslib");
// TODO: types (#22198)
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const deepmerge_1 = tslib_1.__importDefault(require("deepmerge"));
const upath_1 = tslib_1.__importDefault(require("upath"));
const logger_1 = require("../../../../logger");
const external_host_error_1 = require("../../../../types/errors/external-host-error");
const env_1 = require("../../../../util/env");
const fs_1 = require("../../../../util/fs");
const git_1 = require("../../../../util/git");
const hostRules = tslib_1.__importStar(require("../../../../util/host-rules"));
const regex_1 = require("../../../../util/regex");
const url_1 = require("../../../../util/url");
const yaml_1 = require("../../../../util/yaml");
const npm_1 = require("../../../datasource/npm");
const scm_1 = require("../../../platform/scm");
const yarn_1 = require("../extract/yarn");
const utils_1 = require("../utils");
const npm = tslib_1.__importStar(require("./npm"));
const pnpm = tslib_1.__importStar(require("./pnpm"));
const rules_1 = require("./rules");
const yarn = tslib_1.__importStar(require("./yarn"));
// Strips empty values, deduplicates, and returns the directories from filenames
const getDirs = (arr) => Array.from(new Set(arr.filter(is_1.default.string)));
function determineLockFileDirs(config, packageFiles) {
const npmLockDirs = [];
const yarnLockDirs = [];
const pnpmShrinkwrapDirs = [];
for (const upgrade of config.upgrades) {
if (upgrade.updateType === 'lockFileMaintenance' ||
upgrade.isRemediation === true ||
upgrade.isLockfileUpdate === true) {
yarnLockDirs.push(upgrade.managerData?.yarnLock);
npmLockDirs.push(upgrade.managerData?.npmLock);
pnpmShrinkwrapDirs.push(upgrade.managerData?.pnpmShrinkwrap);
}
}
if (config.upgrades.every((upgrade) => upgrade.updateType === 'lockFileMaintenance' ||
upgrade.isLockfileUpdate)) {
return {
yarnLockDirs: getDirs(yarnLockDirs),
npmLockDirs: getDirs(npmLockDirs),
pnpmShrinkwrapDirs: getDirs(pnpmShrinkwrapDirs),
};
}
function getPackageFile(fileName) {
logger_1.logger.trace('Looking for packageFile: ' + fileName);
for (const packageFile of packageFiles.npm) {
if (packageFile.packageFile === fileName) {
logger_1.logger.trace({ packageFile }, 'Found packageFile');
return packageFile;
}
logger_1.logger.trace('No match');
}
return {};
}
// TODO #22198
for (const p of config.updatedPackageFiles) {
logger_1.logger.trace(`Checking ${String(p.path)} for lock files`);
const packageFile = getPackageFile(p.path);
// istanbul ignore if
if (!packageFile.managerData) {
continue;
}
// push full lock file names and convert them later
yarnLockDirs.push(packageFile.managerData.yarnLock);
npmLockDirs.push(packageFile.managerData.npmLock);
pnpmShrinkwrapDirs.push(packageFile.managerData.pnpmShrinkwrap);
}
return {
yarnLockDirs: getDirs(yarnLockDirs),
npmLockDirs: getDirs(npmLockDirs),
pnpmShrinkwrapDirs: getDirs(pnpmShrinkwrapDirs),
};
}
async function writeExistingFiles(config, packageFiles) {
if (!packageFiles.npm) {
return;
}
const npmFiles = packageFiles.npm;
logger_1.logger.debug({ packageFiles: npmFiles.map((n) => n.packageFile) }, 'Writing package.json files');
for (const packageFile of npmFiles) {
// istanbul ignore if
if (!packageFile.managerData) {
continue;
}
// TODO #22198
const basedir = upath_1.default.dirname(packageFile.packageFile);
const npmrc = packageFile.npmrc;
const npmrcFilename = upath_1.default.join(basedir, '.npmrc');
// Write out the file unless the npmrc came from the workspace
// npmrcFilename will be set whenever the file was read from disk during extract
if (is_1.default.string(npmrc) &&
(npmrcFilename === packageFile.managerData.npmrcFileName ||
!packageFile.managerData.npmrcFileName)) {
try {
await (0, fs_1.writeLocalFile)(npmrcFilename, npmrc.replace(/\n?$/, '\n'));
}
catch (err) /* istanbul ignore next */ {
logger_1.logger.warn({ npmrcFilename, err }, 'Error writing .npmrc');
}
}
const npmLock = packageFile.managerData.npmLock;
if (npmLock) {
const npmLockPath = npmLock;
logger_1.logger.debug(`Writing ${npmLock}`);
let existingNpmLock;
try {
existingNpmLock = (await (0, git_1.getFile)(npmLock)) ?? '';
}
catch (err) /* istanbul ignore next */ {
logger_1.logger.warn({ err }, 'Error reading npm lock file');
existingNpmLock = '';
}
const { detectedIndent, lockFileParsed: npmLockParsed } = (0, utils_1.parseLockFile)(existingNpmLock);
if (npmLockParsed) {
const packageNames = 'packages' in npmLockParsed
? Object.keys(npmLockParsed.packages)
: [];
const widens = [];
let lockFileChanged = false;
for (const upgrade of config.upgrades) {
if (upgrade.lockFiles && !upgrade.lockFiles.includes(npmLock)) {
continue;
}
if (!upgrade.managerData) {
continue;
}
if (upgrade.rangeStrategy === 'widen' &&
upgrade.managerData.npmLock === npmLock) {
// TODO #22198
widens.push(upgrade.depName);
}
const { depName } = upgrade;
for (const packageName of packageNames) {
if ('packages' in npmLockParsed &&
(packageName === `node_modules/${depName}` ||
packageName.startsWith(`node_modules/${depName}/`))) {
logger_1.logger.trace({ packageName }, 'Massaging out package name');
lockFileChanged = true;
delete npmLockParsed.packages[packageName];
}
}
}
if (widens.length) {
logger_1.logger.debug(`Removing ${String(widens)} from ${npmLock} to force an update`);
lockFileChanged = true;
try {
if ('dependencies' in npmLockParsed && npmLockParsed.dependencies) {
widens.forEach((depName) => {
// TODO #22198
delete npmLockParsed.dependencies[depName];
});
}
}
catch /* istanbul ignore next */ {
logger_1.logger.warn({ npmLock }, 'Error massaging package-lock.json for widen');
}
}
if (lockFileChanged) {
logger_1.logger.debug('Massaging npm lock file before writing to disk');
existingNpmLock = (0, utils_1.composeLockFile)(npmLockParsed, detectedIndent);
}
await (0, fs_1.writeLocalFile)(npmLockPath, existingNpmLock);
}
}
}
}
async function writeUpdatedPackageFiles(config) {
logger_1.logger.trace({ config }, 'writeUpdatedPackageFiles');
logger_1.logger.debug('Writing any updated package files');
if (!config.updatedPackageFiles) {
logger_1.logger.debug('No files found');
return;
}
const supportedLockFiles = ['package-lock.json', 'yarn.lock'];
for (const packageFile of config.updatedPackageFiles) {
if (packageFile.type !== 'addition') {
continue;
}
if (supportedLockFiles.some((fileName) => packageFile.path.endsWith(fileName))) {
logger_1.logger.debug(`Writing lock file: ${packageFile.path}`);
// TODO #22198
await (0, fs_1.writeLocalFile)(packageFile.path, packageFile.contents);
continue;
}
if (!(packageFile.path.endsWith('package.json') ||
packageFile.path.endsWith('pnpm-workspace.yaml'))) {
continue;
}
logger_1.logger.debug(`Writing ${packageFile.path}`);
await (0, fs_1.writeLocalFile)(packageFile.path, packageFile.contents);
}
}
// istanbul ignore next
async function updateYarnOffline(lockFileDir, updatedArtifacts) {
try {
const resolvedPaths = [];
const yarnrcYml = await (0, git_1.getFile)(upath_1.default.join(lockFileDir, '.yarnrc.yml'));
const yarnrc = await (0, git_1.getFile)(upath_1.default.join(lockFileDir, '.yarnrc'));
// As .yarnrc.yml overrides .yarnrc in Yarn 1 (https://git.io/JUcco)
// both files may exist, so check for .yarnrc.yml first
if (yarnrcYml) {
// Yarn 2 (offline cache and zero-installs)
const paths = (0, yarn_1.getZeroInstallPaths)(yarnrcYml);
resolvedPaths.push(...paths.map((p) => upath_1.default.join(lockFileDir, p)));
}
else if (yarnrc) {
// Yarn 1 (offline mirror)
const mirrorLine = yarnrc
.split(regex_1.newlineRegex)
.find((line) => line.startsWith('yarn-offline-mirror '));
if (mirrorLine) {
const mirrorPath = (0, url_1.ensureTrailingSlash)(mirrorLine.split(' ')[1].replace((0, regex_1.regEx)(/"/g), ''));
resolvedPaths.push(upath_1.default.join(lockFileDir, mirrorPath));
}
}
logger_1.logger.debug({ resolvedPaths }, 'updateYarnOffline resolvedPaths');
if (resolvedPaths.length) {
const status = await (0, git_1.getRepoStatus)();
for (const f of status.modified.concat(status.not_added)) {
if (resolvedPaths.some((p) => f.startsWith(p))) {
updatedArtifacts.push({
type: 'addition',
path: f,
contents: await (0, fs_1.readLocalFile)(f),
});
}
}
for (const f of status.deleted || []) {
if (resolvedPaths.some((p) => f.startsWith(p))) {
updatedArtifacts.push({ type: 'deletion', path: f });
}
}
}
}
catch (err) {
logger_1.logger.error({ err }, 'Error updating yarn offline packages');
}
}
// TODO: move to ./yarn.ts
// exported for testing
async function updateYarnBinary(lockFileDir, updatedArtifacts, existingYarnrcYmlContent) {
let yarnrcYml = existingYarnrcYmlContent;
try {
const yarnrcYmlFilename = upath_1.default.join(lockFileDir, '.yarnrc.yml');
yarnrcYml ??= (await (0, git_1.getFile)(yarnrcYmlFilename)) ?? undefined;
const newYarnrcYml = await (0, fs_1.readLocalFile)(yarnrcYmlFilename, 'utf8');
if (!is_1.default.string(yarnrcYml) || !is_1.default.string(newYarnrcYml)) {
return existingYarnrcYmlContent;
}
// TODO: use schema (#9610)
const oldYarnPath = (0, yaml_1.parseSingleYaml)(yarnrcYml)?.yarnPath;
const newYarnPath = (0, yaml_1.parseSingleYaml)(newYarnrcYml)?.yarnPath;
if (!is_1.default.nonEmptyStringAndNotWhitespace(oldYarnPath) ||
!is_1.default.nonEmptyStringAndNotWhitespace(newYarnPath)) {
return existingYarnrcYmlContent;
}
const oldYarnFullPath = upath_1.default.join(lockFileDir, oldYarnPath);
const newYarnFullPath = upath_1.default.join(lockFileDir, newYarnPath);
logger_1.logger.debug({ oldYarnPath, newYarnPath }, 'Found updated Yarn binary');
yarnrcYml = yarnrcYml.replace(oldYarnPath, newYarnPath);
updatedArtifacts.push({
type: 'addition',
path: yarnrcYmlFilename,
contents: yarnrcYml,
}, {
type: 'deletion',
path: oldYarnFullPath,
}, {
type: 'addition',
path: newYarnFullPath,
contents: await (0, fs_1.readLocalFile)(newYarnFullPath, 'utf8'),
isExecutable: true,
});
}
catch (err) /* istanbul ignore next */ {
logger_1.logger.error({ err }, 'Error updating Yarn binary');
}
return existingYarnrcYmlContent && yarnrcYml;
}
async function getAdditionalFiles(config, packageFiles) {
logger_1.logger.trace({ config }, 'getAdditionalFiles');
const artifactErrors = [];
const updatedArtifacts = [];
if (!packageFiles.npm?.length) {
return { artifactErrors, updatedArtifacts };
}
if (!config.updateLockFiles) {
logger_1.logger.debug('Skipping lock file generation');
return { artifactErrors, updatedArtifacts };
}
logger_1.logger.debug('Getting updated lock files');
if (config.isLockFileMaintenance &&
config.reuseExistingBranch &&
(await scm_1.scm.branchExists(config.branchName))) {
logger_1.logger.debug('Skipping lockFileMaintenance update');
return { artifactErrors, updatedArtifacts };
}
const dirs = determineLockFileDirs(config, packageFiles);
logger_1.logger.trace({ dirs }, 'lock file dirs');
await writeExistingFiles(config, packageFiles);
await writeUpdatedPackageFiles(config);
const { additionalNpmrcContent, additionalYarnRcYml } = (0, rules_1.processHostRules)();
// This isn't passed directly to the child process, so no need to filter.
// But pass custom env and user vars.
const env = {
...(0, env_1.getEnv)(),
NPM_CONFIG_CACHE: await (0, fs_1.ensureCacheDir)('npm'),
YARN_CACHE_FOLDER: await (0, fs_1.ensureCacheDir)('yarn'),
YARN_GLOBAL_FOLDER: await (0, fs_1.ensureCacheDir)('berry'),
npm_config_store: await (0, fs_1.ensureCacheDir)('pnpm'),
NODE_ENV: 'dev',
};
let token;
try {
({ token } = hostRules.find({
hostType: 'github',
url: 'https://api.github.com/',
}));
token = token ? /* istanbul ignore next */ `${token}@` : token;
}
catch (err) /* istanbul ignore next */ {
logger_1.logger.warn({ err }, 'Error getting token for packageFile');
}
const tokenRe = (0, regex_1.regEx)(`${token ?? ''}`, 'g', false);
for (const npmLock of dirs.npmLockDirs) {
const lockFileDir = upath_1.default.dirname(npmLock);
const npmrcContent = await (0, utils_1.getNpmrcContent)(lockFileDir);
await (0, utils_1.updateNpmrcContent)(lockFileDir, npmrcContent, additionalNpmrcContent);
const fileName = upath_1.default.basename(npmLock);
logger_1.logger.debug(`Generating ${fileName} for ${lockFileDir}`);
const upgrades = config.upgrades.filter((upgrade) => upgrade.managerData?.npmLock === npmLock);
const res = await npm.generateLockFile(lockFileDir, env, fileName, config, upgrades);
if (res.error) {
// istanbul ignore if
if (res.stderr?.includes('No matching version found for')) {
for (const upgrade of config.upgrades) {
if (res.stderr.includes(`No matching version found for ${upgrade.depName}`)) {
logger_1.logger.debug({ dependency: upgrade.depName, type: 'npm' }, 'lock file failed for the dependency being updated - skipping branch creation');
const err = new Error('lock file failed for the dependency being updated - skipping branch creation');
throw new external_host_error_1.ExternalHostError(err, npm_1.NpmDatasource.id);
}
}
}
artifactErrors.push({
lockFile: npmLock,
stderr: res.stderr,
});
}
else if (res.lockFile) {
const existingContent = await (0, git_1.getFile)(npmLock, config.reuseExistingBranch ? config.branchName : config.baseBranch);
if (res.lockFile === existingContent) {
logger_1.logger.debug(`${npmLock} hasn't changed`);
}
else {
logger_1.logger.debug(`${npmLock} needs updating`);
updatedArtifacts.push({
type: 'addition',
path: npmLock,
// TODO: can this be undefined? (#22198)
contents: res.lockFile.replace(tokenRe, ''),
});
}
}
await (0, utils_1.resetNpmrcContent)(lockFileDir, npmrcContent);
}
for (const yarnLock of dirs.yarnLockDirs) {
const lockFileDir = upath_1.default.dirname(yarnLock);
const npmrcContent = await (0, utils_1.getNpmrcContent)(lockFileDir);
await (0, utils_1.updateNpmrcContent)(lockFileDir, npmrcContent, additionalNpmrcContent);
let yarnRcYmlFilename;
let existingYarnrcYmlContent;
if (additionalYarnRcYml) {
yarnRcYmlFilename = (0, fs_1.getSiblingFileName)(yarnLock, '.yarnrc.yml');
existingYarnrcYmlContent = await (0, fs_1.readLocalFile)(yarnRcYmlFilename, 'utf8');
if (existingYarnrcYmlContent) {
try {
// TODO: use schema (#9610)
const existingYarnrRcYml = (0, yaml_1.parseSingleYaml)(existingYarnrcYmlContent);
const updatedYarnYrcYml = (0, deepmerge_1.default)(existingYarnrRcYml, yarn.fuzzyMatchAdditionalYarnrcYml(additionalYarnRcYml, existingYarnrRcYml));
await (0, fs_1.writeLocalFile)(yarnRcYmlFilename, (0, yaml_1.dump)(updatedYarnYrcYml));
logger_1.logger.debug('Added authentication to .yarnrc.yml');
}
catch (err) {
logger_1.logger.warn({ err }, 'Error appending .yarnrc.yml content');
}
}
}
logger_1.logger.debug(`Generating yarn.lock for ${lockFileDir}`);
const lockFileName = upath_1.default.join(lockFileDir, 'yarn.lock');
const upgrades = config.upgrades.filter((upgrade) => upgrade.managerData?.yarnLock === yarnLock);
const res = await yarn.generateLockFile(lockFileDir, env, config, upgrades);
if (res.error) {
// istanbul ignore if
if (res.stderr?.includes(`Couldn't find any versions for`)) {
for (const upgrade of config.upgrades) {
/* eslint-disable no-useless-escape */
if (res.stderr.includes(`Couldn't find any versions for \\\"${upgrade.depName}\\\"`)) {
logger_1.logger.debug({ dependency: upgrade.depName, type: 'yarn' }, 'lock file failed for the dependency being updated - skipping branch creation');
throw new external_host_error_1.ExternalHostError(new Error('lock file failed for the dependency being updated - skipping branch creation'), npm_1.NpmDatasource.id);
}
/* eslint-enable no-useless-escape */
}
}
artifactErrors.push({
lockFile: yarnLock,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
stderr: res.stderr || res.stdout,
});
}
else {
const existingContent = await (0, git_1.getFile)(lockFileName, config.reuseExistingBranch ? config.branchName : config.baseBranch);
if (res.lockFile === existingContent) {
logger_1.logger.debug("yarn.lock hasn't changed");
}
else {
logger_1.logger.debug('yarn.lock needs updating');
updatedArtifacts.push({
type: 'addition',
path: lockFileName,
// TODO #22198
contents: res.lockFile,
});
await updateYarnOffline(lockFileDir, updatedArtifacts);
}
// istanbul ignore if: already tested seperately, needs additional test?
if (upgrades.some(yarn.isYarnUpdate)) {
existingYarnrcYmlContent = await updateYarnBinary(lockFileDir, updatedArtifacts, existingYarnrcYmlContent);
}
}
await (0, utils_1.resetNpmrcContent)(lockFileDir, npmrcContent);
// istanbul ignore if: needs test
if (existingYarnrcYmlContent) {
// TODO #22198
await (0, fs_1.writeLocalFile)(yarnRcYmlFilename, existingYarnrcYmlContent);
}
}
for (const pnpmShrinkwrap of dirs.pnpmShrinkwrapDirs) {
const lockFileDir = upath_1.default.dirname(pnpmShrinkwrap);
const npmrcContent = await (0, utils_1.getNpmrcContent)(lockFileDir);
await (0, utils_1.updateNpmrcContent)(lockFileDir, npmrcContent, additionalNpmrcContent);
logger_1.logger.debug(`Generating pnpm-lock.yaml for ${lockFileDir}`);
const upgrades = config.upgrades.filter((upgrade) => upgrade.managerData?.pnpmShrinkwrap === pnpmShrinkwrap);
const res = await pnpm.generateLockFile(lockFileDir, env, config, upgrades);
if (res.error) {
// istanbul ignore if
if (res.stdout?.includes(`No compatible version found:`)) {
for (const upgrade of config.upgrades) {
if (res.stdout.includes(`No compatible version found: ${upgrade.depName}`)) {
logger_1.logger.debug({ dependency: upgrade.depName, type: 'pnpm' }, 'lock file failed for the dependency being updated - skipping branch creation');
throw new external_host_error_1.ExternalHostError(Error('lock file failed for the dependency being updated - skipping branch creation'), npm_1.NpmDatasource.id);
}
}
}
artifactErrors.push({
lockFile: pnpmShrinkwrap,
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
stderr: res.stderr || res.stdout,
});
}
else {
const existingContent = await (0, git_1.getFile)(pnpmShrinkwrap, config.reuseExistingBranch ? config.branchName : config.baseBranch);
if (res.lockFile === existingContent) {
logger_1.logger.debug("pnpm-lock.yaml hasn't changed");
}
else {
logger_1.logger.debug('pnpm-lock.yaml needs updating');
updatedArtifacts.push({
type: 'addition',
path: pnpmShrinkwrap,
// TODO: can be undefined? (#22198)
contents: res.lockFile,
});
}
}
await (0, utils_1.resetNpmrcContent)(lockFileDir, npmrcContent);
}
return { artifactErrors, updatedArtifacts };
}
//# sourceMappingURL=index.js.map