From dc7226b7dc2fc292a22fe8f3aba7fe49d3d5df93 Mon Sep 17 00:00:00 2001 From: Anton Nikolaev Date: Fri, 9 Apr 2021 05:15:14 -0700 Subject: [PATCH] Script for plugin type-checking (#2172) Summary: Pull Request resolved: https://github.com/facebook/flipper/pull/2172 New script which runs "tsc" for all plugins, receives list of errors and then checks which plugins are affected. It works for shared libs too, e.g. if there is an error in a shared library, then all plugins dependant on it will be counted as affected. For convenience, script saves list of errors affecting each plugin to "tsc-errors.log" in plugin folder. This script will be used for automatic type-checking plugins against current "stable" and "insiders" versions of Flipper. An alternative to this implementation would be to simply run "tsc" for each plugin individually, but such implementation takes a lot of time (5+ sec per plugin) and so cannot be effectively used on diffs. Reviewed By: mweststrate Differential Revision: D27499765 fbshipit-source-id: fcbbfc94a13e6c7c5beff0c889a929f84c41b2dd --- desktop/.gitignore | 1 + desktop/app/src/PluginContainer.tsx | 2 +- desktop/package.json | 3 +- desktop/plugins/public/package.json | 6 +- .../plugins/public/reactdevtools/index.tsx | 2 +- desktop/plugins/tsconfig.json | 20 +++ desktop/scripts/tsc-plugins.ts | 148 ++++++++++++++++++ 7 files changed, 176 insertions(+), 6 deletions(-) create mode 100644 desktop/plugins/tsconfig.json create mode 100644 desktop/scripts/tsc-plugins.ts diff --git a/desktop/.gitignore b/desktop/.gitignore index 1682a65fe..6c3e8c204 100644 --- a/desktop/.gitignore +++ b/desktop/.gitignore @@ -6,3 +6,4 @@ node_modules/ /app/src/defaultPlugins/index.tsx /coverage .env +tsc-error.log diff --git a/desktop/app/src/PluginContainer.tsx b/desktop/app/src/PluginContainer.tsx index a35c13f93..a47bac39b 100644 --- a/desktop/app/src/PluginContainer.tsx +++ b/desktop/app/src/PluginContainer.tsx @@ -49,7 +49,7 @@ import {theme, TrackingScope, _SandyPluginRenderer} from 'flipper-plugin'; import {isDevicePluginDefinition, isSandyPlugin} from './utils/pluginUtils'; import {ContentContainer} from './sandy-chrome/ContentContainer'; import {Alert, Typography} from 'antd'; -import {InstalledPluginDetails} from 'plugin-lib'; +import {InstalledPluginDetails} from 'flipper-plugin-lib'; import semver from 'semver'; import {loadPlugin} from './reducers/pluginManager'; import {produce} from 'immer'; diff --git a/desktop/package.json b/desktop/package.json index e904b3568..0b1a49b1e 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -227,9 +227,10 @@ "dev-server": "cross-env NODE_ENV=development ./ts-node scripts/start-dev-server.ts", "everything": "yarn reset && yarn install && yarn lint && yarn test && yarn test-electron && yarn build --mac --mac-dmg --win --linux --linux-deb && yarn start", "fix": "eslint . --fix --ext .js,.ts,.tsx", - "lint": "yarn lint:eslint && yarn lint:tsc", + "lint": "yarn lint:eslint && yarn lint:tsc && yarn tsc-plugins", "lint:eslint": "eslint . --ext .js,.ts,.tsx", "lint:tsc": "tsc --noemit", + "tsc-plugins": "./ts-node scripts/tsc-plugins.ts", "list-plugins": "./ts-node scripts/list-plugins.ts", "open-dist": "open ../dist/mac/Flipper.app --args --launcher=false --inspect=9229", "postinstall": "patch-package && ./ts-node scripts/gen-type-index.ts && yarn --cwd plugins install --mutex network:30331 && yarn build:tsc && ./ts-node scripts/generate-plugin-entry-points.ts && yarn build:themes", diff --git a/desktop/plugins/public/package.json b/desktop/plugins/public/package.json index f7890d7b4..557f1e95c 100644 --- a/desktop/plugins/public/package.json +++ b/desktop/plugins/public/package.json @@ -7,7 +7,8 @@ "*" ], "nohoist": [ - "flipper-plugin-kaios-big-allocations/firefox-client" + "flipper-plugin-kaios-big-allocations/firefox-client", + "flipper-plugin-kaios-big-allocations/patch-package" ] }, "resolutions": { @@ -19,7 +20,6 @@ "url": "https://fb.workplace.com/groups/flippersupport/" }, "devDependencies": { - "rimraf": "^3.0.2", - "patch-package": "^6.2.0" + "rimraf": "^3.0.2" } } diff --git a/desktop/plugins/public/reactdevtools/index.tsx b/desktop/plugins/public/reactdevtools/index.tsx index 12638360c..15ec1fbbb 100644 --- a/desktop/plugins/public/reactdevtools/index.tsx +++ b/desktop/plugins/public/reactdevtools/index.tsx @@ -77,7 +77,7 @@ const GrabMetroDevice = connect< ReduxState >(({connections: {devices}}) => ({ metroDevice: devices.find( - (device) => device.os === 'Metro' && !device.isArchived, + (device: Device) => device.os === 'Metro' && !device.isArchived, ) as MetroDevice, }))(function ({ metroDevice, diff --git a/desktop/plugins/tsconfig.json b/desktop/plugins/tsconfig.json new file mode 100644 index 000000000..8377012e6 --- /dev/null +++ b/desktop/plugins/tsconfig.json @@ -0,0 +1,20 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "baseUrl": ".", + "rootDir": ".", + "outDir": "lib" + }, + "include": [ + "public/", + "fb/" + ], + "exclude": [ + "**/lib/", + "**/dist/", + "**/node_modules/", + "**/__tests__/", + "**/__test__/" + ] +} diff --git a/desktop/scripts/tsc-plugins.ts b/desktop/scripts/tsc-plugins.ts new file mode 100644 index 000000000..47dbb3b83 --- /dev/null +++ b/desktop/scripts/tsc-plugins.ts @@ -0,0 +1,148 @@ +/** + * 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 {exec} from 'child_process'; +import {EOL} from 'os'; +import pmap from 'p-map'; +import {rootDir} from './paths'; +import yargs from 'yargs'; + +const argv = yargs + .usage('yarn tsc-plugins [args]') + .version(false) + .options({ + dir: { + description: 'Plugins directory name ("plugins" by default)', + type: 'string', + default: 'plugins', + alias: 'd', + }, + }) + .help() + .strict() + .parse(process.argv.slice(1)); + +const pluginsDir = path.join(rootDir, argv.dir); +const fbPluginsDir = path.join(pluginsDir, 'fb'); +const publicPluginsDir = path.join(pluginsDir, 'public'); + +async function tscPlugins(): Promise { + const stdout = await new Promise((resolve) => + exec( + `./node_modules/.bin/tsc -p ./${argv.dir}/tsconfig.json`, + { + cwd: rootDir, + }, + (err, stdout) => { + if (err) { + console.error(err); + resolve(stdout); + } else { + resolve(undefined); + } + }, + ), + ); + if (stdout) { + console.error(stdout); + } + const errors = (stdout?.split(EOL) ?? []).filter((l) => l !== ''); + if (errors.length > 0) { + await findAffectedPlugins(errors); + } + return stdout ? 1 : 0; +} + +async function findAffectedPlugins(errors: string[]) { + const [publicPackages, fbPackages] = await Promise.all([ + fs.readdir(publicPluginsDir), + fs.readdir(fbPluginsDir).catch(() => [] as string[]), + ]); + const allPackages = await pmap( + [ + ...publicPackages.map((p) => path.join(publicPluginsDir, p)), + ...fbPackages.map((p) => path.join(fbPluginsDir, p)), + ], + async (p) => ({ + dir: p, + json: await fs + .readJson(path.join(p, 'package.json')) + .catch(() => undefined), + }), + ).then((dirs) => dirs.filter((dir) => !!dir.json)); + const packageByName = new Map( + allPackages.map((p) => [p.json.name as string, p]), + ); + const depsByName = new Map>(); + function getDependencies(name: string): Set { + if (!depsByName.has(name)) { + const set = new Set(); + const pkg = packageByName.get(name)!; + set.add(name); + const allDeps = Object.keys({ + ...(pkg.json.dependencies ?? {}), + ...(pkg.json.peerDependencies ?? {}), + }); + for (const dep of allDeps) { + if (packageByName.get(dep)) { + const subDeps = getDependencies(dep); + for (const subDep of subDeps) { + set.add(subDep); + } + } + } + depsByName.set(name, set); + } + return depsByName.get(name)!; + } + for (const name of packageByName.keys()) { + depsByName.set(name, getDependencies(name)); + } + for (const pkg of allPackages) { + if (!pkg.json?.keywords?.includes('flipper-plugin')) { + continue; + } + const logFile = path.join(pkg.dir, 'tsc-error.log'); + await fs.remove(logFile); + let logStream: fs.WriteStream | undefined; + for (const dep of depsByName.get(pkg.json.name)!) { + const relativeDir = path.relative(rootDir, packageByName.get(dep)!.dir); + for (const error of errors) { + if (error.startsWith(relativeDir)) { + if (!logStream) { + logStream = fs.createWriteStream(logFile); + console.error( + `Plugin ${path.relative( + rootDir, + pkg.dir, + )} has tsc errors. Check ${path.relative( + rootDir, + logFile, + )} for details.`, + ); + } + logStream.write(error); + logStream.write(EOL); + } + } + } + logStream?.close(); + } +} + +tscPlugins() + .then((code) => { + process.exit(code); + }) + .catch((err: any) => { + console.error(err); + process.exit(1); + });