From 12ac29685d38877fcdb2b64eee35e116f766d975 Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Wed, 1 Jul 2020 08:58:40 -0700 Subject: [PATCH] make sure plugins can be loaded into Flipper Summary: Make sure Sandy plugins are loaded properly from disk Reviewed By: jknoxville Differential Revision: D22186275 fbshipit-source-id: fd2f560a7bed959b18e05db2a087909ad876ab9d --- .../dispatcher/__tests__/SandyTestPlugin.tsx | 24 ++++++++++ .../src/dispatcher/__tests__/plugins.node.tsx | 48 +++++++++++++++++++ desktop/app/src/dispatcher/plugins.tsx | 40 +++++++++------- desktop/app/src/plugin.tsx | 1 - desktop/flipper-plugin/src/plugin/Plugin.tsx | 10 +++- 5 files changed, 104 insertions(+), 19 deletions(-) create mode 100644 desktop/app/src/dispatcher/__tests__/SandyTestPlugin.tsx diff --git a/desktop/app/src/dispatcher/__tests__/SandyTestPlugin.tsx b/desktop/app/src/dispatcher/__tests__/SandyTestPlugin.tsx new file mode 100644 index 000000000..ec286894e --- /dev/null +++ b/desktop/app/src/dispatcher/__tests__/SandyTestPlugin.tsx @@ -0,0 +1,24 @@ +/** + * 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 * as React from 'react'; + +import {FlipperClient} from 'flipper-plugin'; + +type Events = { + inc: {delta: number}; +}; + +export function plugin(_client: FlipperClient) { + return {}; +} + +export function Component() { + return

Sandy high fives Flipper

; +} diff --git a/desktop/app/src/dispatcher/__tests__/plugins.node.tsx b/desktop/app/src/dispatcher/__tests__/plugins.node.tsx index 961db6852..d7c24290a 100644 --- a/desktop/app/src/dispatcher/__tests__/plugins.node.tsx +++ b/desktop/app/src/dispatcher/__tests__/plugins.node.tsx @@ -29,6 +29,7 @@ import configureStore from 'redux-mock-store'; 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'; const mockStore = configureStore([])( reducers(undefined, {type: 'INIT'}), @@ -239,3 +240,50 @@ test('bundled versions are used when env var FLIPPER_DISABLE_PLUGIN_AUTO_UPDATE delete process.env.FLIPPER_DISABLE_PLUGIN_AUTO_UPDATE; } }); + +test('requirePlugin loads valid Sandy plugin', () => { + const name = 'pluginID'; + const requireFn = requirePlugin([], {}, require); + const plugin = requireFn({ + ...samplePluginDetails, + name, + entry: path.join(__dirname, 'SandyTestPlugin'), + version: '1.0.0', + flipperSDKVersion: '0.0.0', + }) as SandyPluginDefinition; + expect(plugin).not.toBeNull(); + // @ts-ignore + expect(plugin).toBeInstanceOf(SandyPluginDefinition); + expect(plugin.id).toBe('Sample'); + expect(plugin.details).toMatchObject({ + flipperSDKVersion: '0.0.0', + id: 'Sample', + isDefault: false, + main: 'dist/bundle.js', + name: 'pluginID', + source: 'src/index.js', + specVersion: 2, + title: 'Sample', + version: '1.0.0', + }); + expect(typeof plugin.module.Component).toBe('function'); + expect(plugin.module.Component.displayName).toBe('FlipperPlugin(Sample)'); + expect(typeof plugin.module.plugin).toBe('function'); +}); + +test('requirePlugin errors on invalid Sandy plugin', () => { + const name = 'pluginID'; + const failedPlugins: any[] = []; + const requireFn = requirePlugin(failedPlugins, {}, require); + requireFn({ + ...samplePluginDetails, + name, + // Intentionally the wrong file: + entry: path.join(__dirname, 'TestPlugin'), + version: '1.0.0', + flipperSDKVersion: '0.0.0', + }); + expect(failedPlugins[0][1]).toMatchInlineSnapshot( + `"Flipper plugin 'Sample' should export named function called 'plugin'"`, + ); +}); diff --git a/desktop/app/src/dispatcher/plugins.tsx b/desktop/app/src/dispatcher/plugins.tsx index b14c2ed5a..de882d493 100644 --- a/desktop/app/src/dispatcher/plugins.tsx +++ b/desktop/app/src/dispatcher/plugins.tsx @@ -38,6 +38,7 @@ import * as FlipperPluginSDK from 'flipper-plugin'; // eslint-disable-next-line import/no-unresolved import getPluginIndex from '../utils/getDefaultPluginsIndex'; +import {SandyPluginDefinition} from 'flipper-plugin'; const Paragraph = styled.p({ marginBottom: '0.1em', @@ -271,23 +272,30 @@ const requirePluginInternal = ( let plugin = pluginDetails.isDefault ? defaultPluginsIndex[pluginDetails.name] : reqFn(pluginDetails.entry); - if (plugin.default) { - plugin = plugin.default; - } - if (!(plugin.prototype instanceof FlipperBasePlugin)) { - throw new Error(`Plugin ${plugin.name} is not a FlipperBasePlugin`); - } - plugin.id = plugin.id || pluginDetails.id; - plugin.packageName = pluginDetails.name; - plugin.flipperSDKVersion = pluginDetails.flipperSDKVersion; - plugin.details = pluginDetails; - - // set values from package.json as static variables on class - Object.keys(pluginDetails).forEach((key) => { - if (key !== 'name' && key !== 'id') { - plugin[key] = plugin[key] || pluginDetails[key as keyof PluginDetails]; + if (pluginDetails.flipperSDKVersion) { + // Sandy plugin + // TODO: suppor device Plugins T68738317 + return new SandyPluginDefinition(pluginDetails, plugin); + } else { + // classic plugin + if (plugin.default) { + plugin = plugin.default; } - }); + if (!(plugin.prototype instanceof FlipperBasePlugin)) { + throw new Error(`Plugin ${plugin.name} is not a FlipperBasePlugin`); + } + + plugin.id = plugin.id || pluginDetails.id; + plugin.packageName = pluginDetails.name; + plugin.details = pluginDetails; + + // set values from package.json as static variables on class + Object.keys(pluginDetails).forEach((key) => { + if (key !== 'name' && key !== 'id') { + plugin[key] = plugin[key] || pluginDetails[key as keyof PluginDetails]; + } + }); + } return plugin; }; diff --git a/desktop/app/src/plugin.tsx b/desktop/app/src/plugin.tsx index b9e60646f..594890dd6 100644 --- a/desktop/app/src/plugin.tsx +++ b/desktop/app/src/plugin.tsx @@ -113,7 +113,6 @@ export abstract class FlipperBasePlugin< static category: string | null = null; static id: string = ''; static packageName: string = ''; - static flipperSDKVersion: string | undefined = undefined; static version: string = ''; static icon: string | null = null; static gatekeeper: string | null = null; diff --git a/desktop/flipper-plugin/src/plugin/Plugin.tsx b/desktop/flipper-plugin/src/plugin/Plugin.tsx index 40b39a945..4bfc6f1c0 100644 --- a/desktop/flipper-plugin/src/plugin/Plugin.tsx +++ b/desktop/flipper-plugin/src/plugin/Plugin.tsx @@ -64,6 +64,12 @@ export class FlipperPluginInstance { } } +/** + * A sandy plugin definitions represents a loaded plugin definition, storing two things: + * the loaded JS module, and the meta data (typically coming from package.json). + * + * Also delegates some of the standard plugin functionality to have a similar public static api as FlipperPlugin + */ export class SandyPluginDefinition { id: string; module: FlipperPluginModule; @@ -86,12 +92,12 @@ export class SandyPluginDefinition { this.details = details; if (!module.plugin || typeof module.plugin !== 'function') { throw new Error( - `Sandy plugin ${this.id} doesn't export a named function called 'plugin'`, + `Flipper plugin '${this.id}' should export named function called 'plugin'`, ); } if (!module.Component || typeof module.Component !== 'function') { throw new Error( - `Sandy plugin ${this.id} doesn't export a named function called 'Component'`, + `Flipper plugin '${this.id}' should export named function called 'Component'`, ); } this.module = module;