metro-bundler: Terminal: remove global state

Reviewed By: cpojer

Differential Revision: D5155075

fbshipit-source-id: 1d64bdd0ae13087aca620b65892832e3a1229c4a
This commit is contained in:
Jean Lauliac 2017-06-01 02:57:56 -07:00 committed by Facebook Github Bot
parent 8af0267fdf
commit 98847474cd
7 changed files with 61 additions and 63 deletions

View File

@ -21,7 +21,6 @@ const mime = require('mime-types');
const parsePlatformFilePath = require('../node-haste/lib/parsePlatformFilePath'); const parsePlatformFilePath = require('../node-haste/lib/parsePlatformFilePath');
const path = require('path'); const path = require('path');
const symbolicate = require('./symbolicate'); const symbolicate = require('./symbolicate');
const terminal = require('../lib/terminal');
const url = require('url'); const url = require('url');
const debug = require('debug')('RNP:Server'); const debug = require('debug')('RNP:Server');
@ -396,7 +395,8 @@ class Server {
e => { e => {
res.writeHead(500); res.writeHead(500);
res.end('Internal Error'); res.end('Internal Error');
terminal.log(e.stack); // eslint-disable-line no-console-disallow // FIXME: $FlowFixMe: that's a hack, doesn't work with JSON-mode output
this._reporter.terminal && this._reporter.terminal.log(e.stack);
} }
); );
} else { } else {

View File

@ -7,6 +7,7 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
* *
* @flow * @flow
* @format
*/ */
'use strict'; 'use strict';
@ -84,7 +85,6 @@ function getTTYStream(stream: net$Socket): ?tty.WriteStream {
* single responsibility of handling status messages. * single responsibility of handling status messages.
*/ */
class Terminal { class Terminal {
_logLines: Array<string>; _logLines: Array<string>;
_nextStatusStr: string; _nextStatusStr: string;
_scheduleUpdate: () => void; _scheduleUpdate: () => void;
@ -120,7 +120,10 @@ class Terminal {
}); });
this._logLines = []; this._logLines = [];
if (ttyStream != null) { if (ttyStream != null) {
this._nextStatusStr = chunkString(this._nextStatusStr, ttyStream.columns).join('\n'); this._nextStatusStr = chunkString(
this._nextStatusStr,
ttyStream.columns,
).join('\n');
_stream.write(this._nextStatusStr); _stream.write(this._nextStatusStr);
} }
this._statusStr = this._nextStatusStr; this._statusStr = this._nextStatusStr;
@ -158,25 +161,6 @@ class Terminal {
this.log(this._nextStatusStr); this.log(this._nextStatusStr);
this._nextStatusStr = ''; this._nextStatusStr = '';
} }
} }
/** module.exports = Terminal;
* On the same pattern as node.js `console` module, we export the stdout-based
* terminal at the top-level, but provide access to the Terminal class as a
* field (so it can be used, for instance, with stderr).
*/
class GlobalTerminal extends Terminal {
Terminal: Class<Terminal>;
constructor() {
/* $FlowFixMe: Flow is wrong, Node.js docs specify that process.stdout is an
* instance of a net.Socket (a local socket, not network). */
super(process.stdout);
this.Terminal = Terminal;
}
}
module.exports = new GlobalTerminal();

View File

