diff --git a/headless/index.js b/headless/index.js index a6653cc5d..5245943e7 100644 --- a/headless/index.js +++ b/headless/index.js @@ -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,24 +93,40 @@ 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( - reducers, - devToolsEnhancer({realtime: true, hostname: 'localhost', port: 8181}), - ) - : createStore(reducers); + const store = createStore( + reducers, + devToolsEnhancer.composeWithDevTools(applyMiddleware(headlessMiddleware)), + ); const logger = initLogger(store, {isHeadless: true}); dispatcher(store, logger); - process.on('SIGINT', async () => { - try { - originalConsole.log(await exportStore(store)); - } catch (e) { - console.error(e); - } - process.exit(); - }); + if (exit == 'sigint') { + process.on('SIGINT', async () => { + try { + originalConsole.log(await exportStore(store)); + } catch (e) { + console.error(e); + } + process.exit(); + }); + } } diff --git a/src/index.js b/src/index.js index 13c71eb00..5af250506 100644 --- a/src/index.js +++ b/src/index.js @@ -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, diff --git a/src/plugin.js b/src/plugin.js index 8513fc858..0c958bc93 100644 --- a/src/plugin.js +++ b/src/plugin.js @@ -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, persistedState: ?PersistedState, - store: ?Store, + store: ?MiddlewareAPI, ) => Promise; static getActiveNotifications: ?( persistedState: PersistedState, diff --git a/src/plugins/layout/index.js b/src/plugins/layout/index.js index d93975171..1bdd24765 100644 --- a/src/plugins/layout/index.js +++ b/src/plugins/layout/index.js @@ -9,7 +9,7 @@ import type { ElementID, Element, ElementSearchResultSet, - Store, + MiddlewareAPI, PluginClient, } from 'flipper'; @@ -61,7 +61,7 @@ export default class Layout extends FlipperPlugin { static exportPersistedState = ( callClient: (string, ?Object) => Promise, persistedState: ?PersistedState, - store: ?Store, + store: ?MiddlewareAPI, ): Promise => { const defaultPromise = Promise.resolve(persistedState); if (!store) { diff --git a/src/reducers/index.js b/src/reducers/index.js index ab63e0542..a3633a364 100644 --- a/src/reducers/index.js +++ b/src/reducers/index.js @@ -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; +export type MiddlewareAPI = ReduxMiddlewareAPI; export default combineReducers<_, Actions>({ application, diff --git a/src/utils/exportData.js b/src/utils/exportData.js index 02843c3a5..167d3059d 100644 --- a/src/utils/exportData.js +++ b/src/utils/exportData.js @@ -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 { +export async function getStoreExport( + store: MiddlewareAPI, +): Promise { const state = store.getState(); const {clients} = state.connections; const {pluginStates} = state; @@ -234,7 +236,7 @@ export async function getStoreExport(store: Store): Promise { ); } -export function exportStore(store: Store): Promise { +export function exportStore(store: MiddlewareAPI): Promise { getLogger().track('usage', EXPORT_FLIPPER_TRACE_EVENT); return new Promise(async (resolve, reject) => { const storeExport = await getStoreExport(store);