"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