@ -15,10 +15,10 @@ const chalk = require('chalk');
const formatBanner = require('./formatBanner'); const formatBanner = require('./formatBanner');
const path = require('path'); const path = require('path');
const reporting = require('./reporting'); const reporting = require('./reporting');
const terminal = require('./terminal');
const throttle = require('lodash/throttle'); const throttle = require('lodash/throttle');
const util = require('util'); const util = require('util');
import type Terminal from './TerminalClass';
import type {ReportableEvent, GlobalCacheDisabledReason} from './reporting'; import type {ReportableEvent, GlobalCacheDisabledReason} from './reporting';
const DEP_GRAPH_MESSAGE = 'Loading dependency graph'; const DEP_GRAPH_MESSAGE = 'Loading dependency graph';
@ -72,12 +72,15 @@ class TerminalReporter {
totalFileCount: number, totalFileCount: number,
}) => void; }) => void;
constructor() { +terminal: Terminal;
constructor(terminal: Terminal) {
this._dependencyGraphHasLoaded = false; this._dependencyGraphHasLoaded = false;
this._activeBundles = new Map(); this._activeBundles = new Map();
this._scheduleUpdateBundleProgress = throttle(data => { this._scheduleUpdateBundleProgress = throttle(data => {
this.update({...data, type: 'bundle_transform_progressed_throttled'}); this.update({...data, type: 'bundle_transform_progressed_throttled'});
}, 100); }, 100);
(this: any).terminal = terminal;
} }
/** /**
@ -106,10 +109,18 @@ class TerminalReporter {
const format = GLOBAL_CACHE_DISABLED_MESSAGE_FORMAT; const format = GLOBAL_CACHE_DISABLED_MESSAGE_FORMAT;
switch (reason) { switch (reason) {
case 'too_many_errors': case 'too_many_errors':
reporting.logWarning(terminal, format, 'it has been failing too many times.'); reporting.logWarning(
this.terminal,
format,
'it has been failing too many times.',
);
break; break;
case 'too_many_misses': case 'too_many_misses':
reporting.logWarning(terminal, format, 'it has been missing too many consecutive keys.'); reporting.logWarning(
this.terminal,
format,
'it has been missing too many consecutive keys.',
);
break; break;
} }
} }
@ -122,7 +133,7 @@ class TerminalReporter {
ratio: 1, ratio: 1,
transformedFileCount: progress.totalFileCount, transformedFileCount: progress.totalFileCount,
}, 'done'); }, 'done');
terminal.log(msg); this.terminal.log(msg);
} }
} }
@ -130,12 +141,12 @@ class TerminalReporter {
const progress = this._activeBundles.get(buildID); const progress = this._activeBundles.get(buildID);
if (progress != null) { if (progress != null) {
const msg = this._getBundleStatusMessage(progress, 'failed'); const msg = this._getBundleStatusMessage(progress, 'failed');
terminal.log(msg); this.terminal.log(msg);
} }
} }
_logPackagerInitializing(port: number, projectRoots: $ReadOnlyArray<string>) { _logPackagerInitializing(port: number, projectRoots: $ReadOnlyArray<string>) {
terminal.log( this.terminal.log(
formatBanner( formatBanner(
'Running packager on port ' + 'Running packager on port ' +
port + port +
@ -152,7 +163,7 @@ class TerminalReporter {
) )
); );
terminal.log( this.terminal.log(
'Looking for JS files in\n ', 'Looking for JS files in\n ',
chalk.dim(projectRoots.join('\n ')), chalk.dim(projectRoots.join('\n ')),
'\n' '\n'
@ -161,23 +172,23 @@ class TerminalReporter {
_logPackagerInitializingFailed(port: number, error: Error) { _logPackagerInitializingFailed(port: number, error: Error) {
if (error.code === 'EADDRINUSE') { if (error.code === 'EADDRINUSE') {
terminal.log( this.terminal.log(
chalk.bgRed.bold(' ERROR '), chalk.bgRed.bold(' ERROR '),
chalk.red("Packager can't listen on port", chalk.bold(port)) chalk.red("Packager can't listen on port", chalk.bold(port))
); );
terminal.log('Most likely another process is already using this port'); this.terminal.log('Most likely another process is already using this port');
terminal.log('Run the following command to find out which process:'); this.terminal.log('Run the following command to find out which process:');
terminal.log('\n ', chalk.bold('lsof -i :' + port), '\n'); this.terminal.log('\n ', chalk.bold('lsof -i :' + port), '\n');
terminal.log('Then, you can either shut down the other process:'); this.terminal.log('Then, you can either shut down the other process:');
terminal.log('\n ', chalk.bold('kill -9 <PID>'), '\n'); this.terminal.log('\n ', chalk.bold('kill -9 <PID>'), '\n');
terminal.log('or run packager on different port.'); this.terminal.log('or run packager on different port.');
} else { } else {
terminal.log(chalk.bgRed.bold(' ERROR '), chalk.red(error.message)); this.terminal.log(chalk.bgRed.bold(' ERROR '), chalk.red(error.message));
const errorAttributes = JSON.stringify(error); const errorAttributes = JSON.stringify(error);
if (errorAttributes !== '{}') { if (errorAttributes !== '{}') {
terminal.log(chalk.red(errorAttributes)); this.terminal.log(chalk.red(errorAttributes));
} }
terminal.log(chalk.red(error.stack)); this.terminal.log(chalk.red(error.stack));
} }
} }
@ -191,7 +202,7 @@ class TerminalReporter {
this._logPackagerInitializing(event.port, event.projectRoots); this._logPackagerInitializing(event.port, event.projectRoots);
break; break;
case 'initialize_packager_done': case 'initialize_packager_done':
terminal.log('\nReact packager ready.\n'); this.terminal.log('\nReact packager ready.\n');
break; break;
case 'initialize_packager_failed': case 'initialize_packager_failed':
this._logPackagerInitializingFailed(event.port, event.error); this._logPackagerInitializingFailed(event.port, event.error);
@ -206,13 +217,13 @@ class TerminalReporter {
this._logBundlingError(event.error); this._logBundlingError(event.error);
break; break;
case 'dep_graph_loaded': case 'dep_graph_loaded':
terminal.log(`${DEP_GRAPH_MESSAGE}, done.`); this.terminal.log(`${DEP_GRAPH_MESSAGE}, done.`);
break; break;
case 'global_cache_disabled': case 'global_cache_disabled':
this._logCacheDisabled(event.reason); this._logCacheDisabled(event.reason);
break; break;
case 'transform_cache_reset': case 'transform_cache_reset':
reporting.logWarning(terminal, 'the transform cache was reset.'); reporting.logWarning(this.terminal, 'the transform cache was reset.');
break; break;
case 'worker_stdout_chunk': case 'worker_stdout_chunk':
this._logWorkerChunk('stdout', event.chunk); this._logWorkerChunk('stdout', event.chunk);
@ -230,7 +241,7 @@ class TerminalReporter {
*/ */
_logBundlingError(error: Error) { _logBundlingError(error: Error) {
const str = JSON.stringify(error.message); const str = JSON.stringify(error.message);
reporting.logError(terminal, 'bundling failed: %s', str); reporting.logError(this.terminal, 'bundling failed: %s', str);
} }
_logWorkerChunk(origin: 'stdout' | 'stderr', chunk: string) { _logWorkerChunk(origin: 'stdout' | 'stderr', chunk: string) {
@ -239,7 +250,7 @@ class TerminalReporter {
lines.splice(lines.length - 1, 1); lines.splice(lines.length - 1, 1);
} }
lines.forEach(line => { lines.forEach(line => {
terminal.log(`transform[${origin}]: ${line}`); this.terminal.log(`transform[${origin}]: ${line}`);
}); });
} }
@ -335,7 +346,7 @@ class TerminalReporter {
update(event: TerminalReportableEvent) { update(event: TerminalReportableEvent) {
this._log(event); this._log(event);
this._updateState(event); this._updateState(event);
terminal.status(this._getStatusMessage()); this.terminal.status(this._getStatusMessage());
} }
} }

