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
This commit is contained in:
Michel Weststrate
2020-07-01 08:58:40 -07:00
committed by Facebook GitHub Bot
parent 1029a6c97c
commit 12ac29685d
5 changed files with 104 additions and 19 deletions

View File

@@ -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<Events, {}>) {
return {};
}
export function Component() {
return <h1>Sandy high fives Flipper</h1>;
}

View File

@@ -29,6 +29,7 @@ import configureStore from 'redux-mock-store';
import {TEST_PASSING_GK, TEST_FAILING_GK} from '../../fb-stubs/GK'; import {TEST_PASSING_GK, TEST_FAILING_GK} from '../../fb-stubs/GK';
import TestPlugin from './TestPlugin'; import TestPlugin from './TestPlugin';
import {resetConfigForTesting} from '../../utils/processConfig'; import {resetConfigForTesting} from '../../utils/processConfig';
import {SandyPluginDefinition} from 'flipper-plugin';
const mockStore = configureStore<State, {}>([])( const mockStore = configureStore<State, {}>([])(
reducers(undefined, {type: 'INIT'}), 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; 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'"`,
);
});

View File

@@ -38,6 +38,7 @@ import * as FlipperPluginSDK from 'flipper-plugin';
// eslint-disable-next-line import/no-unresolved // eslint-disable-next-line import/no-unresolved
import getPluginIndex from '../utils/getDefaultPluginsIndex'; import getPluginIndex from '../utils/getDefaultPluginsIndex';
import {SandyPluginDefinition} from 'flipper-plugin';
const Paragraph = styled.p({ const Paragraph = styled.p({
marginBottom: '0.1em', marginBottom: '0.1em',
@@ -271,23 +272,30 @@ const requirePluginInternal = (
let plugin = pluginDetails.isDefault let plugin = pluginDetails.isDefault
? defaultPluginsIndex[pluginDetails.name] ? defaultPluginsIndex[pluginDetails.name]
: reqFn(pluginDetails.entry); : 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; if (pluginDetails.flipperSDKVersion) {
plugin.packageName = pluginDetails.name; // Sandy plugin
plugin.flipperSDKVersion = pluginDetails.flipperSDKVersion; // TODO: suppor device Plugins T68738317
plugin.details = pluginDetails; return new SandyPluginDefinition(pluginDetails, plugin);
} else {
// set values from package.json as static variables on class // classic plugin
Object.keys(pluginDetails).forEach((key) => { if (plugin.default) {
if (key !== 'name' && key !== 'id') { plugin = plugin.default;
plugin[key] = plugin[key] || pluginDetails[key as keyof PluginDetails];
} }
}); 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; return plugin;
}; };

View File

@@ -113,7 +113,6 @@ export abstract class FlipperBasePlugin<
static category: string | null = null; static category: string | null = null;
static id: string = ''; static id: string = '';
static packageName: string = ''; static packageName: string = '';
static flipperSDKVersion: string | undefined = undefined;
static version: string = ''; static version: string = '';
static icon: string | null = null; static icon: string | null = null;
static gatekeeper: string | null = null; static gatekeeper: string | null = null;

View File

@@ -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 { export class SandyPluginDefinition {
id: string; id: string;
module: FlipperPluginModule; module: FlipperPluginModule;
@@ -86,12 +92,12 @@ export class SandyPluginDefinition {
this.details = details; this.details = details;
if (!module.plugin || typeof module.plugin !== 'function') { if (!module.plugin || typeof module.plugin !== 'function') {
throw new Error( 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') { if (!module.Component || typeof module.Component !== 'function') {
throw new Error( 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; this.module = module;