diff --git a/scripts/js-api/build-types/buildGeneratedTypes.js b/scripts/js-api/build-types/buildGeneratedTypes.js index c4572a23ff7..1408b1625b5 100644 --- a/scripts/js-api/build-types/buildGeneratedTypes.js +++ b/scripts/js-api/build-types/buildGeneratedTypes.js @@ -13,7 +13,7 @@ const {ENTRY_POINT, IGNORE_PATTERNS, TYPES_OUTPUT_DIR} = require('../config'); const getRequireStack = require('./resolution/getRequireStack'); const translatedModuleTemplate = require('./templates/translatedModule.d.ts-template'); const translateSourceFile = require('./translateSourceFile'); -const {promises: fs} = require('fs'); +const {promises: fs, watch: fsWatch} = require('fs'); const micromatch = require('micromatch'); const path = require('path'); const {styleText} = require('util'); @@ -28,7 +28,7 @@ const {styleText} = require('util'); * [flow-api-translator](https://www.npmjs.com/package/flow-api-translator) * along with our own pre and post-processing. */ -async function buildGeneratedTypes(): Promise { +async function buildGeneratedTypes(): Promise> { const files = new Set([path.join(REPO_ROOT, ENTRY_POINT)]); const translatedFiles = new Set(); const dependencyEdges: DependencyEdges = []; @@ -83,7 +83,7 @@ async function buildGeneratedTypes(): Promise { ' API translation encountered errors.\n', ); process.exitCode = 1; - return; + return translatedFiles; } const touchedPackages = new Set( @@ -100,6 +100,7 @@ async function buildGeneratedTypes(): Promise { ); } console.log(''); + return translatedFiles; } type DependencyEdges = Array<[string, string]>; @@ -208,4 +209,69 @@ class ModuleTranslationError extends Error { } } -module.exports = buildGeneratedTypes; +async function translateFile(file: string): Promise { + const buildPath = getBuildPath(file); + const source = await fs.readFile(file, 'utf-8'); + const {result: typescriptDef} = await translateSourceFile(source, file); + await fs.mkdir(path.dirname(buildPath), {recursive: true}); + await fs.writeFile( + buildPath, + translatedModuleTemplate({ + originalFileName: path.relative(REPO_ROOT, file), + source: stripDocblock(typescriptDef), + tripleSlashDirectives: extractTripleSlashDirectives(source), + }), + ); +} + +function watchGeneratedTypes(translatedFiles: Set): void { + console.log( + styleText(['bold', 'inverse'], ' Watching for changes... ') + '\n', + ); + + const packageDirs = new Set( + Array.from(translatedFiles).map(file => + path.join( + PACKAGES_DIR, + path.relative(PACKAGES_DIR, file).split(path.sep)[0], + ), + ), + ); + + for (const packageDir of packageDirs) { + fsWatch(packageDir, {recursive: true}, (eventType, filename) => { + if (filename == null) { + return; + } + const filePath = path.join(packageDir, filename); + if (!translatedFiles.has(filePath)) { + return; + } + process.stdout.write( + styleText('dim', 'File changed: ') + + path.relative(REPO_ROOT, filePath) + + '\n', + ); + translateFile(filePath).then( + () => { + process.stdout.write( + styleText('green', ' ✔') + + ' Rebuilt ' + + path.relative(REPO_ROOT, filePath) + + '\n', + ); + }, + (err: Error) => { + process.stderr.write( + styleText('red', ' ✖ Build error: ') + err.message + '\n', + ); + }, + ); + }); + } +} + +module.exports = { + buildGeneratedTypes, + watchGeneratedTypes, +}; diff --git a/scripts/js-api/build-types/index.js b/scripts/js-api/build-types/index.js index 86905c7d274..0c58debfed2 100644 --- a/scripts/js-api/build-types/index.js +++ b/scripts/js-api/build-types/index.js @@ -11,7 +11,10 @@ require('../../shared/babelRegister').registerForScript(); const buildApiSnapshot = require('./buildApiSnapshot'); -const buildGeneratedTypes = require('./buildGeneratedTypes'); +const { + buildGeneratedTypes, + watchGeneratedTypes, +} = require('./buildGeneratedTypes'); const debug = require('debug'); const {parseArgs, styleText} = require('util'); @@ -22,6 +25,7 @@ const config = { help: {type: 'boolean'}, 'skip-snapshot': {type: 'boolean'}, validate: {type: 'boolean'}, + watch: {type: 'boolean'}, }, }; @@ -33,6 +37,7 @@ async function main() { help, 'skip-snapshot': skipSnapshot, validate, + watch, }, /* $FlowFixMe[incompatible-type] Natural Inference rollout. See * https://fburl.com/workplace/6291gfvu */ @@ -52,6 +57,8 @@ async function main() { --skip-snapshot Skip API snapshot generation. --validate Validate if the current API snapshot on disk is up to date. Exits with an error if differences are detected. + --watch Watch for file changes and rebuild incrementally. Skips + API snapshot generation. `); process.exitCode = 0; return; @@ -69,9 +76,11 @@ async function main() { ) + '\n', ); - await buildGeneratedTypes(); + const translatedFiles = await buildGeneratedTypes(); - if (!skipSnapshot) { + if (watch === true) { + watchGeneratedTypes(translatedFiles); + } else if (!skipSnapshot) { console.log( styleText(['bold', 'inverse'], ' Building API snapshot ') + '\n', );