renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
553 lines • 19.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.extractPackage = extractPackage;
exports.extractRegistries = extractRegistries;
exports.parseSettings = parseSettings;
exports.resolveParents = resolveParents;
exports.extractExtensions = extractExtensions;
exports.extractAllPackageFiles = extractAllPackageFiles;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const upath_1 = tslib_1.__importDefault(require("upath"));
const xmldoc_1 = require("xmldoc");
const logger_1 = require("../../../logger");
const fs_1 = require("../../../util/fs");
const regex_1 = require("../../../util/regex");
const maven_1 = require("../../datasource/maven");
const common_1 = require("../../datasource/maven/common");
const extract_1 = require("../buildpacks/extract");
const extract_2 = require("../dockerfile/extract");
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 xmldoc_1.XmlDocument(raw);
if (raw.includes('\r\n')) {
logger_1.logger.warn('Your pom.xml contains windows line endings. This is not supported and may result in parsing issues.');
}
}
catch {
logger_1.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 (is_1.default.nonEmptyArray(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 xmldoc_1.XmlDocument(raw);
}
catch {
logger_1.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 (!is_1.default.nonEmptyArray(children)) {
return null;
}
return extensions;
}
function containsPlaceholder(str) {
return !!str && (0, regex_1.regEx)(/\${[^}]*?}/).test(str);
}
function getCNBDependencies(nodes, config) {
const deps = [];
for (const node of nodes) {
const depString = node.val.trim();
if ((0, extract_1.isDockerRef)(depString)) {
const dep = (0, extract_2.getDep)(depString.replace(extract_1.DOCKER_PREFIX, ''), true, config.registryAliases);
dep.fileReplacePosition = node.position; // TODO: should not be null
if (dep.currentValue || dep.currentDigest) {
deps.push(dep);
}
}
else if ((0, extract_1.isBuildpackRegistryRef)(depString)) {
const dep = (0, extract_1.getDep)(depString.replace(extract_1.BUILDPACK_REGISTRY_PREFIX, ''));
if (dep?.currentValue) {
dep.fileReplacePosition = node.position; // TODO: should not be null
deps.push(dep);
}
}
}
return deps;
}
function getAllCNBDependencies(node, config) {
const pluginNodes = node.childNamed('build')?.childNamed('plugins')?.childrenNamed('plugin') ??
[];
const pluginNode = pluginNodes.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 versionNode = node.descendantWithPath('version');
const fileReplacePosition = versionNode.position; // TODO: should not be null
const datasource = maven_1.MavenDatasource.id;
const result = {
datasource,
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'; // maven default scope is 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 = 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 = new Set();
const replaceAll = (str) => str.replace((0, regex_1.regEx)(/\${[^}]*?}/g), (substr) => {
const propKey = substr.slice(2, -1).trim();
// TODO: wrong types here, props is already `MavenProp`
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((0, regex_1.regEx)(/^\${[^}]*?}$/), (substr) => {
const propKey = substr.slice(2, -1).trim();
// TODO: wrong types here, props is already `MavenProp`
const propValue = props[propKey];
if (propValue) {
sharedVariableName ??= propKey;
fileReplacePosition = propValue.fileReplacePosition;
propSource =
propValue.packageFile ??
// istanbul ignore next
undefined;
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_1.default.basename(parentPath);
if (parentBasename === 'pom.xml' || parentBasename.endsWith('.pom.xml')) {
parentFile = parentBasename;
parentDir = upath_1.default.dirname(parentPath);
}
const dir = upath_1.default.dirname(packageFile);
return upath_1.default.normalize(upath_1.default.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: maven_1.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) {
const fileReplacePosition = propNode.position;
props[key] = { val, fileReplacePosition, 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 (is_1.default.array(dep.registryUrls)) {
repoUrls.forEach((url) => dep.registryUrls.push(url));
}
});
}
if (packageFile && project.childNamed('parent')) {
const parentPath = project.valueWithPath('parent.relativePath')?.trim() ?? '../pom.xml';
result.parent = resolveParentFile(packageFile, parentPath);
}
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);
});
// filter out duplicates
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 xmldoc_1.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] = [];
});
// Construct package-specific prop scopes
// and merge them in reverse order,
// which allows inheritance/overriding.
packageFileNames.forEach((name) => {
registryUrls[name] = new Set();
const propsHierarchy = [];
const visitedPackages = 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);
});
// Resolve registryUrls
packageFileNames.forEach((name) => {
const pkg = extractedPackages[name];
pkg.deps.forEach((rawDep) => {
const urlsSet = new Set([
...(rawDep.registryUrls ?? []),
...registryUrls[name],
]);
rawDep.registryUrls = [...urlsSet];
});
});
const rootDeps = new Set();
// Resolve placeholders
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);
}
}
const sourceName = dep.propSource ?? name;
extractedDeps[sourceName].push(dep);
});
});
const packageFiles = 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 };
});
return packageFiles;
}
function cleanResult(packageFiles) {
packageFiles.forEach((packageFile) => {
delete packageFile.mavenProps;
delete packageFile.parent;
packageFile.deps.forEach((dep) => {
delete dep.propSource;
//Add Registry From SuperPom
if (dep.datasource === maven_1.MavenDatasource.id) {
dep.registryUrls.push(common_1.MAVEN_REPO);
}
});
});
return packageFiles;
}
function extractExtensions(rawContent, packageFile) {
if (!rawContent) {
return null;
}
const extensions = parseExtensions(rawContent, packageFile);
if (!extensions) {
return null;
}
const result = {
datasource: maven_1.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 (0, fs_1.readLocalFile)(packageFile, 'utf8');
if (!content) {
logger_1.logger.debug({ packageFile }, 'packageFile has no content');
continue;
}
if (packageFile.endsWith('settings.xml')) {
const registries = extractRegistries(content);
if (registries) {
logger_1.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_1.logger.trace({ packageFile }, 'can not read extensions');
}
}
else {
const pkg = extractPackage(content, packageFile, config);
if (pkg) {
packages.push(pkg);
}
else {
logger_1.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));
}
//# sourceMappingURL=extract.js.map