"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 RegClient = require("npm-registry-client");
const pnpm_logger_1 = require("pnpm-logger");
const pLimit = require("p-limit");
const npa = require("npm-package-arg");
const pFilter = require("p-filter");
const R = require("ramda");
const safeIsInnerLink_1 = require("../safeIsInnerLink");
const got_1 = require("../network/got");
const getContext_1 = require("./getContext");
const installMultiple_1 = require("../install/installMultiple");
const link_1 = require("../link");
const save_1 = require("../save");
const getSaveType_1 = require("../getSaveType");
const runScript_1 = require("../runScript");
const postInstall_1 = require("../install/postInstall");
const extendOptions_1 = require("./extendOptions");
const pnpmPkgJson_1 = require("../pnpmPkgJson");
const lock_1 = require("./lock");
const shrinkwrap_1 = require("../fs/shrinkwrap");
const modulesController_1 = require("../fs/modulesController");
const removeOrphanPkgs_1 = require("./removeOrphanPkgs");
const memoize_1 = require("../memoize");
const depsToSpecs_1 = require("../depsToSpecs");
function install(maybeOpts) {
    return __awaiter(this, void 0, void 0, function* () {
        const opts = extendOptions_1.default(maybeOpts);
        const installType = 'general';
        const ctx = yield getContext_1.default(opts, installType);
        const installCtx = yield createInstallCmd(opts, ctx.shrinkwrap);
        if (!ctx.pkg)
            throw new Error('No package.json found');
        const specs = specsToInstallFromPackage(ctx.pkg, {
            production: opts.production,
            prefix: opts.prefix,
        });
        const optionalDeps = R.keys(ctx.pkg.optionalDependencies);
        return lock_1.default(ctx.storePath, () => __awaiter(this, void 0, void 0, function* () {
            const scripts = !opts.ignoreScripts && ctx.pkg && ctx.pkg.scripts || {};
            if (scripts['preinstall']) {
                npmRun('preinstall', ctx.root, opts.userAgent);
            }
            yield installInContext(installType, specs, optionalDeps, [], ctx, installCtx, opts);
            if (scripts['postinstall']) {
                npmRun('postinstall', ctx.root, opts.userAgent);
            }
            if (scripts['prepublish']) {
                npmRun('prepublish', ctx.root, opts.userAgent);
            }
        }), { stale: opts.lockStaleDuration });
    });
}
exports.install = install;
function specsToInstallFromPackage(pkg, opts) {
    const depsToInstall = Object.assign({}, !opts.production && pkg.devDependencies, pkg.optionalDependencies, pkg.dependencies);
    return depsToSpecs_1.default(depsToInstall, opts.prefix);
}
/**
 * Perform installation.
 *
 * @example
 *     install({'lodash': '1.0.0', 'foo': '^2.1.0' }, { silent: true })
 */
