// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import * as path from 'node:path';
import { Minimatch } from 'minimatch';
import semver from 'semver';
import npmPacklist from 'npm-packlist';
import ignore from 'ignore';
import { Async, AsyncQueue, Path, FileSystem, Import, JsonFile } from '@rushstack/node-core-library';
import { Colorize } from '@rushstack/terminal';
import { SymlinkAnalyzer } from './SymlinkAnalyzer';
import { AssetHandler } from './AssetHandler';
import { matchesWithStar, remapSourcePathForTargetFolder, remapPathForExtractorMetadata, makeBinLinksAsync } from './Utils';
import { CREATE_LINKS_SCRIPT_FILENAME, EXTRACTOR_METADATA_FILENAME, SCRIPTS_FOLDER_PATH } from './PathConstants';
import { MAX_CONCURRENCY } from './scripts/createLinks/utilities/constants';
export const TARGET_ROOT_SCRIPT_RELATIVE_PATH_TEMPLATE_STRING = '{TARGET_ROOT_SCRIPT_RELATIVE_PATH}';
/**
 * Manages the business logic for the "rush deploy" command.
 *
 * @public
 */
export class PackageExtractor {
    /**
     * Get a list of files that would be included in a package created from the provided package root path.
     *
     * @beta
     */
    static async getPackageIncludedFilesAsync(packageRootPath) {
        // Use npm-packlist to filter the files.  Using the Walker class (instead of the default API) ensures
        // that "bundledDependencies" are not included.
        const walkerPromise = new Promise((resolve, reject) => {
            const walker = new npmPacklist.Walker({
                path: packageRootPath
            });
            walker.on('done', resolve).on('error', reject).start();
        });
        const npmPackFiles = await walkerPromise;
        // npm-packlist@v5 (uses glob@v8) returns "./dist/index.js" for pattern: "./dist/index.js",
        // whereas npm-packlist@v2 (uses glob@v7) returns an empty array.
        // This may cause duplicate files in the result list, leading to copying conflicts
        // in the AssetHandler.ts includeAssetAsync method.
        //
        // Temporary fix: normalize the file paths to be relative to the package root, and remove duplicates.
        //
        // TODO: A better long-term fix is to replace "npm-packlist" with "@pnpm/fs.packlist", notes here:
        //       https://github.com/microsoft/rushstack/pull/5720/changes#r2984873283
        const normalizedFiles = Array.from(new Set(npmPackFiles.map((file) => path.posix.normalize(file))));
        return normalizedFiles;
    }
    /**
     * Extract a package using the provided options
     */
    async extractAsync(options) {
        options = PackageExtractor._normalizeOptions(options);
        const { terminal, projectConfigurations, sourceRootFolder, targetRootFolder, mainProjectName, overwriteExisting, dependencyConfigurations, linkCreation } = options;
        terminal.writeLine(Colorize.cyan(`Extracting to target folder:  ${targetRootFolder}`));
        terminal.writeLine(Colorize.cyan(`Main project for extraction: ${mainProjectName}`));
        await FileSystem.ensureFolderAsync(targetRootFolder);
        const existingExtraction = (await FileSystem.readFolderItemNamesAsync(targetRootFolder)).length > 0;
        if (existingExtraction) {
            if (!overwriteExisting) {
                throw new Error('The extraction target folder is not empty. Overwrite must be explicitly requested');
            }
            terminal.writeLine('Deleting target folder contents...');
            terminal.writeLine('');
            await FileSystem.ensureEmptyFolderAsync(targetRootFolder);
        }
        // Create a new state for each run
        const symlinkAnalyzer = new SymlinkAnalyzer({
            requiredSourceParentPath: sourceRootFolder
        });
        const state = {
            symlinkAnalyzer,
            assetHandler: new AssetHandler({ ...options, symlinkAnalyzer }),
            foldersToCopy: new Set(),
            packageJsonByPath: new Map(),
            projectConfigurationsByName: new Map(projectConfigurations.map((p) => [p.projectName, p])),
            projectConfigurationsByPath: new Map(projectConfigurations.map((p) => [p.projectFolder, p])),
            dependencyConfigurationsByName: new Map()
        };
        // set state dependencyConfigurationsByName
        for (const dependencyConfiguration of dependencyConfigurations || []) {
            const { dependencyName } = dependencyConfiguration;
            let existingDependencyConfigurations = state.dependencyConfigurationsByName.get(dependencyName);
            if (!existingDependencyConfigurations) {
                existingDependencyConfigurations = [];
                state.dependencyConfigurationsByName.set(dependencyName, existingDependencyConfigurations);
            }
            existingDependencyConfigurations.push(dependencyConfiguration);
        }
        await this._performExtractionAsync(options, state);
        await state.assetHandler.finalizeAsync({
            onAfterExtractSymlinksAsync: async () => {
                // We need the symlinks to be created before attempting to create the bin links, since it requires
                // the node_modules folder to be realized. While we're here, we may as well perform some specific
                // link creation tasks and write the extractor-metadata.json file before the asset handler finalizes.
                if (linkCreation === 'default') {
                    await this._makeBinLinksAsync(options, state);
                }
                else if (linkCreation === 'script') {
                    await this._writeCreateLinksScriptAsync(options, state);
                }
                terminal.writeLine('Creating extractor-metadata.json');
                await this._writeExtractorMetadataAsync(options, state);
            }
        });
    }
    static _normalizeOptions(options) {
        if (options.subspaces) {
            if (options.pnpmInstallFolder !== undefined) {
                throw new Error('IExtractorOptions.pnpmInstallFolder cannot be combined with IExtractorOptions.subspaces');
            }
            if (options.transformPackageJson !== undefined) {
                throw new Error('IExtractorOptions.transformPackageJson cannot be combined with IExtractorOptions.subspaces');
            }
            return options;
        }
        const normalizedOptions = { ...options };
        delete normalizedOptions.pnpmInstallFolder;
        delete normalizedOptions.transformPackageJson;
        normalizedOptions.subspaces = [
            {
                subspaceName: 'default',
                pnpmInstallFolder: options.pnpmInstallFolder,
                transformPackageJson: options.transformPackageJson
            }
        ];
        return normalizedOptions;
    }
    async _performExtractionAsync(options, state) {
        const { terminal, mainProjectName, sourceRootFolder, targetRootFolder, folderToCopy: additionalFolderToCopy, createArchiveOnly } = options;
        const { projectConfigurationsByName, foldersToCopy } = state;
        const mainProjectConfiguration = projectConfigurationsByName.get(mainProjectName);
        if (!mainProjectConfiguration) {
            throw new Error(`Main project "${mainProjectName}" was not found in the list of projects`);
        }
        // Calculate the set with additionalProjectsToInclude
        const includedProjectsSet = new Set([mainProjectConfiguration]);
        for (const { additionalProjectsToInclude } of includedProjectsSet) {
            if (additionalProjectsToInclude) {
                for (const additionalProjectNameToInclude of additionalProjectsToInclude) {
                    const additionalProjectToInclude = projectConfigurationsByName.get(additionalProjectNameToInclude);
                    if (!additionalProjectToInclude) {
                        throw new Error(`Project "${additionalProjectNameToInclude}" was not found in the list of projects.`);
                    }
                    includedProjectsSet.add(additionalProjectToInclude);
                }
            }
        }
        for (const { projectName, projectFolder } of includedProjectsSet) {
            terminal.writeLine(Colorize.cyan(`Analyzing project: ${projectName}`));
            await this._collectFoldersAsync(projectFolder, options, state);
        }
        if (!createArchiveOnly) {
            terminal.writeLine(`Copying folders to target folder "${targetRootFolder}"`);
        }
        await Async.forEachAsync(foldersToCopy, async (folderToCopy) => {
            await this._extractFolderAsync(folderToCopy, options, state);
        }, {
            concurrency: MAX_CONCURRENCY
        });
        if (additionalFolderToCopy) {
            // Copy the additional folder directly into the root of the target folder by setting the sourceRootFolder
            // to the root of the folderToCopy
            const additionalFolderPath = path.resolve(sourceRootFolder, additionalFolderToCopy);
            const additionalFolderExtractorOptions = {
                ...options,
                sourceRootFolder: additionalFolderPath,
                targetRootFolder
            };
            await this._extractFolderAsync(additionalFolderPath, additionalFolderExtractorOptions, state);
        }
    }
    /**
     * Recursively crawl the node_modules dependencies and collect the result in IExtractorState.foldersToCopy.
     */
    async _collectFoldersAsync(packageJsonFolder, options, state) {
        const { terminal, subspaces } = options;
        const { projectConfigurationsByPath } = state;
        const packageJsonFolderPathQueue = new AsyncQueue([packageJsonFolder]);
        await Async.forEachAsync(packageJsonFolderPathQueue, async ([packageJsonFolderPath, callback]) => {
            var _a, _b;
            const packageJsonRealFolderPath = await FileSystem.getRealPathAsync(packageJsonFolderPath);
            if (state.foldersToCopy.has(packageJsonRealFolderPath)) {
                // we've already seen this folder
                callback();
                return;
            }
            state.foldersToCopy.add(packageJsonRealFolderPath);
            const originalPackageJson = await JsonFile.loadAsync(path.join(packageJsonRealFolderPath, 'package.json'));
            const targetSubspace = subspaces === null || subspaces === void 0 ? void 0 : subspaces.find((subspace) => subspace.pnpmInstallFolder && Path.isUnder(packageJsonFolderPath, subspace.pnpmInstallFolder));
            // Transform packageJson using the provided transformer, if requested
            const packageJson = (_b = (_a = targetSubspace === null || targetSubspace === void 0 ? void 0 : targetSubspace.transformPackageJson) === null || _a === void 0 ? void 0 : _a.call(targetSubspace, originalPackageJson)) !== null && _b !== void 0 ? _b : originalPackageJson;
            state.packageJsonByPath.set(packageJsonRealFolderPath, packageJson);
            // Union of keys from regular dependencies, peerDependencies, optionalDependencies
            // (and possibly devDependencies if includeDevDependencies=true)
            const dependencyNamesToProcess = new Set();
            // Just the keys from optionalDependencies and peerDependencies
            const optionalDependencyNames = new Set();
            for (const name of Object.keys(packageJson.dependencies || {})) {
                dependencyNamesToProcess.add(name);
            }
            for (const name of Object.keys(packageJson.peerDependencies || {})) {
                dependencyNamesToProcess.add(name);
                optionalDependencyNames.add(name); // consider peers optional, since they are so frequently broken
            }
            for (const name of Object.keys(packageJson.optionalDependencies || {})) {
                dependencyNamesToProcess.add(name);
                optionalDependencyNames.add(name);
            }
            // Check to see if this is a local project
            const projectConfiguration = projectConfigurationsByPath.get(packageJsonRealFolderPath);
            if (projectConfiguration) {
                if (options.includeDevDependencies) {
                    for (const name of Object.keys(packageJson.devDependencies || {})) {
                        dependencyNamesToProcess.add(name);
                    }
                }
                this._applyDependencyFilters(terminal, dependencyNamesToProcess, projectConfiguration.additionalDependenciesToInclude, projectConfiguration.dependenciesToExclude);
            }
            for (const dependencyPackageName of dependencyNamesToProcess) {
                try {
                    const dependencyPackageFolderPath = await Import.resolvePackageAsync({
                        packageName: dependencyPackageName,
                        baseFolderPath: packageJsonRealFolderPath,
                        getRealPathAsync: async (filePath) => {
                            try {
                                return (await state.symlinkAnalyzer.analyzePathAsync({ inputPath: filePath })).nodePath;
                            }
                            catch (error) {
                                if (FileSystem.isFileDoesNotExistError(error)) {
                                    return filePath;
                                }
                                throw error;
                            }
                        }
                    });
                    packageJsonFolderPathQueue.push(dependencyPackageFolderPath);
                }
                catch (resolveErr) {
                    if (optionalDependencyNames.has(dependencyPackageName)) {
                        // Ignore missing optional dependency
                        continue;
                    }
                    throw resolveErr;
                }
            }
            // Replicate the links to the virtual store. Note that if the package has not been hoisted by
            // PNPM, the package will not be resolvable from here.
            // Only apply this logic for packages that were actually installed under the common/temp folder.
            const realPnpmInstallFolder = targetSubspace === null || targetSubspace === void 0 ? void 0 : targetSubspace.pnpmInstallFolder;
            if (realPnpmInstallFolder && Path.isUnder(packageJsonFolderPath, realPnpmInstallFolder)) {
                try {
                    // The PNPM virtual store links are created in this folder.  We will resolve the current package
                    // from that location and collect any additional links encountered along the way.
                    // TODO: This can be configured via NPMRC. We should support that.
                    const pnpmDotFolderPath = path.join(realPnpmInstallFolder, 'node_modules', '.pnpm');
                    // TODO: Investigate how package aliases are handled by PNPM in this case.  For example:
                    //
                    // "dependencies": {
                    //   "alias-name": "npm:real-name@^1.2.3"
                    // }
                    const dependencyPackageFolderPath = await Import.resolvePackageAsync({
                        packageName: packageJson.name,
                        baseFolderPath: pnpmDotFolderPath,
                        getRealPathAsync: async (filePath) => {
                            try {
                                return (await state.symlinkAnalyzer.analyzePathAsync({ inputPath: filePath })).nodePath;
                            }
                            catch (error) {
                                if (FileSystem.isFileDoesNotExistError(error)) {
                                    return filePath;
                                }
                                throw error;
                            }
                        }
                    });
                    packageJsonFolderPathQueue.push(dependencyPackageFolderPath);
                }
                catch (resolveErr) {
                    // The virtual store link isn't guaranteed to exist, so ignore if it's missing
                    // NOTE: If you encounter this warning a lot, please report it to the Rush maintainers.
                    // eslint-disable-next-line no-console
                    console.log('Ignoring missing PNPM virtual store link for ' + packageJsonFolderPath);
                }
            }
            callback();
        }, {
            concurrency: MAX_CONCURRENCY
        });
    }
    _applyDependencyFilters(terminal, allDependencyNames, additionalDependenciesToInclude = [], dependenciesToExclude = []) {
        // Track packages that got added/removed for reporting purposes
        const extraIncludedPackageNames = [];
        const extraExcludedPackageNames = [];
        for (const patternWithStar of dependenciesToExclude) {
            for (const dependency of allDependencyNames) {
                if (matchesWithStar(patternWithStar, dependency)) {
                    if (allDependencyNames.delete(dependency)) {
                        extraExcludedPackageNames.push(dependency);
                    }
                }
            }
        }
        for (const dependencyToInclude of additionalDependenciesToInclude) {
            if (!allDependencyNames.has(dependencyToInclude)) {
                allDependencyNames.add(dependencyToInclude);
                extraIncludedPackageNames.push(dependencyToInclude);
            }
        }
        if (extraIncludedPackageNames.length > 0) {
            extraIncludedPackageNames.sort();
            terminal.writeLine(`Extra dependencies included by settings: ${extraIncludedPackageNames.join(', ')}`);
        }
        if (extraExcludedPackageNames.length > 0) {
            extraExcludedPackageNames.sort();
            terminal.writeLine(`Extra dependencies excluded by settings: ${extraExcludedPackageNames.join(', ')}`);
        }
        return allDependencyNames;
    }
    /**
     * Copy one package folder to the extractor target folder.
     */
    async _extractFolderAsync(sourceFolderPath, options, state) {
        const { includeNpmIgnoreFiles } = options;
        const { projectConfigurationsByPath, packageJsonByPath, dependencyConfigurationsByName, assetHandler } = state;
        let useNpmIgnoreFilter = false;
        const sourceFolderRealPath = await FileSystem.getRealPathAsync(sourceFolderPath);
        const sourceProjectConfiguration = projectConfigurationsByPath.get(sourceFolderRealPath);
        const packagesJson = packageJsonByPath.get(sourceFolderRealPath);
        // As this function will be used to copy folder for both project inside monorepo and third party
        // dependencies insides node_modules. Third party dependencies won't have project configurations
        const isLocalProject = !!sourceProjectConfiguration;
        // Function to filter files inside local project or third party dependencies.
        const isFileExcluded = (filePath) => {
            // Encapsulate exclude logic into a function, so it can be reused.
            const excludeFileByPatterns = (patternsToInclude, patternsToExclude) => {
                let includeFilters;
                let excludeFilters;
                if (patternsToInclude === null || patternsToInclude === void 0 ? void 0 : patternsToInclude.length) {
                    includeFilters = patternsToInclude === null || patternsToInclude === void 0 ? void 0 : patternsToInclude.map((p) => new Minimatch(p, { dot: true }));
                }
                if (patternsToExclude === null || patternsToExclude === void 0 ? void 0 : patternsToExclude.length) {
                    excludeFilters = patternsToExclude === null || patternsToExclude === void 0 ? void 0 : patternsToExclude.map((p) => new Minimatch(p, { dot: true }));
                }
                // If there are no filters, then we can't exclude anything.
                if (!includeFilters && !excludeFilters) {
                    return false;
                }
                const isIncluded = !includeFilters || includeFilters.some((m) => m.match(filePath));
                // If the file is not included, then we don't need to check the excludeFilter. If it is included
                // and there is no exclude filter, then we know that the file is not excluded. If it is included
                // and there is an exclude filter, then we need to check for a match.
                return !isIncluded || !!(excludeFilters === null || excludeFilters === void 0 ? void 0 : excludeFilters.some((m) => m.match(filePath)));
            };
            if (isLocalProject) {
                return excludeFileByPatterns(sourceProjectConfiguration === null || sourceProjectConfiguration === void 0 ? void 0 : sourceProjectConfiguration.patternsToInclude, sourceProjectConfiguration === null || sourceProjectConfiguration === void 0 ? void 0 : sourceProjectConfiguration.patternsToExclude);
            }
            else {
                if (!packagesJson) {
                    return false;
                }
                const dependenciesConfigurations = dependencyConfigurationsByName.get(packagesJson.name);
                if (!dependenciesConfigurations) {
                    return false;
                }
                const matchedDependenciesConfigurations = dependenciesConfigurations.filter((d) => semver.satisfies(packagesJson.version, d.dependencyVersionRange));
                return matchedDependenciesConfigurations.some((d) => excludeFileByPatterns(d.patternsToInclude, d.patternsToExclude));
            }
        };
        if (sourceProjectConfiguration && !includeNpmIgnoreFiles) {
            // Only use the npmignore filter if the project configuration explicitly asks for it
            useNpmIgnoreFilter = true;
        }
        const targetFolderPath = remapSourcePathForTargetFolder({
            ...options,
            sourcePath: sourceFolderPath
        });
        if (useNpmIgnoreFilter) {
            const npmPackFiles = await PackageExtractor.getPackageIncludedFilesAsync(sourceFolderPath);
            await Async.forEachAsync(npmPackFiles, async (npmPackFile) => {
                // In issue https://github.com/microsoft/rushstack/issues/2121 we found that npm-packlist sometimes returns
                // duplicate file paths, for example:
                //
                //   'dist//index.js'
                //   'dist/index.js'
                //
                // Filter out files that are excluded by the project configuration or dependency configuration.
                if (isFileExcluded(npmPackFile)) {
                    return;
                }
                const sourceFilePath = path.resolve(sourceFolderPath, npmPackFile);
                const { kind, linkStats: sourceFileStats } = await state.symlinkAnalyzer.analyzePathAsync({
                    inputPath: sourceFilePath
                });
                if (kind === 'file') {
                    const targetFilePath = path.resolve(targetFolderPath, npmPackFile);
                    await assetHandler.includeAssetAsync({
                        sourceFilePath,
                        sourceFileStats,
                        targetFilePath
                    });
                }
            }, {
                concurrency: MAX_CONCURRENCY
            });
        }
        else {
            // use a simplistic "ignore" ruleset to filter the files
            const ignoreFilter = ignore();
            ignoreFilter.add([
                // The top-level node_modules folder is always excluded
                '/node_modules',
                // Also exclude well-known folders that can contribute a lot of unnecessary files
                '**/.git',
                '**/.svn',
                '**/.hg',
                '**/.DS_Store'
            ]);
            // Do a breadth-first search of the source folder, copying each file to the target folder
            const queue = new AsyncQueue([sourceFolderPath]);
            await Async.forEachAsync(queue, async ([sourcePath, callback]) => {
                const relativeSourcePath = path.relative(sourceFolderPath, sourcePath);
                if (relativeSourcePath !== '' && ignoreFilter.ignores(relativeSourcePath)) {
                    callback();
                    return;
                }
                const sourcePathNode = await state.symlinkAnalyzer.analyzePathAsync({
                    inputPath: sourcePath,
                    // Treat all links to external paths as if they are files for this scenario. In the future, we may
                    // want to explore the target of the external link to see if all files within the target are
                    // excluded, and throw if they are not.
                    shouldIgnoreExternalLink: (linkSourcePath) => {
                        // Ignore the provided linkSourcePath since it may not be the first link in the chain. Instead,
                        // we will consider only the relativeSourcePath, since that would be our entrypoint into the
                        // link chain.
                        return isFileExcluded(relativeSourcePath);
                    }
                });
                if (sourcePathNode === undefined) {
                    // The target was a symlink that is excluded. We don't need to do anything.
                    callback();
                    return;
                }
                else if (sourcePathNode.kind === 'file') {
                    // Only ignore files and not folders to ensure that we traverse the contents of all folders. This is
                    // done so that we can match against subfolder patterns, ex. "src/subfolder/**/*"
                    if (relativeSourcePath !== '' && isFileExcluded(relativeSourcePath)) {
                        callback();
                        return;
                    }
                    const targetFilePath = path.resolve(targetFolderPath, relativeSourcePath);
                    await assetHandler.includeAssetAsync({
                        sourceFilePath: sourcePath,
                        sourceFileStats: sourcePathNode.linkStats,
                        targetFilePath
                    });
                }
                else if (sourcePathNode.kind === 'folder') {
                    const children = await FileSystem.readFolderItemNamesAsync(sourcePath);
                    for (const child of children) {
                        queue.push(path.join(sourcePath, child));
                    }
                }
                callback();
            }, {
                concurrency: MAX_CONCURRENCY
            });
        }
    }
    /**
     * Write the common/deploy/deploy-metadata.json file.
     */
    async _writeExtractorMetadataAsync(options, state) {
        const { mainProjectName, sourceRootFolder, targetRootFolder, linkCreation, linkCreationScriptPath } = options;
        const { projectConfigurationsByPath } = state;
        const extractorMetadataFolderPath = linkCreation === 'script' && linkCreationScriptPath
            ? path.dirname(path.resolve(targetRootFolder, linkCreationScriptPath))
            : targetRootFolder;
        const extractorMetadataFilePath = path.join(extractorMetadataFolderPath, EXTRACTOR_METADATA_FILENAME);
        const extractorMetadataJson = {
            mainProjectName,
            projects: [],
            links: [],
            files: []
        };
        for (const { projectFolder, projectName } of projectConfigurationsByPath.values()) {
            if (state.foldersToCopy.has(projectFolder)) {
                extractorMetadataJson.projects.push({
                    projectName,
                    path: remapPathForExtractorMetadata(sourceRootFolder, projectFolder)
                });
            }
        }
        // Remap the links to be relative to target folder
        for (const { kind, linkPath, targetPath } of state.symlinkAnalyzer.reportSymlinks()) {
            extractorMetadataJson.links.push({
                kind,
                linkPath: remapPathForExtractorMetadata(sourceRootFolder, linkPath),
                targetPath: remapPathForExtractorMetadata(sourceRootFolder, targetPath)
            });
        }
        for (const assetPath of state.assetHandler.assetPaths) {
            extractorMetadataJson.files.push(remapPathForExtractorMetadata(targetRootFolder, assetPath));
        }
        const extractorMetadataFileContent = JSON.stringify(extractorMetadataJson, undefined, 0);
        await state.assetHandler.includeAssetAsync({
            sourceFileContent: extractorMetadataFileContent,
            targetFilePath: extractorMetadataFilePath
        });
    }
    async _makeBinLinksAsync(options, state) {
        const { terminal } = options;
        const extractedProjectFolderPaths = [];
        for (const folderPath of state.projectConfigurationsByPath.keys()) {
            if (state.foldersToCopy.has(folderPath)) {
                extractedProjectFolderPaths.push(remapSourcePathForTargetFolder({ ...options, sourcePath: folderPath }));
            }
        }
        const binFilePaths = await makeBinLinksAsync(terminal, extractedProjectFolderPaths);
        await Async.forEachAsync(binFilePaths, (targetFilePath) => state.assetHandler.includeAssetAsync({ targetFilePath }), {
            concurrency: MAX_CONCURRENCY
        });
    }
    async _writeCreateLinksScriptAsync(options, state) {
        const { terminal, targetRootFolder, linkCreationScriptPath } = options;
        const { assetHandler } = state;
        terminal.writeLine(`Creating ${CREATE_LINKS_SCRIPT_FILENAME}`);
        const createLinksSourceFilePath = `${SCRIPTS_FOLDER_PATH}/${CREATE_LINKS_SCRIPT_FILENAME}`;
        const createLinksTargetFilePath = path.resolve(targetRootFolder, linkCreationScriptPath || CREATE_LINKS_SCRIPT_FILENAME);
        let createLinksScriptContent = await FileSystem.readFileAsync(createLinksSourceFilePath);
        createLinksScriptContent = createLinksScriptContent.replace(TARGET_ROOT_SCRIPT_RELATIVE_PATH_TEMPLATE_STRING, Path.convertToSlashes(path.relative(path.dirname(createLinksTargetFilePath), targetRootFolder)));
        await assetHandler.includeAssetAsync({
            sourceFileContent: createLinksScriptContent,
            targetFilePath: createLinksTargetFilePath
        });
    }
}
//# sourceMappingURL=PackageExtractor.js.map