You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

349 lines
13 KiB

"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