function installPkgs(fuzzyDeps, maybeOpts) {
    return __awaiter(this, void 0, void 0, function* () {
        const opts = extendOptions_1.default(maybeOpts);
        let packagesToInstall = Array.isArray(fuzzyDeps)
            ? argsToSpecs(fuzzyDeps, opts.tag, opts.prefix)
            : depsToSpecs_1.default(fuzzyDeps, opts.prefix);
        if (!Object.keys(packagesToInstall).length) {
            throw new Error('At least one package has to be installed');
        }
        const installType = 'named';
        const ctx = yield getContext_1.default(opts, installType);
        const installCtx = yield createInstallCmd(opts, ctx.shrinkwrap);
        const optionalDependencies = opts.saveOptional
            ? packagesToInstall.map(spec => spec.name)
            : [];
        const currentSpecs = ctx.pkg ? specsToInstallFromPackage(ctx.pkg, {
            production: opts.production,
            prefix: opts.prefix,
        }) : [];
        return lock_1.default(ctx.storePath, () => installInContext(installType, R.uniqBy(spec => spec.name, packagesToInstall.concat(currentSpecs)), R.uniq(optionalDependencies.concat(R.keys(ctx.pkg && ctx.pkg.optionalDependencies))), packagesToInstall.map(spec => spec.name), ctx, installCtx, opts), { stale: opts.lockStaleDuration });
    });
}
exports.installPkgs = installPkgs;
function argsToSpecs(args, defaultTag, where) {
    return args
        .map(arg => npa(arg, where))
        .map(spec => {
        if (spec.type === 'tag' && !spec.rawSpec) {
            spec.fetchSpec = defaultTag;
        }
        return spec;
    });
}
function getResolutions(packagesToInstall, resolvedSpecDeps) {
    return packagesToInstall
        .reduce((resolvedDeps, depSpec) => {
        if (resolvedSpecDeps[depSpec.raw]) {
            resolvedDeps[depSpec.name] = resolvedSpecDeps[depSpec.raw];
        }
        return resolvedDeps;
    }, {});
}
function installInContext(installType, packagesToInstall, optionalDependencies, newPkgs, ctx, installCtx, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        const nodeModulesPath = path.join(ctx.root, 'node_modules');
        const client = new RegClient(adaptConfig(opts));
        const parts = R.partition(spec => newPkgs.indexOf(spec.name) === -1, packagesToInstall);
        const oldSpecs = parts[0];
        const newSpecs = parts[1];
        const resolvedDependencies = getResolutions(oldSpecs, ctx.shrinkwrap.dependencies);
        const installOpts = {
            root: ctx.root,
            storePath: ctx.storePath,
            localRegistry: opts.localRegistry,
            registry: ctx.shrinkwrap.registry,
            force: opts.force,
            depth: opts.depth,
            engineStrict: opts.engineStrict,
            nodeVersion: opts.nodeVersion,
            got: got_1.default(client, {
                networkConcurrency: opts.networkConcurrency,
                rawNpmConfig: opts.rawNpmConfig,
            }),
            metaCache: opts.metaCache,
            resolvedDependencies,
            offline: opts.offline,
            rawNpmConfig: opts.rawNpmConfig,
        };
        const nonLinkedPkgs = yield pFilter(packagesToInstall, (spec) => !spec.name || safeIsInnerLink_1.default(nodeModulesPath, spec.name));
        const pkgs = yield installMultiple_1.default(installCtx, nonLinkedPkgs, optionalDependencies, installOpts);
        const linkedPkgsMap = yield link_1.default(pkgs, installCtx.installs, {
            force: opts.force,
            global: opts.global,
            baseNodeModules: nodeModulesPath,
            bin: opts.bin,
        });
        let newPkg = ctx.pkg;
        if (installType === 'named') {
            if (!ctx.pkg) {
                throw new Error('Cannot save because no package.json found');
            }
            const pkgJsonPath = path.join(ctx.root, 'package.json');
            const saveType = getSaveType_1.default(opts);
            newPkg = yield save_1.default(pkgJsonPath, pkgs.map(dep => {
                const spec = R.find(spec => spec.raw === dep.specRaw, newSpecs);
                if (!spec)
                    return null;
                return {
                    name: dep.pkg.name,
                    saveSpec: getSaveSpec(spec, dep, opts.saveExact)
                };
            }).filter(Boolean), saveType);
        }
        if (newPkg) {
            ctx.shrinkwrap.dependencies = ctx.shrinkwrap.dependencies || {};
            const deps = newPkg.dependencies || {};
            const devDeps = newPkg.devDependencies || {};
            const optionalDeps = newPkg.optionalDependencies || {};
            const getSpecFromPkg = (depName) => deps[depName] || devDeps[depName] || optionalDeps[depName];
            pkgs.forEach(dep => {
                const spec = getSpecFromPkg(dep.pkg.name);
                if (spec) {
                    ctx.shrinkwrap.dependencies[`${dep.pkg.name}@${spec}`] = shrinkwrap_1.pkgIdToRef(dep.id, dep.pkg.version, dep.resolution, ctx.shrinkwrap.registry);
                }
            });
            Object.keys(ctx.shrinkwrap.dependencies)
                .map(rawSpec => npa(rawSpec))
                .filter((depSpec) => getSpecFromPkg(depSpec.name) !== depSpec.rawSpec)
                .map((depSpec) => depSpec.raw)
                .forEach(removedDep => {
                delete ctx.shrinkwrap.dependencies[removedDep];
            });
        }
        const newShr = shrinkwrap_1.prune(ctx.shrinkwrap);
        yield removeOrphanPkgs_1.default(ctx.privateShrinkwrap, newShr, ctx.root, ctx.storePath);
        yield shrinkwrap_1.save(ctx.root, newShr);
        yield modulesController_1.save(path.join(ctx.root, 'node_modules'), {
            packageManager: `${pnpmPkgJson_1.default.name}@${pnpmPkgJson_1.default.version}`,
            storePath: ctx.storePath,
            skipped: R.uniq(R.concat(ctx.skipped.filter(skippedPkgId => !installCtx.installed.has(skippedPkgId)), Array.from(installCtx.installed).filter(pkgId => !installCtx.installs[pkgId]))),
        });
        // postinstall hooks
        if (!(opts.ignoreScripts || !installCtx.installationSequence || !installCtx.installationSequence.length)) {
            const limitChild = pLimit(opts.childConcurrency);
            yield Promise.all(installCtx.installationSequence.map(pkgId => Promise.all(R.uniqBy(linkedPkg => linkedPkg.hardlinkedLocation, R.values(linkedPkgsMap).filter(pkg => pkg.id === pkgId))
                .map(pkg => limitChild(() => __awaiter(this, void 0, void 0, function* () {
                try {
                    yield postInstall_1.default(pkg.hardlinkedLocation, installLogger(pkgId), {
                        userAgent: opts.userAgent
                    });
                }
                catch (err) {
                    if (installCtx.installs[pkgId].optional) {
                        pnpm_logger_1.default.warn({
                            message: `Skipping failed optional dependency ${pkgId}`,
                            err,
                        });
                        return;
                    }
                    throw err;
                }
            }))))));
        }
    });
}
function getSaveSpec(spec, pkg, saveExact) {
    switch (spec.type) {
        case 'version':
        case 'range':
        case 'tag':
            return `${saveExact ? '' : '^'}${pkg.pkg.version}`;
        default:
            return spec.saveSpec;
    }
}
function createInstallCmd(opts, shrinkwrap) {
    return __awaiter(this, void 0, void 0, function* () {
        return {
            installs: {},
            shrinkwrap,
            installed: new Set(),
            installationSequence: [],
            fetchingLocker: memoize_1.default(opts.fetchingConcurrency),
        };
    });
}
function adaptConfig(opts) {
    const registryLog = pnpm_logger_1.default('registry');
    return {
        proxy: {
            http: opts.proxy,
            https: opts.httpsProxy,
            localAddress: opts.localAddress
        },
        ssl: {
            certificate: opts.cert,
            key: opts.key,
            ca: opts.ca,
            strict: opts.strictSsl
        },
        retry: {
            count: opts.fetchRetries,
            factor: opts.fetchRetryFactor,
            minTimeout: opts.fetchRetryMintimeout,
            maxTimeout: opts.fetchRetryMaxtimeout
        },
        userAgent: opts.userAgent,
        log: Object.assign({}, registryLog, {
            verbose: registryLog.debug.bind(null, 'http'),
            http: registryLog.debug.bind(null, 'http'),
        }),
        defaultTag: opts.tag
    };
}
function npmRun(scriptName, pkgRoot, userAgent) {
    const result = runScript_1.sync('npm', ['run', scriptName], {
        cwd: pkgRoot,
        stdio: 'inherit',
        userAgent,
    });
    if (result.status !== 0) {
        process.exit(result.status);
    }
}
const lifecycleLogger = pnpm_logger_1.default('lifecycle');
function installLogger(pkgId) {
    return (stream, line) => {
        const logLevel = stream === 'stderr' ? 'error' : 'info';
        lifecycleLogger[logLevel]({ pkgId, line });
    };
}
//# sourceMappingURL=install.js.map