UNPKG

renovate

Version:

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

488 lines • 23.7 kB
"use strict"; 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