"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.semver = exports.patch = exports.minor = exports.newest = exports.latest = exports.distTag = exports.list = exports.getPeerDependencies = exports.greatest = exports.defaultPrefix = exports.viewOne = exports.viewManyMemoized = exports.mockViewMany = exports.packageAuthorChanged = exports.normalizeNpmConfig = void 0;
const fast_memoize_1 = __importDefault(require("fast-memoize"));
const fs_1 = __importDefault(require("fs"));
const ini_1 = __importDefault(require("ini"));
const camelCase_1 = __importDefault(require("lodash/camelCase"));
const filter_1 = __importDefault(require("lodash/filter"));
const get_1 = __importDefault(require("lodash/get"));
const isEqual_1 = __importDefault(require("lodash/isEqual"));
const last_1 = __importDefault(require("lodash/last"));
const omit_1 = __importDefault(require("lodash/omit"));
const sortBy_1 = __importDefault(require("lodash/sortBy"));
const pacote_1 = __importDefault(require("pacote"));
const path_1 = __importDefault(require("path"));
const semver_1 = __importDefault(require("semver"));
const semver_utils_1 = require("semver-utils");
const spawn_please_1 = __importDefault(require("spawn-please"));
const untildify_1 = __importDefault(require("untildify"));
const filterObject_1 = __importDefault(require("../lib/filterObject"));
const keyValueBy_1 = require("../lib/keyValueBy");
const libnpmconfig_1 = __importDefault(require("../lib/libnpmconfig"));
const logging_1 = require("../lib/logging");
const versionUtil = __importStar(require("../lib/version-util"));
const filters_1 = require("./filters");
const EXPLICIT_RANGE_OPS = new Set(['-', '||', '&&', '<', '<=', '>', '>=']);
/** Returns true if the spec is an explicit version range (not ~ or ^). */
const isExplicitRange = (spec) => {
    const range = (0, semver_utils_1.parseRange)(spec);
    return range.some(parsed => EXPLICIT_RANGE_OPS.has(parsed.operator || ''));
};
/** Normalizes the keys of an npm config for pacote. */
const normalizeNpmConfig = (npmConfig, 
// config path used to determine relative cafile paths
configPath) => {
    const npmConfigToPacoteMap = {
        cafile: (capath) => {
            // load-cafile, based on github.com/npm/cli/blob/40c1b0f/lib/config/load-cafile.js
            if (!capath)
                return;
            // synchronous since it is loaded once on startup, and to avoid complexity in libnpmconfig.read
            // https://github.com/raineorshine/npm-check-updates/issues/636?notification_referrer_id=MDE4Ok5vdGlmaWNhdGlvblRocmVhZDc0Njk2NjAzMjo3NTAyNzY%3D
            const cadata = fs_1.default.readFileSync(path_1.default.resolve(configPath || '', (0, untildify_1.default)(capath)), 'utf8');
            const delim = '-----END CERTIFICATE-----';
            const output = cadata
                .split(delim)
                .filter(xs => !!xs.trim())
                .map(xs => `${xs.trimStart()}${delim}`);
            return { ca: output };
        },
        maxsockets: 'maxSockets',
        'strict-ssl': 'strictSSL',
    };
    // all config variables are read in as strings, so we need to type coerce non-strings
    // lowercased and hyphens removed for comparison purposes
    const keyTypes = {
        all: 'boolean',
        allowsameversion: 'boolean',
        audit: 'boolean',
        binlinks: 'boolean',
        color: 'boolean',
        commithooks: 'boolean',
        description: 'boolean',
        dev: 'boolean',
        diffignoreallspace: 'boolean',
        diffnameonly: 'boolean',
        diffnoprefix: 'boolean',
        difftext: 'boolean',
        dryrun: 'boolean',
        enginestrict: 'boolean',
        force: 'boolean',
        foregroundscripts: 'boolean',
        formatpackagelock: 'boolean',
        fund: 'boolean',
        gittagversion: 'boolean',
        global: 'boolean',
        globalstyle: 'boolean',
        ifpresent: 'boolean',
        ignorescripts: 'boolean',
        includestaged: 'boolean',
        includeworkspaceroot: 'boolean',
        installlinks: 'boolean',
        json: 'boolean',
        legacybundling: 'boolean',
        legacypeerdeps: 'boolean',
        link: 'boolean',
        long: 'boolean',
        offline: 'boolean',
        omitlockfileregistryresolved: 'boolean',
        packagelock: 'boolean',
        packagelockonly: 'boolean',
        parseable: 'boolean',
        preferoffline: 'boolean',
        preferonline: 'boolean',
        progress: 'boolean',
        readonly: 'boolean',
        rebuildbundle: 'boolean',
        save: 'boolean',
        savebundle: 'boolean',
        savedev: 'boolean',
        saveexact: 'boolean',
        saveoptional: 'boolean',
        savepeer: 'boolean',
        saveprod: 'boolean',
        shrinkwrap: 'boolean',
        signgitcommit: 'boolean',
        signgittag: 'boolean',
        strictpeerdeps: 'boolean',
        strictssl: 'boolean',
        timing: 'boolean',
        unicode: 'boolean',
        updatenotifier: 'boolean',
        usage: 'boolean',
        version: 'boolean',
        versions: 'boolean',
        workspacesupdate: 'boolean',
        diffunified: 'number',
        fetchretries: 'number',
        fetchretryfactor: 'number',
        fetchretrymaxtimeout: 'number',
        fetchretrymintimeout: 'number',
        fetchtimeout: 'number',
        logsmax: 'number',
        maxsockets: 'number',
        searchlimit: 'number',
        searchstaleness: 'number',
        ssopollfrequency: 'number',
        timeout: 'number',
    };
    /** Parses a string to a boolean. */
    const stringToBoolean = (s) => !!s && s !== 'false' && s !== '0';
    /** Parses a string to a number. */
    const stringToNumber = (s) => parseInt(s) || 0;
    // needed until pacote supports full npm config compatibility
    // See: https://github.com/zkat/pacote/issues/156
    const config = (0, keyValueBy_1.keyValueBy)(npmConfig, (key, value) => {
        // replace env ${VARS} in strings with the process.env value
        const normalizedValue = typeof value !== 'string'
            ? value
            : // parse stringified booleans
                keyTypes[key.replace(/-/g, '').toLowerCase()] === 'boolean'
                    ? stringToBoolean(value)
                    : keyTypes[key.replace(/-/g, '').toLowerCase()] === 'number'
                        ? stringToNumber(value)
                        : value.replace(/\${([^}]+)}/, (_, envVar) => process.env[envVar]);
        // normalize the key for pacote
        const { [key]: pacoteKey } = npmConfigToPacoteMap;
        return typeof pacoteKey === 'string'
            ? // key is mapped to a string
                { [pacoteKey]: normalizedValue }
            : // key is mapped to a function
                typeof pacoteKey === 'function'
                    ? { ...pacoteKey(normalizedValue.toString()) }
                    : // otherwise assign the camel-cased key
                        { [key.match(/^[a-z]/i) ? (0, camelCase_1.default)(key) : key]: normalizedValue };
    });
    return config;
};
exports.normalizeNpmConfig = normalizeNpmConfig;
/** Finds and parses the npm config at the given path. If the path does not exist, returns null. If no path is provided, finds and merges the global and user npm configs using libnpmconfig and sets cache: false. */
const findNpmConfig = (0, fast_memoize_1.default)((configPath) => {
    let config;
    if (configPath) {
        try {
            config = ini_1.default.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
        }
        catch (err) {
            if (err.code === 'ENOENT') {
                return null;
            }
            else {
                throw err;
            }
        }
    }
    else {
        // libnpmconfig incorrectly (?) ignores NPM_CONFIG_USERCONFIG because it is always overridden by the default builtin.userconfig
        // set userconfig manually so that it is prioritized
        const opts = libnpmconfig_1.default.read(null, {
            userconfig: process.env.npm_config_userconfig || process.env.NPM_CONFIG_USERCONFIG,
        });
        config = {
            ...opts.toJSON(),
            cache: false,
        };
    }
    return (0, exports.normalizeNpmConfig)(config, configPath);
});
// get the base config that is used for all npm queries
// this may be partially overwritten by .npmrc config files when using --deep
const npmConfig = findNpmConfig();
/** A promise that returns true if --global is deprecated on the system npm. Spawns "npm --version". */
const isGlobalDeprecated = (0, fast_memoize_1.default)(async () => {
    const cmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
    const output = await (0, spawn_please_1.default)(cmd, ['--version']);
    const npmVersion = output.trim();
    // --global was deprecated in npm v8.11.0.
    return semver_1.default.valid(npmVersion) && semver_1.default.gte(npmVersion, '8.11.0');
});
/**
 * @typedef {object} CommandAndPackageName
 * @property {string} command
 * @property {string} packageName
 */
