"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