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

@@ -82,12 +82,6 @@
"yargs": "^15.3.1",
"yazl": "^2.5.1"
},
"greenkeeper": {
"ignore": [
"tmp",
"flipper-doctor"
]
},
"optionalDependencies": {
"7zip-bin-mac": "^1.0.1"
}

View File

@@ -143,14 +143,7 @@ class PluginDebugger extends Component<Props> {
getRows(): Array<TableBodyRow> {
const rows: Array<TableBodyRow> = [];
// bundled plugins are loaded from the defaultPlugins directory within
// Flipper's package.
const externalPluginPath = (p: any) =>
p.out
? p.out.startsWith('./defaultPlugins/')
? null
: p.entry
: 'Native Plugin';
const externalPluginPath = (p: any) => p.entry || 'Native Plugin';
this.props.gatekeepedPlugins.forEach((plugin) =>
rows.push(

View File

@@ -69,14 +69,14 @@ test('checkDisabled', () => {
expect(
disabled({
name: 'other Name',
out: './test/index.js',
entry: './test/index.js',
}),
).toBeTruthy();
expect(
disabled({
name: disabledPlugin,
out: './test/index.js',
entry: './test/index.js',
}),
).toBeFalsy();
});
@@ -85,7 +85,7 @@ test('checkGK for plugin without GK', () => {
expect(
checkGK([])({
name: 'pluginID',
out: './test/index.js',
entry: './test/index.js',
}),
).toBeTruthy();
});
@@ -95,7 +95,7 @@ test('checkGK for passing plugin', () => {
checkGK([])({
name: 'pluginID',
gatekeeper: TEST_PASSING_GK,
out: './test/index.js',
entry: './test/index.js',
}),
).toBeTruthy();
});
@@ -106,7 +106,7 @@ test('checkGK for failing plugin', () => {
const plugins = checkGK(gatekeepedPlugins)({
name,
gatekeeper: TEST_FAILING_GK,
out: './test/index.js',
entry: './test/index.js',
});
expect(plugins).toBeFalsy();
@@ -117,7 +117,7 @@ test('requirePlugin returns null for invalid requires', () => {
const requireFn = requirePlugin([], require);
const plugin = requireFn({
name: 'pluginID',
out: 'this/path/does not/exist',
entry: 'this/path/does not/exist',
});
expect(plugin).toBeNull();
@@ -128,7 +128,7 @@ test('requirePlugin loads plugin', () => {
const requireFn = requirePlugin([], require);
const plugin = requireFn({
name,
out: path.join(__dirname, 'TestPlugin'),
entry: path.join(__dirname, 'TestPlugin'),
});
expect(plugin!.prototype).toBeInstanceOf(FlipperPlugin);
expect(plugin!.id).toBe(TestPlugin.id);

View File

@@ -96,15 +96,15 @@ function getBundledPlugins(): Array<PluginDefinition> {
}
return bundledPlugins
.filter((plugin) => notNull(plugin.out))
.filter((plugin) => notNull(plugin.entry))
.map(
(plugin) =>
({
...plugin,
out: path.join(pluginPath, plugin.out!),
entry: path.resolve(pluginPath, plugin.entry!),
} as PluginDefinition),
)
.concat(bundledPlugins.filter((plugin) => !plugin.out));
.concat(bundledPlugins.filter((plugin) => !plugin.entry));
}
export function getDynamicPlugins() {
@@ -155,8 +155,8 @@ export const requirePlugin = (
pluginDefinition: PluginDefinition,
): typeof FlipperPlugin | typeof FlipperDevicePlugin | null => {
try {
let plugin = pluginDefinition.out
? reqFn(pluginDefinition.out)
let plugin = pluginDefinition.entry
? reqFn(pluginDefinition.entry)
: defaultPluginsIndex[pluginDefinition.name];
if (plugin.default) {
plugin = plugin.default;

View File

@@ -4,7 +4,7 @@
"description": "Babel transformer for Flipper plugins",
"repository": "facebook/flipper",
"main": "lib/index.js",
"flipper:source": "src",
"flipperBundlerEntry": "src",
"types": "lib/index.d.ts",
"license": "MIT",
"bugs": "https://github.com/facebook/flipper/issues",

View File

@@ -3,7 +3,7 @@
"version": "0.37.0",
"description": "Utility for checking for issues with a flipper installation",
"main": "lib/index.js",
"flipper:source": "src",
"flipperBundlerEntry": "src",
"types": "lib/index.d.ts",
"license": "MIT",
"devDependencies": {

View File

@@ -4,7 +4,7 @@
"description": "Library for building and publishing Flipper plugins",
"repository": "facebook/flipper",
"main": "lib/index.js",
"flipper:source": "src",
"flipperBundlerEntry": "src",
"types": "lib/index.d.ts",
"license": "MIT",
"bugs": "https://github.com/facebook/flipper/issues",

View File

@@ -0,0 +1,25 @@
/**
* 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
*/
export default interface PluginDetails {
dir: string;
name: string;
specVersion: number;
version: string;
source: string;
main: string;
gatekeeper?: string;
icon?: string;
title?: string;
category?: string;
bugs?: {
email?: string;
url?: string;
};
}

View File

@@ -0,0 +1,70 @@
/**
* 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 fs from 'fs-extra';
import {mocked} from 'ts-jest/utils';
import getPluginDetails from '../getPluginDetails';
jest.mock('fs-extra');
const fsMock = mocked(fs, true);
test('getPluginDetailsV1', async () => {
const pluginV1 = {
name: 'flipper-plugin-test',
version: '2.0.0',
title: 'Test Plugin',
main: 'src/index.tsx',
gatekeeper: 'GK_flipper_plugin_test',
};
fsMock.readJson.mockImplementation(() => pluginV1);
const details = await getPluginDetails('./plugins/flipper-plugin-test');
expect(details).toMatchInlineSnapshot(`
Object {
"bugs": undefined,
"category": undefined,
"dir": "./plugins/flipper-plugin-test",
"gatekeeper": "GK_flipper_plugin_test",
"icon": undefined,
"main": "dist/index.js",
"name": "flipper-plugin-test",
"source": "src/index.tsx",
"specVersion": 1,
"title": "Test Plugin",
"version": "2.0.0",
}
`);
});
test('getPluginDetailsV2', async () => {
const pluginV2 = {
specVersion: 2,
name: 'flipper-plugin-test',
version: '3.0.1',
main: 'dist/bundle.js',
flipperBundlerEntry: 'src/index.tsx',
gatekeeper: 'GK_flipper_plugin_test',
};
fsMock.readJson.mockImplementation(() => pluginV2);
const details = await getPluginDetails('./plugins/flipper-plugin-test');
expect(details).toMatchInlineSnapshot(`
Object {
"bugs": undefined,
"category": undefined,
"dir": "./plugins/flipper-plugin-test",
"gatekeeper": "GK_flipper_plugin_test",
"icon": undefined,
"main": "dist/bundle.js",
"name": "flipper-plugin-test",
"source": "src/index.tsx",
"specVersion": 2,
"title": undefined,
"version": "3.0.1",
}
`);
});

View File

@@ -0,0 +1,71 @@
/**
* 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 fs from 'fs-extra';
import path from 'path';
import PluginDetails from './PluginDetails';
export default async function (
pluginDir: string,
packageJson?: any,
): Promise<PluginDetails> {
packageJson =
packageJson || (await fs.readJson(path.join(pluginDir, 'package.json')));
const specVersion = !packageJson.specVersion
? 1
: (packageJson.specVersion as number);
switch (specVersion) {
case 1:
return await getPluginDetailsV1(pluginDir, packageJson);
case 2:
return await getPluginDetailsV2(pluginDir, packageJson);
default:
throw new Error(`Unknown plugin format version: ${specVersion}`);
}
}
// Plugins packaged using V1 are distributed as sources and compiled in run-time.
async function getPluginDetailsV1(
pluginDir: string,
packageJson: any,
): Promise<PluginDetails> {
return {
specVersion: 1,
dir: pluginDir,
name: packageJson.name,
version: packageJson.version,
main: path.join('dist', 'index.js'),
source: packageJson.main,
gatekeeper: packageJson.gatekeeper,
icon: packageJson.icon,
title: packageJson.title,
category: packageJson.category,
bugs: packageJson.bugs,
};
}
// 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<PluginDetails> {
return {
specVersion: 2,
dir: pluginDir,
name: packageJson.name,
version: packageJson.version,
main: packageJson.main,
source: packageJson.flipperBundlerEntry,
gatekeeper: packageJson.gatekeeper,
icon: packageJson.icon,
title: packageJson.displayName || packageJson.title,
category: packageJson.category,
bugs: packageJson.bugs,
};
}

View File

@@ -9,3 +9,5 @@
export {default as runBuild} from './runBuild';
export {default as getWatchFolders} from './getWatchFolders';
export {default as PluginDetails} from './PluginDetails';
export {default as getPluginDetails} from './getPluginDetails';

View File

@@ -4,7 +4,7 @@
"description": "Utility for building and publishing Flipper plugins",
"repository": "facebook/flipper",
"main": "lib/index.js",
"flipper:source": "src",
"flipperBundlerEntry": "src",
"types": "lib/index.d.ts",
"license": "MIT",
"bin": {

View File

@@ -14,7 +14,7 @@ import * as inquirer from 'inquirer';
import * as path from 'path';
import * as yarn from '../utils/yarn';
import cli from 'cli-ux';
import {runBuild} from 'flipper-pkg-lib';
import {runBuild, getPluginDetails} from 'flipper-pkg-lib';
async function deriveOutputFileName(inputDirectory: string): Promise<string> {
const packageJson = await readJSON(path.join(inputDirectory, 'package.json'));
@@ -100,22 +100,14 @@ export default class Bundle extends Command {
await yarn.install(inputDirectory);
cli.action.stop();
cli.action.start('Reading package.json');
const packageJson = await readJSON(
path.join(inputDirectory, 'package.json'),
);
const entry =
packageJson.main ??
((await pathExists(path.join(inputDirectory, 'index.tsx')))
? 'index.tsx'
: 'index.jsx');
const bundleMain = packageJson.bundleMain ?? path.join('dist', 'index.js');
const out = path.resolve(inputDirectory, bundleMain);
cli.action.stop(`done. Entry: ${entry}. Bundle main: ${bundleMain}.`);
cli.action.start('Reading plugin details');
const plugin = await getPluginDetails(inputDirectory);
const out = path.resolve(inputDirectory, plugin.main);
cli.action.stop(`done. Source: ${plugin.source}. Main: ${plugin.main}.`);
cli.action.start(`Compiling`);
await ensureDir(path.dirname(out));
await runBuild(inputDirectory, entry, out);
await runBuild(inputDirectory, plugin.source, out);
cli.action.stop();
cli.action.start(`Packing to ${outputFile}`);

View File

@@ -122,6 +122,7 @@ export const NetworkRouteContext = createContext<NetworkRouteManager>(
);
export default class extends FlipperPlugin<State, any, PersistedState> {
static id = 'Network';
static keyboardActions: Array<DefaultKeyboardAction> = ['clear'];
static subscribed = [];
static defaultPersistedState = {

View File

@@ -1,21 +1,26 @@
{
"name": "Network",
"name": "flipper-plugin-network",
"specVersion": 2,
"flipperBundlerEntry": "index.tsx",
"main": "dist/index.js",
"title": "Network",
"description": "Use the Network inspector to inspect outgoing network traffic in your apps.",
"icon": "internet",
"version": "1.0.0",
"main": "index.tsx",
"license": "MIT",
"keywords": [
"flipper-plugin"
],
"bugs": {
"email": "oncall+flipper@xmail.facebook.com",
"url": "https://fb.workplace.com/groups/flippersupport/"
},
"dependencies": {
"@types/pako": "^1.0.1",
"lodash": "^4.17.11",
"pako": "^1.0.11",
"xml-beautifier": "^0.4.0"
},
"icon": "internet",
"title": "Network",
"bugs": {
"email": "oncall+flipper@xmail.facebook.com",
"url": "https://fb.workplace.com/groups/flippersupport/"
"devDependencies": {
"@types/pako": "^1.0.1"
}
}

View File

@@ -33,16 +33,13 @@ export function die(err: Error) {
export async function generatePluginEntryPoints() {
console.log('⚙️ Generating plugin entry points...');
const pluginEntryPoints = await getPlugins();
const plugins = await getPlugins();
if (await fs.pathExists(defaultPluginsIndexDir)) {
await fs.remove(defaultPluginsIndexDir);
}
await fs.mkdirp(defaultPluginsIndexDir);
await fs.writeJSON(
path.join(defaultPluginsIndexDir, 'index.json'),
pluginEntryPoints.map((plugin) => plugin.manifest),
);
const pluginRequres = pluginEntryPoints
await fs.writeJSON(path.join(defaultPluginsIndexDir, 'index.json'), plugins);
const pluginRequres = plugins
.map((x) => ` '${x.name}': require('${x.name}')`)
.join(',\n');
const generatedIndex = `
@@ -76,7 +73,7 @@ async function compile(
),
},
resolver: {
resolverMainFields: ['flipper:source', 'module', 'main'],
resolverMainFields: ['flipperBundlerEntry', 'module', 'main'],
blacklistRE: /\.native\.js$/,
sourceExts: ['js', 'jsx', 'ts', 'tsx', 'json', 'mjs'],
},
@@ -151,7 +148,7 @@ export async function compileMain() {
},
resolver: {
sourceExts: ['tsx', 'ts', 'js'],
resolverMainFields: ['flipper:source', 'module', 'main'],
resolverMainFields: ['flipperBundlerEntry', 'module', 'main'],
blacklistRE: /\.native\.js$/,
},
});

View File

@@ -99,7 +99,7 @@ async function startMetroServer(app: Express, server: http.Server) {
},
resolver: {
...baseConfig.resolver,
resolverMainFields: ['flipper:source', 'module', 'main'],
resolverMainFields: ['flipperBundlerEntry', 'module', 'main'],
blacklistRE: /\.native\.js$/,
resolveRequest: (context: any, moduleName: string, platform: string) => {
if (moduleName.startsWith('./localhost:3000')) {

View File

@@ -14,8 +14,8 @@ import util from 'util';
import recursiveReaddir from 'recursive-readdir';
import pMap from 'p-map';
import {homedir} from 'os';
import {getWatchFolders} from 'flipper-pkg-lib';
import {default as getPlugins, PluginManifest, PluginInfo} from './getPlugins';
import {getWatchFolders, PluginDetails} from 'flipper-pkg-lib';
import getPlugins from './getPlugins';
import startWatchPlugins from './startWatchPlugins';
const HOME_DIR = homedir();
@@ -35,13 +35,13 @@ export type CompileOptions = {
recompileOnChanges: boolean;
};
export type CompiledPluginInfo = PluginManifest & {out: string};
export type CompiledPluginDetails = PluginDetails & {entry: string};
export default async function (
reloadCallback: (() => void) | null,
pluginCache: string,
options: CompileOptions = DEFAULT_COMPILE_OPTIONS,
): Promise<CompiledPluginInfo[]> {
): Promise<CompiledPluginDetails[]> {
if (process.env.FLIPPER_FAST_REFRESH) {
console.log(
'🥫 Skipping loading of third-party plugins because Fast Refresh is enabled',
@@ -74,12 +74,12 @@ export default async function (
const compiledDynamicPlugins = (await compilations).filter(
(c) => c !== null,
) as CompiledPluginInfo[];
) as CompiledPluginDetails[];
console.log('✅ Compiled all plugins.');
return compiledDynamicPlugins;
}
async function startWatchChanges(
plugins: PluginInfo[],
plugins: PluginDetails[],
reloadCallback: (() => void) | null,
pluginCache: string,
options: CompileOptions = DEFAULT_COMPILE_OPTIONS,
@@ -88,7 +88,7 @@ async function startWatchChanges(
// no hot reloading for plugins in .flipper folder. This is to prevent
// Flipper from reloading, while we are doing changes on thirdparty plugins.
.filter(
(plugin) => !plugin.rootDir.startsWith(path.join(HOME_DIR, '.flipper')),
(plugin) => !plugin.dir.startsWith(path.join(HOME_DIR, '.flipper')),
);
const watchOptions = Object.assign({}, options, {force: true});
await startWatchPlugins(filteredPlugins, (plugin) =>
@@ -142,30 +142,32 @@ async function getMetroDir() {
return __dirname;
}
async function compilePlugin(
pluginInfo: PluginInfo,
pluginDetails: PluginDetails,
pluginCache: string,
{force, failSilently}: CompileOptions,
): Promise<CompiledPluginInfo | null> {
const {rootDir, manifest, entry, name} = pluginInfo;
const bundleMain = manifest.bundleMain ?? path.join('dist', 'index.js');
const bundlePath = path.join(rootDir, bundleMain);
): Promise<CompiledPluginDetails | null> {
const {dir, specVersion, version, main, source, name} = pluginDetails;
const dev = process.env.NODE_ENV !== 'production';
if (await fs.pathExists(bundlePath)) {
if (specVersion > 1) {
// eslint-disable-next-line no-console
const out = path.join(rootDir, bundleMain);
console.log(`🥫 Using pre-built version of ${name}: ${out}...`);
return Object.assign({}, pluginInfo.manifest, {out});
const entry = path.join(dir, main);
if (await fs.pathExists(entry)) {
console.log(`🥫 Using pre-built version of ${name}: ${entry}...`);
return Object.assign({}, pluginDetails, {entry});
} else {
const out = path.join(
pluginCache,
`${name}@${manifest.version || '0.0.0'}.js`,
console.error(
`❌ Plugin ${name} is ignored, because its entry point not found: ${entry}.`,
);
const result = Object.assign({}, pluginInfo.manifest, {out});
const rootDirCtime = await mostRecentlyChanged(rootDir);
return null;
}
} else {
const entry = path.join(pluginCache, `${name}@${version || '0.0.0'}.js`);
const result = Object.assign({}, pluginDetails, {entry});
const rootDirCtime = await mostRecentlyChanged(dir);
if (
!force &&
(await fs.pathExists(out)) &&
rootDirCtime < (await fs.lstat(out)).ctime
(await fs.pathExists(entry)) &&
rootDirCtime < (await fs.lstat(entry)).ctime
) {
// eslint-disable-next-line no-console
console.log(`🥫 Using cached version of ${name}...`);
@@ -177,9 +179,9 @@ async function compilePlugin(
await Metro.runBuild(
{
reporter: {update: () => {}},
projectRoot: rootDir,
projectRoot: dir,
watchFolders: [metroDir || (await metroDirPromise)].concat(
await getWatchFolders(rootDir),
await getWatchFolders(dir),
),
serializer: {
getRunModuleStatement: (moduleID: string) =>
@@ -197,8 +199,8 @@ async function compilePlugin(
},
},
{
entry: entry.replace(rootDir, '.'),
out,
entry: source,
out: entry,
dev,
sourceMap: true,
minify: false,

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;
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 {
packageJSON = fs
.readFileSync(path.join(pluginPath, name, 'package.json'))
.toString();
} catch (e) {}
if (packageJSON) {
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;
}
}),
)
.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;
})
.filter(Boolean)
.reduce<{[key: string]: PluginInfo}>((acc, cv) => {
}
}),
)
.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;
}

View File

@@ -16,7 +16,8 @@
"metro": "^0.59.0",
"mkdirp": "^1.0.0",
"p-map": "^4.0.0",
"recursive-readdir": "2.2.2",
"p-filter": "^2.1.0",
"recursive-readdir": "^2.2.2",
"uuid": "^7.0.1",
"xdg-basedir": "^4.0.0",
"yargs": "^15.3.1",

View File

@@ -9,18 +9,18 @@
import path from 'path';
import Watchman from './watchman';
import {PluginInfo} from './getPlugins';
import {PluginDetails} from 'flipper-pkg-lib';
export default async function startWatchPlugins(
plugins: PluginInfo[],
compilePlugin: (plugin: PluginInfo) => void | Promise<void>,
plugins: PluginDetails[],
compilePlugin: (plugin: PluginDetails) => void | Promise<void>,
) {
// eslint-disable-next-line no-console
console.log('🕵️‍ Watching for plugin changes');
const delayedCompilation: {[key: string]: NodeJS.Timeout | null} = {};
const kCompilationDelayMillis = 1000;
const onPluginChanged = (plugin: PluginInfo) => {
const onPluginChanged = (plugin: PluginDetails) => {
if (!delayedCompilation[plugin.name]) {
delayedCompilation[plugin.name] = setTimeout(() => {
delayedCompilation[plugin.name] = null;
@@ -41,14 +41,14 @@ export default async function startWatchPlugins(
}
async function startWatchingPluginsUsingWatchman(
plugins: PluginInfo[],
onPluginChanged: (plugin: PluginInfo) => void,
plugins: PluginDetails[],
onPluginChanged: (plugin: PluginDetails) => void,
) {
// Initializing a watchman for each folder containing plugins
const watchmanRootMap: {[key: string]: Watchman} = {};
await Promise.all(
plugins.map(async (plugin) => {
const watchmanRoot = path.resolve(plugin.rootDir, '..');
const watchmanRoot = path.resolve(plugin.dir, '..');
if (!watchmanRootMap[watchmanRoot]) {
watchmanRootMap[watchmanRoot] = new Watchman(watchmanRoot);
await watchmanRootMap[watchmanRoot].initialize();
@@ -58,10 +58,10 @@ async function startWatchingPluginsUsingWatchman(
// Start watching plugins using the initialized watchmans
await Promise.all(
plugins.map(async (plugin) => {
const watchmanRoot = path.resolve(plugin.rootDir, '..');
const watchmanRoot = path.resolve(plugin.dir, '..');
const watchman = watchmanRootMap[watchmanRoot];
await watchman.startWatchFiles(
path.relative(watchmanRoot, plugin.rootDir),
path.relative(watchmanRoot, plugin.dir),
() => onPluginChanged(plugin),
{
excludes: ['**/__tests__/**/*', '**/node_modules/**/*', '**/.*'],

View File

@@ -9997,7 +9997,7 @@ recharts@1.7.1:
recharts-scale "^0.4.2"
reduce-css-calc "^1.3.0"
recursive-readdir@2.2.2, recursive-readdir@^2.2.2:
recursive-readdir@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.2.tgz#9946fb3274e1628de6e36b2f6714953b4845094f"
integrity sha512-nRCcW9Sj7NuZwa2XvH9co8NPeXUBhZP7CRKJtU+cS6PW9FpCIFoI5ib0NT1ZrbNuPoRy0ylyCaUL8Gih4LSyFg==