"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());
    });
};
const path = require('path');
const seq = require('promisequence');
const chalk = require('chalk');
const createDebug = require('debug');
const RegClient = require('npm-registry-client');
const logger = require('@zkochan/logger');
const cloneDeep = require('lodash.clonedeep');
const got_1 = require('../network/got');
const getContext_1 = require('./getContext');
const installMultiple_1 = require('../install/installMultiple');
const save_1 = require('../save');
const linkPeers_1 = require('../install/linkPeers');
const runtimeError_1 = require('../runtimeError');
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 storeController_1 = require('../fs/storeController');
const modulesController_1 = require('../fs/modulesController');
const uninstall_1 = require('./uninstall');
const flattenDependencies_1 = require('../install/flattenDependencies');
const mkdirp_1 = require('../fs/mkdirp');
const env_1 = require('../env');
function install(maybeOpts) {
    return __awaiter(this, void 0, void 0, function* () {
        const opts = extendOptions_1.default(maybeOpts);
        const ctx = yield getContext_1.default(opts);
        const installCtx = yield createInstallCmd(opts, ctx.store, ctx.cache);
        if (!ctx.pkg)
            throw runtimeError_1.default('No package.json found');
        const packagesToInstall = Object.assign({}, ctx.pkg.dependencies || {});
        if (!opts.production)
            Object.assign(packagesToInstall, ctx.pkg.devDependencies || {});
        return lock_1.default(ctx.storePath, () => installInContext('general', packagesToInstall, ctx, installCtx, opts));
    });
}
exports.install = install;
/**
 * 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* () {
        let packagesToInstall = mapify(fuzzyDeps);
        if (!Object.keys(packagesToInstall).length) {
            throw new Error('At least one package has to be installed');
        }
        const opts = extendOptions_1.default(maybeOpts);
        const ctx = yield getContext_1.default(opts);
        const installCtx = yield createInstallCmd(opts, ctx.store, ctx.cache);
        return lock_1.default(ctx.storePath, () => installInContext('named', packagesToInstall, ctx, installCtx, opts));
    });
}
exports.installPkgs = installPkgs;
function installInContext(installType, packagesToInstall, ctx, installCtx, opts) {
    return __awaiter(this, void 0, void 0, function* () {
        // TODO: ctx.store should not be muted. installMultiple should return a new store
        const oldStore = cloneDeep(ctx.store);
        const nodeModulesPath = path.join(ctx.root, 'node_modules');
        yield mkdirp_1.default(nodeModulesPath);
        const client = new RegClient(adaptConfig(opts));
        const pkgs = yield lock_1.default(ctx.cache, function () {
            return __awaiter(this, void 0, void 0, function* () {
                const installOpts = {
                    linkLocal: opts.linkLocal,
                    dependent: ctx.root,
                    root: ctx.root,
                    storePath: ctx.storePath,
                    force: opts.force,
                    depth: opts.depth,
                    tag: opts.tag,
                    engineStrict: opts.engineStrict,
                    nodeVersion: opts.nodeVersion,
                    got: got_1.default(client, {
                        cachePath: ctx.cache,
                        cacheTTL: opts.cacheTTL
                    }),
                    fetchingFiles: Promise.resolve(),
                };
                return yield installMultiple_1.default(installCtx, packagesToInstall, ctx.pkg && ctx.pkg && ctx.pkg.optionalDependencies || {}, nodeModulesPath, installOpts);
            });
        });
        if (opts.flatTree) {
            console.log('Flattening the dependency tree');
            yield flattenDependencies_1.default(ctx.root, ctx.storePath, pkgs, ctx.store.packages);
        }
        if (installType === 'named') {
            const saveType = getSaveType_1.default(opts);
            if (saveType) {
                if (!ctx.pkg) {
                    throw new Error('Cannot save because no package.json found');
                }
                const inputNames = Object.keys(packagesToInstall);
                const savedPackages = pkgs.filter((pkg) => inputNames.indexOf(pkg.pkg.name) > -1);
                const pkgJsonPath = path.join(ctx.root, 'package.json');
                yield save_1.default(pkgJsonPath, savedPackages, saveType, opts.saveExact);
            }
        }
        const newStore = Object.assign({}, ctx.store, {
            pnpm: pnpmPkgJson_1.default.version
        });
        yield removeOrphanPkgs(oldStore, newStore, ctx.root, ctx.storePath);
        yield storeController_1.save(ctx.storePath, newStore);
        if (ctx.isFirstInstallation) {
            yield modulesController_1.save(path.join(ctx.root, 'node_modules'), { storePath: ctx.storePath });
        }
        if (!env_1.preserveSymlinks) {
            yield linkPeers_1.default(ctx.storePath, installCtx.installs);
        }
        else {
            yield linkPeers_1.linkPeersWhenSymlinksPreserved(ctx.storePath, installCtx.installs);
        }
        // postinstall hooks
        if (!(opts.ignoreScripts || !installCtx.piq || !installCtx.piq.length)) {
            yield seq(installCtx.piq.map(pkg => postInstall_1.default(pkg.path, installLogger(pkg.pkgId))
                .catch(err => {
                if (installCtx.installs[pkg.pkgId].optional) {
                    console.log('Skipping failed optional dependency ' + pkg.pkgId + ':');
                    console.log(err.message || err);
                    return;
                }
                throw err;
            })));
        }
        if (!opts.ignoreScripts && ctx.pkg) {
            const scripts = ctx.pkg && ctx.pkg.scripts || {};
            if (scripts['postinstall']) {
                npmRun('postinstall', ctx.root);
            }
            if (installType === 'general' && scripts['prepublish']) {
                npmRun('prepublish', ctx.root);
            }
        }
    });
}
function removeOrphanPkgs(oldStoreJson, newStoreJson, root, store) {
    const oldDeps = oldStoreJson.packages[root] && oldStoreJson.packages[root].dependencies || {};
    const newDeps = newStoreJson.packages[root] && newStoreJson.packages[root].dependencies || {};
    const maybeUninstallPkgs = Object.keys(oldDeps)
        .filter(depName => oldDeps[depName] !== newDeps[depName])
        .map(depName => oldDeps[depName]);
    const uninstallPkgs = uninstall_1.tryUninstall(maybeUninstallPkgs, newStoreJson, root);
    return Promise.all(uninstallPkgs.map(pkgId => uninstall_1.removePkgFromStore(pkgId, store)));
}
function createInstallCmd(opts, store, cache) {
    return __awaiter(this, void 0, void 0, function* () {
        return {
            fetchLocks: {},
            installLocks: {},
            installs: {},
            store,
        };
    });
}
function adaptConfig(opts) {
    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({}, logger, {
            verbose: logger.log.bind(null, 'verbose'),
            http: logger.log.bind(null, 'http')
        }),
        defaultTag: opts.tag
    };
}
function npmRun(scriptName, pkgRoot) {
    const result = runScript_1.sync('npm', ['run', scriptName], {
        cwd: pkgRoot,
        stdio: 'inherit'
    });
    if (result.status !== 0) {
        process.exit(result.status);
    }
}
function installLogger(pkgId) {
    return (stream, line) => {
        createDebug('pnpm:post_install')(`${pkgId} ${line}`);
        if (stream === 'stderr') {
            console.log(chalk.blue(pkgId) + '! ' + chalk.gray(line));
            return;
        }
        console.log(chalk.blue(pkgId) + '  ' + chalk.gray(line));
    };
}
function mapify(pkgs) {
    if (!pkgs)
        return {};
    if (Array.isArray(pkgs)) {
        return pkgs.reduce((pkgsMap, pkgRequest) => {
            const matches = /(@?[^@]+)@(.*)/.exec(pkgRequest);
            if (!matches) {
                pkgsMap[pkgRequest] = '*';
            }
            else {
                pkgsMap[matches[1]] = matches[2];
            }
            return pkgsMap;
        }, {});
    }
    return pkgs;
}
//# sourceMappingURL=install.js.map