diff --git a/desktop/flipper-server/src/attachDevServer.tsx b/desktop/flipper-server/src/attachDevServer.tsx index abb12b6b5..15bf5562f 100644 --- a/desktop/flipper-server/src/attachDevServer.tsx +++ b/desktop/flipper-server/src/attachDevServer.tsx @@ -151,16 +151,19 @@ export async function attachDevServer( next(); }); - await startWatchPlugins((changedPlugins: InstalledPluginDetails[]) => { - socket.clients.forEach((client) => { - client.send( - JSON.stringify({ - event: 'plugins-source-updated', - payload: changedPlugins, - }), - ); - }); - }); + await startWatchPlugins( + process.env.FLIPPER_RELEASE_CHANNEL === 'insiders', + (changedPlugins: InstalledPluginDetails[]) => { + socket.clients.forEach((client) => { + client.send( + JSON.stringify({ + event: 'plugins-source-updated', + payload: changedPlugins, + }), + ); + }); + }, + ); console.log('DEV webserver started.'); } diff --git a/desktop/pkg-lib/package.json b/desktop/pkg-lib/package.json index f9753bb15..2ddce172b 100644 --- a/desktop/pkg-lib/package.json +++ b/desktop/pkg-lib/package.json @@ -18,7 +18,8 @@ "metro": "^0.70.2", "metro-cache": "^0.70.2", "metro-minify-terser": "^0.70.2", - "npm-packlist": "^4.0.0" + "npm-packlist": "^4.0.0", + "p-map": "^4" }, "devDependencies": { "@types/fs-extra": "^9.0.13", diff --git a/desktop/pkg-lib/src/buildDefaultPlugins.tsx b/desktop/pkg-lib/src/buildDefaultPlugins.tsx new file mode 100644 index 000000000..9b7400381 --- /dev/null +++ b/desktop/pkg-lib/src/buildDefaultPlugins.tsx @@ -0,0 +1,54 @@ +/** + * Copyright (c) Meta Platforms, Inc. and 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 {InstalledPluginDetails} from 'flipper-common'; +import pMap from 'p-map'; +import path from 'path'; +import fs from 'fs-extra'; +import runBuild from './runBuild'; + +const defaultPluginsDir = path.join(__dirname, '../../static/defaultPlugins'); + +export async function buildDefaultPlugins( + defaultPlugins: InstalledPluginDetails[], + dev: boolean, +) { + if (process.env.FLIPPER_NO_REBUILD_PLUGINS) { + console.log( + `⚙️ Including ${ + defaultPlugins.length + } plugins into the default plugins list. Skipping rebuilding because "no-rebuild-plugins" option provided. List of default plugins: ${defaultPlugins + .map((p) => p.id) + .join(', ')}`, + ); + } + await pMap( + defaultPlugins, + async function (plugin) { + try { + if (!process.env.FLIPPER_NO_REBUILD_PLUGINS) { + console.log( + `⚙️ Building plugin ${plugin.id} to include it into the default plugins list...`, + ); + await runBuild(plugin.dir, dev); + } + await fs.ensureSymlink( + plugin.dir, + path.join(defaultPluginsDir, plugin.name), + 'junction', + ); + } catch (err) { + console.error(`✖ Failed to build plugin ${plugin.id}`, err); + } + }, + { + concurrency: 16, + }, + ); +} diff --git a/desktop/pkg-lib/src/getDefaultPlugins.tsx b/desktop/pkg-lib/src/getDefaultPlugins.tsx new file mode 100644 index 000000000..e84defa30 --- /dev/null +++ b/desktop/pkg-lib/src/getDefaultPlugins.tsx @@ -0,0 +1,43 @@ +/** + * Copyright (c) Meta Platforms, Inc. and 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 {getSourcePlugins} from 'flipper-plugin-lib'; + +// For insiders builds we bundle top 5 popular device plugins, +// plus top 10 popular "universal" plugins enabled by more than 100 users. +const hardcodedPlugins = new Set([ + // Popular device plugins + 'DeviceLogs', + 'CrashReporter', + 'MobileBuilds', + 'Hermesdebuggerrn', + 'React', + // Popular client plugins + 'Inspector', + 'Network', + 'AnalyticsLogging', + 'GraphQL', + 'UIPerf', + 'MobileConfig', + 'Databases', + 'FunnelLogger', + 'Navigation', + 'Fresco', + 'Preferences', +]); + +export const getDefaultPlugins = async (isInsidersBuild: boolean) => { + const sourcePlugins = await getSourcePlugins(); + const defaultPlugins = sourcePlugins + // we only include headless plugins and a predefined set of regular plugins into insiders release + .filter( + (p) => !isInsidersBuild || hardcodedPlugins.has(p.id) || p.headless, + ); + return defaultPlugins; +}; diff --git a/desktop/pkg-lib/src/index.tsx b/desktop/pkg-lib/src/index.tsx index 813ee02c6..8102e05ae 100644 --- a/desktop/pkg-lib/src/index.tsx +++ b/desktop/pkg-lib/src/index.tsx @@ -13,3 +13,5 @@ export {default as computePackageChecksum} from './computePackageChecksum'; export {default as stripSourceMapComment} from './stripSourceMap'; export {default as startWatchPlugins} from './startWatchPlugins'; export {default as Watchman} from './watchman'; +export {buildDefaultPlugins} from './buildDefaultPlugins'; +export {getDefaultPlugins} from './getDefaultPlugins'; diff --git a/desktop/pkg-lib/src/startWatchPlugins.tsx b/desktop/pkg-lib/src/startWatchPlugins.tsx index ca67e3183..5d3558f6c 100644 --- a/desktop/pkg-lib/src/startWatchPlugins.tsx +++ b/desktop/pkg-lib/src/startWatchPlugins.tsx @@ -17,6 +17,8 @@ import path from 'path'; import chalk from 'chalk'; import runBuild from './runBuild'; import {InstalledPluginDetails} from 'flipper-common'; +import {getDefaultPlugins} from './getDefaultPlugins'; +import {buildDefaultPlugins} from './buildDefaultPlugins'; async function rebuildPlugin(pluginPath: string) { try { @@ -33,6 +35,7 @@ async function rebuildPlugin(pluginPath: string) { } export default async function startWatchPlugins( + isInsidersBuild: boolean, onChanged?: ( changedPlugins: InstalledPluginDetails[], ) => void | Promise, @@ -48,36 +51,46 @@ export default async function startWatchPlugins( delayedCompilation = undefined; // eslint-disable-next-line no-console console.log(`🕵️‍ Detected plugin change`); - const changedDirs = await Promise.all( - // https://facebook.github.io/watchman/docs/nodejs.html#subscribing-to-changes - files.map(async (file: string) => { - const filePathAbs = path.resolve(root, file); - let dirPath = path.dirname(filePathAbs); - while ( - // Stop when we reach plugin root - !(await isPluginDir(dirPath)) - ) { - const relative = path.relative(root, dirPath); - // Stop when we reach desktop/plugins folder - if (!relative || relative.startsWith('..')) { - console.warn( - chalk.yellow('Failed to find a plugin root for path'), - filePathAbs, - ); - return; + try { + const changedDirs = await Promise.all( + // https://facebook.github.io/watchman/docs/nodejs.html#subscribing-to-changes + files.map(async (file: string) => { + const filePathAbs = path.resolve(root, file); + let dirPath = path.dirname(filePathAbs); + while ( + // Stop when we reach plugin root + !(await isPluginDir(dirPath)) + ) { + const relative = path.relative(root, dirPath); + // Stop when we reach desktop/plugins folder + if (!relative || relative.startsWith('..')) { + console.info( + chalk.yellow( + 'Failed to find a plugin root for path. Rebuilding all plugins', + ), + filePathAbs, + ); + throw new Error('REBUILD_ALL'); + } + dirPath = path.resolve(dirPath, '..'); } - dirPath = path.resolve(dirPath, '..'); - } - await rebuildPlugin(dirPath); - return dirPath; - }), - ); - const changedPlugins = await Promise.all( - changedDirs - .filter((dirPath): dirPath is string => !!dirPath) - .map((dirPath) => getInstalledPluginDetails(dirPath)), - ); - onChanged?.(changedPlugins); + await rebuildPlugin(dirPath); + return dirPath; + }), + ); + const changedPlugins = await Promise.all( + changedDirs.map((dirPath) => getInstalledPluginDetails(dirPath)), + ); + onChanged?.(changedPlugins); + } catch (e) { + if (e instanceof Error && e.message === 'REBUILD_ALL') { + const defaultPlugins = await getDefaultPlugins(isInsidersBuild); + await buildDefaultPlugins(defaultPlugins, true); + onChanged?.(defaultPlugins); + return; + } + throw e; + } }, kCompilationDelayMillis); } }; diff --git a/desktop/scripts/build-utils.tsx b/desktop/scripts/build-utils.tsx index 16db7ab6d..6c06094e0 100644 --- a/desktop/scripts/build-utils.tsx +++ b/desktop/scripts/build-utils.tsx @@ -18,13 +18,13 @@ import path from 'path'; import fs from 'fs-extra'; import {spawn, exec} from 'promisify-child-process'; import { + buildDefaultPlugins, + getDefaultPlugins, getWatchFolders, - runBuild, stripSourceMapComment, } from 'flipper-pkg-lib'; import getAppWatchFolders from './get-app-watch-folders'; -import {getSourcePlugins, getPluginSourceFolders} from 'flipper-plugin-lib'; -import type {InstalledPluginDetails} from 'flipper-common'; +import {getPluginSourceFolders} from 'flipper-plugin-lib'; import { appDir, staticDir, @@ -36,34 +36,9 @@ import { } from './paths'; import pFilter from 'p-filter'; import child from 'child_process'; -import pMap from 'p-map'; -import chalk from 'chalk'; const dev = process.env.NODE_ENV !== 'production'; -// For insiders builds we bundle top 5 popular device plugins, -// plus top 10 popular "universal" plugins enabled by more than 100 users. -const hardcodedPlugins = new Set([ - // Popular device plugins - 'DeviceLogs', - 'CrashReporter', - 'MobileBuilds', - 'Hermesdebuggerrn', - 'React', - // Popular client plugins - 'Inspector', - 'Network', - 'AnalyticsLogging', - 'GraphQL', - 'UIPerf', - 'MobileConfig', - 'Databases', - 'FunnelLogger', - 'Navigation', - 'Fresco', - 'Preferences', -]); - export function die(err: Error) { console.error('Script termnated.', err); process.exit(1); @@ -87,52 +62,12 @@ export async function prepareDefaultPlugins(isInsidersBuild: boolean = false) { }); console.log('✅ Copied the provided default plugins dir.'); } else { - const sourcePlugins = await getSourcePlugins(); - const defaultPlugins = sourcePlugins - // we only include headless plugins and a predefined set of regular plugins into insiders release - .filter( - (p) => !isInsidersBuild || hardcodedPlugins.has(p.id) || p.headless, - ); - await buildDefaultPlugins(defaultPlugins); + const defaultPlugins = await getDefaultPlugins(isInsidersBuild); + await buildDefaultPlugins(defaultPlugins, dev); } console.log('✅ Prepared default plugins.'); } -async function buildDefaultPlugins(defaultPlugins: InstalledPluginDetails[]) { - if (process.env.FLIPPER_NO_REBUILD_PLUGINS) { - console.log( - `⚙️ Including ${ - defaultPlugins.length - } plugins into the default plugins list. Skipping rebuilding because "no-rebuild-plugins" option provided. List of default plugins: ${defaultPlugins - .map((p) => p.id) - .join(', ')}`, - ); - } - await pMap( - defaultPlugins, - async function (plugin) { - try { - if (!process.env.FLIPPER_NO_REBUILD_PLUGINS) { - console.log( - `⚙️ Building plugin ${plugin.id} to include it into the default plugins list...`, - ); - await runBuild(plugin.dir, dev); - } - await fs.ensureSymlink( - plugin.dir, - path.join(defaultPluginsDir, plugin.name), - 'junction', - ); - } catch (err) { - console.error(`✖ Failed to build plugin ${plugin.id}`, err); - } - }, - { - concurrency: 16, - }, - ); -} - const minifierConfig = { minifierPath: require.resolve('metro-minify-terser'), minifierConfig: { diff --git a/desktop/scripts/start-dev-server.tsx b/desktop/scripts/start-dev-server.tsx index 68cf88c19..543f1767b 100644 --- a/desktop/scripts/start-dev-server.tsx +++ b/desktop/scripts/start-dev-server.tsx @@ -403,10 +403,9 @@ function checkDevServer() { } (async () => { + const isInsidersBuild = process.env.FLIPPER_RELEASE_CHANNEL === 'insiders'; checkDevServer(); - await prepareDefaultPlugins( - process.env.FLIPPER_RELEASE_CHANNEL === 'insiders', - ); + await prepareDefaultPlugins(isInsidersBuild); await ensurePluginFoldersWatchable(); const port = await detect(DEFAULT_PORT); const {app, server} = await startAssetServer(port); @@ -414,7 +413,7 @@ function checkDevServer() { await startMetroServer(app, server); outputScreen(socket); await compileMain(); - await startWatchPlugins((changedPlugins) => { + await startWatchPlugins(isInsidersBuild, (changedPlugins) => { socket.emit('plugins-source-updated', changedPlugins); }); if (dotenv && dotenv.parsed) { diff --git a/desktop/yarn.lock b/desktop/yarn.lock index 6612efa69..136a6fbca 100644 --- a/desktop/yarn.lock +++ b/desktop/yarn.lock @@ -11293,7 +11293,7 @@ p-map@^2.0.0: resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== -p-map@^4.0.0: +p-map@^4, p-map@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==