"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 pnpm_logger_1 = require("pnpm-logger");
const fs = require("mz/fs");
const path = require("path");
const rimraf = require("rimraf-then");
const resolve_1 = require("../resolve");
const requireJson_1 = require("../fs/requireJson");
const exists = require("exists-file");
const isAvailable_1 = require("./isAvailable");
const memoize_1 = require("../memoize");
const fetchResolution_1 = require("./fetchResolution");
const logInstallStatus_1 = require("../logging/logInstallStatus");
/**
 * Installs a package.
 *
 * What it does:
 *
 * - resolve() - resolve from registry.npmjs.org
 * - fetch() - download tarball into node_modules/.store/{name}@{version}
 * - recurse into its dependencies
 * - symlink node_modules/{name}
 *
 * @param {Object} ctx - the context.
 * @param {Object} pkgMeta - meta info about the package to install.
 *
 * @example
 *     install(ctx, 'rimraf@2', './node_modules')
 */
function fetch(ctx, spec, modules, options) {
    return __awaiter(this, void 0, void 0, function* () {
        pnpm_logger_1.default.debug('installing ' + spec.raw);
        const loggedPkg = {
            rawSpec: spec.rawSpec,
            name: spec.name,
        };
        // Dependency path to the current package. Not actually needed anmyore
        // outside getting its length
        // => ['babel-core@6.4.5', 'babylon@6.4.5', 'babel-runtime@5.8.35']
        const keypath = (options && options.keypath || []);
        try {
            let resolution = ctx.shrinkwrap[spec.raw];
            if (!resolution) {
                // it might be a bundleDependency, in which case, don't bother
                const available = !options.force && (yield isAvailable_1.default(spec, modules));
                if (available) {
                    const fetchedPkg = yield saveCachedResolution();
                    return fetchedPkg;
                }
            }
            if (!resolution || options.update) {
                resolution = yield resolve_1.default(spec, {
                    loggedPkg,
                    got: options.got,
                    root: options.root,
                    linkLocal: options.linkLocal,
                    tag: options.tag
                });
                if (resolution.tarball || resolution.repo) {
                    ctx.shrinkwrap[spec.raw] = Object.assign({}, resolution);
                    delete ctx.shrinkwrap[spec.raw].pkg;
                    delete ctx.shrinkwrap[spec.raw].fetch;
                    delete ctx.shrinkwrap[spec.raw].root;
                }
            }
            const target = path.join(options.storePath, resolution.id);
            const fetchingFiles = fetchToStoreCached({
                fetchLocks: ctx.fetchLocks,
                target,
                resolution,
                loggedPkg,
                got: options.got,
                force: options.force,
            });
            const fetchingPkg = resolution.pkg
                ? Promise.resolve(resolution.pkg)
                : fetchingFiles.then(() => requireJson_1.default(path.join(target, 'package.json')));
            const fetchedPkg = {
                fetchingPkg,
                fetchingFiles,
                id: resolution.id,
                fromCache: false,
                path: target,
                srcPath: resolution.root,
                abort: function () {
                    return __awaiter(this, void 0, void 0, function* () {
                        try {
                            yield fetchingFiles;
                        }
                        finally {
                            return rimraf(target);
                        }
                    });
                },
            };
            return fetchedPkg;
        }
        catch (err) {
            logInstallStatus_1.default({ status: 'error', pkg: loggedPkg });
            throw err;
        }
        function saveCachedResolution() {
            return __awaiter(this, void 0, void 0, function* () {
                const target = path.join(modules, spec.name);
                const stat = yield fs.lstat(target);
                if (stat.isSymbolicLink()) {
                    const linkPath = yield fs.readlink(target);
                    return save(path.resolve(linkPath, target));
                }
                return save(target);
                function save(fullpath) {
                    return __awaiter(this, void 0, void 0, function* () {
                        const data = yield requireJson_1.default(path.join(fullpath, 'package.json'));
                        return {
                            fetchingPkg: Promise.resolve(data),
                            fetchingFiles: Promise.resolve(),
                            id: path.basename(fullpath),
                            fromCache: true,
                            path: fullpath,
                            abort: () => Promise.resolve(),
                        };
                    });
                }
            });
        }
    });
}
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = fetch;
/**
 * Fetch to `.store/lodash@4.0.0`
 * If an ongoing build is already working, use it. Also, if that ongoing build
 * is part of the dependency chain (ie, it's a circular dependency), use its stub
 */
function fetchToStoreCached(opts) {
    return memoize_1.default(opts.fetchLocks, opts.resolution.id, function () {
        return __awaiter(this, void 0, void 0, function* () {
            const target = opts.target;
            const targetStage = `${opts.target}_stage`;
            const targetExists = yield exists(target);
            if (opts.force || !targetExists) {
                // We fetch into targetStage directory first and then fs.rename() it to the
                // target directory.
                yield rimraf(targetStage);
                if (targetExists) {
                    yield rimraf(target);
                }
                logInstallStatus_1.default({ status: 'download-queued', pkg: opts.loggedPkg });
                yield fetchResolution_1.default(opts.resolution, targetStage, { got: opts.got, loggedPkg: opts.loggedPkg });
                // fs.rename(oldPath, newPath) is an atomic operation, so we do it at the
                // end
                yield fs.rename(targetStage, target);
            }
            const pkg = yield requireJson_1.default(path.join(target, 'package.json'));
        });
    });
}
//# sourceMappingURL=fetch.js.map