desktop/scripts/tsc-plugins.tsx (134 lines of code) (raw):

/** * 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 */ /* eslint-disable flipper/no-console-error-without-context */ 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'; import {isPluginJson} from 'flipper-common'; 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() .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<number> { const stdout = await new Promise<string | undefined>((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<string, Set<string>>(); function getDependencies(name: string): Set<string> { if (!depsByName.has(name)) { const set = new Set<string>(); 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 (!isPluginJson(pkg.json)) { 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); });