diff --git a/desktop/app/package.json b/desktop/app/package.json index 90c82f516..9b45e8a4b 100644 --- a/desktop/app/package.json +++ b/desktop/app/package.json @@ -37,6 +37,7 @@ "lodash.memoize": "^4.1.2", "open": "^7.0.0", "openssl-wrapper": "^0.3.4", + "p-map": "^4.0.0", "promise-retry": "^1.1.1", "promisify-child-process": "^4.1.0", "prop-types": "^15.6.0", @@ -53,6 +54,7 @@ "react-transition-group": "^4.4.1", "react-virtualized-auto-sizer": "^1.0.2", "react-window": "^1.3.1", + "recursive-readdir": "^2.2.2", "redux": "^4.0.0", "redux-persist": "^6.0.0", "rsocket-core": "^0.0.19", diff --git a/desktop/app/src/dispatcher/__tests__/plugins.node.tsx b/desktop/app/src/dispatcher/__tests__/plugins.node.tsx index 330183a06..ed8686d1b 100644 --- a/desktop/app/src/dispatcher/__tests__/plugins.node.tsx +++ b/desktop/app/src/dispatcher/__tests__/plugins.node.tsx @@ -8,6 +8,7 @@ */ jest.mock('../../defaultPlugins'); +jest.mock('../../utils/loadDynamicPlugins'); import dispatcher, { getDynamicPlugins, checkDisabled, @@ -17,7 +18,7 @@ import dispatcher, { } from '../plugins'; import {PluginDetails} from 'flipper-plugin-lib'; import path from 'path'; -import {ipcRenderer, remote} from 'electron'; +import {remote} from 'electron'; import {FlipperPlugin} from 'flipper'; import reducers, {State} from '../../reducers/index'; import {getInstance} from '../../fb-stubs/Logger'; @@ -26,6 +27,10 @@ import {TEST_PASSING_GK, TEST_FAILING_GK} from '../../fb-stubs/GK'; import TestPlugin from './TestPlugin'; import {resetConfigForTesting} from '../../utils/processConfig'; import {SandyPluginDefinition} from 'flipper-plugin'; +import {mocked} from 'ts-jest/utils'; +import loadDynamicPlugins from '../../utils/loadDynamicPlugins'; + +const loadDynamicPluginsMock = mocked(loadDynamicPlugins); const mockStore = configureStore([])( reducers(undefined, {type: 'INIT'}), @@ -47,33 +52,26 @@ const samplePluginDetails: PluginDetails = { beforeEach(() => { resetConfigForTesting(); + loadDynamicPluginsMock.mockResolvedValue([]); }); -test('dispatcher dispatches REGISTER_PLUGINS', () => { - dispatcher(mockStore, logger); +afterEach(() => { + loadDynamicPluginsMock.mockClear(); +}); + +test('dispatcher dispatches REGISTER_PLUGINS', async () => { + await dispatcher(mockStore, logger); const actions = mockStore.getActions(); expect(actions.map((a) => a.type)).toContain('REGISTER_PLUGINS'); }); -test('getDynamicPlugins returns empty array on errors', () => { - const sendSyncMock = jest.fn(); - sendSyncMock.mockImplementation(() => { - throw new Error('ooops'); - }); - ipcRenderer.sendSync = sendSyncMock; - const res = getDynamicPlugins(); +test('getDynamicPlugins returns empty array on errors', async () => { + const loadDynamicPluginsMock = mocked(loadDynamicPlugins); + loadDynamicPluginsMock.mockRejectedValue(new Error('ooops')); + const res = await getDynamicPlugins(); expect(res).toEqual([]); }); -test('getDynamicPlugins from main process via ipc', () => { - const plugins = [{name: 'test'}]; - const sendSyncMock = jest.fn(); - sendSyncMock.mockReturnValue(plugins); - ipcRenderer.sendSync = sendSyncMock; - const res = getDynamicPlugins(); - expect(res).toEqual(plugins); -}); - test('checkDisabled', () => { const disabledPlugin = 'pluginName'; const config = {disabledPlugins: [disabledPlugin]}; diff --git a/desktop/app/src/dispatcher/plugins.tsx b/desktop/app/src/dispatcher/plugins.tsx index 95f99c253..2bd94f936 100644 --- a/desktop/app/src/dispatcher/plugins.tsx +++ b/desktop/app/src/dispatcher/plugins.tsx @@ -20,7 +20,6 @@ import { addDisabledPlugins, addFailedPlugins, } from '../reducers/plugins'; -import {ipcRenderer} from 'electron'; import GK from '../fb-stubs/GK'; import {FlipperBasePlugin} from '../plugin'; import {setupMenuBar} from '../MenuBar'; @@ -33,13 +32,14 @@ import semver from 'semver'; import {PluginDetails} from 'flipper-plugin-lib'; import {tryCatchReportPluginFailures, reportUsage} from '../utils/metrics'; import * as FlipperPluginSDK from 'flipper-plugin'; +import {SandyPluginDefinition} from 'flipper-plugin'; +import loadDynamicPlugins from '../utils/loadDynamicPlugins'; import Immer from 'immer'; // eslint-disable-next-line import/no-unresolved import getPluginIndex from '../utils/getDefaultPluginsIndex'; -import {SandyPluginDefinition} from 'flipper-plugin'; -export default (store: Store, logger: Logger) => { +export default async (store: Store, logger: Logger) => { // expose Flipper and exact globally for dynamically loaded plugins const globalObject: any = typeof window === 'undefined' ? global : window; globalObject.React = React; @@ -57,7 +57,7 @@ export default (store: Store, logger: Logger) => { const initialPlugins: PluginDefinition[] = filterNewestVersionOfEachPlugin( getBundledPlugins(), - getDynamicPlugins(), + await getDynamicPlugins(), ) .map(reportVersion) .filter(checkDisabled(disabledPlugins)) @@ -134,14 +134,13 @@ function getBundledPlugins(): Array { return bundledPlugins; } -export function getDynamicPlugins() { - let dynamicPlugins: Array = []; +export async function getDynamicPlugins() { try { - dynamicPlugins = ipcRenderer.sendSync('get-dynamic-plugins'); + return await loadDynamicPlugins(); } catch (e) { - console.error(e); + console.error('Failed to load dynamic plugins', e); + return []; } - return dynamicPlugins; } export const checkGK = (gatekeepedPlugins: Array) => ( diff --git a/desktop/app/src/utils/loadDynamicPlugins.tsx b/desktop/app/src/utils/loadDynamicPlugins.tsx new file mode 100644 index 000000000..643ba9e1d --- /dev/null +++ b/desktop/app/src/utils/loadDynamicPlugins.tsx @@ -0,0 +1,88 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import path from 'path'; +import fs from 'fs-extra'; +import pMap from 'p-map'; +import { + PluginDetails, + getSourcePlugins, + getInstalledPlugins, + finishPendingPluginInstallations, +} from 'flipper-plugin-lib'; +import os from 'os'; +import {getStaticPath} from '../utils/pathUtils'; + +const pluginCache = path.join(os.homedir(), '.flipper', 'plugins'); + +// Load "dynamic" plugins, e.g. those which are either installed or loaded from sources for development purposes. +// This opposed to "static" plugins which are already included into Flipper bundle. +export default async function loadDynamicPlugins(): Promise { + if (process.env.FLIPPER_FAST_REFRESH) { + console.log( + '❌ Skipping loading of dynamic plugins because Fast Refresh is enabled. Fast Refresh only works with bundled plugins.', + ); + return []; + } + try { + await finishPendingPluginInstallations(); + } catch (err) { + console.error('❌ Failed to finish pending installations', err); + } + const staticPath = getStaticPath(); + const defaultPlugins = new Set( + ( + await fs.readJson(path.join(staticPath, 'defaultPlugins', 'index.json')) + ).map((p: any) => p.name) as string[], + ); + const dynamicPlugins = [ + ...(await getInstalledPlugins()), + ...(await getSourcePlugins()).filter((p) => !defaultPlugins.has(p.name)), + ]; + await fs.ensureDir(pluginCache); + const compilations = pMap( + dynamicPlugins, + (plugin) => { + return loadPlugin(plugin); + }, + {concurrency: 4}, + ); + const compiledDynamicPlugins = (await compilations).filter( + (c) => c !== null, + ) as PluginDetails[]; + console.log('✅ Loaded all plugins.'); + return compiledDynamicPlugins; +} +async function loadPlugin( + pluginDetails: PluginDetails, +): Promise { + const {specVersion, version, entry, name} = pluginDetails; + if (specVersion > 1) { + if (await fs.pathExists(entry)) { + return pluginDetails; + } else { + console.error( + `❌ Plugin ${name} is ignored, because its entry point not found: ${entry}.`, + ); + return null; + } + } else { + // Try to load cached version of legacy plugin + const entry = path.join(pluginCache, `${name}@${version || '0.0.0'}.js`); + if (await fs.pathExists(entry)) { + console.log(`🥫 Using cached version of legacy plugin ${name}...`); + return pluginDetails; + } else { + console.error( + `❌ Plugin ${name} is ignored, because it is defined by the unsupported spec v1 and could not be compiled.`, + ); + return null; + } + } +} diff --git a/desktop/package.json b/desktop/package.json index f376d46c3..921412273 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -185,7 +185,9 @@ "eslint-plugin-prettier": "^3.1.2", "eslint-plugin-react": "^7.20.0", "eslint-plugin-react-hooks": "^4.0.4", + "expand-tilde": "^2.0.2", "express": "^4.15.2", + "fb-watchman": "^2.0.1", "flipper-babel-transformer": "0.57.0", "flipper-pkg-lib": "0.57.0", "flipper-plugin-lib": "0.57.0", @@ -218,6 +220,7 @@ "ts-jest": "^26.0.0", "ts-node": "^8.8.1", "typescript": "^3.9.5", + "uuid": "^8.3.0", "yargs": "^15.4.1", "yazl": "^2.5.1" }, diff --git a/desktop/plugin-lib/package.json b/desktop/plugin-lib/package.json index 075d716b0..bacc4f5f9 100644 --- a/desktop/plugin-lib/package.json +++ b/desktop/plugin-lib/package.json @@ -14,10 +14,13 @@ "decompress": "^4.2.1", "decompress-targz": "^4.1.1", "decompress-unzip": "^4.0.1", + "expand-tilde": "^2.0.2", "fs-extra": "^9.0.1", "live-plugin-manager": "^0.14.1", "npm-api": "^1.0.0", + "p-filter": "^2.1.0", "p-map": "^4.0.0", + "recursive-readdir": "^2.2.2", "semver": "^7.3.2", "tmp": "^0.2.1" }, diff --git a/desktop/static/getPlugins.ts b/desktop/plugin-lib/src/getSourcePlugins.ts similarity index 87% rename from desktop/static/getPlugins.ts rename to desktop/plugin-lib/src/getSourcePlugins.ts index c6fe9e7b6..2daf105ba 100644 --- a/desktop/static/getPlugins.ts +++ b/desktop/plugin-lib/src/getSourcePlugins.ts @@ -10,26 +10,16 @@ import path from 'path'; import fs from 'fs-extra'; import expandTilde from 'expand-tilde'; -import { - getPluginsInstallationFolder, - getPluginSourceFolders, -} from './getPluginFolders'; +import {getPluginSourceFolders} from './pluginPaths'; import {PluginDetails, getPluginDetails} from 'flipper-plugin-lib'; import pmap from 'p-map'; import pfilter from 'p-filter'; import {satisfies} from 'semver'; -const flipperVersion = require('./package.json').version; +const flipperVersion = require('../package.json').version; export async function getSourcePlugins(): Promise { - return await getPluginsFromFolders(await getPluginSourceFolders()); -} -export async function getInstalledPlugins(): Promise { - return await getPluginsFromFolders([getPluginsInstallationFolder()]); -} -async function getPluginsFromFolders( - pluginFolders: string[], -): Promise { + const pluginFolders = await getPluginSourceFolders(); const entryPoints: {[key: string]: PluginDetails} = {}; const additionalPlugins = await pmap(pluginFolders, (path) => entryPointForPluginFolder(path), diff --git a/desktop/plugin-lib/src/index.ts b/desktop/plugin-lib/src/index.ts index 68c79f310..d10b1517d 100644 --- a/desktop/plugin-lib/src/index.ts +++ b/desktop/plugin-lib/src/index.ts @@ -12,3 +12,5 @@ export {default as getPluginDetails} from './getPluginDetails'; export * from './pluginInstaller'; export * from './getInstalledPlugins'; export * from './getUpdatablePlugins'; +export * from './getSourcePlugins'; +export {getPluginSourceFolders} from './pluginPaths'; diff --git a/desktop/plugin-lib/src/pluginPaths.ts b/desktop/plugin-lib/src/pluginPaths.ts index c76d8f691..7d1d57c6e 100644 --- a/desktop/plugin-lib/src/pluginPaths.ts +++ b/desktop/plugin-lib/src/pluginPaths.ts @@ -9,6 +9,8 @@ import path from 'path'; import {homedir} from 'os'; +import fs from 'fs-extra'; +import expandTilde from 'expand-tilde'; export const flipperDataDir = path.join(homedir(), '.flipper'); @@ -20,3 +22,23 @@ export const pluginPendingInstallationDir = path.join( ); export const pluginCacheDir = path.join(flipperDataDir, 'plugins'); + +export async function getPluginSourceFolders(): Promise { + const pluginFolders: string[] = []; + if (process.env.FLIPPER_NO_EMBEDDED_PLUGINS === 'true') { + console.log( + '🥫 Skipping embedded plugins because "--no-embedded-plugins" flag provided', + ); + return pluginFolders; + } + const flipperConfigPath = path.join(homedir(), '.flipper', 'config.json'); + if (await fs.pathExists(flipperConfigPath)) { + const config = await fs.readJson(flipperConfigPath); + if (config.pluginPaths) { + pluginFolders.push(...config.pluginPaths); + } + } + pluginFolders.push(path.resolve(__dirname, '..', '..', 'plugins')); + pluginFolders.push(path.resolve(__dirname, '..', '..', 'plugins', 'fb')); + return pluginFolders.map(expandTilde).filter(fs.existsSync); +} diff --git a/desktop/scripts/build-utils.ts b/desktop/scripts/build-utils.ts index 12310423d..86d44a789 100644 --- a/desktop/scripts/build-utils.ts +++ b/desktop/scripts/build-utils.ts @@ -14,7 +14,7 @@ import fs from 'fs-extra'; import {spawn} from 'promisify-child-process'; import {getWatchFolders} from 'flipper-pkg-lib'; import getAppWatchFolders from './get-app-watch-folders'; -import {getSourcePlugins} from '../static/getPlugins'; +import {getSourcePlugins, getPluginSourceFolders} from 'flipper-plugin-lib'; import { appDir, staticDir, @@ -22,7 +22,6 @@ import { headlessDir, babelTransformationsDir, } from './paths'; -import {getPluginSourceFolders} from '../static/getPluginFolders'; const dev = process.env.NODE_ENV !== 'production'; diff --git a/desktop/static/ensurePluginFoldersWatchable.ts b/desktop/scripts/ensurePluginFoldersWatchable.ts similarity index 95% rename from desktop/static/ensurePluginFoldersWatchable.ts rename to desktop/scripts/ensurePluginFoldersWatchable.ts index 99f3c56ca..56b702b97 100644 --- a/desktop/static/ensurePluginFoldersWatchable.ts +++ b/desktop/scripts/ensurePluginFoldersWatchable.ts @@ -7,7 +7,7 @@ * @format */ -import {getPluginSourceFolders} from './getPluginFolders'; +import {getPluginSourceFolders} from 'flipper-plugin-lib'; import fs from 'fs-extra'; const watchmanconfigName = '.watchmanconfig'; diff --git a/desktop/scripts/start-dev-server.ts b/desktop/scripts/start-dev-server.ts index 3efd5eb38..b382096fa 100644 --- a/desktop/scripts/start-dev-server.ts +++ b/desktop/scripts/start-dev-server.ts @@ -20,16 +20,15 @@ import path from 'path'; import fs from 'fs-extra'; import {hostname} from 'os'; import {compileMain, generatePluginEntryPoints} from './build-utils'; -import Watchman from '../static/watchman'; +import Watchman from './watchman'; import Metro from 'metro'; import MetroResolver from 'metro-resolver'; import {staticDir, appDir, babelTransformationsDir} from './paths'; import isFB from './isFB'; import getAppWatchFolders from './get-app-watch-folders'; -import {getSourcePlugins} from '../static/getPlugins'; -import {getPluginSourceFolders} from '../static/getPluginFolders'; -import startWatchPlugins from '../static/startWatchPlugins'; -import ensurePluginFoldersWatchable from '../static/ensurePluginFoldersWatchable'; +import {getPluginSourceFolders} from 'flipper-plugin-lib'; +import ensurePluginFoldersWatchable from './ensurePluginFoldersWatchable'; +import startWatchPlugins from './startWatchPlugins'; const ansiToHtmlConverter = new AnsiToHtmlConverter(); @@ -211,7 +210,7 @@ async function startWatchChanges(io: socketIo.Server) { const watchman = new Watchman(path.resolve(__dirname, '..')); await watchman.initialize(); await Promise.all( - ['app', 'pkg', 'doctor', 'flipper-plugin'].map((dir) => + ['app', 'pkg', 'doctor', 'plugin-lib', 'flipper-plugin'].map((dir) => watchman.startWatchFiles( dir, () => { @@ -223,8 +222,7 @@ async function startWatchChanges(io: socketIo.Server) { ), ), ); - const plugins = await getSourcePlugins(); - await startWatchPlugins(plugins, () => { + await startWatchPlugins(() => { io.emit('refresh'); }); } catch (err) { diff --git a/desktop/scripts/startWatchPlugins.ts b/desktop/scripts/startWatchPlugins.ts new file mode 100644 index 000000000..fde433d0f --- /dev/null +++ b/desktop/scripts/startWatchPlugins.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @format + */ + +import Watchman from './watchman'; +import {getPluginSourceFolders} from 'flipper-plugin-lib'; + +export default async function startWatchPlugins( + onChanged: () => void | Promise, +) { + // eslint-disable-next-line no-console + console.log('🕵️‍ Watching for plugin changes'); + + let delayedCompilation: NodeJS.Timeout | undefined; + const kCompilationDelayMillis = 1000; + const onPluginChangeDetected = () => { + if (!delayedCompilation) { + delayedCompilation = setTimeout(() => { + delayedCompilation = undefined; + // eslint-disable-next-line no-console + console.log(`🕵️‍ Detected plugin change`); + onChanged(); + }, kCompilationDelayMillis); + } + }; + try { + await startWatchingPluginsUsingWatchman(onPluginChangeDetected); + } catch (err) { + console.error( + 'Failed to start watching plugin files using Watchman, continue without hot reloading', + err, + ); + } +} + +async function startWatchingPluginsUsingWatchman(onChange: () => void) { + const pluginFolders = await getPluginSourceFolders(); + await Promise.all( + pluginFolders.map(async (pluginFolder) => { + const watchman = new Watchman(pluginFolder); + await watchman.initialize(); + await watchman.startWatchFiles('.', () => onChange(), { + excludes: ['**/__tests__/**/*', '**/node_modules/**/*', '**/.*'], + }); + }), + ); +} diff --git a/desktop/static/watchman.ts b/desktop/scripts/watchman.ts similarity index 100% rename from desktop/static/watchman.ts rename to desktop/scripts/watchman.ts diff --git a/desktop/static/compilePlugins.ts b/desktop/static/compilePlugins.ts deleted file mode 100644 index f591bc8f9..000000000 --- a/desktop/static/compilePlugins.ts +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -import path from 'path'; -import fs from 'fs-extra'; -import util from 'util'; -import recursiveReaddir from 'recursive-readdir'; -import pMap from 'p-map'; -import {homedir} from 'os'; -import {PluginDetails} from 'flipper-plugin-lib'; -import {runBuild} from 'flipper-pkg-lib'; -import {getSourcePlugins, getInstalledPlugins} from './getPlugins'; -import startWatchPlugins from './startWatchPlugins'; -import ensurePluginFoldersWatchable from './ensurePluginFoldersWatchable'; - -const HOME_DIR = homedir(); - -const DEFAULT_COMPILE_OPTIONS: CompileOptions = { - force: false, - failSilently: true, - recompileOnChanges: true, -}; - -export type CompileOptions = { - force: boolean; - failSilently: boolean; - recompileOnChanges: boolean; -}; - -export default async function ( - reloadCallback: (() => void) | null, - pluginCache: string, - options: CompileOptions = DEFAULT_COMPILE_OPTIONS, -): Promise { - if (process.env.FLIPPER_FAST_REFRESH) { - console.log( - '🥫 Skipping loading of installed plugins because Fast Refresh is enabled', - ); - return []; - } - await ensurePluginFoldersWatchable(); - options = Object.assign({}, DEFAULT_COMPILE_OPTIONS, options); - const defaultPlugins = ( - await fs.readJson(path.join(__dirname, 'defaultPlugins', 'index.json')) - ).map((p: any) => p.name) as string[]; - const dynamicPlugins = [ - ...(await getInstalledPlugins()), - ...(await getSourcePlugins()).filter( - (p) => !defaultPlugins.includes(p.name), - ), - ]; - await fs.ensureDir(pluginCache); - if (options.recompileOnChanges) { - await startWatchChanges( - dynamicPlugins, - reloadCallback, - pluginCache, - options, - ); - } - const compilations = pMap( - dynamicPlugins, - (plugin) => { - return compilePlugin(plugin, pluginCache, options); - }, - {concurrency: 4}, - ); - - const compiledDynamicPlugins = (await compilations).filter( - (c) => c !== null, - ) as PluginDetails[]; - console.log('✅ Compiled all plugins.'); - return compiledDynamicPlugins; -} -async function startWatchChanges( - plugins: PluginDetails[], - reloadCallback: (() => void) | null, - pluginCache: string, - options: CompileOptions = DEFAULT_COMPILE_OPTIONS, -) { - const filteredPlugins = plugins - // no hot reloading for plugins in .flipper folder. This is to prevent - // Flipper from reloading, while we are doing changes on thirdparty plugins. - .filter( - (plugin) => !plugin.dir.startsWith(path.join(HOME_DIR, '.flipper')), - ); - const watchOptions = Object.assign({}, options, {force: true}); - await startWatchPlugins(filteredPlugins, (plugin) => - compilePlugin(plugin, pluginCache, watchOptions).then( - reloadCallback ?? (() => {}), - ), - ); -} -async function mostRecentlyChanged(dir: string) { - const files = await util.promisify(recursiveReaddir)(dir); - return files - .map((f) => fs.lstatSync(f).ctime) - .reduce((a, b) => (a > b ? a : b), new Date(0)); -} -async function compilePlugin( - pluginDetails: PluginDetails, - pluginCache: string, - {force, failSilently}: CompileOptions, -): Promise { - const {dir, specVersion, version, entry, source, name} = pluginDetails; - if (specVersion > 1) { - // eslint-disable-next-line no-console - if (await fs.pathExists(entry)) { - console.log(`🥫 Using pre-built version of ${name}: ${entry}...`); - return pluginDetails; - } else { - console.error( - `❌ Plugin ${name} is ignored, because its entry point not found: ${entry}.`, - ); - return null; - } - } else { - const entry = path.join(pluginCache, `${name}@${version || '0.0.0'}.js`); - const rootDirCtime = await mostRecentlyChanged(dir); - if ( - !force && - (await fs.pathExists(entry)) && - rootDirCtime < (await fs.lstat(entry)).ctime - ) { - // eslint-disable-next-line no-console - console.log(`🥫 Using cached version of ${name}...`); - return pluginDetails; - } else { - // eslint-disable-line no-console - console.log(`⚙️ Compiling ${name}...`); - try { - await runBuild(dir, source, entry, false); - } catch (e) { - if (failSilently) { - console.error( - `❌ Plugin ${name} is ignored, because it could not be compiled.`, - ); - console.error(e); - return null; - } else { - throw e; - } - } - return pluginDetails; - } - } -} diff --git a/desktop/static/getPluginFolders.ts b/desktop/static/getPluginFolders.ts deleted file mode 100644 index fa51cecbe..000000000 --- a/desktop/static/getPluginFolders.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -import path from 'path'; -import fs from 'fs-extra'; -import expandTilde from 'expand-tilde'; -import {homedir} from 'os'; - -export function getPluginsInstallationFolder(): string { - return path.join(homedir(), '.flipper', 'thirdparty'); -} - -export async function getPluginSourceFolders(): Promise { - const pluginFolders: string[] = []; - if (process.env.FLIPPER_NO_EMBEDDED_PLUGINS === 'true') { - console.log( - '🥫 Skipping embedded plugins because "--no-embedded-plugins" flag provided', - ); - return pluginFolders; - } - const flipperConfigPath = path.join(homedir(), '.flipper', 'config.json'); - if (await fs.pathExists(flipperConfigPath)) { - const config = await fs.readJson(flipperConfigPath); - if (config.pluginPaths) { - pluginFolders.push(...config.pluginPaths); - } - } - pluginFolders.push(path.resolve(__dirname, '..', 'plugins')); - pluginFolders.push(path.resolve(__dirname, '..', 'plugins', 'fb')); - return pluginFolders.map(expandTilde).filter(fs.existsSync); -} diff --git a/desktop/static/main.ts b/desktop/static/main.ts index 4f08df880..2cfb072f0 100644 --- a/desktop/static/main.ts +++ b/desktop/static/main.ts @@ -23,12 +23,10 @@ import url from 'url'; import fs from 'fs'; import fixPath from 'fix-path'; import {exec} from 'child_process'; -import compilePlugins from './compilePlugins'; import setup from './setup'; import isFB from './fb-stubs/isFB'; import delegateToLauncher from './launcher'; import yargs from 'yargs'; -import {finishPendingPluginInstallations} from 'flipper-plugin-lib'; const VERSION: string = (global as any).__VERSION__; @@ -89,7 +87,7 @@ const argv = yargs .help() .parse(process.argv.slice(1)); -const {config, configPath, flipperDir} = setup(argv); +const {config, configPath} = setup(argv); if (isFB && process.env.FLIPPER_FB === undefined) { process.env.FLIPPER_FB = 'true'; @@ -100,7 +98,6 @@ process.env.CONFIG = JSON.stringify(config); // possible reference to main app window let win: BrowserWindow; let appReady = false; -let pluginsCompiled = false; let deeplinkURL: string | undefined = argv.url; let filePath: string | undefined = argv.file; @@ -111,22 +108,6 @@ setInterval(() => { } }, 60 * 1000); -finishPendingPluginInstallations() - .then(() => - compilePlugins(() => { - if (win) { - win.reload(); - } - }, path.join(flipperDir, 'plugins')), - ) - .then((dynamicPlugins) => { - ipcMain.on('get-dynamic-plugins', (event) => { - event.returnValue = dynamicPlugins; - }); - pluginsCompiled = true; - tryCreateWindow(); - }); - // check if we already have an instance of this app open const gotTheLock = app.requestSingleInstanceLock(); @@ -185,7 +166,7 @@ app.on('ready', () => { appReady = true; app.commandLine.appendSwitch('scroll-bounce'); configureSession(); - tryCreateWindow(); + createWindow(); // if in development install the react devtools extension if (process.env.NODE_ENV === 'development') { const { @@ -278,72 +259,70 @@ app.setAsDefaultProtocolClient('flipper'); // is workaround suggested in the issue app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors'); -function tryCreateWindow() { - if (appReady && pluginsCompiled) { - win = new BrowserWindow({ - show: false, - title: 'Flipper', - width: config.lastWindowPosition?.width || 1400, - height: config.lastWindowPosition?.height || 1000, - minWidth: 800, - minHeight: 600, - center: true, - titleBarStyle: 'hiddenInset', - vibrancy: 'sidebar', - webPreferences: { - backgroundThrottling: false, - webSecurity: false, - scrollBounce: true, - experimentalFeatures: true, - nodeIntegration: true, - webviewTag: true, - nativeWindowOpen: true, - }, - }); - win.once('ready-to-show', () => { - win.show(); - if (argv['open-dev-tools']) { - win.webContents.openDevTools(); - } - }); - win.once('close', () => { - win.webContents.send('trackUsage', 'exit'); - if (process.env.NODE_ENV === 'development') { - // Removes as a default protocol for debug builds. Because even when the - // production application is installed, and one tries to deeplink through - // browser, it still looks for the debug one and tries to open electron - app.removeAsDefaultProtocolClient('flipper'); - } - const [x, y] = win.getPosition(); - const [width, height] = win.getSize(); - // save window position and size - fs.writeFileSync( - configPath, - JSON.stringify({ - ...config, - lastWindowPosition: { - x, - y, - width, - height, - }, - }), - ); - }); - if ( - config.lastWindowPosition && - config.lastWindowPosition.x && - config.lastWindowPosition.y - ) { - win.setPosition(config.lastWindowPosition.x, config.lastWindowPosition.y); +function createWindow() { + win = new BrowserWindow({ + show: false, + title: 'Flipper', + width: config.lastWindowPosition?.width || 1400, + height: config.lastWindowPosition?.height || 1000, + minWidth: 800, + minHeight: 600, + center: true, + titleBarStyle: 'hiddenInset', + vibrancy: 'sidebar', + webPreferences: { + backgroundThrottling: false, + webSecurity: false, + scrollBounce: true, + experimentalFeatures: true, + nodeIntegration: true, + webviewTag: true, + nativeWindowOpen: true, + }, + }); + win.once('ready-to-show', () => { + win.show(); + if (argv['open-dev-tools']) { + win.webContents.openDevTools(); } - const entryUrl = - process.env.ELECTRON_URL || - url.format({ - pathname: path.join(__dirname, 'index.html'), - protocol: 'file:', - slashes: true, - }); - win.loadURL(entryUrl); + }); + win.once('close', () => { + win.webContents.send('trackUsage', 'exit'); + if (process.env.NODE_ENV === 'development') { + // Removes as a default protocol for debug builds. Because even when the + // production application is installed, and one tries to deeplink through + // browser, it still looks for the debug one and tries to open electron + app.removeAsDefaultProtocolClient('flipper'); + } + const [x, y] = win.getPosition(); + const [width, height] = win.getSize(); + // save window position and size + fs.writeFileSync( + configPath, + JSON.stringify({ + ...config, + lastWindowPosition: { + x, + y, + width, + height, + }, + }), + ); + }); + if ( + config.lastWindowPosition && + config.lastWindowPosition.x && + config.lastWindowPosition.y + ) { + win.setPosition(config.lastWindowPosition.x, config.lastWindowPosition.y); } + const entryUrl = + process.env.ELECTRON_URL || + url.format({ + pathname: path.join(__dirname, 'index.html'), + protocol: 'file:', + slashes: true, + }); + win.loadURL(entryUrl); } diff --git a/desktop/static/package.json b/desktop/static/package.json index 2f49d3972..67ef4dfa1 100644 --- a/desktop/static/package.json +++ b/desktop/static/package.json @@ -6,21 +6,14 @@ "license": "MIT", "dependencies": { "electron-devtools-installer": "^3.1.1", - "expand-tilde": "^2.0.2", - "fb-watchman": "^2.0.0", "fix-path": "^3.0.0", "flipper-pkg-lib": "0.57.0", "flipper-plugin-lib": "0.57.0", "fs-extra": "^9.0.1", "ignore": "^5.1.4", "mac-ca": "^1.0.4", - "mem": "^6.0.0", "mkdirp": "^1.0.4", - "p-filter": "^2.1.0", - "p-map": "^4.0.0", - "recursive-readdir": "^2.2.2", - "semver": "^7.3.2", - "uuid": "^8.1.0", + "node-fetch": "^2.6.1", "ws": "^7.3.0", "xdg-basedir": "^4.0.0", "yargs": "^15.4.1" diff --git a/desktop/static/setup.ts b/desktop/static/setup.ts index 84d9eea94..1c4667109 100644 --- a/desktop/static/setup.ts +++ b/desktop/static/setup.ts @@ -59,5 +59,5 @@ export default function setup(argv: any) { launcherMsg: argv.launcherMsg, }; - return {config, configPath, flipperDir}; + return {config, configPath}; } diff --git a/desktop/static/startWatchPlugins.ts b/desktop/static/startWatchPlugins.ts deleted file mode 100644 index f654cd472..000000000 --- a/desktop/static/startWatchPlugins.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @format - */ - -import path from 'path'; -import Watchman from './watchman'; -import {PluginDetails} from 'flipper-plugin-lib'; - -export default async function startWatchPlugins( - plugins: PluginDetails[], - compilePlugin: (plugin: PluginDetails) => void | Promise, -) { - // eslint-disable-next-line no-console - console.log('🕵️‍ Watching for plugin changes'); - - const delayedCompilation: {[key: string]: NodeJS.Timeout | null} = {}; - const kCompilationDelayMillis = 1000; - const onPluginChanged = (plugin: PluginDetails) => { - if (!delayedCompilation[plugin.name]) { - delayedCompilation[plugin.name] = setTimeout(() => { - delayedCompilation[plugin.name] = null; - // eslint-disable-next-line no-console - console.log(`🕵️‍ Detected changes in ${plugin.name}`); - compilePlugin(plugin); - }, kCompilationDelayMillis); - } - }; - try { - await startWatchingPluginsUsingWatchman(plugins, onPluginChanged); - } catch (err) { - console.error( - 'Failed to start watching plugin files using Watchman, continue without hot reloading', - err, - ); - } -} - -async function startWatchingPluginsUsingWatchman( - plugins: PluginDetails[], - onPluginChanged: (plugin: PluginDetails) => void, -) { - // Initializing a watchman for each folder containing plugins - const watchmanRootMap: {[key: string]: Watchman} = {}; - await Promise.all( - plugins.map(async (plugin) => { - const watchmanRoot = path.resolve(plugin.dir, '..'); - if (!watchmanRootMap[watchmanRoot]) { - watchmanRootMap[watchmanRoot] = new Watchman(watchmanRoot); - await watchmanRootMap[watchmanRoot].initialize(); - } - }), - ); - // Start watching plugins using the initialized watchmans - await Promise.all( - plugins.map(async (plugin) => { - const watchmanRoot = path.resolve(plugin.dir, '..'); - const watchman = watchmanRootMap[watchmanRoot]; - await watchman.startWatchFiles( - path.relative(watchmanRoot, plugin.dir), - () => onPluginChanged(plugin), - { - excludes: ['**/__tests__/**/*', '**/node_modules/**/*', '**/.*'], - }, - ); - }), - ); -} diff --git a/desktop/yarn.lock b/desktop/yarn.lock index ade567eb1..554212984 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -8420,13 +8420,6 @@ makeerror@1.0.x: dependencies: tmpl "1.0.x" -map-age-cleaner@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -8473,14 +8466,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -mem@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/mem/-/mem-6.0.1.tgz#3f8ad1b0f8c4e00daf07f104e95b9d78131d7908" - integrity sha512-uIRYASflIsXqvKe+7aXbLrydaRzz4qiK6amqZDQI++eRtW3UoKtnDcGeCAOREgll7YMxO5E4VB9+3B0LFmy96g== - dependencies: - map-age-cleaner "^0.1.3" - mimic-fn "^3.0.0" - "memoize-one@>=3.1.1 <6": version "5.1.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" @@ -8810,11 +8795,6 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-fn@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.0.0.tgz#76044cfa8818bbf6999c5c9acadf2d3649b14b4b" - integrity sha512-PiVO95TKvhiwgSwg1IdLYlCTdul38yZxZMIcnDSFIBUm4BNZha2qpQ4GpJ++15bHoKDtrW2D69lMfFwdFYtNZQ== - mimic-response@^1.0.0, mimic-response@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" @@ -8985,6 +8965,11 @@ node-fetch@^1.0.1: encoding "^0.1.11" is-stream "^1.0.1" +node-fetch@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" + integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== + node-forge@^0.7.1, node-forge@^0.7.5: version "0.7.6" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" @@ -9315,11 +9300,6 @@ p-cancelable@^1.0.0: resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-1.1.0.tgz#d078d15a3af409220c886f1d9a0ca2e441ab26cc" integrity sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw== -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - p-each-series@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48" @@ -12251,11 +12231,6 @@ uuid@^7.0.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== -uuid@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.1.0.tgz#6f1536eb43249f473abc6bd58ff983da1ca30d8d" - integrity sha512-CI18flHDznR0lq54xBycOVmphdCYnQLKn8abKn7PXUiKUGdEd+/l9LWNJmugXel4hXq7S+RMNl34ecyC9TntWg== - uuid@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea"