"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateArtifacts = void 0;
const shlex_1 = require("shlex");
const error_messages_1 = require("../../../constants/error-messages");
const logger_1 = require("../../../logger");
const array_1 = require("../../../util/array");
const exec_1 = require("../../../util/exec");
const fs_1 = require("../../../util/fs");
const auth_1 = require("../../../util/git/auth");
const regex_1 = require("../../../util/regex");
const locked_version_1 = require("./locked-version");
async function cargoUpdate(manifestPath, isLockFileMaintenance, constraint) {
    let cmd = `cargo update --config net.git-fetch-with-cli=true --manifest-path ${(0, shlex_1.quote)(manifestPath)}`;
    // If we're updating a specific crate, `cargo-update` requires `--workspace`
    // for more information, see: https://github.com/renovatebot/renovate/issues/12332
    if (!isLockFileMaintenance) {
        cmd += ` --workspace`;
    }
    const execOptions = {
        extraEnv: { ...(0, auth_1.getGitEnvironmentVariables)(['cargo']) },
        docker: {},
        toolConstraints: [{ toolName: 'rust', constraint }],
    };
    await (0, exec_1.exec)(cmd, execOptions);
}
async function cargoUpdatePrecise(manifestPath, updatedDeps, constraint) {
    // First update all dependencies that have been bumped in `Cargo.toml`.
    const cmds = [
        'cargo update --config net.git-fetch-with-cli=true' +
            ` --manifest-path ${(0, shlex_1.quote)(manifestPath)} --workspace`,
    ];
    // Update individual dependencies to their `newVersion`. Necessary when
    // using the `update-lockfile` rangeStrategy which doesn't touch Cargo.toml.
    for (const dep of updatedDeps) {
        cmds.push(`cargo update --config net.git-fetch-with-cli=true` +
            ` --manifest-path ${(0, shlex_1.quote)(manifestPath)}` +
            ` --package ${dep.packageName}@${dep.lockedVersion}` +
            ` --precise ${dep.newVersion}`);
    }
    const execOptions = {
        extraEnv: { ...(0, auth_1.getGitEnvironmentVariables)(['cargo']) },
        docker: {},
        toolConstraints: [{ toolName: 'rust', constraint }],
    };
    await (0, exec_1.exec)(cmds, execOptions);
}
async function updateArtifacts(updateArtifact) {
    return await updateArtifactsImpl(updateArtifact);
}
exports.updateArtifacts = updateArtifacts;
async function updateArtifactsImpl({ packageFileName, updatedDeps, newPackageFileContent, config, }, recursionLimit = 10) {
    logger_1.logger.debug(`cargo.updateArtifacts(${packageFileName})`);
    // For standalone package crates, the `Cargo.lock` will be in the same
    // directory as `Cargo.toml` (ie. a sibling). For cargo workspaces, it
    // will be further up.
    const lockFileName = await (0, fs_1.findLocalSiblingOrParent)(packageFileName, 'Cargo.lock');
    const existingLockFileContent = lockFileName
        ? await (0, fs_1.readLocalFile)(lockFileName)
        : null;
    if (!existingLockFileContent || !lockFileName) {
        logger_1.logger.debug('No Cargo.lock found');
        return null;
    }
    const isLockFileMaintenance = config.updateType === 'lockFileMaintenance';
    if (!isLockFileMaintenance && !updatedDeps?.length) {
        logger_1.logger.debug('No more dependencies to update');
        return [
            {
                file: {
                    type: 'addition',
                    path: lockFileName,
                    contents: existingLockFileContent,
                },
            },
        ];
    }
    try {
        await (0, fs_1.writeLocalFile)(packageFileName, newPackageFileContent);
        logger_1.logger.debug('Updating ' + lockFileName);
        if (isLockFileMaintenance) {
            await cargoUpdate(packageFileName, true, config.constraints?.rust);
        }
        else {
            const missingDep = updatedDeps.find((dep) => !dep.lockedVersion);
            if (missingDep) {
                // If there is a dependency without a locked version then log a warning
                // and perform a regular workspace lockfile update.
                logger_1.logger.warn(`Missing locked version for dependency \`${missingDep.depName}\``);
                await cargoUpdate(packageFileName, false, config.constraints?.rust);
            }
            else {
                // If all dependencies have locked versions then update them precisely.
                await cargoUpdatePrecise(packageFileName, updatedDeps, config.constraints?.rust);
            }
        }
        logger_1.logger.debug('Returning updated Cargo.lock');
        const newCargoLockContent = await (0, fs_1.readLocalFile)(lockFileName);
        if (existingLockFileContent === newCargoLockContent) {
            logger_1.logger.debug('Cargo.lock is unchanged');
            return null;
        }
        return [
            {
                file: {
                    type: 'addition',
                    path: lockFileName,
                    contents: newCargoLockContent,
                },
            },
        ];
    }
    catch (err) {
        // istanbul ignore if
        if (err.message === error_messages_1.TEMPORARY_ERROR) {
            throw err;
        }
        // Sometimes `cargo update` will fail when a preceding dependency update
        // causes another dependency to update. In this case we can no longer
        // reference the dependency by its old version, so we filter it out
        // and retry recursively.
        const newCargoLockContent = await (0, fs_1.readLocalFile)(lockFileName, 'utf8');
        if (recursionLimit > 0 &&
            newCargoLockContent &&
            (0, regex_1.regEx)(/error: package ID specification/).test(err.stderr)) {
            const versions = (0, locked_version_1.extractLockFileContentVersions)(newCargoLockContent);
            const newUpdatedDeps = updatedDeps.filter((dep) => !(0, array_1.coerceArray)(versions?.get(dep.packageName)).includes(dep.newVersion));
            if (newUpdatedDeps.length < updatedDeps.length) {
                logger_1.logger.debug('Dependency already up to date - reattempting recursively');
                return updateArtifactsImpl({
                    packageFileName,
                    updatedDeps: newUpdatedDeps,
                    newPackageFileContent,
                    config,
                }, recursionLimit - 1);
            }
        }
        logger_1.logger.debug({ err }, 'Failed to update Cargo lock file');
        return [
            {
                artifactError: {
                    lockFile: lockFileName,
                    stderr: err.message,
                },
            },
        ];
    }
}
//# sourceMappingURL=artifacts.js.map