"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateArtifacts = void 0;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const semver_1 = tslib_1.__importDefault(require("semver"));
const shlex_1 = require("shlex");
const upath_1 = tslib_1.__importDefault(require("upath"));
const global_1 = require("../../../config/global");
const error_messages_1 = require("../../../constants/error-messages");
const logger_1 = require("../../../logger");
const exec_1 = require("../../../util/exec");
const fs_1 = require("../../../util/fs");
const git_1 = require("../../../util/git");
const auth_1 = require("../../../util/git/auth");
const regex_1 = require("../../../util/regex");
const semver_2 = require("../../versioning/semver");
const { major, valid } = semver_1.default;
function getUpdateImportPathCmds(updatedDeps, { constraints }) {
    // Check if we fail to parse any major versions and log that they're skipped
    const invalidMajorDeps = updatedDeps.filter(({ newVersion }) => !valid(newVersion));
    if (invalidMajorDeps.length > 0) {
        invalidMajorDeps.forEach(({ depName }) => logger_1.logger.warn({ depName }, 'Ignoring dependency: Could not get major version'));
    }
    const updateImportCommands = updatedDeps
        .filter(({ newVersion }) => valid(newVersion) && !newVersion.endsWith('+incompatible'))
        .map(({ depName, newVersion }) => ({
        depName: depName,
        newMajor: major(newVersion),
    }))
        // Skip path updates going from v0 to v1
        .filter(({ depName, newMajor }) => depName.startsWith('gopkg.in/') || newMajor > 1)
        .map(({ depName, newMajor }) => `mod upgrade --mod-name=${depName} -t=${newMajor}`);
    if (updateImportCommands.length > 0) {
        let installMarwanModArgs = 'install github.com/marwan-at-work/mod/cmd/mod@latest';
        const gomodModCompatibility = constraints?.gomodMod;
        if (gomodModCompatibility) {
            if (gomodModCompatibility.startsWith('v') &&
                (0, semver_2.isValid)(gomodModCompatibility.replace((0, regex_1.regEx)(/^v/), ''))) {
                installMarwanModArgs = installMarwanModArgs.replace((0, regex_1.regEx)(/@latest$/), `@${gomodModCompatibility}`);
            }
            else {
                logger_1.logger.debug({ gomodModCompatibility }, 'marwan-at-work/mod compatibility range is not valid - skipping');
            }
        }
        else {
            logger_1.logger.debug('No marwan-at-work/mod compatibility range found - installing marwan-at-work/mod latest');
        }
        updateImportCommands.unshift(`go ${installMarwanModArgs}`);
    }
    return updateImportCommands;
}
function useModcacherw(goVersion) {
    if (!is_1.default.string(goVersion)) {
        return true;
    }
    const [, majorPart, minorPart] = (0, regex_1.regEx)(/(\d+)\.(\d+)/).exec(goVersion) ?? [];
    const [major, minor] = [majorPart, minorPart].map((x) => parseInt(x, 10));
    return (!Number.isNaN(major) &&
        !Number.isNaN(minor) &&
        (major > 1 || (major === 1 && minor >= 14)));
}
async function updateArtifacts({ packageFileName: goModFileName, updatedDeps, newPackageFileContent: newGoModContent, config, }) {
    logger_1.logger.debug(`gomod.updateArtifacts(${goModFileName})`);
    const sumFileName = goModFileName.replace((0, regex_1.regEx)(/\.mod$/), '.sum');
    const existingGoSumContent = await (0, fs_1.readLocalFile)(sumFileName);
    if (!existingGoSumContent) {
        logger_1.logger.debug('No go.sum found');
        return null;
    }
    const vendorDir = upath_1.default.join(upath_1.default.dirname(goModFileName), 'vendor/');
    const vendorModulesFileName = upath_1.default.join(vendorDir, 'modules.txt');
    const useVendor = (await (0, fs_1.readLocalFile)(vendorModulesFileName)) !== null;
    let massagedGoMod = newGoModContent;
    if (config.postUpdateOptions?.includes('gomodMassage')) {
        // Regex match inline replace directive, example:
        // replace golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
        // https://go.dev/ref/mod#go-mod-file-replace
        // replace bracket after comments, so it doesn't break the regex, doing a complex regex causes problems
        // when there's a comment and ")" after it, the regex will read replace block until comment.. and stop.
        massagedGoMod = massagedGoMod
            .split('\n')
            .map((line) => {
            if (line.trim().startsWith('//')) {
                return line.replace(')', 'renovate-replace-bracket');
            }
            return line;
        })
            .join('\n');
        const inlineReplaceRegEx = (0, regex_1.regEx)(/(\r?\n)(replace\s+[^\s]+\s+=>\s+\.\.\/.*)/g);
        // $1 will be matched with the (\r?n) group
        // $2 will be matched with the inline replace match, example
        // "// renovate-replace replace golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5"
        const inlineCommentOut = '$1// renovate-replace $2';
        // Regex match replace directive block, example:
        // replace (
        //     golang.org/x/net v1.2.3 => example.com/fork/net v1.4.5
        // )
        const blockReplaceRegEx = (0, regex_1.regEx)(/(\r?\n)replace\s*\([^)]+\s*\)/g);
        /**
         * replacerFunction for commenting out replace blocks
         * @param match A string representing a golang replace directive block
         * @returns A commented out block with // renovate-replace
         */
        const blockCommentOut = (match) => match.replace(/(\r?\n)/g, '$1// renovate-replace ');
        // Comment out golang replace directives
        massagedGoMod = massagedGoMod
            .replace(inlineReplaceRegEx, inlineCommentOut)
            .replace(blockReplaceRegEx, blockCommentOut);
        if (massagedGoMod !== newGoModContent) {
            logger_1.logger.debug('Removed some relative replace statements and comments from go.mod');
        }
    }
    const goConstraints = config.constraints?.go ?? (await getGoConstraints(goModFileName));
    try {
        await (0, fs_1.writeLocalFile)(goModFileName, massagedGoMod);
        const cmd = 'go';
        const execOptions = {
            cwdFile: goModFileName,
            extraEnv: {
                GOPATH: await (0, fs_1.ensureCacheDir)('go'),
                GOPROXY: process.env.GOPROXY,
                GOPRIVATE: process.env.GOPRIVATE,
                GONOPROXY: process.env.GONOPROXY,
                GONOSUMDB: process.env.GONOSUMDB,
                GOSUMDB: process.env.GOSUMDB,
                GOINSECURE: process.env.GOINSECURE,
                GOFLAGS: useModcacherw(goConstraints) ? '-modcacherw' : null,
                CGO_ENABLED: global_1.GlobalConfig.get('binarySource') === 'docker' ? '0' : null,
                ...(0, auth_1.getGitEnvironmentVariables)(['go']),
            },
            docker: {},
            toolConstraints: [
                {
                    toolName: 'golang',
                    constraint: goConstraints,
                },
            ],
        };
        const execCommands = [];
        let goGetDirs;
        if (config.goGetDirs) {
            goGetDirs = config.goGetDirs
                .filter((dir) => {
                const isValid = (0, fs_1.isValidLocalPath)(dir);
                if (!isValid) {
                    logger_1.logger.warn({ dir }, 'Invalid path in goGetDirs');
                }
                return isValid;
            })
                .map(shlex_1.quote)
                .join(' ');
            if (goGetDirs === '') {
                throw new Error('Invalid goGetDirs');
            }
        }
        let args = `get -d -t ${goGetDirs ?? './...'}`;
        logger_1.logger.trace({ cmd, args }, 'go get command included');
        execCommands.push(`${cmd} ${args}`);
        // Update import paths on major updates
        const isImportPathUpdateRequired = config.postUpdateOptions?.includes('gomodUpdateImportPaths') &&
            config.updateType === 'major';
        if (isImportPathUpdateRequired) {
            const updateImportCmds = getUpdateImportPathCmds(updatedDeps, config);
            if (updateImportCmds.length > 0) {
                logger_1.logger.debug(updateImportCmds, 'update import path commands included');
                // The updates
                execCommands.push(...updateImportCmds);
            }
        }
        const mustSkipGoModTidy = !config.postUpdateOptions?.includes('gomodUpdateImportPaths') &&
            config.updateType === 'major';
        if (mustSkipGoModTidy) {
            logger_1.logger.debug('go mod tidy command skipped');
        }
        let tidyOpts = '';
        if (config.postUpdateOptions?.includes('gomodTidy1.17')) {
            tidyOpts += ' -compat=1.17';
        }
        if (config.postUpdateOptions?.includes('gomodTidyE')) {
            tidyOpts += ' -e';
        }
        const isGoModTidyRequired = !mustSkipGoModTidy &&
            (config.postUpdateOptions?.includes('gomodTidy') === true ||
                config.postUpdateOptions?.includes('gomodTidy1.17') === true ||
                config.postUpdateOptions?.includes('gomodTidyE') === true ||
                (config.updateType === 'major' && isImportPathUpdateRequired));
        if (isGoModTidyRequired) {
            args = 'mod tidy' + tidyOpts;
            logger_1.logger.debug('go mod tidy command included');
            execCommands.push(`${cmd} ${args}`);
        }
        if (useVendor) {
            args = 'mod vendor';
            logger_1.logger.debug('go mod tidy command included');
            execCommands.push(`${cmd} ${args}`);
            if (isGoModTidyRequired) {
                args = 'mod tidy' + tidyOpts;
                logger_1.logger.debug('go mod tidy command included');
                execCommands.push(`${cmd} ${args}`);
            }
        }
        // We tidy one more time as a solution for #6795
        if (isGoModTidyRequired) {
            args = 'mod tidy' + tidyOpts;
            logger_1.logger.debug('go mod tidy command included');
            execCommands.push(`${cmd} ${args}`);
        }
        await (0, exec_1.exec)(execCommands, execOptions);
        const status = await (0, git_1.getRepoStatus)();
        if (!status.modified.includes(sumFileName) &&
            !status.modified.includes(goModFileName)) {
            return null;
        }
        const res = [];
        if (status.modified.includes(sumFileName)) {
            logger_1.logger.debug('Returning updated go.sum');
            res.push({
                file: {
                    type: 'addition',
                    path: sumFileName,
                    contents: await (0, fs_1.readLocalFile)(sumFileName),
                },
            });
        }
        // Include all the .go file import changes
        if (isImportPathUpdateRequired) {
            logger_1.logger.debug('Returning updated go source files for import path changes');
            for (const f of status.modified) {
                if (f.endsWith('.go')) {
                    res.push({
                        file: {
                            type: 'addition',
                            path: f,
                            contents: await (0, fs_1.readLocalFile)(f),
                        },
                    });
                }
            }
        }
        if (useVendor) {
            for (const f of status.modified.concat(status.not_added)) {
                if (f.startsWith(vendorDir)) {
                    res.push({
                        file: {
                            type: 'addition',
                            path: f,
                            contents: await (0, fs_1.readLocalFile)(f),
                        },
                    });
                }
            }
            for (const f of status.deleted || []) {
                res.push({
                    file: {
                        type: 'deletion',
                        path: f,
                    },
                });
            }
        }
        // TODO: throws in tests (#22198)
        const finalGoModContent = (await (0, fs_1.readLocalFile)(goModFileName, 'utf8'))
            .replace((0, regex_1.regEx)(/\/\/ renovate-replace /g), '')
            .replace((0, regex_1.regEx)(/renovate-replace-bracket/g), ')');
        if (finalGoModContent !== newGoModContent) {
            logger_1.logger.debug('Found updated go.mod after go.sum update');
            res.push({
                file: {
                    type: 'addition',
                    path: goModFileName,
                    contents: finalGoModContent,
                },
            });
        }
        return res;
    }
    catch (err) {
        // istanbul ignore if
        if (err.message === error_messages_1.TEMPORARY_ERROR) {
            throw err;
        }
        logger_1.logger.debug({ err }, 'Failed to update go.sum');
        return [
            {
                artifactError: {
                    lockFile: sumFileName,
                    stderr: err.message,
                },
            },
        ];
    }
}
exports.updateArtifacts = updateArtifacts;
async function getGoConstraints(goModFileName) {
    const content = (await (0, fs_1.readLocalFile)(goModFileName, 'utf8')) ?? null;
    if (!content) {
        return undefined;
    }
    const re = (0, regex_1.regEx)(/^go\s*(?<gover>\d+\.\d+)$/m);
    const match = re.exec(content);
    if (!match?.groups?.gover) {
        return undefined;
    }
    return '^' + match.groups.gover;
}
//# sourceMappingURL=artifacts.js.map