"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const constants_1 = require("@pnpm/constants");
const lockfile_file_1 = require("@pnpm/lockfile-file");
const npm_resolver_1 = require("@pnpm/npm-resolver");
const read_package_json_1 = require("@pnpm/read-package-json");
const store_path_1 = require("@pnpm/store-path");
const types_1 = require("@pnpm/types");
const utils_1 = require("@pnpm/utils");
const dp = require("dependency-path");
async function default_1(pkgPath, opts) {
    return _outdated([], pkgPath, opts);
}
exports.default = default_1;
async function forPackages(packages, pkgPath, opts) {
    return _outdated(packages, pkgPath, opts);
}
exports.forPackages = forPackages;
async function _outdated(forPkgs, pkgPath, opts) {
    const registries = utils_1.normalizeRegistries(opts.registries);
    const lockfileDirectory = opts.lockfileDirectory || pkgPath;
    const pkg = await read_package_json_1.fromDir(pkgPath);
    if (packageHasNoDeps(pkg))
        return [];
    const wantedLockfile = await lockfile_file_1.readWantedLockfile(lockfileDirectory, { ignoreIncompatible: false })
        || await lockfile_file_1.readCurrentLockfile(lockfileDirectory, { ignoreIncompatible: false });
    if (!wantedLockfile) {
        throw new Error('No lockfile in this directory. Run `pnpm install` to generate one.');
    }
    const storePath = await store_path_1.default(pkgPath, opts.store);
    const importerId = lockfile_file_1.getLockfileImporterId(lockfileDirectory, pkgPath);
    const currentLockfile = await lockfile_file_1.readCurrentLockfile(lockfileDirectory, { ignoreIncompatible: false }) || { importers: { [importerId]: {} } };
    const resolve = npm_resolver_1.default({
        fetchRetries: opts.fetchRetries,
        fetchRetryFactor: opts.fetchRetryFactor,
        fetchRetryMaxtimeout: opts.fetchRetryMaxtimeout,
        fetchRetryMintimeout: opts.fetchRetryMintimeout,
        metaCache: new Map(),
        offline: opts.offline,
        rawNpmConfig: opts.rawNpmConfig,
        store: storePath,
    });
    const outdated = [];
    await Promise.all(types_1.DEPENDENCIES_FIELDS.map(async (depType) => {
        if (!wantedLockfile.importers[importerId][depType])
            return;
        let pkgs = Object.keys(wantedLockfile.importers[importerId][depType]);
        if (forPkgs.length) {
            pkgs = pkgs.filter((pkgName) => forPkgs.indexOf(pkgName) !== -1);
        }
        await Promise.all(pkgs.map(async (packageName) => {
            const ref = wantedLockfile.importers[importerId][depType][packageName];
            // ignoring linked packages. (For backward compatibility)
            if (ref.startsWith('file:')) {
                return;
            }
            const relativeDepPath = dp.refToRelative(ref, packageName);
            // ignoring linked packages
            if (relativeDepPath === null)
                return;
            const pkgSnapshot = wantedLockfile.packages && wantedLockfile.packages[relativeDepPath];
            if (!pkgSnapshot) {
                throw new Error(`Invalid ${constants_1.WANTED_LOCKFILE} file. ${relativeDepPath} not found in packages field`);
            }
            const currentRef = currentLockfile.importers[importerId][depType][packageName];
            const currentRelative = currentRef && dp.refToRelative(currentRef, packageName);
            const current = currentRelative && dp.parse(currentRelative).version || currentRef;
            const wanted = dp.parse(relativeDepPath).version || ref;
            // It might be not the best solution to check for pkgSnapshot.name
            // TODO: add some other field to distinct packages not from the registry
            if (pkgSnapshot.resolution && (pkgSnapshot.resolution['type'] || pkgSnapshot.name)) { // tslint:disable-line:no-string-literal
                if (current !== wanted) {
                    outdated.push({
                        current,
                        latest: undefined,
                        packageName,
                        wanted,
                    });
                }
                return;
            }
            // TODO: what about aliased dependencies?
            // TODO: what about scoped dependencies?
            const resolution = await resolve({ alias: packageName, pref: 'latest' }, {
                registry: registries.default,
            });
            if (!resolution || !resolution.latest)
                return;
            const latest = resolution.latest;
            if (!current) {
                outdated.push({
                    latest,
                    packageName,
                    wanted,
                });
                return;
            }
            if (current !== wanted || latest !== current) {
                outdated.push({
                    current,
                    latest,
                    packageName,
                    wanted,
                });
            }
        }));
    }));
    return outdated.sort((pkg1, pkg2) => pkg1.packageName.localeCompare(pkg2.packageName));
}
// tslint:disable-next-line:no-any
function packageHasNoDeps(pkg) {
    return (!pkg.dependencies || isEmpty(pkg.dependencies))
        && (!pkg.devDependencies || isEmpty(pkg.devDependencies))
        && (!pkg.optionalDependencies || isEmpty(pkg.optionalDependencies));
}
function isEmpty(obj) {
    return Object.keys(obj).length === 0;
}
//# sourceMappingURL=index.js.map