"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.restartAsync = restartAsync;
exports.printConnectionInstructions = printConnectionInstructions;
exports.startAsync = startAsync;
exports.stopAsync = stopAsync;
exports.openAsync = openAsync;
exports.compileWebAppAsync = compileWebAppAsync;
exports.bundleWebAppAsync = bundleWebAppAsync;
exports.bundleAsync = bundleAsync;
exports.getProjectNameAsync = getProjectNameAsync;
exports.getProjectUseNextJsAsync = getProjectUseNextJsAsync;
exports.getServer = getServer;
exports.getPort = getPort;
exports.getUrlAsync = getUrlAsync;
exports.getProtocolAsync = getProtocolAsync;
exports.getAvailablePortAsync = getAvailablePortAsync;
exports.setMode = setMode;
exports.DEFAULT_PORT = exports.HOST = void 0;

function ConfigUtils() {
  const data = _interopRequireWildcard(require("@expo/config"));

  ConfigUtils = function () {
    return data;
  };

  return data;
}

function _chalk() {
  const data = _interopRequireDefault(require("chalk"));

  _chalk = function () {
    return data;
  };

  return data;
}

function _express() {
  const data = _interopRequireDefault(require("express"));

  _express = function () {
    return data;
  };

  return data;
}

function _fsExtra() {
  const data = _interopRequireDefault(require("fs-extra"));

  _fsExtra = function () {
    return data;
  };

  return data;
}

function _getenv() {
  const data = _interopRequireDefault(require("getenv"));

  _getenv = function () {
    return data;
  };

  return data;
}

function _path() {
  const data = _interopRequireDefault(require("path"));

  _path = function () {
    return data;
  };

  return data;
}

function _formatWebpackMessages() {
  const data = _interopRequireDefault(require("react-dev-utils/formatWebpackMessages"));

  _formatWebpackMessages = function () {
    return data;
  };

  return data;
}

function _WebpackDevServerUtils() {
  const data = require("react-dev-utils/WebpackDevServerUtils");

  _WebpackDevServerUtils = function () {
    return data;
  };

  return data;
}

function _webpack() {
  const data = _interopRequireDefault(require("webpack"));

  _webpack = function () {
    return data;
  };

  return data;
}

function _webpackDevServer() {
  const data = _interopRequireDefault(require("webpack-dev-server"));

  _webpackDevServer = function () {
    return data;
  };

  return data;
}

function _createWebpackCompiler() {
  const data = _interopRequireWildcard(require("./createWebpackCompiler"));

  _createWebpackCompiler = function () {
    return data;
  };

  return data;
}

function _ip() {
  const data = _interopRequireDefault(require("./ip"));

  _ip = function () {
    return data;
  };

  return data;
}

function ProjectUtils() {
  const data = _interopRequireWildcard(require("./project/ProjectUtils"));

  ProjectUtils = function () {
    return data;
  };

  return data;
}

function ProjectSettings() {
  const data = _interopRequireWildcard(require("./ProjectSettings"));

  ProjectSettings = function () {
    return data;
  };

  return data;
}

function Web() {
  const data = _interopRequireWildcard(require("./Web"));

  Web = function () {
    return data;
  };

  return data;
}

function _XDLError() {
  const data = _interopRequireDefault(require("./XDLError"));

  _XDLError = function () {
    return data;
  };

  return data;
}

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } }

// @ts-ignore missing types for Doctor until it gets converted to TypeScript
const HOST = _getenv().default.string('WEB_HOST', '0.0.0.0');

exports.HOST = HOST;

const DEFAULT_PORT = _getenv().default.int('WEB_PORT', 19006);

exports.DEFAULT_PORT = DEFAULT_PORT;
const WEBPACK_LOG_TAG = 'expo';
let webpackDevServerInstance = null;
let webpackServerPort = null;

async function restartAsync(projectRoot, options = {}) {
  await stopAsync(projectRoot);
  return await startAsync(projectRoot, options);
}

const PLATFORM_TAG = ProjectUtils().getPlatformTag('web');

const withTag = (...messages) => [PLATFORM_TAG + ' ', ...messages].join('');

let devServerInfo = null;

function printConnectionInstructions(projectRoot, options = {}) {
  if (!devServerInfo) return;
  (0, _createWebpackCompiler().printInstructions)(projectRoot, {
    appName: devServerInfo.appName,
    urls: devServerInfo.urls,
    showInDevtools: false,
    showHelp: false,
    ...options
  });
}

