diff --git a/desktop/app/src/chrome/plugin-manager/PluginDebugger.tsx b/desktop/app/src/chrome/plugin-manager/PluginDebugger.tsx index 0a7e8aec1..8c8e5b6d6 100644 --- a/desktop/app/src/chrome/plugin-manager/PluginDebugger.tsx +++ b/desktop/app/src/chrome/plugin-manager/PluginDebugger.tsx @@ -139,7 +139,7 @@ class PluginDebugger extends Component { getRows(): Array { const rows: Array = []; - const externalPluginPath = (p: any) => (p.isDefault ? 'bundled' : p.entry); + const externalPluginPath = (p: any) => (p.isBundled ? 'bundled' : p.entry); this.props.gatekeepedPlugins.forEach((plugin) => rows.push( diff --git a/desktop/app/src/chrome/plugin-manager/__tests__/PluginInstaller.node.tsx b/desktop/app/src/chrome/plugin-manager/__tests__/PluginInstaller.node.tsx index c296c629e..80c414db3 100644 --- a/desktop/app/src/chrome/plugin-manager/__tests__/PluginInstaller.node.tsx +++ b/desktop/app/src/chrome/plugin-manager/__tests__/PluginInstaller.node.tsx @@ -39,7 +39,8 @@ const samplePluginDetails1: UpdatablePluginDetails = { id: 'Hello', title: 'Hello', description: 'World?', - isDefault: false, + isBundled: false, + isActivatable: true, updateStatus: { kind: 'not-installed', version: '0.1.0', @@ -57,7 +58,8 @@ const samplePluginDetails2: UpdatablePluginDetails = { id: 'World', title: 'World', description: 'Hello?', - isDefault: false, + isBundled: false, + isActivatable: true, updateStatus: { kind: 'not-installed', version: '0.2.0', diff --git a/desktop/app/src/dispatcher/__tests__/plugins.node.tsx b/desktop/app/src/dispatcher/__tests__/plugins.node.tsx index bc2b2bf3a..cfdd57dc3 100644 --- a/desktop/app/src/dispatcher/__tests__/plugins.node.tsx +++ b/desktop/app/src/dispatcher/__tests__/plugins.node.tsx @@ -16,7 +16,7 @@ import dispatcher, { createRequirePluginFunction, filterNewestVersionOfEachPlugin, } from '../plugins'; -import {PluginDetails} from 'flipper-plugin-lib'; +import {BundledPluginDetails, InstalledPluginDetails} from 'flipper-plugin-lib'; import path from 'path'; import {remote} from 'electron'; import {FlipperPlugin} from '../../plugin'; @@ -37,17 +37,23 @@ const mockStore = configureStore([])( ); const logger = getInstance(); -const samplePluginDetails: PluginDetails = { +const sampleInstalledPluginDetails: InstalledPluginDetails = { 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, + dir: '/Users/mock/.flipper/thirdparty/flipper-plugin-sample', + entry: 'this/path/does not/exist', + isBundled: false, + isActivatable: true, +}; + +const sampleBundledPluginDetails: BundledPluginDetails = { + ...sampleInstalledPluginDetails, + isBundled: true, }; beforeEach(() => { @@ -80,18 +86,16 @@ test('checkDisabled', () => { expect( disabled({ - ...samplePluginDetails, + ...sampleBundledPluginDetails, name: 'other Name', - entry: './test/index.js', version: '1.0.0', }), ).toBeTruthy(); expect( disabled({ - ...samplePluginDetails, + ...sampleBundledPluginDetails, name: disabledPlugin, - entry: './test/index.js', version: '1.0.0', }), ).toBeFalsy(); @@ -100,9 +104,8 @@ test('checkDisabled', () => { test('checkGK for plugin without GK', () => { expect( checkGK([])({ - ...samplePluginDetails, + ...sampleBundledPluginDetails, name: 'pluginID', - entry: './test/index.js', version: '1.0.0', }), ).toBeTruthy(); @@ -111,23 +114,21 @@ test('checkGK for plugin without GK', () => { test('checkGK for passing plugin', () => { expect( checkGK([])({ - ...samplePluginDetails, + ...sampleBundledPluginDetails, name: 'pluginID', gatekeeper: TEST_PASSING_GK, - entry: './test/index.js', version: '1.0.0', }), ).toBeTruthy(); }); test('checkGK for failing plugin', () => { - const gatekeepedPlugins: PluginDetails[] = []; + const gatekeepedPlugins: InstalledPluginDetails[] = []; const name = 'pluginID'; const plugins = checkGK(gatekeepedPlugins)({ - ...samplePluginDetails, + ...sampleBundledPluginDetails, name, gatekeeper: TEST_FAILING_GK, - entry: './test/index.js', version: '1.0.0', }); @@ -138,8 +139,9 @@ test('checkGK for failing plugin', () => { test('requirePlugin returns null for invalid requires', () => { const requireFn = createRequirePluginFunction([], require); const plugin = requireFn({ - ...samplePluginDetails, + ...sampleInstalledPluginDetails, name: 'pluginID', + dir: '/Users/mock/.flipper/thirdparty/flipper-plugin-sample', entry: 'this/path/does not/exist', version: '1.0.0', }); @@ -151,8 +153,9 @@ test('requirePlugin loads plugin', () => { const name = 'pluginID'; const requireFn = createRequirePluginFunction([], require); const plugin = requireFn({ - ...samplePluginDetails, + ...sampleInstalledPluginDetails, name, + dir: '/Users/mock/.flipper/thirdparty/flipper-plugin-sample', entry: path.join(__dirname, 'TestPlugin'), version: '1.0.0', }); @@ -162,21 +165,33 @@ test('requirePlugin loads plugin', () => { }); test('newest version of each plugin is used', () => { - const bundledPlugins: PluginDetails[] = [ - {...samplePluginDetails, name: 'flipper-plugin-test1', version: '0.1.0'}, + const bundledPlugins: BundledPluginDetails[] = [ { - ...samplePluginDetails, + ...sampleBundledPluginDetails, + name: 'flipper-plugin-test1', + version: '0.1.0', + }, + { + ...sampleBundledPluginDetails, name: 'flipper-plugin-test2', version: '0.1.0-alpha.201', }, ]; - const installedPlugins: PluginDetails[] = [ + const installedPlugins: InstalledPluginDetails[] = [ { - ...samplePluginDetails, + ...sampleInstalledPluginDetails, name: 'flipper-plugin-test2', version: '0.1.0-alpha.21', + dir: '/Users/mock/.flipper/thirdparty/flipper-plugin-test2', + entry: './test/index.js', + }, + { + ...sampleInstalledPluginDetails, + name: 'flipper-plugin-test1', + version: '0.10.0', + dir: '/Users/mock/.flipper/thirdparty/flipper-plugin-test1', + entry: './test/index.js', }, - {...samplePluginDetails, name: 'flipper-plugin-test1', version: '0.10.0'}, ]; const filteredPlugins = filterNewestVersionOfEachPlugin( bundledPlugins, @@ -184,12 +199,14 @@ test('newest version of each plugin is used', () => { ); expect(filteredPlugins).toHaveLength(2); expect(filteredPlugins).toContainEqual({ - ...samplePluginDetails, + ...sampleInstalledPluginDetails, name: 'flipper-plugin-test1', version: '0.10.0', + dir: '/Users/mock/.flipper/thirdparty/flipper-plugin-test1', + entry: './test/index.js', }); expect(filteredPlugins).toContainEqual({ - ...samplePluginDetails, + ...sampleBundledPluginDetails, name: 'flipper-plugin-test2', version: '0.1.0-alpha.201', }); @@ -198,21 +215,33 @@ test('newest version of each plugin is used', () => { test('bundled versions are used when env var FLIPPER_DISABLE_PLUGIN_AUTO_UPDATE is set even if newer versions are installed', () => { process.env.FLIPPER_DISABLE_PLUGIN_AUTO_UPDATE = 'true'; try { - const bundledPlugins: PluginDetails[] = [ - {...samplePluginDetails, name: 'flipper-plugin-test1', version: '0.1.0'}, + const bundledPlugins: BundledPluginDetails[] = [ { - ...samplePluginDetails, + ...sampleBundledPluginDetails, + name: 'flipper-plugin-test1', + version: '0.1.0', + }, + { + ...sampleBundledPluginDetails, name: 'flipper-plugin-test2', version: '0.1.0-alpha.21', }, ]; - const installedPlugins: PluginDetails[] = [ + const installedPlugins: InstalledPluginDetails[] = [ { - ...samplePluginDetails, + ...sampleInstalledPluginDetails, name: 'flipper-plugin-test2', version: '0.1.0-alpha.201', + dir: '/Users/mock/.flipper/thirdparty/flipper-plugin-test2', + entry: './test/index.js', + }, + { + ...sampleInstalledPluginDetails, + name: 'flipper-plugin-test1', + version: '0.10.0', + dir: '/Users/mock/.flipper/thirdparty/flipper-plugin-test1', + entry: './test/index.js', }, - {...samplePluginDetails, name: 'flipper-plugin-test1', version: '0.10.0'}, ]; const filteredPlugins = filterNewestVersionOfEachPlugin( bundledPlugins, @@ -220,12 +249,12 @@ test('bundled versions are used when env var FLIPPER_DISABLE_PLUGIN_AUTO_UPDATE ); expect(filteredPlugins).toHaveLength(2); expect(filteredPlugins).toContainEqual({ - ...samplePluginDetails, + ...sampleBundledPluginDetails, name: 'flipper-plugin-test1', version: '0.1.0', }); expect(filteredPlugins).toContainEqual({ - ...samplePluginDetails, + ...sampleBundledPluginDetails, name: 'flipper-plugin-test2', version: '0.1.0-alpha.21', }); @@ -238,8 +267,12 @@ test('requirePlugin loads valid Sandy plugin', () => { const name = 'pluginID'; const requireFn = createRequirePluginFunction([], require); const plugin = requireFn({ - ...samplePluginDetails, + ...sampleInstalledPluginDetails, name, + dir: path.join( + __dirname, + '../../../../flipper-plugin/src/__tests__/TestPlugin', + ), entry: path.join( __dirname, '../../../../flipper-plugin/src/__tests__/TestPlugin', @@ -253,7 +286,7 @@ test('requirePlugin loads valid Sandy plugin', () => { expect(plugin.details).toMatchObject({ flipperSDKVersion: '0.0.0', id: 'Sample', - isDefault: false, + isBundled: false, main: 'dist/bundle.js', name: 'pluginID', source: 'src/index.js', @@ -272,9 +305,10 @@ test('requirePlugin errors on invalid Sandy plugin', () => { const failedPlugins: any[] = []; const requireFn = createRequirePluginFunction(failedPlugins, require); requireFn({ - ...samplePluginDetails, + ...sampleInstalledPluginDetails, name, // Intentionally the wrong file: + dir: __dirname, entry: path.join(__dirname, 'TestPlugin'), version: '1.0.0', flipperSDKVersion: '0.0.0', @@ -288,8 +322,12 @@ test('requirePlugin loads valid Sandy Device plugin', () => { const name = 'pluginID'; const requireFn = createRequirePluginFunction([], require); const plugin = requireFn({ - ...samplePluginDetails, + ...sampleInstalledPluginDetails, name, + dir: path.join( + __dirname, + '../../../../flipper-plugin/src/__tests__/DeviceTestPlugin', + ), entry: path.join( __dirname, '../../../../flipper-plugin/src/__tests__/DeviceTestPlugin', @@ -303,7 +341,7 @@ test('requirePlugin loads valid Sandy Device plugin', () => { expect(plugin.details).toMatchObject({ flipperSDKVersion: '0.0.0', id: 'Sample', - isDefault: false, + isBundled: false, main: 'dist/bundle.js', name: 'pluginID', source: 'src/index.js', diff --git a/desktop/app/src/dispatcher/pluginDownloads.tsx b/desktop/app/src/dispatcher/pluginDownloads.tsx index 2526871b5..16f52533a 100644 --- a/desktop/app/src/dispatcher/pluginDownloads.tsx +++ b/desktop/app/src/dispatcher/pluginDownloads.tsx @@ -9,6 +9,8 @@ import { DownloadablePluginDetails, + getInstalledPluginDetails, + getPluginVersionInstallationDir, installPluginFromFile, } from 'flipper-plugin-lib'; import {Store} from '../reducers/index'; @@ -62,23 +64,24 @@ async function handlePluginDownload( store: Store, ) { const dispatch = store.dispatch; - const {name, title, version, downloadUrl, dir} = plugin; + const {name, title, version, downloadUrl} = plugin; + const installationDir = getPluginVersionInstallationDir(name, version); console.log( - `Downloading plugin "${title}" v${version} from "${downloadUrl}" to "${dir}".`, + `Downloading plugin "${title}" v${version} from "${downloadUrl}" to "${installationDir}".`, ); - const targetDir = await getTempDirName(); - const targetFile = path.join(targetDir, `${name}-${version}.tgz`); + const tmpDir = await getTempDirName(); + const tmpFile = path.join(tmpDir, `${name}-${version}.tgz`); try { const cancellationSource = axios.CancelToken.source(); dispatch( pluginDownloadStarted({plugin, cancel: cancellationSource.cancel}), ); - if (await fs.pathExists(dir)) { + if (await fs.pathExists(installationDir)) { console.log( - `Using existing files instead of downloading plugin "${title}" v${version} from "${downloadUrl}" to "${dir}"`, + `Using existing files instead of downloading plugin "${title}" v${version} from "${downloadUrl}" to "${installationDir}"`, ); } else { - await fs.ensureDir(targetDir); + await fs.ensureDir(tmpDir); let percentCompleted = 0; const response = await axios.get(plugin.downloadUrl, { adapter: axiosHttpAdapter, @@ -103,15 +106,16 @@ async function handlePluginDownload( } const responseStream = response.data as fs.ReadStream; const writeStream = responseStream.pipe( - fs.createWriteStream(targetFile, {autoClose: true}), + fs.createWriteStream(tmpFile, {autoClose: true}), ); await new Promise((resolve, reject) => writeStream.once('finish', resolve).once('error', reject), ); - await installPluginFromFile(targetFile); + await installPluginFromFile(tmpFile); } + const installedPlugin = await getInstalledPluginDetails(installationDir); if (!store.getState().plugins.clientPlugins.has(plugin.id)) { - const pluginDefinition = requirePlugin(plugin); + const pluginDefinition = requirePlugin(installedPlugin); dispatch( registerPluginUpdate({ plugin: pluginDefinition, @@ -120,11 +124,11 @@ async function handlePluginDownload( ); } console.log( - `Successfully downloaded and installed plugin "${title}" v${version} from "${downloadUrl}" to "${dir}".`, + `Successfully downloaded and installed plugin "${title}" v${version} from "${downloadUrl}" to "${installationDir}".`, ); } catch (error) { console.error( - `Failed to download plugin "${title}" v${version} from "${downloadUrl}" to "${dir}".`, + `Failed to download plugin "${title}" v${version} from "${downloadUrl}" to "${installationDir}".`, error, ); if (startedByUser) { @@ -144,6 +148,6 @@ async function handlePluginDownload( } } finally { dispatch(pluginDownloadFinished({plugin})); - await fs.remove(targetDir); + await fs.remove(tmpDir); } } diff --git a/desktop/app/src/dispatcher/plugins.tsx b/desktop/app/src/dispatcher/plugins.tsx index b648dea26..abfc08d58 100644 --- a/desktop/app/src/dispatcher/plugins.tsx +++ b/desktop/app/src/dispatcher/plugins.tsx @@ -29,7 +29,11 @@ import isProduction from '../utils/isProduction'; import {notNull} from '../utils/typeUtils'; import {sideEffect} from '../utils/sideEffect'; import semver from 'semver'; -import {PluginDetails} from 'flipper-plugin-lib'; +import { + ActivatablePluginDetails, + BundledPluginDetails, + InstalledPluginDetails, +} from 'flipper-plugin-lib'; import {tryCatchReportPluginFailures, reportUsage} from '../utils/metrics'; import * as FlipperPluginSDK from 'flipper-plugin'; import {_SandyPluginDefinition} from 'flipper-plugin'; @@ -51,9 +55,9 @@ export default async (store: Store, logger: Logger) => { globalObject.FlipperPlugin = FlipperPluginSDK; globalObject.Immer = Immer; - const gatekeepedPlugins: Array = []; - const disabledPlugins: Array = []; - const failedPlugins: Array<[PluginDetails, string]> = []; + const gatekeepedPlugins: Array = []; + const disabledPlugins: Array = []; + const failedPlugins: Array<[ActivatablePluginDetails, string]> = []; defaultPluginsIndex = getDefaultPluginsIndex(); @@ -89,7 +93,7 @@ export default async (store: Store, logger: Logger) => { ); }; -function reportVersion(pluginDetails: PluginDetails) { +function reportVersion(pluginDetails: ActivatablePluginDetails) { reportUsage( 'plugin:version', { @@ -101,10 +105,10 @@ function reportVersion(pluginDetails: PluginDetails) { } export function filterNewestVersionOfEachPlugin( - bundledPlugins: PluginDetails[], - dynamicPlugins: PluginDetails[], -): PluginDetails[] { - const pluginByName: {[key: string]: PluginDetails} = {}; + bundledPlugins: BundledPluginDetails[], + dynamicPlugins: InstalledPluginDetails[], +): ActivatablePluginDetails[] { + const pluginByName: {[key: string]: ActivatablePluginDetails} = {}; for (const plugin of bundledPlugins) { pluginByName[plugin.name] = plugin; } @@ -120,7 +124,7 @@ export function filterNewestVersionOfEachPlugin( return Object.values(pluginByName); } -function getBundledPlugins(): Array { +function getBundledPlugins(): Array { // DefaultPlugins that are included in the bundle. // List of defaultPlugins is written at build time const pluginPath = @@ -129,7 +133,7 @@ function getBundledPlugins(): Array { ? path.join(__dirname, 'defaultPlugins') : './defaultPlugins/index.json'); - let bundledPlugins: Array = []; + let bundledPlugins: Array = []; try { bundledPlugins = global.electronRequire(pluginPath); } catch (e) { @@ -148,8 +152,8 @@ export async function getDynamicPlugins() { } } -export const checkGK = (gatekeepedPlugins: Array) => ( - plugin: PluginDetails, +export const checkGK = (gatekeepedPlugins: Array) => ( + plugin: ActivatablePluginDetails, ): boolean => { if (!plugin.gatekeeper) { return true; @@ -161,7 +165,9 @@ export const checkGK = (gatekeepedPlugins: Array) => ( return result; }; -export const checkDisabled = (disabledPlugins: Array) => { +export const checkDisabled = ( + disabledPlugins: Array, +) => { const enabledList = process.env.FLIPPER_ENABLED_PLUGINS ? new Set(process.env.FLIPPER_ENABLED_PLUGINS.split(',')) : null; @@ -171,7 +177,7 @@ export const checkDisabled = (disabledPlugins: Array) => { } catch (e) { console.error(e); } - return (plugin: PluginDetails): boolean => { + return (plugin: ActivatablePluginDetails): boolean => { if (disabledList.has(plugin.name)) { disabledPlugins.push(plugin); return false; @@ -192,10 +198,10 @@ export const checkDisabled = (disabledPlugins: Array) => { }; export const createRequirePluginFunction = ( - failedPlugins: Array<[PluginDetails, string]>, + failedPlugins: Array<[ActivatablePluginDetails, string]>, reqFn: Function = global.electronRequire, ) => { - return (pluginDetails: PluginDetails): PluginDefinition | null => { + return (pluginDetails: ActivatablePluginDetails): PluginDefinition | null => { try { return requirePlugin(pluginDetails, reqFn); } catch (e) { @@ -207,7 +213,7 @@ export const createRequirePluginFunction = ( }; export const requirePlugin = ( - pluginDetails: PluginDetails, + pluginDetails: ActivatablePluginDetails, reqFn: Function = global.electronRequire, ): PluginDefinition => { return tryCatchReportPluginFailures( @@ -218,10 +224,10 @@ export const requirePlugin = ( }; const requirePluginInternal = ( - pluginDetails: PluginDetails, + pluginDetails: ActivatablePluginDetails, reqFn: Function = global.electronRequire, ): PluginDefinition => { - let plugin = pluginDetails.isDefault + let plugin = pluginDetails.isBundled ? defaultPluginsIndex[pluginDetails.name] : reqFn(pluginDetails.entry); if (pluginDetails.flipperSDKVersion) { @@ -248,7 +254,8 @@ const requirePluginInternal = ( // 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]; + plugin[key] = + plugin[key] || pluginDetails[key as keyof ActivatablePluginDetails]; } }); } diff --git a/desktop/app/src/plugin.tsx b/desktop/app/src/plugin.tsx index 4495e459a..7088d3e29 100644 --- a/desktop/app/src/plugin.tsx +++ b/desktop/app/src/plugin.tsx @@ -18,7 +18,7 @@ import {Idler} from './utils/Idler'; import {StaticView} from './reducers/connections'; import {State as ReduxState} from './reducers'; import {DEFAULT_MAX_QUEUE_SIZE} from './reducers/pluginMessageQueue'; -import {PluginDetails} from 'flipper-plugin-lib'; +import {ActivatablePluginDetails} from 'flipper-plugin-lib'; import {Settings} from './reducers/settings'; import {_SandyPluginDefinition} from 'flipper-plugin'; @@ -122,9 +122,8 @@ export abstract class FlipperBasePlugin< static version: string = ''; static icon: string | null = null; static gatekeeper: string | null = null; - static entry: string | null = null; - static isDefault: boolean; - static details: PluginDetails; + static isBundled: boolean; + static details: ActivatablePluginDetails; static keyboardActions: KeyboardActions | null; static screenshot: string | null; static defaultPersistedState: any; diff --git a/desktop/app/src/plugins/TableNativePlugin.tsx b/desktop/app/src/plugins/TableNativePlugin.tsx index 1ac9d0d73..e531dfb2f 100644 --- a/desktop/app/src/plugins/TableNativePlugin.tsx +++ b/desktop/app/src/plugins/TableNativePlugin.tsx @@ -34,7 +34,7 @@ import createPaste from '../fb-stubs/createPaste'; import {ReactNode} from 'react'; import React from 'react'; import {KeyboardActions} from '../MenuBar'; -import {PluginDetails} from 'flipper-plugin-lib'; +import {BundledPluginDetails} from 'flipper-plugin-lib'; type ID = string; @@ -256,7 +256,7 @@ export default function createTableNativePlugin(id: string, title: string) { static id = id || ''; static title = title || ''; - static details: PluginDetails = { + static details: BundledPluginDetails = { id, title, icon: 'apps', @@ -264,11 +264,10 @@ export default function createTableNativePlugin(id: string, title: string) { // all hmm... specVersion: 1, version: 'auto', - dir: '', source: '', main: '', - entry: '', - isDefault: true, + isBundled: true, + isActivatable: true, }; static defaultPersistedState: PersistedState = { diff --git a/desktop/app/src/reducers/__tests__/pluginManager.node.tsx b/desktop/app/src/reducers/__tests__/pluginManager.node.tsx index 5e1e9017f..bd2778650 100644 --- a/desktop/app/src/reducers/__tests__/pluginManager.node.tsx +++ b/desktop/app/src/reducers/__tests__/pluginManager.node.tsx @@ -8,7 +8,7 @@ */ import {default as reducer, registerInstalledPlugins} from '../pluginManager'; -import {PluginDetails} from 'flipper-plugin-lib'; +import {InstalledPluginDetails} from 'flipper-plugin-lib'; test('reduce empty registerInstalledPlugins', () => { const result = reducer(undefined, registerInstalledPlugins([])); @@ -22,12 +22,13 @@ const EXAMPLE_PLUGIN = { dir: '/plugins/test', specVersion: 2, source: 'src/index.ts', - isDefault: false, + isBundled: false, + isActivatable: true, main: 'lib/index.js', title: 'test', id: 'test', entry: '/plugins/test/lib/index.js', -} as PluginDetails; +} as InstalledPluginDetails; test('reduce registerInstalledPlugins, clear again', () => { const result = reducer(undefined, registerInstalledPlugins([EXAMPLE_PLUGIN])); diff --git a/desktop/app/src/reducers/__tests__/plugins.node.tsx b/desktop/app/src/reducers/__tests__/plugins.node.tsx index f16d15914..0d48b4652 100644 --- a/desktop/app/src/reducers/__tests__/plugins.node.tsx +++ b/desktop/app/src/reducers/__tests__/plugins.node.tsx @@ -83,7 +83,7 @@ test('add gatekeeped plugin', () => { dir: '/plugins/test', specVersion: 2, source: 'src/index.ts', - isDefault: false, + isBundled: false, main: 'lib/index.js', title: 'test', id: 'test', diff --git a/desktop/app/src/reducers/pluginDownloads.tsx b/desktop/app/src/reducers/pluginDownloads.tsx index 150f1b84d..83e32adee 100644 --- a/desktop/app/src/reducers/pluginDownloads.tsx +++ b/desktop/app/src/reducers/pluginDownloads.tsx @@ -7,7 +7,10 @@ * @format */ -import {DownloadablePluginDetails} from 'flipper-plugin-lib'; +import { + DownloadablePluginDetails, + getPluginVersionInstallationDir, +} from 'flipper-plugin-lib'; import {Actions} from '.'; import produce from 'immer'; import {Canceler} from 'axios'; @@ -66,18 +69,22 @@ export default function reducer( switch (action.type) { case 'PLUGIN_DOWNLOAD_START': { const {plugin, startedByUser} = action.payload; - const downloadState = state[plugin.dir]; + const installationDir = getPluginVersionInstallationDir( + plugin.name, + plugin.version, + ); + const downloadState = state[installationDir]; if (downloadState) { // If download is already in progress - re-use the existing state. return produce(state, (draft) => { - draft[plugin.dir] = { + draft[installationDir] = { ...downloadState, startedByUser: startedByUser || downloadState.startedByUser, }; }); } return produce(state, (draft) => { - draft[plugin.dir] = { + draft[installationDir] = { plugin, startedByUser: startedByUser, status: PluginDownloadStatus.QUEUED, @@ -86,15 +93,19 @@ export default function reducer( } case 'PLUGIN_DOWNLOAD_STARTED': { const {plugin, cancel} = action.payload; - const downloadState = state[plugin.dir]; + const installationDir = getPluginVersionInstallationDir( + plugin.name, + plugin.version, + ); + const downloadState = state[installationDir]; if (downloadState?.status !== PluginDownloadStatus.QUEUED) { console.warn( - `Invalid state transition PLUGIN_DOWNLOAD_STARTED in status ${downloadState?.status} for download to directory ${plugin.dir}.`, + `Invalid state transition PLUGIN_DOWNLOAD_STARTED in status ${downloadState?.status} for download to directory ${installationDir}.`, ); return state; } return produce(state, (draft) => { - draft[plugin.dir] = { + draft[installationDir] = { status: PluginDownloadStatus.STARTED, plugin, startedByUser: downloadState.startedByUser, @@ -104,8 +115,12 @@ export default function reducer( } case 'PLUGIN_DOWNLOAD_FINISHED': { const {plugin} = action.payload; + const installationDir = getPluginVersionInstallationDir( + plugin.name, + plugin.version, + ); return produce(state, (draft) => { - delete draft[plugin.dir]; + delete draft[installationDir]; }); } default: diff --git a/desktop/app/src/reducers/pluginManager.tsx b/desktop/app/src/reducers/pluginManager.tsx index c08106386..7d3664b88 100644 --- a/desktop/app/src/reducers/pluginManager.tsx +++ b/desktop/app/src/reducers/pluginManager.tsx @@ -8,19 +8,19 @@ */ import {Actions} from './'; -import {PluginDetails} from 'flipper-plugin-lib'; +import {InstalledPluginDetails} from 'flipper-plugin-lib'; import {PluginDefinition} from '../plugin'; import {produce} from 'immer'; export type State = { - installedPlugins: PluginDetails[]; + installedPlugins: InstalledPluginDetails[]; uninstalledPlugins: Set; }; export type Action = | { type: 'REGISTER_INSTALLED_PLUGINS'; - payload: PluginDetails[]; + payload: InstalledPluginDetails[]; } | { // Implemented by rootReducer in `store.tsx` @@ -48,7 +48,9 @@ export default function reducer( } } -export const registerInstalledPlugins = (payload: PluginDetails[]): Action => ({ +export const registerInstalledPlugins = ( + payload: InstalledPluginDetails[], +): Action => ({ type: 'REGISTER_INSTALLED_PLUGINS', payload, }); diff --git a/desktop/app/src/sandy-chrome/appinspect/PluginList.tsx b/desktop/app/src/sandy-chrome/appinspect/PluginList.tsx index f01a0eb68..32c1a5f48 100644 --- a/desktop/app/src/sandy-chrome/appinspect/PluginList.tsx +++ b/desktop/app/src/sandy-chrome/appinspect/PluginList.tsx @@ -229,7 +229,7 @@ export const PluginList = memo(function PluginList({ tooltip={getPluginTooltip(plugin.details)} actions={ <> - {!plugin.details.isDefault && ( + {!plugin.details.isBundled && ( { +export default async function loadDynamicPlugins(): Promise< + InstalledPluginDetails[] +> { if (process.env.FLIPPER_FAST_REFRESH) { console.log( '❌ Skipping loading of dynamic plugins because Fast Refresh is enabled. Fast Refresh only works with bundled plugins.', diff --git a/desktop/app/src/utils/testUtils.tsx b/desktop/app/src/utils/testUtils.tsx index 7fdc3de20..24058fbda 100644 --- a/desktop/app/src/utils/testUtils.tsx +++ b/desktop/app/src/utils/testUtils.tsx @@ -39,15 +39,12 @@ export function createMockDownloadablePluginDetails( }, category: 'tools', description: 'Description of Test Plugin', - dir: `/Users/mock/.flipper/thirdparty/${name}`, - entry: `/Users/mock/.flipper/thirdparty/${name}/dist/bundle.js`, flipperSDKVersion: flipperEngineVersion, engines: { flipper: flipperEngineVersion, }, gatekeeper: gatekeeper ?? `GK_${lowercasedID}`, icon: 'internet', - isDefault: false, main: 'dist/bundle.js', source: 'src/index.tsx', specVersion: 2, @@ -55,6 +52,8 @@ export function createMockDownloadablePluginDetails( version: version, downloadUrl: `http://localhost/${lowercasedID}/${version}`, lastUpdated: lastUpdated, + isBundled: false, + isActivatable: false, }; return details; } diff --git a/desktop/flipper-plugin/src/plugin/SandyPluginDefinition.tsx b/desktop/flipper-plugin/src/plugin/SandyPluginDefinition.tsx index 798226c1f..bfb4cb41e 100644 --- a/desktop/flipper-plugin/src/plugin/SandyPluginDefinition.tsx +++ b/desktop/flipper-plugin/src/plugin/SandyPluginDefinition.tsx @@ -7,7 +7,7 @@ * @format */ -import {PluginDetails} from 'flipper-plugin-lib'; +import {ActivatablePluginDetails} from 'flipper-plugin-lib'; import {PluginFactory, FlipperPluginComponent} from './Plugin'; import {DevicePluginPredicate, DevicePluginFactory} from './DevicePlugin'; @@ -42,7 +42,7 @@ export type FlipperPluginModule> = { export class SandyPluginDefinition { id: string; module: FlipperPluginModule | FlipperDevicePluginModule; - details: PluginDetails; + details: ActivatablePluginDetails; isDevicePlugin: boolean; // TODO: Implement T68683476 @@ -58,10 +58,10 @@ export class SandyPluginDefinition { | undefined = undefined; constructor( - details: PluginDetails, + details: ActivatablePluginDetails, module: FlipperPluginModule | FlipperDevicePluginModule, ); - constructor(details: PluginDetails, module: any) { + constructor(details: ActivatablePluginDetails, module: any) { this.id = details.id; this.details = details; if (module.supportsDevice) { @@ -123,8 +123,8 @@ export class SandyPluginDefinition { return this.details.version; } - get isDefault() { - return this.details.isDefault; + get isBundled() { + return this.details.isBundled; } get keyboardActions() { diff --git a/desktop/flipper-plugin/src/test-utils/test-utils.tsx b/desktop/flipper-plugin/src/test-utils/test-utils.tsx index e783d133e..bf98db906 100644 --- a/desktop/flipper-plugin/src/test-utils/test-utils.tsx +++ b/desktop/flipper-plugin/src/test-utils/test-utils.tsx @@ -14,7 +14,7 @@ import { act as testingLibAct, } from '@testing-library/react'; import {queries} from '@testing-library/dom'; -import {PluginDetails} from 'flipper-plugin-lib'; +import {InstalledPluginDetails} from 'flipper-plugin-lib'; import { RealFlipperClient, @@ -383,15 +383,16 @@ function createBasePluginResult( } export function createMockPluginDetails( - details?: Partial, -): PluginDetails { + details?: Partial, +): InstalledPluginDetails { return { id: 'TestPlugin', dir: '', name: 'TestPlugin', specVersion: 0, entry: '', - isDefault: false, + isBundled: false, + isActivatable: true, main: '', source: '', title: 'Testing Plugin', diff --git a/desktop/pkg/src/commands/bundle.ts b/desktop/pkg/src/commands/bundle.ts index 6b01692ec..db9b9c493 100644 --- a/desktop/pkg/src/commands/bundle.ts +++ b/desktop/pkg/src/commands/bundle.ts @@ -12,7 +12,7 @@ import {args} from '@oclif/parser'; import fs from 'fs-extra'; import path from 'path'; import {runBuild} from 'flipper-pkg-lib'; -import {getPluginDetailsFromDir} from 'flipper-plugin-lib'; +import {getInstalledPluginDetails} from 'flipper-plugin-lib'; export default class Bundle extends Command { public static description = 'transpiles and bundles plugin'; @@ -55,7 +55,7 @@ export default class Bundle extends Command { `package.json is not found in plugin source directory ${inputDirectory}.`, ); } - const plugin = await getPluginDetailsFromDir(inputDirectory); + const plugin = await getInstalledPluginDetails(inputDirectory); const out = path.resolve(inputDirectory, plugin.main); await fs.ensureDir(path.dirname(out)); diff --git a/desktop/pkg/src/commands/pack.ts b/desktop/pkg/src/commands/pack.ts index 1219c5985..0ac8598ec 100644 --- a/desktop/pkg/src/commands/pack.ts +++ b/desktop/pkg/src/commands/pack.ts @@ -16,7 +16,7 @@ import * as path from 'path'; import * as yarn from '../utils/yarn'; import cli from 'cli-ux'; import {runBuild} from 'flipper-pkg-lib'; -import {getPluginDetailsFromDir} from 'flipper-plugin-lib'; +import {getInstalledPluginDetails} from 'flipper-plugin-lib'; async function deriveOutputFileName(inputDirectory: string): Promise { const packageJson = await readJSON(path.join(inputDirectory, 'package.json')); @@ -116,7 +116,7 @@ export default class Pack extends Command { cli.action.stop(); cli.action.start('Reading plugin details'); - const plugin = await getPluginDetailsFromDir(inputDirectory); + const plugin = await getInstalledPluginDetails(inputDirectory); const out = path.resolve(inputDirectory, plugin.main); cli.action.stop(`done. Source: ${plugin.source}. Main: ${plugin.main}.`); diff --git a/desktop/pkg/src/utils/runMigrate.ts b/desktop/pkg/src/utils/runMigrate.ts index bd0624d0d..d7bbf495d 100644 --- a/desktop/pkg/src/utils/runMigrate.ts +++ b/desktop/pkg/src/utils/runMigrate.ts @@ -9,7 +9,7 @@ import path from 'path'; import fs from 'fs-extra'; -import {getPluginDetails} from 'flipper-plugin-lib'; +import {getInstalledPluginDetails} from 'flipper-plugin-lib'; import {kebabCase} from 'lodash'; export default async function ( @@ -36,7 +36,7 @@ export default async function ( console.log(`⚙️ Migrating Flipper plugin package in ${dir}`); const packageJsonString = (await fs.readFile(packageJsonPath)).toString(); const packageJson = JSON.parse(packageJsonString); - const pluginDetails = await getPluginDetails(dir, packageJson); + const pluginDetails = await getInstalledPluginDetails(dir, packageJson); if (pluginDetails.specVersion === 2) { console.log( `✅ Plugin is already defined according to the latest specification version.`, diff --git a/desktop/plugin-lib/src/PluginDetails.ts b/desktop/plugin-lib/src/PluginDetails.ts index 94f504736..50b85f056 100644 --- a/desktop/plugin-lib/src/PluginDetails.ts +++ b/desktop/plugin-lib/src/PluginDetails.ts @@ -8,15 +8,12 @@ */ export interface PluginDetails { - dir: string; name: string; specVersion: number; version: string; source: string; main: string; id: string; - isDefault: boolean; - entry: string; gatekeeper?: string; title: string; icon?: string; @@ -32,7 +29,36 @@ export interface PluginDetails { flipperSDKVersion?: string; } -export interface DownloadablePluginDetails extends PluginDetails { +export interface ConcretePluginDetails extends PluginDetails { + // Determines whether the plugin is a part of the Flipper JS bundle. + isBundled: boolean; + // Determines whether the plugin is physically available for activation in Flipper. + isActivatable: boolean; +} + +// Describes plugin which is a part of the Flipper JS bundle. +export interface BundledPluginDetails extends ConcretePluginDetails { + isBundled: true; + isActivatable: true; +} + +// Describes plugin installed on the disk. +export interface InstalledPluginDetails extends ConcretePluginDetails { + isBundled: false; + isActivatable: true; + dir: string; + entry: string; +} + +// Describes plugin physically available for activation in Flipper. +export type ActivatablePluginDetails = + | BundledPluginDetails + | InstalledPluginDetails; + +// Describes plugin available for downloading. Until downloaded to the disk it is not available for activation in Flipper. +export interface DownloadablePluginDetails extends ConcretePluginDetails { + isActivatable: false; + isBundled: false; downloadUrl: string; lastUpdated: Date; } diff --git a/desktop/plugin-lib/src/__tests__/getPluginDetails.node.ts b/desktop/plugin-lib/src/__tests__/getPluginDetails.node.ts index c0a8f3243..65974fd96 100644 --- a/desktop/plugin-lib/src/__tests__/getPluginDetails.node.ts +++ b/desktop/plugin-lib/src/__tests__/getPluginDetails.node.ts @@ -9,7 +9,7 @@ import fs from 'fs-extra'; import path from 'path'; -import {getPluginDetailsFromDir} from '../getPluginDetails'; +import {getInstalledPluginDetails} from '../getPluginDetails'; import {pluginInstallationDir} from '../pluginPaths'; import {normalizePath} from 'flipper-test-utils'; @@ -31,7 +31,7 @@ test('getPluginDetailsV1', async () => { }; jest.mock('fs-extra', () => jest.fn()); fs.readJson = jest.fn().mockImplementation(() => pluginV1); - const details = await getPluginDetailsFromDir(pluginPath); + const details = await getInstalledPluginDetails(pluginPath); details.dir = normalizePath(details.dir); details.entry = normalizePath(details.entry); expect(details).toMatchInlineSnapshot(` @@ -45,7 +45,8 @@ test('getPluginDetailsV1', async () => { "gatekeeper": "GK_flipper_plugin_test", "icon": undefined, "id": "flipper-plugin-test", - "isDefault": false, + "isActivatable": true, + "isBundled": false, "main": "dist/bundle.js", "name": "flipper-plugin-test", "source": "src/index.tsx", @@ -69,7 +70,7 @@ test('getPluginDetailsV2', async () => { }; jest.mock('fs-extra', () => jest.fn()); fs.readJson = jest.fn().mockImplementation(() => pluginV2); - const details = await getPluginDetailsFromDir(pluginPath); + const details = await getInstalledPluginDetails(pluginPath); details.dir = normalizePath(details.dir); details.entry = normalizePath(details.entry); expect(details).toMatchInlineSnapshot(` @@ -83,7 +84,8 @@ test('getPluginDetailsV2', async () => { "gatekeeper": "GK_flipper_plugin_test", "icon": undefined, "id": "flipper-plugin-test", - "isDefault": false, + "isActivatable": true, + "isBundled": false, "main": "dist/bundle.js", "name": "flipper-plugin-test", "source": "src/index.tsx", @@ -107,7 +109,7 @@ test('id used as title if the latter omited', async () => { }; jest.mock('fs-extra', () => jest.fn()); fs.readJson = jest.fn().mockImplementation(() => pluginV2); - const details = await getPluginDetailsFromDir(pluginPath); + const details = await getInstalledPluginDetails(pluginPath); details.dir = normalizePath(details.dir); details.entry = normalizePath(details.entry); expect(details).toMatchInlineSnapshot(` @@ -121,7 +123,8 @@ test('id used as title if the latter omited', async () => { "gatekeeper": "GK_flipper_plugin_test", "icon": undefined, "id": "test", - "isDefault": false, + "isActivatable": true, + "isBundled": false, "main": "dist/bundle.js", "name": "flipper-plugin-test", "source": "src/index.tsx", @@ -144,7 +147,7 @@ test('name without "flipper-plugin-" prefix is used as title if the latter omite }; jest.mock('fs-extra', () => jest.fn()); fs.readJson = jest.fn().mockImplementation(() => pluginV2); - const details = await getPluginDetailsFromDir(pluginPath); + const details = await getInstalledPluginDetails(pluginPath); details.dir = normalizePath(details.dir); details.entry = normalizePath(details.entry); expect(details).toMatchInlineSnapshot(` @@ -158,7 +161,8 @@ test('name without "flipper-plugin-" prefix is used as title if the latter omite "gatekeeper": "GK_flipper_plugin_test", "icon": undefined, "id": "flipper-plugin-test", - "isDefault": false, + "isActivatable": true, + "isBundled": false, "main": "dist/bundle.js", "name": "flipper-plugin-test", "source": "src/index.tsx", @@ -184,7 +188,7 @@ test('flipper-plugin-version is parsed', async () => { }; jest.mock('fs-extra', () => jest.fn()); fs.readJson = jest.fn().mockImplementation(() => pluginV2); - const details = await getPluginDetailsFromDir(pluginPath); + const details = await getInstalledPluginDetails(pluginPath); details.dir = normalizePath(details.dir); details.entry = normalizePath(details.entry); expect(details).toMatchInlineSnapshot(` @@ -198,7 +202,8 @@ test('flipper-plugin-version is parsed', async () => { "gatekeeper": "GK_flipper_plugin_test", "icon": undefined, "id": "flipper-plugin-test", - "isDefault": false, + "isActivatable": true, + "isBundled": false, "main": "dist/bundle.js", "name": "flipper-plugin-test", "source": "src/index.tsx", diff --git a/desktop/plugin-lib/src/__tests__/getUpdatablePlugins.node.ts b/desktop/plugin-lib/src/__tests__/getUpdatablePlugins.node.ts index 38485c5c1..2f31e0ca0 100644 --- a/desktop/plugin-lib/src/__tests__/getUpdatablePlugins.node.ts +++ b/desktop/plugin-lib/src/__tests__/getUpdatablePlugins.node.ts @@ -18,7 +18,7 @@ import { import {getInstalledPlugins} from '../pluginInstaller'; import {mocked} from 'ts-jest/utils'; import type {Package} from 'npm-api'; -import PluginDetails from '../PluginDetails'; +import {InstalledPluginDetails} from '../PluginDetails'; jest.mock('npm-api', () => { return jest.fn().mockImplementation(() => { @@ -54,7 +54,7 @@ jest.mock('npm-api', () => { }); }); -const installedPlugins: PluginDetails[] = [ +const installedPlugins: InstalledPluginDetails[] = [ { name: 'flipper-plugin-hello', entry: './test/index.js', @@ -66,7 +66,8 @@ const installedPlugins: PluginDetails[] = [ id: 'Hello', title: 'Hello', description: 'World?', - isDefault: false, + isBundled: false, + isActivatable: true, }, { name: 'flipper-plugin-world', @@ -79,7 +80,8 @@ const installedPlugins: PluginDetails[] = [ id: 'World', title: 'World', description: 'Hello?', - isDefault: false, + isBundled: false, + isActivatable: true, }, ]; diff --git a/desktop/plugin-lib/src/getPluginDetails.ts b/desktop/plugin-lib/src/getPluginDetails.ts index fdc827226..7d46821cc 100644 --- a/desktop/plugin-lib/src/getPluginDetails.ts +++ b/desktop/plugin-lib/src/getPluginDetails.ts @@ -9,10 +9,14 @@ import fs from 'fs-extra'; import path from 'path'; -import {PluginDetails} from './PluginDetails'; -import {getPluginVersionInstallationDir, pluginCacheDir} from './pluginPaths'; +import { + DownloadablePluginDetails, + InstalledPluginDetails, + PluginDetails, +} from './PluginDetails'; +import {pluginCacheDir} from './pluginPaths'; -export async function getPluginDetails(pluginDir: string, packageJson: any) { +export function getPluginDetails(packageJson: any): PluginDetails { const specVersion = packageJson.$schema && packageJson.$schema === @@ -21,60 +25,61 @@ export async function getPluginDetails(pluginDir: string, packageJson: any) { : 1; switch (specVersion) { case 1: - return await getPluginDetailsV1(pluginDir, packageJson); + return getPluginDetailsV1(packageJson); case 2: - return await getPluginDetailsV2(pluginDir, packageJson); + return getPluginDetailsV2(packageJson); default: throw new Error(`Unknown plugin format version: ${specVersion}`); } } -export async function getPluginDetailsFromDir( - pluginDir: string, -): Promise { - const packageJson = await fs.readJson(path.join(pluginDir, 'package.json')); - return await getPluginDetails(pluginDir, packageJson); +export async function getInstalledPluginDetails( + dir: string, + packageJson?: any, +): Promise { + packageJson = + packageJson ?? (await fs.readJson(path.join(dir, 'package.json'))); + const pluginDetails = getPluginDetails(packageJson); + const entry = + pluginDetails.specVersion === 1 + ? path.resolve( + pluginCacheDir, + `${packageJson.name}@${packageJson.version || '0.0.0'}.js`, + ) + : path.resolve(dir, packageJson.main); + return { + ...pluginDetails, + isBundled: false, + isActivatable: true, + dir, + entry, + }; } -export async function getPluginDetailsFromPackageJson(packageJson: any) { - const pluginDir = getPluginVersionInstallationDir( - packageJson.name, - packageJson.version, - ); - return await getPluginDetails(pluginDir, packageJson); -} - -export async function getDownloadablePluginDetails( +export function getDownloadablePluginDetails( packageJson: any, downloadUrl: string, lastUpdated: Date, -) { - const details = await getPluginDetailsFromPackageJson(packageJson); +): DownloadablePluginDetails { + const details = getPluginDetails(packageJson); return { ...details, + isBundled: false, + isActivatable: false, downloadUrl, lastUpdated, }; } // Plugins packaged using V1 are distributed as sources and compiled in run-time. -async function getPluginDetailsV1( - pluginDir: string, - packageJson: any, -): Promise { +function getPluginDetailsV1(packageJson: any): PluginDetails { return { specVersion: 1, - dir: pluginDir, name: packageJson.name, version: packageJson.version, main: 'dist/bundle.js', - entry: path.join( - pluginCacheDir, - `${packageJson.name}@${packageJson.version || '0.0.0'}.js`, - ), source: packageJson.main, id: packageJson.name, - isDefault: false, gatekeeper: packageJson.gatekeeper, icon: packageJson.icon, title: packageJson.title || packageJson.name, @@ -86,19 +91,13 @@ async function getPluginDetailsV1( } // Plugins packaged using V2 are pre-bundled, so compilation in run-time is not required for them. -async function getPluginDetailsV2( - pluginDir: string, - packageJson: any, -): Promise { +function getPluginDetailsV2(packageJson: any): PluginDetails { return { specVersion: 2, - dir: pluginDir, name: packageJson.name, version: packageJson.version, main: packageJson.main, - entry: path.resolve(pluginDir, packageJson.main), source: packageJson.flipperBundlerEntry, - isDefault: false, id: packageJson.id || packageJson.name, gatekeeper: packageJson.gatekeeper, icon: packageJson.icon, @@ -111,9 +110,10 @@ async function getPluginDetailsV2( }; } -function getTitleFromName(name: string) { +function getTitleFromName(name: string): string { const prefix = 'flipper-plugin-'; if (name.startsWith(prefix)) { return name.substr(prefix.length); } + return name; } diff --git a/desktop/plugin-lib/src/getSourcePlugins.ts b/desktop/plugin-lib/src/getSourcePlugins.ts index d925cf2cd..9434185c8 100644 --- a/desktop/plugin-lib/src/getSourcePlugins.ts +++ b/desktop/plugin-lib/src/getSourcePlugins.ts @@ -11,16 +11,17 @@ import path from 'path'; import fs from 'fs-extra'; import expandTilde from 'expand-tilde'; import {getPluginSourceFolders} from './pluginPaths'; -import {PluginDetails, getPluginDetails} from 'flipper-plugin-lib'; import pmap from 'p-map'; import pfilter from 'p-filter'; import {satisfies} from 'semver'; +import {getInstalledPluginDetails} from './getPluginDetails'; +import {InstalledPluginDetails} from './PluginDetails'; const flipperVersion = require('../package.json').version; -export async function getSourcePlugins(): Promise { +export async function getSourcePlugins(): Promise { const pluginFolders = await getPluginSourceFolders(); - const entryPoints: {[key: string]: PluginDetails} = {}; + const entryPoints: {[key: string]: InstalledPluginDetails} = {}; const additionalPlugins = await pmap(pluginFolders, (path) => entryPointForPluginFolder(path), ); @@ -47,7 +48,7 @@ export async function getSourcePlugins(): Promise { } async function entryPointForPluginFolder( pluginsDir: string, -): Promise<{[key: string]: PluginDetails}> { +): Promise<{[key: string]: InstalledPluginDetails}> { pluginsDir = expandTilde(pluginsDir); if (!fs.existsSync(pluginsDir)) { return {}; @@ -96,7 +97,7 @@ async function entryPointForPluginFolder( .then((packages) => pmap(packages, async ({manifest, dir}) => { try { - const details = await getPluginDetails(dir, manifest); + const details = await getInstalledPluginDetails(dir, manifest); if ( details.flipperSDKVersion && !satisfies(flipperVersion, details.flipperSDKVersion) @@ -117,7 +118,7 @@ async function entryPointForPluginFolder( ) .then((plugins) => plugins.filter(notNull)) .then((plugins) => - plugins.reduce<{[key: string]: PluginDetails}>((acc, cv) => { + plugins.reduce<{[key: string]: InstalledPluginDetails}>((acc, cv) => { acc[cv!.name] = cv!; return acc; }, {}), diff --git a/desktop/plugin-lib/src/getUpdatablePlugins.ts b/desktop/plugin-lib/src/getUpdatablePlugins.ts index 021d0339f..c78539952 100644 --- a/desktop/plugin-lib/src/getUpdatablePlugins.ts +++ b/desktop/plugin-lib/src/getUpdatablePlugins.ts @@ -7,15 +7,12 @@ * @format */ -import PluginDetails from './PluginDetails'; +import {InstalledPluginDetails} from './PluginDetails'; import {getInstalledPlugins} from './pluginInstaller'; import semver from 'semver'; import {getNpmHostedPlugins, NpmPackageDescriptor} from './getNpmHostedPlugins'; import NpmApi from 'npm-api'; -import { - getPluginDetails, - getPluginDetailsFromPackageJson, -} from './getPluginDetails'; +import {getInstalledPluginDetails, getPluginDetails} from './getPluginDetails'; import {getPluginVersionInstallationDir} from './pluginPaths'; import pmap from 'p-map'; import {notNull} from './typeUtils'; @@ -31,7 +28,7 @@ export type UpdatablePlugin = { updateStatus: UpdateResult; }; -export type UpdatablePluginDetails = PluginDetails & UpdatablePlugin; +export type UpdatablePluginDetails = InstalledPluginDetails & UpdatablePlugin; export async function getUpdatablePlugins( query?: string, @@ -51,7 +48,7 @@ export async function getUpdatablePlugins( semver.lt(installedPlugin.version, npmPackageDescriptor.version) ) { const pkg = await npmApi.repo(npmPackageDescriptor.name).package(); - const npmPluginDetails = await getPluginDetails( + const npmPluginDetails = await getInstalledPluginDetails( getPluginVersionInstallationDir( npmPackageDescriptor.name, npmPackageDescriptor.version, @@ -91,7 +88,7 @@ export async function getUpdatablePlugins( async (notInstalledPlugin) => { try { const pkg = await npmApi.repo(notInstalledPlugin.name).package(); - const npmPluginDetails = await getPluginDetailsFromPackageJson(pkg); + const npmPluginDetails = getPluginDetails(pkg); if (npmPluginDetails.specVersion === 1) { return null; } diff --git a/desktop/plugin-lib/src/pluginInstaller.ts b/desktop/plugin-lib/src/pluginInstaller.ts index 3b83adf62..49daa678e 100644 --- a/desktop/plugin-lib/src/pluginInstaller.ts +++ b/desktop/plugin-lib/src/pluginInstaller.ts @@ -15,8 +15,8 @@ import decompress from 'decompress'; import decompressTargz from 'decompress-targz'; import decompressUnzip from 'decompress-unzip'; import tmp from 'tmp'; -import PluginDetails from './PluginDetails'; -import {getPluginDetailsFromDir} from './getPluginDetails'; +import {InstalledPluginDetails} from './PluginDetails'; +import {getInstalledPluginDetails} from './getPluginDetails'; import { getPluginVersionInstallationDir, getPluginDirNameFromPackageName, @@ -37,8 +37,8 @@ function providePluginManagerNoDependencies(): PM { async function installPluginFromTempDir( sourceDir: string, -): Promise { - const pluginDetails = await getPluginDetailsFromDir(sourceDir); +): Promise { + const pluginDetails = await getInstalledPluginDetails(sourceDir); const {name, version} = pluginDetails; const backupDir = path.join(await getTmpDir(), `${name}-${version}`); const destinationDir = getPluginVersionInstallationDir(name, version); @@ -63,7 +63,7 @@ async function installPluginFromTempDir( } throw err; } - return await getPluginDetailsFromDir(destinationDir); + return await getInstalledPluginDetails(destinationDir); } async function getPluginRootDir(dir: string) { @@ -87,12 +87,12 @@ async function getPluginRootDir(dir: string) { export async function getInstalledPlugin( name: string, version: string, -): Promise { +): Promise { const dir = getPluginVersionInstallationDir(name, version); if (!(await fs.pathExists(dir))) { return null; } - return await getPluginDetailsFromDir(dir); + return await getInstalledPluginDetails(dir); } export async function installPluginFromNpm(name: string) { @@ -114,7 +114,7 @@ export async function installPluginFromNpm(name: string) { export async function installPluginFromFile( packagePath: string, -): Promise { +): Promise { const tmpDir = await getTmpDir(); try { const files = await decompress(packagePath, tmpDir, { @@ -140,14 +140,14 @@ export async function removePlugins( await pmap(names, (name) => removePlugin(name)); } -export async function getInstalledPlugins(): Promise { +export async function getInstalledPlugins(): Promise { const versionDirs = await getInstalledPluginVersionDirs(); return pmap( versionDirs .filter(([_, versionDirs]) => versionDirs.length > 0) .map(([_, versionDirs]) => versionDirs[0]), (latestVersionDir) => - getPluginDetailsFromDir(latestVersionDir).catch((err) => { + getInstalledPluginDetails(latestVersionDir).catch((err) => { console.error(`Failed to load plugin from ${latestVersionDir}`, err); return null; }), @@ -187,7 +187,7 @@ export async function moveInstalledPluginsFromLegacyDir() { ) .then((dirs) => pmap(dirs, (dir) => - getPluginDetailsFromDir(dir).catch(async (err) => { + getInstalledPluginDetails(dir).catch(async (err) => { console.error( `Failed to load plugin from ${dir} on moving legacy plugins. Removing it.`, err, diff --git a/desktop/scripts/build-utils.ts b/desktop/scripts/build-utils.ts index 9a823764c..88a189987 100644 --- a/desktop/scripts/build-utils.ts +++ b/desktop/scripts/build-utils.ts @@ -14,7 +14,11 @@ import fs from 'fs-extra'; import {spawn} from 'promisify-child-process'; import {getWatchFolders} from 'flipper-pkg-lib'; import getAppWatchFolders from './get-app-watch-folders'; -import {getSourcePlugins, getPluginSourceFolders} from 'flipper-plugin-lib'; +import { + getSourcePlugins, + getPluginSourceFolders, + BundledPluginDetails, +} from 'flipper-plugin-lib'; import { appDir, staticDir, @@ -33,19 +37,26 @@ export function die(err: Error) { export async function generatePluginEntryPoints() { console.log('⚙️ Generating plugin entry points...'); - const plugins = await getSourcePlugins(); - for (const plugin of plugins) { - plugin.isDefault = true; - plugin.version = plugin.version === '0.0.0' ? version : plugin.version; - plugin.flipperSDKVersion = - plugin.flipperSDKVersion === '0.0.0' ? version : plugin.flipperSDKVersion; - } + const sourcePlugins = await getSourcePlugins(); + const bundledPlugins = sourcePlugins.map( + (p) => + ({ + ...p, + isBundled: true, + version: p.version === '0.0.0' ? version : p.version, + flipperSDKVersion: + p.flipperSDKVersion === '0.0.0' ? version : p.flipperSDKVersion, + } as BundledPluginDetails), + ); if (await fs.pathExists(defaultPluginsIndexDir)) { await fs.remove(defaultPluginsIndexDir); } await fs.mkdirp(defaultPluginsIndexDir); - await fs.writeJSON(path.join(defaultPluginsIndexDir, 'index.json'), plugins); - const pluginRequres = plugins + await fs.writeJSON( + path.join(defaultPluginsIndexDir, 'index.json'), + bundledPlugins, + ); + const pluginRequres = bundledPlugins .map((x) => ` '${x.name}': require('${x.name}')`) .join(',\n'); const generatedIndex = `