UNPKG

renovate

Version:

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

402 lines (401 loc) • 14.4 kB
import { regEx } from "../../../util/regex.js"; import { logger } from "../../../logger/index.js"; import { readLocalFile } from "../../../util/fs/index.js"; import { MAVEN_REPO } from "../../datasource/maven/common.js"; import { MavenDatasource } from "../../datasource/maven/index.js"; import { getDep } from "../dockerfile/extract.js"; import { BUILDPACK_REGISTRY_PREFIX, DOCKER_PREFIX, getDep as getDep$1, isBuildpackRegistryRef, isDockerRef } from "../buildpacks/extract.js"; import { isArray, isNonEmptyArray } from "@sindresorhus/is"; import upath from "upath"; import { XmlDocument } from "xmldoc"; //#region lib/modules/manager/maven/extract.ts const supportedNamespaces = [ "http://maven.apache.org/SETTINGS/1.0.0", "http://maven.apache.org/SETTINGS/1.1.0", "http://maven.apache.org/SETTINGS/1.2.0" ]; const supportedExtensionsNamespaces = [ "http://maven.apache.org/EXTENSIONS/1.0.0", "http://maven.apache.org/EXTENSIONS/1.1.0", "http://maven.apache.org/EXTENSIONS/1.2.0" ]; function parsePom(raw, packageFile) { let project; try { project = new XmlDocument(raw); if (raw.includes("\r\n")) logger.warn("Your pom.xml contains windows line endings. This is not supported and may result in parsing issues."); } catch { logger.debug({ packageFile }, `Failed to parse as XML`); return null; } const { name, attr, children } = project; if (name !== "project") return null; if (attr.xmlns === "http://maven.apache.org/POM/4.0.0") return project; if (isNonEmptyArray(children) && children.some((c) => c.name === "modelVersion" && c.val === "4.0.0")) return project; return null; } function parseExtensions(raw, packageFile) { let extensions; try { extensions = new XmlDocument(raw); } catch { logger.debug({ packageFile }, `Failed to parse as XML`); return null; } const { name, attr, children } = extensions; if (name !== "extensions") return null; if (!supportedExtensionsNamespaces.includes(attr.xmlns)) return null; if (!isNonEmptyArray(children)) return null; return extensions; } function containsPlaceholder(str) { return !!str && (regEx(/\${[^}]*?}/).test(str) || regEx(/\{\{[^}]*?\}\}/).test(str)); } function getCNBDependencies(nodes, config) { const deps = []; for (const node of nodes) { const depString = node.val.trim(); if (isDockerRef(depString)) { const dep = getDep(depString.replace(DOCKER_PREFIX, ""), true, config.registryAliases); dep.fileReplacePosition = node.position; if (dep.currentValue || dep.currentDigest) deps.push(dep); } else if (isBuildpackRegistryRef(depString)) { const dep = getDep$1(depString.replace(BUILDPACK_REGISTRY_PREFIX, "")); if (dep?.currentValue) { dep.fileReplacePosition = node.position; deps.push(dep); } } } return deps; } function getAllCNBDependencies(node, config) { const pluginNode = (node.childNamed("build")?.childNamed("plugins")?.childrenNamed("plugin") ?? []).find((pluginNode) => { return pluginNode.valueWithPath("groupId")?.trim() === "org.springframework.boot" && pluginNode.valueWithPath("artifactId")?.trim() === "spring-boot-maven-plugin"; }); if (!pluginNode) return null; const deps = []; const imageNode = pluginNode.childNamed("configuration")?.childNamed("image"); if (!imageNode) return null; const builder = getCNBDependencies(imageNode.childrenNamed("builder"), config); const runImage = getCNBDependencies(imageNode.childrenNamed("runImage"), config); const buildpacks = getCNBDependencies(imageNode.childNamed("buildpacks")?.childrenNamed("buildpack") ?? [], config); deps.push(...builder, ...runImage, ...buildpacks); return deps.length ? deps : null; } function depFromNode(node, underBuildSettingsElement) { if (!("valueWithPath" in node)) return null; let groupId = node.valueWithPath("groupId")?.trim(); const artifactId = node.valueWithPath("artifactId")?.trim(); const currentValue = node.valueWithPath("version")?.trim(); let depType; if (!groupId && node.name === "plugin") groupId = "org.apache.maven.plugins"; if (groupId && artifactId && currentValue) { const depName = `${groupId}:${artifactId}`; const fileReplacePosition = node.descendantWithPath("version").position; const result = { datasource: MavenDatasource.id, depName, currentValue, fileReplacePosition, registryUrls: [] }; switch (node.name) { case "plugin": case "extension": depType = "build"; break; case "parent": depType = "parent"; break; case "dependency": if (underBuildSettingsElement) depType = "build"; else if (node.valueWithPath("optional")?.trim() === "true") depType = "optional"; else depType = node.valueWithPath("scope")?.trim() ?? "compile"; break; } if (depType) result.depType = depType; return result; } return null; } function deepExtract(node, result = [], isRoot = true, underBuildSettingsElement = false) { const dep = depFromNode(node, underBuildSettingsElement); if (dep && !isRoot) result.push(dep); if (node.children) for (const child of node.children) deepExtract(child, result, false, node.name === "build" || node.name === "reporting" || underBuildSettingsElement); return result; } function applyProps(dep, depPackageFile, props) { let result = dep; let anyChange = false; const alreadySeenProps = /* @__PURE__ */ new Set(); do { const [returnedResult, returnedAnyChange, fatal] = applyPropsInternal(result, depPackageFile, props, alreadySeenProps); if (fatal) { dep.skipReason = "recursive-placeholder"; return dep; } result = returnedResult; anyChange = returnedAnyChange; } while (anyChange); if (containsPlaceholder(result.depName)) result.skipReason = "name-placeholder"; else if (containsPlaceholder(result.currentValue)) result.skipReason = "version-placeholder"; return result; } function applyPropsInternal(dep, depPackageFile, props, previouslySeenProps) { let anyChange = false; let fatal = false; const seenProps = /* @__PURE__ */ new Set(); const replaceAll = (str) => str.replace(regEx(/\${[^}]*?}/g), (substr) => { const propKey = substr.slice(2, -1).trim(); const propValue = props[propKey]; if (propValue) { anyChange = true; if (previouslySeenProps.has(propKey)) fatal = true; else seenProps.add(propKey); return propValue.val; } return substr; }); let depName = dep.depName; if (dep.depName) depName = replaceAll(dep.depName); const registryUrls = dep.registryUrls.map((url) => replaceAll(url)); let fileReplacePosition = dep.fileReplacePosition; let propSource = dep.propSource; let sharedVariableName = null; let currentValue = null; if (dep.currentValue) currentValue = dep.currentValue.replace(regEx(/^\${[^}]*?}$/), (substr) => { const propKey = substr.slice(2, -1).trim(); const propValue = props[propKey]; if (propValue) { sharedVariableName ??= propKey; fileReplacePosition = propValue.fileReplacePosition; propSource = propValue.packageFile ?? void 0; anyChange = true; if (previouslySeenProps.has(propKey)) fatal = true; else seenProps.add(propKey); return propValue.val; } return substr; }); const result = { ...dep, depName, registryUrls, fileReplacePosition, propSource, currentValue }; if (sharedVariableName) result.sharedVariableName = sharedVariableName; if (propSource && depPackageFile !== propSource) result.editFile = propSource; for (const prop of seenProps) previouslySeenProps.add(prop); return [ result, anyChange, fatal ]; } function resolveParentFile(packageFile, parentPath) { let parentFile = "pom.xml"; let parentDir = parentPath; const parentBasename = upath.basename(parentPath); if (parentBasename === "pom.xml" || parentBasename.endsWith(".pom.xml")) { parentFile = parentBasename; parentDir = upath.dirname(parentPath); } const dir = upath.dirname(packageFile); return upath.normalize(upath.join(dir, parentDir, parentFile)); } function extractPackage(rawContent, packageFile, config) { if (!rawContent) return null; const project = parsePom(rawContent, packageFile); if (!project) return null; const result = { datasource: MavenDatasource.id, packageFile, deps: [] }; result.deps = deepExtract(project); const CNBDependencies = getAllCNBDependencies(project, config); if (CNBDependencies) result.deps.push(...CNBDependencies); const propsNode = project.childNamed("properties"); const props = {}; if (propsNode?.children) for (const propNode of propsNode.children) { const key = propNode.name; const val = propNode?.val?.trim(); if (key && val && propNode.position) props[key] = { val, fileReplacePosition: propNode.position, packageFile }; } result.mavenProps = props; const repositories = project.childNamed("repositories"); if (repositories?.children) { const repoUrls = []; for (const repo of repositories.childrenNamed("repository")) { const repoUrl = repo.valueWithPath("url")?.trim(); if (repoUrl) repoUrls.push(repoUrl); } result.deps.forEach((dep) => { if (isArray(dep.registryUrls)) repoUrls.forEach((url) => dep.registryUrls.push(url)); }); } if (packageFile && project.childNamed("parent")) result.parent = resolveParentFile(packageFile, project.valueWithPath("parent.relativePath")?.trim() ?? "../pom.xml"); if (project.childNamed("version")) result.packageFileVersion = project.valueWithPath("version").trim(); return result; } function extractRegistries(rawContent) { if (!rawContent) return []; const settings = parseSettings(rawContent); if (!settings) return []; const urls = []; const mirrorUrls = parseUrls(settings, "mirrors"); urls.push(...mirrorUrls); settings.childNamed("profiles")?.eachChild((profile) => { const repositoryUrls = parseUrls(profile, "repositories"); urls.push(...repositoryUrls); }); return [...new Set(urls)]; } function parseUrls(xmlNode, path) { const children = xmlNode.descendantWithPath(path); const urls = []; if (children?.children) children.eachChild((child) => { const url = child.valueWithPath("url"); if (url) urls.push(url); }); return urls; } function parseSettings(raw) { let settings; try { settings = new XmlDocument(raw); } catch { return null; } const { name, attr } = settings; if (name !== "settings") return null; if (supportedNamespaces.includes(attr.xmlns)) return settings; return null; } function resolveParents(packages) { const packageFileNames = []; const extractedPackages = {}; const extractedDeps = {}; const extractedProps = {}; const registryUrls = {}; packages.forEach((pkg) => { const name = pkg.packageFile; packageFileNames.push(name); extractedPackages[name] = pkg; extractedDeps[name] = []; }); packageFileNames.forEach((name) => { registryUrls[name] = /* @__PURE__ */ new Set(); const propsHierarchy = []; const visitedPackages = /* @__PURE__ */ new Set(); let pkg = extractedPackages[name]; while (pkg) { propsHierarchy.unshift(pkg.mavenProps); if (pkg.deps) pkg.deps.forEach((dep) => { if (dep.registryUrls) dep.registryUrls.forEach((url) => { registryUrls[name].add(url); }); }); if (pkg.parent && !visitedPackages.has(pkg.parent)) { visitedPackages.add(pkg.parent); pkg = extractedPackages[pkg.parent]; } else pkg = null; } propsHierarchy.unshift({}); extractedProps[name] = Object.assign.apply(null, propsHierarchy); }); packageFileNames.forEach((name) => { extractedPackages[name].deps.forEach((rawDep) => { rawDep.registryUrls = [...new Set([...rawDep.registryUrls ?? [], ...registryUrls[name]])]; }); }); const rootDeps = /* @__PURE__ */ new Set(); packageFileNames.forEach((name) => { const pkg = extractedPackages[name]; pkg.deps.forEach((rawDep) => { const dep = applyProps(rawDep, name, extractedProps[name]); if (dep.depType === "parent") { const parentPkg = extractedPackages[pkg.parent]; const hasParentWithNoParent = parentPkg && !parentPkg.parent; const hasParentWithExternalParent = parentPkg && !packageFileNames.includes(parentPkg.parent); if (hasParentWithNoParent || hasParentWithExternalParent) rootDeps.add(dep.depName); } extractedDeps[dep.propSource ?? name].push(dep); }); }); return packageFileNames.map((packageFile) => { const pkg = extractedPackages[packageFile]; const deps = extractedDeps[packageFile]; for (const dep of deps) if (rootDeps.has(dep.depName)) dep.depType = "parent-root"; return { ...pkg, deps }; }); } function cleanResult(packageFiles) { packageFiles.forEach((packageFile) => { delete packageFile.mavenProps; delete packageFile.parent; packageFile.deps.forEach((dep) => { delete dep.propSource; if (dep.datasource === MavenDatasource.id) dep.registryUrls.push(MAVEN_REPO); }); }); return packageFiles; } function extractExtensions(rawContent, packageFile) { if (!rawContent) return null; const extensions = parseExtensions(rawContent, packageFile); if (!extensions) return null; const result = { datasource: MavenDatasource.id, packageFile, deps: [] }; result.deps = deepExtract(extensions); return result; } async function extractAllPackageFiles(config, packageFiles) { const packages = []; const additionalRegistryUrls = []; for (const packageFile of packageFiles) { const content = await readLocalFile(packageFile, "utf8"); if (!content) { logger.debug({ packageFile }, "packageFile has no content"); continue; } if (packageFile.endsWith("settings.xml")) { const registries = extractRegistries(content); if (registries) { logger.debug({ registries, packageFile }, "Found registryUrls in settings.xml"); additionalRegistryUrls.push(...registries); } } else if (packageFile.endsWith(".mvn/extensions.xml")) { const extensions = extractExtensions(content, packageFile); if (extensions) packages.push(extensions); else logger.trace({ packageFile }, "can not read extensions"); } else { const pkg = extractPackage(content, packageFile, config); if (pkg) packages.push(pkg); else logger.trace({ packageFile }, "can not read dependencies"); } } if (additionalRegistryUrls) { for (const pkgFile of packages) for (const dep of pkgFile.deps) if (dep.registryUrls) dep.registryUrls.unshift(...additionalRegistryUrls); } return cleanResult(resolveParents(packages)); } //#endregion export { extractAllPackageFiles, extractRegistries }; //# sourceMappingURL=extract.js.map