renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
180 lines (179 loc) • 7.1 kB
JavaScript
import { cleanDatasourceKeys } from "../../../util/cache/memory/index.js";
import { logger } from "../../../logger/index.js";
import { isNotNullOrUndefined } from "../../../util/array.js";
import { instrument } from "../../../instrumentation/index.js";
import { getCache } from "../../../util/cache/repository/index.js";
import { checkGithubToken } from "../../../util/check-token.js";
import { scm } from "../../../modules/platform/scm.js";
import { hashMap } from "../../../modules/manager/fingerprint.generated.js";
import "../../../modules/manager/index.js";
import { fingerprint } from "../../../util/fingerprint.js";
import { Vulnerabilities } from "./vulnerabilities.js";
import { generateFingerprintConfig } from "../extract/extract-fingerprint-config.js";
import { extractAllDependencies } from "../extract/index.js";
import { branchifyUpgrades } from "../updates/branchify.js";
import { fetchUpdates } from "./fetch.js";
import { calculateLibYears } from "./libyear.js";
import { sortBranches } from "./sort.js";
import { writeUpdates } from "./write.js";
import { isNonEmptyArray } from "@sindresorhus/is";
// istanbul ignore next
function extractStats(packageFiles) {
if (!packageFiles) return null;
const stats = {
managers: {},
total: {
fileCount: 0,
depCount: 0
}
};
for (const [manager, managerPackageFiles] of Object.entries(packageFiles)) {
const fileCount = managerPackageFiles.length;
let depCount = 0;
for (const file of managerPackageFiles) depCount += file.deps.length;
stats.managers[manager] = {
fileCount,
depCount
};
stats.total.fileCount += fileCount;
stats.total.depCount += depCount;
}
return stats;
}
function isCacheExtractValid(baseBranchSha, configHash, cachedExtract) {
if (!cachedExtract) return false;
if (!cachedExtract.revision) {
logger.debug("Cached extract is missing revision, so cannot be used");
return false;
}
if (cachedExtract.revision !== 1) {
logger.debug(`Extract cache revision has changed (old=${cachedExtract.revision}, new=1)`);
return false;
}
if (!(cachedExtract.sha && cachedExtract.configHash)) return false;
if (cachedExtract.sha !== baseBranchSha) {
logger.debug(`Cached extract result cannot be used due to base branch SHA change (old=${cachedExtract.sha}, new=${baseBranchSha})`);
return false;
}
if (cachedExtract.configHash !== configHash) {
logger.debug("Cached extract result cannot be used due to config change");
return false;
}
if (!cachedExtract.extractionFingerprints) {
logger.debug("Cached extract is missing extractionFingerprints, so cannot be used");
return false;
}
const changedManagers = /* @__PURE__ */ new Set();
for (const [manager, fingerprint] of Object.entries(cachedExtract.extractionFingerprints)) if (fingerprint !== hashMap.get(manager)) changedManagers.add(manager);
if (changedManagers.size > 0) {
logger.debug({ changedManagers: [...changedManagers] }, "Manager fingerprint(s) have changed, extract cache cannot be reused");
return false;
}
logger.debug(`Cached extract for sha=${baseBranchSha} is valid and can be used`);
return true;
}
async function extract(config, overwriteCache = true) {
logger.debug("extract()");
const { baseBranch } = config;
const baseBranchSha = await scm.getBranchCommit(baseBranch);
let packageFiles;
const cache = getCache();
cache.scan ??= {};
const cachedExtract = cache.scan[baseBranch];
const configHash = instrument("fingerprint", () => fingerprint(generateFingerprintConfig(config)));
// istanbul ignore if
if (overwriteCache && isCacheExtractValid(baseBranchSha, configHash, cachedExtract)) {
packageFiles = cachedExtract.packageFiles;
try {
for (const files of Object.values(packageFiles)) for (const file of files) for (const dep of file.deps) delete dep.updates;
logger.debug("Deleted cached dep updates");
} catch (err) {
logger.info({ err }, "Error deleting cached dep updates");
}
} else {
await instrument("checkoutBranch", async () => await scm.checkoutBranch(baseBranch));
const extractResult = await instrument("extractAllDependencies", async () => await extractAllDependencies(config) || {});
packageFiles = extractResult.packageFiles;
const { extractionFingerprints } = extractResult;
if (overwriteCache) cache.scan[baseBranch] = {
revision: 1,
sha: baseBranchSha,
configHash,
extractionFingerprints,
packageFiles
};
const baseBranches = isNonEmptyArray(config.baseBranches) ? config.baseBranches : [baseBranch];
Object.keys(cache.scan).forEach((branchName) => {
if (!baseBranches.includes(branchName)) delete cache.scan[branchName];
});
}
const stats = extractStats(packageFiles);
logger.info({
baseBranch: config.baseBranch,
stats
}, `Dependency extraction complete`);
logger.trace({ config: packageFiles }, "packageFiles");
checkGithubToken(packageFiles);
return packageFiles;
}
async function fetchVulnerabilities(config, packageFiles) {
if (config.osvVulnerabilityAlerts) {
logger.debug("fetchVulnerabilities() - osvVulnerabilityAlerts=true");
try {
await (await Vulnerabilities.create()).appendVulnerabilityPackageRules(config, packageFiles);
} catch (err) {
logger.warn({ err }, "Unable to read vulnerability information");
}
}
}
async function lookup(config, packageFiles) {
await fetchVulnerabilities(config, packageFiles);
await fetchUpdates(config, packageFiles);
await fetchVulnerabilities(config, packageFiles);
cleanDatasourceKeys();
calculateLibYears(config, packageFiles);
const { branches, branchList } = await branchifyUpgrades(config, packageFiles);
reportMaliciousSkippedDependencies(packageFiles);
logger.debug({
baseBranch: config.baseBranch,
config: packageFiles
}, "packageFiles with updates");
sortBranches(branches);
return {
branches,
branchList,
packageFiles
};
}
function reportMaliciousSkippedDependencies(allPackageFiles) {
if (allPackageFiles === void 0) return;
for (const [manager, packageFiles] of Object.entries(allPackageFiles)) for (const packageFile of packageFiles) for (const dep of packageFile.deps) if (dep.skipReason === "malicious-version-in-use") {
logger.warn({
packageFile: packageFile.packageFile,
depName: dep.depName,
packageName: dep.packageName,
manager,
datasource: dep.datasource
}, `Dependency ${dep.depName} is currently using a malicious version`);
delete dep.skipReason;
delete dep.skipStage;
} else if (dep.skipReason === "malicious-update-proposed") {
const newVersions = dep.updates?.map((u) => u.newVersion ?? u.newValue).filter(isNotNullOrUndefined);
logger.warn({
packageFile: packageFile.packageFile,
depName: dep.depName,
packageName: dep.packageName,
manager,
datasource: dep.datasource,
newVersions
}, `Dependency ${dep.depName} has update(s) proposed which would update you to a malicious version - skipping`);
}
}
async function update(config, branches) {
let res;
if (config.repoIsOnboarded) res = await writeUpdates(config, branches);
return res;
}
//#endregion
export { extract, lookup, update };
//# sourceMappingURL=extract-update.js.map