"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getConstraints = exports.getPnpmLock = exports.detectPnpmWorkspaces = exports.findPnpmWorkspace = exports.extractPnpmFilters = void 0;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const find_packages_1 = require("find-packages");
const js_yaml_1 = require("js-yaml");
const semver_1 = tslib_1.__importDefault(require("semver"));
const upath_1 = tslib_1.__importDefault(require("upath"));
const global_1 = require("../../../../config/global");
const logger_1 = require("../../../../logger");
const fs_1 = require("../../../../util/fs");
const regex_1 = require("../../../../util/regex");
function isPnpmLockfile(obj) {
    return is_1.default.plainObject(obj) && 'lockfileVersion' in obj;
}
async function extractPnpmFilters(fileName) {
    try {
        // TODO #7154
        const contents = (0, js_yaml_1.load)((await (0, fs_1.readLocalFile)(fileName, 'utf8')), {
            json: true,
        });
        if (!Array.isArray(contents.packages) ||
            !contents.packages.every((item) => is_1.default.string(item))) {
            logger_1.logger.trace({ fileName }, 'Failed to find required "packages" array in pnpm-workspace.yaml');
            return undefined;
        }
        return contents.packages;
    }
    catch (err) {
        logger_1.logger.trace({ fileName, err }, 'Failed to parse pnpm-workspace.yaml');
        return undefined;
    }
}
exports.extractPnpmFilters = extractPnpmFilters;
async function findPnpmWorkspace(packageFile) {
    // search for pnpm-workspace.yaml
    const workspaceYamlPath = await (0, fs_1.findLocalSiblingOrParent)(packageFile, 'pnpm-workspace.yaml');
    if (!workspaceYamlPath) {
        logger_1.logger.trace({ packageFile }, 'Failed to locate pnpm-workspace.yaml in a parent directory.');
        return null;
    }
    // search for pnpm-lock.yaml next to pnpm-workspace.yaml
    const pnpmLockfilePath = (0, fs_1.getSiblingFileName)(workspaceYamlPath, 'pnpm-lock.yaml');
    if (!(await (0, fs_1.localPathExists)(pnpmLockfilePath))) {
        logger_1.logger.trace({ workspaceYamlPath, packageFile }, 'Failed to find a pnpm-lock.yaml sibling for the workspace.');
        return null;
    }
    return {
        lockFilePath: pnpmLockfilePath,
        workspaceYamlPath,
    };
}
exports.findPnpmWorkspace = findPnpmWorkspace;
async function detectPnpmWorkspaces(packageFiles) {
    logger_1.logger.debug(`Detecting pnpm Workspaces`);
    const packagePathCache = new Map();
    for (const p of packageFiles) {
        const { packageFile, managerData } = p;
        const { pnpmShrinkwrap } = managerData;
        // check if pnpmShrinkwrap-file has already been provided
        if (pnpmShrinkwrap) {
            logger_1.logger.trace({ packageFile, pnpmShrinkwrap }, 'Found an existing pnpm shrinkwrap file; skipping pnpm monorepo check.');
            continue;
        }
        // search for corresponding pnpm workspace
        // TODO #7154
        const pnpmWorkspace = await findPnpmWorkspace(packageFile);
        if (pnpmWorkspace === null) {
            continue;
        }
        const { workspaceYamlPath, lockFilePath } = pnpmWorkspace;
        // check if package matches workspace filter
        if (!packagePathCache.has(workspaceYamlPath)) {
            const filters = await extractPnpmFilters(workspaceYamlPath);
            const { localDir } = global_1.GlobalConfig.get();
            const packages = await (0, find_packages_1.findPackages)(upath_1.default.dirname(upath_1.default.join(localDir, workspaceYamlPath)), {
                patterns: filters,
                // Match the ignores used in @pnpm/find-workspace-packages
                ignore: ['**/node_modules/**', '**/bower_components/**'],
            });
            const packagePaths = packages.map((pkg) => upath_1.default.join(pkg.dir, 'package.json'));
            packagePathCache.set(workspaceYamlPath, packagePaths);
        }
        const packagePaths = packagePathCache.get(workspaceYamlPath);
        const isPackageInWorkspace = packagePaths?.some((p) => p.endsWith(packageFile));
        if (isPackageInWorkspace) {
            p.managerData ??= {};
            p.managerData.pnpmShrinkwrap = lockFilePath;
        }
        else {
            logger_1.logger.trace({ packageFile, workspaceYamlPath }, `Didn't find the package in the pnpm workspace`);
        }
    }
}
exports.detectPnpmWorkspaces = detectPnpmWorkspaces;
async function getPnpmLock(filePath) {
    try {
        const pnpmLockRaw = await (0, fs_1.readLocalFile)(filePath, 'utf8');
        if (!pnpmLockRaw) {
            throw new Error('Unable to read pnpm-lock.yaml');
        }
        const lockParsed = (0, js_yaml_1.load)(pnpmLockRaw);
        if (!isPnpmLockfile(lockParsed)) {
            throw new Error('Invalid or empty lockfile');
        }
        logger_1.logger.trace({ lockParsed }, 'pnpm lockfile parsed');
        // field lockfileVersion is type string in lockfileVersion = 6 and type number in < 6
        const lockfileVersion = is_1.default.number(lockParsed.lockfileVersion)
            ? lockParsed.lockfileVersion
            : parseFloat(lockParsed.lockfileVersion);
        const lockedVersions = {};
        const packagePathRegex = (0, regex_1.regEx)(/^\/(?<packageName>.+)(?:@|\/)(?<version>[^/@]+)$/); // eg. "/<packageName>(@|/)<version>"
        for (const packagePath of Object.keys(lockParsed.packages ?? {})) {
            const result = packagePath.match(packagePathRegex);
            if (!result?.groups) {
                logger_1.logger.trace(`Invalid package path ${packagePath}`);
                continue;
            }
            const packageName = result.groups.packageName;
            const version = result.groups.version;
            logger_1.logger.trace({
                packagePath,
                packageName,
                version,
            });
            lockedVersions[packageName] = version;
        }
        return {
            lockedVersions,
            lockfileVersion,
        };
    }
    catch (err) {
        logger_1.logger.debug({ filePath, err }, 'Warning: Exception parsing pnpm lockfile');
        return { lockedVersions: {} };
    }
}
exports.getPnpmLock = getPnpmLock;
function getConstraints(lockfileVersion, constraints) {
    let newConstraints = constraints;
    // 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 { lowerBound, upperBound, lowerConstraint, upperConstraint } = lockToPnpmVersionMapping.find((m) => m.lockfileVersion === lockfileVersion) ?? {
        lockfileVersion: 5.0,
        lowerBound: '2.24.0',
        upperBound: '3.5.0',
        lowerConstraint: '>=3',
        upperConstraint: '<3.5.0',
    };
    // inorder to ensure that the constraint doesn't allow any pnpm versions that can't generate the extracted lockfileVersion
    // compare the current constraint to the lowerBound and upperBound of the lockfileVersion
    // if the current constraint is not comaptible, add the lowerConstraint and upperConstraint, whichever is needed
    if (newConstraints) {
        // if constraint satisfies versions lower than lowerBound add the lowerConstraint to narrow the range
        if (semver_1.default.satisfies(lowerBound, newConstraints)) {
            newConstraints += ` ${lowerConstraint}`;
        }
        // if constraint satisfies versions higher than upperBound add the upperConstraint to narrow the range
        if (upperBound &&
            upperConstraint &&
            semver_1.default.satisfies(upperBound, newConstraints)) {
            newConstraints += ` ${upperConstraint}`;
        }
    }
    // if no constraint is present, add the lowerConstraint and upperConstraint corresponding to the lockfileVersion
    else {
        newConstraints = `${lowerConstraint}${upperConstraint ? ` ${upperConstraint}` : ''}`;
    }
    return newConstraints;
}
exports.getConstraints = getConstraints;
/**
 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, lowerBound: '7.32.0', lowerConstraint: '>=8' },
    {
        lockfileVersion: 5.4,
        lowerBound: '6.35.1',
        upperBound: '8.0.0',
        lowerConstraint: '>=7',
        upperConstraint: '<8',
    },
    {
        lockfileVersion: 5.3,
        lowerBound: '5.18.10',
        upperBound: '7.0.0',
        lowerConstraint: '>=6',
        upperConstraint: '<7',
    },
    {
        lockfileVersion: 5.2,
        lowerBound: '5.9.3',
        upperBound: '5.18.10',
        lowerConstraint: '>=5.10.0',
        upperConstraint: '<6',
    },
    {
        lockfileVersion: 5.1,
        lowerBound: '3.4.1',
        upperBound: '5.9.3',
        lowerConstraint: '>=3.5.0',
        upperConstraint: '<5.9.3',
    },
];
//# sourceMappingURL=pnpm.js.map