renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
230 lines (229 loc) • 7.9 kB
JavaScript
import { newlineRegex, regEx } from "../../../util/regex.js";
import { logger } from "../../../logger/index.js";
import { find } from "../../../util/host-rules.js";
import { detectPlatform } from "../../../util/common.js";
import { parseSingleYaml } from "../../../util/yaml.js";
import { GithubTagsDatasource } from "../../datasource/github-tags/index.js";
import { GitlabTagsDatasource } from "../../datasource/gitlab-tags/index.js";
import { extractDependency as extractDependency$1 } from "../npm/extract/common/dependency.js";
import { parseLine } from "../gomod/line-parser.js";
import { pep508ToPackageDependency } from "../pep621/utils.js";
import { matchesPrecommitConfigHeuristic, matchesPrecommitDependencyHeuristic } from "./parsing.js";
import { isEmptyObject, isNonEmptyObject, isPlainObject } from "@sindresorhus/is";
//#region lib/modules/manager/pre-commit/extract.ts
/**
* Determines the datasource(id) to be used for this dependency
* @param repository the full git url, ie git@github.com/user/project.
* Used in debug statements to clearly indicate the related dependency.
* @param hostname the hostname (ie github.com)
* Used to determine which renovate datasource should be used.
* Is matched literally against `github.com` and `gitlab.com`.
* If that doesn't match, `hostRules.find()` is used to find related sources.
* In that case, the hostname is passed on as registryUrl to the corresponding datasource.
*/
function determineDatasource(repository, hostname) {
if (hostname === "github.com" || detectPlatform(repository) === "github") {
logger.debug({
repository,
hostname
}, "Found github dependency");
return { datasource: GithubTagsDatasource.id };
}
if (hostname === "gitlab.com") {
logger.debug({
repository,
hostname
}, "Found gitlab dependency");
return { datasource: GitlabTagsDatasource.id };
}
if (detectPlatform(repository) === "gitlab") {
logger.debug({
repository,
hostname
}, "Found gitlab dependency with custom registryUrl");
return {
datasource: GitlabTagsDatasource.id,
registryUrls: [`https://${hostname}`]
};
}
const hostUrl = `https://${hostname}`;
if (isEmptyObject(find({ url: hostUrl }))) {
logger.debug({
repository,
hostUrl
}, "Provided hostname does not match any hostRules. Ignoring");
return {
skipReason: "unknown-registry",
registryUrls: [hostname]
};
}
for (const [hostType, sourceId] of [["github", GithubTagsDatasource.id], ["gitlab", GitlabTagsDatasource.id]]) if (isNonEmptyObject(find({
hostType,
url: hostUrl
}))) {
logger.debug({
repository,
hostUrl,
hostType
}, `Provided hostname matches a ${hostType} hostrule.`);
return {
datasource: sourceId,
registryUrls: [hostname]
};
}
logger.debug({
repository,
registry: hostUrl
}, "Provided hostname did not match any of the hostRules of hostType github nor gitlab");
return {
skipReason: "unknown-registry",
registryUrls: [hostname]
};
}
const gitUrlRegex = regEx(/\.git$/i);
const revLineWithFrozenCommentRegex = regEx(/^\s*rev:\s*(?<replaceString>(?<currentDigest>[a-f0-9]{40})(?<commentWhiteSpaces>\s+)#\s*frozen:\s*(?<currentValue>\S+))/);
function extractWithRegex(content) {
logger.trace("pre-commit.extractWithRegex()");
const regexDeps = /* @__PURE__ */ new Map();
for (const line of content.split(newlineRegex)) {
if (line.trim().startsWith("#")) continue;
const match = revLineWithFrozenCommentRegex.exec(line);
if (match?.groups) {
const { currentDigest, currentValue, replaceString, commentWhiteSpaces } = match.groups;
regexDeps.set(currentDigest, {
currentDigest,
currentValue,
replaceString,
autoReplaceStringTemplate: `{{newDigest}}${commentWhiteSpaces}# frozen: {{newValue}}`
});
}
}
return regexDeps;
}
function extractDependency(tag, repository) {
logger.debug(`Found version ${tag}`);
const urlMatchers = [
regEx("^https?://(?<hostname>[^/]+)/(?<depName>\\S*)"),
regEx("^git@(?<hostname>[^:]+):(?<depName>\\S*)"),
regEx(/^git:\/\/(?<hostname>[^/]+)\/(?<depName>\S*)/),
regEx(/^ssh:\/\/git@(?<hostname>[^/]+)\/(?<depName>\S*)/)
];
for (const urlMatcher of urlMatchers) {
const match = urlMatcher.exec(repository);
if (match?.groups) {
const hostname = match.groups.hostname;
const depName = match.groups.depName.replace(gitUrlRegex, "");
return {
...determineDatasource(repository, hostname),
depName,
depType: "repository",
packageName: depName,
currentValue: tag
};
}
}
logger.info({ repository }, "Could not separate hostname from full dependency url.");
return {
depName: void 0,
depType: "repository",
datasource: void 0,
packageName: void 0,
skipReason: "invalid-url",
currentValue: tag
};
}
/**
* Find all supported dependencies in the pre-commit yaml object.
*
* @param precommitFile the parsed yaml config file
* @param regexDeps Map of regex-extracted deps keyed by digest for enrichment
*/
function findDependencies(precommitFile, regexDeps) {
if (!precommitFile.repos) {
logger.debug(`No repos section found, skipping file`);
return [];
}
const packageDependencies = [];
for (const item of precommitFile.repos) {
if (item.repo !== "meta") {
for (const hook of item.hooks ?? []) if (hook.language === "node") hook.additional_dependencies?.map((req) => {
const match = regEx("^(?<name>.+)@(?<range>.+)$").exec(req);
if (!match?.groups) return;
const depType = "pre-commit-node";
const dep = extractDependency$1(depType, match.groups.name, match.groups.range);
packageDependencies.push({
depType,
depName: match.groups.name,
packageName: match.groups.name,
...dep
});
});
else if (hook.language === "python") hook.additional_dependencies?.map((req) => {
const dep = pep508ToPackageDependency("pre-commit-python", req);
if (dep) packageDependencies.push(dep);
});
else if (hook.language === "golang") hook.additional_dependencies?.map((req) => {
const dep = parseLine(`require ${req.replace("@", " ")}`);
if (dep) {
const depType = "pre-commit-golang";
packageDependencies.push({
...dep,
depType
});
}
});
}
if (matchesPrecommitDependencyHeuristic(item)) {
logger.trace(item, "Matched pre-commit dependency spec");
const repository = String(item.repo);
const tag = String(item.rev);
const dep = extractDependency(tag, repository);
const regexDep = regexDeps.get(tag);
if (regexDep) {
dep.currentDigest = regexDep.currentDigest;
dep.currentValue = regexDep.currentValue;
dep.replaceString = regexDep.replaceString;
dep.autoReplaceStringTemplate = regexDep.autoReplaceStringTemplate;
}
packageDependencies.push(dep);
} else logger.trace(item, "Did not find pre-commit repo spec");
}
return packageDependencies;
}
function extractPackageFile(content, packageFile) {
let parsedContent;
try {
parsedContent = parseSingleYaml(content);
} catch (err) {
logger.debug({
filename: packageFile,
err
}, "Failed to parse pre-commit config YAML");
return null;
}
if (!isPlainObject(parsedContent)) {
logger.debug({ packageFile }, `Parsing of pre-commit config YAML returned invalid result`);
return null;
}
if (!matchesPrecommitConfigHeuristic(parsedContent)) {
logger.debug({ packageFile }, `File does not look like a pre-commit config file`);
return null;
}
try {
const regexDeps = extractWithRegex(content);
const deps = findDependencies(parsedContent, regexDeps);
if (deps.length) {
logger.trace({ deps }, "Found dependencies in pre-commit config");
return { deps };
}
} catch (err) /* istanbul ignore next */ {
logger.debug({
packageFile,
err
}, "Error scanning parsed pre-commit config");
}
return null;
}
//#endregion
export { extractPackageFile };
//# sourceMappingURL=extract.js.map