UNPKG

renovate

Version:

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

423 lines (422 loc) • 18.3 kB
import { getEnv } from "../../../../util/env.js"; import { newlineRegex, regEx } from "../../../../util/regex.js"; import { logger } from "../../../../logger/index.js"; import { ensureTrailingSlash } from "../../../../util/url.js"; import { find } from "../../../../util/host-rules.js"; import { dump, parseSingleYaml } from "../../../../util/yaml.js"; import { ExternalHostError } from "../../../../types/errors/external-host-error.js"; import { ensureCacheDir, getSiblingFileName, readLocalFile, writeLocalFile } from "../../../../util/fs/index.js"; import { getFile, getRepoStatus } from "../../../../util/git/index.js"; import { NpmDatasource } from "../../../datasource/npm/index.js"; import { PNPM_CACHE_BASE_DIR, YARN_CACHE_DIR, YARN_GLOBAL_DIR } from "../constants.js"; import { processHostRules } from "./rules.js"; import { composeLockFile, getNpmrcContent, parseLockFile, resetNpmrcContent, updateNpmrcContent } from "../utils.js"; import { getZeroInstallPaths } from "../extract/yarn.js"; import { scm } from "../../../platform/scm.js"; import { generateLockFile } from "./npm.js"; import { generateLockFile as generateLockFile$1 } from "./pnpm.js"; import { fuzzyMatchAdditionalYarnrcYml, generateLockFile as generateLockFile$2, isYarnUpdate } from "./yarn.js"; import { isNonEmptyStringAndNotWhitespace, isString } from "@sindresorhus/is"; import upath from "upath"; import merge from "deepmerge"; //#region lib/modules/manager/npm/post-update/index.ts const getDirs = (arr) => Array.from(new Set(arr.filter(isString))); function determineLockFileDirs(config, packageFiles) { const npmLockDirs = []; const yarnLockDirs = []; const pnpmShrinkwrapDirs = []; for (const upgrade of config.upgrades) // v8 ignore else -- TODO: add test #40625 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.trace(`Looking for packageFile: ${fileName}`); for (const packageFile of packageFiles.npm) { if (packageFile.packageFile === fileName) { logger.trace({ packageFile }, "Found packageFile"); return packageFile; } logger.trace("No match"); } return {}; } for (const p of config.updatedPackageFiles) { logger.trace(`Checking ${String(p.path)} for lock files`); const packageFile = getPackageFile(p.path); /* v8 ignore next 3 -- needs test */ if (!packageFile.managerData) continue; 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.debug({ packageFiles: npmFiles.map((n) => n.packageFile) }, "Writing package.json files"); for (const packageFile of npmFiles) { /* v8 ignore if -- TODO: add test #40625 */ if (!packageFile.managerData) continue; const basedir = upath.dirname(packageFile.packageFile); const npmrc = packageFile.npmrc; const npmrcFilename = upath.join(basedir, ".npmrc"); if (isString(npmrc) && (npmrcFilename === packageFile.managerData.npmrcFileName || !packageFile.managerData.npmrcFileName)) try { await writeLocalFile(npmrcFilename, npmrc.replace(/\n?$/, "\n")); } catch (err) /* v8 ignore next -- TODO: add test #40625 */ { logger.warn({ npmrcFilename, err }, "Error writing .npmrc"); } const npmLock = packageFile.managerData.npmLock; if (npmLock) { const npmLockPath = npmLock; logger.debug(`Writing ${npmLock}`); let existingNpmLock; try { existingNpmLock = await getFile(npmLock) ?? ""; } catch (err) /* v8 ignore next -- TODO: add test #40625 */ { logger.warn({ err }, "Error reading npm lock file"); existingNpmLock = ""; } const { detectedIndent, lockFileParsed: npmLockParsed } = 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) 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.trace({ packageName }, "Massaging out package name"); lockFileChanged = true; delete npmLockParsed.packages[packageName]; } } // v8 ignore else -- TODO: add test #40625 if (widens.length) { logger.debug(`Removing ${String(widens)} from ${npmLock} to force an update`); lockFileChanged = true; try { if ("dependencies" in npmLockParsed && npmLockParsed.dependencies) widens.forEach((depName) => { delete npmLockParsed.dependencies[depName]; }); } catch /* v8 ignore next -- TODO: add test #40625 */ { logger.warn({ npmLock }, "Error massaging package-lock.json for widen"); } } // v8 ignore else -- TODO: add test #40625 if (lockFileChanged) { logger.debug("Massaging npm lock file before writing to disk"); existingNpmLock = composeLockFile(npmLockParsed, detectedIndent); } await writeLocalFile(npmLockPath, existingNpmLock); } } } } async function writeUpdatedPackageFiles(config) { logger.trace({ config }, "writeUpdatedPackageFiles"); logger.debug("Writing any updated package files"); if (!config.updatedPackageFiles) { logger.debug("No files found"); return; } const artifactContents = /* @__PURE__ */ new Map(); if (config.updatedArtifacts) { for (const artifact of config.updatedArtifacts) if (artifact.type === "addition" && artifact.contents) artifactContents.set(artifact.path, artifact.contents.toString()); } 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.debug(`Writing lock file: ${packageFile.path}`); await writeLocalFile(packageFile.path, packageFile.contents); continue; } if (!(packageFile.path.endsWith("package.json") || packageFile.path.endsWith("pnpm-workspace.yaml") || packageFile.path.endsWith(".yarnrc.yml"))) continue; const contents = artifactContents.get(packageFile.path) ?? packageFile.contents; logger.debug(`Writing ${packageFile.path}`); await writeLocalFile(packageFile.path, contents); } } /* v8 ignore next -- needs test */ async function updateYarnOffline(lockFileDir, updatedArtifacts) { try { const resolvedPaths = []; const yarnrcYml = await getFile(upath.join(lockFileDir, ".yarnrc.yml")); const yarnrc = await getFile(upath.join(lockFileDir, ".yarnrc")); if (yarnrcYml) { const paths = getZeroInstallPaths(yarnrcYml); resolvedPaths.push(...paths.map((p) => upath.join(lockFileDir, p))); } else if (yarnrc) { const mirrorLine = yarnrc.split(newlineRegex).find((line) => line.startsWith("yarn-offline-mirror ")); if (mirrorLine) { const mirrorPath = ensureTrailingSlash(mirrorLine.split(" ")[1].replace(regEx(/"/g), "")); resolvedPaths.push(upath.join(lockFileDir, mirrorPath)); } } logger.debug({ resolvedPaths }, "updateYarnOffline resolvedPaths"); if (resolvedPaths.length) { const status = await 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 readLocalFile(f) }); for (const f of status.deleted || []) if (resolvedPaths.some((p) => f.startsWith(p))) updatedArtifacts.push({ type: "deletion", path: f }); } } catch (err) { logger.error({ err }, "Error updating yarn offline packages"); } } async function updateYarnBinary(lockFileDir, updatedArtifacts, existingYarnrcYmlContent) { let yarnrcYml = existingYarnrcYmlContent; try { const yarnrcYmlFilename = upath.join(lockFileDir, ".yarnrc.yml"); yarnrcYml ??= await getFile(yarnrcYmlFilename) ?? void 0; const newYarnrcYml = await readLocalFile(yarnrcYmlFilename, "utf8"); if (!isString(yarnrcYml) || !isString(newYarnrcYml)) return existingYarnrcYmlContent; const oldYarnPath = parseSingleYaml(yarnrcYml)?.yarnPath; const newYarnPath = parseSingleYaml(newYarnrcYml)?.yarnPath; if (!isNonEmptyStringAndNotWhitespace(oldYarnPath) || !isNonEmptyStringAndNotWhitespace(newYarnPath)) return existingYarnrcYmlContent; const oldYarnFullPath = upath.join(lockFileDir, oldYarnPath); const newYarnFullPath = upath.join(lockFileDir, newYarnPath); 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 readLocalFile(newYarnFullPath, "utf8"), isExecutable: true }); } catch (err) /* v8 ignore next -- TODO: add test #40625 */ { logger.error({ err }, "Error updating Yarn binary"); } return existingYarnrcYmlContent && yarnrcYml; } async function getAdditionalFiles(config, packageFiles) { logger.trace({ config }, "getAdditionalFiles"); const artifactErrors = []; const artifactNotices = []; const updatedArtifacts = []; if (!packageFiles.npm?.length) return { artifactErrors, artifactNotices, updatedArtifacts }; if (config.skipArtifactsUpdate) { logger.debug("Skipping lock file generation"); return { artifactErrors, artifactNotices, updatedArtifacts }; } logger.debug("Getting updated lock files"); if (config.isLockFileMaintenance && config.reuseExistingBranch && await scm.branchExists(config.branchName)) { logger.debug("Skipping lockFileMaintenance update"); return { artifactErrors, artifactNotices, updatedArtifacts }; } const dirs = determineLockFileDirs(config, packageFiles); logger.trace({ dirs }, "lock file dirs"); await writeExistingFiles(config, packageFiles); await writeUpdatedPackageFiles(config); const { additionalNpmrcContent, additionalYarnRcYml } = processHostRules(); const env = { ...getEnv(), NPM_CONFIG_CACHE: await ensureCacheDir("npm"), YARN_CACHE_FOLDER: await ensureCacheDir(YARN_CACHE_DIR), YARN_GLOBAL_FOLDER: await ensureCacheDir(YARN_GLOBAL_DIR), npm_config_store: await ensureCacheDir(PNPM_CACHE_BASE_DIR), NODE_ENV: "dev" }; let token; try { ({token} = find({ hostType: "github", url: "https://api.github.com/" })); // v8 ignore next -- TODO: add test #40625 token = token ? `${token}@` : token; } catch (err) /* v8 ignore next -- TODO: add test #40625 */ { logger.warn({ err }, "Error getting token for packageFile"); } const tokenRe = regEx(`${token ?? ""}`, "g", false); for (const npmLock of dirs.npmLockDirs) { const lockFileDir = upath.dirname(npmLock); const npmrcContent = await getNpmrcContent(lockFileDir); await updateNpmrcContent(lockFileDir, npmrcContent, additionalNpmrcContent); const fileName = upath.basename(npmLock); logger.debug(`Generating ${fileName} for ${lockFileDir}`); const res = await generateLockFile(lockFileDir, env, fileName, config, config.upgrades.filter((upgrade) => upgrade.managerData?.npmLock === npmLock), npmrcContent); if (res.error) { /* v8 ignore next -- needs test */ 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.debug({ dependency: upgrade.depName, type: "npm" }, "lock file failed for the dependency being updated - skipping branch creation"); throw new ExternalHostError(/* @__PURE__ */ new Error("lock file failed for the dependency being updated - skipping branch creation"), NpmDatasource.id); } } artifactErrors.push({ fileName: npmLock, stderr: res.stderr }); } else if (res.lockFile) { if (res.beforeFallback) { const message = "npm `--before` could not be enforced because existing locked packages were published after the `minimumReleaseAge` cutoff. This will resolve after the next lock file maintenance run."; logger.warn({ npmLock }, message); artifactNotices.push({ file: npmLock, message }); } const existingContent = await getFile(npmLock, config.reuseExistingBranch ? config.branchName : config.baseBranch); if (res.lockFile === existingContent) logger.debug(`${npmLock} hasn't changed`); else { logger.debug(`${npmLock} needs updating`); updatedArtifacts.push({ type: "addition", path: npmLock, contents: res.lockFile.replace(tokenRe, "") }); } } await resetNpmrcContent(lockFileDir, npmrcContent); } for (const yarnLock of dirs.yarnLockDirs) { const lockFileDir = upath.dirname(yarnLock); const npmrcContent = await getNpmrcContent(lockFileDir); await updateNpmrcContent(lockFileDir, npmrcContent, additionalNpmrcContent); let yarnRcYmlFilename; let existingYarnrcYmlContent; if (additionalYarnRcYml) { yarnRcYmlFilename = getSiblingFileName(yarnLock, ".yarnrc.yml"); existingYarnrcYmlContent = await readLocalFile(yarnRcYmlFilename, "utf8"); // v8 ignore else -- TODO: add test #40625 if (existingYarnrcYmlContent) try { const existingYarnrRcYml = parseSingleYaml(existingYarnrcYmlContent); const updatedYarnYrcYml = merge(existingYarnrRcYml, fuzzyMatchAdditionalYarnrcYml(additionalYarnRcYml, existingYarnrRcYml)); await writeLocalFile(yarnRcYmlFilename, dump(updatedYarnYrcYml)); logger.debug("Added authentication to .yarnrc.yml"); } catch (err) { logger.warn({ err }, "Error appending .yarnrc.yml content"); } } logger.debug(`Generating yarn.lock for ${lockFileDir}`); const lockFileName = upath.join(lockFileDir, "yarn.lock"); const upgrades = config.upgrades.filter((upgrade) => upgrade.managerData?.yarnLock === yarnLock); const res = await generateLockFile$2(lockFileDir, env, config, upgrades); if (res.error) { /* v8 ignore next -- needs test */ if (res.stderr?.includes(`Couldn't find any versions for`)) { for (const upgrade of config.upgrades) if (res.stderr.includes(`Couldn't find any versions for \\"${upgrade.depName}\\"`)) { logger.debug({ dependency: upgrade.depName, type: "yarn" }, "lock file failed for the dependency being updated - skipping branch creation"); throw new ExternalHostError(/* @__PURE__ */ new Error("lock file failed for the dependency being updated - skipping branch creation"), NpmDatasource.id); } } artifactErrors.push({ fileName: yarnLock, stderr: res.stderr || res.stdout }); } else { const existingContent = await getFile(lockFileName, config.reuseExistingBranch ? config.branchName : config.baseBranch); if (res.lockFile === existingContent) logger.debug("yarn.lock hasn't changed"); else { logger.debug("yarn.lock needs updating"); updatedArtifacts.push({ type: "addition", path: lockFileName, contents: res.lockFile }); await updateYarnOffline(lockFileDir, updatedArtifacts); } /* v8 ignore next 7 -- needs test */ if (upgrades.some(isYarnUpdate)) existingYarnrcYmlContent = await updateYarnBinary(lockFileDir, updatedArtifacts, existingYarnrcYmlContent); } await resetNpmrcContent(lockFileDir, npmrcContent); /* v8 ignore next 4 -- needs test */ if (existingYarnrcYmlContent) await writeLocalFile(yarnRcYmlFilename, existingYarnrcYmlContent); } for (const pnpmShrinkwrap of dirs.pnpmShrinkwrapDirs) { const lockFileDir = upath.dirname(pnpmShrinkwrap); const npmrcContent = await getNpmrcContent(lockFileDir); await updateNpmrcContent(lockFileDir, npmrcContent, additionalNpmrcContent); logger.debug(`Generating pnpm-lock.yaml for ${lockFileDir}`); const res = await generateLockFile$1(lockFileDir, env, config, config.upgrades.filter((upgrade) => upgrade.managerData?.pnpmShrinkwrap === pnpmShrinkwrap)); if (res.error) { /* v8 ignore next -- needs test */ 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.debug({ dependency: upgrade.depName, type: "pnpm" }, "lock file failed for the dependency being updated - skipping branch creation"); throw new ExternalHostError(Error("lock file failed for the dependency being updated - skipping branch creation"), NpmDatasource.id); } } artifactErrors.push({ fileName: pnpmShrinkwrap, stderr: res.stderr || res.stdout }); } else { const existingContent = await getFile(pnpmShrinkwrap, config.reuseExistingBranch ? config.branchName : config.baseBranch); if (res.lockFile === existingContent) logger.debug("pnpm-lock.yaml hasn't changed"); else { logger.debug("pnpm-lock.yaml needs updating"); updatedArtifacts.push({ type: "addition", path: pnpmShrinkwrap, contents: res.lockFile }); } } await resetNpmrcContent(lockFileDir, npmrcContent); } return { artifactErrors, artifactNotices, updatedArtifacts }; } //#endregion export { getAdditionalFiles }; //# sourceMappingURL=index.js.map