/**
 * Parse JSON and throw an informative error on failure.
 *
 * @param result Data to be parsed
 * @param data
 * @returns
 */
function parseJson(result, data) {
    let json;
    try {
        json = JSON.parse(result);
    }
    catch (err) {
        throw new Error(`Expected JSON from "${data.command}".${data.packageName ? ` There could be problems with the ${data.packageName} package.` : ''} ${result ? 'Instead received: ' + result : 'Received empty response.'}`);
    }
    return json;
}
/**
 * Check if package author changed between current and upgraded version.
 *
 * @param packageName Name of the package
 * @param currentVersion Current version declaration (may be range)
 * @param upgradedVersion Upgraded version declaration (may be range)
 * @param npmConfigLocal Additional npm config variables that are merged into the system npm config
 * @returns A promise that fulfills with boolean value.
 */
async function packageAuthorChanged(packageName, currentVersion, upgradedVersion, options = {}, npmConfigLocal) {
    var _a, _b;
    const result = await pacote_1.default.packument(packageName, {
        ...npmConfigLocal,
        ...npmConfig,
        fullMetadata: true,
        ...(options.registry ? { registry: options.registry, silent: true } : null),
    });
    if (result.versions) {
        const pkgVersions = Object.keys(result.versions);
        const current = semver_1.default.minSatisfying(pkgVersions, currentVersion);
        const upgraded = semver_1.default.maxSatisfying(pkgVersions, upgradedVersion);
        if (current && upgraded && result.versions[current]._npmUser && result.versions[upgraded]._npmUser) {
            const currentAuthor = (_a = result.versions[current]._npmUser) === null || _a === void 0 ? void 0 : _a.name;
            const latestAuthor = (_b = result.versions[upgraded]._npmUser) === null || _b === void 0 ? void 0 : _b.name;
            return !(0, isEqual_1.default)(currentAuthor, latestAuthor);
        }
    }
    return false;
}
exports.packageAuthorChanged = packageAuthorChanged;
/** Returns true if an object is a Packument. */
const isPackument = (o) => o && (o.name || o.engines || o.version || o.versions);
/** Creates a function with the same signature as viewMany that always returns the given versions. */
const mockViewMany = (mockReturnedVersions) => (name, fields, currentVersion, options) => {
    var _a, _b;
    // a partial Packument
    const partialPackument = typeof mockReturnedVersions === 'function'
        ? (_a = mockReturnedVersions(options)) === null || _a === void 0 ? void 0 : _a[name]
        : typeof mockReturnedVersions === 'string' || isPackument(mockReturnedVersions)
            ? mockReturnedVersions
            : mockReturnedVersions[name];
    const version = isPackument(partialPackument) ? partialPackument.version : partialPackument;
    // if there is no version, hard exit
    // otherwise getPackageProtected will swallow the error
    if (!version) {
        console.error(`No mock version supplied for ${name}`);
        process.exit(1);
    }
    const time = (isPackument(partialPackument) && ((_b = partialPackument.time) === null || _b === void 0 ? void 0 : _b[version])) || new Date().toISOString();
    const packument = {
        name,
        engines: { node: '' },
        time: {
            [version]: time,
        },
        version,
        // overwritten below
        versions: [],
        ...(isPackument(partialPackument) ? partialPackument : null),
    };
    return Promise.resolve((0, keyValueBy_1.keyValueBy)(fields, field => ({
        [field]: field === 'versions'
            ? {
                [version]: packument,
            }
            : field === 'time'
                ? {
                    [version]: time,
                }
                : {
                    ...packument,
                    versions: [packument],
                },
    })));
};
exports.mockViewMany = mockViewMany;
/**
 * Returns an object of specified values retrieved by npm view.
 *
 * @param packageName   Name of the package
 * @param fields        Array of fields like versions, time, version
 * @param               currentVersion
 * @returns             dist-tags field return Index<Packument>, time field returns Index<Index<string>>>, versions field returns Index<Index<Packument>>
 */
