xref: /expo/packages/@expo/config/build/Config.js (revision f2fbea2e)
1"use strict";
2
3Object.defineProperty(exports, "__esModule", {
4  value: true
5});
6var _exportNames = {
7  getConfig: true,
8  getPackageJson: true,
9  readConfigJson: true,
10  getConfigFilePaths: true,
11  findConfigFile: true,
12  configFilename: true,
13  readExpRcAsync: true,
14  resetCustomConfigPaths: true,
15  setCustomConfigPath: true,
16  modifyConfigAsync: true,
17  writeConfigJsonAsync: true,
18  getWebOutputPath: true,
19  getNameFromConfig: true,
20  getDefaultTarget: true,
21  getProjectConfigDescription: true,
22  getProjectConfigDescriptionWithPaths: true,
23  isLegacyImportsEnabled: true
24};
25exports.configFilename = configFilename;
26exports.findConfigFile = findConfigFile;
27exports.getConfig = getConfig;
28exports.getConfigFilePaths = getConfigFilePaths;
29exports.getDefaultTarget = getDefaultTarget;
30exports.getNameFromConfig = getNameFromConfig;
31exports.getPackageJson = getPackageJson;
32exports.getProjectConfigDescription = getProjectConfigDescription;
33exports.getProjectConfigDescriptionWithPaths = getProjectConfigDescriptionWithPaths;
34exports.getWebOutputPath = getWebOutputPath;
35Object.defineProperty(exports, "isLegacyImportsEnabled", {
36  enumerable: true,
37  get: function () {
38    return _isLegacyImportsEnabled().isLegacyImportsEnabled;
39  }
40});
41exports.modifyConfigAsync = modifyConfigAsync;
42exports.readConfigJson = readConfigJson;
43exports.readExpRcAsync = readExpRcAsync;
44exports.resetCustomConfigPaths = resetCustomConfigPaths;
45exports.setCustomConfigPath = setCustomConfigPath;
46exports.writeConfigJsonAsync = writeConfigJsonAsync;
47
48function _jsonFile() {
49  const data = _interopRequireDefault(require("@expo/json-file"));
50
51  _jsonFile = function () {
52    return data;
53  };
54
55  return data;
56}
57
58function _fs() {
59  const data = _interopRequireDefault(require("fs"));
60
61  _fs = function () {
62    return data;
63  };
64
65  return data;
66}
67
68function _glob() {
69  const data = require("glob");
70
71  _glob = function () {
72    return data;
73  };
74
75  return data;
76}
77
78function _path() {
79  const data = _interopRequireDefault(require("path"));
80
81  _path = function () {
82    return data;
83  };
84
85  return data;
86}
87
88function _resolveFrom() {
89  const data = _interopRequireDefault(require("resolve-from"));
90
91  _resolveFrom = function () {
92    return data;
93  };
94
95  return data;
96}
97
98function _semver() {
99  const data = _interopRequireDefault(require("semver"));
100
101  _semver = function () {
102    return data;
103  };
104
105  return data;
106}
107
108function _slugify() {
109  const data = _interopRequireDefault(require("slugify"));
110
111  _slugify = function () {
112    return data;
113  };
114
115  return data;
116}
117
118function _Errors() {
119  const data = require("./Errors");
120
121  _Errors = function () {
122    return data;
123  };
124
125  return data;
126}
127
128function _Project() {
129  const data = require("./Project");
130
131  _Project = function () {
132    return data;
133  };
134
135  return data;
136}
137
138function _getConfig() {
139  const data = require("./getConfig");
140
141  _getConfig = function () {
142    return data;
143  };
144
145  return data;
146}
147
148function _getFullName() {
149  const data = require("./getFullName");
150
151  _getFullName = function () {
152    return data;
153  };
154
155  return data;
156}
157
158function _withConfigPlugins() {
159  const data = require("./plugins/withConfigPlugins");
160
161  _withConfigPlugins = function () {
162    return data;
163  };
164
165  return data;
166}
167
168function _withInternal() {
169  const data = require("./plugins/withInternal");
170
171  _withInternal = function () {
172    return data;
173  };
174
175  return data;
176}
177
178function _resolvePackageJson() {
179  const data = require("./resolvePackageJson");
180
181  _resolvePackageJson = function () {
182    return data;
183  };
184
185  return data;
186}
187
188var _Config = require("./Config.types");
189
190Object.keys(_Config).forEach(function (key) {
191  if (key === "default" || key === "__esModule") return;
192  if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
193  if (key in exports && exports[key] === _Config[key]) return;
194  Object.defineProperty(exports, key, {
195    enumerable: true,
196    get: function () {
197      return _Config[key];
198    }
199  });
200});
201
202function _isLegacyImportsEnabled() {
203  const data = require("./isLegacyImportsEnabled");
204
205  _isLegacyImportsEnabled = function () {
206    return data;
207  };
208
209  return data;
210}
211
212function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
213
214/**
215 * If a config has an `expo` object then that will be used as the config.
216 * This method reduces out other top level values if an `expo` object exists.
217 *
218 * @param config Input config object to reduce
219 */
220function reduceExpoObject(config) {
221  var _config$expo;
222
223  if (!config) return config === undefined ? null : config;
224  const {
225    mods,
226    ...expo
227  } = (_config$expo = config.expo) !== null && _config$expo !== void 0 ? _config$expo : config;
228  return {
229    expo,
230    mods
231  };
232}
233/**
234 * Get all platforms that a project is currently capable of running.
235 *
236 * @param projectRoot
237 * @param exp
238 */
239
240
241function getSupportedPlatforms(projectRoot) {
242  const platforms = [];
243
244  if (_resolveFrom().default.silent(projectRoot, 'react-native')) {
245    platforms.push('ios', 'android');
246  }
247
248  if (_resolveFrom().default.silent(projectRoot, 'react-native-web')) {
249    platforms.push('web');
250  }
251
252  return platforms;
253}
254/**
255 * Evaluate the config for an Expo project.
256 * If a function is exported from the `app.config.js` then a partial config will be passed as an argument.
257 * The partial config is composed from any existing app.json, and certain fields from the `package.json` like name and description.
258 *
259 * If options.isPublicConfig is true, the Expo config will include only public-facing options (omitting private keys).
260 * The resulting config should be suitable for hosting or embedding in a publicly readable location.
261 *
262 * **Example**
263 * ```js
264 * module.exports = function({ config }) {
265 *   // mutate the config before returning it.
266 *   config.slug = 'new slug'
267 *   return { expo: config };
268 * }
269 * ```
270 *
271 * **Supports**
272 * - `app.config.ts`
273 * - `app.config.js`
274 * - `app.config.json`
275 * - `app.json`
276 *
277 * @param projectRoot the root folder containing all of your application code
278 * @param options enforce criteria for a project config
279 */
280
281
282function getConfig(projectRoot, options = {}) {
283  const paths = getConfigFilePaths(projectRoot);
284  const rawStaticConfig = paths.staticConfigPath ? (0, _getConfig().getStaticConfig)(paths.staticConfigPath) : null; // For legacy reasons, always return an object.
285
286  const rootConfig = rawStaticConfig || {};
287  const staticConfig = reduceExpoObject(rawStaticConfig) || {}; // Can only change the package.json location if an app.json or app.config.json exists
288
289  const [packageJson, packageJsonPath] = getPackageJsonAndPath(projectRoot);
290
291  function fillAndReturnConfig(config, dynamicConfigObjectType) {
292    const configWithDefaultValues = { ...ensureConfigHasDefaultValues({
293        projectRoot,
294        exp: config.expo,
295        pkg: packageJson,
296        skipSDKVersionRequirement: options.skipSDKVersionRequirement,
297        paths,
298        packageJsonPath
299      }),
300      mods: config.mods,
301      dynamicConfigObjectType,
302      rootConfig,
303      dynamicConfigPath: paths.dynamicConfigPath,
304      staticConfigPath: paths.staticConfigPath
305    };
306
307    if (options.isModdedConfig) {
308      var _config$mods;
309
310      // @ts-ignore: Add the mods back to the object.
311      configWithDefaultValues.exp.mods = (_config$mods = config.mods) !== null && _config$mods !== void 0 ? _config$mods : null;
312    } // Apply static json plugins, should be done after _internal
313
314
315    configWithDefaultValues.exp = (0, _withConfigPlugins().withConfigPlugins)(configWithDefaultValues.exp, !!options.skipPlugins);
316
317    if (!options.isModdedConfig) {
318      // @ts-ignore: Delete mods added by static plugins when they won't have a chance to be evaluated
319      delete configWithDefaultValues.exp.mods;
320    }
321
322    if (options.isPublicConfig) {
323      var _configWithDefaultVal, _configWithDefaultVal2, _configWithDefaultVal3, _configWithDefaultVal4;
324
325      // Remove internal values with references to user's file paths from the public config.
326      delete configWithDefaultValues.exp._internal;
327
328      if (configWithDefaultValues.exp.hooks) {
329        delete configWithDefaultValues.exp.hooks;
330      }
331
332      if ((_configWithDefaultVal = configWithDefaultValues.exp.ios) !== null && _configWithDefaultVal !== void 0 && _configWithDefaultVal.config) {
333        delete configWithDefaultValues.exp.ios.config;
334      }
335
336      if ((_configWithDefaultVal2 = configWithDefaultValues.exp.android) !== null && _configWithDefaultVal2 !== void 0 && _configWithDefaultVal2.config) {
337        delete configWithDefaultValues.exp.android.config;
338      } // These value will be overwritten when the manifest is being served from the host (i.e. not completely accurate).
339      // @ts-ignore: currentFullName not on type yet.
340
341
342      configWithDefaultValues.exp.currentFullName = (0, _getFullName().getFullName)(configWithDefaultValues.exp); // @ts-ignore: originalFullName not on type yet.
343
344      configWithDefaultValues.exp.originalFullName = (0, _getFullName().getFullName)(configWithDefaultValues.exp);
345      (_configWithDefaultVal3 = configWithDefaultValues.exp.updates) === null || _configWithDefaultVal3 === void 0 ? true : delete _configWithDefaultVal3.codeSigningCertificate;
346      (_configWithDefaultVal4 = configWithDefaultValues.exp.updates) === null || _configWithDefaultVal4 === void 0 ? true : delete _configWithDefaultVal4.codeSigningMetadata;
347    }
348
349    return configWithDefaultValues;
350  } // Fill in the static config
351
352
353  function getContextConfig(config) {
354    return ensureConfigHasDefaultValues({
355      projectRoot,
356      exp: config.expo,
357      pkg: packageJson,
358      skipSDKVersionRequirement: true,
359      paths,
360      packageJsonPath
361    }).exp;
362  }
363
364  if (paths.dynamicConfigPath) {
365    // No app.config.json or app.json but app.config.js
366    const {
367      exportedObjectType,
368      config: rawDynamicConfig
369    } = (0, _getConfig().getDynamicConfig)(paths.dynamicConfigPath, {
370      projectRoot,
371      staticConfigPath: paths.staticConfigPath,
372      packageJsonPath,
373      config: getContextConfig(staticConfig)
374    }); // Allow for the app.config.js to `export default null;`
375    // Use `dynamicConfigPath` to detect if a dynamic config exists.
376
377    const dynamicConfig = reduceExpoObject(rawDynamicConfig) || {};
378    return fillAndReturnConfig(dynamicConfig, exportedObjectType);
379  } // No app.config.js but json or no config
380
381
382  return fillAndReturnConfig(staticConfig || {}, null);
383}
384
385function getPackageJson(projectRoot) {
386  const [pkg] = getPackageJsonAndPath(projectRoot);
387  return pkg;
388}
389
390function getPackageJsonAndPath(projectRoot) {
391  const packageJsonPath = (0, _resolvePackageJson().getRootPackageJsonPath)(projectRoot);
392  return [_jsonFile().default.read(packageJsonPath), packageJsonPath];
393}
394
395function readConfigJson(projectRoot, skipValidation = false, skipSDKVersionRequirement = false) {
396  const paths = getConfigFilePaths(projectRoot);
397  const rawStaticConfig = paths.staticConfigPath ? (0, _getConfig().getStaticConfig)(paths.staticConfigPath) : null;
398
399  const getConfigName = () => {
400    if (paths.staticConfigPath) return ` \`${_path().default.basename(paths.staticConfigPath)}\``;
401    return '';
402  };
403
404  let outputRootConfig = rawStaticConfig;
405
406  if (outputRootConfig === null || typeof outputRootConfig !== 'object') {
407    if (skipValidation) {
408      outputRootConfig = {
409        expo: {}
410      };
411    } else {
412      throw new (_Errors().ConfigError)(`Project at path ${_path().default.resolve(projectRoot)} does not contain a valid Expo config${getConfigName()}`, 'NOT_OBJECT');
413    }
414  }
415
416  let exp = outputRootConfig.expo;
417
418  if (exp === null || typeof exp !== 'object') {
419    throw new (_Errors().ConfigError)(`Property 'expo' in${getConfigName()} for project at path ${_path().default.resolve(projectRoot)} is not an object. Please make sure${getConfigName()} includes a managed Expo app config like this: ${APP_JSON_EXAMPLE}`, 'NO_EXPO');
420  }
421
422  exp = { ...exp
423  };
424  const [pkg, packageJsonPath] = getPackageJsonAndPath(projectRoot);
425  return { ...ensureConfigHasDefaultValues({
426      projectRoot,
427      exp,
428      pkg,
429      skipSDKVersionRequirement,
430      paths,
431      packageJsonPath
432    }),
433    mods: null,
434    dynamicConfigObjectType: null,
435    rootConfig: { ...outputRootConfig
436    },
437    ...paths
438  };
439}
440/**
441 * Get the static and dynamic config paths for a project. Also accounts for custom paths.
442 *
443 * @param projectRoot
444 */
445
446
447function getConfigFilePaths(projectRoot) {
448  const customPaths = getCustomConfigFilePaths(projectRoot);
449
450  if (customPaths) {
451    return customPaths;
452  }
453
454  return {
455    dynamicConfigPath: getDynamicConfigFilePath(projectRoot),
456    staticConfigPath: getStaticConfigFilePath(projectRoot)
457  };
458}
459
460function getCustomConfigFilePaths(projectRoot) {
461  if (!customConfigPaths[projectRoot]) {
462    return null;
463  } // If the user picks a custom config path, we will only use that and skip searching for a secondary config.
464
465
466  if (isDynamicFilePath(customConfigPaths[projectRoot])) {
467    return {
468      dynamicConfigPath: customConfigPaths[projectRoot],
469      staticConfigPath: null
470    };
471  } // Anything that's not js or ts will be treated as json.
472
473
474  return {
475    staticConfigPath: customConfigPaths[projectRoot],
476    dynamicConfigPath: null
477  };
478}
479
480function getDynamicConfigFilePath(projectRoot) {
481  for (const fileName of ['app.config.ts', 'app.config.js']) {
482    const configPath = _path().default.join(projectRoot, fileName);
483
484    if (_fs().default.existsSync(configPath)) {
485      return configPath;
486    }
487  }
488
489  return null;
490}
491
492function getStaticConfigFilePath(projectRoot) {
493  for (const fileName of ['app.config.json', 'app.json']) {
494    const configPath = _path().default.join(projectRoot, fileName);
495
496    if (_fs().default.existsSync(configPath)) {
497      return configPath;
498    }
499  }
500
501  return null;
502} // TODO: This should account for dynamic configs
503
504
505function findConfigFile(projectRoot) {
506  let configPath; // Check for a custom config path first.
507
508  if (customConfigPaths[projectRoot]) {
509    configPath = customConfigPaths[projectRoot]; // We shouldn't verify if the file exists because
510    // the user manually specified that this path should be used.
511
512    return {
513      configPath,
514      configName: _path().default.basename(configPath),
515      configNamespace: 'expo'
516    };
517  } else {
518    // app.config.json takes higher priority over app.json
519    configPath = _path().default.join(projectRoot, 'app.config.json');
520
521    if (!_fs().default.existsSync(configPath)) {
522      configPath = _path().default.join(projectRoot, 'app.json');
523    }
524  }
525
526  return {
527    configPath,
528    configName: _path().default.basename(configPath),
529    configNamespace: 'expo'
530  };
531} // TODO: deprecate
532
533
534function configFilename(projectRoot) {
535  return findConfigFile(projectRoot).configName;
536}
537
538async function readExpRcAsync(projectRoot) {
539  const expRcPath = _path().default.join(projectRoot, '.exprc');
540
541  return await _jsonFile().default.readAsync(expRcPath, {
542    json5: true,
543    cantReadFileDefault: {}
544  });
545}
546
547const customConfigPaths = {};
548
549function resetCustomConfigPaths() {
550  for (const key of Object.keys(customConfigPaths)) {
551    delete customConfigPaths[key];
552  }
553}
554
555function setCustomConfigPath(projectRoot, configPath) {
556  customConfigPaths[projectRoot] = configPath;
557}
558/**
559 * Attempt to modify an Expo project config.
560 * This will only fully work if the project is using static configs only.
561 * Otherwise 'warn' | 'fail' will return with a message about why the config couldn't be updated.
562 * The potentially modified config object will be returned for testing purposes.
563 *
564 * @param projectRoot
565 * @param modifications modifications to make to an existing config
566 * @param readOptions options for reading the current config file
567 * @param writeOptions If true, the static config file will not be rewritten
568 */
569
570
571async function modifyConfigAsync(projectRoot, modifications, readOptions = {}, writeOptions = {}) {
572  const config = getConfig(projectRoot, readOptions);
573
574  if (config.dynamicConfigPath) {
575    // We cannot automatically write to a dynamic config.
576
577    /* Currently we should just use the safest approach possible, informing the user that they'll need to manually modify their dynamic config.
578     if (config.staticConfigPath) {
579      // Both a dynamic and a static config exist.
580      if (config.dynamicConfigObjectType === 'function') {
581        // The dynamic config exports a function, this means it possibly extends the static config.
582      } else {
583        // Dynamic config ignores the static config, there isn't a reason to automatically write to it.
584        // Instead we should warn the user to add values to their dynamic config.
585      }
586    }
587    */
588    return {
589      type: 'warn',
590      message: `Cannot automatically write to dynamic config at: ${_path().default.relative(projectRoot, config.dynamicConfigPath)}`,
591      config: null
592    };
593  } else if (config.staticConfigPath) {
594    // Static with no dynamic config, this means we can append to the config automatically.
595    let outputConfig; // If the config has an expo object (app.json) then append the options to that object.
596
597    if (config.rootConfig.expo) {
598      outputConfig = { ...config.rootConfig,
599        expo: { ...config.rootConfig.expo,
600          ...modifications
601        }
602      };
603    } else {
604      // Otherwise (app.config.json) just add the config modification to the top most level.
605      outputConfig = { ...config.rootConfig,
606        ...modifications
607      };
608    }
609
610    if (!writeOptions.dryRun) {
611      await _jsonFile().default.writeAsync(config.staticConfigPath, outputConfig, {
612        json5: false
613      });
614    }
615
616    return {
617      type: 'success',
618      config: outputConfig
619    };
620  }
621
622  return {
623    type: 'fail',
624    message: 'No config exists',
625    config: null
626  };
627}
628
629const APP_JSON_EXAMPLE = JSON.stringify({
630  expo: {
631    name: 'My app',
632    slug: 'my-app',
633    sdkVersion: '...'
634  }
635});
636
637function ensureConfigHasDefaultValues({
638  projectRoot,
639  exp,
640  pkg,
641  paths,
642  packageJsonPath,
643  skipSDKVersionRequirement = false
644}) {
645  var _exp$name, _exp$slug, _exp$version;
646
647  if (!exp) {
648    exp = {};
649  }
650
651  exp = (0, _withInternal().withInternal)(exp, {
652    projectRoot,
653    ...(paths !== null && paths !== void 0 ? paths : {}),
654    packageJsonPath
655  }); // Defaults for package.json fields
656
657  const pkgName = typeof pkg.name === 'string' ? pkg.name : _path().default.basename(projectRoot);
658  const pkgVersion = typeof pkg.version === 'string' ? pkg.version : '1.0.0';
659  const pkgWithDefaults = { ...pkg,
660    name: pkgName,
661    version: pkgVersion
662  }; // Defaults for app.json/app.config.js fields
663
664  const name = (_exp$name = exp.name) !== null && _exp$name !== void 0 ? _exp$name : pkgName;
665  const slug = (_exp$slug = exp.slug) !== null && _exp$slug !== void 0 ? _exp$slug : (0, _slugify().default)(name.toLowerCase());
666  const version = (_exp$version = exp.version) !== null && _exp$version !== void 0 ? _exp$version : pkgVersion;
667  let description = exp.description;
668
669  if (!description && typeof pkg.description === 'string') {
670    description = pkg.description;
671  }
672
673  const expWithDefaults = { ...exp,
674    name,
675    slug,
676    version,
677    description
678  };
679  let sdkVersion;
680
681  try {
682    sdkVersion = (0, _Project().getExpoSDKVersion)(projectRoot, expWithDefaults);
683  } catch (error) {
684    if (!skipSDKVersionRequirement) throw error;
685  }
686
687  let platforms = exp.platforms;
688
689  if (!platforms) {
690    platforms = getSupportedPlatforms(projectRoot);
691  }
692
693  return {
694    exp: { ...expWithDefaults,
695      sdkVersion,
696      platforms
697    },
698    pkg: pkgWithDefaults
699  };
700}
701
702async function writeConfigJsonAsync(projectRoot, options) {
703  const paths = getConfigFilePaths(projectRoot);
704  let {
705    exp,
706    pkg,
707    rootConfig,
708    dynamicConfigObjectType
709  } = readConfigJson(projectRoot);
710  exp = { ...rootConfig.expo,
711    ...options
712  };
713  rootConfig = { ...rootConfig,
714    expo: exp
715  };
716
717  if (paths.staticConfigPath) {
718    await _jsonFile().default.writeAsync(paths.staticConfigPath, rootConfig, {
719      json5: false
720    });
721  } else {
722    console.log('Failed to write to config: ', options);
723  }
724
725  return {
726    exp,
727    pkg,
728    rootConfig,
729    dynamicConfigObjectType,
730    ...paths
731  };
732}
733
734const DEFAULT_BUILD_PATH = `web-build`;
735
736function getWebOutputPath(config = {}) {
737  var _expo$web, _expo$web$build;
738
739  if (process.env.WEBPACK_BUILD_OUTPUT_PATH) {
740    return process.env.WEBPACK_BUILD_OUTPUT_PATH;
741  }
742
743  const expo = config.expo || config || {};
744  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;
745}
746
747function getNameFromConfig(exp = {}) {
748  // For RN CLI support
749  const appManifest = exp.expo || exp;
750  const {
751    web = {}
752  } = appManifest; // rn-cli apps use a displayName value as well.
753
754  const appName = exp.displayName || appManifest.displayName || appManifest.name;
755  const webName = web.name || appName;
756  return {
757    appName,
758    webName
759  };
760}
761
762function getDefaultTarget(projectRoot, exp) {
763  var _exp;
764
765  (_exp = exp) !== null && _exp !== void 0 ? _exp : exp = getConfig(projectRoot, {
766    skipSDKVersionRequirement: true
767  }).exp; // before SDK 37, always default to managed to preserve previous behavior
768
769  if (exp.sdkVersion && exp.sdkVersion !== 'UNVERSIONED' && _semver().default.lt(exp.sdkVersion, '37.0.0')) {
770    return 'managed';
771  }
772
773  return isBareWorkflowProject(projectRoot) ? 'bare' : 'managed';
774}
775
776function isBareWorkflowProject(projectRoot) {
777  const [pkg] = getPackageJsonAndPath(projectRoot);
778
779  if (pkg.dependencies && pkg.dependencies.expokit) {
780    return false;
781  }
782
783  const xcodeprojFiles = (0, _glob().sync)('ios/**/*.xcodeproj', {
784    absolute: true,
785    cwd: projectRoot
786  });
787
788  if (xcodeprojFiles.length) {
789    return true;
790  }
791
792  const gradleFiles = (0, _glob().sync)('android/**/*.gradle', {
793    absolute: true,
794    cwd: projectRoot
795  });
796
797  if (gradleFiles.length) {
798    return true;
799  }
800
801  return false;
802}
803/**
804 * true if the file is .js or .ts
805 *
806 * @param filePath
807 */
808
809
810function isDynamicFilePath(filePath) {
811  return !!filePath.match(/\.[j|t]s$/);
812}
813/**
814 * Return a useful name describing the project config.
815 * - dynamic: app.config.js
816 * - static: app.json
817 * - custom path app config relative to root folder
818 * - both: app.config.js or app.json
819 */
820
821
822function getProjectConfigDescription(projectRoot) {
823  const paths = getConfigFilePaths(projectRoot);
824  return getProjectConfigDescriptionWithPaths(projectRoot, paths);
825}
826/**
827 * Returns a string describing the configurations used for the given project root.
828 * Will return null if no config is found.
829 *
830 * @param projectRoot
831 * @param projectConfig
832 */
833
834
835function getProjectConfigDescriptionWithPaths(projectRoot, projectConfig) {
836  if (projectConfig.dynamicConfigPath) {
837    const relativeDynamicConfigPath = _path().default.relative(projectRoot, projectConfig.dynamicConfigPath);
838
839    if (projectConfig.staticConfigPath) {
840      return `${relativeDynamicConfigPath} or ${_path().default.relative(projectRoot, projectConfig.staticConfigPath)}`;
841    }
842
843    return relativeDynamicConfigPath;
844  } else if (projectConfig.staticConfigPath) {
845    return _path().default.relative(projectRoot, projectConfig.staticConfigPath);
846  } // If a config doesn't exist, our tooling will generate a static app.json
847
848
849  return 'app.json';
850}
851//# sourceMappingURL=Config.js.map