View File

@ -19,7 +19,6 @@ const invariant = require('fbjs/lib/invariant');
const mkdirp = require('mkdirp'); const mkdirp = require('mkdirp');
const path = require('path'); const path = require('path');
const rimraf = require('rimraf'); const rimraf = require('rimraf');
const terminal = require('../lib/terminal');
const writeFileAtomicSync = require('write-file-atomic').sync; const writeFileAtomicSync = require('write-file-atomic').sync;
import type {Options as WorkerOptions} from '../JSTransformer/worker'; import type {Options as WorkerOptions} from '../JSTransformer/worker';
@ -226,7 +225,7 @@ class FileBasedCache {
lastCollected == null || lastCollected == null ||
Date.now() - lastCollected > GARBAGE_COLLECTION_PERIOD Date.now() - lastCollected > GARBAGE_COLLECTION_PERIOD
) { ) {
this._collectSyncNoThrow(); this._collectSyncNoThrow(options.reporter);
} }
} }
@ -241,16 +240,20 @@ class FileBasedCache {
* We want to avoid preventing tool use if the cleanup fails for some reason, * We want to avoid preventing tool use if the cleanup fails for some reason,
* but still provide some chance for people to report/fix things. * but still provide some chance for people to report/fix things.
*/ */
_collectSyncNoThrow() { _collectSyncNoThrow(reporter: Reporter) {
try { try {
this._collectCacheIfOldSync(); this._collectCacheIfOldSync();
} catch (error) { } catch (error) {
// FIXME: $FlowFixMe: this is a hack, only works for TerminalReporter
const {terminal} = reporter;
if (terminal != null) {
terminal.log(error.stack); terminal.log(error.stack);
terminal.log( terminal.log(
'Error: Cleaning up the cache folder failed. Continuing anyway.', 'Error: Cleaning up the cache folder failed. Continuing anyway.',
); );
terminal.log('The cache folder is: %s', this._rootPath); terminal.log('The cache folder is: %s', this._rootPath);
} }
}
this._lastCollected = Date.now(); this._lastCollected = Date.now();
} }

View File

@ -9,7 +9,7 @@
'use strict'; 'use strict';
jest.dontMock('../terminal').dontMock('lodash/throttle'); jest.dontMock('../TerminalClass').dontMock('lodash/throttle');
jest.mock('readline', () => ({ jest.mock('readline', () => ({
moveCursor: (stream, dx, dy) => { moveCursor: (stream, dx, dy) => {
@ -27,14 +27,14 @@ jest.mock('readline', () => ({
}, },
})); }));
describe('terminal', () => { describe('Terminal', () => {
beforeEach(() => { beforeEach(() => {
jest.resetModules(); jest.resetModules();
}); });
function prepare(isTTY) { function prepare(isTTY) {
const {Terminal} = require('../terminal'); const Terminal = require('../TerminalClass');
const lines = 10; const lines = 10;
const columns = 10; const columns = 10;
const stream = Object.create( const stream = Object.create(

View File

@ -88,7 +88,7 @@ describe('TransformCaching.FileBasedCache', () => {
const {result} = args; const {result} = args;
const cachedResult = transformCache.readSync({ const cachedResult = transformCache.readSync({
...args, ...args,
cacheOptions: {resetCache: false}, cacheOptions: {reporter: {}, resetCache: false},
}); });
expect(cachedResult.result).toEqual(result); expect(cachedResult.result).toEqual(result);
}); });
@ -121,7 +121,7 @@ describe('TransformCaching.FileBasedCache', () => {
const {result} = args; const {result} = args;
const cachedResult = transformCache.readSync({ const cachedResult = transformCache.readSync({
...args, ...args,
cacheOptions: {resetCache: false}, cacheOptions: {reporter: {}, resetCache: false},
}); });
expect(cachedResult.result).toEqual(result); expect(cachedResult.result).toEqual(result);
}); });
@ -129,7 +129,7 @@ describe('TransformCaching.FileBasedCache', () => {
allCases.forEach(entry => { allCases.forEach(entry => {
const cachedResult = transformCache.readSync({ const cachedResult = transformCache.readSync({
...argsFor(entry), ...argsFor(entry),
cacheOptions: {resetCache: false}, cacheOptions: {reporter: {}, resetCache: false},
}); });
expect(cachedResult.result).toBeNull(); expect(cachedResult.result).toBeNull();
expect(cachedResult.outdatedDependencies).toEqual(['foo', 'bar']); expect(cachedResult.outdatedDependencies).toEqual(['foo', 'bar']);

View File

@ -14,7 +14,7 @@
const chalk = require('chalk'); const chalk = require('chalk');
const util = require('util'); const util = require('util');
import type {Terminal} from './terminal'; import type Terminal from './TerminalClass';
export type GlobalCacheDisabledReason = 'too_many_errors' | 'too_many_misses'; export type GlobalCacheDisabledReason = 'too_many_errors' | 'too_many_misses';