"use strict"; var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; result["default"] = mod; return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const path = __importStar(require("path")); const xcode_1 = __importDefault(require("xcode")); const pbxFile_1 = __importDefault(require("xcode/lib/pbxFile")); const errors_1 = require("../../utils/errors"); const warnings_1 = require("../../utils/warnings"); const Paths = __importStar(require("../Paths")); function getProjectName(projectRoot) { const sourceRoot = Paths.getSourceRoot(projectRoot); return path.basename(sourceRoot); } exports.getProjectName = getProjectName; // TODO: come up with a better solution for using app.json expo.name in various places function sanitizedName(name) { return name .replace(/[\W_]+/g, '') .normalize('NFD') .replace(/[\u0300-\u036f]/g, ''); } // TODO: it's silly and kind of fragile that we look at app config to determine // the ios project paths. Overall this function needs to be revamped, just a // placeholder for now! Make this more robust when we support applying config // at any time (currently it's only applied on eject). function getHackyProjectName(projectRoot, config) { // Attempt to get the current ios folder name (apply). try { return getProjectName(projectRoot); } catch (_a) { // If no iOS project exists then create a new one (eject). const projectName = config.name; errors_1.assert(projectName, 'Your project needs a name in app.json/app.config.js.'); return sanitizedName(projectName); } } exports.getHackyProjectName = getHackyProjectName; function createProjectFileForGroup({ filepath, group }) { const file = new pbxFile_1.default(filepath); const conflictingFile = group.children.find(child => child.comment === file.basename); if (conflictingFile) { // This can happen when a file like the GoogleService-Info.plist needs to be added and the eject command is run twice. // Not much we can do here since it might be a conflicting file. return null; } return file; } /** * Add a resource file (ex: `SplashScreen.storyboard`, `Images.xcassets`) to an Xcode project. * This is akin to creating a new code file in Xcode with `⌘+n`. */ function addResourceFileToGroup({ filepath, groupName, // Should add to `PBXBuildFile Section` isBuildFile, project, }) { return addFileToGroupAndLink({ filepath, groupName, project, addFileToProject({ project, file }) { project.addToPbxFileReferenceSection(file); if (isBuildFile) { project.addToPbxBuildFileSection(file); } project.addToPbxResourcesBuildPhase(file); }, }); } exports.addResourceFileToGroup = addResourceFileToGroup; /** * Add a build source file (ex: `AppDelegate.m`, `ViewController.swift`) to an Xcode project. * This is akin to creating a new code file in Xcode with `⌘+n`. */ function addBuildSourceFileToGroup({ filepath, groupName, project, }) { return addFileToGroupAndLink({ filepath, groupName, project, addFileToProject({ project, file }) { project.addToPbxFileReferenceSection(file); project.addToPbxBuildFileSection(file); project.addToPbxSourcesBuildPhase(file); }, }); } exports.addBuildSourceFileToGroup = addBuildSourceFileToGroup; // TODO(brentvatne): I couldn't figure out how to do this with an existing // higher level function exposed by the xcode library, but we should find out how to do // that and replace this with it function addFileToGroupAndLink({ filepath, groupName, project, addFileToProject, }) { const group = pbxGroupByPathOrAssert(project, groupName); const file = createProjectFileForGroup({ filepath, group }); if (!file) { // This can happen when a file like the GoogleService-Info.plist needs to be added and the eject command is run twice. // Not much we can do here since it might be a conflicting file. warnings_1.addWarningIOS('ios-xcode-project', `Skipped adding duplicate file "${filepath}" to PBXGroup named "${groupName}"`); return project; } file.uuid = project.generateUuid(); file.fileRef = project.generateUuid(); addFileToProject({ project, file }); group.children.push({ value: file.fileRef, comment: file.basename, }); return project; } exports.addFileToGroupAndLink = addFileToGroupAndLink; function getApplicationNativeTarget({ project, projectName, }) { const applicationNativeTarget = project.getTarget('com.apple.product-type.application'); errors_1.assert(applicationNativeTarget, `Couldn't locate application PBXNativeTarget in '.xcodeproj' file.`); errors_1.assert(String(applicationNativeTarget.target.name) === projectName, `Application native target name mismatch. Expected ${projectName}, but found ${applicationNativeTarget.target.name}.`); return applicationNativeTarget; } exports.getApplicationNativeTarget = getApplicationNativeTarget; /** * Add a framework to the default app native target. * * @param projectName Name of the PBX project. * @param framework String ending in `.framework`, i.e. `StoreKit.framework` */ function addFramework({ project, projectName, framework, }) { const target = getApplicationNativeTarget({ project, projectName }); return project.addFramework(framework, { target: target.uuid }); } exports.addFramework = addFramework; function splitPath(path) { // TODO: Should we account for other platforms that may not use `/` return path.split('/'); } const findGroup = (group, name) => { if (!group) { return undefined; } return group.children.find(group => group.comment === name); }; function findGroupInsideGroup(project, group, name) { var _a; const foundGroup = findGroup(group, name); if (foundGroup) { return (_a = project.getPBXGroupByKey(foundGroup.value)) !== null && _a !== void 0 ? _a : null; } return null; } function pbxGroupByPathOrAssert(project, path) { const { firstProject } = project.getFirstProject(); let group = project.getPBXGroupByKey(firstProject.mainGroup); const components = splitPath(path); for (const name of components) { const nextGroup = findGroupInsideGroup(project, group, name); if (nextGroup) { group = nextGroup; } else { break; } } if (!group) { throw Error(`Xcode PBXGroup with name "${path}" could not be found in the Xcode project.`); } return group; } function ensureGroupRecursively(project, filepath) { const components = splitPath(filepath); const hasChild = (group, name) => group.children.find(({ comment }) => comment === name); const { firstProject } = project.getFirstProject(); let topMostGroup = project.getPBXGroupByKey(firstProject.mainGroup); for (const pathComponent of components) { if (topMostGroup && !hasChild(topMostGroup, pathComponent)) { topMostGroup.children.push({ comment: pathComponent, value: project.pbxCreateGroup(pathComponent, '""'), }); } topMostGroup = project.pbxGroupByName(pathComponent); } return topMostGroup !== null && topMostGroup !== void 0 ? topMostGroup : null; } exports.ensureGroupRecursively = ensureGroupRecursively; /** * Get the pbxproj for the given path */ function getPbxproj(projectRoot) { const projectPath = Paths.getPBXProjectPath(projectRoot); const project = xcode_1.default.project(projectPath); project.parseSync(); return project; } exports.getPbxproj = getPbxproj; /** * Get the productName for a project, if the name is using a variable `$(TARGET_NAME)`, then attempt to get the value of that variable. * * @param project */ function getProductName(project) { var _a, _b; let productName = project.productName; if (productName === '$(TARGET_NAME)') { const targetName = (_b = (_a = project.getFirstTarget()) === null || _a === void 0 ? void 0 : _a.firstTarget) === null || _b === void 0 ? void 0 : _b.productName; productName = targetName !== null && targetName !== void 0 ? targetName : project.productName; } return productName; } exports.getProductName = getProductName; function getProjectSection(project) { return project.pbxProjectSection(); } exports.getProjectSection = getProjectSection; function getNativeTargets(project) { const section = project.pbxNativeTargetSection(); return Object.entries(section).filter(isNotComment); } exports.getNativeTargets = getNativeTargets; function findFirstNativeTarget(project) { const { targets } = Object.values(getProjectSection(project))[0]; const target = targets[0].value; const nativeTargets = getNativeTargets(project); return nativeTargets.find(([key]) => key === target); } exports.findFirstNativeTarget = findFirstNativeTarget; function findNativeTargetByName(project, targetName) { const nativeTargets = getNativeTargets(project); return nativeTargets.find(([, i]) => i.name === targetName || i.name === `"${targetName}"`); } exports.findNativeTargetByName = findNativeTargetByName; function getXCConfigurationListEntries(project) { const lists = project.pbxXCConfigurationList(); return Object.entries(lists).filter(isNotComment); } exports.getXCConfigurationListEntries = getXCConfigurationListEntries; function getBuildConfigurationForId(project, configurationListId) { const configurationListEntries = getXCConfigurationListEntries(project); const [, configurationList] = configurationListEntries.find(([key]) => key === configurationListId); const buildConfigurations = configurationList.buildConfigurations.map(i => i.value); return Object.entries(project.pbxXCBuildConfigurationSection()) .filter(isNotComment) .filter(isBuildConfig) .filter(isNotTestHost) .filter(([key]) => buildConfigurations.includes(key)); } exports.getBuildConfigurationForId = getBuildConfigurationForId; function isBuildConfig([, sectionItem]) { return sectionItem.isa === 'XCBuildConfiguration'; } exports.isBuildConfig = isBuildConfig; function isNotTestHost([, sectionItem]) { return !sectionItem.buildSettings.TEST_HOST; } exports.isNotTestHost = isNotTestHost; function isNotComment([key]) { return !key.endsWith(`_comment`); } exports.isNotComment = isNotComment; //# sourceMappingURL=Xcodeproj.js.map