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
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
|