async function viewMany(packageName, fields, currentVersion, options, retried = 0, npmConfigLocal, npmConfigWorkspaceProject) {
    var _a;
    // See: /test/helpers/stubNpmView
    if (process.env.STUB_NPM_VIEW) {
        const mockReturnedVersions = JSON.parse(process.env.STUB_NPM_VIEW);
        return (0, exports.mockViewMany)(mockReturnedVersions)(packageName, fields, currentVersion, options);
    }
    if (currentVersion && (!semver_1.default.validRange(currentVersion) || versionUtil.isWildCard(currentVersion))) {
        return Promise.resolve({});
    }
    const fieldsExtended = ((_a = options.format) === null || _a === void 0 ? void 0 : _a.includes('time')) ? [...fields, 'time'] : fields;
    // merge project npm config with base config
    const npmConfigProjectPath = options.packageFile ? path_1.default.join(options.packageFile, '../.npmrc') : null;
    const npmConfigProject = options.packageFile ? findNpmConfig(npmConfigProjectPath || undefined) : null;
    const npmConfigCWDPath = options.cwd ? path_1.default.join(options.cwd, '.npmrc') : null;
    const npmConfigCWD = options.cwd ? findNpmConfig(npmConfigCWDPath) : null;
    if (npmConfigWorkspaceProject && Object.keys(npmConfigWorkspaceProject).length > 0) {
        (0, logging_1.print)(options, `\nnpm config (workspace project):`, 'verbose');
        (0, logging_1.print)(options, (0, omit_1.default)(npmConfigWorkspaceProject, 'cache'), 'verbose');
    }
    if (npmConfig && Object.keys(npmConfig).length > 0) {
        (0, logging_1.print)(options, `\nnpm config (local):`, 'verbose');
        (0, logging_1.print)(options, (0, omit_1.default)(npmConfig, 'cache'), 'verbose');
    }
    if (npmConfigLocal && Object.keys(npmConfigLocal).length > 0) {
        (0, logging_1.print)(options, `\nnpm config (local override):`, 'verbose');
        (0, logging_1.print)(options, (0, omit_1.default)(npmConfigLocal, 'cache'), 'verbose');
    }
    if (npmConfigProject && Object.keys(npmConfigProject).length > 0) {
        (0, logging_1.print)(options, `\nnpm config (project: ${npmConfigProjectPath}):`, 'verbose');
        (0, logging_1.print)(options, (0, omit_1.default)(npmConfigProject, 'cache'), 'verbose');
    }
    if (npmConfigCWD && Object.keys(npmConfigCWD).length > 0) {
        (0, logging_1.print)(options, `\nnpm config (cwd: ${npmConfigCWDPath}):`, 'verbose');
        // omit cache since it is added to every config
        (0, logging_1.print)(options, (0, omit_1.default)(npmConfigCWD, 'cache'), 'verbose');
    }
    const npmConfigMerged = {
        ...npmConfigWorkspaceProject,
        ...npmConfig,
        ...npmConfigLocal,
        ...npmConfigProject,
        ...npmConfigCWD,
        ...(options.registry ? { registry: options.registry, silent: true } : null),
        ...(options.timeout ? { timeout: options.timeout } : null),
        fullMetadata: fieldsExtended.includes('time'),
    };
    const isMerged = npmConfigWorkspaceProject || npmConfigLocal || npmConfigProject || npmConfigCWD;
    (0, logging_1.print)(options, `\nUsing${isMerged ? ' merged' : ''} npm config:`, 'verbose');
    // omit cache since it is added to every config
    (0, logging_1.print)(options, (0, omit_1.default)(npmConfigMerged, 'cache'), 'verbose');
    let result;
    try {
        result = (await pacote_1.default.packument(packageName, npmConfigMerged));
    }
    catch (err) {
        if (options.retry && ++retried <= options.retry) {
            return viewMany(packageName, fieldsExtended, currentVersion, options, retried, npmConfigLocal);
        }
        throw err;
    }
    // select each field from the result object
    const resultNormalized = (0, keyValueBy_1.keyValueBy)(fields, field => {
        var _a;
        let value = result[field];
        // index into the result object to get the dist-tag
        if (field.startsWith('dist-tags.') && result.versions) {
            const packument = result.versions[(0, get_1.default)(result, field)];
            // since viewOne only keeps a single field, we need to add time onto the dist-tag field
            value = ((_a = options.format) === null || _a === void 0 ? void 0 : _a.includes('time')) ? { ...packument, time: result.time } : packument;
        }
        return {
            [field]: value,
        };
    });
    return resultNormalized;
}
/** Memoize viewMany for --deep performance. */
exports.viewManyMemoized = (0, fast_memoize_1.default)(viewMany);
/**
 * Returns the value of one of the properties retrieved by npm view.
 *
 * @param packageName   Name of the package
 * @param field         Field such as "versions" or "dist-tags.latest" are parsed from the pacote result (https://www.npmjs.com/package/pacote#packument)
 * @param currentVersion
 * @returns            Promised result
 */
