"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 path = require("path");
const R = require("ramda");
const get_npm_tarball_url_1 = require("get-npm-tarball-url");
const exists = require("path-exists");
const fetch_1 = require("./fetch");
const logInstallStatus_1 = require("../logging/logInstallStatus");
const shrinkwrap_1 = require("../fs/shrinkwrap");
const depsToSpecs_1 = require("../depsToSpecs");
const getIsInstallable_1 = require("./getIsInstallable");
const pkgIdToFilename_1 = require("../fs/pkgIdToFilename");
function installMultiple(ctx, specs, options) {
    return __awaiter(this, void 0, void 0, function* () {
        const resolvedDependencies = options.resolvedDependencies || {};
        const pkgAddresses = (yield Promise.all(specs
            .map((spec) => __awaiter(this, void 0, void 0, function* () {
            const reference = resolvedDependencies[spec.name];
            const pkgShortId = reference && shrinkwrap_1.getPkgShortId(reference, spec.name);
            const dependencyShrinkwrap = pkgShortId && ctx.shrinkwrap.packages[pkgShortId];
            const pkgId = dependencyShrinkwrap && dependencyShrinkwrap.id ||
                reference && shrinkwrap_1.getPkgId(reference, spec.name, ctx.shrinkwrap.registry);
            const shrinkwrapResolution = pkgShortId && dependencyShrinkwrap
                ? dependencyShrToResolution(pkgShortId, dependencyShrinkwrap, options.registry)
                : undefined;
            return yield install(spec, ctx, Object.assign({}, options, {
                pkgId,
                resolvedDependencies: dependencyShrinkwrap &&
                    Object.assign({}, dependencyShrinkwrap.dependencies, dependencyShrinkwrap.optionalDependencies) || {},
                shrinkwrapResolution,
            }));
        }))))
            .filter(Boolean);
        return pkgAddresses;
    });
}
exports.default = installMultiple;
function dependencyShrToResolution(pkgShortId, depShr, registry) {
    if (!depShr.resolution['type'] && !depShr.resolution['tarball']) {
        return Object.assign({}, depShr.resolution, {
            tarball: getTarball()
        });
    }
    return depShr.resolution;
    function getTarball() {
        const noPrefixPkgShortId = pkgShortId.substr(1);
        const divideAt = noPrefixPkgShortId.lastIndexOf('/');
        return get_npm_tarball_url_1.default(noPrefixPkgShortId.substr(0, divideAt), noPrefixPkgShortId.substr(divideAt + 1), { registry });
    }
}
function install(spec, ctx, options) {
    return __awaiter(this, void 0, void 0, function* () {
        const keypath = options.keypath || [];
        const proceed = keypath.length <= options.depth;
        const parentIsInstallable = options.parentIsInstallable === undefined || options.parentIsInstallable;
        if (!proceed && options.pkgId && (yield exists(path.join(options.nodeModules, `.${options.pkgId}`)))) {
            return null;
        }
        const registry = normalizeRegistry(spec.scope && options.rawNpmConfig[`${spec.scope}:registry`] || options.registry);
        const dependentId = keypath[keypath.length - 1];
        const loggedPkg = {
            rawSpec: spec.rawSpec,
            name: spec.name,
            dependentId,
        };
        logInstallStatus_1.default({
            status: 'installing',
            pkg: loggedPkg,
        });
        const fetchedPkg = yield fetch_1.default(spec, {
            loggedPkg,
            update: options.update,
            fetchingLocker: ctx.fetchingLocker,
            registry,
            prefix: options.prefix,
            storePath: options.storePath,
            localRegistry: options.localRegistry,
            metaCache: options.metaCache,
            got: options.got,
            shrinkwrapResolution: options.shrinkwrapResolution,
            pkgId: options.pkgId,
            offline: options.offline,
        });
        if (options.parentNodeId.indexOf(`:${dependentId}:${fetchedPkg.id}:`) !== -1) {
            return null;
        }
        const pkg = yield fetchedPkg.fetchingPkg;
        logInstallStatus_1.default({ status: 'downloaded_manifest', pkgId: fetchedPkg.id, pkgVersion: pkg.version });
        const currentIsInstallable = (options.force ||
            (yield getIsInstallable_1.default(fetchedPkg.id, pkg, fetchedPkg, {
                optional: spec.optional,
                engineStrict: options.engineStrict,
                nodeVersion: options.nodeVersion,
            })));
        const installable = parentIsInstallable && currentIsInstallable;
        // using colon as it will never be used inside a package ID
        const nodeId = `${options.parentNodeId}${fetchedPkg.id}:`;
        if (installable) {
            ctx.skipped.delete(fetchedPkg.id);
        }
        if (!ctx.installs[fetchedPkg.id]) {
            if (!installable) {
                // optional dependencies are resolved for consistent shrinkwrap.yaml files
                // but installed only on machines that are supported by the package
                ctx.skipped.add(fetchedPkg.id);
            }
            ctx.installs[fetchedPkg.id] = {
                id: fetchedPkg.id,
                resolution: fetchedPkg.resolution,
                srcPath: fetchedPkg.srcPath,
                optional: spec.optional,
                name: pkg.name,
                version: pkg.version,
                dev: spec.dev,
                fetchingFiles: fetchedPkg.fetchingFiles,
                path: fetchedPkg.path,
                specRaw: spec.raw,
                peerDependencies: pkg.peerDependencies || {},
                optionalDependencies: new Set(R.keys(pkg.optionalDependencies)),
                hasBundledDependencies: !!(pkg.bundledDependencies || pkg.bundleDependencies),
                localLocation: path.join(options.nodeModules, `.${pkgIdToFilename_1.default(fetchedPkg.id)}`),
            };
            const children = yield installDependencies(pkg, spec, fetchedPkg.id, ctx, Object.assign({}, options, {
                referencedFrom: fetchedPkg.srcPath,
                parentIsInstallable: installable,
                currentDepth: options.currentDepth + 1,
                parentNodeId: nodeId,
            }));
            ctx.childrenIdsByParentId[fetchedPkg.id] = children.map(child => child.pkgId);
            ctx.tree[nodeId] = {
                nodeId,
                pkg: ctx.installs[fetchedPkg.id],
                children: children.map(child => child.nodeId),
                depth: options.currentDepth,
                installable,
            };
        }
        else {
            ctx.installs[fetchedPkg.id].dev = ctx.installs[fetchedPkg.id].dev && spec.dev;
            ctx.installs[fetchedPkg.id].optional = ctx.installs[fetchedPkg.id].optional && spec.optional;
            ctx.nodesToBuild.push({
                nodeId,
                pkg: ctx.installs[fetchedPkg.id],
                depth: options.currentDepth,
                installable,
            });
        }
        // we need this for saving to package.json
        if (options.currentDepth === 0) {
            ctx.installs[fetchedPkg.id].specRaw = spec.raw;
        }
        logInstallStatus_1.default({ status: 'dependencies_installed', pkgId: fetchedPkg.id });
        return {
            nodeId,
            pkgId: fetchedPkg.id,
        };
    });
}
function normalizeRegistry(registry) {
    if (registry.endsWith('/'))
        return registry;
    return `${registry}/`;
}
function installDependencies(pkg, parentSpec, pkgId, ctx, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const depsInstallOpts = Object.assign({}, opts, {
            keypath: opts.keypath.concat([pkgId]),
        });
        const bundledDeps = pkg.bundleDependencies || pkg.bundledDependencies || [];
        const filterDeps = getNotBundledDeps.bind(null, bundledDeps);
        const deps = depsToSpecs_1.default(filterDeps(Object.assign({}, pkg.optionalDependencies, pkg.dependencies)), {
            where: opts.referencedFrom,
            devDependencies: pkg.devDependencies || {},
            optionalDependencies: pkg.optionalDependencies || {},
        });
        return yield installMultiple(ctx, deps, depsInstallOpts);
    });
}
function getNotBundledDeps(bundledDeps, deps) {
    return Object.keys(deps)
        .filter(depName => bundledDeps.indexOf(depName) === -1)
        .reduce((notBundledDeps, depName) => {
        notBundledDeps[depName] = deps[depName];
        return notBundledDeps;
    }, {});
}
//# sourceMappingURL=installMultiple.js.map