1"use strict"; 2 3Object.defineProperty(exports, "__esModule", { 4 value: true 5}); 6exports.addBuildSourceFileToGroup = addBuildSourceFileToGroup; 7exports.addFileToGroupAndLink = addFileToGroupAndLink; 8exports.addFramework = addFramework; 9exports.addResourceFileToGroup = addResourceFileToGroup; 10exports.ensureGroupRecursively = ensureGroupRecursively; 11exports.getApplicationNativeTarget = getApplicationNativeTarget; 12exports.getBuildConfigurationForListIdAndName = getBuildConfigurationForListIdAndName; 13exports.getBuildConfigurationsForListId = getBuildConfigurationsForListId; 14exports.getHackyProjectName = getHackyProjectName; 15exports.getPbxproj = getPbxproj; 16exports.getProductName = getProductName; 17exports.getProjectName = getProjectName; 18exports.getProjectSection = getProjectSection; 19exports.getXCConfigurationListEntries = getXCConfigurationListEntries; 20exports.isBuildConfig = isBuildConfig; 21exports.isNotComment = isNotComment; 22exports.isNotTestHost = isNotTestHost; 23exports.resolvePathOrProject = resolvePathOrProject; 24exports.sanitizedName = sanitizedName; 25exports.unquote = unquote; 26function _assert() { 27 const data = _interopRequireDefault(require("assert")); 28 _assert = function () { 29 return data; 30 }; 31 return data; 32} 33function _path() { 34 const data = _interopRequireDefault(require("path")); 35 _path = function () { 36 return data; 37 }; 38 return data; 39} 40function _slugify() { 41 const data = _interopRequireDefault(require("slugify")); 42 _slugify = function () { 43 return data; 44 }; 45 return data; 46} 47function _xcode() { 48 const data = _interopRequireDefault(require("xcode")); 49 _xcode = function () { 50 return data; 51 }; 52 return data; 53} 54function _pbxFile() { 55 const data = _interopRequireDefault(require("xcode/lib/pbxFile")); 56 _pbxFile = function () { 57 return data; 58 }; 59 return data; 60} 61function _string() { 62 const data = require("./string"); 63 _string = function () { 64 return data; 65 }; 66 return data; 67} 68function _warnings() { 69 const data = require("../../utils/warnings"); 70 _warnings = function () { 71 return data; 72 }; 73 return data; 74} 75function Paths() { 76 const data = _interopRequireWildcard(require("../Paths")); 77 Paths = function () { 78 return data; 79 }; 80 return data; 81} 82function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } 83function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } 84function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 85function getProjectName(projectRoot) { 86 const sourceRoot = Paths().getSourceRoot(projectRoot); 87 return _path().default.basename(sourceRoot); 88} 89function resolvePathOrProject(projectRootOrProject) { 90 if (typeof projectRootOrProject === 'string') { 91 try { 92 return getPbxproj(projectRootOrProject); 93 } catch { 94 return null; 95 } 96 } 97 return projectRootOrProject; 98} 99 100// TODO: come up with a better solution for using app.json expo.name in various places 101function sanitizedName(name) { 102 // Default to the name `app` when every safe character has been sanitized 103 return sanitizedNameForProjects(name) || sanitizedNameForProjects((0, _slugify().default)(name)) || 'app'; 104} 105function sanitizedNameForProjects(name) { 106 return name.replace(/[\W_]+/g, '').normalize('NFD').replace(/[\u0300-\u036f]/g, ''); 107} 108 109// TODO: it's silly and kind of fragile that we look at app config to determine 110// the ios project paths. Overall this function needs to be revamped, just a 111// placeholder for now! Make this more robust when we support applying config 112// at any time (currently it's only applied on eject). 113function getHackyProjectName(projectRoot, config) { 114 // Attempt to get the current ios folder name (apply). 115 try { 116 return getProjectName(projectRoot); 117 } catch { 118 // If no iOS project exists then create a new one (eject). 119 const projectName = config.name; 120 (0, _assert().default)(projectName, 'Your project needs a name in app.json/app.config.js.'); 121 return sanitizedName(projectName); 122 } 123} 124function createProjectFileForGroup({ 125 filepath, 126 group 127}) { 128 const file = new (_pbxFile().default)(filepath); 129 const conflictingFile = group.children.find(child => child.comment === file.basename); 130 if (conflictingFile) { 131 // This can happen when a file like the GoogleService-Info.plist needs to be added and the eject command is run twice. 132 // Not much we can do here since it might be a conflicting file. 133 return null; 134 } 135 return file; 136} 137 138/** 139 * Add a resource file (ex: `SplashScreen.storyboard`, `Images.xcassets`) to an Xcode project. 140 * This is akin to creating a new code file in Xcode with `⌘+n`. 141 */ 142function addResourceFileToGroup({ 143 filepath, 144 groupName, 145 // Should add to `PBXBuildFile Section` 146 isBuildFile, 147 project, 148 verbose, 149 targetUuid 150}) { 151 return addFileToGroupAndLink({ 152 filepath, 153 groupName, 154 project, 155 verbose, 156 targetUuid, 157 addFileToProject({ 158 project, 159 file 160 }) { 161 project.addToPbxFileReferenceSection(file); 162 if (isBuildFile) { 163 project.addToPbxBuildFileSection(file); 164 } 165 project.addToPbxResourcesBuildPhase(file); 166 } 167 }); 168} 169 170/** 171 * Add a build source file (ex: `AppDelegate.m`, `ViewController.swift`) to an Xcode project. 172 * This is akin to creating a new code file in Xcode with `⌘+n`. 173 */ 174function addBuildSourceFileToGroup({ 175 filepath, 176 groupName, 177 project, 178 verbose, 179 targetUuid 180}) { 181 return addFileToGroupAndLink({ 182 filepath, 183 groupName, 184 project, 185 verbose, 186 targetUuid, 187 addFileToProject({ 188 project, 189 file 190 }) { 191 project.addToPbxFileReferenceSection(file); 192 project.addToPbxBuildFileSection(file); 193 project.addToPbxSourcesBuildPhase(file); 194 } 195 }); 196} 197 198// TODO(brentvatne): I couldn't figure out how to do this with an existing 199// higher level function exposed by the xcode library, but we should find out how to do 200// that and replace this with it 201function addFileToGroupAndLink({ 202 filepath, 203 groupName, 204 project, 205 verbose, 206 addFileToProject, 207 targetUuid 208}) { 209 const group = pbxGroupByPathOrAssert(project, groupName); 210 const file = createProjectFileForGroup({ 211 filepath, 212 group 213 }); 214 if (!file) { 215 if (verbose) { 216 // This can happen when a file like the GoogleService-Info.plist needs to be added and the eject command is run twice. 217 // Not much we can do here since it might be a conflicting file. 218 (0, _warnings().addWarningIOS)('ios-xcode-project', `Skipped adding duplicate file "${filepath}" to PBXGroup named "${groupName}"`); 219 } 220 return project; 221 } 222 if (targetUuid != null) { 223 file.target = targetUuid; 224 } else { 225 const applicationNativeTarget = project.getTarget('com.apple.product-type.application'); 226 file.target = applicationNativeTarget === null || applicationNativeTarget === void 0 ? void 0 : applicationNativeTarget.uuid; 227 } 228 file.uuid = project.generateUuid(); 229 file.fileRef = project.generateUuid(); 230 addFileToProject({ 231 project, 232 file 233 }); 234 group.children.push({ 235 value: file.fileRef, 236 comment: file.basename 237 }); 238 return project; 239} 240function getApplicationNativeTarget({ 241 project, 242 projectName 243}) { 244 const applicationNativeTarget = project.getTarget('com.apple.product-type.application'); 245 (0, _assert().default)(applicationNativeTarget, `Couldn't locate application PBXNativeTarget in '.xcodeproj' file.`); 246 (0, _assert().default)(String(applicationNativeTarget.target.name) === projectName, `Application native target name mismatch. Expected ${projectName}, but found ${applicationNativeTarget.target.name}.`); 247 return applicationNativeTarget; 248} 249 250/** 251 * Add a framework to the default app native target. 252 * 253 * @param projectName Name of the PBX project. 254 * @param framework String ending in `.framework`, i.e. `StoreKit.framework` 255 */ 256function addFramework({ 257 project, 258 projectName, 259 framework 260}) { 261 const target = getApplicationNativeTarget({ 262 project, 263 projectName 264 }); 265 return project.addFramework(framework, { 266 target: target.uuid 267 }); 268} 269function splitPath(path) { 270 // TODO: Should we account for other platforms that may not use `/` 271 return path.split('/'); 272} 273const findGroup = (group, name) => { 274 if (!group) { 275 return undefined; 276 } 277 return group.children.find(group => group.comment === name); 278}; 279function findGroupInsideGroup(project, group, name) { 280 const foundGroup = findGroup(group, name); 281 if (foundGroup) { 282 var _project$getPBXGroupB; 283 return (_project$getPBXGroupB = project.getPBXGroupByKey(foundGroup.value)) !== null && _project$getPBXGroupB !== void 0 ? _project$getPBXGroupB : null; 284 } 285 return null; 286} 287function pbxGroupByPathOrAssert(project, path) { 288 const { 289 firstProject 290 } = project.getFirstProject(); 291 let group = project.getPBXGroupByKey(firstProject.mainGroup); 292 const components = splitPath(path); 293 for (const name of components) { 294 const nextGroup = findGroupInsideGroup(project, group, name); 295 if (nextGroup) { 296 group = nextGroup; 297 } else { 298 break; 299 } 300 } 301 if (!group) { 302 throw Error(`Xcode PBXGroup with name "${path}" could not be found in the Xcode project.`); 303 } 304 return group; 305} 306function ensureGroupRecursively(project, filepath) { 307 var _topMostGroup; 308 const components = splitPath(filepath); 309 const hasChild = (group, name) => group.children.find(({ 310 comment 311 }) => comment === name); 312 const { 313 firstProject 314 } = project.getFirstProject(); 315 let topMostGroup = project.getPBXGroupByKey(firstProject.mainGroup); 316 for (const pathComponent of components) { 317 if (topMostGroup && !hasChild(topMostGroup, pathComponent)) { 318 topMostGroup.children.push({ 319 comment: pathComponent, 320 value: project.pbxCreateGroup(pathComponent, '""') 321 }); 322 } 323 topMostGroup = project.pbxGroupByName(pathComponent); 324 } 325 return (_topMostGroup = topMostGroup) !== null && _topMostGroup !== void 0 ? _topMostGroup : null; 326} 327 328/** 329 * Get the pbxproj for the given path 330 */ 331function getPbxproj(projectRoot) { 332 const projectPath = Paths().getPBXProjectPath(projectRoot); 333 const project = _xcode().default.project(projectPath); 334 project.parseSync(); 335 return project; 336} 337 338/** 339 * Get the productName for a project, if the name is using a variable `$(TARGET_NAME)`, then attempt to get the value of that variable. 340 * 341 * @param project 342 */ 343function getProductName(project) { 344 let productName = '$(TARGET_NAME)'; 345 try { 346 // If the product name is numeric, this will fail (it's a getter). 347 // If the bundle identifier' final component is only numeric values, then the PRODUCT_NAME 348 // will be a numeric value, this results in a bug where the product name isn't useful, 349 // i.e. `com.bacon.001` -> `1` -- in this case, use the first target name. 350 productName = project.productName; 351 } catch {} 352 if (productName === '$(TARGET_NAME)') { 353 var _project$getFirstTarg, _project$getFirstTarg2; 354 const targetName = (_project$getFirstTarg = project.getFirstTarget()) === null || _project$getFirstTarg === void 0 ? void 0 : (_project$getFirstTarg2 = _project$getFirstTarg.firstTarget) === null || _project$getFirstTarg2 === void 0 ? void 0 : _project$getFirstTarg2.productName; 355 productName = targetName !== null && targetName !== void 0 ? targetName : productName; 356 } 357 return productName; 358} 359function getProjectSection(project) { 360 return project.pbxProjectSection(); 361} 362function getXCConfigurationListEntries(project) { 363 const lists = project.pbxXCConfigurationList(); 364 return Object.entries(lists).filter(isNotComment); 365} 366function getBuildConfigurationsForListId(project, configurationListId) { 367 const configurationListEntries = getXCConfigurationListEntries(project); 368 const [, configurationList] = configurationListEntries.find(([key]) => key === configurationListId); 369 const buildConfigurations = configurationList.buildConfigurations.map(i => i.value); 370 return Object.entries(project.pbxXCBuildConfigurationSection()).filter(isNotComment).filter(isBuildConfig).filter(([key]) => buildConfigurations.includes(key)); 371} 372function getBuildConfigurationForListIdAndName(project, { 373 configurationListId, 374 buildConfiguration 375}) { 376 const xcBuildConfigurationEntry = getBuildConfigurationsForListId(project, configurationListId).find(i => (0, _string().trimQuotes)(i[1].name) === buildConfiguration); 377 if (!xcBuildConfigurationEntry) { 378 throw new Error(`Build configuration '${buildConfiguration}' does not exist in list with id '${configurationListId}'`); 379 } 380 return xcBuildConfigurationEntry; 381} 382function isBuildConfig([, sectionItem]) { 383 return sectionItem.isa === 'XCBuildConfiguration'; 384} 385function isNotTestHost([, sectionItem]) { 386 return !sectionItem.buildSettings.TEST_HOST; 387} 388function isNotComment([key]) { 389 return !key.endsWith(`_comment`); 390} 391 392// Remove surrounding double quotes if they exist. 393function unquote(value) { 394 var _value$match$, _value$match; 395 // projects with numeric names will fail due to a bug in the xcode package. 396 if (typeof value === 'number') { 397 value = String(value); 398 } 399 return (_value$match$ = (_value$match = value.match(/^"(.*)"$/)) === null || _value$match === void 0 ? void 0 : _value$match[1]) !== null && _value$match$ !== void 0 ? _value$match$ : value; 400} 401//# sourceMappingURL=Xcodeproj.js.map