UNPKG

@embrace-io/react-native

Version:
321 lines (320 loc) 14.6 kB
"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.XcodeProject = exports.findNameWithCaseSensitiveFromPath = exports.getBridgingHeaderContents = exports.projectNameToBridgingHeader = exports.getXcodeProject = exports.xcodePatchable = exports.embracePlistPatchable = exports.podfilePatchable = exports.getPodFile = exports.getAppDelegateByIOSLanguage = exports.EMBRACE_IMPORT_OBJECTIVEC = exports.ENOENT_XCODE_PROJ_ERROR_MESSAGE = exports.UPLOAD_SYMBOLS_PHASE = exports.EMBRACE_INIT_OBJECTIVEC = exports.EMBR_RUN_SCRIPT = exports.EXPORT_SOURCEMAP_RN_VAR = exports.MKDIR_SOURCEMAP_DIR = exports.BUNDLE_PHASE_REGEXP = exports.EMBR_KSCRASH_MODULAR_HEADER_POD = exports.EMBR_NATIVE_POD = void 0; /* eslint-disable @typescript-eslint/no-explicit-any -- untyped xcode pbxproj graph */ const common_1 = require("../setup/patches/common"); const EmbraceLogger_1 = require("../../src/utils/EmbraceLogger"); const file_1 = require("./file"); const osPath = require("path"); const fs = require("fs"); const xcode = require("xcode"); const glob = require("glob"); const LOGGER = new EmbraceLogger_1.default(console); const UPLOAD_SYMBOLS_PHASE = "Embrace Symbol Uploads"; exports.UPLOAD_SYMBOLS_PHASE = UPLOAD_SYMBOLS_PHASE; const EMBRACE_INIT_OBJECTIVEC = "[EmbraceInitializer start];"; exports.EMBRACE_INIT_OBJECTIVEC = EMBRACE_INIT_OBJECTIVEC; const EMBR_RUN_SCRIPT = '"${SRCROOT}/../node_modules/@embrace-io/react-native/ios/scripts/run.sh"'; exports.EMBR_RUN_SCRIPT = EMBR_RUN_SCRIPT; const EMBR_NATIVE_POD = `pod 'EmbraceIO'`; exports.EMBR_NATIVE_POD = EMBR_NATIVE_POD; const EMBR_KSCRASH_MODULAR_HEADER_POD = ` # [Embrace] Make KSCrash modular so Swift can import it pod 'KSCrash', :modular_headers => true `; exports.EMBR_KSCRASH_MODULAR_HEADER_POD = EMBR_KSCRASH_MODULAR_HEADER_POD; const BUNDLE_PHASE_REGEXP = /^.*?\/(packager|scripts)\/react-native-xcode\.sh\s*/m; exports.BUNDLE_PHASE_REGEXP = BUNDLE_PHASE_REGEXP; const MKDIR_SOURCEMAP_DIR = 'mkdir -p "$CONFIGURATION_BUILD_DIR/embrace-assets"'; exports.MKDIR_SOURCEMAP_DIR = MKDIR_SOURCEMAP_DIR; const EXPORT_SOURCEMAP_RN_VAR = 'export SOURCEMAP_FILE="$CONFIGURATION_BUILD_DIR/embrace-assets/main.jsbundle.map";'; exports.EXPORT_SOURCEMAP_RN_VAR = EXPORT_SOURCEMAP_RN_VAR; const DOCS_MESSAGE = "Please refer to the docs at https://embrace.io/docs to update manually"; const ENOENT_XCODE_PROJ_ERROR_MESSAGE = "Looks like the app name in package.json doesn't match your iOS project. If you used a custom package name (--package-name), double-check the iOS project name and rerun"; exports.ENOENT_XCODE_PROJ_ERROR_MESSAGE = ENOENT_XCODE_PROJ_ERROR_MESSAGE; const EMBRACE_IMPORT_OBJECTIVEC = ({ bridgingHeader, }) => [`#import "${bridgingHeader}"`]; exports.EMBRACE_IMPORT_OBJECTIVEC = EMBRACE_IMPORT_OBJECTIVEC; const getAppDelegateByIOSLanguage = (projectName, language) => { const appDelegatePathFounded = glob.sync(`**/${common_1.MAIN_CLASS_BY_LANGUAGE[language]}`, { ignore: ["node_modules/**", "ios/Pods/**"], }); let appDelegatePath; if (appDelegatePathFounded.length === 1) { appDelegatePath = appDelegatePathFounded[0]; } else if (appDelegatePathFounded.length > 1) { appDelegatePath = appDelegatePathFounded.find((path) => { return (path.toLocaleLowerCase().indexOf(projectName.toLocaleLowerCase()) > -1); }); } if (!appDelegatePath) { return undefined; } return (0, file_1.getFileContents)(appDelegatePath); }; exports.getAppDelegateByIOSLanguage = getAppDelegateByIOSLanguage; const getPodFile = () => { const podfilePath = glob.sync("ios/Podfile")[0]; if (!podfilePath) { throw new Error(`Could not find Podfile. ${DOCS_MESSAGE}.`); } return (0, file_1.getFileContents)(podfilePath); }; exports.getPodFile = getPodFile; const podfilePatchable = () => { return new Promise((resolve, reject) => { try { return resolve(getPodFile()); } catch (e) { if (e instanceof Error) { return reject(e.message); } return reject(e); } }); }; exports.podfilePatchable = podfilePatchable; const embracePlistPatchable = () => { return new Promise((resolve, reject) => { const plistPath = glob.sync("ios/**/Embrace-Info.plist")[0]; if (!plistPath) { return reject(LOGGER.format("Could not find Embrace-Info.plist")); } return resolve((0, file_1.getFileContents)(plistPath)); }); }; exports.embracePlistPatchable = embracePlistPatchable; const xcodePatchable = (projectName) => { return new Promise((resolve, reject) => { const projectPathFounded = glob.sync("**/*.xcodeproj/project.pbxproj", { ignore: ["node_modules/**", "ios/Pods/**"] }); let projectPath; if (projectPathFounded.length === 1) { projectPath = projectPathFounded[0]; } else if (projectPathFounded.length > 1) { projectPath = projectPathFounded.find((path) => { return (path.toLocaleLowerCase().indexOf(projectName.toLocaleLowerCase()) > -1); }); } if (!projectPath) { return reject(`Could not find xcode project file. ${ENOENT_XCODE_PROJ_ERROR_MESSAGE}. ${DOCS_MESSAGE}.`); } return getXcodeProject(projectPath).then(resolve).catch(reject); }); }; exports.xcodePatchable = xcodePatchable; const getXcodeProject = (path) => { const project = xcode.project(path); return new Promise((resolve, reject) => { project.parse((err) => { if (err) { return reject(err); } resolve(new XcodeProject(path, project)); }); }); }; exports.getXcodeProject = getXcodeProject; class XcodeProject { constructor(path = "", project) { this.path = path; this.project = project; } buildPhaseObj() { return this.project.hash.project.objects.PBXShellScriptBuildPhase || {}; } hasLine(key, line) { const buildPhaseObj = this.buildPhaseObj(); const phase = buildPhaseObj[key]; if (!phase) { return false; } if (!phase.shellScript) { return false; } const code = JSON.parse(phase.shellScript); return ((line instanceof RegExp ? code.search(line) : code.indexOf(line)) > -1); } modifyPhase(key, line, add) { // Doesn't include line if (!this.hasLine(key, line)) { return; } // Already has add if (add && this.hasLine(key, add)) { return; } const buildPhaseObj = this.buildPhaseObj(); const phase = buildPhaseObj[key]; if (!phase) { return; } if (!phase.shellScript) { return; } let code = JSON.parse(phase.shellScript); code = code.replace(line, (match) => `${add}${add === "" ? "" : match}`); phase.shellScript = JSON.stringify(code); } findPhase(line) { const buildPhaseObj = this.buildPhaseObj(); return (Object.keys(buildPhaseObj).find(key => { return this.hasLine(key, line); }) || ""); } findAndRemovePhase(match) { const buildPhaseObj = this.buildPhaseObj(); this.project.hash.project.objects.PBXShellScriptBuildPhase = Object.keys(buildPhaseObj).reduce((a, key) => { const phase = buildPhaseObj[key]; if (!phase) { return a; } // against the name of the Phase if (phase.name) { // supporting regexp const isRegExp = match instanceof RegExp && match.test(phase.name); // checking plane string const isPlaneString = typeof match === "string" && phase.name.includes(match); if (isRegExp || isPlaneString) { return a; } } // against a line of the Shell Script if (phase.shellScript) { // supporting regexp const isRegExp = match instanceof RegExp && match.test(phase.shellScript); // checking plane string const isPlaneString = typeof match === "string" && phase.shellScript.includes(match); if (isRegExp || isPlaneString) { return a; } } return Object.assign(Object.assign({}, a), { [key]: buildPhaseObj[key] }); }, {}); const nativeTargets = this.project.hash.project.objects.PBXNativeTarget; this.project.hash.project.objects.PBXNativeTarget = Object.keys(nativeTargets).reduce((a, key) => { const phase = nativeTargets[key]; if (!phase) { return a; } if (phase.buildPhases) { phase.buildPhases = phase.buildPhases.filter(({ comment }) => !comment.includes(UPLOAD_SYMBOLS_PHASE)); } return Object.assign(Object.assign({}, a), { [key]: phase }); }, {}); } patch() { fs.writeFileSync(this.path, this.writeSync()); } writeSync() { return this.project.writeSync(); } addFile(groupName, path, fileType) { const target = this.findHash(this.project.hash.project.objects.PBXNativeTarget, groupName); const group = this.findHash(this.project.hash.project.objects.PBXGroup, groupName); if (target && group) { const file = this.project.addFile(path, group[0], { target: target[0] }); file.target = target[0]; file.uuid = this.project.generateUuid(); this.project.addToPbxBuildFileSection(file); if (fileType === "resource") { this.project.addToPbxResourcesBuildPhase(file); } else if (fileType === "source") { this.project.addToPbxSourcesBuildPhase(file); } } } getBridgingHeaderName(groupName) { const productModuleName = this.getBuildProperty(groupName, "PRODUCT_MODULE_NAME") || groupName; return projectNameToBridgingHeader(productModuleName); } getTargetHash(name) { const target = this.project.pbxTargetByName(name); return target ? target.buildConfigurationList : ""; } getBuildProperty(groupName, propertyName) { return this.project.getBuildProperty(propertyName, undefined, groupName); } updateBuildProperty(groupName, propertyName, value) { const targetHash = this.getTargetHash(groupName); const configurations = this.project.pbxXCBuildConfigurationSection(); const xcConfigList = this.project.pbxXCConfigurationList(); for (const configName in xcConfigList) { if (configName !== targetHash) { continue; } for (const item of xcConfigList[configName].buildConfigurations) { const config = configurations[item.value]; if (config) { config.buildSettings[propertyName] = value; } } } } removeResourceFile(groupName, path) { const target = this.findHash(this.project.hash.project.objects.PBXNativeTarget, groupName); const group = this.findHash(this.project.hash.project.objects.PBXGroup, groupName); if (target && group) { const file = this.project.removeSourceFile(path, { target: target[0] }, group[0]); this.project.removeFromPbxResourcesBuildPhase(file); } } findHash(objects, groupName) { return Object.entries(objects).find(([_, group]) => { return group.name === groupName; }); } addBridgingHeader(projectName) { return __awaiter(this, void 0, void 0, function* () { const bridgingHeader = this.getBuildProperty(projectName, "SWIFT_OBJC_BRIDGING_HEADER"); if (bridgingHeader) { LOGGER.warn("bridging header already exists"); return true; } // Add the bridging header file const filename = `${projectName}-Bridging-Header.h`; fs.writeFileSync(osPath.join(this.path, "../../", projectName, filename), getBridgingHeaderContents()); const nameWithCaseSensitive = findNameWithCaseSensitiveFromPath(this.path, projectName); this.addFile(nameWithCaseSensitive, `${nameWithCaseSensitive}/${filename}`, "source"); this.updateBuildProperty(projectName, "SWIFT_OBJC_BRIDGING_HEADER", `"${nameWithCaseSensitive}/${filename}"`); this.patch(); return true; }); } } exports.XcodeProject = XcodeProject; // NOTE: https://developer.apple.com/documentation/swift/importing-swift-into-objective-c#Overview const projectNameToBridgingHeader = (projectName) => { const alphanumericOnly = projectName.replace(/\W+/g, "_"); const firstNumberReplaced = alphanumericOnly.replace(/^\d/, "_"); return `${firstNumberReplaced}-Swift.h`; }; exports.projectNameToBridgingHeader = projectNameToBridgingHeader; const getBridgingHeaderContents = () => { return `// // Use this file to import your target's public headers that you would like to expose to Swift. // `; }; exports.getBridgingHeaderContents = getBridgingHeaderContents; const findNameWithCaseSensitiveFromPath = (path, name) => { const pathSplitted = path.split("/"); const nameInLowerCase = name.toLocaleLowerCase(); const nameFounded = pathSplitted.find(element => element.toLocaleLowerCase() === `${nameInLowerCase}.xcodeproj`); if (nameFounded) { return nameFounded.replace(".xcodeproj", ""); } return name; }; exports.findNameWithCaseSensitiveFromPath = findNameWithCaseSensitiveFromPath;