async function viewOne(packageName, field, currentVersion, options, npmConfigLocal, npmConfigProject) {
    const result = await (0, exports.viewManyMemoized)(packageName, [field], currentVersion, options, 0, npmConfigLocal, npmConfigProject);
    return result[field];
}
exports.viewOne = viewOne;
/**
 * Spawns npm with --json. Handles different commands for Window and Linux/OSX, and automatically converts --location=global to --global on node < 8.11.0.
 *
 * @param args
 * @param [npmOptions={}]
 * @param [spawnOptions={}]
 * @returns
 */
async function spawnNpm(args, npmOptions = {}, spawnOptions = {}) {
    const cmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
    args = Array.isArray(args) ? args : [args];
    const fullArgs = args.concat(npmOptions.location
        ? (await isGlobalDeprecated())
            ? `--location=${npmOptions.location}`
            : npmOptions.location === 'global'
                ? '--global'
                : ''
        : [], npmOptions.prefix ? `--prefix=${npmOptions.prefix}` : [], '--json');
    return (0, spawn_please_1.default)(cmd, fullArgs, spawnOptions);
}
/**
 * Get platform-specific default prefix to pass on to npm.
 *
 * @param options
 * @param [options.global]
 * @param [options.prefix]
 * @returns
 */
async function defaultPrefix(options) {
    if (options.prefix) {
        return Promise.resolve(options.prefix);
    }
    const cmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
    let prefix;
    // catch spawn error which can occur on Windows
    // https://github.com/raineorshine/npm-check-updates/issues/703
    try {
        prefix = await (0, spawn_please_1.default)(cmd, ['config', 'get', 'prefix']);
    }
    catch (e) {
        const message = (e.message || e || '').toString();
        (0, logging_1.print)(options, 'Error executing `npm config get prefix`. Caught and ignored. Unsolved: https://github.com/raineorshine/npm-check-updates/issues/703. ERROR: ' +
            message, 'verbose', 'error');
    }
    // FIX: for ncu -g doesn't work on homebrew or windows #146
    // https://github.com/raineorshine/npm-check-updates/issues/146
    return options.global && (prefix === null || prefix === void 0 ? void 0 : prefix.match('Cellar'))
        ? '/usr/local'
        : // Workaround: get prefix on windows for global packages
            // Only needed when using npm api directly
            process.platform === 'win32' && options.global && !process.env.prefix
                ? prefix
                    ? prefix.trim()
                    : `${process.env.AppData}\\npm`
                : undefined;
}
exports.defaultPrefix = defaultPrefix;
/**
 * Fetches the highest version number, regardless of tag or publish time.
 *
 * @param packageName
 * @param currentVersion
 * @param options
 * @returns
 */
