"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.startReactNativeServerAsync = startReactNativeServerAsync; exports.stopReactNativeServerAsync = stopReactNativeServerAsync; function _config() { const data = require("@expo/config"); _config = function () { return data; }; return data; } function _paths() { const data = require("@expo/config/paths"); _paths = function () { return data; }; return data; } function _axios() { const data = _interopRequireDefault(require("axios")); _axios = function () { return data; }; return data; } function _child_process() { const data = _interopRequireDefault(require("child_process")); _child_process = function () { return data; }; return data; } function _escapeRegExp() { const data = _interopRequireDefault(require("lodash/escapeRegExp")); _escapeRegExp = function () { return data; }; return data; } function _path() { const data = _interopRequireDefault(require("path")); _path = function () { return data; }; return data; } function _resolveFrom() { const data = _interopRequireDefault(require("resolve-from")); _resolveFrom = function () { return data; }; return data; } function _split() { const data = _interopRequireDefault(require("split")); _split = function () { return data; }; return data; } function _treeKill() { const data = _interopRequireDefault(require("tree-kill")); _treeKill = function () { return data; }; return data; } function _util() { const data = require("util"); _util = function () { return data; }; return data; } function _internal() { const data = require("../internal"); _internal = function () { return data; }; return data; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const treekillAsync = (0, _util().promisify)(_treeKill().default); // The --verbose flag is intended for react-native-cli/metro, not expo-cli const METRO_VERBOSE_WARNING = 'Run CLI with --verbose flag for more details.'; // Remove these constants and related code when SDK35 isn't supported anymore // Context: https://github.com/expo/expo-cli/issues/1074 const NODE_12_WINDOWS_METRO_ERROR = `Invalid regular expression: /(.*\\__fixtures__\\.*|node_modules[\\]react[\\]dist[\\].*|website\\node_modules\\.*|heapCapture\\bundle.js|.*\\__tests__\\.*)$/: Unterminated character class`; const NODE_12_WINDOWS_METRO_SUGGESTION = `\nUnable to start the project due to a documented incompatibility between Node 12 LTS and Expo SDK 35 on Windows. Please refer to this GitHub comment for a solution: https://github.com/expo/expo-cli/issues/1074#issuecomment-559220752\n`; function _logPackagerOutput(projectRoot, level, data) { let output = data.toString(); if (!output) { return; } // Temporarily hide warnings about duplicate providesModule declarations // under react-native if (_isIgnorableDuplicateModuleWarning(projectRoot, level, output)) { _internal().ProjectUtils.logDebug(projectRoot, 'expo', `Suppressing @providesModule warning: ${output}`, 'project-suppress-providesmodule-warning'); return; } if (_isIgnorableMetroConsoleOutput(output) || _isIgnorableRnpmWarning(output)) { _internal().ProjectUtils.logDebug(projectRoot, 'expo', output); return; } if (output.includes(NODE_12_WINDOWS_METRO_ERROR)) { _internal().ProjectUtils.logError(projectRoot, 'expo', NODE_12_WINDOWS_METRO_SUGGESTION); return; } if (output.includes(METRO_VERBOSE_WARNING)) { output = output.replace(METRO_VERBOSE_WARNING, ''); } if (/^Scanning folders for symlinks in /.test(output)) { _internal().ProjectUtils.logDebug(projectRoot, 'metro', output); return; } if (level === 'info') { _internal().ProjectUtils.logInfo(projectRoot, 'metro', output); } else { _internal().ProjectUtils.logError(projectRoot, 'metro', output); } } function _isIgnorableMetroConsoleOutput(output) { // As of react-native 0.61.x, Metro prints console logs from the device to console, without // passing them through the custom log reporter. // // Managed apps have a separate remote logging implementation included in the Expo SDK, // (see: _handleDeviceLogs), so we can just ignore these device logs from Metro. // if (/^ () /) // // These logs originate from: // https://github.com/facebook/metro/blob/e8181fb9db7db31adf7d1ed9ab840f54449ef238/packages/metro/src/lib/logToConsole.js#L50 return /^\s+(INFO|WARN|LOG|GROUP|DEBUG) /.test(output); } function _isIgnorableRnpmWarning(output) { return output.startsWith('warn The following packages use deprecated "rnpm" config that will stop working from next release'); } function _isIgnorableDuplicateModuleWarning(projectRoot, level, output) { if (level !== 'error' || !output.startsWith('jest-haste-map: @providesModule naming collision:')) { return false; } const reactNativeNodeModulesPath = _path().default.join(projectRoot, 'node_modules', 'react-native', 'node_modules'); const reactNativeNodeModulesPattern = (0, _escapeRegExp().default)(reactNativeNodeModulesPath); const reactNativeNodeModulesCollisionRegex = new RegExp(`Paths: ${reactNativeNodeModulesPattern}.+ collides with ${reactNativeNodeModulesPattern}.+`); return reactNativeNodeModulesCollisionRegex.test(output); } async function startReactNativeServerAsync({ projectRoot, options = {}, exp = (0, _config().getConfig)(projectRoot).exp, verbose = true }) { (0, _internal().assertValidProjectRoot)(projectRoot); await stopReactNativeServerAsync(projectRoot); await _internal().Watchman.addToPathAsync(); // Attempt to fix watchman if it's hanging await _internal().Watchman.unblockAndGetVersionAsync(projectRoot); let packagerPort = await (0, _internal().getFreePortAsync)(options.metroPort || 19001); // Create packager options const customLogReporterPath = require.resolve(_path().default.join(__dirname, '../../build/reporter')); // TODO: Bacon: Support .mjs (short-lived JS modules extension that some packages use) const sourceExtsConfig = { isTS: true, isReact: true, isModern: false }; const sourceExts = options.target === 'bare' ? (0, _paths().getBareExtensions)([], sourceExtsConfig) : (0, _paths().getManagedExtensions)([], sourceExtsConfig); let packagerOpts = { port: packagerPort, customLogReporterPath, sourceExts }; if (options.nonPersistent && !_internal().Versions.gteSdkVersion(exp, '33.0.0')) { // Expo SDK -32 | React Native -57 packagerOpts.nonPersistent = true; } if (!_internal().Versions.lteSdkVersion(exp, '32.0.0')) { // Expo SDK +33 | React Native +59.8 (hooks): Add asset plugins // starting with SDK 37, we bundle this plugin with the expo-asset package instead of expo, // so check there first and fall back to expo if we can't find it in expo-asset packagerOpts.assetPlugins = _resolveFrom().default.silent(projectRoot, 'expo-asset/tools/hashAssetFiles'); if (!packagerOpts.assetPlugins) { packagerOpts.assetPlugins = _resolveFrom().default.silent(projectRoot, 'expo/tools/hashAssetFiles'); if (!packagerOpts.assetPlugins) { throw new Error('Unable to find the expo-asset package in the current project. Install it and try again.'); } } } if (options.maxWorkers) { packagerOpts['max-workers'] = options.maxWorkers; } if (_internal().Versions.lteSdkVersion(exp, '15.0.0')) { // Expo SDK -15 | React Native -42: customLogReporterPath is not supported delete packagerOpts.customLogReporterPath; } const userPackagerOpts = exp.packagerOpts; if (userPackagerOpts) { var _userPackagerOpts$sou; // The RN CLI expects rn-cli.config.js's path to be absolute. We use the // project root to resolve relative paths since that was the original // behavior of the RN CLI. if (userPackagerOpts.config) { userPackagerOpts.config = _path().default.resolve(projectRoot, userPackagerOpts.config); } // Provide a fallback if the value isn't given const userSourceExts = (_userPackagerOpts$sou = userPackagerOpts.sourceExts) !== null && _userPackagerOpts$sou !== void 0 ? _userPackagerOpts$sou : []; packagerOpts = { ...packagerOpts, ...userPackagerOpts, // In order to prevent people from forgetting to include the .expo extension or other things // NOTE(brentvatne): we should probably do away with packagerOpts soon in favor of @expo/metro-config! sourceExts: [...new Set([...packagerOpts.sourceExts, ...userSourceExts])] }; if (userPackagerOpts.port !== undefined && userPackagerOpts.port !== null) { packagerPort = userPackagerOpts.port; } } const cliOpts = ['start']; for (const [key, val] of Object.entries(packagerOpts)) { // If the packager opt value is boolean, don't set // --[opt] [value], just set '--opt' if (val && typeof val === 'boolean') { cliOpts.push(`--${key}`); } else if (val) { cliOpts.push(`--${key}`, val); } } if (process.env.EXPO_DEBUG) { cliOpts.push('--verbose'); } if (options.reset) { cliOpts.push('--reset-cache'); } // Get the CLI path const cliPath = (0, _resolveFrom().default)(projectRoot, 'react-native/local-cli/cli.js'); // Run the copy of Node that's embedded in Electron by setting the // ELECTRON_RUN_AS_NODE environment variable // Note: the CLI script sets up graceful-fs and sets ulimit to 4096 in the // child process const packagerProcess = _child_process().default.fork(cliPath, cliOpts, { cwd: projectRoot, env: { ...process.env, NODE_OPTIONS: process.env.METRO_NODE_OPTIONS, REACT_NATIVE_APP_ROOT: projectRoot, ELECTRON_RUN_AS_NODE: '1' }, silent: true }); await _internal().ProjectSettings.setPackagerInfoAsync(projectRoot, { packagerPort, packagerPid: packagerProcess.pid }); // TODO: do we need this? don't know if it's ever called process.on('exit', () => { (0, _treeKill().default)(packagerProcess.pid); }); if (!packagerProcess.stdout) { throw new Error('Expected spawned process to have a stdout stream, but none was found.'); } if (!packagerProcess.stderr) { throw new Error('Expected spawned process to have a stderr stream, but none was found.'); } packagerProcess.stdout.setEncoding('utf8'); packagerProcess.stderr.setEncoding('utf8'); packagerProcess.stdout.pipe((0, _split().default)()).on('data', data => { if (verbose) { _logPackagerOutput(projectRoot, 'info', data); } }); packagerProcess.stderr.on('data', data => { if (verbose) { _logPackagerOutput(projectRoot, 'error', data); } }); const exitPromise = new Promise((resolve, reject) => { packagerProcess.once('exit', async code => { _internal().ProjectUtils.logDebug(projectRoot, 'expo', `Metro Bundler process exited with code ${code}`); if (code) { reject(new Error(`Metro Bundler process exited with code ${code}`)); } else { resolve(); } try { await _internal().ProjectSettings.setPackagerInfoAsync(projectRoot, { packagerPort: null, packagerPid: null }); } catch {} }); }); const packagerUrl = await _internal().UrlUtils.constructBundleUrlAsync(projectRoot, { urlType: 'http', hostType: 'localhost' }); await Promise.race([_waitForRunningAsync(projectRoot, `${packagerUrl}/status`), exitPromise]); } async function stopReactNativeServerAsync(projectRoot) { (0, _internal().assertValidProjectRoot)(projectRoot); const packagerInfo = await _internal().ProjectSettings.readPackagerInfoAsync(projectRoot); if (!packagerInfo.packagerPort || !packagerInfo.packagerPid) { _internal().ProjectUtils.logDebug(projectRoot, 'expo', `No packager found for project at ${projectRoot}.`); return; } _internal().ProjectUtils.logDebug(projectRoot, 'expo', `Killing packager process tree: ${packagerInfo.packagerPid}`); try { await treekillAsync(packagerInfo.packagerPid, 'SIGKILL'); } catch (e) { _internal().ProjectUtils.logDebug(projectRoot, 'expo', `Error stopping packager process: ${e.toString()}`); } await _internal().ProjectSettings.setPackagerInfoAsync(projectRoot, { packagerPort: null, packagerPid: null }); } async function _waitForRunningAsync(projectRoot, url, retries = 300) { try { const response = await _axios().default.request({ url, responseType: 'text', proxy: false }); if (/packager-status:running/.test(response.data)) { return true; } else if (retries === 0) { _internal().ProjectUtils.logError(projectRoot, 'expo', `Could not get status from Metro bundler. Server response: ${response.data}`); } } catch (e) { if (retries === 0) { _internal().ProjectUtils.logError(projectRoot, 'expo', `Could not get status from Metro bundler. ${e.message}`); } } if (retries <= 0) { throw new Error('Connecting to Metro bundler failed.'); } else { await (0, _internal().delayAsync)(100); return _waitForRunningAsync(projectRoot, url, retries - 1); } } //# sourceMappingURL=startLegacyReactNativeServerAsync.js.map