@expo/cli
Version:
367 lines (366 loc) • 14.6 kB
JavaScript
/**
* Copyright © 2022 650 Industries.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/ "use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
function _export(target, all) {
for(var name in all)Object.defineProperty(target, name, {
enumerable: true,
get: all[name]
});
}
_export(exports, {
IS_METRO_BUNDLE_ERROR_SYMBOL: function() {
return IS_METRO_BUNDLE_ERROR_SYMBOL;
},
getErrorOverlayHtmlAsync: function() {
return getErrorOverlayHtmlAsync;
},
getStackAsFormattedLog: function() {
return getStackAsFormattedLog;
},
logMetroError: function() {
return logMetroError;
},
logMetroErrorAsync: function() {
return logMetroErrorAsync;
},
logMetroErrorWithStack: function() {
return logMetroErrorWithStack;
}
});
function _paths() {
const data = require("@expo/config/paths");
_paths = function() {
return data;
};
return data;
}
function _chalk() {
const data = /*#__PURE__*/ _interop_require_default(require("chalk"));
_chalk = function() {
return data;
};
return data;
}
function _path() {
const data = /*#__PURE__*/ _interop_require_default(require("path"));
_path = function() {
return data;
};
return data;
}
function _resolvefrom() {
const data = /*#__PURE__*/ _interop_require_default(require("resolve-from"));
_resolvefrom = function() {
return data;
};
return data;
}
function _stacktraceparser() {
const data = require("stacktrace-parser");
_stacktraceparser = function() {
return data;
};
return data;
}
function _terminallink() {
const data = /*#__PURE__*/ _interop_require_default(require("terminal-link"));
_terminallink = function() {
return data;
};
return data;
}
const _LogBoxLog = require("./log-box/LogBoxLog");
const _formatProjectFilePath = require("./log-box/formatProjectFilePath");
const _log = require("../../../log");
const _ansi = require("../../../utils/ansi");
const _env = require("../../../utils/env");
const _errors = require("../../../utils/errors");
const _getStaticRenderFunctions = require("../getStaticRenderFunctions");
function _interop_require_default(obj) {
return obj && obj.__esModule ? obj : {
default: obj
};
}
function fill(width) {
return Array(width).join(' ');
}
function formatPaths(config) {
const filePath = _chalk().default.reset(config.filePath);
return _chalk().default.dim('(') + filePath + _chalk().default.dim(`:${[
config.line,
config.col
].filter(Boolean).join(':')})`);
}
async function logMetroErrorWithStack(projectRoot, { stack, codeFrame, error }) {
if (error instanceof _errors.SilentError) {
return;
}
// process.stdout.write('\u001b[0m'); // Reset attributes
// process.stdout.write('\u001bc'); // Reset the terminal
_log.Log.log();
_log.Log.log(_chalk().default.red('Metro error: ') + error.message);
_log.Log.log();
if (error instanceof _errors.CommandError) {
return;
}
_log.Log.log(getStackAsFormattedLog(projectRoot, {
stack,
codeFrame,
error,
showCollapsedFrames: true
}));
}
function getStackAsFormattedLog(projectRoot, { stack, codeFrame, error, showCollapsedFrames = _env.env.EXPO_DEBUG }) {
const logs = [];
let hasCodeFramePresented = false;
if (codeFrame) {
var _codeFrame_location;
const maxWarningLineLength = Math.max(800, process.stdout.columns);
const lineText = codeFrame.content;
const isPreviewTooLong = codeFrame.content.split('\n').some((line)=>line.length > maxWarningLineLength);
const column = (_codeFrame_location = codeFrame.location) == null ? void 0 : _codeFrame_location.column;
// When the preview is too long, we skip reading the file and attempting to apply
// code coloring, this is because it can get very slow.
if (isPreviewTooLong) {
var _codeFrame_location1, _codeFrame_location2;
let previewLine = '';
let cursorLine = '';
const formattedPath = formatPaths({
filePath: codeFrame.fileName,
line: (_codeFrame_location1 = codeFrame.location) == null ? void 0 : _codeFrame_location1.row,
col: (_codeFrame_location2 = codeFrame.location) == null ? void 0 : _codeFrame_location2.column
});
// Create a curtailed preview line like:
// `...transition:'fade'},k._updatePropsStack=function(){clearImmediate(k._updateImmediate),k._updateImmediate...`
// If there is no text preview or column number, we can't do anything.
if (lineText && column != null) {
var _codeFrame_fileName;
const rangeWindow = Math.round(Math.max(((_codeFrame_fileName = codeFrame.fileName) == null ? void 0 : _codeFrame_fileName.length) ?? 0, Math.max(80, process.stdout.columns)) / 2);
let minBounds = Math.max(0, column - rangeWindow);
const maxBounds = Math.min(minBounds + rangeWindow * 2, lineText.length);
previewLine = lineText.slice(minBounds, maxBounds);
// If we splice content off the start, then we should append `...`.
// This is unlikely to happen since we limit the activation size.
if (minBounds > 0) {
// Adjust the min bounds so the cursor is aligned after we add the "..."
minBounds -= 3;
previewLine = _chalk().default.dim('...') + previewLine;
}
if (maxBounds < lineText.length) {
previewLine += _chalk().default.dim('...');
}
// If the column property could be found, then use that to fix the cursor location which is often broken in regex.
cursorLine = (column == null ? '' : fill(column) + _chalk().default.reset('^')).slice(minBounds);
logs.push(formattedPath, '', previewLine, cursorLine, _chalk().default.dim('(error truncated)'));
hasCodeFramePresented = true;
}
} else {
logs.push(codeFrame.content);
hasCodeFramePresented = true;
}
}
if (stack == null ? void 0 : stack.length) {
const stackProps = stack.map((frame)=>{
return {
title: frame.methodName,
subtitle: (0, _formatProjectFilePath.getStackFormattedLocation)(projectRoot, frame),
collapse: frame.collapse
};
});
const stackLines = [];
const backupStackLines = [];
stackProps.forEach((frame)=>{
const shouldShow = !frame.collapse || showCollapsedFrames;
const position = _terminallink().default.isSupported ? (0, _terminallink().default)(frame.subtitle, frame.subtitle) : frame.subtitle;
let lineItem = _chalk().default.gray(` ${frame.title} (${position})`);
if (frame.collapse) {
lineItem = _chalk().default.dim(lineItem);
}
// Never show the internal module system.
const isMetroRuntime = /\/metro-runtime\/src\/polyfills\/require\.js/.test(frame.subtitle) || /\/metro-require\/require\.js/.test(frame.subtitle);
if (!isMetroRuntime) {
if (shouldShow) {
stackLines.push(lineItem);
}
backupStackLines.push(lineItem);
}
});
if (hasCodeFramePresented) {
logs.push('');
}
logs.push(_chalk().default.bold`Call Stack`);
if (!backupStackLines.length) {
logs.push(_chalk().default.gray(' No stack trace available.'));
} else {
// If there are not stack lines then it means the error likely happened in the node modules, in this case we should fallback to showing all the
// the stacks to give the user whatever help we can.
const displayStack = stackLines.length ? stackLines : backupStackLines;
logs.push(displayStack.join('\n'));
}
} else if (error) {
logs.push(_chalk().default.gray(` ${error.stack}`));
}
return logs.join('\n');
}
const IS_METRO_BUNDLE_ERROR_SYMBOL = Symbol('_isMetroBundleError');
const HAS_LOGGED_SYMBOL = Symbol('_hasLoggedInCLI');
async function logMetroError(projectRoot, { error }) {
var _log_symbolicated_stack, _log_symbolicated;
if (error instanceof _errors.SilentError || error[HAS_LOGGED_SYMBOL]) {
return;
}
error[HAS_LOGGED_SYMBOL] = true;
const stack = parseErrorStack(projectRoot, error.stack);
const log = new _LogBoxLog.LogBoxLog({
level: 'static',
message: {
content: error.message,
substitutions: []
},
isComponentError: false,
stack,
category: 'static',
componentStack: []
});
await new Promise((res)=>log.symbolicate('stack', res));
logMetroErrorWithStack(projectRoot, {
stack: ((_log_symbolicated = log.symbolicated) == null ? void 0 : (_log_symbolicated_stack = _log_symbolicated.stack) == null ? void 0 : _log_symbolicated_stack.stack) ?? [],
codeFrame: log.codeFrame,
error
});
}
function isTransformError(error) {
return error.type === 'TransformError';
}
/** @returns the html required to render the static metro error as an SPA. */ function logFromError({ error, projectRoot }) {
// Remap direct Metro Node.js errors to a format that will appear more client-friendly in the logbox UI.
let stack;
if (isTransformError(error) && error.filename) {
// Syntax errors in static rendering.
stack = [
{
file: _path().default.join(projectRoot, error.filename),
methodName: '<unknown>',
arguments: [],
// TODO: Import stack
lineNumber: error.lineNumber,
column: error.column
}
];
} else if ('originModulePath' in error && typeof error.originModulePath === 'string') {
// TODO: Use import stack here when the error is resolution based.
stack = [
{
file: error.originModulePath,
methodName: '<unknown>',
arguments: [],
// TODO: Import stack
lineNumber: 0,
column: 0
}
];
} else {
stack = parseErrorStack(projectRoot, error.stack);
}
return new _LogBoxLog.LogBoxLog({
level: 'static',
message: {
content: error.message,
substitutions: []
},
isComponentError: false,
stack,
category: 'static',
componentStack: []
});
}
async function logMetroErrorAsync({ error, projectRoot }) {
var _log_symbolicated_stack, _log_symbolicated;
const log = logFromError({
projectRoot,
error
});
await new Promise((res)=>log.symbolicate('stack', ()=>res()));
logMetroErrorWithStack(projectRoot, {
stack: ((_log_symbolicated = log.symbolicated) == null ? void 0 : (_log_symbolicated_stack = _log_symbolicated.stack) == null ? void 0 : _log_symbolicated_stack.stack) ?? [],
codeFrame: log.codeFrame,
error
});
}
async function getErrorOverlayHtmlAsync({ error, projectRoot, routerRoot }) {
var _log_symbolicated_stack, _log_symbolicated;
const log = logFromError({
projectRoot,
error
});
await new Promise((res)=>log.symbolicate('stack', ()=>res()));
logMetroErrorWithStack(projectRoot, {
stack: ((_log_symbolicated = log.symbolicated) == null ? void 0 : (_log_symbolicated_stack = _log_symbolicated.stack) == null ? void 0 : _log_symbolicated_stack.stack) ?? [],
codeFrame: log.codeFrame,
error
});
if ('message' in log && 'content' in log.message && typeof log.message.content === 'string') {
log.message.content = (0, _ansi.stripAnsi)(log.message.content);
}
const logBoxContext = {
selectedLogIndex: 0,
isDisabled: false,
logs: [
log
]
};
const html = `<html><head><style>#root,body,html{height:100%}body{overflow:hidden}#root{display:flex}</style></head><body><div id="root"></div><script id="_expo-static-error" type="application/json">${JSON.stringify(logBoxContext)}</script></body></html>`;
const errorOverlayEntry = await (0, _getStaticRenderFunctions.createMetroEndpointAsync)(projectRoot, // Keep the URL relative
'', (0, _resolvefrom().default)(projectRoot, 'expo-router/_error'), {
mode: 'development',
platform: 'web',
minify: false,
optimize: false,
usedExports: false,
baseUrl: '',
routerRoot,
isExporting: false,
reactCompiler: false
});
const htmlWithJs = html.replace('</body>', `<script src=${errorOverlayEntry}></script></body>`);
return htmlWithJs;
}
function parseErrorStack(projectRoot, stack) {
if (stack == null) {
return [];
}
if (Array.isArray(stack)) {
return stack;
}
const serverRoot = (0, _paths().getMetroServerRoot)(projectRoot);
return (0, _stacktraceparser().parse)(stack).map((frame)=>{
// frame.file will mostly look like `http://localhost:8081/index.bundle?platform=web&dev=true&hot=false`
if (frame.file) {
// SSR will sometimes have absolute paths followed by `.bundle?...`, we need to try and make them relative paths and append a dev server URL.
if (frame.file.startsWith('/') && frame.file.includes('bundle?') && !canParse(frame.file)) {
// Malformed stack file from SSR. Attempt to repair.
frame.file = 'https://localhost:8081/' + _path().default.relative(serverRoot, frame.file);
}
}
return {
...frame,
column: frame.column != null ? frame.column - 1 : null
};
}).filter((frame)=>frame.file && !frame.file.includes('node_modules'));
}
function canParse(url) {
try {
// eslint-disable-next-line no-new
new URL(url);
return true;
} catch {
return false;
}
}
//# sourceMappingURL=metroErrorInterface.js.map