const greatest = async (packageName, currentVersion, options = {}, npmConfig, npmConfigProject) => {
    // known type based on 'versions'
    const versions = (await viewOne(packageName, 'versions', currentVersion, options, npmConfig, npmConfigProject));
    return {
        version: (0, last_1.default)(
        // eslint-disable-next-line fp/no-mutating-methods
        (0, filter_1.default)(versions, (0, filters_1.filterPredicate)(options))
            .map(o => o.version)
            .sort(versionUtil.compareVersions)) || null,
    };
};
exports.greatest = greatest;
/**
 * Fetches the list of peer dependencies for a specific package version.
 *
 * @param packageName
 * @param version
 * @returns Promised {packageName: version} collection
 */
const getPeerDependencies = async (packageName, version) => {
    // if version number uses >, omit the version and find latest
    // otherwise, it will error out in the shell
    // https://github.com/raineorshine/npm-check-updates/issues/1181
    const atVersion = !version.startsWith('>') ? `@${version}` : '';
    const args = ['view', `${packageName}${atVersion}`, 'peerDependencies'];
    const result = await spawnNpm(args, {}, { rejectOnError: false });
    return result ? parseJson(result, { command: [...args, '--json'].join(' ') }) : {};
};
exports.getPeerDependencies = getPeerDependencies;
/**
 * Fetches the list of all installed packages.
 *
 * @param [options]
 * @param [options.cwd]
 * @param [options.global]
 * @param [options.prefix]
 * @returns
 */
