"use strict";
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 fs = require("mz/fs");
const pLimit = require("p-limit");
const path = require("path");
const exists = require("path-exists");
const importIndexedDir_1 = require("../fs/importIndexedDir");
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 || 'auto') {
        case 'clone':
            return clonePkg;
        case 'hardlink':
            return hardlinkPkg;
        case 'auto': {
            return createAutoImporter();
        }
        case 'copy':
            return copyPkg;
        default:
            throw new Error(`Unknown package import method ${packageImportMethod}`);
    }
}
function createAutoImporter() {
    let auto = initialAuto;
    return auto;
    async function initialAuto(to, opts) {
        try {
            await clonePkg(to, opts);
            auto = clonePkg;
            return;
        }
        catch (err) {
            // ignore
        }
        try {
            await hardlinkPkg(to, opts);
            auto = hardlinkPkg;
            return;
        }
        catch (err) {
            if (!err.message.startsWith('EXDEV: cross-device link not permitted'))
                throw err;
            logger_1.globalWarn(err.message);
            logger_1.globalInfo('Falling back to copying packages from store');
            auto = copyPkg;
            await auto(to, opts);
        }
    }
}
async function clonePkg(to, opts) {
    const pkgJsonPath = path.join(to, 'package.json');
    if (!opts.fromStore || opts.force || !await exists(pkgJsonPath)) {
        core_loggers_1.importingLogger.debug({ to, method: 'clone' });
        await importIndexedDir_1.default(cloneFile, to, opts.filesMap);
    }
}
async function cloneFile(from, to) {
    await fs.copyFile(from, to, fs.constants.COPYFILE_FICLONE_FORCE);
}
async function hardlinkPkg(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)) {
        core_loggers_1.importingLogger.debug({ to, method: 'hardlink' });
        await importIndexedDir_1.default(linkOrCopy, to, opts.filesMap);
    }
}
async function linkOrCopy(existingPath, newPath) {
    try {
        await fs.link(existingPath, newPath);
    }
    catch (err) {
        // 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)) {
        core_loggers_1.importingLogger.debug({ to, method: 'copy' });
        await importIndexedDir_1.default(fs.copyFile, to, opts.filesMap);
    }
}
exports.copyPkg = copyPkg;
//# sourceMappingURL=createImportPackage.js.map