"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    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) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.CocoaPodsService = void 0;
const os_1 = require("os");
const path = require("path");
const _ = require("lodash");
const constants_1 = require("../constants");
const helpers_1 = require("../common/helpers");
const yok_1 = require("../common/yok");
const constants = require("../constants");
class CocoaPodsService {
    constructor($cocoaPodsPlatformManager, $fs, $childProcess, $errors, $logger, $config, $xcconfigService, $xcodeSelectService) {
        this.$cocoaPodsPlatformManager = $cocoaPodsPlatformManager;
        this.$fs = $fs;
        this.$childProcess = $childProcess;
        this.$errors = $errors;
        this.$logger = $logger;
        this.$config = $config;
        this.$xcconfigService = $xcconfigService;
        this.$xcodeSelectService = $xcodeSelectService;
        this.getCocoaPodsFromPodfile = _.memoize(this._getCocoaPodsFromPodfile, helpers_1.getHash);
    }
    getPodfileHeader(targetName) {
        return `use_frameworks!${os_1.EOL}${os_1.EOL}target "${targetName}" do${os_1.EOL}`;
    }
    getPodfileFooter() {
        return `${os_1.EOL}end`;
    }
    getProjectPodfilePath(projectRoot) {
        return path.join(projectRoot, constants_1.PODFILE_NAME);
    }
    executePodInstall(projectRoot, xcodeProjPath) {
        return __awaiter(this, void 0, void 0, function* () {
            this.$logger.info("Installing pods...");
            let podTool = this.$config.USE_POD_SANDBOX ? "sandbox-pod" : "pod";
            const args = ["install"];
            if (process.platform === "darwin" && process.arch === "arm64") {
                // check if pod is installed as an x86_64 binary or a native arm64 one
                // we run the following:
                // arch -x86_64 pod --version
                // if it's an arm64 binary, we'll get something like this as a result:
                // arch: posix_spawnp: pod: Bad CPU type in executable
                // in which case, we should run it natively.
                const res = yield this.$childProcess
                    .exec("arch -x86_64 pod --version", null, {
                    showStderr: true,
                })
                    .then((res) => res.stdout + " " + res.stderr)
                    .catch((err) => err.message);
                if (!res.includes("Bad CPU type in executable")) {
                    this.$logger.trace("Running on arm64 but pod is installed under rosetta2 - running pod through rosetta2");
                    args.unshift(podTool);
                    args.unshift("-x86_64");
                    podTool = "arch";
                }
            }
            // cocoapods print a lot of non-error information on stderr. Pipe the `stderr` to `stdout`, so we won't polute CLI's stderr output.
            const podInstallResult = yield this.$childProcess.spawnFromEvent(podTool, args, "close", { cwd: projectRoot, stdio: ["pipe", process.stdout, process.stdout] }, { throwError: false });
            if (podInstallResult.exitCode !== 0) {
                // https://github.com/CocoaPods/CocoaPods/blob/92aaf0f1120d32f3487960b485fb69fcaf61486c/lib/cocoapods/resolver.rb#L498
                // TODO add article
                const versionResolutionHint = podInstallResult.exitCode === 31
                    ? `For more information on resolving CocoaPod issues in NativeScript read.`
                    : "";
                this.$errors.fail(`'${podTool} install' command failed.${podInstallResult.stderr ? " Error is: " + podInstallResult.stderr : ""}
${versionResolutionHint}`);
            }
            return podInstallResult;
        });
    }
    mergePodXcconfigFile(projectData, platformData) {
        return __awaiter(this, void 0, void 0, function* () {
            const podFilesRootDirName = path.join("Pods", "Target Support Files", `Pods-${projectData.projectName}`);
            const podFolder = path.join(platformData.projectRoot, podFilesRootDirName);
            if (this.$fs.exists(podFolder)) {
                const pluginsXcconfigFilePaths = this.$xcconfigService.getPluginsXcconfigFilePaths(platformData.projectRoot);
                for (const configuration in pluginsXcconfigFilePaths) {
                    const pluginsXcconfigFilePath = pluginsXcconfigFilePaths[configuration];
                    const podXcconfigFilePath = path.join(podFolder, `Pods-${projectData.projectName}.${configuration}.xcconfig`);
                    yield this.$xcconfigService.mergeFiles(podXcconfigFilePath, pluginsXcconfigFilePath);
                }
            }
        });
    }
    applyPodfileFromAppResources(projectData, platformData) {
        return __awaiter(this, void 0, void 0, function* () {
            const { projectRoot, normalizedPlatformName } = platformData;
            const mainPodfilePath = path.join(projectData.appResourcesDirectoryPath, normalizedPlatformName, constants_1.PODFILE_NAME);
            const projectPodfilePath = this.getProjectPodfilePath(projectRoot);
            if (this.$fs.exists(projectPodfilePath) ||
                this.$fs.exists(mainPodfilePath)) {
                yield this.applyPodfileToProject(constants_1.NS_BASE_PODFILE, mainPodfilePath, projectData, platformData);
            }
        });
    }
    applyPodfileArchExclusions(projectData, platformData) {
        return __awaiter(this, void 0, void 0, function* () {
            const xcodeVersionData = yield this.$xcodeSelectService.getXcodeVersion();
            // only apply EXCLUDED_ARCHS workaround on XCode 12
            if (+xcodeVersionData.major !== 12) {
                return;
            }
            const { projectRoot } = platformData;
            const exclusionsPodfile = path.join(projectRoot, "Podfile-exclusions");
            if (!this.$fs.exists(exclusionsPodfile)) {
                const exclusions = `
post_install do |installer|
  installer.pods_project.build_configurations.each do |config|
    config.build_settings.delete "VALID_ARCHS"
    config.build_settings["EXCLUDED_ARCHS_x86_64"] = "arm64 arm64e"
    config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "i386 armv6 armv7 armv7s armv8 $(EXCLUDED_ARCHS_$(NATIVE_ARCH_64_BIT))"
    config.build_settings["EXCLUDED_ARCHS[sdk=iphoneos*]"] = "i386 armv6 armv7 armv7s armv8 x86_64"
  end
end`.trim();
                this.$fs.writeFile(exclusionsPodfile, exclusions);
            }
            yield this.applyPodfileToProject("NativeScript-CLI-Architecture-Exclusions", exclusionsPodfile, projectData, platformData);
            // clean up
            this.$fs.deleteFile(exclusionsPodfile);
        });
    }
    applyPodfileFromExtensions(projectData, platformData) {
        return __awaiter(this, void 0, void 0, function* () {
            const extensionFolderPath = path.join(projectData.getAppResourcesDirectoryPath(), constants.iOSAppResourcesFolderName, constants.NATIVE_EXTENSION_FOLDER);
            const projectPodfilePath = this.getProjectPodfilePath(platformData.projectRoot);
            if (!this.$fs.exists(extensionFolderPath) ||
                !this.$fs.exists(projectPodfilePath)) {
                return;
            }
            let projectPodFileContent = this.$fs.readText(projectPodfilePath);
            const extensionsPodfile = this.$fs
                .readDirectory(extensionFolderPath)
                .filter((name) => {
                const extensionPath = path.join(extensionFolderPath, name);
                const stats = this.$fs.getFsStats(extensionPath);
                return stats.isDirectory() && !name.startsWith(".");
            })
                .map((name) => ({
                targetName: name,
                podfilePath: path.join(extensionFolderPath, name, constants.PODFILE_NAME),
            }));
            extensionsPodfile.forEach(({ targetName, podfilePath }) => {
                // Remove the data between #Begin Podfile and #EndPodfile
                const regExpToRemove = new RegExp(`${this.getExtensionPodfileHeader(podfilePath, targetName)}[\\s\\S]*?${this.getExtensionPodfileEnd()}`, "mg");
                projectPodFileContent = projectPodFileContent.replace(regExpToRemove, "");
                if (this.$fs.exists(podfilePath)) {
                    const podfileContentWithoutTarget = this.$fs.readText(podfilePath);
                    const podFileContent = this.getExtensionPodfileHeader(podfilePath, targetName) +
                        os_1.EOL +
                        podfileContentWithoutTarget +
                        os_1.EOL +
                        this.getExtensionPodfileEnd();
                    projectPodFileContent += os_1.EOL + podFileContent;
                }
            });
            this.$fs.writeFile(projectPodfilePath, projectPodFileContent);
        });
    }
    applyPodfileToProject(moduleName, podfilePath, projectData, platformData) {
        return __awaiter(this, void 0, void 0, function* () {
            const nativeProjectPath = platformData.projectRoot;
            if (!this.$fs.exists(podfilePath)) {
                this.removePodfileFromProject(moduleName, podfilePath, projectData, nativeProjectPath);
                return;
            }
            const { podfileContent, replacedFunctions, podfilePlatformData } = this.buildPodfileContent(podfilePath, moduleName, projectData, platformData);
            const pathToProjectPodfile = this.getProjectPodfilePath(nativeProjectPath);
            const projectPodfileContent = this.$fs.exists(pathToProjectPodfile)
                ? this.$fs.readText(pathToProjectPodfile).trim()
                : "";
            if (projectPodfileContent.indexOf(podfileContent) === -1) {
                // Remove old occurences of the plugin from the project's Podfile.
                this.removePodfileFromProject(moduleName, podfilePath, projectData, nativeProjectPath);
                let finalPodfileContent = this.$fs.exists(pathToProjectPodfile)
                    ? this.getPodfileContentWithoutTarget(projectData, this.$fs.readText(pathToProjectPodfile))
                    : "";
                if (podfileContent.indexOf(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME) !== -1) {
                    finalPodfileContent = this.addPostInstallHook(replacedFunctions, finalPodfileContent, podfileContent);
                }
                if (podfilePlatformData) {
                    finalPodfileContent = this.$cocoaPodsPlatformManager.addPlatformSection(projectData, podfilePlatformData, finalPodfileContent);
                }
                finalPodfileContent = `${finalPodfileContent.trim()}${os_1.EOL}${os_1.EOL}${podfileContent.trim()}${os_1.EOL}`;
                this.saveProjectPodfile(projectData, finalPodfileContent, nativeProjectPath);
            }
        });
    }
    removePodfileFromProject(moduleName, podfilePath, projectData, projectRoot) {
        if (this.$fs.exists(this.getProjectPodfilePath(projectRoot))) {
            let projectPodFileContent = this.$fs.readText(this.getProjectPodfilePath(projectRoot));
            // Remove the data between #Begin Podfile and #EndPodfile
            const regExpToRemove = new RegExp(`${this.getPluginPodfileHeader(podfilePath)}[\\s\\S]*?${this.getPluginPodfileEnd()}`, "mg");
            projectPodFileContent = projectPodFileContent.replace(regExpToRemove, "");
            projectPodFileContent = this.removePostInstallHook(moduleName, projectPodFileContent);
            projectPodFileContent =
                this.$cocoaPodsPlatformManager.removePlatformSection(moduleName, projectPodFileContent, podfilePath);
            const defaultPodfileBeginning = this.getPodfileHeader(projectData.projectName);
            const defaultContentWithPostInstallHook = `${defaultPodfileBeginning}${this.getPostInstallHookHeader()}end${os_1.EOL}end`;
            const defaultContentWithoutPostInstallHook = `${defaultPodfileBeginning}${os_1.EOL}end`;
            const trimmedProjectPodFileContent = projectPodFileContent.trim();
            if (!trimmedProjectPodFileContent ||
                trimmedProjectPodFileContent === defaultContentWithPostInstallHook ||
                trimmedProjectPodFileContent === defaultContentWithoutPostInstallHook) {
                this.$fs.deleteFile(this.getProjectPodfilePath(projectRoot));
            }
            else {
                this.$fs.writeFile(this.getProjectPodfilePath(projectRoot), projectPodFileContent);
            }
        }
    }
    getPluginPodfilePath(pluginData) {
        const pluginPlatformsFolderPath = pluginData.pluginPlatformsFolderPath("ios" /* PlatformTypes.ios */);
        const pluginPodFilePath = path.join(pluginPlatformsFolderPath, constants_1.PODFILE_NAME);
        return pluginPodFilePath;
    }
    addPostInstallHook(replacedFunctions, finalPodfileContent, pluginPodfileContent) {
        const postInstallHookStart = this.getPostInstallHookHeader();
        let postInstallHookContent = "";
        _.each(replacedFunctions, (rubyFunction) => {
            let functionExecution = rubyFunction.functionName;
            if (rubyFunction.functionParameters &&
                rubyFunction.functionParameters.length) {
                functionExecution = `${functionExecution} ${CocoaPodsService.INSTALLER_BLOCK_PARAMETER_NAME}`;
            }
            postInstallHookContent += `  ${functionExecution}${os_1.EOL}`;
        });
        if (postInstallHookContent) {
            const index = finalPodfileContent.indexOf(postInstallHookStart);
            if (index !== -1) {
                const regExp = new RegExp(`(${(0, helpers_1.regExpEscape)(postInstallHookStart)}[\\s\\S]*?)(\\bend\\b)`, "m");
                finalPodfileContent = finalPodfileContent.replace(regExp, `$1${postInstallHookContent.trimRight()}${os_1.EOL}$2`);
            }
            else {
                if (finalPodfileContent.length > 0) {
                    finalPodfileContent += `${os_1.EOL}${os_1.EOL}`;
                }
                const postInstallHook = `${postInstallHookStart}${postInstallHookContent}end`;
                finalPodfileContent = `${finalPodfileContent}${postInstallHook}`;
            }
        }
        return finalPodfileContent;
    }
    getPodfileContentWithoutTarget(projectData, projectPodfileContent) {
        const podFileHeader = this.getPodfileHeader(projectData.projectName);
        if (_.startsWith(projectPodfileContent, podFileHeader)) {
            projectPodfileContent = projectPodfileContent.substr(podFileHeader.length);
            const podFileFooter = this.getPodfileFooter();
            // Only remove the final end in case the file starts with the podFileHeader
            if (_.endsWith(projectPodfileContent, podFileFooter)) {
                projectPodfileContent = projectPodfileContent.substr(0, projectPodfileContent.length - podFileFooter.length);
            }
        }
        return projectPodfileContent.trim();
    }
    saveProjectPodfile(projectData, projectPodfileContent, projectRoot) {
        projectPodfileContent = this.getPodfileContentWithoutTarget(projectData, projectPodfileContent);
        const podFileHeader = this.getPodfileHeader(projectData.projectName);
        const podFileFooter = this.getPodfileFooter();
        const contentToWrite = `${podFileHeader}${projectPodfileContent}${podFileFooter}`;
        const projectPodfilePath = this.getProjectPodfilePath(projectRoot);
        this.$fs.writeFile(projectPodfilePath, contentToWrite);
    }
    removePostInstallHook(moduleName, projectPodFileContent) {
        const regExp = new RegExp(`^.*?${this.getHookBasicFuncNameForPlugin(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME, moduleName)}.*?$\\r?\\n`, "gm");
        projectPodFileContent = projectPodFileContent.replace(regExp, "");
        return projectPodFileContent;
    }
    getHookBasicFuncNameForPlugin(hookName, pluginName) {
        // nativescript-hook and nativescript_hook should have different names, so replace all _ with ___ first and then replace all special symbols with _
        // This will lead to a clash in case plugins are called nativescript-hook and nativescript___hook
        const replacedPluginName = pluginName
            .replace(/_/g, "___")
            .replace(/[^A-Za-z0-9_]/g, "_");
        return `${hookName}${replacedPluginName}`;
    }
    replaceHookContent(hookName, podfileContent, pluginName) {
        const hookStart = `${hookName} do`;
        const hookDefinitionRegExp = new RegExp(`${hookStart} *(\\|(\\w+)\\|)?`, "g");
        const newFunctions = [];
        const replacedContent = podfileContent.replace(hookDefinitionRegExp, (substring, firstGroup, secondGroup, index) => {
            const newFunctionName = `${this.getHookBasicFuncNameForPlugin(hookName, pluginName)}_${newFunctions.length}`;
            let newDefinition = `def ${newFunctionName}`;
            const rubyFunction = { functionName: newFunctionName };
            // firstGroup is the block parameter, secondGroup is the block parameter name.
            if (firstGroup && secondGroup) {
                newDefinition = `${newDefinition} (${secondGroup})`;
                rubyFunction.functionParameters = secondGroup;
            }
            newFunctions.push(rubyFunction);
            return newDefinition;
        });
        return { replacedContent, newFunctions };
    }
    getPluginPodfileHeader(pluginPodFilePath) {
        // escape special + from the podfile path (pnpm)
        pluginPodFilePath = pluginPodFilePath.replace(/\+/g, "\\+");
        return `# Begin Podfile - ${pluginPodFilePath}`;
    }
    getPluginPodfileEnd() {
        return `# End Podfile${os_1.EOL}`;
    }
    getExtensionPodfileHeader(extensionPodFilePath, targetName) {
        const targetHeader = `target "${targetName.trim()}" do`;
        return `${this.getPluginPodfileHeader(extensionPodFilePath)}${os_1.EOL}${targetHeader}`;
    }
    getExtensionPodfileEnd() {
        return `end${os_1.EOL}${this.getPluginPodfileEnd()}`;
    }
    getPostInstallHookHeader() {
        return `${CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME} do |${CocoaPodsService.INSTALLER_BLOCK_PARAMETER_NAME}|${os_1.EOL}`;
    }
    buildPodfileContent(pluginPodFilePath, pluginName, projectData, platformData) {
        const pluginPodfileContent = this.$fs.readText(pluginPodFilePath);
        const data = this.replaceHookContent(CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME, pluginPodfileContent, pluginName);
        const cocoapodsData = this.$cocoaPodsPlatformManager.replacePlatformRow(data.replacedContent, pluginPodFilePath);
        const podfilePlatformData = cocoapodsData.podfilePlatformData;
        let replacedContent = cocoapodsData.replacedContent;
        if (projectData.nsConfig &&
            projectData.nsConfig.overridePods &&
            !this.isMainPodFile(pluginPodFilePath, projectData, platformData)) {
            replacedContent = this.overridePodsFromFile(replacedContent, projectData, platformData);
        }
        return {
            podfileContent: `${this.getPluginPodfileHeader(pluginPodFilePath)}${os_1.EOL}${replacedContent}${os_1.EOL}${this.getPluginPodfileEnd()}`,
            replacedFunctions: data.newFunctions,
            podfilePlatformData,
        };
    }
    getMainPodFilePath(projectData, platformData) {
        return path.join(projectData.appResourcesDirectoryPath, platformData.normalizedPlatformName, constants_1.PODFILE_NAME);
    }
    isMainPodFile(podFilePath, projectData, platformData) {
        const mainPodfilePath = this.getMainPodFilePath(projectData, platformData);
        return podFilePath === mainPodfilePath;
    }
    overridePodsFromFile(podfileContent, projectData, platformData) {
        const mainPodfilePath = this.getMainPodFilePath(projectData, platformData);
        if (this.$fs.exists(mainPodfilePath)) {
            const mainPodfileContent = this.$fs.readText(mainPodfilePath);
            const pods = this.getCocoaPodsFromPodfile(mainPodfileContent);
            _.forEach(pods, (pod) => {
                podfileContent = podfileContent.replace(new RegExp(`^[ ]*pod\\s*["']${pod}['"].*$`, "gm"), "#$&");
            });
        }
        return podfileContent;
    }
    _getCocoaPodsFromPodfile(podfileContent) {
        const pods = [];
        const podsRegex = /^\s*pod\s*["'](.*?)['"].*$/gm;
        let match = podsRegex.exec(podfileContent);
        while (match != null) {
            const podName = match[1];
            if (podName) {
                pods.push(podName);
            }
            match = podsRegex.exec(podfileContent);
        }
        return pods;
    }
}
CocoaPodsService.PODFILE_POST_INSTALL_SECTION_NAME = "post_install";
CocoaPodsService.INSTALLER_BLOCK_PARAMETER_NAME = "installer";
exports.CocoaPodsService = CocoaPodsService;
yok_1.injector.register("cocoapodsService", CocoaPodsService);
//# sourceMappingURL=cocoapods-service.js.map