UNPKG

19.2 kBJavaScriptView Raw
1"use strict";
2var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3 function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4 return new (P || (P = Promise))(function (resolve, reject) {
5 function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6 function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7 function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8 step((generator = generator.apply(thisArg, _arguments || [])).next());
9 });
10};
11var __importDefault = (this && this.__importDefault) || function (mod) {
12 return (mod && mod.__esModule) ? mod : { "default": mod };
13};
14Object.defineProperty(exports, "__esModule", { value: true });
15const json_file_1 = __importDefault(require("@expo/json-file"));
16const fs_extra_1 = __importDefault(require("fs-extra"));
17const globby_1 = __importDefault(require("globby"));
18const path_1 = __importDefault(require("path"));
19const semver_1 = __importDefault(require("semver"));
20const slugify_1 = __importDefault(require("slugify"));
21const Errors_1 = require("./Errors");
22const getConfig_1 = require("./getConfig");
23const Modules_1 = require("./Modules");
24const Project_1 = require("./Project");
25/**
26 * If a config has an `expo` object then that will be used as the config.
27 * This method reduces out other top level values if an `expo` object exists.
28 *
29 * @param config Input config object to reduce
30 */
31function reduceExpoObject(config) {
32 if (!config)
33 return config === undefined ? null : config;
34 if (typeof config.expo === 'object') {
35 // TODO: We should warn users in the future that if there are more values than "expo", those values outside of "expo" will be omitted in favor of the "expo" object.
36 return config.expo;
37 }
38 return config;
39}
40/**
41 * Get all platforms that a project is currently capable of running.
42 *
43 * @param projectRoot
44 * @param exp
45 */
46function getSupportedPlatforms(projectRoot, exp) {
47 const platforms = [];
48 if (Modules_1.projectHasModule('react-native', projectRoot, exp)) {
49 platforms.push('ios', 'android');
50 }
51 if (Modules_1.projectHasModule('react-native-web', projectRoot, exp)) {
52 platforms.push('web');
53 }
54 return platforms;
55}
56/**
57 * Evaluate the config for an Expo project.
58 * If a function is exported from the `app.config.js` then a partial config will be passed as an argument.
59 * The partial config is composed from any existing app.json, and certain fields from the `package.json` like name and description.
60 *
61 *
62 * **Example**
63 * ```js
64 * module.exports = function({ config }) {
65 * // mutate the config before returning it.
66 * config.slug = 'new slug'
67 * return config;
68 * }
69 *
70 * **Supports**
71 * - `app.config.ts`
72 * - `app.config.js`
73 * - `app.config.json`
74 * - `app.json`
75 *
76 * @param projectRoot the root folder containing all of your application code
77 * @param options enforce criteria for a project config
78 */
79function getConfig(projectRoot, options = {}) {
80 const paths = getConfigFilePaths(projectRoot);
81 const rawStaticConfig = paths.staticConfigPath ? getConfig_1.getStaticConfig(paths.staticConfigPath) : null;
82 // For legacy reasons, always return an object.
83 const rootConfig = (rawStaticConfig || {});
84 const staticConfig = reduceExpoObject(rawStaticConfig) || {};
85 const jsonFileWithNodeModulesPath = reduceExpoObject(rootConfig);
86 // Can only change the package.json location if an app.json or app.config.json exists with nodeModulesPath
87 const [packageJson, packageJsonPath] = getPackageJsonAndPath(projectRoot, jsonFileWithNodeModulesPath);
88 function fillAndReturnConfig(config, dynamicConfigObjectType) {
89 return Object.assign(Object.assign({}, ensureConfigHasDefaultValues(projectRoot, config, packageJson, options.skipSDKVersionRequirement)), { dynamicConfigObjectType,
90 rootConfig, dynamicConfigPath: paths.dynamicConfigPath, staticConfigPath: paths.staticConfigPath });
91 }
92 // Fill in the static config
93 function getContextConfig(config = {}) {
94 return ensureConfigHasDefaultValues(projectRoot, config, packageJson, true).exp;
95 }
96 if (paths.dynamicConfigPath) {
97 // No app.config.json or app.json but app.config.js
98 const { exportedObjectType, config: rawDynamicConfig } = getConfig_1.getDynamicConfig(paths.dynamicConfigPath, {
99 projectRoot,
100 staticConfigPath: paths.staticConfigPath,
101 packageJsonPath,
102 config: getContextConfig(staticConfig),
103 });
104 // Allow for the app.config.js to `export default null;`
105 // Use `dynamicConfigPath` to detect if a dynamic config exists.
106 const dynamicConfig = reduceExpoObject(rawDynamicConfig) || {};
107 return fillAndReturnConfig(dynamicConfig, exportedObjectType);
108 }
109 // No app.config.js but json or no config
110 return fillAndReturnConfig(staticConfig || {}, null);
111}
112exports.getConfig = getConfig;
113function getPackageJson(projectRoot, config = {}) {
114 const [pkg] = getPackageJsonAndPath(projectRoot, config);
115 return pkg;
116}
117exports.getPackageJson = getPackageJson;
118function getPackageJsonAndPath(projectRoot, config = {}) {
119 const packageJsonPath = Modules_1.getRootPackageJsonPath(projectRoot, config);
120 return [json_file_1.default.read(packageJsonPath), packageJsonPath];
121}
122function readConfigJson(projectRoot, skipValidation = false, skipNativeValidation = false) {
123 const paths = getConfigFilePaths(projectRoot);
124 const rawStaticConfig = paths.staticConfigPath ? getConfig_1.getStaticConfig(paths.staticConfigPath) : null;
125 const getConfigName = () => {
126 if (paths.staticConfigPath)
127 ` \`${path_1.default.basename(paths.staticConfigPath)}\``;
128 return '';
129 };
130 let outputRootConfig = rawStaticConfig;
131 if (outputRootConfig === null || typeof outputRootConfig !== 'object') {
132 if (skipValidation) {
133 outputRootConfig = { expo: {} };
134 }
135 else {
136 throw new Errors_1.ConfigError(`Project at path ${path_1.default.resolve(projectRoot)} does not contain a valid Expo config${getConfigName()}`, 'NOT_OBJECT');
137 }
138 }
139 let exp = outputRootConfig.expo;
140 if (exp === null || typeof exp !== 'object') {
141 throw new Errors_1.ConfigError(`Property 'expo' in${getConfigName()} for project at path ${path_1.default.resolve(projectRoot)} is not an object. Please make sure${getConfigName()} includes a managed Expo app config like this: ${APP_JSON_EXAMPLE}`, 'NO_EXPO');
142 }
143 exp = Object.assign({}, exp);
144 const [pkg] = getPackageJsonAndPath(projectRoot, exp);
145 return Object.assign(Object.assign(Object.assign({}, ensureConfigHasDefaultValues(projectRoot, exp, pkg, skipNativeValidation)), { dynamicConfigPath: null, dynamicConfigObjectType: null, rootConfig: Object.assign({}, outputRootConfig) }), paths);
146}
147exports.readConfigJson = readConfigJson;
148function readConfigJsonAsync(projectRoot, skipValidation = false, skipNativeValidation = false) {
149 return __awaiter(this, void 0, void 0, function* () {
150 return readConfigJson(projectRoot, skipValidation, skipNativeValidation);
151 });
152}
153exports.readConfigJsonAsync = readConfigJsonAsync;
154/**
155 * Get the static and dynamic config paths for a project. Also accounts for custom paths.
156 *
157 * @param projectRoot
158 */
159function getConfigFilePaths(projectRoot) {
160 const customPaths = getCustomConfigFilePaths(projectRoot);
161 if (customPaths) {
162 return customPaths;
163 }
164 return {
165 dynamicConfigPath: getDynamicConfigFilePath(projectRoot),
166 staticConfigPath: getStaticConfigFilePath(projectRoot),
167 };
168}
169exports.getConfigFilePaths = getConfigFilePaths;
170function getCustomConfigFilePaths(projectRoot) {
171 if (!customConfigPaths[projectRoot]) {
172 return null;
173 }
174 // If the user picks a custom config path, we will only use that and skip searching for a secondary config.
175 if (isDynamicFilePath(customConfigPaths[projectRoot])) {
176 return {
177 dynamicConfigPath: customConfigPaths[projectRoot],
178 staticConfigPath: null,
179 };
180 }
181 // Anything that's not js or ts will be treated as json.
182 return { staticConfigPath: customConfigPaths[projectRoot], dynamicConfigPath: null };
183}
184function getDynamicConfigFilePath(projectRoot) {
185 for (const fileName of ['app.config.ts', 'app.config.js']) {
186 const configPath = path_1.default.join(projectRoot, fileName);
187 if (fs_extra_1.default.existsSync(configPath)) {
188 return configPath;
189 }
190 }
191 return null;
192}
193function getStaticConfigFilePath(projectRoot) {
194 for (const fileName of ['app.config.json', 'app.json']) {
195 const configPath = path_1.default.join(projectRoot, fileName);
196 if (fs_extra_1.default.existsSync(configPath)) {
197 return configPath;
198 }
199 }
200 return null;
201}
202// TODO: This should account for dynamic configs
203function findConfigFile(projectRoot) {
204 let configPath;
205 // Check for a custom config path first.
206 if (customConfigPaths[projectRoot]) {
207 configPath = customConfigPaths[projectRoot];
208 // We shouldn't verify if the file exists because
209 // the user manually specified that this path should be used.
210 return {
211 configPath,
212 configName: path_1.default.basename(configPath),
213 configNamespace: 'expo',
214 };
215 }
216 else {
217 // app.config.json takes higher priority over app.json
218 configPath = path_1.default.join(projectRoot, 'app.config.json');
219 if (!fs_extra_1.default.existsSync(configPath)) {
220 configPath = path_1.default.join(projectRoot, 'app.json');
221 }
222 }
223 return {
224 configPath,
225 configName: path_1.default.basename(configPath),
226 configNamespace: 'expo',
227 };
228}
229exports.findConfigFile = findConfigFile;
230// TODO: deprecate
231function configFilename(projectRoot) {
232 return findConfigFile(projectRoot).configName;
233}
234exports.configFilename = configFilename;
235function readExpRcAsync(projectRoot) {
236 return __awaiter(this, void 0, void 0, function* () {
237 const expRcPath = path_1.default.join(projectRoot, '.exprc');
238 return yield json_file_1.default.readAsync(expRcPath, { json5: true, cantReadFileDefault: {} });
239 });
240}
241exports.readExpRcAsync = readExpRcAsync;
242const customConfigPaths = {};
243function setCustomConfigPath(projectRoot, configPath) {
244 customConfigPaths[projectRoot] = configPath;
245}
246exports.setCustomConfigPath = setCustomConfigPath;
247/**
248 * Attempt to modify an Expo project config.
249 * This will only fully work if the project is using static configs only.
250 * Otherwise 'warn' | 'fail' will return with a message about why the config couldn't be updated.
251 * The potentially modified config object will be returned for testing purposes.
252 *
253 * @param projectRoot
254 * @param modifications modifications to make to an existing config
255 * @param readOptions options for reading the current config file
256 * @param writeOptions If true, the static config file will not be rewritten
257 */
258function modifyConfigAsync(projectRoot, modifications, readOptions = {}, writeOptions = {}) {
259 return __awaiter(this, void 0, void 0, function* () {
260 const config = getConfig(projectRoot, readOptions);
261 if (config.dynamicConfigPath) {
262 // We cannot automatically write to a dynamic config.
263 /* Currently we should just use the safest approach possible, informing the user that they'll need to manually modify their dynamic config.
264
265 if (config.staticConfigPath) {
266 // Both a dynamic and a static config exist.
267 if (config.dynamicConfigObjectType === 'function') {
268 // The dynamic config exports a function, this means it possibly extends the static config.
269 } else {
270 // Dynamic config ignores the static config, there isn't a reason to automatically write to it.
271 // Instead we should warn the user to add values to their dynamic config.
272 }
273 }
274 */
275 return {
276 type: 'warn',
277 message: `Cannot automatically write to dynamic config at: ${path_1.default.relative(projectRoot, config.dynamicConfigPath)}`,
278 config: null,
279 };
280 }
281 else if (config.staticConfigPath) {
282 // Static with no dynamic config, this means we can append to the config automatically.
283 let outputConfig;
284 // If the config has an expo object (app.json) then append the options to that object.
285 if (config.rootConfig.expo) {
286 outputConfig = Object.assign(Object.assign({}, config.rootConfig), { expo: Object.assign(Object.assign({}, config.rootConfig.expo), modifications) });
287 }
288 else {
289 // Otherwise (app.config.json) just add the config modification to the top most level.
290 outputConfig = Object.assign(Object.assign({}, config.rootConfig), modifications);
291 }
292 if (!writeOptions.dryRun) {
293 yield json_file_1.default.writeAsync(config.staticConfigPath, outputConfig, { json5: false });
294 }
295 return { type: 'success', config: outputConfig };
296 }
297 return { type: 'fail', message: 'No config exists', config: null };
298 });
299}
300exports.modifyConfigAsync = modifyConfigAsync;
301const APP_JSON_EXAMPLE = JSON.stringify({
302 expo: {
303 name: 'My app',
304 slug: 'my-app',
305 sdkVersion: '...',
306 },
307});
308function ensureConfigHasDefaultValues(projectRoot, exp, pkg, skipSDKVersionRequirement = false) {
309 if (!exp)
310 exp = {};
311 if (!exp.name) {
312 if (typeof pkg.name !== 'string') {
313 pkg.name = path_1.default.basename(projectRoot);
314 }
315 exp.name = pkg.name;
316 }
317 if (!exp.description && typeof pkg.description === 'string') {
318 exp.description = pkg.description;
319 }
320 if (!exp.slug && typeof exp.name === 'string') {
321 exp.slug = slugify_1.default(exp.name.toLowerCase());
322 }
323 if (!exp.version) {
324 if (typeof pkg.version === 'string') {
325 exp.version = pkg.version;
326 }
327 else {
328 pkg.version = '1.0.0';
329 }
330 exp.version = pkg.version;
331 }
332 if (exp.nodeModulesPath) {
333 exp.nodeModulesPath = path_1.default.resolve(projectRoot, exp.nodeModulesPath);
334 }
335 try {
336 exp.sdkVersion = Project_1.getExpoSDKVersion(projectRoot, exp);
337 }
338 catch (error) {
339 if (!skipSDKVersionRequirement)
340 throw error;
341 }
342 if (!exp.platforms) {
343 exp.platforms = getSupportedPlatforms(projectRoot, exp);
344 }
345 return { exp, pkg };
346}
347function writeConfigJsonAsync(projectRoot, options) {
348 return __awaiter(this, void 0, void 0, function* () {
349 const paths = getConfigFilePaths(projectRoot);
350 let { exp, pkg, rootConfig, dynamicConfigObjectType, staticConfigPath, } = yield readConfigJsonAsync(projectRoot);
351 exp = Object.assign(Object.assign({}, rootConfig.expo), options);
352 rootConfig = Object.assign(Object.assign({}, rootConfig), { expo: exp });
353 if (paths.staticConfigPath) {
354 yield json_file_1.default.writeAsync(paths.staticConfigPath, rootConfig, { json5: false });
355 }
356 else {
357 console.log('Failed to write to config: ', options);
358 }
359 return Object.assign({ exp,
360 pkg,
361 rootConfig,
362 staticConfigPath,
363 dynamicConfigObjectType }, paths);
364 });
365}
366exports.writeConfigJsonAsync = writeConfigJsonAsync;
367const DEFAULT_BUILD_PATH = `web-build`;
368function getWebOutputPath(config = {}) {
369 var _a, _b, _c;
370 if (process.env.WEBPACK_BUILD_OUTPUT_PATH) {
371 return process.env.WEBPACK_BUILD_OUTPUT_PATH;
372 }
373 const expo = config.expo || config || {};
374 return ((_c = (_b = (_a = expo) === null || _a === void 0 ? void 0 : _a.web) === null || _b === void 0 ? void 0 : _b.build) === null || _c === void 0 ? void 0 : _c.output) || DEFAULT_BUILD_PATH;
375}
376exports.getWebOutputPath = getWebOutputPath;
377function getNameFromConfig(exp = {}) {
378 // For RN CLI support
379 const appManifest = exp.expo || exp;
380 const { web = {} } = appManifest;
381 // rn-cli apps use a displayName value as well.
382 const appName = exp.displayName || appManifest.displayName || appManifest.name;
383 const webName = web.name || appName;
384 return {
385 appName,
386 webName,
387 };
388}
389exports.getNameFromConfig = getNameFromConfig;
390function getDefaultTarget(projectRoot) {
391 const { exp } = getConfig(projectRoot, { skipSDKVersionRequirement: true });
392 // before SDK 37, always default to managed to preserve previous behavior
393 if (exp.sdkVersion && exp.sdkVersion !== 'UNVERSIONED' && semver_1.default.lt(exp.sdkVersion, '37.0.0')) {
394 return 'managed';
395 }
396 return isBareWorkflowProject(projectRoot) ? 'bare' : 'managed';
397}
398exports.getDefaultTarget = getDefaultTarget;
399function isBareWorkflowProject(projectRoot) {
400 const { pkg } = getConfig(projectRoot, {
401 skipSDKVersionRequirement: true,
402 });
403 if (pkg.dependencies && pkg.dependencies.expokit) {
404 return false;
405 }
406 if (fs_extra_1.default.existsSync(path_1.default.resolve(projectRoot, 'ios'))) {
407 const xcodeprojFiles = globby_1.default.sync([path_1.default.join(projectRoot, 'ios', '/**/*.xcodeproj')]);
408 if (xcodeprojFiles.length) {
409 return true;
410 }
411 }
412 if (fs_extra_1.default.existsSync(path_1.default.resolve(projectRoot, 'android'))) {
413 const gradleFiles = globby_1.default.sync([path_1.default.join(projectRoot, 'android', '/**/*.gradle')]);
414 if (gradleFiles.length) {
415 return true;
416 }
417 }
418 return false;
419}
420/**
421 * true if the file is .js or .ts
422 *
423 * @param filePath
424 */
425function isDynamicFilePath(filePath) {
426 return !!filePath.match(/\.[j|t]s$/);
427}
428/**
429 * Returns a string describing the configurations used for the given project root.
430 * Will return null if no config is found.
431 *
432 * @param projectRoot
433 * @param projectConfig
434 */
435function getProjectConfigDescription(projectRoot, projectConfig) {
436 if (projectConfig.dynamicConfigPath) {
437 const relativeDynamicConfigPath = path_1.default.relative(projectRoot, projectConfig.dynamicConfigPath);
438 if (projectConfig.staticConfigPath) {
439 return `Using dynamic config \`${relativeDynamicConfigPath}\` and static config \`${path_1.default.relative(projectRoot, projectConfig.staticConfigPath)}\``;
440 }
441 return `Using dynamic config \`${relativeDynamicConfigPath}\``;
442 }
443 else if (projectConfig.staticConfigPath) {
444 return `Using static config \`${path_1.default.relative(projectRoot, projectConfig.staticConfigPath)}\``;
445 }
446 return null;
447}
448exports.getProjectConfigDescription = getProjectConfigDescription;
449//# sourceMappingURL=Config.js.map
\No newline at end of file