import { regEx } from "../../../../util/regex.js";
import { GlobalConfig } from "../../../../config/global.js";
import { sanitize } from "../../../../util/sanitize.js";
import { minimatch } from "../../../../util/minimatch.js";
import { addMeta, logger } from "../../../../logger/index.js";
import { coerceArray } from "../../../../util/array.js";
import { compile } from "../../../../util/template/index.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 { mergeChildConfig } from "../../../../config/utils.js";
import "../../../../config/index.js";
import { isArray, isNonEmptyArray } from "@sindresorhus/is";
import upath from "upath";
import crypto from "crypto";

//#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", false),
						cwd: workingDir,
						extraEnv: getGitEnvironmentVariables()
					};
					if (dataFilePath) execOpts.env = { RENOVATE_POST_UPGRADE_COMMAND_DATA_FILE: dataFilePath };
					const execResult = await exec(compiledCmd, execOpts);
					logger.debug({
						cmd: compiledCmd,
						...execResult
					}, "Executed post-upgrade task");
				} catch (error) {
					artifactErrors.push({
						lockFile: upgrade.packageFile,
						stderr: sanitize(error.message)
					});
				}
				else {
					logger.warn({
						cmd: compiledCmd,
						allowedCommands
					}, "Post-upgrade task did not match any on allowedCommands list");
					artifactErrors.push({
						lockFile: 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