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