"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const lockfile_file_1 = require("@pnpm/lockfile-file");
const modules_yaml_1 = require("@pnpm/modules-yaml");
const read_modules_dir_1 = require("@pnpm/read-modules-dir");
const utils_1 = require("@pnpm/utils");
const assert = require("assert");
const dependency_path_1 = require("dependency-path");
const normalizePath = require("normalize-path");
const path = require("path");
const resolveLinkTarget = require("resolve-link-target");
const semver = require("semver");
function forPackages(packages, projectPath, opts) {
    assert(packages, 'packages should be defined');
    if (!packages.length)
        return [];
    return dependenciesHierarchy(projectPath, packages, opts);
}
exports.forPackages = forPackages;
function default_1(projectPath, opts) {
    return dependenciesHierarchy(projectPath, [], opts);
}
exports.default = default_1;
async function dependenciesHierarchy(projectPath, searched, maybeOpts) {
    const modules = await modules_yaml_1.read(projectPath);
    const registries = utils_1.normalizeRegistries(Object.assign({}, maybeOpts && maybeOpts.registries, modules && modules.registries));
    const lockfileDirectory = maybeOpts && maybeOpts.lockfileDirectory || projectPath;
    const lockfile = await lockfile_file_1.readCurrentLockfile(lockfileDirectory, { ignoreIncompatible: false });
    if (!lockfile)
        return [];
    const opts = Object.assign({ depth: 0, only: undefined }, maybeOpts);
    const importerId = lockfile_file_1.getLockfileImporterId(lockfileDirectory, projectPath);
    if (!lockfile.importers[importerId])
        return [];
    const topDeps = getFilteredDependencies(lockfile.importers[importerId], opts) || {};
    const modulesDir = path.join(projectPath, 'node_modules');
    const savedDeps = getAllDirectDependencies(lockfile.importers[importerId]);
    const allDirectDeps = await read_modules_dir_1.default(modulesDir) || [];
    const unsavedDeps = allDirectDeps.filter((directDep) => !savedDeps[directDep]);
    if (Object.keys(topDeps).length === 0 && unsavedDeps.length === 0)
        return [];
    const getChildrenTree = getTree.bind(null, {
        currentDepth: 1,
        maxDepth: opts.depth,
        modulesDir,
        prod: opts.only === 'prod',
        registries,
        searched,
    }, lockfile.packages);
    const result = [];
    Object.keys(topDeps).forEach((depName) => {
        const pkgPath = dependency_path_1.refToAbsolute(topDeps[depName], depName, registries);
        const pkg = {
            name: depName,
            path: pkgPath && path.join(modulesDir, `.${pkgPath}`) || path.join(modulesDir, '..', topDeps[depName].substr(5)),
            version: topDeps[depName],
        };
        let newEntry = null;
        const matchedSearched = searched.length && matches(searched, pkg);
        if (pkgPath === null) {
            if (searched.length && !matchedSearched)
                return;
            newEntry = { pkg };
        }
        else {
            const relativeId = dependency_path_1.refToRelative(topDeps[depName], depName);
            const dependencies = getChildrenTree([relativeId], relativeId);
            if (dependencies.length) {
                newEntry = {
                    dependencies,
                    pkg,
                };
            }
            else if (!searched.length || matches(searched, pkg)) {
                newEntry = { pkg };
            }
        }
        if (newEntry) {
            if (matchedSearched) {
                newEntry.searched = true;
            }
            result.push(newEntry);
        }
    });
    await Promise.all(unsavedDeps.map(async (unsavedDep) => {
        let pkgPath = path.join(modulesDir, unsavedDep);
        let version;
        try {
            pkgPath = await resolveLinkTarget(pkgPath);
            version = `link:${normalizePath(path.relative(projectPath, pkgPath))}`;
        }
        catch (err) {
            // if error happened. The package is not a link
            const pkg = await utils_1.safeReadPackageFromDir(pkgPath);
            version = pkg && pkg.version || 'undefined';
        }
        const pkg = {
            name: unsavedDep,
            path: pkgPath,
            version,
        };
        const matchedSearched = searched.length && matches(searched, pkg);
        if (searched.length && !matchedSearched)
            return;
        const newEntry = {
            pkg,
            saved: false,
        };
        if (matchedSearched) {
            newEntry.searched = true;
        }
        result.push(newEntry);
    }));
    return result;
}
function getFilteredDependencies(lockfileImporter, opts) {
    switch (opts.only) {
        case 'prod':
            return lockfileImporter.dependencies;
        case 'dev':
            return lockfileImporter.devDependencies;
        default:
            return getAllDirectDependencies(lockfileImporter);
    }
}
function getAllDirectDependencies(lockfileImporter) {
    return Object.assign({}, lockfileImporter.dependencies, lockfileImporter.devDependencies, lockfileImporter.optionalDependencies);
}
function getTree(opts, packages, keypath, parentId) {
    if (opts.currentDepth > opts.maxDepth || !packages || !packages[parentId])
        return [];
    const deps = opts.prod
        ? packages[parentId].dependencies
        : Object.assign({}, packages[parentId].dependencies, packages[parentId].optionalDependencies);
    if (!deps)
        return [];
    const getChildrenTree = getTree.bind(null, Object.assign({}, opts, { currentDepth: opts.currentDepth + 1 }), packages);
    const result = [];
    Object.keys(deps).forEach((depName) => {
        const pkgPath = dependency_path_1.refToAbsolute(deps[depName], depName, opts.registries);
        const pkg = {
            name: depName,
            path: pkgPath && path.join(opts.modulesDir, `.${pkgPath}`) || path.join(opts.modulesDir, '..', deps[depName].substr(5)),
            version: deps[depName],
        };
        let circular;
        const matchedSearched = opts.searched.length && matches(opts.searched, pkg);
        let newEntry = null;
        if (pkgPath === null) {
            circular = false;
            newEntry = { pkg };
        }
        else {
            const relativeId = dependency_path_1.refToRelative(deps[depName], depName); // we know for sure that relative is not null if pkgPath is not null
            circular = keypath.indexOf(relativeId) !== -1;
            const dependencies = circular ? [] : getChildrenTree(keypath.concat([relativeId]), relativeId);
            if (dependencies.length) {
                newEntry = {
                    dependencies,
                    pkg,
                };
            }
            else if (!opts.searched.length || matchedSearched) {
                newEntry = { pkg };
            }
        }
        if (newEntry) {
            if (circular) {
                newEntry.circular = true;
            }
            if (matchedSearched) {
                newEntry.searched = true;
            }
            result.push(newEntry);
        }
    });
    return result;
}
function matches(searched, pkg) {
    return searched.some((searchedPkg) => {
        if (typeof searchedPkg === 'string') {
            return pkg.name === searchedPkg;
        }
        return searchedPkg.name === pkg.name &&
            !pkg.version.startsWith('link:') &&
            semver.satisfies(pkg.version, searchedPkg.range);
    });
}
//# sourceMappingURL=index.js.map