async function startAsync(projectRoot, options = {}, deprecatedVerbose) {
  if (typeof deprecatedVerbose !== 'undefined') {
    throw new (_XDLError().default)('WEBPACK_DEPRECATED', 'startAsync(root, options, verbose): The `verbose` option is deprecated.');
  }

  const usingNextJs = await getProjectUseNextJsAsync(projectRoot);
  options.unimodulesOnly = usingNextJs;
  let serverName = 'Webpack';

  if (usingNextJs) {
    serverName = 'Next.js';
  }

  if (webpackDevServerInstance) {
    ProjectUtils().logError(projectRoot, WEBPACK_LOG_TAG, withTag(_chalk().default.red(`${serverName} is already running.`)));
    return null;
  }

  const {
    env,
    config
  } = await createWebpackConfigAsync(projectRoot, options);
  const port = await getAvailablePortAsync({
    defaultPort: options.port
  });
  webpackServerPort = port;
  ProjectUtils().logInfo(projectRoot, WEBPACK_LOG_TAG, withTag(`Starting ${serverName} on port ${webpackServerPort} in ${_chalk().default.underline(env.mode)} mode.`));
  const protocol = env.https ? 'https' : 'http';
  const urls = (0, _WebpackDevServerUtils().prepareUrls)(protocol, '::', webpackServerPort);
  const useYarn = ConfigUtils().isUsingYarn(projectRoot);
  const appName = await getProjectNameAsync(projectRoot);
  const nonInteractive = validateBoolOption('nonInteractive', options.nonInteractive, !process.stdout.isTTY);
  let server;

  if (usingNextJs) {
    if (protocol === 'https') {
      // TODO: Support https.
      throw new Error('https with Next.js is not supported for now.');
    }

    server = await startNextJsAsync({
      projectRoot,
      port: webpackServerPort,
      dev: env.mode !== 'production'
    });
    (0, _createWebpackCompiler().printSuccessMessages)({
      projectRoot,
      appName,
      urls,
      config,
      isFirstCompile: true,
      nonInteractive
    });
  } else {
    devServerInfo = {
      urls,
      protocol,
      useYarn,
      appName,
      nonInteractive,
      port: webpackServerPort
    };
    server = await new Promise(resolve => {
      // Create a webpack compiler that is configured with custom messages.
      const compiler = (0, _createWebpackCompiler().default)({
        projectRoot,
        appName,
        config,
        urls,
        nonInteractive,
        webpackFactory: _webpack().default,
        onFinished: () => resolve(server)
      });
      const server = new (_webpackDevServer().default)(compiler, config.devServer); // Launch WebpackDevServer.

      server.listen(port, HOST, error => {
        if (error) {
          ProjectUtils().logError(projectRoot, WEBPACK_LOG_TAG, error.message);
        }

        if (typeof options.onWebpackFinished === 'function') {
          options.onWebpackFinished(error);
        }
      });
      webpackDevServerInstance = server;
    });
  }

  await ProjectSettings().setPackagerInfoAsync(projectRoot, {
    webpackServerPort
  });

  const host = _ip().default.address();

  const url = `${protocol}://${host}:${webpackServerPort}`;
  return {
    url,
    server,
    port,
    protocol,
    host
  };
}

async function stopAsync(projectRoot) {
  if (webpackDevServerInstance) {
    ProjectUtils().logInfo(projectRoot, WEBPACK_LOG_TAG, '\u203A Closing Webpack server');
    webpackDevServerInstance.close();
    webpackDevServerInstance = null;
    devServerInfo = null;
    webpackServerPort = null;
    await ProjectSettings().setPackagerInfoAsync(projectRoot, {
      webpackServerPort: null
    });
  }
}

async function openAsync(projectRoot, options) {
  if (!webpackDevServerInstance) {
    await startAsync(projectRoot, options);
  }

  await Web().openProjectAsync(projectRoot);
}

