diff --git a/.gitignore b/.gitignore index 53fe182d97201..bf3af85e9eb6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .DS_STORE node_modules +scripts/flow/*/.flowconfig *~ *.pyc .grunt @@ -21,4 +22,4 @@ chrome-user-data *.iml .vscode *.swp -*.swo \ No newline at end of file +*.swo diff --git a/package.json b/package.json index 0d70a61bb127b..520222adcf933 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "linc": "node ./scripts/tasks/linc.js", "lint": "node ./scripts/tasks/eslint.js", "lint-build": "node ./scripts/rollup/validate/index.js", - "postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json", + "postinstall": "node node_modules/fbjs-scripts/node/check-dev-engines.js package.json && node ./scripts/flow/createFlowConfigs.js", "debug-test": "cross-env NODE_ENV=development node --inspect-brk node_modules/.bin/jest --config ./scripts/jest/config.source.js --runInBand", "test": "cross-env NODE_ENV=development jest --config ./scripts/jest/config.source.js", "test-prod": "cross-env NODE_ENV=production jest --config ./scripts/jest/config.source.js", diff --git a/.flowconfig b/scripts/flow/config/flowconfig similarity index 53% rename from .flowconfig rename to scripts/flow/config/flowconfig index 634625671f9f6..6484737582bc5 100644 --- a/.flowconfig +++ b/scripts/flow/config/flowconfig @@ -1,25 +1,26 @@ [ignore] - -/fixtures/.* -/build/.* -/scripts/bench/.* +.*/scripts/bench/.* # These shims are copied into external projects: -/scripts/rollup/shims/facebook-www/.* -/scripts/rollup/shims/react-native/.* +.*/rollup/shims/facebook-www/.* +.*/rollup/shims/react-native/.* -/.*/node_modules/y18n/.* -/node_modules/chrome-devtools-frontend/.* -/node_modules/devtools-timeline-model/.* -/node_modules/create-react-class/.* -/.*/__mocks__/.* -/.*/__tests__/.* +.*/node_modules/y18n/.* +.*/node_modules/chrome-devtools-frontend/.* +.*/node_modules/devtools-timeline-model/.* +.*/node_modules/create-react-class/.* +.*/__mocks__/.* +.*/__tests__/.* [include] +../../../node_modules/ +../../../packages/ +../../../scripts/ [libs] -./node_modules/fbjs/flow/lib/dev.js -./scripts/flow +../../../node_modules/fbjs/flow/lib/dev.js +../environment.js +../react-native-host-hooks.js [lints] untyped-type-import=error @@ -42,4 +43,4 @@ suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError [version] -^0.61.0 +^0.61.0 \ No newline at end of file diff --git a/scripts/flow/createFlowConfigs.js b/scripts/flow/createFlowConfigs.js new file mode 100644 index 0000000000000..ab31dc4cbbbcd --- /dev/null +++ b/scripts/flow/createFlowConfigs.js @@ -0,0 +1,44 @@ +'use strict'; + +const chalk = require('chalk'); +const fs = require('fs'); +const mkdirp = require('mkdirp'); +const {typedRenderers} = require('./typedRenderers'); + +const config = fs.readFileSync(__dirname + '/config/flowconfig'); + +function writeConfig(folder) { + mkdirp.sync(folder); + + const disclaimer = ` +# ---------------------------------------------------------------# +# NOTE: this file is generated. # +# If you want to edit it, open ./scripts/flow/config/flowconfig. # +# Then run Yarn for changes to take effect. # +# ---------------------------------------------------------------# + `.trim(); + + const configFile = folder + '/.flowconfig'; + let oldConfig; + try { + oldConfig = fs.readFileSync(configFile).toString(); + } catch (err) { + oldConfig = null; + } + const newConfig = ` +${disclaimer} +${config} +${disclaimer} +`.trim(); + + if (newConfig !== oldConfig) { + fs.writeFileSync(configFile, newConfig); + console.log(chalk.dim('Wrote a Flow config to ' + configFile)); + } +} + +// Write multiple configs in different folders +// so that we can run those checks in parallel if we want. +typedRenderers.forEach(renderer => { + writeConfig(__dirname + '/' + renderer); +}); diff --git a/scripts/flow/runFlow.js b/scripts/flow/runFlow.js new file mode 100644 index 0000000000000..92c8f20f17ed5 --- /dev/null +++ b/scripts/flow/runFlow.js @@ -0,0 +1,37 @@ +'use strict'; + +const chalk = require('chalk'); +const spawn = require('child_process').spawn; +const extension = process.platform === 'win32' ? '.cmd' : ''; + +require('./createFlowConfigs'); + +async function runFlow(renderer, args) { + return new Promise(resolve => { + console.log( + 'Running Flow for the ' + chalk.cyan(renderer) + ' renderer...', + ); + spawn('../../../node_modules/.bin/flow' + extension, args, { + // Allow colors to pass through: + stdio: 'inherit', + // Use a specific renderer config: + cwd: process.cwd() + '/scripts/flow/' + renderer + '/', + }).on('close', function(code) { + if (code !== 0) { + console.error( + 'Flow failed for the ' + chalk.red(renderer) + ' renderer', + ); + console.log(); + process.exit(code); + } else { + console.log( + 'Flow passed for the ' + chalk.green(renderer) + ' renderer', + ); + console.log(); + resolve(); + } + }); + }); +} + +module.exports = runFlow; diff --git a/scripts/flow/typedRenderers.js b/scripts/flow/typedRenderers.js new file mode 100644 index 0000000000000..ded6a1200dfa1 --- /dev/null +++ b/scripts/flow/typedRenderers.js @@ -0,0 +1,3 @@ +'use strict'; + +exports.typedRenderers = ['dom', 'fabric', 'native', 'test']; diff --git a/scripts/tasks/flow-ci.js b/scripts/tasks/flow-ci.js index d3881e036977e..1d7444eb7ab7d 100644 --- a/scripts/tasks/flow-ci.js +++ b/scripts/tasks/flow-ci.js @@ -7,23 +7,18 @@ 'use strict'; -const path = require('path'); -const spawn = require('child_process').spawn; - -const extension = process.platform === 'win32' ? '.cmd' : ''; +process.on('unhandledRejection', err => { + throw err; +}); -// This script forces a complete check. -// Use it for the CI. +const runFlow = require('../flow/runFlow'); +const {typedRenderers} = require('../flow/typedRenderers'); -spawn(path.join('node_modules', '.bin', 'flow' + extension), ['check', '.'], { - // Allow colors to pass through - stdio: 'inherit', -}).on('close', function(code) { - if (code !== 0) { - console.error('Flow failed'); - } else { - console.log('Flow passed'); +async function checkAll() { + // eslint-disable-next-line no-for-of-loops/no-for-of-loops + for (let renderer of typedRenderers) { + await runFlow(renderer, ['check']); } +} - process.exit(code); -}); +checkAll(); diff --git a/scripts/tasks/flow.js b/scripts/tasks/flow.js index a7b643712b374..4c587432c1b25 100644 --- a/scripts/tasks/flow.js +++ b/scripts/tasks/flow.js @@ -7,23 +7,42 @@ 'use strict'; -const path = require('path'); -const spawn = require('child_process').spawn; +process.on('unhandledRejection', err => { + throw err; +}); -const extension = process.platform === 'win32' ? '.cmd' : ''; +const chalk = require('chalk'); +const runFlow = require('../flow/runFlow'); +const {typedRenderers} = require('../flow/typedRenderers'); // This script is using `flow status` for a quick check with a server. // Use it for local development. -spawn(path.join('node_modules', '.bin', 'flow' + extension), ['status', '.'], { - // Allow colors to pass through - stdio: 'inherit', -}).on('close', function(code) { - if (code !== 0) { - console.error('Flow failed'); - } else { - console.log('Flow passed'); - } +const primaryRenderer = process.argv[2]; +if (typedRenderers.indexOf(primaryRenderer) === -1) { + console.log( + 'The ' + + chalk.red('yarn flow') + + ' command now requires you to pick a primary renderer:' + ); + console.log(); + typedRenderers.forEach(renderer => { + console.log(' * ' + chalk.cyan('yarn flow ' + renderer)); + }); + console.log(); + console.log('If you are not sure, run ' + chalk.green('yarn flow dom') + '.'); + console.log( + 'This will still typecheck non-DOM packages, although less precisely.' + ); + console.log(); + console.log('Note that checks for all renderers will run on CI.'); + console.log( + 'You can also do this locally with ' + + chalk.cyan('yarn flow-ci') + + ' but it will be slow.' + ); + console.log(); + process.exit(1); +} - process.exit(code); -}); +runFlow(primaryRenderer, ['status']);