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