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