"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const path_1 = __importDefault(require("path")); const fs_extra_1 = __importDefault(require("fs-extra")); const json_file_1 = __importDefault(require("@expo/json-file")); const resolve_from_1 = __importDefault(require("resolve-from")); const slugify_1 = __importDefault(require("slugify")); const find_yarn_workspace_root_1 = __importDefault(require("find-yarn-workspace-root")); const APP_JSON_FILE_NAME = 'app.json'; // To work with the iPhone X "notch" add `viewport-fit=cover` to the `viewport` meta tag. const DEFAULT_VIEWPORT = 'width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1.00001,viewport-fit=cover'; // Use root to work better with create-react-app const DEFAULT_ROOT_ID = `root`; const DEFAULT_BUILD_PATH = `web-build`; const DEFAULT_LANGUAGE_ISO_CODE = `en`; const DEFAULT_NO_JS_MESSAGE = `Oh no! It looks like JavaScript is not enabled in your browser.`; const DEFAULT_NAME = 'Expo App'; const DEFAULT_THEME_COLOR = '#4630EB'; const DEFAULT_DESCRIPTION = 'A Neat Expo App'; const DEFAULT_BACKGROUND_COLOR = '#ffffff'; const DEFAULT_START_URL = '.'; const DEFAULT_DISPLAY = 'standalone'; const DEFAULT_STATUS_BAR = 'default'; const DEFAULT_LANG_DIR = 'auto'; const DEFAULT_ORIENTATION = 'any'; const ICON_SIZES = [192, 512]; const MAX_SHORT_NAME_LENGTH = 12; const DEFAULT_PREFER_RELATED_APPLICATIONS = true; function isUsingYarn(projectRoot) { const workspaceRoot = find_yarn_workspace_root_1.default(projectRoot); if (workspaceRoot) { return fs_extra_1.default.existsSync(path_1.default.join(workspaceRoot, 'yarn.lock')); } else { return fs_extra_1.default.existsSync(path_1.default.join(projectRoot, 'yarn.lock')); } } exports.isUsingYarn = isUsingYarn; function fileExistsAsync(file) { return __awaiter(this, void 0, void 0, function* () { try { return (yield fs_extra_1.default.stat(file)).isFile(); } catch (e) { return false; } }); } exports.fileExistsAsync = fileExistsAsync; function fileExists(file) { try { return fs_extra_1.default.statSync(file).isFile(); } catch (e) { return false; } } exports.fileExists = fileExists; function resolveModule(request, projectRoot, exp) { const fromDir = exp.nodeModulesPath ? exp.nodeModulesPath : projectRoot; return resolve_from_1.default(fromDir, request); } exports.resolveModule = resolveModule; // DEPRECATED: Use findConfigFile function findConfigFileAsync(projectRoot) { return __awaiter(this, void 0, void 0, function* () { return findConfigFile(projectRoot); }); } exports.findConfigFileAsync = findConfigFileAsync; function findConfigFile(projectRoot) { let configPath; if (customConfigPaths[projectRoot]) { configPath = customConfigPaths[projectRoot]; } else { configPath = path_1.default.join(projectRoot, APP_JSON_FILE_NAME); } return { configPath, configName: APP_JSON_FILE_NAME, configNamespace: 'expo' }; } exports.findConfigFile = findConfigFile; // DEPRECATED: Use configFilename function configFilenameAsync(projectRoot) { return __awaiter(this, void 0, void 0, function* () { return findConfigFile(projectRoot).configName; }); } exports.configFilenameAsync = configFilenameAsync; function configFilename(projectRoot) { return findConfigFile(projectRoot).configName; } exports.configFilename = configFilename; function readExpRcAsync(projectRoot) { return __awaiter(this, void 0, void 0, function* () { const expRcPath = path_1.default.join(projectRoot, '.exprc'); return yield json_file_1.default.readAsync(expRcPath, { json5: true, cantReadFileDefault: {} }); }); } exports.readExpRcAsync = readExpRcAsync; const customConfigPaths = {}; function setCustomConfigPath(projectRoot, configPath) { customConfigPaths[projectRoot] = configPath; } exports.setCustomConfigPath = setCustomConfigPath; function createEnvironmentConstants(appManifest, pwaManifestLocation) { let web; try { web = require(pwaManifestLocation); } catch (e) { web = {}; } return Object.assign({}, appManifest, { name: appManifest.displayName || appManifest.name, /** * Omit app.json properties that get removed during the native turtle build * * `facebookScheme` * `facebookAppId` * `facebookDisplayName` */ facebookScheme: undefined, facebookAppId: undefined, facebookDisplayName: undefined, // Remove iOS and Android. ios: undefined, android: undefined, // Use the PWA `manifest.json` as the native web manifest. web }); } exports.createEnvironmentConstants = createEnvironmentConstants; function sanitizePublicPath(publicPath) { if (typeof publicPath !== 'string' || !publicPath.length) { return '/'; } if (publicPath.endsWith('/')) { return publicPath; } return publicPath + '/'; } function getConfigForPWA(projectRoot, getAbsolutePath, options) { const { exp } = readConfigJson(projectRoot, true); return ensurePWAConfig(exp, getAbsolutePath, options); } exports.getConfigForPWA = getConfigForPWA; function getNameFromConfig(exp = {}) { // For RN CLI support const appManifest = exp.expo || exp; const { web = {} } = appManifest; // rn-cli apps use a displayName value as well. const appName = exp.displayName || appManifest.displayName || appManifest.name || DEFAULT_NAME; const webName = web.name || appName; return { appName, webName, }; } exports.getNameFromConfig = getNameFromConfig; function validateShortName(shortName) { return __awaiter(this, void 0, void 0, function* () { // Validate short name if (shortName.length > MAX_SHORT_NAME_LENGTH) { console.warn(`PWA short name should be 12 characters or less, otherwise it'll be curtailed on the mobile device homepage. You should define web.shortName in your ${APP_JSON_FILE_NAME} as a string that is ${MAX_SHORT_NAME_LENGTH} or less characters.`); } }); } exports.validateShortName = validateShortName; // Convert expo value to PWA value function ensurePWAorientation(orientation) { if (orientation && typeof orientation === 'string') { let webOrientation = orientation.toLowerCase(); if (webOrientation !== 'default') { return webOrientation; } } return DEFAULT_ORIENTATION; } function getWebManifestFromConfig(config = {}) { const appManifest = config.expo || config || {}; return appManifest.web || {}; } function getWebOutputPath(config = {}) { const web = getWebManifestFromConfig(config); const { build = {} } = web; return build.output || DEFAULT_BUILD_PATH; } exports.getWebOutputPath = getWebOutputPath; function applyWebDefaults(appJSON) { // For RN CLI support const appManifest = appJSON.expo || appJSON || {}; const { web: webManifest = {}, splash = {}, ios = {}, android = {} } = appManifest; const { build: webBuild = {}, webDangerous = {}, meta = {} } = webManifest; const { apple = {} } = meta; // rn-cli apps use a displayName value as well. const { appName, webName } = getNameFromConfig(appJSON); const languageISOCode = webManifest.lang || DEFAULT_LANGUAGE_ISO_CODE; const noJavaScriptMessage = webDangerous.noJavaScriptMessage || DEFAULT_NO_JS_MESSAGE; const rootId = webBuild.rootId || DEFAULT_ROOT_ID; const buildOutputPath = getWebOutputPath(appJSON); const publicPath = sanitizePublicPath(webManifest.publicPath); const primaryColor = appManifest.primaryColor || DEFAULT_THEME_COLOR; const description = appManifest.description || DEFAULT_DESCRIPTION; // The theme_color sets the color of the tool bar, and may be reflected in the app's preview in task switchers. const webThemeColor = webManifest.themeColor || primaryColor; const dir = webManifest.dir || DEFAULT_LANG_DIR; const shortName = webManifest.shortName || webName; const display = webManifest.display || DEFAULT_DISPLAY; const startUrl = webManifest.startUrl || DEFAULT_START_URL; const webViewport = meta.viewport || DEFAULT_VIEWPORT; const { scope, crossorigin } = webManifest; const barStyle = apple.barStyle || webManifest.barStyle || DEFAULT_STATUS_BAR; const orientation = ensurePWAorientation(webManifest.orientation || appManifest.orientation); /** * **Splash screen background color** * `https://developers.google.com/web/fundamentals/web-app-manifest/#splash-screen` * The background_color should be the same color as the load page, * to provide a smooth transition from the splash screen to your app. */ const backgroundColor = webManifest.backgroundColor || splash.backgroundColor || DEFAULT_BACKGROUND_COLOR; // No default background color /** * * https://developer.mozilla.org/en-US/docs/Web/Manifest#prefer_related_applications * Specifies a boolean value that hints for the user agent to indicate * to the user that the specified native applications (see below) are recommended over the website. * This should only be used if the related native apps really do offer something that the website can't... like Expo ;) * * >> The banner won't show up if the app is already installed: * https://github.com/GoogleChrome/samples/issues/384#issuecomment-326387680 */ const preferRelatedApplications = webManifest.preferRelatedApplications || DEFAULT_PREFER_RELATED_APPLICATIONS; const relatedApplications = inferWebRelatedApplicationsFromConfig(appManifest); return Object.assign({}, appManifest, { name: appName, description, primaryColor, // Ensure these objects exist ios: Object.assign({}, ios), android: Object.assign({}, android), web: Object.assign({}, webManifest, { meta: Object.assign({}, meta, { apple: Object.assign({}, apple, { formatDetection: apple.formatDetection || 'telephone=no', mobileWebAppCapable: apple.mobileWebAppCapable || 'yes', touchFullscreen: apple.touchFullscreen || 'yes', barStyle }), viewport: webViewport }), build: Object.assign({}, webBuild, { output: buildOutputPath, rootId, publicPath }), dangerous: Object.assign({}, webDangerous, { noJavaScriptMessage }), scope, crossorigin, description, preferRelatedApplications, relatedApplications, startUrl, shortName, display, orientation, dir, barStyle, backgroundColor, themeColor: webThemeColor, lang: languageISOCode, name: webName }) }); } /** * https://developer.mozilla.org/en-US/docs/Web/Manifest#related_applications * An array of native applications that are installable by, or accessible to, the underlying platform * for example, a native Android application obtainable through the Google Play Store. * Such applications are intended to be alternatives to the * website that provides similar/equivalent functionality — like the native app version of the website. */ function inferWebRelatedApplicationsFromConfig({ web = {}, ios = {}, android = {} }) { const relatedApplications = web.relatedApplications || []; const { bundleIdentifier, appStoreUrl } = ios; if (bundleIdentifier) { const PLATFORM_ITUNES = 'itunes'; let iosApp = relatedApplications.some(({ platform }) => platform === PLATFORM_ITUNES); if (!iosApp) { relatedApplications.push({ platform: PLATFORM_ITUNES, url: appStoreUrl, id: bundleIdentifier, }); } } const { package: androidPackage, playStoreUrl } = android; if (androidPackage) { const PLATFORM_PLAY = 'play'; const alreadyHasAndroidApp = relatedApplications.some(({ platform }) => platform === PLATFORM_PLAY); if (!alreadyHasAndroidApp) { relatedApplications.push({ platform: PLATFORM_PLAY, url: playStoreUrl || `http://play.google.com/store/apps/details?id=${androidPackage}`, id: androidPackage, }); } } return relatedApplications; } function inferWebHomescreenIcons(config = {}, getAbsolutePath, options) { const { web = {}, ios = {} } = config; if (Array.isArray(web.icons)) { return web.icons; } let icons = []; let icon; if (web.icon || config.icon) { icon = getAbsolutePath(web.icon || config.icon); } else { // Use template icon icon = options.templateIcon; } const destination = `apple/icons`; icons.push({ src: icon, size: ICON_SIZES, destination }); const iOSIcon = config.icon || ios.icon; if (iOSIcon) { const iOSIconPath = getAbsolutePath(iOSIcon); icons.push({ ios: true, sizes: 180, src: iOSIconPath, destination, }); } return icons; } function inferWebStartupImages(config, getAbsolutePath, options) { const { icon, web = {}, splash = {}, primaryColor } = config; if (Array.isArray(web.startupImages)) { return web.startupImages; } const { splash: webSplash = {} } = web; let startupImages = []; let splashImageSource; const possibleIconSrc = webSplash.image || splash.image || icon; if (possibleIconSrc) { const resizeMode = webSplash.resizeMode || splash.resizeMode || 'contain'; const backgroundColor = webSplash.backgroundColor || splash.backgroundColor || primaryColor || '#ffffff'; splashImageSource = getAbsolutePath(possibleIconSrc); startupImages.push({ resizeMode, color: backgroundColor, src: splashImageSource, supportsTablet: webSplash.supportsTablet === undefined ? true : webSplash.supportsTablet, orientation: web.orientation, destination: `apple/splash`, }); } return startupImages; } function ensurePWAConfig(appJSON, getAbsolutePath, options) { const config = applyWebDefaults(appJSON); if (getAbsolutePath) { config.web.icons = inferWebHomescreenIcons(config, getAbsolutePath, options); config.web.startupImages = inferWebStartupImages(config, getAbsolutePath, options); } return config; } exports.ensurePWAConfig = ensurePWAConfig; class ConfigError extends Error { constructor(message, code) { super(message); this.code = code; } } const APP_JSON_EXAMPLE = JSON.stringify({ expo: { name: 'My app', slug: 'my-app', sdkVersion: '...', }, }); function readConfigJson(projectRoot, skipValidation = false) { const { configPath } = findConfigFile(projectRoot); let rootConfig; try { rootConfig = require(configPath); } catch (error) { rootConfig = {}; } let { expo: exp } = rootConfig; if (!exp) { if (skipValidation) { exp = {}; } else { throw new ConfigError(`Property 'expo' in app.json is not an object. Please make sure app.json includes a managed Expo app config like this: ${APP_JSON_EXAMPLE}`, 'NO_EXPO'); } } const packageJsonPath = 'nodeModulesPath' in exp && typeof exp.nodeModulesPath === 'string' ? path_1.default.join(path_1.default.resolve(projectRoot, exp.nodeModulesPath), 'package.json') : path_1.default.join(projectRoot, 'package.json'); const pkg = require(packageJsonPath); if (exp && !exp.name && typeof pkg.name === 'string') { exp.name = pkg.name; } if (exp && !exp.slug && typeof exp.name === 'string') { exp.slug = slugify_1.default(exp.name.toLowerCase()); } if (exp && !exp.version) { exp.version = pkg.version; } if (exp && !exp.platforms) { exp.platforms = ['android', 'ios']; } if (exp.nodeModulesPath) { exp.nodeModulesPath = path_1.default.resolve(projectRoot, exp.nodeModulesPath); } return { exp, pkg, rootConfig: rootConfig }; } exports.readConfigJson = readConfigJson; function readConfigJsonAsync(projectRoot, skipValidation = false) { return __awaiter(this, void 0, void 0, function* () { const { configPath } = findConfigFile(projectRoot); let rootConfig = null; try { rootConfig = yield json_file_1.default.readAsync(configPath, { json5: true }); } catch (error) { } if (rootConfig === null || typeof rootConfig !== 'object') { if (skipValidation) { rootConfig = { expo: {} }; } else { throw new ConfigError('app.json must include a JSON object.', 'NOT_OBJECT'); } } const exp = rootConfig.expo; if (exp === null || typeof exp !== 'object') { throw new ConfigError(`Property 'expo' in app.json is not an object. Please make sure app.json includes a managed Expo app config like this: ${APP_JSON_EXAMPLE}`, 'NO_EXPO'); } const packageJsonPath = 'nodeModulesPath' in exp && typeof exp.nodeModulesPath === 'string' ? path_1.default.join(path_1.default.resolve(projectRoot, exp.nodeModulesPath), 'package.json') : path_1.default.join(projectRoot, 'package.json'); const pkg = yield json_file_1.default.readAsync(packageJsonPath); if (exp && !exp.name && typeof pkg.name === 'string') { exp.name = pkg.name; } if (exp && !exp.slug && typeof exp.name === 'string') { exp.slug = slugify_1.default(exp.name.toLowerCase()); } if (exp && !exp.version) { exp.version = pkg.version; } if (exp && !exp.platforms) { exp.platforms = ['android', 'ios']; } if (exp.nodeModulesPath) { exp.nodeModulesPath = path_1.default.resolve(projectRoot, exp.nodeModulesPath); } return { exp, pkg, rootConfig: rootConfig }; }); } exports.readConfigJsonAsync = readConfigJsonAsync; function writeConfigJsonAsync(projectRoot, options) { return __awaiter(this, void 0, void 0, function* () { const { configPath } = findConfigFile(projectRoot); let { exp, pkg, rootConfig } = yield readConfigJsonAsync(projectRoot); exp = Object.assign({}, exp, options); rootConfig = Object.assign({}, rootConfig, { expo: exp }); yield json_file_1.default.writeAsync(configPath, rootConfig, { json5: false }); return { exp, pkg, rootConfig, }; }); } exports.writeConfigJsonAsync = writeConfigJsonAsync; //# sourceMappingURL=Config.js.map