Move app/src (mostly) to flipper-ui-core/src
Summary: This diff moves all UI code from app/src to app/flipper-ui-core. That is now slightly too much (e.g. node deps are not removed yet), but from here it should be easier to move things out again, as I don't want this diff to be open for too long to avoid too much merge conflicts. * But at least flipper-ui-core is Electron free :) * Killed all cross module imports as well, as they where now even more in the way * Some unit test needed some changes, most not too big (but emotion hashes got renumbered in the snapshots, feel free to ignore that) * Found some files that were actually meaningless (tsconfig in plugins, WatchTools files, that start generating compile errors, removed those Follow up work: * make flipper-ui-core configurable, and wire up flipper-server-core in Electron instead of here * remove node deps (aigoncharov) * figure out correct place to load GKs, plugins, make intern requests etc., and move to the correct module * clean up deps Reviewed By: aigoncharov Differential Revision: D32427722 fbshipit-source-id: 14fe92e1ceb15b9dcf7bece367c8ab92df927a70
This commit is contained in:
committed by
Facebook GitHub Bot
parent
54b7ce9308
commit
7e50c0466a
360
desktop/flipper-ui-core/src/dispatcher/plugins.tsx
Normal file
360
desktop/flipper-ui-core/src/dispatcher/plugins.tsx
Normal file
@@ -0,0 +1,360 @@
|
||||
/**
|
||||
* 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 type {Store} from '../reducers/index';
|
||||
import type {Logger} from 'flipper-common';
|
||||
import {PluginDefinition} from '../plugin';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import adbkit from 'adbkit';
|
||||
import {
|
||||
registerPlugins,
|
||||
addGatekeepedPlugins,
|
||||
addDisabledPlugins,
|
||||
addFailedPlugins,
|
||||
registerLoadedPlugins,
|
||||
registerBundledPlugins,
|
||||
registerMarketplacePlugins,
|
||||
MarketplacePluginDetails,
|
||||
pluginsInitialized,
|
||||
} from '../reducers/plugins';
|
||||
import GK from '../fb-stubs/GK';
|
||||
import {FlipperBasePlugin} from '../plugin';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import {default as config} from '../utils/processConfig';
|
||||
import {notNull} from '../utils/typeUtils';
|
||||
import {
|
||||
ActivatablePluginDetails,
|
||||
BundledPluginDetails,
|
||||
ConcretePluginDetails,
|
||||
} from 'flipper-plugin-lib';
|
||||
import {tryCatchReportPluginFailures, reportUsage} from 'flipper-common';
|
||||
import * as FlipperPluginSDK from 'flipper-plugin';
|
||||
import {_SandyPluginDefinition} from 'flipper-plugin';
|
||||
import loadDynamicPlugins from '../utils/loadDynamicPlugins';
|
||||
import * as Immer from 'immer';
|
||||
import * as antd from 'antd';
|
||||
import * as emotion_styled from '@emotion/styled';
|
||||
import * as antdesign_icons from '@ant-design/icons';
|
||||
// @ts-ignore
|
||||
import * as crc32 from 'crc32';
|
||||
|
||||
import {isDevicePluginDefinition} from '../utils/pluginUtils';
|
||||
import isPluginCompatible from '../utils/isPluginCompatible';
|
||||
import isPluginVersionMoreRecent from '../utils/isPluginVersionMoreRecent';
|
||||
import {getStaticPath} from '../utils/pathUtils';
|
||||
import {createSandyPluginWrapper} from '../utils/createSandyPluginWrapper';
|
||||
import {getRenderHostInstance} from '../RenderHost';
|
||||
let defaultPluginsIndex: any = null;
|
||||
|
||||
export default async (store: Store, _logger: Logger) => {
|
||||
// expose Flipper and exact globally for dynamically loaded plugins
|
||||
const globalObject: any = typeof window === 'undefined' ? global : window;
|
||||
|
||||
// this list should match `replace-flipper-requires.tsx` and the `builtInModules` in `desktop/.eslintrc`
|
||||
globalObject.React = React;
|
||||
globalObject.ReactDOM = ReactDOM;
|
||||
globalObject.Flipper = require('../deprecated-exports');
|
||||
globalObject.adbkit = adbkit;
|
||||
globalObject.FlipperPlugin = FlipperPluginSDK;
|
||||
globalObject.Immer = Immer;
|
||||
globalObject.antd = antd;
|
||||
globalObject.emotion_styled = emotion_styled;
|
||||
globalObject.antdesign_icons = antdesign_icons;
|
||||
globalObject.crc32_hack_fix_me = crc32;
|
||||
|
||||
const gatekeepedPlugins: Array<ActivatablePluginDetails> = [];
|
||||
const disabledPlugins: Array<ActivatablePluginDetails> = [];
|
||||
const failedPlugins: Array<[ActivatablePluginDetails, string]> = [];
|
||||
|
||||
defaultPluginsIndex = getRenderHostInstance().loadDefaultPlugins();
|
||||
|
||||
const marketplacePlugins = selectCompatibleMarketplaceVersions(
|
||||
store.getState().plugins.marketplacePlugins,
|
||||
);
|
||||
store.dispatch(registerMarketplacePlugins(marketplacePlugins));
|
||||
|
||||
const uninstalledPluginNames =
|
||||
store.getState().plugins.uninstalledPluginNames;
|
||||
|
||||
const bundledPlugins = await getBundledPlugins();
|
||||
|
||||
const allLocalVersions = [
|
||||
...bundledPlugins,
|
||||
...(await getDynamicPlugins()),
|
||||
].filter((p) => !uninstalledPluginNames.has(p.name));
|
||||
|
||||
const loadedPlugins =
|
||||
getLatestCompatibleVersionOfEachPlugin(allLocalVersions);
|
||||
|
||||
const initialPlugins: PluginDefinition[] = loadedPlugins
|
||||
.map(reportVersion)
|
||||
.filter(checkDisabled(disabledPlugins))
|
||||
.filter(checkGK(gatekeepedPlugins))
|
||||
.map(createRequirePluginFunction(failedPlugins))
|
||||
.filter(notNull);
|
||||
|
||||
const classicPlugins = initialPlugins.filter(
|
||||
(p) => !isSandyPlugin(p.details),
|
||||
);
|
||||
if (process.env.NODE_ENV !== 'test' && classicPlugins.length) {
|
||||
console.warn(
|
||||
`${
|
||||
classicPlugins.length
|
||||
} plugin(s) were loaded in legacy mode. Please visit https://fbflipper.com/docs/extending/sandy-migration to learn how to migrate these plugins to the new Sandy architecture: \n${classicPlugins
|
||||
.map((p) => `${p.title} (id: ${p.id})`)
|
||||
.sort()
|
||||
.join('\n')}`,
|
||||
);
|
||||
}
|
||||
|
||||
store.dispatch(registerBundledPlugins(bundledPlugins));
|
||||
store.dispatch(registerLoadedPlugins(loadedPlugins));
|
||||
store.dispatch(addGatekeepedPlugins(gatekeepedPlugins));
|
||||
store.dispatch(addDisabledPlugins(disabledPlugins));
|
||||
store.dispatch(addFailedPlugins(failedPlugins));
|
||||
store.dispatch(registerPlugins(initialPlugins));
|
||||
store.dispatch(pluginsInitialized());
|
||||
};
|
||||
|
||||
function reportVersion(pluginDetails: ActivatablePluginDetails) {
|
||||
reportUsage(
|
||||
'plugin:version',
|
||||
{
|
||||
version: pluginDetails.version,
|
||||
},
|
||||
pluginDetails.id,
|
||||
);
|
||||
return pluginDetails;
|
||||
}
|
||||
|
||||
export function getLatestCompatibleVersionOfEachPlugin<
|
||||
T extends ConcretePluginDetails,
|
||||
>(plugins: T[]): T[] {
|
||||
const latestCompatibleVersions: Map<string, T> = new Map();
|
||||
for (const plugin of plugins) {
|
||||
if (isPluginCompatible(plugin)) {
|
||||
const loadedVersion = latestCompatibleVersions.get(plugin.id);
|
||||
if (!loadedVersion || isPluginVersionMoreRecent(plugin, loadedVersion)) {
|
||||
latestCompatibleVersions.set(plugin.id, plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Array.from(latestCompatibleVersions.values());
|
||||
}
|
||||
|
||||
async function getBundledPlugins(): Promise<Array<BundledPluginDetails>> {
|
||||
// defaultPlugins that are included in the Flipper distributive.
|
||||
// List of default bundled plugins is written at build time to defaultPlugins/bundled.json.
|
||||
const pluginPath = getStaticPath(
|
||||
path.join('defaultPlugins', 'bundled.json'),
|
||||
{asarUnpacked: true},
|
||||
);
|
||||
let bundledPlugins: Array<BundledPluginDetails> = [];
|
||||
try {
|
||||
bundledPlugins = await fs.readJson(pluginPath);
|
||||
} catch (e) {
|
||||
console.error('Failed to load list of bundled plugins', e);
|
||||
}
|
||||
|
||||
return bundledPlugins;
|
||||
}
|
||||
|
||||
export async function getDynamicPlugins() {
|
||||
try {
|
||||
return await loadDynamicPlugins();
|
||||
} catch (e) {
|
||||
console.error('Failed to load dynamic plugins', e);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export const checkGK =
|
||||
(gatekeepedPlugins: Array<ActivatablePluginDetails>) =>
|
||||
(plugin: ActivatablePluginDetails): boolean => {
|
||||
try {
|
||||
if (!plugin.gatekeeper) {
|
||||
return true;
|
||||
}
|
||||
const result = GK.get(plugin.gatekeeper);
|
||||
if (!result) {
|
||||
gatekeepedPlugins.push(plugin);
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.error(`Failed to check GK for plugin ${plugin.id}`, err);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const checkDisabled = (
|
||||
disabledPlugins: Array<ActivatablePluginDetails>,
|
||||
) => {
|
||||
let enabledList: Set<string> | null = null;
|
||||
let disabledList: Set<string> = new Set();
|
||||
try {
|
||||
if (process.env.FLIPPER_ENABLED_PLUGINS) {
|
||||
enabledList = new Set<string>(
|
||||
process.env.FLIPPER_ENABLED_PLUGINS.split(','),
|
||||
);
|
||||
}
|
||||
disabledList = config().disabledPlugins;
|
||||
} catch (e) {
|
||||
console.error('Failed to compute enabled/disabled plugins', e);
|
||||
}
|
||||
return (plugin: ActivatablePluginDetails): boolean => {
|
||||
try {
|
||||
if (disabledList.has(plugin.name)) {
|
||||
disabledPlugins.push(plugin);
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
enabledList &&
|
||||
!(
|
||||
enabledList.has(plugin.name) ||
|
||||
enabledList.has(plugin.id) ||
|
||||
enabledList.has(plugin.name.replace('flipper-plugin-', ''))
|
||||
)
|
||||
) {
|
||||
disabledPlugins.push(plugin);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Failed to check whether plugin ${plugin.id} is disabled`,
|
||||
e,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const createRequirePluginFunction = (
|
||||
failedPlugins: Array<[ActivatablePluginDetails, string]>,
|
||||
reqFn: Function = global.electronRequire,
|
||||
) => {
|
||||
return (pluginDetails: ActivatablePluginDetails): PluginDefinition | null => {
|
||||
try {
|
||||
const pluginDefinition = requirePlugin(pluginDetails, reqFn);
|
||||
if (
|
||||
pluginDefinition &&
|
||||
isDevicePluginDefinition(pluginDefinition) &&
|
||||
pluginDefinition.details.pluginType !== 'device'
|
||||
) {
|
||||
console.warn(
|
||||
`Package ${pluginDefinition.details.name} contains the device plugin "${pluginDefinition.title}" defined in a wrong format. Specify "pluginType" and "supportedDevices" properties and remove exported function "supportsDevice". See details at https://fbflipper.com/docs/extending/desktop-plugin-structure#creating-a-device-plugin.`,
|
||||
);
|
||||
}
|
||||
return pluginDefinition;
|
||||
} catch (e) {
|
||||
failedPlugins.push([pluginDetails, e.message]);
|
||||
console.error(`Plugin ${pluginDetails.id} failed to load`, e);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const requirePlugin = (
|
||||
pluginDetails: ActivatablePluginDetails,
|
||||
reqFn: Function = global.electronRequire,
|
||||
): PluginDefinition => {
|
||||
reportUsage(
|
||||
'plugin:load',
|
||||
{
|
||||
version: pluginDetails.version,
|
||||
},
|
||||
pluginDetails.id,
|
||||
);
|
||||
return tryCatchReportPluginFailures(
|
||||
() => requirePluginInternal(pluginDetails, reqFn),
|
||||
'plugin:load',
|
||||
pluginDetails.id,
|
||||
);
|
||||
};
|
||||
|
||||
const isSandyPlugin = (pluginDetails: ActivatablePluginDetails) => {
|
||||
return !!pluginDetails.flipperSDKVersion;
|
||||
};
|
||||
|
||||
const requirePluginInternal = (
|
||||
pluginDetails: ActivatablePluginDetails,
|
||||
reqFn: Function = global.electronRequire,
|
||||
): PluginDefinition => {
|
||||
let plugin = pluginDetails.isBundled
|
||||
? defaultPluginsIndex[pluginDetails.name]
|
||||
: reqFn(pluginDetails.entry);
|
||||
if (isSandyPlugin(pluginDetails)) {
|
||||
// Sandy plugin
|
||||
return new _SandyPluginDefinition(pluginDetails, plugin);
|
||||
} else {
|
||||
// classic plugin
|
||||
if (plugin.default) {
|
||||
plugin = plugin.default;
|
||||
}
|
||||
if (plugin.prototype === undefined) {
|
||||
throw new Error(
|
||||
`Plugin ${pluginDetails.name} is neither a class-based plugin nor a Sandy-based one.
|
||||
Ensure that it exports either a FlipperPlugin class or has flipper-plugin declared as a peer-dependency and exports a plugin and Component.
|
||||
See https://fbflipper.com/docs/extending/sandy-migration/ for more information.`,
|
||||
);
|
||||
} else if (!(plugin.prototype instanceof FlipperBasePlugin)) {
|
||||
throw new Error(
|
||||
`Plugin ${pluginDetails.name} is not a FlipperBasePlugin`,
|
||||
);
|
||||
}
|
||||
|
||||
if (plugin.id && pluginDetails.id !== plugin.id) {
|
||||
console.error(
|
||||
`Plugin name mismatch: Package '${pluginDetails.id}' exposed a plugin with id '${plugin.id}'. Please update the 'package.json' to match the exposed plugin id`,
|
||||
);
|
||||
}
|
||||
plugin.id = plugin.id || pluginDetails.id;
|
||||
plugin.packageName = pluginDetails.name;
|
||||
plugin.details = pluginDetails;
|
||||
|
||||
return createSandyPluginFromClassicPlugin(pluginDetails, plugin);
|
||||
}
|
||||
};
|
||||
|
||||
export function createSandyPluginFromClassicPlugin(
|
||||
pluginDetails: ActivatablePluginDetails,
|
||||
plugin: any,
|
||||
) {
|
||||
pluginDetails.id = plugin.id; // for backward compatibility, see above check!
|
||||
return new _SandyPluginDefinition(
|
||||
pluginDetails,
|
||||
createSandyPluginWrapper(plugin),
|
||||
);
|
||||
}
|
||||
|
||||
export function selectCompatibleMarketplaceVersions(
|
||||
availablePlugins: MarketplacePluginDetails[],
|
||||
): MarketplacePluginDetails[] {
|
||||
const plugins: MarketplacePluginDetails[] = [];
|
||||
for (const plugin of availablePlugins) {
|
||||
if (!isPluginCompatible(plugin)) {
|
||||
const compatibleVersion =
|
||||
plugin.availableVersions?.find(isPluginCompatible) ??
|
||||
plugin.availableVersions?.slice(-1).pop();
|
||||
if (compatibleVersion) {
|
||||
plugins.push({
|
||||
...compatibleVersion,
|
||||
availableVersions: plugin?.availableVersions,
|
||||
});
|
||||
} else {
|
||||
plugins.push(plugin);
|
||||
}
|
||||
} else {
|
||||
plugins.push(plugin);
|
||||
}
|
||||
}
|
||||
return plugins;
|
||||
}
|
||||
Reference in New Issue
Block a user