async function compileWebAppAsync(projectRoot, compiler) {
  // We generate the stats.json file in the webpack-config
  const {
    warnings
  } = await new Promise((resolve, reject) => compiler.run((error, stats) => {
    let messages;

    if (error) {
      if (!error.message) {
        return reject(error);
      }

      messages = (0, _formatWebpackMessages().default)({
        errors: [error.message],
        warnings: [],
        _showErrors: true,
        _showWarnings: true
      });
    } else {
      messages = (0, _formatWebpackMessages().default)(stats.toJson({
        all: false,
        warnings: true,
        errors: true
      }));
    }

    if (messages.errors.length) {
      // Only keep the first error. Others are often indicative
      // of the same problem, but confuse the reader with noise.
      if (messages.errors.length > 1) {
        messages.errors.length = 1;
      }

      return reject(new Error(messages.errors.join('\n\n')));
    }

    if (process.env.CI && (typeof process.env.CI !== 'string' || process.env.CI.toLowerCase() !== 'false') && messages.warnings.length) {
      ProjectUtils().logWarning(projectRoot, WEBPACK_LOG_TAG, withTag(_chalk().default.yellow('\nTreating warnings as errors because process.env.CI = true.\n' + 'Most CI servers set it automatically.\n')));
      return reject(new Error(messages.warnings.join('\n\n')));
    }

    resolve({
      warnings: messages.warnings
    });
  }));
  return {
    warnings
  };
}

async function bundleWebAppAsync(projectRoot, config) {
  const compiler = (0, _webpack().default)(config);

  try {
    const {
      warnings
    } = await compileWebAppAsync(projectRoot, compiler);

    if (warnings.length) {
      ProjectUtils().logWarning(projectRoot, WEBPACK_LOG_TAG, withTag(_chalk().default.yellow('Compiled with warnings.\n')));
      ProjectUtils().logWarning(projectRoot, WEBPACK_LOG_TAG, warnings.join('\n\n'));
    } else {
      ProjectUtils().logInfo(projectRoot, WEBPACK_LOG_TAG, withTag(_chalk().default.green('Compiled successfully.\n')));
    }
  } catch (error) {
    ProjectUtils().logError(projectRoot, WEBPACK_LOG_TAG, withTag(_chalk().default.red('Failed to compile.\n')));
    throw error;
  }
}

async function bundleAsync(projectRoot, options) {
  const isUsingNextJs = await getProjectUseNextJsAsync(projectRoot);
  const {
    config
  } = await createWebpackConfigAsync(projectRoot, { ...options,
    unimodulesOnly: isUsingNextJs
  });

  if (isUsingNextJs) {
    await bundleNextJsAsync(projectRoot);
  } else {
    await bundleWebAppAsync(projectRoot, config);
  }
}

async function getProjectNameAsync(projectRoot) {
  const {
    exp
  } = await ConfigUtils().readConfigJsonAsync(projectRoot, true);
  const {
    webName
  } = ConfigUtils().getNameFromConfig(exp);
  return webName;
}

async function getProjectUseNextJsAsync(projectRoot) {
  const {
    exp
  } = await ConfigUtils().readConfigJsonAsync(projectRoot, true);
  const {
    use = null
  } = exp.web || {};
  return use === 'nextjs';
}

function getServer(projectRoot) {
  if (webpackDevServerInstance == null) {
    ProjectUtils().logError(projectRoot, WEBPACK_LOG_TAG, withTag('Webpack is not running.'));
  }

  return webpackDevServerInstance;
}

function getPort() {
  return webpackServerPort;
}

async function getUrlAsync(projectRoot) {
  const devServer = getServer(projectRoot);

  if (!devServer) {
    return null;
  }

  const host = _ip().default.address();

  const protocol = await getProtocolAsync(projectRoot);
  return `${protocol}://${host}:${webpackServerPort}`;
}

async function getProtocolAsync(projectRoot) {
  // TODO: Bacon: Handle when not in expo
  const {
    https
  } = await ProjectSettings().readAsync(projectRoot);
  return https === true ? 'https' : 'http';
}

async function getAvailablePortAsync(options = {}) {
  try {
    const defaultPort = 'defaultPort' in options && options.defaultPort ? options.defaultPort : DEFAULT_PORT;
    const port = await (0, _WebpackDevServerUtils().choosePort)('host' in options && options.host ? options.host : HOST, defaultPort);
    if (!port) throw new Error(`Port ${defaultPort} not available.`);else return port;
  } catch (error) {
    throw new (_XDLError().default)('NO_PORT_FOUND', 'No available port found: ' + error.message);
  }
}

function setMode(mode) {
  process.env.BABEL_ENV = mode;
  process.env.NODE_ENV = mode;
}

