"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.allowedPipOptions = exports.optionsWithArguments = exports.disallowedPipOptions = exports.constraintLineRegex = void 0;
exports.getPythonVersionConstraint = getPythonVersionConstraint;
exports.getPipToolsVersionConstraint = getPipToolsVersionConstraint;
exports.getExecOptions = getExecOptions;
exports.extractHeaderCommand = extractHeaderCommand;
exports.extractPythonVersion = extractPythonVersion;
exports.getRegistryCredVarsFromPackageFiles = getRegistryCredVarsFromPackageFiles;
exports.matchManager = matchManager;
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 logger_1 = require("../../../logger");
const array_1 = require("../../../util/array");
const fs_1 = require("../../../util/fs");
const util_1 = require("../../../util/fs/util");
const hostRules = tslib_1.__importStar(require("../../../util/host-rules"));
const regex_1 = require("../../../util/regex");
function getPythonVersionConstraint(config, extractedPythonVersion) {
    const { constraints = {} } = config;
    const { python } = constraints;
    if (python) {
        logger_1.logger.debug('Using python constraint from config');
        return python;
    }
    if (extractedPythonVersion) {
        logger_1.logger.debug('Using python constraint extracted from the lock file');
        return `==${extractedPythonVersion}`;
    }
    return undefined;
}
function getPipToolsVersionConstraint(config) {
    const { constraints = {} } = config;
    const { pipTools } = constraints;
    if (is_1.default.string(pipTools)) {
        logger_1.logger.debug('Using pipTools constraint from config');
        return pipTools;
    }
    return '';
}
async function getExecOptions(config, cwd, extraEnv, extractedPythonVersion) {
    const constraint = getPythonVersionConstraint(config, extractedPythonVersion);
    const pipToolsConstraint = getPipToolsVersionConstraint(config);
    const execOptions = {
        cwd: (0, util_1.ensureLocalPath)(cwd),
        docker: {},
        userConfiguredEnv: config.env,
        toolConstraints: [
            {
                toolName: 'python',
                constraint,
            },
            {
                toolName: 'pip-tools',
                constraint: pipToolsConstraint,
            },
        ],
        extraEnv: {
            PIP_CACHE_DIR: await (0, fs_1.ensureCacheDir)('pip'),
            PIP_NO_INPUT: 'true', // ensure pip doesn't block forever waiting for credentials on stdin
            PIP_KEYRING_PROVIDER: 'import',
            PYTHON_KEYRING_BACKEND: 'keyrings.envvars.keyring.EnvvarsKeyring',
            ...extraEnv,
        },
    };
    return execOptions;
}
exports.constraintLineRegex = (0, regex_1.regEx)(/^(#.*?\r?\n)+# {4}(?<command>\S*)(?<arguments> .*?)?\r?\n/);
exports.disallowedPipOptions = [
    '--no-header', // header is required by this manager
];
exports.optionsWithArguments = [
    '--output-file',
    '--extra',
    '--extra-index-url',
    '--resolver',
    '--constraint',
];
exports.allowedPipOptions = [
    '-v',
    '--all-extras',
    '--allow-unsafe',
    '--generate-hashes',
    '--no-emit-index-url',
    '--emit-index-url',
    '--strip-extras',
    '--index-url',
    ...exports.optionsWithArguments,
];
// TODO(not7cd): test on all correct headers, even with CUSTOM_COMPILE_COMMAND
function extractHeaderCommand(content, fileName) {
    const compileCommand = exports.constraintLineRegex.exec(content);
    if (compileCommand?.groups === undefined) {
        throw new Error(`Failed to extract command from header in ${fileName} ${content}`);
    }
    logger_1.logger.trace(`pip-compile: found header in ${fileName}: \n${compileCommand[0]}`);
    const command = compileCommand.groups.command;
    const argv = [command];
    const isCustomCommand = command !== 'pip-compile';
    if (compileCommand.groups.arguments) {
        argv.push(...(0, shlex_1.split)(compileCommand.groups.arguments));
    }
    logger_1.logger.debug({ fileName, argv, isCustomCommand }, `pip-compile: extracted command from header`);
    const result = {
        argv,
        command,
        isCustomCommand,
        outputFile: '',
        sourceFiles: [],
    };
    for (const arg of argv.slice(1)) {
        // TODO(not7cd): check for "--option -- argument" case
        if (!arg.startsWith('-')) {
            result.sourceFiles.push(arg);
            continue;
        }
        throwForDisallowedOption(arg);
        throwForNoEqualSignInOptionWithArgument(arg);
        throwForUnknownOption(arg);
        if (arg.includes('=')) {
            const [option, value] = arg.split('=');
            if (option === '--extra') {
                result.extra = result.extra ?? [];
                result.extra.push(value);
            }
            else if (option === '--extra-index-url') {
                result.extraIndexUrl = result.extraIndexUrl ?? [];
                result.extraIndexUrl.push(value);
                // TODO: add to secrets? next PR
            }
            else if (option === '--constraint') {
                result.constraintsFiles = result.constraintsFiles ?? [];
                result.constraintsFiles.push(value);
            }
            else if (option === '--output-file') {
                if (result.outputFile) {
                    throw new Error('Cannot use multiple --output-file options');
                }
                result.outputFile = upath_1.default.normalize(value);
            }
            else if (option === '--index-url') {
                if (result.indexUrl) {
                    throw new Error('Cannot use multiple --index-url options');
                }
                result.indexUrl = value;
                // TODO: add to secrets? next PR
            }
            else {
                logger_1.logger.debug({ option }, `pip-compile: option not handled`);
            }
            continue;
        }
        if (arg === '--no-emit-index-url') {
            result.noEmitIndexUrl = true;
            continue;
        }
        if (arg === '--emit-index-url') {
            result.emitIndexUrl = true;
            continue;
        }
        if (arg === '--all-extras') {
            result.allExtras = true;
            continue;
        }
        logger_1.logger.debug({ option: arg }, `pip-compile: option not handled`);
    }
    logger_1.logger.trace({
        ...result,
    }, 'Parsed pip-compile command from header');
    if (result.noEmitIndexUrl && result.emitIndexUrl) {
        throw new Error('Cannot use both --no-emit-index-url and --emit-index-url');
    }
    if (result.sourceFiles.length === 0) {
        throw new Error('No source files detected in command, pass at least one package file explicitly');
    }
    return result;
}
const pythonVersionRegex = (0, regex_1.regEx)(/^(#.*?\r?\n)*# This file is autogenerated by pip-compile with Python (?<pythonVersion>\d+(\.\d+)*)\s/, 'i');
function extractPythonVersion(content, fileName) {
    const match = pythonVersionRegex.exec(content);
    if (match?.groups === undefined) {
        logger_1.logger.warn(`pip-compile: failed to extract Python version from header in ${fileName} ${content}`);
        return undefined;
    }
    logger_1.logger.trace(`pip-compile: found Python version header in ${fileName}: \n${match[0]}`);
    const { pythonVersion } = match.groups;
    logger_1.logger.debug({ fileName, pythonVersion }, `pip-compile: extracted Python version from header`);
    return pythonVersion;
}
function throwForDisallowedOption(arg) {
    if (exports.disallowedPipOptions.includes(arg)) {
        throw new Error(`Option ${arg} not allowed for this manager`);
    }
}
function throwForNoEqualSignInOptionWithArgument(arg) {
    if (exports.optionsWithArguments.includes(arg)) {
        throw new Error(`Option ${arg} must have equal sign '=' separating it's argument`);
    }
}
function throwForUnknownOption(arg) {
    if (arg.includes('=')) {
        const [option] = arg.split('=');
        if (exports.allowedPipOptions.includes(option)) {
            return;
        }
    }
    if (exports.allowedPipOptions.includes(arg)) {
        return;
    }
    throw new Error(`Option ${arg} not supported (yet)`);
}
function getRegistryCredEnvVars(url, index) {
    const hostRule = hostRules.find({ url: url.href });
    logger_1.logger.debug(hostRule, `Found host rule for url ${url.href}`);
    const ret = {};
    if (!!hostRule.username || !!hostRule.password) {
        ret[`KEYRING_SERVICE_NAME_${index}`] = url.hostname;
        ret[`KEYRING_SERVICE_USERNAME_${index}`] = hostRule.username ?? '';
        ret[`KEYRING_SERVICE_PASSWORD_${index}`] = hostRule.password ?? '';
    }
    return ret;
}
function cleanUrl(url) {
    try {
        // Strip everything but protocol, host, and port
        const urlObj = new URL(url);
        return new URL(urlObj.origin);
    }
    catch {
        return null;
    }
}
function getRegistryCredVarsFromPackageFiles(packageFiles) {
    const urls = [];
    for (const packageFile of packageFiles) {
        urls.push(...(packageFile.registryUrls ?? []), ...(packageFile.additionalRegistryUrls ?? []));
    }
    logger_1.logger.debug(urls, 'Extracted registry URLs from package files');
    const uniqueHosts = new Set(urls.map(cleanUrl).filter(array_1.isNotNullOrUndefined));
    let allCreds = {};
    for (const [index, host] of [...uniqueHosts].entries()) {
        const hostCreds = getRegistryCredEnvVars(host, index);
        allCreds = {
            ...allCreds,
            ...hostCreds,
        };
    }
    return allCreds;
}
function matchManager(filename) {
    if (filename.endsWith('setup.py')) {
        return 'pip_setup';
    }
    if (filename.endsWith('setup.cfg')) {
        return 'setup-cfg';
    }
    if (filename.endsWith('pyproject.toml')) {
        return 'pep621';
    }
    // naive, could be improved, maybe use pip_requirements.fileMatch
    if (filename.endsWith('.in') || filename.endsWith('.txt')) {
        return 'pip_requirements';
    }
    return 'unknown';
}
//# sourceMappingURL=common.js.map