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.

407 lines
15 KiB

"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
function _chalk() {
const data = _interopRequireDefault(require("chalk"));
_chalk = 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 _internal() {
const data = require("../internal");
_internal = function () {
return data;
};
return data;
}
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
class PackagerLogsStream {
constructor({
projectRoot,
getCurrentOpenProjectId,
updateLogs,
onStartBuildBundle,
onProgressBuildBundle,
onFinishBuildBundle,
getSnippetForError
}) {
_defineProperty(this, "_projectRoot", void 0);
_defineProperty(this, "_getCurrentOpenProjectId", void 0);
_defineProperty(this, "_updateLogs", void 0);
_defineProperty(this, "_logsToAdd", []);
_defineProperty(this, "_bundleBuildChunkID", null);
_defineProperty(this, "_onStartBuildBundle", void 0);
_defineProperty(this, "_onProgressBuildBundle", void 0);
_defineProperty(this, "_onFinishBuildBundle", void 0);
_defineProperty(this, "_bundleBuildStart", null);
_defineProperty(this, "_getSnippetForError", void 0);
_defineProperty(this, "projectId", void 0);
_defineProperty(this, "bundleDetailsCache", {});
_defineProperty(this, "_handleBundleTransformEvent", chunk => {
const msg = chunk.msg;
const bundleDetails = 'buildID' in msg ? this.bundleDetailsCache[msg.buildID] || null : null;
if (msg.type === 'bundle_build_started') {
// Cache bundle details for later.
this.bundleDetailsCache[String(msg.buildID)] = msg.bundleDetails;
chunk._metroEventType = 'BUILD_STARTED';
this._handleNewBundleTransformStarted(chunk, msg.bundleDetails);
} else if (msg.type === 'bundle_transform_progressed' && this._bundleBuildChunkID) {
chunk._metroEventType = 'BUILD_PROGRESS';
this._handleUpdateBundleTransformProgress(chunk, bundleDetails);
} else if (msg.type === 'bundle_build_failed' && this._bundleBuildChunkID) {
chunk._metroEventType = 'BUILD_FAILED';
this._handleUpdateBundleTransformProgress(chunk, bundleDetails);
} else if (msg.type === 'bundle_build_done' && this._bundleBuildChunkID) {
chunk._metroEventType = 'BUILD_DONE';
this._handleUpdateBundleTransformProgress(chunk, bundleDetails);
}
});
_defineProperty(this, "_enqueueFlushLogsToAdd", () => {
this._updateLogs(logs => {
if (this._logsToAdd.length === 0) {
return logs;
}
const nextLogs = logs.concat(this._logsToAdd);
this._logsToAdd = [];
return nextLogs;
});
});
_defineProperty(this, "_cleanUpNodeErrors", chunk => {
if (typeof chunk.msg !== 'string') {
return chunk;
}
if (chunk.msg.match(/\(node:.\d*\)/)) {
// Example: (node:13817) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): SyntaxError: SyntaxError /Users/brent/universe/apps/new-project-template/main.js: Unexpected token (10:6)
// The first part of this is totally useless, so let's remove it.
if (chunk.msg.match(/UnhandledPromiseRejectionWarning/)) {
chunk.msg = chunk.msg.replace(/\(node:.*\(rejection .*\):/, '');
if (chunk.msg.match(/SyntaxError: SyntaxError/)) {
chunk.msg = chunk.msg.replace('SyntaxError: ', '');
}
} else if (chunk.msg.match(/DeprecationWarning/)) {
chunk.msg = '';
}
}
return chunk;
});
this._projectRoot = projectRoot;
this._getCurrentOpenProjectId = getCurrentOpenProjectId || (() => 1);
this._updateLogs = updateLogs;
// Optional properties in case the consumer wants to handle updates on
// its own, eg: for a progress bar
this._onStartBuildBundle = onStartBuildBundle;
this._onProgressBuildBundle = onProgressBuildBundle;
this._onFinishBuildBundle = onFinishBuildBundle;
// Optional function for creating custom code frame snippet
// (e.g. with terminal colors) from a syntax error.
this._getSnippetForError = getSnippetForError;
this._attachLoggerStream();
}
_attachLoggerStream() {
this.projectId = this._getCurrentOpenProjectId();
_internal().ProjectUtils.attachLoggerStream(this._projectRoot, {
stream: {
write: this._handleChunk.bind(this)
},
type: 'raw'
});
}
_handleChunk(chunk) {
if (chunk.tag !== 'metro' && chunk.tag !== 'expo') {
return;
} else if (this._getCurrentOpenProjectId() !== this.projectId) {
// TODO: We should be confident that we are properly unsubscribing
// from the stream rather than doing a defensive check like this.
return;
}
chunk = this._maybeParseMsgJSON(chunk);
chunk = this._cleanUpNodeErrors(chunk);
if (chunk.tag === 'metro') {
this._handleMetroEvent(chunk);
} else if (typeof chunk.msg === 'string' && chunk.msg.match(/\w/) && chunk.msg[0] !== '{') {
this._enqueueAppendLogChunk(chunk);
}
}
_handleMetroEvent(originalChunk) {
const chunk = {
...originalChunk
};
const {
msg
} = chunk;
if (typeof msg === 'string') {
if (msg.includes('HTTP/1.1') && !_getenv().default.boolish('EXPO_DEBUG', false)) {
// Do nothing with this message - we want to filter out network requests logged by Metro.
} else {
// If Metro crashes for some reason, it may log an error message as a plain string to stderr.
this._enqueueAppendLogChunk(chunk);
}
return;
}
switch (msg.type) {
// Bundle transform events
case 'bundle_build_started':
case 'bundle_transform_progressed':
case 'bundle_build_failed':
case 'bundle_build_done':
this._handleBundleTransformEvent(chunk);
return;
case 'initialize_started':
chunk._metroEventType = 'METRO_INITIALIZE_STARTED';
chunk.msg = 'Starting Metro Bundler';
break;
case 'initialize_done':
chunk.msg = `Started Metro Bundler`;
break;
case 'initialize_failed':
{
// SDK <=22
const code = msg.error.code;
chunk.msg = code === 'EADDRINUSE' ? `Metro Bundler can't listen on port ${msg.port}. The port is in use.` : `Metro Bundler failed to start. (code: ${code})`;
break;
}
case 'bundling_error':
chunk.msg = this._formatModuleResolutionError(msg.error) || this._formatBundlingError(msg.error) || msg;
chunk.level = _internal().Logger.ERROR;
break;
case 'bundling_warning':
chunk.msg = msg.warning;
chunk.level = _internal().Logger.WARN;
break;
case 'transform_cache_reset':
chunk.msg = 'Your JavaScript transform cache is empty, rebuilding (this may take a minute).';
break;
case 'hmr_client_error':
chunk.msg = `A WebSocket client got a connection error. Please reload your device to get HMR working again.`;
break;
case 'global_cache_disabled':
if (msg.reason === 'too_many_errors') {
chunk.msg = 'The global cache is now disabled because it has been failing too many times.';
} else if (msg.reason === 'too_many_misses') {
chunk.msg = `The global cache is now disabled because it has been missing too many consecutive keys.`;
} else {
chunk.msg = `The global cache is now disabled. Reason: ${msg.reason}`;
}
break;
case 'worker_stdout_chunk':
chunk.msg = this._formatWorkerChunk('stdout', msg.chunk);
break;
case 'worker_stderr_chunk':
chunk.msg = this._formatWorkerChunk('stderr', msg.chunk);
break;
// Ignored events.
case 'client_log':
case 'dep_graph_loading':
case 'dep_graph_loaded':
case 'global_cache_error':
case 'transformer_load_started':
case 'transformer_load_done':
return;
default:
chunk.msg = `Unrecognized event: ${JSON.stringify(msg)}`;
break;
}
this._enqueueAppendLogChunk(chunk);
}
// A cache of { [buildID]: BundleDetails } which can be used to
// add more contextual logs. BundleDetails is currently only sent with `bundle_build_started`
// so we need to cache the details in order to print the platform info with other event types.
static getPlatformTagForBuildDetails(bundleDetails) {
var _bundleDetails$platfo;
const platform = (_bundleDetails$platfo = bundleDetails === null || bundleDetails === void 0 ? void 0 : bundleDetails.platform) !== null && _bundleDetails$platfo !== void 0 ? _bundleDetails$platfo : null;
if (platform) {
const formatted = {
ios: 'iOS',
android: 'Android',
web: 'Web'
}[platform] || platform;
return `${_chalk().default.bold(formatted)} `;
}
return '';
}
_handleNewBundleTransformStarted(chunk, bundleDetails) {
if (this._bundleBuildChunkID) {
return;
}
this._bundleBuildChunkID = chunk.id;
this._bundleBuildStart = new Date();
chunk.msg = 'Building JavaScript bundle';
if (this._onStartBuildBundle) {
this._onStartBuildBundle({
chunk,
bundleDetails
});
} else {
this._enqueueAppendLogChunk(chunk);
}
}
_handleUpdateBundleTransformProgress(progressChunk, bundleDetails) {
const msg = progressChunk.msg;
let percentProgress;
let bundleComplete = false;
if (msg.type === 'bundle_build_done') {
percentProgress = 100;
bundleComplete = true;
if (this._bundleBuildStart) {
const duration = new Date().getTime() - this._bundleBuildStart.getTime();
progressChunk.msg = `Building JavaScript bundle: finished in ${duration}ms.`;
} else {
progressChunk.msg = `Building JavaScript bundle: finished.`;
}
} else if (msg.type === 'bundle_build_failed') {
percentProgress = -1;
bundleComplete = true;
progressChunk.msg = `Building JavaScript bundle: error`;
progressChunk.level = _internal().Logger.ERROR;
} else if (msg.type === 'bundle_transform_progressed') {
if (msg.percentage) {
percentProgress = msg.percentage * 100;
} else {
percentProgress = msg.transformedFileCount / msg.totalFileCount * 100;
// percentProgress = Math.floor((msg.transformedFileCount / msg.totalFileCount) * 100);
}
const roundedPercentProgress = Math.floor(100 * percentProgress) / 100;
progressChunk.msg = `Building JavaScript bundle: ${roundedPercentProgress}%`;
} else {
return;
}
if (this._bundleBuildChunkID) {
progressChunk.id = this._bundleBuildChunkID;
}
if (this._onProgressBuildBundle) {
this._onProgressBuildBundle({
progress: percentProgress,
start: this._bundleBuildStart,
chunk: progressChunk,
bundleDetails
});
if (bundleComplete) {
if (this._onFinishBuildBundle && this._bundleBuildStart) {
const error = msg.type === 'bundle_build_failed' ? 'Build failed' : null;
this._onFinishBuildBundle({
error,
start: this._bundleBuildStart,
end: new Date(),
chunk: progressChunk,
bundleDetails
});
}
this._bundleBuildStart = null;
this._bundleBuildChunkID = null;
}
} else {
this._updateLogs(logs => {
if (!logs || !logs.length) {
return [];
}
logs.forEach(log => {
if (log.id === this._bundleBuildChunkID) {
log.msg = progressChunk.msg;
}
});
if (bundleComplete) {
this._bundleBuildChunkID = null;
}
return [...logs];
});
}
}
_formatModuleResolutionError(error) {
if (!error.message) {
return null;
}
const match = /^Unable to resolve module `(.+?)`/.exec(error.message);
const originModulePath = error.originModulePath;
if (!match || !originModulePath) {
return null;
}
const moduleName = match[1];
const relativePath = _path().default.relative(this._projectRoot, originModulePath);
const DOCS_PAGE_URL = 'https://docs.expo.dev/workflow/using-libraries/#using-third-party-libraries';
if (NODE_STDLIB_MODULES.includes(moduleName)) {
if (originModulePath.includes('node_modules')) {
return `The package at "${relativePath}" attempted to import the Node standard library module "${moduleName}". It failed because the native React runtime does not include the Node standard library. Read more at ${DOCS_PAGE_URL}`;
} else {
return `You attempted attempted to import the Node standard library module "${moduleName}" from "${relativePath}". It failed because the native React runtime does not include the Node standard library. Read more at ${DOCS_PAGE_URL}`;
}
}
return `Unable to resolve "${moduleName}" from "${relativePath}"`;
}
_formatBundlingError(error) {
let message = error.message;
if (!message && Array.isArray(error.errors) && error.errors.length) {
message = error.errors[0].description;
}
if (!message) {
return null;
}
message = _chalk().default.red(message);
const snippet = this._getSnippetForError && this._getSnippetForError(error) || error.snippet;
if (snippet) {
message += `\n${snippet}`;
}
// Import errors are already pretty useful and don't need extra info added to them.
const isAmbiguousError = !error.name || ['SyntaxError'].includes(error.name);
// When you have a basic syntax error in application code it will tell you the file
// and usually also provide a well informed error.
const isComprehensiveTransformError = error.type === 'TransformError' && error.filename;
// console.log(require('util').inspect(error, { depth: 4 }));
if (error.stack && isAmbiguousError && !isComprehensiveTransformError) {
message += `\n${_chalk().default.gray(error.stack)}`;
}
return message;
}
_formatWorkerChunk(origin, chunk) {
return chunk;
// const lines = chunk.split('\n');
// if (lines.length >= 1 && lines[lines.length - 1] === '') {
// lines.splice(lines.length - 1, 1);
// }
// return lines.map(line => `transform[${origin}]: ${line}`).join('\n');
}
_enqueueAppendLogChunk(chunk) {
if (!chunk.shouldHide) {
this._logsToAdd.push(chunk);
this._enqueueFlushLogsToAdd();
}
}
_maybeParseMsgJSON(chunk) {
try {
const parsedMsg = JSON.parse(chunk.msg);
chunk.msg = parsedMsg;
} catch {
// non-JSON message
}
return chunk;
}
}
exports.default = PackagerLogsStream;
const NODE_STDLIB_MODULES = ['assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'crypto', 'dgram', 'dns', 'domain', 'events', 'fs', 'http', 'https', 'net', 'os', 'path', 'punycode', 'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib'];
//# sourceMappingURL=PackagerLogsStream.js.map