renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
213 lines (212 loc) • 9.6 kB
JavaScript
import { regEx } from "../../../../util/regex.js";
import { GlobalConfig } from "../../../../config/global.js";
import { minimatch } from "../../../../util/minimatch.js";
import { sanitize } from "../../../../util/sanitize.js";
import { addMeta, logger } from "../../../../logger/index.js";
import { mergeChildConfig } from "../../../../config/utils.js";
import { coerceArray } from "../../../../util/array.js";
import { compile } from "../../../../util/template/index.js";
import { isConstraintName, isToolName } from "../../../../util/exec/types.js";
import { ensureLocalDir, localPathIsFile, outputCacheFile, privateCacheDir, readLocalFile, writeLocalFile } from "../../../../util/fs/index.js";
import { getGitEnvironmentVariables } from "../../../../util/git/auth.js";
import { exec } from "../../../../util/exec/index.js";
import { getRepoStatus } from "../../../../util/git/index.js";
import "../../../../config/index.js";
import { isArray, isNonEmptyArray } from "@sindresorhus/is";
import crypto from "node:crypto";
import upath from "upath";
//#region lib/workers/repository/update/branch/execute-post-upgrade-commands.ts
async function postUpgradeCommandsExecutor(filteredUpgradeCommands, config) {
let updatedArtifacts = [...config.updatedArtifacts ?? []];
const artifactErrors = [...config.artifactErrors ?? []];
const allowedCommands = GlobalConfig.get("allowedCommands");
for (const upgrade of filteredUpgradeCommands) {
addMeta({ dep: upgrade.depName });
logger.trace({
tasks: upgrade.postUpgradeTasks,
allowedCommands
}, `Checking for post-upgrade tasks`);
const commands = upgrade.postUpgradeTasks?.commands;
const dataFileTemplate = upgrade.postUpgradeTasks?.dataFileTemplate;
const fileFilters = upgrade.postUpgradeTasks?.fileFilters ?? ["**/*"];
if (isNonEmptyArray(commands)) {
const previouslyModifiedFiles = config.updatedPackageFiles.concat(updatedArtifacts);
for (const file of previouslyModifiedFiles) {
const canWriteFile = await localPathIsFile(file.path);
if (file.type === "addition" && !file.isSymlink && canWriteFile) {
let contents;
if (typeof file.contents === "string") contents = Buffer.from(file.contents);
else contents = file.contents;
await writeLocalFile(file.path, contents);
}
}
let dataFilePath = null;
if (dataFileTemplate) {
const dataFileContent = sanitize(compile(dataFileTemplate, mergeChildConfig(config, upgrade)));
logger.debug({ dataFileTemplate }, "Processed post-upgrade commands data file template.");
const dataFileName = `post-upgrade-data-file-${crypto.randomBytes(8).toString("hex")}.tmp`;
dataFilePath = upath.join(privateCacheDir(), dataFileName);
try {
await outputCacheFile(dataFilePath, dataFileContent);
logger.debug({
dataFilePath,
dataFileContent
}, "Created post-upgrade commands data file.");
} catch (error) {
artifactErrors.push({ stderr: sanitize(`Failed to create post-upgrade commands data file at ${dataFilePath}, reason: ${error.message}`) });
dataFilePath = null;
}
}
const workingDirTemplate = upgrade.postUpgradeTasks?.workingDirTemplate;
let workingDir = GlobalConfig.get("localDir");
if (workingDirTemplate) {
workingDir = sanitize(compile(workingDirTemplate, mergeChildConfig(config, upgrade)));
workingDir = await ensureLocalDir(workingDir);
logger.trace({ workingDirTemplate }, "Processed post-upgrade commands working directory template.");
}
for (const cmd of commands) {
const compiledCmd = compile(cmd, mergeChildConfig(config, upgrade));
if (compiledCmd !== cmd) logger.debug({
rawCmd: cmd,
compiledCmd
}, "Post-upgrade command has been compiled");
if (allowedCommands.some((pattern) => regEx(pattern).test(compiledCmd))) try {
logger.trace({ cmd: compiledCmd }, "Executing post-upgrade task");
const execOpts = {
shell: GlobalConfig.get("allowShellExecutorForPostUpgradeCommands"),
cwd: workingDir,
extraEnv: getGitEnvironmentVariables()
};
if (dataFilePath) execOpts.env = { RENOVATE_POST_UPGRADE_COMMAND_DATA_FILE: dataFilePath };
if (upgrade.postUpgradeTasks?.installTools) {
execOpts.toolConstraints ??= [];
for (const [tool] of Object.entries(upgrade.postUpgradeTasks?.installTools)) {
const validTool = isToolName(tool);
const validConstraint = isConstraintName(tool);
if (!validTool) {
logger.warn({
tool,
validTool,
validConstraint
}, `Skipping ${validConstraint ? "valid" : "invalid"} constraint that is not a tool that Containerbase knows`);
continue;
}
execOpts.toolConstraints.push({
toolName: tool,
constraint: upgrade.constraints?.[tool]
});
}
}
const execResult = await exec(compiledCmd, execOpts);
logger.debug({
cmd: compiledCmd,
...execResult
}, "Executed post-upgrade task");
} catch (error) {
artifactErrors.push({
fileName: upgrade.packageFile,
stderr: sanitize(error.message)
});
}
else {
logger.warn({
cmd: compiledCmd,
allowedCommands
}, "Post-upgrade task did not match any on allowedCommands list");
artifactErrors.push({
fileName: upgrade.packageFile,
stderr: sanitize(`Post-upgrade command '${compiledCmd}' has not been added to the allowed list in allowedCommands`)
});
}
}
const status = await getRepoStatus();
logger.trace({ status }, "git status after post-upgrade tasks");
logger.debug({
addedCount: status.not_added?.length,
modifiedCount: status.modified?.length,
deletedCount: status.deleted?.length,
renamedCount: status.renamed?.length
}, "git status counts after post-upgrade tasks");
const addedOrModifiedFiles = [
...coerceArray(status.not_added),
...coerceArray(status.modified),
...coerceArray(status.renamed?.map((x) => x.to))
];
const changedFiles = [
...addedOrModifiedFiles,
...coerceArray(status.deleted),
...coerceArray(status.renamed?.map((x) => x.from))
];
const previouslyDeletedFiles = updatedArtifacts.filter((ua) => ua.type === "deletion");
for (const previouslyDeletedFile of previouslyDeletedFiles)
/* v8 ignore if -- TODO: needs test */
if (!changedFiles.includes(previouslyDeletedFile.path)) {
logger.debug({ file: previouslyDeletedFile.path }, "Previously deleted file has been restored without modification");
updatedArtifacts = updatedArtifacts.filter((ua) => !(ua.type === "deletion" && ua.path === previouslyDeletedFile.path));
}
logger.trace({ addedOrModifiedFiles }, "Added or modified files");
logger.debug(`Checking ${addedOrModifiedFiles.length} added or modified files for post-upgrade changes`);
const fileExcludes = [];
if (config.npmrc) fileExcludes.push(".npmrc");
for (const relativePath of addedOrModifiedFiles) {
if (fileExcludes.some((pattern) => minimatch(pattern, { dot: true }).match(relativePath))) continue;
let fileMatched = false;
for (const pattern of fileFilters) if (minimatch(pattern, { dot: true }).match(relativePath)) {
fileMatched = true;
logger.debug({
file: relativePath,
pattern
}, "Post-upgrade file saved");
const existingContent = await readLocalFile(relativePath);
const existingUpdatedArtifacts = updatedArtifacts.find((ua) => ua.path === relativePath);
if (existingUpdatedArtifacts?.type === "addition") existingUpdatedArtifacts.contents = existingContent;
else updatedArtifacts.push({
type: "addition",
path: relativePath,
contents: existingContent
});
updatedArtifacts = updatedArtifacts.filter((ua) => !(ua.type === "deletion" && ua.path === relativePath));
}
if (!fileMatched) logger.debug({ file: relativePath }, "Post-upgrade file did not match any file filters");
}
for (const relativePath of coerceArray(status.deleted)) for (const pattern of fileFilters) if (minimatch(pattern, { dot: true }).match(relativePath)) {
if (!updatedArtifacts.some((ua) => ua.path === relativePath && ua.type === "deletion")) {
logger.debug({
file: relativePath,
pattern
}, "Post-upgrade file removed");
updatedArtifacts.push({
type: "deletion",
path: relativePath
});
}
updatedArtifacts = updatedArtifacts.filter((ua) => !(ua.type === "addition" && ua.path === relativePath));
}
}
}
return {
updatedArtifacts,
artifactErrors
};
}
async function executePostUpgradeCommands(config) {
if (!(isArray(config.updatedPackageFiles) && config.updatedPackageFiles.length > 0 || isArray(config.updatedArtifacts) && config.updatedArtifacts.length > 0)) {
logger.debug("No changes to package files, skipping post-upgrade tasks");
return null;
}
const branchUpgradeCommands = [{
manager: config.manager,
depName: config.upgrades.map(({ depName }) => depName).join(" "),
branchName: config.branchName,
postUpgradeTasks: config.postUpgradeTasks.executionMode === "branch" ? config.postUpgradeTasks : void 0
}];
const { updatedArtifacts, artifactErrors } = await postUpgradeCommandsExecutor(config.upgrades.filter(({ postUpgradeTasks }) => !postUpgradeTasks?.executionMode || postUpgradeTasks.executionMode === "update"), config);
return postUpgradeCommandsExecutor(branchUpgradeCommands, {
...config,
updatedArtifacts,
artifactErrors
});
}
//#endregion
export { executePostUpgradeCommands as default };
//# sourceMappingURL=execute-post-upgrade-commands.js.map