"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
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 pnpm_shrinkwrap_1 = require("pnpm-shrinkwrap");
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;
function dependenciesHierarchy(projectPath, searched, maybeOpts) {
    return __awaiter(this, void 0, void 0, function* () {
        const modules = yield modules_yaml_1.read(projectPath);
        const registries = utils_1.normalizeRegistries(Object.assign({}, maybeOpts && maybeOpts.registries, modules && modules.registries));
        const shrinkwrapDirectory = maybeOpts && maybeOpts.shrinkwrapDirectory || projectPath;
        const shrinkwrap = yield pnpm_shrinkwrap_1.readCurrent(shrinkwrapDirectory, { ignoreIncompatible: false });
        if (!shrinkwrap)
            return [];
        const opts = Object.assign({ depth: 0, only: undefined }, maybeOpts);
        const importerId = pnpm_shrinkwrap_1.getImporterId(shrinkwrapDirectory, projectPath);
        const topDeps = getFilteredDependencies(shrinkwrap.importers[importerId], opts) || {};
        const modulesDir = path.join(projectPath, 'node_modules');
        const savedDeps = getAllDirectDependencies(shrinkwrap.importers[importerId]);
        const allDirectDeps = (yield 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',
            registry: registries.default,
            searched,
        }, shrinkwrap.packages);
        const result = [];
        Object.keys(topDeps).forEach((depName) => {
            const pkgPath = dependency_path_1.refToAbsolute(topDeps[depName], depName, registries.default);
            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);
            }
        });
        yield Promise.all(unsavedDeps.map((unsavedDep) => __awaiter(this, void 0, void 0, function* () {
            let pkgPath = path.join(modulesDir, unsavedDep);
            let version;
            try {
                pkgPath = yield resolveLinkTarget(pkgPath);
                version = `link:${normalizePath(path.relative(projectPath, pkgPath))}`;
            }
            catch (err) {
                // if error happened. The package is not a link
                const pkg = yield 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(shrinkwrapImporter, opts) {
    switch (opts.only) {
        case 'prod':
            return shrinkwrapImporter.dependencies;
        case 'dev':
            return shrinkwrapImporter.devDependencies;
        default:
            return getAllDirectDependencies(shrinkwrapImporter);
    }
}
function getAllDirectDependencies(shrinkwrapImporter) {
    return Object.assign({}, shrinkwrapImporter.dependencies, shrinkwrapImporter.devDependencies, shrinkwrapImporter.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.registry);
        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