"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UvProcessor = void 0;
const tslib_1 = require("tslib");
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const shlex_1 = require("shlex");
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 auth_1 = require("../../../../util/git/auth");
const host_rules_1 = require("../../../../util/host-rules");
const result_1 = require("../../../../util/result");
const url_1 = require("../../../../util/url");
const pypi_1 = require("../../../datasource/pypi");
const util_1 = require("../../util");
const schema_1 = require("../schema");
const utils_1 = require("../utils");
const uvUpdateCMD = 'uv lock';
class UvProcessor {
    process(project, deps) {
        const uv = project.tool?.uv;
        if (is_1.default.nullOrUndefined(uv)) {
            return deps;
        }
        deps.push(...(0, utils_1.parseDependencyList)(utils_1.depTypes.uvDevDependencies, uv['dev-dependencies']));
        // https://docs.astral.sh/uv/concepts/dependencies/#dependency-sources
        // Skip sources that do not make sense to handle (e.g. path).
        if (uv.sources) {
            for (const dep of deps) {
                // istanbul ignore if
                if (!dep.packageName) {
                    continue;
                }
                // Using `packageName` as it applies PEP 508 normalization, which is
                // also applied by uv when matching a source to a dependency.
                const depSource = uv.sources[dep.packageName];
                if (depSource) {
                    dep.depType = utils_1.depTypes.uvSources;
                    if ('url' in depSource) {
                        dep.skipReason = 'unsupported-url';
                    }
                    else if ('path' in depSource) {
                        dep.skipReason = 'path-dependency';
                    }
                    else if ('workspace' in depSource) {
                        dep.skipReason = 'inherited-dependency';
                    }
                    else {
                        (0, util_1.applyGitSource)(dep, depSource.git, depSource.rev, depSource.tag, depSource.branch);
                    }
                }
            }
        }
        return deps;
    }
    async extractLockedVersions(project, deps, packageFile) {
        const lockFileName = (0, fs_1.getSiblingFileName)(packageFile, 'uv.lock');
        const lockFileContent = await (0, fs_1.readLocalFile)(lockFileName, 'utf8');
        if (lockFileContent) {
            const { val: lockFileMapping, err } = result_1.Result.parse(lockFileContent, schema_1.UvLockfileSchema).unwrap();
            if (err) {
                logger_1.logger.debug({ packageFile, err }, `Error parsing uv lock file`);
            }
            else {
                for (const dep of deps) {
                    const packageName = dep.packageName;
                    if (packageName && packageName in lockFileMapping) {
                        dep.lockedVersion = lockFileMapping[packageName];
                    }
                }
            }
        }
        return Promise.resolve(deps);
    }
    async updateArtifacts(updateArtifact, project) {
        const { config, updatedDeps, packageFileName } = updateArtifact;
        const isLockFileMaintenance = config.updateType === 'lockFileMaintenance';
        // abort if no lockfile is defined
        const lockFileName = (0, fs_1.getSiblingFileName)(packageFileName, 'uv.lock');
        try {
            const existingLockFileContent = await (0, fs_1.readLocalFile)(lockFileName, 'utf8');
            if (is_1.default.nullOrUndefined(existingLockFileContent)) {
                logger_1.logger.debug('No uv.lock found');
                return null;
            }
            const pythonConstraint = {
                toolName: 'python',
                constraint: config.constraints?.python ?? project.project?.['requires-python'],
            };
            const uvConstraint = {
                toolName: 'uv',
                constraint: config.constraints?.uv,
            };
            const extraEnv = {
                ...(0, auth_1.getGitEnvironmentVariables)(['pep621']),
                ...getUvExtraIndexUrl(updateArtifact.updatedDeps),
            };
            const execOptions = {
                cwdFile: packageFileName,
                extraEnv,
                docker: {},
                userConfiguredEnv: config.env,
                toolConstraints: [pythonConstraint, uvConstraint],
            };
            // on lockFileMaintenance do not specify any packages and update the complete lock file
            // else only update specific packages
            let cmd;
            if (isLockFileMaintenance) {
                cmd = `${uvUpdateCMD} --upgrade`;
            }
            else {
                cmd = generateCMD(updatedDeps);
            }
            await (0, exec_1.exec)(cmd, execOptions);
            // check for changes
            const fileChanges = [];
            const newLockContent = await (0, fs_1.readLocalFile)(lockFileName, 'utf8');
            const isLockFileChanged = existingLockFileContent !== newLockContent;
            if (isLockFileChanged) {
                fileChanges.push({
                    file: {
                        type: 'addition',
                        path: lockFileName,
                        contents: newLockContent,
                    },
                });
            }
            else {
                logger_1.logger.debug('uv.lock is unchanged');
            }
            return fileChanges.length ? fileChanges : null;
        }
        catch (err) {
            // istanbul ignore if
            if (err.message === error_messages_1.TEMPORARY_ERROR) {
                throw err;
            }
            logger_1.logger.debug({ err }, 'Failed to update uv lock file');
            return [
                {
                    artifactError: {
                        lockFile: lockFileName,
                        stderr: err.message,
                    },
                },
            ];
        }
    }
}
exports.UvProcessor = UvProcessor;
function generateCMD(updatedDeps) {
    const deps = [];
    for (const dep of updatedDeps) {
        switch (dep.depType) {
            case utils_1.depTypes.optionalDependencies: {
                deps.push(dep.depName.split('/')[1]);
                break;
            }
            case utils_1.depTypes.uvDevDependencies:
            case utils_1.depTypes.uvSources: {
                deps.push(dep.depName);
                break;
            }
            case utils_1.depTypes.buildSystemRequires:
                // build requirements are not locked in the lock files, no need to update.
                break;
            default: {
                deps.push(dep.packageName);
            }
        }
    }
    return `${uvUpdateCMD} ${deps.map((dep) => `--upgrade-package ${(0, shlex_1.quote)(dep)}`).join(' ')}`;
}
function getMatchingHostRule(url) {
    return (0, host_rules_1.find)({ hostType: pypi_1.PypiDatasource.id, url });
}
function getUvExtraIndexUrl(deps) {
    const pyPiRegistryUrls = deps
        .filter((dep) => dep.datasource === pypi_1.PypiDatasource.id)
        .map((dep) => dep.registryUrls)
        .flat();
    const registryUrls = new Set(pyPiRegistryUrls);
    const extraIndexUrls = [];
    for (const registryUrl of registryUrls) {
        const parsedUrl = (0, url_1.parseUrl)(registryUrl);
        if (!parsedUrl) {
            continue;
        }
        const rule = getMatchingHostRule(parsedUrl.toString());
        if (rule.username) {
            parsedUrl.username = rule.username;
        }
        if (rule.password) {
            parsedUrl.password = rule.password;
        }
        extraIndexUrls.push(parsedUrl.toString());
    }
    return {
        UV_EXTRA_INDEX_URL: extraIndexUrls.join(' '),
    };
}
//# sourceMappingURL=uv.js.map