Files
flipper/desktop/flipper-ui-core/src/dispatcher/plugins.tsx
Andrey Goncharov 3c09ac8b2b Enable power search as default experience for users passing GK
Reviewed By: antonk52

Differential Revision: D49822508

fbshipit-source-id: f280f0032a6292fbc8c73a36a8b47e35ffb4e7fd
2023-10-02 08:27:37 -07:00

223 lines
6.8 KiB
TypeScript

/**
* Copyright (c) Meta Platforms, Inc. and 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 {
InstalledPluginDetails,
Logger,
MarketplacePluginDetails,
} from 'flipper-common';
import {PluginDefinition} from '../plugin';
import React from 'react';
import * as ReactJsxRuntime from 'react/jsx-runtime';
import ReactDOM from 'react-dom';
import ReactDOMClient from 'react-dom/client';
import ReactIs from 'react-is';
import {
registerPlugins,
addGatekeepedPlugins,
addDisabledPlugins,
addFailedPlugins,
registerLoadedPlugins,
registerMarketplacePlugins,
pluginsInitialized,
} from '../reducers/plugins';
import {FlipperBasePlugin} from '../plugin';
import {ActivatablePluginDetails} from 'flipper-common';
import * as FlipperPluginSDK from 'flipper-plugin';
import {_SandyPluginDefinition} from 'flipper-plugin';
import * as Immer from 'immer';
import * as antd from 'antd';
import * as emotion_styled from '@emotion/styled';
import * as emotion_css from '@emotion/css';
import * as antdesign_icons from '@ant-design/icons';
import isPluginCompatible from '../utils/isPluginCompatible';
import {createSandyPluginWrapper} from '../utils/createSandyPluginWrapper';
import {
AbstractPluginInitializer,
getRenderHostInstance,
setGlobalObject,
isSandyPlugin,
wrapRequirePlugin,
} from 'flipper-frontend-core';
import * as deprecatedExports from '../deprecated-exports';
import {getAppVersion} from '../utils/info';
class UIPluginInitializer extends AbstractPluginInitializer {
constructor(private readonly store: Store) {
super();
}
async init() {
await super.init();
const classicPlugins = this._initialPlugins.filter(
(p) => !isSandyPlugin(p.details),
);
if (
getRenderHostInstance().serverConfig.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')}`,
);
}
this.store.dispatch(registerLoadedPlugins(this.loadedPlugins));
this.store.dispatch(addGatekeepedPlugins(this.gatekeepedPlugins));
this.store.dispatch(addDisabledPlugins(this.disabledPlugins));
this.store.dispatch(addFailedPlugins(this.failedPlugins));
this.store.dispatch(registerPlugins(this._initialPlugins));
this.store.dispatch(pluginsInitialized());
}
protected async getFlipperVersion() {
return getAppVersion();
}
public requirePluginImpl(pluginDetails: ActivatablePluginDetails) {
return requirePluginInternal(pluginDetails);
}
protected loadMarketplacePlugins() {
const marketplacePlugins = selectCompatibleMarketplaceVersions(
this.store.getState().plugins.marketplacePlugins,
);
this.store.dispatch(registerMarketplacePlugins(marketplacePlugins));
}
protected loadUninstalledPluginNames() {
return this.store.getState().plugins.uninstalledPluginNames;
}
}
let uiPluginInitializer: UIPluginInitializer;
export default async (store: Store, _logger: Logger) => {
let FlipperPlugin = FlipperPluginSDK;
if (getRenderHostInstance().GK('flipper_power_search')) {
FlipperPlugin = {
...FlipperPlugin,
MasterDetail: FlipperPlugin._MasterDetailWithPowerSearch as any,
DataTable: FlipperPlugin._DataTableWithPowerSearch as any,
};
}
setGlobalObject({
React,
ReactDOM,
ReactDOMClient,
ReactIs,
Flipper: deprecatedExports,
FlipperPlugin,
Immer,
antd,
emotion_styled,
emotion_css,
antdesign_icons,
ReactJsxRuntime,
});
uiPluginInitializer = new UIPluginInitializer(store);
await uiPluginInitializer.init();
};
export const requirePlugin = (pluginDetails: ActivatablePluginDetails) =>
wrapRequirePlugin(
uiPluginInitializer!.requirePluginImpl.bind(uiPluginInitializer),
)(pluginDetails);
export const requirePluginInternal = async (
pluginDetails: ActivatablePluginDetails,
): Promise<PluginDefinition> => {
const requiredPlugin = await getRenderHostInstance().requirePlugin(
(pluginDetails as InstalledPluginDetails).entry,
);
if (!requiredPlugin || !requiredPlugin.plugin) {
throw new Error(
`Failed to obtain plugin source for: ${pluginDetails.name}`,
);
}
if (isSandyPlugin(pluginDetails)) {
// Sandy plugin
return new _SandyPluginDefinition(
pluginDetails,
requiredPlugin.plugin,
requiredPlugin.css,
);
} else {
// Classic plugin
let plugin = requiredPlugin.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;
}