"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.copyPkg = void 0;
const core_loggers_1 = require("@pnpm/core-loggers");
const logger_1 = require("@pnpm/logger");
const importIndexedDir_1 = __importDefault(require("../fs/importIndexedDir"));
const path = require("path");
const fs = require("mz/fs");
const pLimit = require("p-limit");
const exists = require("path-exists");
const limitLinking = pLimit(16);
exports.default = (packageImportMethod) => {
    const importPackage = createImportPackage(packageImportMethod);
    return (to, opts) => limitLinking(() => importPackage(to, opts));
};
function createImportPackage(packageImportMethod) {
    // this works in the following way:
    // - hardlink: hardlink the packages, no fallback
    // - clone: clone the packages, no fallback
    // - auto: try to clone or hardlink the packages, if it fails, fallback to copy
    // - copy: copy the packages, do not try to link them first
    switch (packageImportMethod !== null && packageImportMethod !== void 0 ? packageImportMethod : 'auto') {
        case 'clone':
            core_loggers_1.packageImportMethodLogger.debug({ method: 'clone' });
            return clonePkg;
        case 'hardlink':
            core_loggers_1.packageImportMethodLogger.debug({ method: 'hardlink' });
            return hardlinkPkg.bind(null, linkOrCopy);
        case 'auto': {
            return createAutoImporter();
        }
        case 'copy':
            core_loggers_1.packageImportMethodLogger.debug({ method: 'copy' });
            return copyPkg;
        default:
            throw new Error(`Unknown package import method ${packageImportMethod}`);
    }
}
function createAutoImporter() {
    let auto = initialAuto;
    return (to, opts) => auto(to, opts);
    async function initialAuto(to, opts) {
        try {
            if (!await clonePkg(to, opts))
                return undefined;
            core_loggers_1.packageImportMethodLogger.debug({ method: 'clone' });
            auto = clonePkg;
            return 'clone';
        }
        catch (err) {
            // ignore
        }
        try {
            if (!await hardlinkPkg(fs.link, to, opts))
                return undefined;
            core_loggers_1.packageImportMethodLogger.debug({ method: 'hardlink' });
            auto = hardlinkPkg.bind(null, linkOrCopy);
            return 'hardlink';
        }
        catch (err) {
            if (err.message.startsWith('EXDEV: cross-device link not permitted')) {
                logger_1.globalWarn(err.message);
                logger_1.globalInfo('Falling back to copying packages from store');
                core_loggers_1.packageImportMethodLogger.debug({ method: 'copy' });
                auto = copyPkg;
                return auto(to, opts);
            }
            // We still choose hard linking that will fall back to copying in edge cases.
            core_loggers_1.packageImportMethodLogger.debug({ method: 'hardlink' });
            auto = hardlinkPkg.bind(null, linkOrCopy);
            return auto(to, opts);
        }
    }
}
async function clonePkg(to, opts) {
    const pkgJsonPath = path.join(to, 'package.json');
    if (!opts.fromStore || opts.force || !await exists(pkgJsonPath)) {
        await importIndexedDir_1.default(cloneFile, to, opts.filesMap);
        return 'clone';
    }
    return undefined;
}
async function cloneFile(from, to) {
    await fs.copyFile(from, to, fs.constants.COPYFILE_FICLONE_FORCE);
}
async function hardlinkPkg(importFile, to, opts) {
    const pkgJsonPath = path.join(to, 'package.json');
    if (!opts.fromStore || opts.force || !await exists(pkgJsonPath) || !await pkgLinkedToStore(pkgJsonPath, opts.filesMap['package.json'], to)) {
        await importIndexedDir_1.default(importFile, to, opts.filesMap);
        return 'hardlink';
    }
    return undefined;
}
async function linkOrCopy(existingPath, newPath) {
    try {
        await fs.link(existingPath, newPath);
    }
    catch (err) {
        // If a hard link to the same file already exists
        // then trying to copy it will make an empty file from it.
        if (err['code'] === 'EEXIST')
            return;
        // In some VERY rare cases (1 in a thousand), hard-link creation fails on Windows.
        // In that case, we just fall back to copying.
        // This issue is reproducible with "pnpm add @material-ui/icons@4.9.1"
        await fs.copyFile(existingPath, newPath);
    }
}
async function pkgLinkedToStore(pkgJsonPath, pkgJsonPathInStore, to) {
    if (await isSameFile(pkgJsonPath, pkgJsonPathInStore))
        return true;
    logger_1.globalInfo(`Relinking ${to} from the store`);
    return false;
}
async function isSameFile(file1, file2) {
    const stats = await Promise.all([fs.stat(file1), fs.stat(file2)]);
    return stats[0].ino === stats[1].ino;
}
async function copyPkg(to, opts) {
    const pkgJsonPath = path.join(to, 'package.json');
    if (!opts.fromStore || opts.force || !await exists(pkgJsonPath)) {
        await importIndexedDir_1.default(fs.copyFile, to, opts.filesMap);
        return 'copy';
    }
    return undefined;
}
exports.copyPkg = copyPkg;
//# sourceMappingURL=createImportPackage.js.map