Add headless option to dump on disconnect

Summary:
This adds an optional exit strategy that reacts to the client disconnecting rather than a `SIGINT` which can be used for integration tests.

`MiddlewareAPI` is a subset of `Store` and required to work here.

Annoyingly, it's not quite clear to me why this does not work as part of an event loop cycle and requires a `setTimeout`. This doesn't have any negative effects and works in the same way that the SIGINT interruption works, but it's a bit of an eyesore.

Reviewed By: danielbuechele

Differential Revision: D14622111

fbshipit-source-id: e2caca056e478428d977565dc9bc09eefca4230c
This commit is contained in:
Pascal Hartig
2019-03-28 06:47:41 -07:00
committed by Facebook Github Bot
parent ddd06971f1
commit b20d0a4c8b
6 changed files with 58 additions and 28 deletions

View File

@@ -5,15 +5,17 @@
* @format
*/
import path from 'path';
import {createStore} from 'redux';
import reducers from '../src/reducers/index.js';
import {applyMiddleware} from 'redux';
import yargs from 'yargs';
import dispatcher from '../src/dispatcher/index.js';
import {init as initLogger} from '../src/fb-stubs/Logger.js';
import path from 'path';
import reducers from '../src/reducers/index.js';
import {exportStore} from '../src/utils/exportData.js';
// $FlowFixMe this file exist, trust me, flow!
import setup from '../static/setup.js';
import yargs from 'yargs';
import {exportStore} from '../src/utils/exportData.js';
yargs
.usage('$0 [args]')
@@ -37,6 +39,11 @@ yargs
'Enable redux-devtools. Tries to connect to devtools running on port 8181',
type: 'boolean',
});
yargs.option('exit', {
describe: 'Controls when to exit and dump the store to stdout.',
choices: ['sigint', 'disconnect'],
default: 'sigint',
});
yargs.option('v', {
alias: 'verbose',
default: false,
@@ -52,6 +59,7 @@ yargs
function startFlipper({
dev,
verbose,
exit,
'insecure-port': insecurePort,
'secure-port': securePort,
}) {
@@ -85,18 +93,33 @@ function startFlipper({
process.env.FLIPPER_PORTS = `${insecurePort},${securePort}`;
// needs to be required after WebSocket polyfill is loaded
const devToolsEnhancer = require('remote-redux-devtools').default;
const devToolsEnhancer = require('remote-redux-devtools');
const headlessMiddleware = store => next => action => {
if (exit == 'disconnect' && action.type == 'CLIENT_REMOVED') {
// TODO(T42325892): Investigate why the export stalls without exiting the
// current eventloop task here.
setTimeout(() => {
exportStore(store)
.then(output => {
originalConsole.log(output);
process.exit();
})
.catch(console.error);
}, 10);
}
return next(action);
};
setup({});
const store = dev
? createStore(
const store = createStore(
reducers,
devToolsEnhancer({realtime: true, hostname: 'localhost', port: 8181}),
)
: createStore(reducers);
devToolsEnhancer.composeWithDevTools(applyMiddleware(headlessMiddleware)),
);
const logger = initLogger(store, {isHeadless: true});
dispatcher(store, logger);
if (exit == 'sigint') {
process.on('SIGINT', async () => {
try {
originalConsole.log(await exportStore(store));
@@ -106,3 +129,4 @@ function startFlipper({
process.exit();
});
}
}

View File

@@ -25,7 +25,7 @@ export {connect} from 'react-redux';
export {selectPlugin} from './reducers/connections';
export {getPluginKey, getPersistedState} from './utils/pluginUtils.js';
export {default as BaseDevice} from './devices/BaseDevice.js';
export type {Store} from './reducers/index.js';
export type {Store, MiddlewareAPI} from './reducers/index.js';
export {
default as SidebarExtensions,

View File

@@ -9,7 +9,7 @@ import type {KeyboardActions} from './MenuBar.js';
import type {App} from './App.js';
import type {Logger} from './fb-interfaces/Logger.js';
import type Client from './Client.js';
import type {Store} from './reducers/index.js';
import type {Store, MiddlewareAPI} from './reducers/index.js';
import React from 'react';
import type {Node} from 'react';
@@ -86,7 +86,7 @@ export class FlipperBasePlugin<
static exportPersistedState: ?(
callClient: (string, ?Object) => Promise<Object>,
persistedState: ?PersistedState,
store: ?Store,
store: ?MiddlewareAPI,
) => Promise<?PersistedState>;
static getActiveNotifications: ?(
persistedState: PersistedState,

View File

@@ -9,7 +9,7 @@ import type {
ElementID,
Element,
ElementSearchResultSet,
Store,
MiddlewareAPI,
PluginClient,
} from 'flipper';
@@ -61,7 +61,7 @@ export default class Layout extends FlipperPlugin<State, void, PersistedState> {
static exportPersistedState = (
callClient: (string, ?Object) => Promise<Object>,
persistedState: ?PersistedState,
store: ?Store,
store: ?MiddlewareAPI,
): Promise<?PersistedState> => {
const defaultPromise = Promise.resolve(persistedState);
if (!store) {

View File

@@ -37,7 +37,10 @@ import type {
Action as PluginsAction,
} from './plugins.js';
import type {State as UserState, Action as UserAction} from './user.js';
import type {Store as ReduxStore} from 'redux';
import type {
Store as ReduxStore,
MiddlewareAPI as ReduxMiddlewareAPI,
} from 'redux';
type Actions =
| ApplicationAction
@@ -58,6 +61,7 @@ export type State = {|
|};
export type Store = ReduxStore<State, Actions>;
export type MiddlewareAPI = ReduxMiddlewareAPI<State, Actions>;
export default combineReducers<_, Actions>({
application,

View File

@@ -5,7 +5,7 @@
* @format
*/
import {getInstance as getLogger} from '../fb-stubs/Logger';
import type {Store} from '../reducers';
import type {Store, MiddlewareAPI} from '../reducers';
import type {DeviceExport} from '../devices/BaseDevice';
import type {State as PluginStates} from '../reducers/pluginStates';
import type {PluginNotification} from '../reducers/notifications.js';
@@ -183,7 +183,9 @@ export const processStore = async (
return null;
};
export async function getStoreExport(store: Store): Promise<?ExportType> {
export async function getStoreExport(
store: MiddlewareAPI,
): Promise<?ExportType> {
const state = store.getState();
const {clients} = state.connections;
const {pluginStates} = state;
@@ -234,7 +236,7 @@ export async function getStoreExport(store: Store): Promise<?ExportType> {
);
}
export function exportStore(store: Store): Promise<string> {
export function exportStore(store: MiddlewareAPI): Promise<string> {
getLogger().track('usage', EXPORT_FLIPPER_TRACE_EVENT);
return new Promise(async (resolve, reject) => {
const storeExport = await getStoreExport(store);