xref: /expo/packages/@expo/config/build/Config.js (revision 8886ca5a)
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4  value: true
5});
6var _exportNames = {
7  getConfig: true,
8  getPackageJson: true,
9  getConfigFilePaths: true,
10  modifyConfigAsync: true,
11  getWebOutputPath: true,
12  getNameFromConfig: true,
13  getDefaultTarget: true,
14  getProjectConfigDescription: true,
15  getProjectConfigDescriptionWithPaths: true
16};
17exports.getConfig = getConfig;
18exports.getConfigFilePaths = getConfigFilePaths;
19exports.getDefaultTarget = getDefaultTarget;
20exports.getNameFromConfig = getNameFromConfig;
21exports.getPackageJson = getPackageJson;
22exports.getProjectConfigDescription = getProjectConfigDescription;
23exports.getProjectConfigDescriptionWithPaths = getProjectConfigDescriptionWithPaths;
24exports.getWebOutputPath = getWebOutputPath;
25exports.modifyConfigAsync = modifyConfigAsync;
26
27function _jsonFile() {
28  const data = _interopRequireDefault(require("@expo/json-file"));
29
30  _jsonFile = function () {
31    return data;
32  };
33
34  return data;
35}
36
37function _fs() {
38  const data = _interopRequireDefault(require("fs"));
39
40  _fs = function () {
41    return data;
42  };
43
44  return data;
45}
46
47function _glob() {
48  const data = require("glob");
49
50  _glob = function () {
51    return data;
52  };
53
54  return data;
55}
56
57function _path() {
58  const data = _interopRequireDefault(require("path"));
59
60  _path = function () {
61    return data;
62  };
63
64  return data;
65}
66
67function _resolveFrom() {
68  const data = _interopRequireDefault(require("resolve-from"));
69
70  _resolveFrom = function () {
71    return data;
72  };
73
74  return data;
75}
76
77function _semver() {
78  const data = _interopRequireDefault(require("semver"));
79
80  _semver = function () {
81    return data;
82  };
83
84  return data;
85}
86
87function _slugify() {
88  const data = _interopRequireDefault(require("slugify"));
89
90  _slugify = function () {
91    return data;
92  };
93
94  return data;
95}
96
97function _getConfig() {
98  const data = require("./getConfig");
99
100  _getConfig = function () {
101    return data;
102  };
103
104  return data;
105}
106
107function _getExpoSDKVersion() {
108  const data = require("./getExpoSDKVersion");
109
110  _getExpoSDKVersion = function () {
111    return data;
112  };
113
114  return data;
115}
116
117function _getFullName() {
118  const data = require("./getFullName");
119
120  _getFullName = function () {
121    return data;
122  };
123
124  return data;
125}
126
127function _withConfigPlugins() {
128  const data = require("./plugins/withConfigPlugins");
129
130  _withConfigPlugins = function () {
131    return data;
132  };
133
134  return data;
135}
136
137function _withInternal() {
138  const data = require("./plugins/withInternal");
139
140  _withInternal = function () {
141    return data;
142  };
143
144  return data;
145}
146
147function _resolvePackageJson() {
148  const data = require("./resolvePackageJson");
149
150  _resolvePackageJson = function () {
151    return data;
152  };
153
154  return data;
155}
156
157var _Config = require("./Config.types");
158
159Object.keys(_Config).forEach(function (key) {
160  if (key === "default" || key === "__esModule") return;
161  if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
162  if (key in exports && exports[key] === _Config[key]) return;
163  Object.defineProperty(exports, key, {
164    enumerable: true,
165    get: function () {
166      return _Config[key];
167    }
168  });
169});
170
171function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
172
173/**
174 * If a config has an `expo` object then that will be used as the config.
175 * This method reduces out other top level values if an `expo` object exists.
176 *
177 * @param config Input config object to reduce
178 */
179function reduceExpoObject(config) {
180  var _config$expo;
181
182  if (!config) return config === undefined ? null : config;
183  const {
184    mods,
185    ...expo
186  } = (_config$expo = config.expo) !== null && _config$expo !== void 0 ? _config$expo : config;
187  return {
188    expo,
189    mods
190  };
191}
192/**
193 * Get all platforms that a project is currently capable of running.
194 *
195 * @param projectRoot
196 * @param exp
197 */
198
199
200function getSupportedPlatforms(projectRoot) {
201  const platforms = [];
202
203  if (_resolveFrom().default.silent(projectRoot, 'react-native')) {
204    platforms.push('ios', 'android');
205  }
206
207  if (_resolveFrom().default.silent(projectRoot, 'react-native-web')) {
208    platforms.push('web');
209  }
210
211  return platforms;
212}
213/**
214 * Evaluate the config for an Expo project.
215 * If a function is exported from the `app.config.js` then a partial config will be passed as an argument.
216 * The partial config is composed from any existing app.json, and certain fields from the `package.json` like name and description.
217 *
218 * If options.isPublicConfig is true, the Expo config will include only public-facing options (omitting private keys).
219 * The resulting config should be suitable for hosting or embedding in a publicly readable location.
220 *
221 * **Example**
222 * ```js
223 * module.exports = function({ config }) {
224 *   // mutate the config before returning it.
225 *   config.slug = 'new slug'
226 *   return { expo: config };
227 * }
228 * ```
229 *
230 * **Supports**
231 * - `app.config.ts`
232 * - `app.config.js`
233 * - `app.config.json`
234 * - `app.json`
235 *
236 * @param projectRoot the root folder containing all of your application code
237 * @param options enforce criteria for a project config
238 */
239
240
241function getConfig(projectRoot, options = {}) {
242  const paths = getConfigFilePaths(projectRoot);
243  const rawStaticConfig = paths.staticConfigPath ? (0, _getConfig().getStaticConfig)(paths.staticConfigPath) : null; // For legacy reasons, always return an object.
244
245  const rootConfig = rawStaticConfig || {};
246  const staticConfig = reduceExpoObject(rawStaticConfig) || {}; // Can only change the package.json location if an app.json or app.config.json exists
247
248  const [packageJson, packageJsonPath] = getPackageJsonAndPath(projectRoot);
249
250  function fillAndReturnConfig(config, dynamicConfigObjectType) {
251    const configWithDefaultValues = { ...ensureConfigHasDefaultValues({
252        projectRoot,
253        exp: config.expo,
254        pkg: packageJson,
255        skipSDKVersionRequirement: options.skipSDKVersionRequirement,
256        paths,
257        packageJsonPath
258      }),
259      mods: config.mods,
260      dynamicConfigObjectType,
261      rootConfig,
262      dynamicConfigPath: paths.dynamicConfigPath,
263      staticConfigPath: paths.staticConfigPath
264    };
265
266    if (options.isModdedConfig) {
267      var _config$mods;
268
269      // @ts-ignore: Add the mods back to the object.
270      configWithDefaultValues.exp.mods = (_config$mods = config.mods) !== null && _config$mods !== void 0 ? _config$mods : null;
271    } // Apply static json plugins, should be done after _internal
272
273
274    configWithDefaultValues.exp = (0, _withConfigPlugins().withConfigPlugins)(configWithDefaultValues.exp, !!options.skipPlugins);
275
276    if (!options.isModdedConfig) {
277      // @ts-ignore: Delete mods added by static plugins when they won't have a chance to be evaluated
278      delete configWithDefaultValues.exp.mods;
279    }
280
281    if (options.isPublicConfig) {
282      var _configWithDefaultVal, _configWithDefaultVal2, _configWithDefaultVal3, _configWithDefaultVal4;
283
284      // TODD(EvanBacon): Drop plugins array after it's been resolved.
285      // Remove internal values with references to user's file paths from the public config.
286      delete configWithDefaultValues.exp._internal;
287
288      if (configWithDefaultValues.exp.hooks) {
289        delete configWithDefaultValues.exp.hooks;
290      }
291
292      if ((_configWithDefaultVal = configWithDefaultValues.exp.ios) !== null && _configWithDefaultVal !== void 0 && _configWithDefaultVal.config) {
293        delete configWithDefaultValues.exp.ios.config;
294      }
295
296      if ((_configWithDefaultVal2 = configWithDefaultValues.exp.android) !== null && _configWithDefaultVal2 !== void 0 && _configWithDefaultVal2.config) {
297        delete configWithDefaultValues.exp.android.config;
298      } // These value will be overwritten when the manifest is being served from the host (i.e. not completely accurate).
299      // @ts-ignore: currentFullName not on type yet.
300
301
302      configWithDefaultValues.exp.currentFullName = (0, _getFullName().getFullName)(configWithDefaultValues.exp); // @ts-ignore: originalFullName not on type yet.
303
304      configWithDefaultValues.exp.originalFullName = (0, _getFullName().getFullName)(configWithDefaultValues.exp);
305      (_configWithDefaultVal3 = configWithDefaultValues.exp.updates) === null || _configWithDefaultVal3 === void 0 ? true : delete _configWithDefaultVal3.codeSigningCertificate;
306      (_configWithDefaultVal4 = configWithDefaultValues.exp.updates) === null || _configWithDefaultVal4 === void 0 ? true : delete _configWithDefaultVal4.codeSigningMetadata;
307    }
308
309    return configWithDefaultValues;
310  } // Fill in the static config
311
312
313  function getContextConfig(config) {
314    return ensureConfigHasDefaultValues({
315      projectRoot,
316      exp: config.expo,
317      pkg: packageJson,
318      skipSDKVersionRequirement: true,
319      paths,
320      packageJsonPath
321    }).exp;
322  }
323
324  if (paths.dynamicConfigPath) {
325    // No app.config.json or app.json but app.config.js
326    const {
327      exportedObjectType,
328      config: rawDynamicConfig
329    } = (0, _getConfig().getDynamicConfig)(paths.dynamicConfigPath, {
330      projectRoot,
331      staticConfigPath: paths.staticConfigPath,
332      packageJsonPath,
333      config: getContextConfig(staticConfig)
334    }); // Allow for the app.config.js to `export default null;`
335    // Use `dynamicConfigPath` to detect if a dynamic config exists.
336
337    const dynamicConfig = reduceExpoObject(rawDynamicConfig) || {};
338    return fillAndReturnConfig(dynamicConfig, exportedObjectType);
339  } // No app.config.js but json or no config
340
341
342  return fillAndReturnConfig(staticConfig || {}, null);
343}
344
345function getPackageJson(projectRoot) {
346  const [pkg] = getPackageJsonAndPath(projectRoot);
347  return pkg;
348}
349
350function getPackageJsonAndPath(projectRoot) {
351  const packageJsonPath = (0, _resolvePackageJson().getRootPackageJsonPath)(projectRoot);
352  return [_jsonFile().default.read(packageJsonPath), packageJsonPath];
353}
354/**
355 * Get the static and dynamic config paths for a project. Also accounts for custom paths.
356 *
357 * @param projectRoot
358 */
359
360
361function getConfigFilePaths(projectRoot) {
362  return {
363    dynamicConfigPath: getDynamicConfigFilePath(projectRoot),
364    staticConfigPath: getStaticConfigFilePath(projectRoot)
365  };
366}
367
368function getDynamicConfigFilePath(projectRoot) {
369  for (const fileName of ['app.config.ts', 'app.config.js']) {
370    const configPath = _path().default.join(projectRoot, fileName);
371
372    if (_fs().default.existsSync(configPath)) {
373      return configPath;
374    }
375  }
376
377  return null;
378}
379
380function getStaticConfigFilePath(projectRoot) {
381  for (const fileName of ['app.config.json', 'app.json']) {
382    const configPath = _path().default.join(projectRoot, fileName);
383
384    if (_fs().default.existsSync(configPath)) {
385      return configPath;
386    }
387  }
388
389  return null;
390}
391/**
392 * Attempt to modify an Expo project config.
393 * This will only fully work if the project is using static configs only.
394 * Otherwise 'warn' | 'fail' will return with a message about why the config couldn't be updated.
395 * The potentially modified config object will be returned for testing purposes.
396 *
397 * @param projectRoot
398 * @param modifications modifications to make to an existing config
399 * @param readOptions options for reading the current config file
400 * @param writeOptions If true, the static config file will not be rewritten
401 */
402
403
404async function modifyConfigAsync(projectRoot, modifications, readOptions = {}, writeOptions = {}) {
405  const config = getConfig(projectRoot, readOptions);
406
407  if (config.dynamicConfigPath) {
408    // We cannot automatically write to a dynamic config.
409
410    /* Currently we should just use the safest approach possible, informing the user that they'll need to manually modify their dynamic config.
411     if (config.staticConfigPath) {
412      // Both a dynamic and a static config exist.
413      if (config.dynamicConfigObjectType === 'function') {
414        // The dynamic config exports a function, this means it possibly extends the static config.
415      } else {
416        // Dynamic config ignores the static config, there isn't a reason to automatically write to it.
417        // Instead we should warn the user to add values to their dynamic config.
418      }
419    }
420    */
421    return {
422      type: 'warn',
423      message: `Cannot automatically write to dynamic config at: ${_path().default.relative(projectRoot, config.dynamicConfigPath)}`,
424      config: null
425    };
426  } else if (config.staticConfigPath) {
427    // Static with no dynamic config, this means we can append to the config automatically.
428    let outputConfig; // If the config has an expo object (app.json) then append the options to that object.
429
430    if (config.rootConfig.expo) {
431      outputConfig = { ...config.rootConfig,
432        expo: { ...config.rootConfig.expo,
433          ...modifications
434        }
435      };
436    } else {
437      // Otherwise (app.config.json) just add the config modification to the top most level.
438      outputConfig = { ...config.rootConfig,
439        ...modifications
440      };
441    }
442
443    if (!writeOptions.dryRun) {
444      await _jsonFile().default.writeAsync(config.staticConfigPath, outputConfig, {
445        json5: false
446      });
447    }
448
449    return {
450      type: 'success',
451      config: outputConfig
452    };
453  }
454
455  return {
456    type: 'fail',
457    message: 'No config exists',
458    config: null
459  };
460}
461
462function ensureConfigHasDefaultValues({
463  projectRoot,
464  exp,
465  pkg,
466  paths,
467  packageJsonPath,
468  skipSDKVersionRequirement = false
469}) {
470  var _exp$name, _exp$slug, _exp$version;
471
472  if (!exp) {
473    exp = {};
474  }
475
476  exp = (0, _withInternal().withInternal)(exp, {
477    projectRoot,
478    ...(paths !== null && paths !== void 0 ? paths : {}),
479    packageJsonPath
480  }); // Defaults for package.json fields
481
482  const pkgName = typeof pkg.name === 'string' ? pkg.name : _path().default.basename(projectRoot);
483  const pkgVersion = typeof pkg.version === 'string' ? pkg.version : '1.0.0';
484  const pkgWithDefaults = { ...pkg,
485    name: pkgName,
486    version: pkgVersion
487  }; // Defaults for app.json/app.config.js fields
488
489  const name = (_exp$name = exp.name) !== null && _exp$name !== void 0 ? _exp$name : pkgName;
490  const slug = (_exp$slug = exp.slug) !== null && _exp$slug !== void 0 ? _exp$slug : (0, _slugify().default)(name.toLowerCase());
491  const version = (_exp$version = exp.version) !== null && _exp$version !== void 0 ? _exp$version : pkgVersion;
492  let description = exp.description;
493
494  if (!description && typeof pkg.description === 'string') {
495    description = pkg.description;
496  }
497
498  const expWithDefaults = { ...exp,
499    name,
500    slug,
501    version,
502    description
503  };
504  let sdkVersion;
505
506  try {
507    sdkVersion = (0, _getExpoSDKVersion().getExpoSDKVersion)(projectRoot, expWithDefaults);
508  } catch (error) {
509    if (!skipSDKVersionRequirement) throw error;
510  }
511
512  let platforms = exp.platforms;
513
514  if (!platforms) {
515    platforms = getSupportedPlatforms(projectRoot);
516  }
517
518  return {
519    exp: { ...expWithDefaults,
520      sdkVersion,
521      platforms
522    },
523    pkg: pkgWithDefaults
524  };
525}
526
527const DEFAULT_BUILD_PATH = `web-build`;
528
529function getWebOutputPath(config = {}) {
530  var _expo$web, _expo$web$build;
531
532  if (process.env.WEBPACK_BUILD_OUTPUT_PATH) {
533    return process.env.WEBPACK_BUILD_OUTPUT_PATH;
534  }
535
536  const expo = config.expo || config || {};
537  return (expo === null || expo === void 0 ? void 0 : (_expo$web = expo.web) === null || _expo$web === void 0 ? void 0 : (_expo$web$build = _expo$web.build) === null || _expo$web$build === void 0 ? void 0 : _expo$web$build.output) || DEFAULT_BUILD_PATH;
538}
539
540function getNameFromConfig(exp = {}) {
541  // For RN CLI support
542  const appManifest = exp.expo || exp;
543  const {
544    web = {}
545  } = appManifest; // rn-cli apps use a displayName value as well.
546
547  const appName = exp.displayName || appManifest.displayName || appManifest.name;
548  const webName = web.name || appName;
549  return {
550    appName,
551    webName
552  };
553}
554
555function getDefaultTarget(projectRoot, exp) {
556  var _exp;
557
558  (_exp = exp) !== null && _exp !== void 0 ? _exp : exp = getConfig(projectRoot, {
559    skipSDKVersionRequirement: true
560  }).exp; // before SDK 37, always default to managed to preserve previous behavior
561
562  if (exp.sdkVersion && exp.sdkVersion !== 'UNVERSIONED' && _semver().default.lt(exp.sdkVersion, '37.0.0')) {
563    return 'managed';
564  }
565
566  return isBareWorkflowProject(projectRoot) ? 'bare' : 'managed';
567}
568
569function isBareWorkflowProject(projectRoot) {
570  const [pkg] = getPackageJsonAndPath(projectRoot); // TODO: Drop this
571
572  if (pkg.dependencies && pkg.dependencies.expokit) {
573    return false;
574  }
575
576  const xcodeprojFiles = (0, _glob().sync)('ios/**/*.xcodeproj', {
577    absolute: true,
578    cwd: projectRoot
579  });
580
581  if (xcodeprojFiles.length) {
582    return true;
583  }
584
585  const gradleFiles = (0, _glob().sync)('android/**/*.gradle', {
586    absolute: true,
587    cwd: projectRoot
588  });
589
590  if (gradleFiles.length) {
591    return true;
592  }
593
594  return false;
595}
596/**
597 * Return a useful name describing the project config.
598 * - dynamic: app.config.js
599 * - static: app.json
600 * - custom path app config relative to root folder
601 * - both: app.config.js or app.json
602 */
603
604
605function getProjectConfigDescription(projectRoot) {
606  const paths = getConfigFilePaths(projectRoot);
607  return getProjectConfigDescriptionWithPaths(projectRoot, paths);
608}
609/**
610 * Returns a string describing the configurations used for the given project root.
611 * Will return null if no config is found.
612 *
613 * @param projectRoot
614 * @param projectConfig
615 */
616
617
618function getProjectConfigDescriptionWithPaths(projectRoot, projectConfig) {
619  if (projectConfig.dynamicConfigPath) {
620    const relativeDynamicConfigPath = _path().default.relative(projectRoot, projectConfig.dynamicConfigPath);
621
622    if (projectConfig.staticConfigPath) {
623      return `${relativeDynamicConfigPath} or ${_path().default.relative(projectRoot, projectConfig.staticConfigPath)}`;
624    }
625
626    return relativeDynamicConfigPath;
627  } else if (projectConfig.staticConfigPath) {
628    return _path().default.relative(projectRoot, projectConfig.staticConfigPath);
629  } // If a config doesn't exist, our tooling will generate a static app.json
630
631
632  return 'app.json';
633}
634//# sourceMappingURL=Config.js.map