function validateBoolOption(name, value, defaultValue) {
  if (typeof value === 'undefined') {
    value = defaultValue;
  }

  if (typeof value !== 'boolean') {
    throw new (_XDLError().default)('WEBPACK_INVALID_OPTION', `'${name}' option must be a boolean.`);
  }

  return value;
}

function transformCLIOptions(options) {
  // Transform the CLI flags into more explicit values
  return { ...options,
    isImageEditingEnabled: options.pwa
  };
}

async function createWebpackConfigAsync(projectRoot, options = {}) {
  const fullOptions = transformCLIOptions(options);
  const env = await getWebpackConfigEnvFromBundlingOptionsAsync(projectRoot, fullOptions);
  setMode(env.mode);
  let config;

  if (options.unimodulesOnly) {
    const withUnimodules = require('@expo/webpack-config/withUnimodules');

    config = withUnimodules({}, env);
  } else {
    config = await Web().invokeWebpackConfigAsync(env);
  }

  return {
    env,
    config
  };
}

async function applyOptionsToProjectSettingsAsync(projectRoot, options) {
  let newSettings = {}; // Change settings before reading them

  if (typeof options.https === 'boolean') {
    newSettings.https = options.https;
  }

  if (typeof options.dev === 'boolean') {
    newSettings.dev = options.dev;
  }

  if (Object.keys(newSettings).length) {
    await ProjectSettings().setAsync(projectRoot, newSettings);
  }

  return await ProjectSettings().readAsync(projectRoot);
}

async function getWebpackConfigEnvFromBundlingOptionsAsync(projectRoot, options) {
  // Bacon: Prevent dev flag from being used in production
  if (options.mode === 'production') {
    options.dev = false;
  }

  let {
    dev,
    https
  } = await applyOptionsToProjectSettingsAsync(projectRoot, options);
  const mode = typeof options.mode === 'string' ? options.mode : dev ? 'development' : 'production';
  const isImageEditingEnabled = validateBoolOption('isImageEditingEnabled', options.isImageEditingEnabled, true);
  const isDebugInfoEnabled = validateBoolOption('isDebugInfoEnabled', options.isDebugInfoEnabled, Web().isInfoEnabled());
  return {
    projectRoot,
    pwa: isImageEditingEnabled,
    mode,
    https,
    info: isDebugInfoEnabled,
    ...(options.webpackEnv || {})
  };
}

async function startNextJsAsync({
  projectRoot,
  port,
  dev
}) {
  const {
    exp
  } = await ConfigUtils().readConfigJsonAsync(projectRoot, true);
  let next;

  try {
    next = require(ConfigUtils().resolveModule('next', projectRoot, exp));
  } catch (_unused) {
    throw new (_XDLError().default)('NEXTJS_NOT_INSTALLED', 'Next.js is not installed in your app. See https://docs.expo.io/versions/latest/guides/using-nextjs/');
  } // Build first if in production mode.
  // https://nextjs.org/docs#custom-server-and-routing


  if (!dev) {
    await bundleNextJsAsync(projectRoot);
  }

  await _copyCustomNextJsTemplatesAsync(projectRoot);
  const app = next({
    dev,
    dir: projectRoot
  });
  const handle = app.getRequestHandler();
  await app.prepare();
  const server = (0, _express().default)();
  server.get('/expo-service-worker.js', (req, res) => {
    res.sendFile(_path().default.resolve(projectRoot, 'static', 'expo-service-worker.js'));
  });
  server.get('/service-worker.js', (req, res) => {
    // This file should be provided by https://github.com/hanford/next-offline if installed.
    const serviceWorkerPath = _path().default.resolve(projectRoot, '.next', 'service-worker.js');

    if (!_fsExtra().default.existsSync(serviceWorkerPath)) {
      // Simply return a blank service worker file if the user is not using `next-offline`.
      res.sendFile(_path().default.resolve(projectRoot, 'static', 'service-worker.js'));
      return;
    }

    res.sendFile(serviceWorkerPath);
  });
  server.get('*', handle);
  webpackDevServerInstance = server.listen(port, err => {
    if (err) {
      throw new Error(`Express server failed to start: ${err.toString()}`);
    }
  });
  return webpackDevServerInstance;
}