const list = async (options = {}) => {
    const result = await spawnNpm(['ls', '--depth=0'], {
        // spawnNpm takes the modern --location option and converts it to --global on older versions of npm
        ...(options.global ? { location: 'global' } : null),
        ...(options.prefix ? { prefix: options.prefix } : null),
    }, {
        ...(options.cwd ? { cwd: options.cwd } : null),
        rejectOnError: false,
    });
    const dependencies = parseJson(result, {
        command: `npm${process.platform === 'win32' ? '.cmd' : ''} ls --json${options.global ? ' --location=global' : ''}${options.prefix ? ' --prefix ' + options.prefix : ''}`,
    }).dependencies;
    return (0, keyValueBy_1.keyValueBy)(dependencies, (name, info) => {
        var _a;
        return ({
            // unmet peer dependencies have a different structure
            [name]: info.version || ((_a = info.required) === null || _a === void 0 ? void 0 : _a.version),
        });
    });
};
exports.list = list;
/**
 * Fetches the version of a package published to options.distTag.
 *
 * @param packageName
 * @param currentVersion
 * @param options
 * @returns
 */
const distTag = async (packageName, currentVersion, options = {}, npmConfig, npmConfigProject) => {
    var _a;
    const packument = (await viewOne(packageName, `dist-tags.${options.distTag}`, currentVersion, options, npmConfig, npmConfigProject)); // known type based on dist-tags.latest
    // latest should not be deprecated
    // if latest exists and latest is not a prerelease version, return it
    // if latest exists and latest is a prerelease version and --pre is specified, return it
    // if latest exists and latest not satisfies min version of engines.node
    if (packument && (0, filters_1.filterPredicate)(options)(packument)) {
        return {
            version: packument.version,
            ...(((_a = packument.time) === null || _a === void 0 ? void 0 : _a[packument.version]) ? { time: packument.time[packument.version] } : null),
        };
    }
    // If we use a custom dist-tag, we do not want to get other 'pre' versions, just the ones from this dist-tag
    if (options.distTag && options.distTag !== 'latest')
        return {};
    // if latest is a prerelease version and --pre is not specified
    // or latest is deprecated
    // find the next valid version
    // known type based on dist-tags.latest
    return (0, exports.greatest)(packageName, currentVersion, options, npmConfig, npmConfigProject);
};
exports.distTag = distTag;
/**
 * Fetches the version published to the latest tag.
 *
 * @param packageName
 * @param currentVersion
 * @param options
 * @returns
 */
