Versioning for plugin format

Summary:
Added versioning for plugin format.

The first version is where "main" points to source code entry and plugins are bundled by Flipper in run-time on loading them.

The second version is where "main" points to the already existing bundle and Flipper just loads it without bundling. The plugins of version 2 must be bundled using "flipper-pkg" tool before publishing.

Changelog: Support new packaging format for plugins.

Reviewed By: mweststrate

Differential Revision: D21074173

fbshipit-source-id: 7b70250e48e5bd5d359c96149fb5b14e67783c4d
This commit is contained in:
Anton Nikolaev
2020-04-20 06:01:08 -07:00
committed by Facebook GitHub Bot
parent eb34b2f6e3
commit ca2d04a5da
22 changed files with 329 additions and 163 deletions

View File

@@ -11,82 +11,95 @@ import path from 'path';
import fs from 'fs-extra';
import expandTilde from 'expand-tilde';
import getPluginFolders from './getPluginFolders';
import {PluginDetails, getPluginDetails} from 'flipper-pkg-lib';
import pmap from 'p-map';
import pfilter from 'p-filter';
export type PluginManifest = {
version: string;
name: string;
main?: string;
bundleMain?: string;
[key: string]: any;
};
export type PluginInfo = {
rootDir: string;
name: string;
entry: string;
manifest: PluginManifest;
};
export default async function getPlugins(includeThirdparty: boolean = false) {
export default async function getPlugins(
includeThirdparty: boolean = false,
): Promise<PluginDetails[]> {
const pluginFolders = await getPluginFolders(includeThirdparty);
const entryPoints: {[key: string]: PluginInfo} = {};
pluginFolders.forEach((additionalPath) => {
const additionalPlugins = entryPointForPluginFolder(additionalPath);
Object.keys(additionalPlugins).forEach((key) => {
entryPoints[key] = additionalPlugins[key];
const entryPoints: {[key: string]: PluginDetails} = {};
const additionalPlugins = await pmap(pluginFolders, (path) =>
entryPointForPluginFolder(path),
);
for (const p of additionalPlugins) {
Object.keys(p).forEach((key) => {
entryPoints[key] = p[key];
});
});
}
return Object.values(entryPoints);
}
function entryPointForPluginFolder(pluginPath: string) {
pluginPath = expandTilde(pluginPath);
if (!fs.existsSync(pluginPath)) {
async function entryPointForPluginFolder(
pluginsDir: string,
): Promise<{[key: string]: PluginDetails}> {
pluginsDir = expandTilde(pluginsDir);
if (!fs.existsSync(pluginsDir)) {
return {};
}
return fs
.readdirSync(pluginPath)
.filter((name) => fs.lstatSync(path.join(pluginPath, name)).isDirectory())
.filter(Boolean)
.map((name) => {
let packageJSON;
try {
packageJSON = fs
.readFileSync(path.join(pluginPath, name, 'package.json'))
.toString();
} catch (e) {}
if (packageJSON) {
return await fs
.readdir(pluginsDir)
.then((entries) =>
entries.map((name) => ({
dir: path.join(pluginsDir, name),
manifestPath: path.join(pluginsDir, name, 'package.json'),
})),
)
.then((entries) =>
pfilter(entries, ({manifestPath}) => fs.pathExists(manifestPath)),
)
.then((packages) =>
pmap(packages, async ({manifestPath, dir}) => {
try {
const json = JSON.parse(packageJSON);
if (json.workspaces) {
return;
}
if (!json.keywords || !json.keywords.includes('flipper-plugin')) {
console.log(
`Skipping package "${json.name}" as its "keywords" field does not contain tag "flipper-plugin"`,
);
return null;
}
const pkg = json as PluginManifest;
const plugin: PluginInfo = {
manifest: pkg,
name: pkg.name,
entry: path.join(pluginPath, name, pkg.main || 'index.js'),
rootDir: path.join(pluginPath, name),
const manifest = await fs.readJson(manifestPath);
return {
dir,
manifest,
};
return plugin;
} catch (e) {
console.error(
`Could not load plugin "${pluginPath}", because package.json is invalid.`,
`Could not load plugin from "${dir}", because package.json is invalid.`,
);
console.error(e);
return null;
}
}
return null;
})
.filter(Boolean)
.reduce<{[key: string]: PluginInfo}>((acc, cv) => {
acc[cv!.name] = cv!;
return acc;
}, {});
}),
)
.then((packages) => packages.filter(notNull))
.then((packages) => packages.filter(({manifest}) => !manifest.workspaces))
.then((packages) =>
packages.filter(({manifest: {keywords, name}}) => {
if (!keywords || !keywords.includes('flipper-plugin')) {
console.log(
`Skipping package "${name}" as its "keywords" field does not contain tag "flipper-plugin"`,
);
return false;
}
return true;
}),
)
.then((packages) =>
pmap(packages, async ({manifest, dir}) => {
try {
return await getPluginDetails(dir, manifest);
} catch (e) {
console.error(
`Could not load plugin from "${dir}", because package.json is invalid.`,
);
console.error(e);
return null;
}
}),
)
.then((plugins) => plugins.filter(notNull))
.then((plugins) =>
plugins.reduce<{[key: string]: PluginDetails}>((acc, cv) => {
acc[cv!.name] = cv!;
return acc;
}, {}),
);
}
function notNull<T>(x: T | null | undefined): x is T {
return x !== null && x !== undefined;
}