"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CreateLinksAction = void 0;
const node_path_1 = __importDefault(require("node:path"));
const node_core_library_1 = require("@rushstack/node-core-library");
const ts_command_line_1 = require("@rushstack/ts-command-line");
const Utils_1 = require("../../../../Utils");
const CreateLinksUtilities_1 = require("../../utilities/CreateLinksUtilities");
const constants_1 = require("../../utilities/constants");
const RemoveLinksAction_1 = require("./RemoveLinksAction");
async function createLinksAsync(terminal, targetRootFolder, extractorMetadataObject) {
    await node_core_library_1.Async.forEachAsync(extractorMetadataObject.links, async (linkInfo) => {
        // Link to the relative path for symlinks
        const newLinkPath = node_path_1.default.join(targetRootFolder, linkInfo.linkPath);
        const linkTargetPath = node_path_1.default.join(targetRootFolder, linkInfo.targetPath);
        // Make sure the containing folder exists
        await node_core_library_1.FileSystem.ensureFolderAsync(node_path_1.default.dirname(newLinkPath));
        // NOTE: This logic is based on NpmLinkManager._createSymlink()
        if (linkInfo.kind === 'folderLink') {
            terminal.writeVerboseLine(`Creating linked folder at path "${newLinkPath}"`);
            await node_core_library_1.FileSystem.createSymbolicLinkJunctionAsync({ newLinkPath, linkTargetPath });
        }
        else if (linkInfo.kind === 'fileLink') {
            // Use hardlinks for Windows and symlinks for other platforms since creating a symbolic link
            // requires administrator permission on Windows. This may cause unexpected behaviour for consumers
            // of the hardlinked files. If this becomes an issue, we may need to revisit this.
            terminal.writeVerboseLine(`Creating linked file at path "${newLinkPath}"`);
            if (process.platform === 'win32') {
                await node_core_library_1.FileSystem.createHardLinkAsync({ newLinkPath, linkTargetPath });
            }
            else {
                await node_core_library_1.FileSystem.createSymbolicLinkFileAsync({ newLinkPath, linkTargetPath });
            }
        }
    }, { concurrency: constants_1.MAX_CONCURRENCY });
}
async function realizeFilesAsync(terminal, targetRootFolder, extractorMetadataObject) {
    await node_core_library_1.Async.forEachAsync(extractorMetadataObject.files, async (relativeFilePath) => {
        const filePath = `${targetRootFolder}/${relativeFilePath}`;
        const realFilePath = await node_core_library_1.FileSystem.getRealPathAsync(filePath);
        if (!node_core_library_1.Path.isEqual(realFilePath, filePath)) {
            // Delete the existing symlink and create a hardlink to the real file, since creating hardlinks
            // is less overhead than copying the file.
            terminal.writeVerboseLine(`Realizing file at path "${filePath}"`);
            await node_core_library_1.FileSystem.deleteFileAsync(filePath);
            await node_core_library_1.FileSystem.createHardLinkAsync({ newLinkPath: filePath, linkTargetPath: realFilePath });
        }
    }, { concurrency: constants_1.MAX_CONCURRENCY });
}
class CreateLinksAction extends ts_command_line_1.CommandLineAction {
    constructor(terminal) {
        super({
            actionName: 'create',
            summary: 'Create symlinks for extraction',
            documentation: 'This action creates symlinks for the extraction process.'
        });
        this._terminal = terminal;
        this._realizeFilesParameter = this.defineFlagParameter({
            parameterLongName: constants_1.REALIZE_FILES_PARAMETER_NAME,
            description: 'Realize files instead of creating symlinks'
        });
        this._linkBinsParameter = this.defineFlagParameter({
            parameterLongName: constants_1.LINK_BINS_PARAMETER_NAME,
            description: 'Create the .bin files for extracted packages'
        });
    }
    async onExecuteAsync() {
        const extractorMetadataObject = await (0, CreateLinksUtilities_1.getExtractorMetadataAsync)();
        const realizeFiles = this._realizeFilesParameter.value;
        const linkBins = this._linkBinsParameter.value;
        this._terminal.writeLine(`Creating links for extraction at path "${constants_1.TARGET_ROOT_FOLDER}"`);
        await (0, RemoveLinksAction_1.removeLinksAsync)(this._terminal, constants_1.TARGET_ROOT_FOLDER, extractorMetadataObject);
        await createLinksAsync(this._terminal, constants_1.TARGET_ROOT_FOLDER, extractorMetadataObject);
        if (realizeFiles) {
            this._terminal.writeLine(`Realizing files for extraction at path "${constants_1.TARGET_ROOT_FOLDER}"`);
            await realizeFilesAsync(this._terminal, constants_1.TARGET_ROOT_FOLDER, extractorMetadataObject);
        }
        if (linkBins) {
            this._terminal.writeLine(`Linking bins for extraction at path "${constants_1.TARGET_ROOT_FOLDER}"`);
            const extractedProjectFolderPaths = extractorMetadataObject.projects.map((project) => node_path_1.default.join(constants_1.TARGET_ROOT_FOLDER, project.path));
            await (0, Utils_1.makeBinLinksAsync)(this._terminal, extractedProjectFolderPaths);
        }
    }
}
exports.CreateLinksAction = CreateLinksAction;
//# sourceMappingURL=CreateLinksAction.js.map