"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 logger_1 = require("@pnpm/logger");
const loadJsonFile = require("load-json-file");
const mkdirp = require("mkdirp-promise");
const path = require("path");
const exists = require("path-exists");
const renameOverwrite = require("rename-overwrite");
const rimraf = require("rimraf-then");
const symlinkDir = require("symlink-dir");
const writeJsonFile = require("write-json-file");
const fetchResolution_1 = require("./fetchResolution");
const pkgIdToFilename_1 = require("./fs/pkgIdToFilename");
const readPkg_1 = require("./fs/readPkg");
const loggers_1 = require("./loggers");
const pkgIsUntouched_1 = require("./pkgIsUntouched");
const resolve_1 = require("./resolve");
function fetch(spec, options) {
    return __awaiter(this, void 0, void 0, function* () {
        try {
            let latest;
            let pkg;
            let resolution = options.shrinkwrapResolution;
            let pkgId = options.pkgId;
            if (!resolution || options.update) {
                const resolveResult = yield resolve_1.default(spec, {
                    downloadPriority: options.downloadPriority,
                    got: options.got,
                    loggedPkg: options.loggedPkg,
                    metaCache: options.metaCache,
                    offline: options.offline,
                    prefix: options.prefix,
                    registry: options.registry,
                    storePath: options.storePath,
                });
                // keep the shrinkwrap resolution when possible
                // to keep the original shasum
                if (pkgId !== resolveResult.id || !resolution) {
                    resolution = resolveResult.resolution;
                }
                pkgId = resolveResult.id;
                pkg = resolveResult.package;
                latest = resolveResult.latest;
            }
            const id = pkgId;
            loggers_1.progressLogger.debug({ status: 'resolved', pkgId: id, pkg: options.loggedPkg });
            if (resolution.type === 'directory') {
                if (!pkg) {
                    throw new Error(`Couldn't read package.json of local dependency ${spec}`);
                }
                return {
                    id,
                    isLocal: true,
                    pkg,
                    resolution,
                };
            }
            const targetRelative = pkgIdToFilename_1.default(id);
            const target = path.join(options.storePath, targetRelative);
            if (!options.fetchingLocker[id]) {
                options.fetchingLocker[id] = fetchToStore({
                    got: options.got,
                    offline: options.offline,
                    pkg,
                    pkgId: id,
                    prefix: options.prefix,
                    resolution: resolution,
                    storeIndex: options.storeIndex,
                    storePath: options.storePath,
                    target,
                    targetRelative,
                    verifyStoreIntegrity: options.verifyStoreIntegrity,
                });
            }
            return {
                calculatingIntegrity: options.fetchingLocker[id].calculatingIntegrity,
                fetchingFiles: options.fetchingLocker[id].fetchingFiles,
                fetchingPkg: options.fetchingLocker[id].fetchingPkg,
                id,
                isLocal: false,
                latest,
                path: target,
                resolution,
            };
        }
        catch (err) {
            loggers_1.progressLogger.debug({ status: 'error', pkg: options.loggedPkg });
            throw err;
        }
    });
}
exports.default = fetch;
function fetchToStore(opts) {
    const fetchingPkg = differed();
    const fetchingFiles = differed();
    const calculatingIntegrity = differed();
    doFetchToStore();
    return {
        calculatingIntegrity: calculatingIntegrity.promise,
        fetchingFiles: fetchingFiles.promise,
        fetchingPkg: opts.pkg && Promise.resolve(opts.pkg) || fetchingPkg.promise,
    };
    function doFetchToStore() {
        return __awaiter(this, void 0, void 0, function* () {
            try {
                loggers_1.progressLogger.debug({
                    pkgId: opts.pkgId,
                    status: 'resolving_content',
                });
                const target = opts.target;
                const linkToUnpacked = path.join(target, 'package');
                // We can safely assume that if there is no data about the package in `store.json` then
                // it is not in the store yet.
                // In case there is record about the package in `store.json`, we check it in the file system just in case
                const targetExists = opts.storeIndex[opts.targetRelative] && (yield exists(path.join(linkToUnpacked, 'package.json')));
                if (targetExists) {
                    // if target exists and it wasn't modified, then no need to refetch it
                    const satisfiedIntegrity = opts.verifyStoreIntegrity
                        ? yield pkgIsUntouched_1.default(linkToUnpacked)
                        : yield loadJsonFile(path.join(path.dirname(linkToUnpacked), 'integrity.json'));
                    if (satisfiedIntegrity) {
                        loggers_1.progressLogger.debug({
                            pkgId: opts.pkgId,
                            status: 'found_in_store',
                        });
                        fetchingFiles.resolve({
                            index: satisfiedIntegrity,
                            isNew: false,
                        });
                        if (!opts.pkg) {
                            readPkg_1.fromDir(linkToUnpacked)
                                .then(fetchingPkg.resolve)
                                .catch(fetchingPkg.reject);
                        }
                        calculatingIntegrity.resolve(undefined);
                        return;
                    }
                    logger_1.default.warn(`Refetching ${target} to store, as it was modified`);
                }
                // We fetch into targetStage directory first and then fs.rename() it to the
                // target directory.
                const targetStage = `${target}_stage`;
                yield rimraf(targetStage);
                let packageIndex = {};
                yield Promise.all([
                    (() => __awaiter(this, void 0, void 0, function* () {
                        packageIndex = yield fetchResolution_1.default(opts.resolution, targetStage, {
                            got: opts.got,
                            offline: opts.offline,
                            pkgId: opts.pkgId,
                            prefix: opts.prefix,
                            storePath: opts.storePath,
                        });
                    }))(),
                    // removing only the folder with the unpacked files
                    // not touching tarball and integrity.json
                    targetExists && (yield rimraf(path.join(target, 'node_modules'))),
                ]);
                loggers_1.progressLogger.debug({
                    pkgId: opts.pkgId,
                    status: 'fetched',
                });
                // fetchingFilse shouldn't care about when this is saved at all
                if (!targetExists) {
                    (() => __awaiter(this, void 0, void 0, function* () {
                        const integrity = opts.verifyStoreIntegrity
                            ? yield packageIndex.integrityPromise
                            : yield packageIndex.headers;
                        writeJsonFile(path.join(target, 'integrity.json'), integrity, { indent: null });
                        calculatingIntegrity.resolve(undefined);
                    }))();
                }
                else {
                    calculatingIntegrity.resolve(undefined);
                }
                let pkg;
                if (opts.pkg) {
                    pkg = opts.pkg;
                }
                else {
                    pkg = yield readPkg_1.fromDir(targetStage);
                    fetchingPkg.resolve(pkg);
                }
                const unpacked = path.join(target, 'node_modules', pkg.name);
                yield mkdirp(path.dirname(unpacked));
                // rename(oldPath, newPath) is an atomic operation, so we do it at the
                // end
                yield renameOverwrite(targetStage, unpacked);
                yield symlinkDir(unpacked, linkToUnpacked);
                fetchingFiles.resolve({
                    index: packageIndex.headers,
                    isNew: true,
                });
            }
            catch (err) {
                fetchingFiles.reject(err);
                if (!opts.pkg) {
                    fetchingPkg.reject(err);
                }
            }
        });
    }
}
// tslint:disable-next-line
function noop() { }
function differed() {
    let pResolve = noop;
    let pReject = noop;
    const promise = new Promise((resolve, reject) => {
        pResolve = resolve;
        pReject = reject;
    });
    return {
        promise,
        reject: pReject,
        resolve: pResolve,
    };
}
//# sourceMappingURL=fetch.js.map