"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getConstraintFromLockFile = exports.generateLockFile = void 0;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
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 string_1 = require("../../../../util/string");
const yaml_1 = require("../../../../util/yaml");
const node_version_1 = require("./node-version");
const utils_1 = require("./utils");
function getPnpmConstraintFromUpgrades(upgrades) {
    for (const upgrade of upgrades) {
        if (upgrade.depName === 'pnpm' && upgrade.newVersion) {
            return upgrade.newVersion;
        }
    }
    return null;
}
async function generateLockFile(lockFileDir, env, config, upgrades = []) {
    const lockFileName = upath_1.default.join(lockFileDir, 'pnpm-lock.yaml');
    logger_1.logger.debug(`Spawning pnpm install to create ${lockFileName}`);
    let lockFile = null;
    let stdout;
    let stderr;
    const commands = [];
    try {
        const lazyPgkJson = (0, utils_1.lazyLoadPackageJson)(lockFileDir);
        const pnpmToolConstraint = {
            toolName: 'pnpm',
            constraint: getPnpmConstraintFromUpgrades(upgrades) ?? // if pnpm is being upgraded, it comes first
                config.constraints?.pnpm ?? // from user config or extraction
                (0, utils_1.getPackageManagerVersion)('pnpm', await lazyPgkJson.getValue()) ?? // look in package.json > packageManager or engines
                (await getConstraintFromLockFile(lockFileName)), // use lockfileVersion to find pnpm version range
        };
        const extraEnv = {
            NPM_CONFIG_CACHE: env.NPM_CONFIG_CACHE,
            npm_config_store: env.npm_config_store,
        };
        const execOptions = {
            userConfiguredEnv: config.env,
            cwdFile: lockFileName,
            extraEnv,
            docker: {},
            toolConstraints: [
                await (0, node_version_1.getNodeToolConstraint)(config, upgrades, lockFileDir, lazyPgkJson),
                pnpmToolConstraint,
            ],
        };
        // istanbul ignore if
        if (global_1.GlobalConfig.get('exposeAllEnv')) {
            extraEnv.NPM_AUTH = env.NPM_AUTH;
            extraEnv.NPM_EMAIL = env.NPM_EMAIL;
        }
        let args = '--recursive --lockfile-only';
        if (!global_1.GlobalConfig.get('allowScripts') || config.ignoreScripts) {
            args += ' --ignore-scripts';
            args += ' --ignore-pnpmfile';
        }
        logger_1.logger.trace({ args }, 'pnpm command options');
        const lockUpdates = upgrades.filter((upgrade) => upgrade.isLockfileUpdate);
        if (lockUpdates.length !== upgrades.length) {
            // This command updates the lock file based on package.json
            commands.push(`pnpm install ${args}`);
        }
        // rangeStrategy = update-lockfile
        if (lockUpdates.length) {
            logger_1.logger.debug('Performing lockfileUpdate (pnpm)');
            commands.push(`pnpm update --no-save ${lockUpdates
                // TODO: types (#22198)
                .map((update) => `${update.packageName}@${update.newVersion}`)
                .filter(string_1.uniqueStrings)
                .map(shlex_1.quote)
                .join(' ')} ${args}`);
        }
        // postUpdateOptions
        if (config.postUpdateOptions?.includes('pnpmDedupe')) {
            commands.push('pnpm dedupe --config.ignore-scripts=true');
        }
        if (upgrades.find((upgrade) => upgrade.isLockFileMaintenance)) {
            logger_1.logger.debug(`Removing ${lockFileName} first due to lock file maintenance upgrade`);
            try {
                await (0, fs_1.deleteLocalFile)(lockFileName);
            }
            catch (err) /* istanbul ignore next */ {
                logger_1.logger.debug({ err, lockFileName }, 'Error removing yarn.lock for lock file maintenance');
            }
        }
        await (0, exec_1.exec)(commands, execOptions);
        lockFile = await (0, fs_1.readLocalFile)(lockFileName, 'utf8');
    }
    catch (err) /* istanbul ignore next */ {
        if (err.message === error_messages_1.TEMPORARY_ERROR) {
            throw err;
        }
        logger_1.logger.debug({
            commands,
            err,
            stdout,
            stderr,
            type: 'pnpm',
        }, 'lock file error');
        return { error: true, stderr: err.stderr, stdout: err.stdout };
    }
    return { lockFile };
}
exports.generateLockFile = generateLockFile;
async function getConstraintFromLockFile(lockFileName) {
    let constraint = null;
    try {
        const lockfileContent = await (0, fs_1.readLocalFile)(lockFileName, 'utf8');
        if (!lockfileContent) {
            return null;
        }
        // TODO: use schema (#9610)
        const pnpmLock = (0, yaml_1.parseSingleYaml)(lockfileContent);
        if (!is_1.default.number(pnpmLock?.lockfileVersion)) {
            return null;
        }
        // find matching lockfileVersion and use its constraints
        // if no match found use lockfileVersion 5
        // lockfileVersion 5 is the minimum version required to generate the pnpm-lock.yaml file
        const { lowerConstraint, upperConstraint } = lockToPnpmVersionMapping.find((m) => m.lockfileVersion === pnpmLock.lockfileVersion) ?? {
            lockfileVersion: 5.0,
            lowerConstraint: '>=3',
            upperConstraint: '<3.5.0',
        };
        constraint = lowerConstraint;
        if (upperConstraint) {
            constraint += ` ${upperConstraint}`;
        }
    }
    catch (err) {
        logger_1.logger.warn({ err }, 'Error getting pnpm constraints from lock file');
    }
    return constraint;
}
exports.getConstraintFromLockFile = getConstraintFromLockFile;
/**
 pnpm lockfiles have corresponding version numbers called "lockfileVersion"
 each lockfileVersion can only be generated by a certain pnpm version ranges
 eg. lockfileVersion: 5.4 can only be generated by pnpm version >=7 && <8
 official list can be found here : https://github.com/pnpm/spec/tree/master/lockfile
 we use the mapping present below to find the compatible pnpm version range for a given lockfileVersion

 the various terms used in the mapping are explained below:
 lowerConstriant : lowest pnpm version that can generate the lockfileVersion
 upperConstraint : highest pnpm version that can generate the lockfileVersion
 lowerBound      : highest pnpm version that is less than the lowerConstraint
 upperBound      : lowest pnpm version that is greater than upperConstraint

 For handling future lockfileVersions, we need to:
 1. add a upperBound and upperConstraint to the current lastest lockfileVersion
 2. add an object for the new lockfileVersion with lowerBound and lowerConstraint
 */
const lockToPnpmVersionMapping = [
    { lockfileVersion: 6.0, lowerConstraint: '>=7.24.2' },
    {
        lockfileVersion: 5.4,
        lowerConstraint: '>=7',
        upperConstraint: '<8',
    },
    {
        lockfileVersion: 5.3,
        lowerConstraint: '>=6',
        upperConstraint: '<7',
    },
    {
        lockfileVersion: 5.2,
        lowerConstraint: '>=5.10.0',
        upperConstraint: '<6',
    },
    {
        lockfileVersion: 5.1,
        lowerConstraint: '>=3.5.0',
        upperConstraint: '<5.9.3',
    },
];
//# sourceMappingURL=pnpm.js.map