async function bundleNextJsAsync(projectRoot) {
  const {
    exp
  } = await ConfigUtils().readConfigJsonAsync(projectRoot, true);
  let nextBuild;

  try {
    nextBuild = require(ConfigUtils().resolveModule('next/dist/build', projectRoot, exp)).default;
  } catch (_unused2) {
    throw new (_XDLError().default)('NEXTJS_NOT_INSTALLED', 'Next.js (or its build component) is not installed in your app. See https://docs.expo.io/versions/latest/guides/using-nextjs/');
  }

  await _copyCustomNextJsTemplatesAsync(projectRoot);
  await nextBuild(projectRoot);
}

async function _copyCustomNextJsTemplatesAsync(projectRoot) {
  try {
    await _fsExtra().default.writeFile(_path().default.join(projectRoot, '.expo', 'next_document.js'), nextJsDocument);
  } catch (e) {
    throw new Error(`Could not write to _document.js: ${e.toString()}`);
  }

  const pagesDocument = _path().default.join(projectRoot, 'pages', '_document.js');

  if (!_fsExtra().default.existsSync(pagesDocument)) {
    // Only write to `pages/_document.js` if it doesn't exists.
    try {
      await _fsExtra().default.writeFile(pagesDocument, nextJsImportDocument);
    } catch (e) {
      throw new Error(`Could not write to pages/_document.js: ${e.toString()}`);
    }
  } // TODO: Use `public/` folder when Next.js eventually deprecates `static/` folder.


  const staticFolder = _path().default.join(projectRoot, 'static');

  if (!_fsExtra().default.existsSync(staticFolder)) {
    _fsExtra().default.mkdirSync(staticFolder);
  }

  try {
    await _fsExtra().default.copyFile(require.resolve('@expo/webpack-config/web-default/expo-service-worker.js'), _path().default.join(staticFolder, 'expo-service-worker.js'));
  } catch (e) {
    throw new Error(`Could not copy expo-service-worker.js: ${e.toString()}`);
  }

  const serviceWorkerPath = _path().default.join(staticFolder, 'service-worker.js');

  if (!_fsExtra().default.existsSync(serviceWorkerPath)) {
    // Write a blank service-worker.js file for users who do not use any other service worker.
    try {
      await _fsExtra().default.writeFile(serviceWorkerPath, '');
    } catch (e) {
      throw new Error(`Could not write to service-worker.js: ${e.toString()}`);
    }
  }
}

const nextJsDocument = `\
// Based on https://github.com/zeit/next.js/tree/canary/examples/with-react-native-web
// and https://github.com/expo/expo-cli/blob/master/packages/webpack-config/web-default/index.html
import Document, { Head, Main, NextScript } from 'next/document'
import React from 'react'
import { AppRegistry } from 'react-native'

const normalizeNextElements = \`
/**
 * Building on the RNWeb reset:
 * https://github.com/necolas/react-native-web/blob/master/packages/react-native-web/src/exports/StyleSheet/initialRules.js
 */
html, body, #__next {
  width: 100%;
  /* To smooth any scrolling behavior */
  -webkit-overflow-scrolling: touch;
  margin: 0px;
  padding: 0px;
  /* Allows content to fill the viewport and go beyond the bottom */
  min-height: 100%;
}
#__next {
  flex-shrink: 0;
  flex-basis: auto;
  flex-grow: 1;
  display: flex;
  flex: 1;
}
html {
  font-size: 14px;
  scroll-behavior: smooth;
  /* Prevent text size change on orientation change https://gist.github.com/tfausak/2222823#file-ios-8-web-app-html-L138 */
  -webkit-text-size-adjust: 100%;
  height: 100%;
}
body {
  display: flex;
  /* Allows you to scroll below the viewport; default value is visible */
  overflow-y: auto;
  overscroll-behavior-y: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  -ms-overflow-style: scrollbar;
}
\`;

export default class ExpoDocument extends Document {
  static async getInitialProps ({ renderPage }) {
    AppRegistry.registerComponent('Main', () => Main)
    const { getStyleElement } = AppRegistry.getApplication('Main')
    const page = renderPage()
    const styles = [
      <style dangerouslySetInnerHTML={{ __html: normalizeNextElements }} />,
      getStyleElement()
    ]
    return { ...page, styles: React.Children.toArray(styles) }
  }

  render () {
    return (
      <html>
        <Head>
          <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </html>
    );
  }
}
`;
const nextJsImportDocument = `\
import ExpoDocument from '../.expo/next_document';
export default ExpoDocument;
`;
//# sourceMappingURL=__sourcemaps__/Webpack.js.map