const latest = async (packageName, currentVersion, options = {}, npmConfig, npmConfigProject) => (0, exports.distTag)(packageName, currentVersion, { ...options, distTag: 'latest' }, npmConfig, npmConfigProject);
exports.latest = latest;
/**
 * Fetches the most recently published version, regardless of version number.
 *
 * @param packageName
 * @param currentVersion
 * @param options
 * @returns
 */
const newest = async (packageName, currentVersion, options = {}, npmConfig, npmConfigProject) => {
    const result = await (0, exports.viewManyMemoized)(packageName, ['time', 'versions'], currentVersion, options, 0, npmConfig, npmConfigProject);
    // Generate a map of versions that satisfy the node engine.
    // result.versions is an object but is parsed as an array, so manually convert it to an object.
    // Otherwise keyValueBy will pass the predicate arguments in the wrong order.
    const versionsSatisfyingNodeEngine = (0, keyValueBy_1.keyValueBy)(Object.values(result.versions || {}), (packument) => (0, filters_1.satisfiesNodeEngine)(packument, options.nodeEngineVersion) ? { [packument.version]: true } : null);
    // filter out times that do not satisfy the node engine
    // filter out prereleases if pre:false (same as allowPreOrIsNotPre)
    const timesSatisfyingNodeEngine = (0, filterObject_1.default)((result.time || {}), version => versionsSatisfyingNodeEngine[version] && (options.pre !== false || !versionUtil.isPre(version)));
    // sort by timestamp (entry[1]) and map versions
    const versionsSortedByTime = (0, sortBy_1.default)(Object.entries(timesSatisfyingNodeEngine), 1).map(([version]) => version);
    return { version: (0, last_1.default)(versionsSortedByTime) };
};
exports.newest = newest;
/**
 * Fetches the highest version with the same major version as currentVersion.
 *
 * @param packageName
 * @param currentVersion
 * @param options
 * @returns
 */
const minor = async (packageName, currentVersion, options = {}, npmConfig, npmConfigProject) => {
    const versions = (await viewOne(packageName, 'versions', currentVersion, options, npmConfig, npmConfigProject));
    const version = versionUtil.findGreatestByLevel((0, filter_1.default)(versions, (0, filters_1.filterPredicate)(options)).map(o => o.version), currentVersion, 'minor');
    return { version };
};
exports.minor = minor;
/**
 * Fetches the highest version with the same minor and major version as currentVersion.
 *
 * @param packageName
 * @param currentVersion
 * @param options
 * @returns
 */
const patch = async (packageName, currentVersion, options = {}, npmConfig, npmConfigProject) => {
    const versions = (await viewOne(packageName, 'versions', currentVersion, options, npmConfig, npmConfigProject));
    const version = versionUtil.findGreatestByLevel((0, filter_1.default)(versions, (0, filters_1.filterPredicate)(options)).map(o => o.version), currentVersion, 'patch');
    return { version };
};
exports.patch = patch;
/**
 * Fetches the highest version that satisfies the semver range specified in the package.json.
 *
 * @param packageName
 * @param currentVersion
 * @param options
 * @returns
 */
const semver = async (packageName, currentVersion, options = {}, npmConfig, npmConfigProject) => {
    const versions = (await viewOne(packageName, 'versions', currentVersion, options, npmConfig, npmConfigProject));
    // ignore explicit version ranges
    if (isExplicitRange(currentVersion))
        return { version: null };
    const versionsFiltered = (0, filter_1.default)(versions, (0, filters_1.filterPredicate)(options)).map(o => o.version);
    // TODO: Upgrading within a prerelease does not seem to work.
    // { includePrerelease: true } does not help.
    const version = semver_1.default.maxSatisfying(versionsFiltered, currentVersion);
    return { version };
};
exports.semver = semver;
exports.default = spawnNpm;
//# sourceMappingURL=npm.js.map