Use the single type representing plugins
Summary: Use interface PluginDetails everywhere where plugins are handled and removed PluginDefinition type which was effectively a subset of PluginDetails Reviewed By: mweststrate Differential Revision: D21927456 fbshipit-source-id: 434ebeef955b922cc11757e78fbba8dec05f1060
This commit is contained in:
committed by
Facebook GitHub Bot
parent
907cb9e3cc
commit
db3f04a2d7
@@ -7,7 +7,7 @@
|
|||||||
* @format
|
* @format
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {PluginDefinition} from '../../dispatcher/plugins';
|
import {PluginDetails} from 'flipper-plugin-lib';
|
||||||
import Client from '../../Client';
|
import Client from '../../Client';
|
||||||
import {TableBodyRow} from '../../ui/components/table/types';
|
import {TableBodyRow} from '../../ui/components/table/types';
|
||||||
import React, {Component, Fragment} from 'react';
|
import React, {Component, Fragment} from 'react';
|
||||||
@@ -46,9 +46,9 @@ const Lamp = (props: {on: boolean}) => (
|
|||||||
);
|
);
|
||||||
|
|
||||||
type StateFromProps = {
|
type StateFromProps = {
|
||||||
gatekeepedPlugins: Array<PluginDefinition>;
|
gatekeepedPlugins: Array<PluginDetails>;
|
||||||
disabledPlugins: Array<PluginDefinition>;
|
disabledPlugins: Array<PluginDetails>;
|
||||||
failedPlugins: Array<[PluginDefinition, string]>;
|
failedPlugins: Array<[PluginDetails, string]>;
|
||||||
clients: Array<Client>;
|
clients: Array<Client>;
|
||||||
selectedDevice: string | null | undefined;
|
selectedDevice: string | null | undefined;
|
||||||
devicePlugins: Array<typeof FlipperDevicePlugin>;
|
devicePlugins: Array<typeof FlipperDevicePlugin>;
|
||||||
@@ -101,7 +101,7 @@ class PluginDebugger extends Component<Props> {
|
|||||||
loaded: boolean,
|
loaded: boolean,
|
||||||
status: string,
|
status: string,
|
||||||
GKname: string | null | undefined,
|
GKname: string | null | undefined,
|
||||||
pluginPath: string | null | undefined,
|
pluginPath: string,
|
||||||
): TableBodyRow {
|
): TableBodyRow {
|
||||||
return {
|
return {
|
||||||
key: name.toLowerCase(),
|
key: name.toLowerCase(),
|
||||||
@@ -123,12 +123,10 @@ class PluginDebugger extends Component<Props> {
|
|||||||
value: this.getSupportedClients(name),
|
value: this.getSupportedClients(name),
|
||||||
},
|
},
|
||||||
source: {
|
source: {
|
||||||
value: pluginPath ? (
|
value: (
|
||||||
<Ellipsis code title={pluginPath}>
|
<Ellipsis code title={pluginPath}>
|
||||||
{pluginPath}
|
{pluginPath}
|
||||||
</Ellipsis>
|
</Ellipsis>
|
||||||
) : (
|
|
||||||
<i>bundled</i>
|
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -149,7 +147,7 @@ class PluginDebugger extends Component<Props> {
|
|||||||
getRows(): Array<TableBodyRow> {
|
getRows(): Array<TableBodyRow> {
|
||||||
const rows: Array<TableBodyRow> = [];
|
const rows: Array<TableBodyRow> = [];
|
||||||
|
|
||||||
const externalPluginPath = (p: any) => p.entry || 'Native Plugin';
|
const externalPluginPath = (p: any) => (p.isDefault ? 'bundled' : p.entry);
|
||||||
|
|
||||||
this.props.gatekeepedPlugins.forEach((plugin) =>
|
this.props.gatekeepedPlugins.forEach((plugin) =>
|
||||||
rows.push(
|
rows.push(
|
||||||
|
|||||||
@@ -29,18 +29,16 @@ import React, {useCallback, useState, useMemo, useEffect} from 'react';
|
|||||||
import {List} from 'immutable';
|
import {List} from 'immutable';
|
||||||
import {SearchIndex} from 'algoliasearch';
|
import {SearchIndex} from 'algoliasearch';
|
||||||
import {SearchResponse} from '@algolia/client-search';
|
import {SearchResponse} from '@algolia/client-search';
|
||||||
import path from 'path';
|
|
||||||
import fs from 'fs-extra';
|
|
||||||
import {reportPlatformFailures, reportUsage} from '../../utils/metrics';
|
import {reportPlatformFailures, reportUsage} from '../../utils/metrics';
|
||||||
import restartFlipper from '../../utils/restartFlipper';
|
import restartFlipper from '../../utils/restartFlipper';
|
||||||
|
import {registerInstalledPlugins} from '../../reducers/pluginManager';
|
||||||
import {
|
import {
|
||||||
PluginMap,
|
|
||||||
PluginDefinition,
|
|
||||||
registerInstalledPlugins,
|
|
||||||
} from '../../reducers/pluginManager';
|
|
||||||
import {
|
|
||||||
PLUGIN_DIR,
|
|
||||||
readInstalledPlugins,
|
readInstalledPlugins,
|
||||||
|
removePlugin,
|
||||||
|
PluginMap,
|
||||||
|
PluginDetails,
|
||||||
|
} from 'flipper-plugin-lib';
|
||||||
|
import {
|
||||||
provideSearchIndex,
|
provideSearchIndex,
|
||||||
findPluginUpdates as _findPluginUpdates,
|
findPluginUpdates as _findPluginUpdates,
|
||||||
UpdateResult,
|
UpdateResult,
|
||||||
@@ -126,11 +124,11 @@ type UpdatablePlugin = {
|
|||||||
updateStatus: UpdateResult;
|
updateStatus: UpdateResult;
|
||||||
};
|
};
|
||||||
|
|
||||||
type UpdatablePluginDefinition = PluginDefinition & UpdatablePlugin;
|
type UpdatablePluginDefinition = PluginDetails & UpdatablePlugin;
|
||||||
|
|
||||||
// exported for testing
|
// exported for testing
|
||||||
export function annotatePluginsWithUpdates(
|
export function annotatePluginsWithUpdates(
|
||||||
installedPlugins: Map<string, PluginDefinition>,
|
installedPlugins: PluginMap,
|
||||||
updates: Map<string, UpdateResult>,
|
updates: Map<string, UpdateResult>,
|
||||||
): Map<string, UpdatablePluginDefinition> {
|
): Map<string, UpdatablePluginDefinition> {
|
||||||
const annotated: Array<[string, UpdatablePluginDefinition]> = Array.from(
|
const annotated: Array<[string, UpdatablePluginDefinition]> = Array.from(
|
||||||
@@ -211,7 +209,7 @@ const AlignedGlyph = styled(Glyph)({
|
|||||||
marginTop: 6,
|
marginTop: 6,
|
||||||
});
|
});
|
||||||
|
|
||||||
function liftUpdatable(val: PluginDefinition): UpdatablePluginDefinition {
|
function liftUpdatable(val: PluginDetails): UpdatablePluginDefinition {
|
||||||
return {
|
return {
|
||||||
...val,
|
...val,
|
||||||
updateStatus: {kind: 'up-to-date'},
|
updateStatus: {kind: 'up-to-date'},
|
||||||
@@ -272,7 +270,7 @@ function InstallButton(props: {
|
|||||||
catchError('Remove', async () => {
|
catchError('Remove', async () => {
|
||||||
reportUsage(`${TAG}:remove`, undefined, props.name);
|
reportUsage(`${TAG}:remove`, undefined, props.name);
|
||||||
setAction({kind: 'Waiting'});
|
setAction({kind: 'Waiting'});
|
||||||
await fs.remove(path.join(PLUGIN_DIR, props.name));
|
await removePlugin(props.name);
|
||||||
props.onInstall();
|
props.onInstall();
|
||||||
setAction({kind: 'Install'});
|
setAction({kind: 'Install'});
|
||||||
}),
|
}),
|
||||||
@@ -336,7 +334,7 @@ function useNPMSearch(
|
|||||||
query: string,
|
query: string,
|
||||||
setQuery: (query: string) => void,
|
setQuery: (query: string) => void,
|
||||||
searchClientFactory: () => SearchIndex,
|
searchClientFactory: () => SearchIndex,
|
||||||
installedPlugins: Map<string, PluginDefinition>,
|
installedPlugins: PluginMap,
|
||||||
onInstall: () => Promise<void>,
|
onInstall: () => Promise<void>,
|
||||||
findPluginUpdates: (
|
findPluginUpdates: (
|
||||||
currentPlugins: PluginMap,
|
currentPlugins: PluginMap,
|
||||||
@@ -403,11 +401,11 @@ function useNPMSearch(
|
|||||||
(async () => {
|
(async () => {
|
||||||
let cancelled = false;
|
let cancelled = false;
|
||||||
const {hits} = await reportPlatformFailures(
|
const {hits} = await reportPlatformFailures(
|
||||||
index.search<PluginDefinition>('', {
|
index.search<PluginDetails>('', {
|
||||||
query,
|
query,
|
||||||
filters: 'keywords:flipper-plugin',
|
filters: 'keywords:flipper-plugin',
|
||||||
hitsPerPage: 20,
|
hitsPerPage: 20,
|
||||||
}) as Promise<SearchResponse<PluginDefinition>>,
|
}) as Promise<SearchResponse<PluginDetails>>,
|
||||||
`${TAG}:queryIndex`,
|
`${TAG}:queryIndex`,
|
||||||
);
|
);
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
|
|||||||
@@ -9,15 +9,24 @@
|
|||||||
|
|
||||||
import {annotatePluginsWithUpdates} from '../PluginInstaller';
|
import {annotatePluginsWithUpdates} from '../PluginInstaller';
|
||||||
import {UpdateResult} from '../../../utils/pluginManager';
|
import {UpdateResult} from '../../../utils/pluginManager';
|
||||||
|
import {PluginDetails} from 'flipper-plugin-lib';
|
||||||
|
|
||||||
test('annotatePluginsWithUpdates', async () => {
|
test('annotatePluginsWithUpdates', async () => {
|
||||||
const installedPlugins = new Map([
|
const installedPlugins = new Map<string, PluginDetails>([
|
||||||
[
|
[
|
||||||
'example',
|
'example',
|
||||||
{
|
{
|
||||||
name: 'example',
|
name: 'example',
|
||||||
version: '0.1.0',
|
version: '0.1.0',
|
||||||
description: 'Gaze into the death crystal',
|
description: 'Gaze into the death crystal',
|
||||||
|
dir: '/plugins/example',
|
||||||
|
specVersion: 2,
|
||||||
|
source: 'src/index.ts',
|
||||||
|
isDefault: false,
|
||||||
|
main: 'lib/index.js',
|
||||||
|
title: 'Example',
|
||||||
|
id: 'Example',
|
||||||
|
entry: '/plugins/example/lib/index.js',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
@@ -26,6 +35,14 @@ test('annotatePluginsWithUpdates', async () => {
|
|||||||
name: 'ricksybusiness',
|
name: 'ricksybusiness',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
description: 'Rick Die Rickpeat',
|
description: 'Rick Die Rickpeat',
|
||||||
|
dir: '/plugins/example',
|
||||||
|
specVersion: 2,
|
||||||
|
source: 'src/index.ts',
|
||||||
|
isDefault: false,
|
||||||
|
main: 'lib/index.js',
|
||||||
|
title: 'ricksybusiness',
|
||||||
|
id: 'ricksybusiness',
|
||||||
|
entry: '/plugins/ricksybusiness/lib/index.js',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
@@ -37,7 +54,15 @@ test('annotatePluginsWithUpdates', async () => {
|
|||||||
Map {
|
Map {
|
||||||
"example" => Object {
|
"example" => Object {
|
||||||
"description": "Gaze into the death crystal",
|
"description": "Gaze into the death crystal",
|
||||||
|
"dir": "/plugins/example",
|
||||||
|
"entry": "/plugins/example/lib/index.js",
|
||||||
|
"id": "Example",
|
||||||
|
"isDefault": false,
|
||||||
|
"main": "lib/index.js",
|
||||||
"name": "example",
|
"name": "example",
|
||||||
|
"source": "src/index.ts",
|
||||||
|
"specVersion": 2,
|
||||||
|
"title": "Example",
|
||||||
"updateStatus": Object {
|
"updateStatus": Object {
|
||||||
"kind": "update-available",
|
"kind": "update-available",
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
@@ -46,7 +71,15 @@ test('annotatePluginsWithUpdates', async () => {
|
|||||||
},
|
},
|
||||||
"ricksybusiness" => Object {
|
"ricksybusiness" => Object {
|
||||||
"description": "Rick Die Rickpeat",
|
"description": "Rick Die Rickpeat",
|
||||||
|
"dir": "/plugins/example",
|
||||||
|
"entry": "/plugins/ricksybusiness/lib/index.js",
|
||||||
|
"id": "ricksybusiness",
|
||||||
|
"isDefault": false,
|
||||||
|
"main": "lib/index.js",
|
||||||
"name": "ricksybusiness",
|
"name": "ricksybusiness",
|
||||||
|
"source": "src/index.ts",
|
||||||
|
"specVersion": 2,
|
||||||
|
"title": "ricksybusiness",
|
||||||
"updateStatus": Object {
|
"updateStatus": Object {
|
||||||
"kind": "up-to-date",
|
"kind": "up-to-date",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,13 +10,13 @@
|
|||||||
jest.mock('../../defaultPlugins');
|
jest.mock('../../defaultPlugins');
|
||||||
|
|
||||||
import dispatcher, {
|
import dispatcher, {
|
||||||
PluginDefinition,
|
|
||||||
getDynamicPlugins,
|
getDynamicPlugins,
|
||||||
checkDisabled,
|
checkDisabled,
|
||||||
checkGK,
|
checkGK,
|
||||||
requirePlugin,
|
requirePlugin,
|
||||||
filterNewestVersionOfEachPlugin,
|
filterNewestVersionOfEachPlugin,
|
||||||
} from '../plugins';
|
} from '../plugins';
|
||||||
|
import {PluginDetails} from 'flipper-plugin-lib';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import {ipcRenderer, remote} from 'electron';
|
import {ipcRenderer, remote} from 'electron';
|
||||||
import {FlipperPlugin} from 'flipper';
|
import {FlipperPlugin} from 'flipper';
|
||||||
@@ -32,6 +32,19 @@ const mockStore = configureStore<State, {}>([])(
|
|||||||
);
|
);
|
||||||
const logger = initLogger(mockStore);
|
const logger = initLogger(mockStore);
|
||||||
|
|
||||||
|
const samplePluginDetails: PluginDetails = {
|
||||||
|
name: 'other Name',
|
||||||
|
entry: './test/index.js',
|
||||||
|
version: '1.0.0',
|
||||||
|
specVersion: 2,
|
||||||
|
main: 'dist/bundle.js',
|
||||||
|
dir: '/Users/mock/.flipper/thirdparty/flipper-plugin-sample',
|
||||||
|
source: 'src/index.js',
|
||||||
|
id: 'Sample',
|
||||||
|
title: 'Sample',
|
||||||
|
isDefault: false,
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
resetConfigForTesting();
|
resetConfigForTesting();
|
||||||
});
|
});
|
||||||
@@ -69,6 +82,7 @@ test('checkDisabled', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
disabled({
|
disabled({
|
||||||
|
...samplePluginDetails,
|
||||||
name: 'other Name',
|
name: 'other Name',
|
||||||
entry: './test/index.js',
|
entry: './test/index.js',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
@@ -77,6 +91,7 @@ test('checkDisabled', () => {
|
|||||||
|
|
||||||
expect(
|
expect(
|
||||||
disabled({
|
disabled({
|
||||||
|
...samplePluginDetails,
|
||||||
name: disabledPlugin,
|
name: disabledPlugin,
|
||||||
entry: './test/index.js',
|
entry: './test/index.js',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
@@ -87,6 +102,7 @@ test('checkDisabled', () => {
|
|||||||
test('checkGK for plugin without GK', () => {
|
test('checkGK for plugin without GK', () => {
|
||||||
expect(
|
expect(
|
||||||
checkGK([])({
|
checkGK([])({
|
||||||
|
...samplePluginDetails,
|
||||||
name: 'pluginID',
|
name: 'pluginID',
|
||||||
entry: './test/index.js',
|
entry: './test/index.js',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
@@ -97,6 +113,7 @@ test('checkGK for plugin without GK', () => {
|
|||||||
test('checkGK for passing plugin', () => {
|
test('checkGK for passing plugin', () => {
|
||||||
expect(
|
expect(
|
||||||
checkGK([])({
|
checkGK([])({
|
||||||
|
...samplePluginDetails,
|
||||||
name: 'pluginID',
|
name: 'pluginID',
|
||||||
gatekeeper: TEST_PASSING_GK,
|
gatekeeper: TEST_PASSING_GK,
|
||||||
entry: './test/index.js',
|
entry: './test/index.js',
|
||||||
@@ -106,9 +123,10 @@ test('checkGK for passing plugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('checkGK for failing plugin', () => {
|
test('checkGK for failing plugin', () => {
|
||||||
const gatekeepedPlugins: PluginDefinition[] = [];
|
const gatekeepedPlugins: PluginDetails[] = [];
|
||||||
const name = 'pluginID';
|
const name = 'pluginID';
|
||||||
const plugins = checkGK(gatekeepedPlugins)({
|
const plugins = checkGK(gatekeepedPlugins)({
|
||||||
|
...samplePluginDetails,
|
||||||
name,
|
name,
|
||||||
gatekeeper: TEST_FAILING_GK,
|
gatekeeper: TEST_FAILING_GK,
|
||||||
entry: './test/index.js',
|
entry: './test/index.js',
|
||||||
@@ -122,6 +140,7 @@ test('checkGK for failing plugin', () => {
|
|||||||
test('requirePlugin returns null for invalid requires', () => {
|
test('requirePlugin returns null for invalid requires', () => {
|
||||||
const requireFn = requirePlugin([], {}, require);
|
const requireFn = requirePlugin([], {}, require);
|
||||||
const plugin = requireFn({
|
const plugin = requireFn({
|
||||||
|
...samplePluginDetails,
|
||||||
name: 'pluginID',
|
name: 'pluginID',
|
||||||
entry: 'this/path/does not/exist',
|
entry: 'this/path/does not/exist',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
@@ -134,6 +153,7 @@ test('requirePlugin loads plugin', () => {
|
|||||||
const name = 'pluginID';
|
const name = 'pluginID';
|
||||||
const requireFn = requirePlugin([], {}, require);
|
const requireFn = requirePlugin([], {}, require);
|
||||||
const plugin = requireFn({
|
const plugin = requireFn({
|
||||||
|
...samplePluginDetails,
|
||||||
name,
|
name,
|
||||||
entry: path.join(__dirname, 'TestPlugin'),
|
entry: path.join(__dirname, 'TestPlugin'),
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
@@ -143,19 +163,29 @@ test('requirePlugin loads plugin', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('newest version of each plugin is taken', () => {
|
test('newest version of each plugin is taken', () => {
|
||||||
const plugins: PluginDefinition[] = [
|
const plugins: PluginDetails[] = [
|
||||||
{name: 'flipper-plugin-test1', version: '0.1.0'},
|
{...samplePluginDetails, name: 'flipper-plugin-test1', version: '0.1.0'},
|
||||||
{name: 'flipper-plugin-test2', version: '0.1.0-alpha.201'},
|
{
|
||||||
{name: 'flipper-plugin-test2', version: '0.1.0-alpha.21'},
|
...samplePluginDetails,
|
||||||
{name: 'flipper-plugin-test1', version: '0.10.0'},
|
name: 'flipper-plugin-test2',
|
||||||
|
version: '0.1.0-alpha.201',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...samplePluginDetails,
|
||||||
|
name: 'flipper-plugin-test2',
|
||||||
|
version: '0.1.0-alpha.21',
|
||||||
|
},
|
||||||
|
{...samplePluginDetails, name: 'flipper-plugin-test1', version: '0.10.0'},
|
||||||
];
|
];
|
||||||
const filteredPlugins = filterNewestVersionOfEachPlugin(plugins);
|
const filteredPlugins = filterNewestVersionOfEachPlugin(plugins);
|
||||||
expect(filteredPlugins).toHaveLength(2);
|
expect(filteredPlugins).toHaveLength(2);
|
||||||
expect(filteredPlugins).toContainEqual({
|
expect(filteredPlugins).toContainEqual({
|
||||||
|
...samplePluginDetails,
|
||||||
name: 'flipper-plugin-test1',
|
name: 'flipper-plugin-test1',
|
||||||
version: '0.10.0',
|
version: '0.10.0',
|
||||||
});
|
});
|
||||||
expect(filteredPlugins).toContainEqual({
|
expect(filteredPlugins).toContainEqual({
|
||||||
|
...samplePluginDetails,
|
||||||
name: 'flipper-plugin-test2',
|
name: 'flipper-plugin-test2',
|
||||||
version: '0.1.0-alpha.201',
|
version: '0.1.0-alpha.201',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
import {Store} from '../reducers/index';
|
import {Store} from '../reducers/index';
|
||||||
import {Logger} from '../fb-interfaces/Logger';
|
import {Logger} from '../fb-interfaces/Logger';
|
||||||
import {registerInstalledPlugins} from '../reducers/pluginManager';
|
import {registerInstalledPlugins} from '../reducers/pluginManager';
|
||||||
import {readInstalledPlugins} from '../utils/pluginManager';
|
import {readInstalledPlugins} from 'flipper-plugin-lib';
|
||||||
|
|
||||||
function refreshInstalledPlugins(store: Store) {
|
function refreshInstalledPlugins(store: Store) {
|
||||||
readInstalledPlugins().then((plugins) =>
|
readInstalledPlugins().then((plugins) =>
|
||||||
|
|||||||
@@ -30,19 +30,11 @@ import isProduction from '../utils/isProduction';
|
|||||||
import {notNull} from '../utils/typeUtils';
|
import {notNull} from '../utils/typeUtils';
|
||||||
import {sideEffect} from '../utils/sideEffect';
|
import {sideEffect} from '../utils/sideEffect';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
|
import {PluginDetails} from 'flipper-plugin-lib';
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import getPluginIndex from '../utils/getDefaultPluginsIndex';
|
import getPluginIndex from '../utils/getDefaultPluginsIndex';
|
||||||
|
|
||||||
export type PluginDefinition = {
|
|
||||||
id?: string;
|
|
||||||
name: string;
|
|
||||||
out?: string;
|
|
||||||
gatekeeper?: string;
|
|
||||||
entry?: string;
|
|
||||||
version: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default (store: Store, logger: Logger) => {
|
export default (store: Store, logger: Logger) => {
|
||||||
// expose Flipper and exact globally for dynamically loaded plugins
|
// expose Flipper and exact globally for dynamically loaded plugins
|
||||||
const globalObject: any = typeof window === 'undefined' ? global : window;
|
const globalObject: any = typeof window === 'undefined' ? global : window;
|
||||||
@@ -51,9 +43,9 @@ export default (store: Store, logger: Logger) => {
|
|||||||
globalObject.Flipper = Flipper;
|
globalObject.Flipper = Flipper;
|
||||||
globalObject.adbkit = adbkit;
|
globalObject.adbkit = adbkit;
|
||||||
|
|
||||||
const gatekeepedPlugins: Array<PluginDefinition> = [];
|
const gatekeepedPlugins: Array<PluginDetails> = [];
|
||||||
const disabledPlugins: Array<PluginDefinition> = [];
|
const disabledPlugins: Array<PluginDetails> = [];
|
||||||
const failedPlugins: Array<[PluginDefinition, string]> = [];
|
const failedPlugins: Array<[PluginDetails, string]> = [];
|
||||||
|
|
||||||
const defaultPluginsIndex = getPluginIndex();
|
const defaultPluginsIndex = getPluginIndex();
|
||||||
|
|
||||||
@@ -88,9 +80,9 @@ export default (store: Store, logger: Logger) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function filterNewestVersionOfEachPlugin(
|
export function filterNewestVersionOfEachPlugin(
|
||||||
plugins: PluginDefinition[],
|
plugins: PluginDetails[],
|
||||||
): PluginDefinition[] {
|
): PluginDetails[] {
|
||||||
const pluginByName: {[key: string]: PluginDefinition} = {};
|
const pluginByName: {[key: string]: PluginDetails} = {};
|
||||||
for (const plugin of plugins) {
|
for (const plugin of plugins) {
|
||||||
if (
|
if (
|
||||||
!pluginByName[plugin.name] ||
|
!pluginByName[plugin.name] ||
|
||||||
@@ -102,7 +94,7 @@ export function filterNewestVersionOfEachPlugin(
|
|||||||
return Object.values(pluginByName);
|
return Object.values(pluginByName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBundledPlugins(): Array<PluginDefinition> {
|
function getBundledPlugins(): Array<PluginDetails> {
|
||||||
// DefaultPlugins that are included in the bundle.
|
// DefaultPlugins that are included in the bundle.
|
||||||
// List of defaultPlugins is written at build time
|
// List of defaultPlugins is written at build time
|
||||||
const pluginPath =
|
const pluginPath =
|
||||||
@@ -111,27 +103,18 @@ function getBundledPlugins(): Array<PluginDefinition> {
|
|||||||
? path.join(__dirname, 'defaultPlugins')
|
? path.join(__dirname, 'defaultPlugins')
|
||||||
: './defaultPlugins/index.json');
|
: './defaultPlugins/index.json');
|
||||||
|
|
||||||
let bundledPlugins: Array<PluginDefinition> = [];
|
let bundledPlugins: Array<PluginDetails> = [];
|
||||||
try {
|
try {
|
||||||
bundledPlugins = global.electronRequire(pluginPath);
|
bundledPlugins = global.electronRequire(pluginPath);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return bundledPlugins
|
return bundledPlugins;
|
||||||
.filter((plugin) => notNull(plugin.entry))
|
|
||||||
.map(
|
|
||||||
(plugin) =>
|
|
||||||
({
|
|
||||||
...plugin,
|
|
||||||
entry: path.resolve(pluginPath, plugin.entry!),
|
|
||||||
} as PluginDefinition),
|
|
||||||
)
|
|
||||||
.concat(bundledPlugins.filter((plugin) => !plugin.entry));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDynamicPlugins() {
|
export function getDynamicPlugins() {
|
||||||
let dynamicPlugins: Array<PluginDefinition> = [];
|
let dynamicPlugins: Array<PluginDetails> = [];
|
||||||
try {
|
try {
|
||||||
dynamicPlugins = ipcRenderer.sendSync('get-dynamic-plugins');
|
dynamicPlugins = ipcRenderer.sendSync('get-dynamic-plugins');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -140,8 +123,8 @@ export function getDynamicPlugins() {
|
|||||||
return dynamicPlugins;
|
return dynamicPlugins;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const checkGK = (gatekeepedPlugins: Array<PluginDefinition>) => (
|
export const checkGK = (gatekeepedPlugins: Array<PluginDetails>) => (
|
||||||
plugin: PluginDefinition,
|
plugin: PluginDetails,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
if (!plugin.gatekeeper) {
|
if (!plugin.gatekeeper) {
|
||||||
return true;
|
return true;
|
||||||
@@ -153,8 +136,8 @@ export const checkGK = (gatekeepedPlugins: Array<PluginDefinition>) => (
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const checkDisabled = (disabledPlugins: Array<PluginDefinition>) => (
|
export const checkDisabled = (disabledPlugins: Array<PluginDetails>) => (
|
||||||
plugin: PluginDefinition,
|
plugin: PluginDetails,
|
||||||
): boolean => {
|
): boolean => {
|
||||||
let disabledList: Set<string> = new Set();
|
let disabledList: Set<string> = new Set();
|
||||||
try {
|
try {
|
||||||
@@ -171,17 +154,17 @@ export const checkDisabled = (disabledPlugins: Array<PluginDefinition>) => (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const requirePlugin = (
|
export const requirePlugin = (
|
||||||
failedPlugins: Array<[PluginDefinition, string]>,
|
failedPlugins: Array<[PluginDetails, string]>,
|
||||||
defaultPluginsIndex: any,
|
defaultPluginsIndex: any,
|
||||||
reqFn: Function = global.electronRequire,
|
reqFn: Function = global.electronRequire,
|
||||||
) => {
|
) => {
|
||||||
return (
|
return (
|
||||||
pluginDefinition: PluginDefinition,
|
pluginDetails: PluginDetails,
|
||||||
): typeof FlipperPlugin | typeof FlipperDevicePlugin | null => {
|
): typeof FlipperPlugin | typeof FlipperDevicePlugin | null => {
|
||||||
try {
|
try {
|
||||||
let plugin = pluginDefinition.entry
|
let plugin = pluginDetails.isDefault
|
||||||
? reqFn(pluginDefinition.entry)
|
? defaultPluginsIndex[pluginDetails.name]
|
||||||
: defaultPluginsIndex[pluginDefinition.name];
|
: reqFn(pluginDetails.entry);
|
||||||
if (plugin.default) {
|
if (plugin.default) {
|
||||||
plugin = plugin.default;
|
plugin = plugin.default;
|
||||||
}
|
}
|
||||||
@@ -189,21 +172,21 @@ export const requirePlugin = (
|
|||||||
throw new Error(`Plugin ${plugin.name} is not a FlipperBasePlugin`);
|
throw new Error(`Plugin ${plugin.name} is not a FlipperBasePlugin`);
|
||||||
}
|
}
|
||||||
|
|
||||||
plugin.id = plugin.id || pluginDefinition.id;
|
plugin.id = plugin.id || pluginDetails.id;
|
||||||
plugin.packageName = pluginDefinition.name;
|
plugin.packageName = pluginDetails.name;
|
||||||
|
|
||||||
// set values from package.json as static variables on class
|
// set values from package.json as static variables on class
|
||||||
Object.keys(pluginDefinition).forEach((key) => {
|
Object.keys(pluginDetails).forEach((key) => {
|
||||||
if (key !== 'name' && key !== 'id') {
|
if (key !== 'name' && key !== 'id') {
|
||||||
plugin[key] =
|
plugin[key] =
|
||||||
plugin[key] || pluginDefinition[key as keyof PluginDefinition];
|
plugin[key] || pluginDetails[key as keyof PluginDetails];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return plugin;
|
return plugin;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
failedPlugins.push([pluginDefinition, e.message]);
|
failedPlugins.push([pluginDetails, e.message]);
|
||||||
console.error(pluginDefinition, e);
|
console.error(pluginDetails, e);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ export abstract class FlipperBasePlugin<
|
|||||||
static icon: string | null = null;
|
static icon: string | null = null;
|
||||||
static gatekeeper: string | null = null;
|
static gatekeeper: string | null = null;
|
||||||
static entry: string | null = null;
|
static entry: string | null = null;
|
||||||
|
static isDefault: boolean;
|
||||||
static bugs: {
|
static bugs: {
|
||||||
email?: string;
|
email?: string;
|
||||||
url?: string;
|
url?: string;
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ const EXAMPLE_PLUGIN = {
|
|||||||
name: 'test',
|
name: 'test',
|
||||||
version: '0.1',
|
version: '0.1',
|
||||||
description: 'my test plugin',
|
description: 'my test plugin',
|
||||||
|
dir: '/plugins/test',
|
||||||
|
specVersion: 2,
|
||||||
|
source: 'src/index.ts',
|
||||||
|
isDefault: false,
|
||||||
|
main: 'lib/index.js',
|
||||||
|
title: 'test',
|
||||||
|
id: 'test',
|
||||||
|
entry: '/plugins/test/lib/index.js',
|
||||||
};
|
};
|
||||||
|
|
||||||
test('reduce registerInstalledPlugins, clear again', () => {
|
test('reduce registerInstalledPlugins, clear again', () => {
|
||||||
|
|||||||
@@ -72,7 +72,21 @@ test('do not add plugin twice', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('add gatekeeped plugin', () => {
|
test('add gatekeeped plugin', () => {
|
||||||
const gatekeepedPlugins = [{name: 'plugin', out: 'out.js', version: '1.0.0'}];
|
const gatekeepedPlugins = [
|
||||||
|
{
|
||||||
|
name: 'plugin',
|
||||||
|
out: 'out.js',
|
||||||
|
version: '1.0.0',
|
||||||
|
dir: '/plugins/test',
|
||||||
|
specVersion: 2,
|
||||||
|
source: 'src/index.ts',
|
||||||
|
isDefault: false,
|
||||||
|
main: 'lib/index.js',
|
||||||
|
title: 'test',
|
||||||
|
id: 'test',
|
||||||
|
entry: '/plugins/test/lib/index.js',
|
||||||
|
},
|
||||||
|
];
|
||||||
const res = reducer(
|
const res = reducer(
|
||||||
{
|
{
|
||||||
devicePlugins: new Map(),
|
devicePlugins: new Map(),
|
||||||
|
|||||||
@@ -8,14 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {Actions} from './';
|
import {Actions} from './';
|
||||||
|
import {PluginMap} from 'flipper-plugin-lib';
|
||||||
export type PluginDefinition = {
|
|
||||||
name: string;
|
|
||||||
version: string;
|
|
||||||
description: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PluginMap = Map<string, PluginDefinition>;
|
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
installedPlugins: PluginMap;
|
installedPlugins: PluginMap;
|
||||||
|
|||||||
@@ -8,16 +8,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {FlipperPlugin, FlipperDevicePlugin} from '../plugin';
|
import {FlipperPlugin, FlipperDevicePlugin} from '../plugin';
|
||||||
import {PluginDefinition} from '../dispatcher/plugins';
|
import {PluginDetails} from 'flipper-plugin-lib';
|
||||||
import {Actions} from '.';
|
import {Actions} from '.';
|
||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
devicePlugins: Map<string, typeof FlipperDevicePlugin>;
|
devicePlugins: Map<string, typeof FlipperDevicePlugin>;
|
||||||
clientPlugins: Map<string, typeof FlipperPlugin>;
|
clientPlugins: Map<string, typeof FlipperPlugin>;
|
||||||
gatekeepedPlugins: Array<PluginDefinition>;
|
gatekeepedPlugins: Array<PluginDetails>;
|
||||||
disabledPlugins: Array<PluginDefinition>;
|
disabledPlugins: Array<PluginDetails>;
|
||||||
failedPlugins: Array<[PluginDefinition, string]>;
|
failedPlugins: Array<[PluginDetails, string]>;
|
||||||
selectedPlugins: Array<string>;
|
selectedPlugins: Array<string>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -32,15 +32,15 @@ export type Action =
|
|||||||
| RegisterPluginAction
|
| RegisterPluginAction
|
||||||
| {
|
| {
|
||||||
type: 'GATEKEEPED_PLUGINS';
|
type: 'GATEKEEPED_PLUGINS';
|
||||||
payload: Array<PluginDefinition>;
|
payload: Array<PluginDetails>;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'DISABLED_PLUGINS';
|
type: 'DISABLED_PLUGINS';
|
||||||
payload: Array<PluginDefinition>;
|
payload: Array<PluginDetails>;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'FAILED_PLUGINS';
|
type: 'FAILED_PLUGINS';
|
||||||
payload: Array<[PluginDefinition, string]>;
|
payload: Array<[PluginDetails, string]>;
|
||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'SELECTED_PLUGINS';
|
type: 'SELECTED_PLUGINS';
|
||||||
@@ -113,21 +113,19 @@ export const registerPlugins = (payload: Array<PluginClass>): Action => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const addGatekeepedPlugins = (
|
export const addGatekeepedPlugins = (
|
||||||
payload: Array<PluginDefinition>,
|
payload: Array<PluginDetails>,
|
||||||
): Action => ({
|
): Action => ({
|
||||||
type: 'GATEKEEPED_PLUGINS',
|
type: 'GATEKEEPED_PLUGINS',
|
||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const addDisabledPlugins = (
|
export const addDisabledPlugins = (payload: Array<PluginDetails>): Action => ({
|
||||||
payload: Array<PluginDefinition>,
|
|
||||||
): Action => ({
|
|
||||||
type: 'DISABLED_PLUGINS',
|
type: 'DISABLED_PLUGINS',
|
||||||
payload,
|
payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const addFailedPlugins = (
|
export const addFailedPlugins = (
|
||||||
payload: Array<[PluginDefinition, string]>,
|
payload: Array<[PluginDetails, string]>,
|
||||||
): Action => ({
|
): Action => ({
|
||||||
type: 'FAILED_PLUGINS',
|
type: 'FAILED_PLUGINS',
|
||||||
payload,
|
payload,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
} from '../pluginUtils.tsx';
|
} from '../pluginUtils.tsx';
|
||||||
import type {State as PluginsState} from '../../reducers/plugins.tsx';
|
import type {State as PluginsState} from '../../reducers/plugins.tsx';
|
||||||
import type {State as PluginStatesState} from '../../reducers/pluginStates.tsx';
|
import type {State as PluginStatesState} from '../../reducers/pluginStates.tsx';
|
||||||
import type {PluginDefinition} from '../../dispatcher/plugins.tsx';
|
import type {PluginDetails} from 'flipper-plugin-lib';
|
||||||
import type {State as PluginMessageQueueState} from '../../reducers/pluginStates.tsx';
|
import type {State as PluginMessageQueueState} from '../../reducers/pluginStates.tsx';
|
||||||
import {FlipperBasePlugin} from 'flipper';
|
import {FlipperBasePlugin} from 'flipper';
|
||||||
import type {ReduxState} from '../../reducers/index.tsx';
|
import type {ReduxState} from '../../reducers/index.tsx';
|
||||||
@@ -60,9 +60,9 @@ function createMockFlipperPluginWithNoPersistedState(id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function mockPluginState(
|
function mockPluginState(
|
||||||
gatekeepedPlugins: Array<PluginDefinition>,
|
gatekeepedPlugins: Array<PluginDetails>,
|
||||||
disabledPlugins: Array<PluginDefinition>,
|
disabledPlugins: Array<PluginDetails>,
|
||||||
failedPlugins: Array<[PluginDefinition, string]>,
|
failedPlugins: Array<[PluginDetails, string]>,
|
||||||
): PluginsState {
|
): PluginsState {
|
||||||
return {
|
return {
|
||||||
devicePlugins: new Map([
|
devicePlugins: new Map([
|
||||||
@@ -92,7 +92,7 @@ function mockPluginState(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function mockPluginDefinition(name: string): PluginDefinition {
|
function mockPluginDefinition(name: string): PluginDetails {
|
||||||
return {
|
return {
|
||||||
name,
|
name,
|
||||||
out: 'out',
|
out: 'out',
|
||||||
|
|||||||
@@ -8,9 +8,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
|
||||||
import {homedir} from 'os';
|
import {homedir} from 'os';
|
||||||
import {PluginMap, PluginDefinition} from '../reducers/pluginManager';
|
import {PluginMap, PluginDetails} from 'flipper-plugin-lib';
|
||||||
import {default as algoliasearch, SearchIndex} from 'algoliasearch';
|
import {default as algoliasearch, SearchIndex} from 'algoliasearch';
|
||||||
import NpmApi, {Package} from 'npm-api';
|
import NpmApi, {Package} from 'npm-api';
|
||||||
import semver from 'semver';
|
import semver from 'semver';
|
||||||
@@ -26,36 +25,6 @@ export function provideSearchIndex(): SearchIndex {
|
|||||||
return client.initIndex('npm-search');
|
return client.initIndex('npm-search');
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function readInstalledPlugins(): Promise<PluginMap> {
|
|
||||||
const pluginDirExists = await fs.pathExists(PLUGIN_DIR);
|
|
||||||
|
|
||||||
if (!pluginDirExists) {
|
|
||||||
return new Map();
|
|
||||||
}
|
|
||||||
const dirs = await fs.readdir(PLUGIN_DIR);
|
|
||||||
const plugins = await Promise.all<[string, PluginDefinition]>(
|
|
||||||
dirs.map(
|
|
||||||
(name) =>
|
|
||||||
new Promise(async (resolve, reject) => {
|
|
||||||
if (!(await fs.lstat(path.join(PLUGIN_DIR, name))).isDirectory()) {
|
|
||||||
return resolve(undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
const packageJSON = await fs.readFile(
|
|
||||||
path.join(PLUGIN_DIR, name, 'package.json'),
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
resolve([name, JSON.parse(packageJSON.toString())]);
|
|
||||||
} catch (e) {
|
|
||||||
reject(e);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return new Map(plugins.filter(Boolean));
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UpdateResult =
|
export type UpdateResult =
|
||||||
| {kind: 'up-to-date'}
|
| {kind: 'up-to-date'}
|
||||||
| {kind: 'error'; error: Error}
|
| {kind: 'error'; error: Error}
|
||||||
@@ -68,9 +37,7 @@ export async function findPluginUpdates(
|
|||||||
|
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
Array.from(currentPlugins.values()).map(
|
Array.from(currentPlugins.values()).map(
|
||||||
async (
|
async (currentPlugin: PluginDetails): Promise<[string, UpdateResult]> =>
|
||||||
currentPlugin: PluginDefinition,
|
|
||||||
): Promise<[string, UpdateResult]> =>
|
|
||||||
npm
|
npm
|
||||||
.repo(currentPlugin.name)
|
.repo(currentPlugin.name)
|
||||||
.package()
|
.package()
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {FlipperDevicePlugin, FlipperPlugin, FlipperBasePlugin} from '../plugin';
|
|||||||
import {State as PluginStatesState} from '../reducers/pluginStates';
|
import {State as PluginStatesState} from '../reducers/pluginStates';
|
||||||
import {State as PluginsState} from '../reducers/plugins';
|
import {State as PluginsState} from '../reducers/plugins';
|
||||||
import {State as PluginMessageQueueState} from '../reducers/pluginMessageQueue';
|
import {State as PluginMessageQueueState} from '../reducers/pluginMessageQueue';
|
||||||
import {PluginDefinition} from '../dispatcher/plugins';
|
import {PluginDetails} from 'flipper-plugin-lib';
|
||||||
import {deconstructPluginKey, deconstructClientId} from './clientUtils';
|
import {deconstructPluginKey, deconstructClientId} from './clientUtils';
|
||||||
|
|
||||||
type Client = import('../Client').default;
|
type Client = import('../Client').default;
|
||||||
@@ -188,16 +188,16 @@ export function getPersistentPlugins(plugins: PluginsState): Array<string> {
|
|||||||
typeof FlipperDevicePlugin | typeof FlipperPlugin
|
typeof FlipperDevicePlugin | typeof FlipperPlugin
|
||||||
> = pluginsClassMap(plugins);
|
> = pluginsClassMap(plugins);
|
||||||
|
|
||||||
const arr: Array<PluginDefinition> = plugins.disabledPlugins.concat(
|
const arr: Array<PluginDetails> = plugins.disabledPlugins.concat(
|
||||||
plugins.gatekeepedPlugins,
|
plugins.gatekeepedPlugins,
|
||||||
);
|
);
|
||||||
arr.forEach((plugin: PluginDefinition) => {
|
arr.forEach((plugin: PluginDetails) => {
|
||||||
if (pluginsMap.has(plugin.name)) {
|
if (pluginsMap.has(plugin.name)) {
|
||||||
pluginsMap.delete(plugin.name);
|
pluginsMap.delete(plugin.name);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
plugins.failedPlugins.forEach((plugin: [PluginDefinition, string]) => {
|
plugins.failedPlugins.forEach((plugin: [PluginDetails, string]) => {
|
||||||
if (plugin[0] && plugin[0].name && pluginsMap.has(plugin[0].name)) {
|
if (plugin[0] && plugin[0].name && pluginsMap.has(plugin[0].name)) {
|
||||||
pluginsMap.delete(plugin[0].name);
|
pluginsMap.delete(plugin[0].name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ export default interface PluginDetails {
|
|||||||
source: string;
|
source: string;
|
||||||
main: string;
|
main: string;
|
||||||
id: string;
|
id: string;
|
||||||
|
isDefault: boolean;
|
||||||
|
entry: string;
|
||||||
gatekeeper?: string;
|
gatekeeper?: string;
|
||||||
icon?: string;
|
|
||||||
title: string;
|
title: string;
|
||||||
|
icon?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
category?: string;
|
category?: string;
|
||||||
bugs?: {
|
bugs?: {
|
||||||
|
|||||||
@@ -8,7 +8,16 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
import path from 'path';
|
||||||
import getPluginDetails from '../getPluginDetails';
|
import getPluginDetails from '../getPluginDetails';
|
||||||
|
import {pluginInstallationDir} from '../pluginPaths';
|
||||||
|
|
||||||
|
jest.mock('../pluginPaths', () => ({
|
||||||
|
pluginInstallationDir: '/Users/mock/.flipper/thirdparty',
|
||||||
|
pluginCacheDir: '/Users/mock/.flipper/plugins',
|
||||||
|
}));
|
||||||
|
|
||||||
|
const pluginPath = path.join(pluginInstallationDir, 'flipper-plugin-test');
|
||||||
|
|
||||||
test('getPluginDetailsV1', async () => {
|
test('getPluginDetailsV1', async () => {
|
||||||
const pluginV1 = {
|
const pluginV1 = {
|
||||||
@@ -21,16 +30,18 @@ test('getPluginDetailsV1', async () => {
|
|||||||
};
|
};
|
||||||
jest.mock('fs-extra', () => jest.fn());
|
jest.mock('fs-extra', () => jest.fn());
|
||||||
fs.readJson = jest.fn().mockImplementation(() => pluginV1);
|
fs.readJson = jest.fn().mockImplementation(() => pluginV1);
|
||||||
const details = await getPluginDetails('./plugins/flipper-plugin-test');
|
const details = await getPluginDetails(pluginPath);
|
||||||
expect(details).toMatchInlineSnapshot(`
|
expect(details).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"bugs": undefined,
|
"bugs": undefined,
|
||||||
"category": undefined,
|
"category": undefined,
|
||||||
"description": "Description of Test Plugin",
|
"description": "Description of Test Plugin",
|
||||||
"dir": "./plugins/flipper-plugin-test",
|
"dir": "/Users/mock/.flipper/thirdparty/flipper-plugin-test",
|
||||||
|
"entry": "/Users/mock/.flipper/plugins/flipper-plugin-test@2.0.0.js",
|
||||||
"gatekeeper": "GK_flipper_plugin_test",
|
"gatekeeper": "GK_flipper_plugin_test",
|
||||||
"icon": undefined,
|
"icon": undefined,
|
||||||
"id": "flipper-plugin-test",
|
"id": "flipper-plugin-test",
|
||||||
|
"isDefault": false,
|
||||||
"main": "dist/bundle.js",
|
"main": "dist/bundle.js",
|
||||||
"name": "flipper-plugin-test",
|
"name": "flipper-plugin-test",
|
||||||
"source": "src/index.tsx",
|
"source": "src/index.tsx",
|
||||||
@@ -54,16 +65,18 @@ test('getPluginDetailsV2', async () => {
|
|||||||
};
|
};
|
||||||
jest.mock('fs-extra', () => jest.fn());
|
jest.mock('fs-extra', () => jest.fn());
|
||||||
fs.readJson = jest.fn().mockImplementation(() => pluginV2);
|
fs.readJson = jest.fn().mockImplementation(() => pluginV2);
|
||||||
const details = await getPluginDetails('./plugins/flipper-plugin-test');
|
const details = await getPluginDetails(pluginPath);
|
||||||
expect(details).toMatchInlineSnapshot(`
|
expect(details).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"bugs": undefined,
|
"bugs": undefined,
|
||||||
"category": undefined,
|
"category": undefined,
|
||||||
"description": "Description of Test Plugin",
|
"description": "Description of Test Plugin",
|
||||||
"dir": "./plugins/flipper-plugin-test",
|
"dir": "/Users/mock/.flipper/thirdparty/flipper-plugin-test",
|
||||||
|
"entry": "/Users/mock/.flipper/thirdparty/flipper-plugin-test/dist/bundle.js",
|
||||||
"gatekeeper": "GK_flipper_plugin_test",
|
"gatekeeper": "GK_flipper_plugin_test",
|
||||||
"icon": undefined,
|
"icon": undefined,
|
||||||
"id": "flipper-plugin-test",
|
"id": "flipper-plugin-test",
|
||||||
|
"isDefault": false,
|
||||||
"main": "dist/bundle.js",
|
"main": "dist/bundle.js",
|
||||||
"name": "flipper-plugin-test",
|
"name": "flipper-plugin-test",
|
||||||
"source": "src/index.tsx",
|
"source": "src/index.tsx",
|
||||||
@@ -87,16 +100,18 @@ test('id used as title if the latter omited', async () => {
|
|||||||
};
|
};
|
||||||
jest.mock('fs-extra', () => jest.fn());
|
jest.mock('fs-extra', () => jest.fn());
|
||||||
fs.readJson = jest.fn().mockImplementation(() => pluginV2);
|
fs.readJson = jest.fn().mockImplementation(() => pluginV2);
|
||||||
const details = await getPluginDetails('./plugins/flipper-plugin-test');
|
const details = await getPluginDetails(pluginPath);
|
||||||
expect(details).toMatchInlineSnapshot(`
|
expect(details).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"bugs": undefined,
|
"bugs": undefined,
|
||||||
"category": undefined,
|
"category": undefined,
|
||||||
"description": "Description of Test Plugin",
|
"description": "Description of Test Plugin",
|
||||||
"dir": "./plugins/flipper-plugin-test",
|
"dir": "/Users/mock/.flipper/thirdparty/flipper-plugin-test",
|
||||||
|
"entry": "/Users/mock/.flipper/thirdparty/flipper-plugin-test/dist/bundle.js",
|
||||||
"gatekeeper": "GK_flipper_plugin_test",
|
"gatekeeper": "GK_flipper_plugin_test",
|
||||||
"icon": undefined,
|
"icon": undefined,
|
||||||
"id": "test",
|
"id": "test",
|
||||||
|
"isDefault": false,
|
||||||
"main": "dist/bundle.js",
|
"main": "dist/bundle.js",
|
||||||
"name": "flipper-plugin-test",
|
"name": "flipper-plugin-test",
|
||||||
"source": "src/index.tsx",
|
"source": "src/index.tsx",
|
||||||
@@ -119,16 +134,18 @@ test('name without "flipper-plugin-" prefix is used as title if the latter omite
|
|||||||
};
|
};
|
||||||
jest.mock('fs-extra', () => jest.fn());
|
jest.mock('fs-extra', () => jest.fn());
|
||||||
fs.readJson = jest.fn().mockImplementation(() => pluginV2);
|
fs.readJson = jest.fn().mockImplementation(() => pluginV2);
|
||||||
const details = await getPluginDetails('./plugins/flipper-plugin-test');
|
const details = await getPluginDetails(pluginPath);
|
||||||
expect(details).toMatchInlineSnapshot(`
|
expect(details).toMatchInlineSnapshot(`
|
||||||
Object {
|
Object {
|
||||||
"bugs": undefined,
|
"bugs": undefined,
|
||||||
"category": undefined,
|
"category": undefined,
|
||||||
"description": "Description of Test Plugin",
|
"description": "Description of Test Plugin",
|
||||||
"dir": "./plugins/flipper-plugin-test",
|
"dir": "/Users/mock/.flipper/thirdparty/flipper-plugin-test",
|
||||||
|
"entry": "/Users/mock/.flipper/thirdparty/flipper-plugin-test/dist/bundle.js",
|
||||||
"gatekeeper": "GK_flipper_plugin_test",
|
"gatekeeper": "GK_flipper_plugin_test",
|
||||||
"icon": undefined,
|
"icon": undefined,
|
||||||
"id": "flipper-plugin-test",
|
"id": "flipper-plugin-test",
|
||||||
|
"isDefault": false,
|
||||||
"main": "dist/bundle.js",
|
"main": "dist/bundle.js",
|
||||||
"name": "flipper-plugin-test",
|
"name": "flipper-plugin-test",
|
||||||
"source": "src/index.tsx",
|
"source": "src/index.tsx",
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import PluginDetails from './PluginDetails';
|
import PluginDetails from './PluginDetails';
|
||||||
|
import {pluginCacheDir} from './pluginPaths';
|
||||||
|
|
||||||
export default async function (
|
export default async function (
|
||||||
pluginDir: string,
|
pluginDir: string,
|
||||||
@@ -44,8 +45,13 @@ async function getPluginDetailsV1(
|
|||||||
name: packageJson.name,
|
name: packageJson.name,
|
||||||
version: packageJson.version,
|
version: packageJson.version,
|
||||||
main: 'dist/bundle.js',
|
main: 'dist/bundle.js',
|
||||||
|
entry: path.join(
|
||||||
|
pluginCacheDir,
|
||||||
|
`${packageJson.name}@${packageJson.version || '0.0.0'}.js`,
|
||||||
|
),
|
||||||
source: packageJson.main,
|
source: packageJson.main,
|
||||||
id: packageJson.name,
|
id: packageJson.name,
|
||||||
|
isDefault: false,
|
||||||
gatekeeper: packageJson.gatekeeper,
|
gatekeeper: packageJson.gatekeeper,
|
||||||
icon: packageJson.icon,
|
icon: packageJson.icon,
|
||||||
title: packageJson.title || packageJson.name,
|
title: packageJson.title || packageJson.name,
|
||||||
@@ -66,7 +72,9 @@ async function getPluginDetailsV2(
|
|||||||
name: packageJson.name,
|
name: packageJson.name,
|
||||||
version: packageJson.version,
|
version: packageJson.version,
|
||||||
main: packageJson.main,
|
main: packageJson.main,
|
||||||
|
entry: path.resolve(pluginDir, packageJson.main),
|
||||||
source: packageJson.flipperBundlerEntry,
|
source: packageJson.flipperBundlerEntry,
|
||||||
|
isDefault: false,
|
||||||
id: packageJson.id || packageJson.name,
|
id: packageJson.id || packageJson.name,
|
||||||
gatekeeper: packageJson.gatekeeper,
|
gatekeeper: packageJson.gatekeeper,
|
||||||
icon: packageJson.icon,
|
icon: packageJson.icon,
|
||||||
|
|||||||
@@ -10,17 +10,19 @@
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
import {promisify} from 'util';
|
import {promisify} from 'util';
|
||||||
import {homedir} from 'os';
|
|
||||||
import {PluginManager as PM} from 'live-plugin-manager';
|
import {PluginManager as PM} from 'live-plugin-manager';
|
||||||
import decompress from 'decompress';
|
import decompress from 'decompress';
|
||||||
import decompressTargz from 'decompress-targz';
|
import decompressTargz from 'decompress-targz';
|
||||||
import decompressUnzip from 'decompress-unzip';
|
import decompressUnzip from 'decompress-unzip';
|
||||||
import tmp from 'tmp';
|
import tmp from 'tmp';
|
||||||
|
import PluginDetails from './PluginDetails';
|
||||||
|
import getPluginDetails from './getPluginDetails';
|
||||||
|
import {pluginInstallationDir} from './pluginPaths';
|
||||||
|
|
||||||
|
export type PluginMap = Map<string, PluginDetails>;
|
||||||
|
|
||||||
const getTmpDir = promisify(tmp.dir) as () => Promise<string>;
|
const getTmpDir = promisify(tmp.dir) as () => Promise<string>;
|
||||||
|
|
||||||
export const PLUGIN_DIR = path.join(homedir(), '.flipper', 'thirdparty');
|
|
||||||
|
|
||||||
function providePluginManager(): PM {
|
function providePluginManager(): PM {
|
||||||
return new PM({
|
return new PM({
|
||||||
ignoredDependencies: [/^flipper$/, /^react$/, /^react-dom$/, /^@types\//],
|
ignoredDependencies: [/^flipper$/, /^react$/, /^react-dom$/, /^@types\//],
|
||||||
@@ -38,10 +40,10 @@ async function installPluginFromTempDir(pluginDir: string) {
|
|||||||
);
|
);
|
||||||
const name = packageJSON.name;
|
const name = packageJSON.name;
|
||||||
|
|
||||||
await fs.ensureDir(PLUGIN_DIR);
|
await fs.ensureDir(pluginInstallationDir);
|
||||||
// create empty watchman config (required by metro's file watcher)
|
// create empty watchman config (required by metro's file watcher)
|
||||||
await fs.writeFile(path.join(PLUGIN_DIR, '.watchmanconfig'), '{}');
|
await fs.writeFile(path.join(pluginInstallationDir, '.watchmanconfig'), '{}');
|
||||||
const destinationDir = path.join(PLUGIN_DIR, name);
|
const destinationDir = path.join(pluginInstallationDir, name);
|
||||||
// Clean up existing destination files.
|
// Clean up existing destination files.
|
||||||
await fs.remove(destinationDir);
|
await fs.remove(destinationDir);
|
||||||
await fs.ensureDir(destinationDir);
|
await fs.ensureDir(destinationDir);
|
||||||
@@ -118,3 +120,32 @@ export async function installPluginFromFile(packagePath: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function readInstalledPlugins(): Promise<PluginMap> {
|
||||||
|
const pluginDirExists = await fs.pathExists(pluginInstallationDir);
|
||||||
|
if (!pluginDirExists) {
|
||||||
|
return new Map();
|
||||||
|
}
|
||||||
|
const dirs = await fs.readdir(pluginInstallationDir);
|
||||||
|
const plugins = await Promise.all<[string, PluginDetails]>(
|
||||||
|
dirs.map(
|
||||||
|
(name) =>
|
||||||
|
new Promise(async (resolve, reject) => {
|
||||||
|
const pluginDir = path.join(pluginInstallationDir, name);
|
||||||
|
if (!(await fs.lstat(pluginDir)).isDirectory()) {
|
||||||
|
return resolve(undefined);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
resolve([name, await getPluginDetails(pluginDir)]);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return new Map(plugins.filter(Boolean));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removePlugin(name: string): Promise<void> {
|
||||||
|
await fs.remove(path.join(pluginInstallationDir, name));
|
||||||
|
}
|
||||||
17
desktop/plugin-lib/src/pluginPaths.ts
Normal file
17
desktop/plugin-lib/src/pluginPaths.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* 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 {homedir} from 'os';
|
||||||
|
|
||||||
|
export const flipperDataDir = path.join(homedir(), '.flipper');
|
||||||
|
|
||||||
|
export const pluginInstallationDir = path.join(flipperDataDir, 'thirdparty');
|
||||||
|
|
||||||
|
export const pluginCacheDir = path.join(flipperDataDir, 'plugins');
|
||||||
@@ -34,6 +34,9 @@ export function die(err: Error) {
|
|||||||
export async function generatePluginEntryPoints() {
|
export async function generatePluginEntryPoints() {
|
||||||
console.log('⚙️ Generating plugin entry points...');
|
console.log('⚙️ Generating plugin entry points...');
|
||||||
const plugins = await getSourcePlugins();
|
const plugins = await getSourcePlugins();
|
||||||
|
for (const plugin of plugins) {
|
||||||
|
plugin.isDefault = true;
|
||||||
|
}
|
||||||
if (await fs.pathExists(defaultPluginsIndexDir)) {
|
if (await fs.pathExists(defaultPluginsIndexDir)) {
|
||||||
await fs.remove(defaultPluginsIndexDir);
|
await fs.remove(defaultPluginsIndexDir);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,13 +33,11 @@ export type CompileOptions = {
|
|||||||
recompileOnChanges: boolean;
|
recompileOnChanges: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CompiledPluginDetails = PluginDetails & {entry: string};
|
|
||||||
|
|
||||||
export default async function (
|
export default async function (
|
||||||
reloadCallback: (() => void) | null,
|
reloadCallback: (() => void) | null,
|
||||||
pluginCache: string,
|
pluginCache: string,
|
||||||
options: CompileOptions = DEFAULT_COMPILE_OPTIONS,
|
options: CompileOptions = DEFAULT_COMPILE_OPTIONS,
|
||||||
): Promise<CompiledPluginDetails[]> {
|
): Promise<PluginDetails[]> {
|
||||||
if (process.env.FLIPPER_FAST_REFRESH) {
|
if (process.env.FLIPPER_FAST_REFRESH) {
|
||||||
console.log(
|
console.log(
|
||||||
'🥫 Skipping loading of installed plugins because Fast Refresh is enabled',
|
'🥫 Skipping loading of installed plugins because Fast Refresh is enabled',
|
||||||
@@ -76,7 +74,7 @@ export default async function (
|
|||||||
|
|
||||||
const compiledDynamicPlugins = (await compilations).filter(
|
const compiledDynamicPlugins = (await compilations).filter(
|
||||||
(c) => c !== null,
|
(c) => c !== null,
|
||||||
) as CompiledPluginDetails[];
|
) as PluginDetails[];
|
||||||
console.log('✅ Compiled all plugins.');
|
console.log('✅ Compiled all plugins.');
|
||||||
return compiledDynamicPlugins;
|
return compiledDynamicPlugins;
|
||||||
}
|
}
|
||||||
@@ -109,14 +107,13 @@ async function compilePlugin(
|
|||||||
pluginDetails: PluginDetails,
|
pluginDetails: PluginDetails,
|
||||||
pluginCache: string,
|
pluginCache: string,
|
||||||
{force, failSilently}: CompileOptions,
|
{force, failSilently}: CompileOptions,
|
||||||
): Promise<CompiledPluginDetails | null> {
|
): Promise<PluginDetails | null> {
|
||||||
const {dir, specVersion, version, main, source, name} = pluginDetails;
|
const {dir, specVersion, version, entry, source, name} = pluginDetails;
|
||||||
if (specVersion > 1) {
|
if (specVersion > 1) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
const entry = path.join(dir, main);
|
|
||||||
if (await fs.pathExists(entry)) {
|
if (await fs.pathExists(entry)) {
|
||||||
console.log(`🥫 Using pre-built version of ${name}: ${entry}...`);
|
console.log(`🥫 Using pre-built version of ${name}: ${entry}...`);
|
||||||
return Object.assign({}, pluginDetails, {entry});
|
return pluginDetails;
|
||||||
} else {
|
} else {
|
||||||
console.error(
|
console.error(
|
||||||
`❌ Plugin ${name} is ignored, because its entry point not found: ${entry}.`,
|
`❌ Plugin ${name} is ignored, because its entry point not found: ${entry}.`,
|
||||||
@@ -125,7 +122,6 @@ async function compilePlugin(
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const entry = path.join(pluginCache, `${name}@${version || '0.0.0'}.js`);
|
const entry = path.join(pluginCache, `${name}@${version || '0.0.0'}.js`);
|
||||||
const result = Object.assign({}, pluginDetails, {entry});
|
|
||||||
const rootDirCtime = await mostRecentlyChanged(dir);
|
const rootDirCtime = await mostRecentlyChanged(dir);
|
||||||
if (
|
if (
|
||||||
!force &&
|
!force &&
|
||||||
@@ -134,7 +130,7 @@ async function compilePlugin(
|
|||||||
) {
|
) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(`🥫 Using cached version of ${name}...`);
|
console.log(`🥫 Using cached version of ${name}...`);
|
||||||
return result;
|
return pluginDetails;
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-line no-console
|
// eslint-disable-line no-console
|
||||||
console.log(`⚙️ Compiling ${name}...`);
|
console.log(`⚙️ Compiling ${name}...`);
|
||||||
@@ -151,7 +147,7 @@ async function compilePlugin(
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return pluginDetails;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user