PHP WebShell
Текущая директория: /usr/lib/node_modules/bitgo/node_modules/metro/src/lib
Просмотр файла: TerminalReporter.js.flow
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
* @oncall react_native
*/
import type {BundleDetails, ReportableEvent} from './reporting';
import type {Terminal} from 'metro-core';
import type {HealthCheckResult, WatcherStatus} from 'metro-file-map';
import logToConsole from './logToConsole';
import * as reporting from './reporting';
import chalk from 'chalk';
import throttle from 'lodash.throttle';
import {AmbiguousModuleResolutionError} from 'metro-core';
import path from 'path';
type BundleProgress = {
bundleDetails: BundleDetails,
transformedFileCount: number,
totalFileCount: number,
ratio: number,
isPrefetch?: boolean,
...
};
export type TerminalReportableEvent =
| ReportableEvent
| {
buildID: string,
type: 'bundle_transform_progressed_throttled',
transformedFileCount: number,
totalFileCount: number,
...
}
| {
type: 'unstable_server_log',
level: 'info' | 'warn' | 'error',
data: string | Array<mixed>,
...
}
| {
type: 'unstable_server_menu_updated',
message: string,
...
}
| {
type: 'unstable_server_menu_cleared',
...
};
type BuildPhase = 'in_progress' | 'done' | 'failed';
type SnippetError = ErrnoError &
interface {
filename?: string,
snippet?: string,
};
const DARK_BLOCK_CHAR = '\u2593';
const LIGHT_BLOCK_CHAR = '\u2591';
const MAX_PROGRESS_BAR_CHAR_WIDTH = 16;
/**
* We try to print useful information to the terminal for interactive builds.
* This implements the `Reporter` interface from the './reporting' module.
*/
export default class TerminalReporter {
/**
* The bundle builds for which we are actively maintaining the status on the
* terminal, ie. showing a progress bar. There can be several bundles being
* built at the same time.
*/
_activeBundles: Map<string, BundleProgress>;
_interactionStatus: ?string;
_scheduleUpdateBundleProgress: {
(data: {
buildID: string,
transformedFileCount: number,
totalFileCount: number,
...
}): void,
cancel(): void,
};
_prevHealthCheckResult: ?HealthCheckResult;
+terminal: Terminal;
constructor(terminal: Terminal) {
this._activeBundles = new Map();
this._scheduleUpdateBundleProgress = throttle(data => {
this.update({...data, type: 'bundle_transform_progressed_throttled'});
}, 100);
this.terminal = terminal;
}
/**
* Construct a message that represents the progress of a
* single bundle build, for example:
*
* BUNDLE path/to/bundle.js ▓▓▓▓▓░░░░░░░░░░░ 36.6% (4790/7922)
*/
_getBundleStatusMessage(
{
bundleDetails: {entryFile, bundleType},
transformedFileCount,
totalFileCount,
ratio,
isPrefetch,
}: BundleProgress,
phase: BuildPhase,
): string {
if (isPrefetch) {
bundleType = 'PREBUNDLE';
}
const localPath = path.relative('.', entryFile);
const filledBar = Math.floor(ratio * MAX_PROGRESS_BAR_CHAR_WIDTH);
const bundleTypeColor =
phase === 'done'
? chalk.green
: phase === 'failed'
? chalk.red
: chalk.yellow;
const progress =
phase === 'in_progress'
? chalk.green.bgGreen(DARK_BLOCK_CHAR.repeat(filledBar)) +
chalk.bgWhite.white(
LIGHT_BLOCK_CHAR.repeat(MAX_PROGRESS_BAR_CHAR_WIDTH - filledBar),
) +
chalk.bold(` ${(100 * ratio).toFixed(1)}% `) +
chalk.dim(`(${transformedFileCount}/${totalFileCount})`)
: '';
return (
bundleTypeColor.inverse.bold(` ${bundleType.toUpperCase()} `) +
chalk.reset.dim(` ${path.dirname(localPath)}/`) +
chalk.bold(path.basename(localPath)) +
' ' +
progress
);
}
_logBundleBuildDone(buildID: string): void {
const progress = this._activeBundles.get(buildID);
if (progress != null) {
const msg = this._getBundleStatusMessage(
{
...progress,
ratio: 1,
transformedFileCount: progress.totalFileCount,
},
'done',
);
this.terminal.log(msg);
this._activeBundles.delete(buildID);
}
}
_logBundleBuildFailed(buildID: string): void {
const progress = this._activeBundles.get(buildID);
if (progress != null) {
const msg = this._getBundleStatusMessage(progress, 'failed');
this.terminal.log(msg);
}
}
_logInitializing(port: number, hasReducedPerformance: boolean): void {
const logo = [
'',
' ▒▒▓▓▓▓▒▒',
' ▒▓▓▓▒▒░░▒▒▓▓▓▒',
' ▒▓▓▓▓░░░▒▒▒▒░░░▓▓▓▓▒',
' ▓▓▒▒▒▓▓▓▓▓▓▓▓▓▓▓▓▒▒▒▓▓',
' ▓▓░░░░░▒▓▓▓▓▓▓▒░░░░░▓▓',
' ▓▓░░▓▓▒░░░▒▒░░░▒▓▒░░▓▓',
' ▓▓░░▓▓▓▓▓▒▒▒▒▓▓▓▓▒░░▓▓',
' ▓▓░░▓▓▓▓▓▓▓▓▓▓▓▓▓▒░░▓▓',
' ▓▓▒░░▒▒▓▓▓▓▓▓▓▓▒░░░▒▓▓',
' ▒▓▓▓▒░░░▒▓▓▒░░░▒▓▓▓▒',
' ▒▓▓▓▒░░░░▒▓▓▓▒',
' ▒▒▓▓▓▓▒▒',
'',
'',
];
const color = hasReducedPerformance ? chalk.red : chalk.blue;
this.terminal.log(color(logo.join('\n')));
}
_logInitializingFailed(port: number, error: SnippetError): void {
if (error.code === 'EADDRINUSE') {
this.terminal.log(
chalk.bgRed.bold(' ERROR '),
chalk.red("Metro can't listen on port", chalk.bold(String(port))),
);
this.terminal.log(
'Most likely another process is already using this port',
);
this.terminal.log('Run the following command to find out which process:');
this.terminal.log('\n ', chalk.bold('lsof -i :' + port), '\n');
this.terminal.log('Then, you can either shut down the other process:');
this.terminal.log('\n ', chalk.bold('kill -9 <PID>'), '\n');
this.terminal.log('or run Metro on different port.');
} else {
this.terminal.log(chalk.bgRed.bold(' ERROR '), chalk.red(error.message));
const errorAttributes = JSON.stringify(error);
if (errorAttributes !== '{}') {
this.terminal.log(chalk.red(errorAttributes));
}
this.terminal.log(chalk.red(error.stack));
}
}
/**
* This function is only concerned with logging and should not do state
* or terminal status updates.
*/
_log(event: TerminalReportableEvent): void {
switch (event.type) {
case 'initialize_started':
this._logInitializing(event.port, event.hasReducedPerformance);
break;
case 'initialize_failed':
this._logInitializingFailed(event.port, event.error);
break;
case 'bundle_build_done':
this._logBundleBuildDone(event.buildID);
break;
case 'bundle_save_log':
this.terminal.log('LOG:' + event.message);
break;
case 'bundle_build_failed':
this._logBundleBuildFailed(event.buildID);
break;
case 'bundling_error':
this._logBundlingError(event.error);
break;
case 'resolver_warning':
this._logWarning(event.message);
break;
case 'transform_cache_reset':
reporting.logWarning(this.terminal, 'the transform cache was reset.');
break;
case 'worker_stdout_chunk':
this._logWorkerChunk('stdout', event.chunk);
break;
case 'worker_stderr_chunk':
this._logWorkerChunk('stderr', event.chunk);
break;
case 'hmr_client_error':
this._logHmrClientError(event.error);
break;
case 'client_log':
logToConsole(this.terminal, event.level, ...event.data);
break;
case 'unstable_server_log':
const logFn = {
info: reporting.logInfo,
warn: reporting.logWarning,
error: reporting.logError,
}[event.level];
const [format, ...args] = [].concat(event.data);
logFn(this.terminal, String(format), ...args);
break;
case 'dep_graph_loading':
const color = event.hasReducedPerformance ? chalk.red : chalk.blue;
// eslint-disable-next-line import/no-commonjs
const version = 'v' + require('../../package.json').version;
this.terminal.log(
color.bold(
' '.repeat(19 - version.length / 2),
'Welcome to Metro ' + chalk.white(version) + '\n',
) + chalk.dim(' Fast - Scalable - Integrated\n\n'),
);
if (event.hasReducedPerformance) {
this.terminal.log(
chalk.red(
'Metro is operating with reduced performance.\n' +
'Please fix the problem above and restart Metro.\n\n',
),
);
}
break;
case 'watcher_health_check_result':
this._logWatcherHealthCheckResult(event.result);
break;
case 'watcher_status':
this._logWatcherStatus(event.status);
break;
}
}
/**
* We do not want to log the whole stacktrace for bundling error, because
* these are operational errors, not programming errors, and the stacktrace
* is not actionable to end users.
*/
_logBundlingError(error: SnippetError): void {
if (error instanceof AmbiguousModuleResolutionError) {
const he = error.hasteError;
const message =
'ambiguous resolution: module `' +
`${error.fromModulePath}\` tries to require \`${he.hasteName}\`, ` +
'but there are several files providing this module. You can delete ' +
'or fix them: \n\n' +
Object.keys(he.duplicatesSet)
.sort()
.map(dupFilePath => ` * \`${dupFilePath}\`\n`)
.join('');
reporting.logError(this.terminal, message);
return;
}
let {message} = error;
// Do not log the stack trace for SyntaxError (because it will always be in
// the parser, which is not helpful).
if (!(error instanceof SyntaxError)) {
if (error.snippet == null && error.stack != null) {
message = error.stack;
}
}
if (error.filename && !message.includes(error.filename)) {
// $FlowFixMe[incompatible-type]
message += ` [${error.filename}]`;
}
if (error.snippet != null) {
message += '\n' + error.snippet;
}
reporting.logError(this.terminal, message);
}
_logWorkerChunk(origin: 'stdout' | 'stderr', chunk: string): void {
const lines = chunk.split('\n');
if (lines.length >= 1 && lines[lines.length - 1] === '') {
lines.splice(lines.length - 1, 1);
}
lines.forEach((line: string) => {
this.terminal.log(`transform[${origin}]: ${line}`);
});
}
/**
* Because we know the `totalFileCount` is going to progressively increase
* starting with 1:
* - We use Math.max(totalFileCount, 10) to prevent the ratio to raise too
* quickly when the total file count is low. (e.g 1/2 5/6)
* - We prevent the ratio from going backwards.
* - Instead, we use Math.pow(ratio, 2) to as a conservative measure of progress.
*/
_updateBundleProgress({
buildID,
transformedFileCount,
totalFileCount,
}: {
buildID: string,
transformedFileCount: number,
totalFileCount: number,
...
}): void {
const currentProgress = this._activeBundles.get(buildID);
if (currentProgress == null) {
return;
}
const ratio = Math.min(
Math.max(
Math.pow(transformedFileCount / Math.max(totalFileCount, 10), 2),
currentProgress.ratio,
),
0.999, // make sure not to go above 99.9% to not get rounded to 100%,
);
// $FlowFixMe[unsafe-object-assign]
Object.assign(currentProgress, {
ratio,
transformedFileCount,
totalFileCount,
});
}
/**
* This function is exclusively concerned with updating the internal state.
* No logging or status updates should be done at this point.
*/
_updateState(event: TerminalReportableEvent): void {
switch (event.type) {
case 'bundle_build_done':
case 'bundle_build_failed':
this._activeBundles.delete(event.buildID);
break;
case 'bundle_build_started':
const bundleProgress: BundleProgress = {
bundleDetails: event.bundleDetails,
transformedFileCount: 0,
totalFileCount: 1,
ratio: 0,
isPrefetch: event.isPrefetch,
};
this._activeBundles.set(event.buildID, bundleProgress);
break;
case 'bundle_transform_progressed_throttled':
this._updateBundleProgress(event);
break;
case 'unstable_server_menu_updated':
this._interactionStatus = event.message;
break;
case 'unstable_server_menu_cleared':
this._interactionStatus = null;
break;
}
}
/**
* Return a status message that is always consistent with the current state
* of the application. Having this single function ensures we don't have
* different callsites overriding each other status messages.
*/
_getStatusMessage(): string {
return Array.from(this._activeBundles.entries())
.map(([_, progress]: [string, BundleProgress]) =>
this._getBundleStatusMessage(progress, 'in_progress'),
)
.concat([this._interactionStatus])
.filter((str: ?string) => str != null)
.join('\n');
}
_logHmrClientError(e: Error): void {
reporting.logError(
this.terminal,
'A WebSocket client got a connection error. Please reload your device ' +
'to get HMR working again: %s',
e,
);
}
_logWarning(message: string): void {
reporting.logWarning(this.terminal, message);
}
_logWatcherHealthCheckResult(result: HealthCheckResult) {
// Don't be spammy; only report changes in status.
if (
!this._prevHealthCheckResult ||
result.type !== this._prevHealthCheckResult.type ||
(result.type === 'timeout' &&
this._prevHealthCheckResult.type === 'timeout' &&
(result.pauseReason ?? null) !==
(this._prevHealthCheckResult.pauseReason ?? null))
) {
const watcherName = "'" + (result.watcher ?? 'unknown') + "'";
switch (result.type) {
case 'success':
// Only report success after a prior failure.
if (this._prevHealthCheckResult) {
this.terminal.log(
chalk.green(`Watcher ${watcherName} is now healthy.`),
);
}
break;
case 'error':
reporting.logWarning(
this.terminal,
'Failed to perform watcher health check. Check whether the project root is writable.',
);
break;
case 'timeout':
const why =
result.pauseReason != null
? ` This may be because: ${result.pauseReason}`
: '';
reporting.logWarning(
this.terminal,
`Watcher ${watcherName} failed to detect a file change within ${result.timeout}ms.` +
why,
);
break;
}
}
this._prevHealthCheckResult = result;
}
_logWatcherStatus(status: WatcherStatus) {
switch (status.type) {
case 'watchman_warning':
const warning =
typeof status.warning === 'string'
? status.warning
: `unknown warning (type: ${typeof status.warning})`;
reporting.logWarning(
this.terminal,
`Watchman \`${status.command}\` returned a warning: ${warning}`,
);
break;
case 'watchman_slow_command':
this.terminal.log(
chalk.dim(
`Waiting for Watchman \`${status.command}\` (${Math.round(
status.timeElapsed / 1000,
)}s)...`,
),
);
break;
case 'watchman_slow_command_complete':
this.terminal.log(
chalk.green(
`Watchman \`${status.command}\` finished after ${(
status.timeElapsed / 1000
).toFixed(1)}s.`,
),
);
break;
}
}
/**
* Single entry point for reporting events. That allows us to implement the
* corresponding JSON reporter easily and have a consistent reporting.
*/
update(event: TerminalReportableEvent): void {
if (event.type === 'bundle_transform_progressed') {
this._scheduleUpdateBundleProgress(event);
return;
}
this._log(event);
this._updateState(event);
this.terminal.status(this._getStatusMessage());
}
}
Выполнить команду
Для локальной разработки. Не используйте в интернете!