From 10ec304283353ad05bdb6daee1c63de47d11e87c Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Sun, 4 Dec 2016 19:23:23 +0100 Subject: [PATCH 01/74] Setup a plugin to run a flow checking lifecycle This will only run if a @flow annotation is seen: - At the first @flow file found: - Write a .flowconfig if none exists - [Not done yet] Run flow-typed install - [Not done yet] Start a flow instance - When a file with an @flow comment changes during a compilation: - [Not done yet] Run a flow check - If there are some errors in flow: - If CI: output as an error - If not CI: output as a warning --- .../react-dev-utils/FlowTypecheckPlugin.js | 83 +++++++++++++++++++ packages/react-dev-utils/package.json | 1 + .../config/webpack.config.dev.js | 13 ++- 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 packages/react-dev-utils/FlowTypecheckPlugin.js diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js new file mode 100644 index 00000000000..ff4d8bd3500 --- /dev/null +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -0,0 +1,83 @@ +var fs = require('fs'); +var path = require('path'); + +function FlowTypecheckPlugin(options) { + this.options = options || {}; + // If flow is globally present in the project, this will stay across compilations + this._flowInitialized = false; + // If flow should run in a current compilation + this._flowShouldRun = false; + // Stores the last flow output + this._flowOutput = ""; +} + +FlowTypecheckPlugin.prototype.apply = function(compiler) { + compiler.plugin('compilation', function(compilation, params) { + // Detect the presence of flow and initialize it + compilation.plugin('normal-module-loader', function(loaderContext, module) { + // We're only checking the presence of flow in non-node_modules + // (some dependencies may keep their flow comments, we don't want to match them) + if (module.resource.indexOf("node_modules") < 0) { + // We use webpack's cached FileSystem to avoid slowing down compilation + loaderContext.fs.readFile(module.resource, function(err, data) { + if (data && data.toString().indexOf('@flow') >= 0) { + if (!this._flowInitialized) { + this._initializeFlow(compiler.options.context); + this._flowInitialized = true; + } + this._flowShouldRun = true; + } + }.bind(this)); + } + }.bind(this)) + }.bind(this)); + + // While emitting run a flow check if flow has been detected + compiler.plugin('emit', function(compilation, callback) { + // Only if a file with @ flow has been changed + if (this._flowShouldRun) { + this._flowOutput = this._flowCheck(); + this._flowShouldRun = false; + } + if (this._flowOutput.length > 0) { + // In a CI, we wish to get flow breaking the build so we write errors here + if (process.env.CI) { + compilation.errors.push(this._flowOutput); + } else { + compilation.warnings.push(this._flowOutput); + } + } + callback(); + }.bind(this)); +}; + +// This initializer will run once per webpack run (runs once across all compilations) +FlowTypecheckPlugin.prototype._initializeFlow = function(projectPath) { + const flowconfigPath = path.join(projectPath, '.flowconfig'); + fs.exists(flowconfigPath, function(exists) { + if (!exists) { + fs.writeFile(flowconfigPath, (this.options.flowconfig || []).join('\n')); + } + }.bind(this)); + // TODO: run flow-typed + // TODO: start a flow instance +}; + +// This check will run each time a compilation sees a file with @ flow change +FlowTypecheckPlugin.prototype._flowCheck = function() { + // TODO: run a single flow check + return ` +src/App.js:11 + 11: f("abc"); + ^^^^^^^^ function call + 11: f("abc"); + ^^^^^ string. This type is incompatible with + 7: function f(x: number) { + ^^^^^^ number + + +Found 1 error + `; +}; + +module.exports = FlowTypecheckPlugin; diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index b7660595454..34aad38931f 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -12,6 +12,7 @@ }, "files": [ "clearConsole.js", + "FlowTypecheckPlugin.js", "checkRequiredFiles.js", "formatWebpackMessages.js", "getProcessForPort.js", diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index 96fd632b795..97fdb25913d 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -15,6 +15,7 @@ var HtmlWebpackPlugin = require('html-webpack-plugin'); var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin'); var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); var WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); +var FlowTypecheckPlugin = require('react-dev-utils/FlowTypecheckPlugin'); var getClientEnvironment = require('./env'); var paths = require('./paths'); @@ -226,7 +227,17 @@ module.exports = { // to restart the development server for Webpack to discover it. This plugin // makes the discovery automatic so you don't have to restart. // See https://github.com/facebookincubator/create-react-app/issues/186 - new WatchMissingNodeModulesPlugin(paths.appNodeModules) + new WatchMissingNodeModulesPlugin(paths.appNodeModules), + // Trigger some typechecking if a file matches with an @ flow comment + new FlowTypecheckPlugin({ + flowconfig: [ + '[ignore]', + '/node_modules/fbjs/.*', + '', + '[libs]', + './flow-typed' + ] + }) ], // Some libraries import Node modules but don't use them in the browser. // Tell Webpack to provide empty mocks for them so importing them works. From c7eacf89622f782df6698264d4fe350ece332295 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Sun, 4 Dec 2016 19:35:16 +0100 Subject: [PATCH 02/74] Make flow fail on CI builds We don't need complex logic with process.env on the plugin side, we let the build script figure this out! --- packages/react-dev-utils/FlowTypecheckPlugin.js | 8 ++------ packages/react-scripts/config/webpack.config.prod.js | 10 +++++++++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index ff4d8bd3500..e815d7b14ed 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -39,13 +39,9 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { this._flowOutput = this._flowCheck(); this._flowShouldRun = false; } + // Output a warning if flow failed if (this._flowOutput.length > 0) { - // In a CI, we wish to get flow breaking the build so we write errors here - if (process.env.CI) { - compilation.errors.push(this._flowOutput); - } else { - compilation.warnings.push(this._flowOutput); - } + compilation.warnings.push(this._flowOutput); } callback(); }.bind(this)); diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 058db0d7921..82d6eb318f1 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -15,6 +15,8 @@ var HtmlWebpackPlugin = require('html-webpack-plugin'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); var ManifestPlugin = require('webpack-manifest-plugin'); var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); +var SubresourceIntegrityPlugin = require('webpack-subresource-integrity'); +var FlowTypecheckPlugin = require('react-dev-utils/FlowTypecheckPlugin'); var url = require('url'); var paths = require('./paths'); var getClientEnvironment = require('./env'); @@ -268,7 +270,13 @@ module.exports = { // having to parse `index.html`. new ManifestPlugin({ fileName: 'asset-manifest.json' - }) + }), + // Generate and inject subresources hashes in the final `index.html`. + new SubresourceIntegrityPlugin({ + hashFuncNames: ['sha256', 'sha384'] + }), + // Run Flow only if we see some @ flow annotations, will error on CI + new FlowTypecheckPlugin({}) ], // Some libraries import Node modules but don't use them in the browser. // Tell Webpack to provide empty mocks for them so importing them works. From b0c39e7a1a2e0a26dc21a80b3f7184be4c357809 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Sun, 4 Dec 2016 22:05:36 +0100 Subject: [PATCH 03/74] Run flow as a server and check with status --- .../react-dev-utils/FlowTypecheckPlugin.js | 80 ++++++++++++++----- packages/react-dev-utils/package.json | 1 + .../config/webpack.config.dev.js | 3 +- 3 files changed, 62 insertions(+), 22 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index e815d7b14ed..6dc631aa7e9 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -1,5 +1,7 @@ var fs = require('fs'); var path = require('path'); +var flowBinPath = require('flow-bin'); +var childProcess = require('child_process'); function FlowTypecheckPlugin(options) { this.options = options || {}; @@ -9,6 +11,8 @@ function FlowTypecheckPlugin(options) { this._flowShouldRun = false; // Stores the last flow output this._flowOutput = ""; + // Flow server process + this._flowServer = null; } FlowTypecheckPlugin.prototype.apply = function(compiler) { @@ -36,14 +40,27 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { compiler.plugin('emit', function(compilation, callback) { // Only if a file with @ flow has been changed if (this._flowShouldRun) { - this._flowOutput = this._flowCheck(); this._flowShouldRun = false; + var version = this.options.flowVersion || 'latest'; + this._flowCheck(compiler.options.context, version, function(err, flowOutput) { + if (err) { + compilation.errors.push(err.message); + return callback(); + } + this._flowOutput = flowOutput; + // Output a warning if flow failed + if (this._flowOutput.indexOf('No errors!') < 0) { + compilation.warnings.push(this._flowOutput); + } + callback(); + }.bind(this)); + } else { + // Output a warning if flow failed in a previous run + if (this._flowOutput.length > 0 && this._flowOutput.indexOf('No errors!') < 0) { + compilation.warnings.push(this._flowOutput); + } + callback(); } - // Output a warning if flow failed - if (this._flowOutput.length > 0) { - compilation.warnings.push(this._flowOutput); - } - callback(); }.bind(this)); }; @@ -56,24 +73,45 @@ FlowTypecheckPlugin.prototype._initializeFlow = function(projectPath) { } }.bind(this)); // TODO: run flow-typed - // TODO: start a flow instance + this._flowServer = childProcess.spawn( + flowBinPath, + ['server'], + { cwd: projectPath } + ); }; // This check will run each time a compilation sees a file with @ flow change -FlowTypecheckPlugin.prototype._flowCheck = function() { - // TODO: run a single flow check - return ` -src/App.js:11 - 11: f("abc"); - ^^^^^^^^ function call - 11: f("abc"); - ^^^^^ string. This type is incompatible with - 7: function f(x: number) { - ^^^^^^ number - - -Found 1 error - `; +FlowTypecheckPlugin.prototype._flowCheck = function(projectPath, flowVersion, cb) { + var flowOutput = ""; + var flowErrOutput = ""; + var statusCheck = childProcess.spawn( + flowBinPath, + ['status', '--no-auto-start', '--color=always'], + { cwd: projectPath } + ); + statusCheck.stdout.on('data', function(chunk) { + flowOutput += chunk.toString(); + }); + statusCheck.stderr.on('data', function(chunk) { + flowErrOutput += chunk.toString(); + }); + statusCheck.on('close', function() { + if (flowErrOutput.length > 0) { + if (flowErrOutput.indexOf("There is no Flow server running") >= 0) { + return cb(new Error( + 'flow server: unexpectedly died.\n' + + 'This is likely due to a version mismatch between a global flow ' + + 'server (that you or your IDE may try to run) and react-script\'s ' + + 'flow server.\n' + + 'You should run: \n' + + 'npm install -g flow-bin@' + flowVersion + )); + } else if(flowErrOutput.indexOf("still initializing") < 0) { + return cb(new Error(flowErrOutput)); + } + } + cb(null, flowOutput); + }); }; module.exports = FlowTypecheckPlugin; diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index 34aad38931f..063c87153d4 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -27,6 +27,7 @@ "ansi-html": "0.0.5", "chalk": "1.1.3", "escape-string-regexp": "1.0.5", + "flow-bin": "^0.36.0", "html-entities": "1.2.0", "opn": "4.0.2", "sockjs-client": "1.0.3", diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index 97fdb25913d..20f46521887 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -236,7 +236,8 @@ module.exports = { '', '[libs]', './flow-typed' - ] + ], + flowVersion: "0.36.0", }) ], // Some libraries import Node modules but don't use them in the browser. From de16269da97a9f19a93ab3d1c01708d6d41f9e8a Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Sun, 4 Dec 2016 22:20:25 +0100 Subject: [PATCH 04/74] Add flow ignore comment to the start hints --- packages/react-scripts/scripts/start.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index 3e71e2de886..0e11ef0321f 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -131,6 +131,7 @@ function setupCompiler(host, port, protocol) { console.log('You may use special comments to disable some warnings.'); console.log('Use ' + chalk.yellow('// eslint-disable-next-line') + ' to ignore the next line.'); console.log('Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.'); + console.log('Use ' + chalk.yellow('// $FlowFixMe') + ' to ignore flow-related warnings on the next line.'); } }); } From 31b2e042574e31e4d6e0fac2d3ec622bbf6638be Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Sun, 4 Dec 2016 23:11:52 +0100 Subject: [PATCH 05/74] Add flow-typed resolution at start --- .../react-dev-utils/FlowTypecheckPlugin.js | 47 ++++++++++++++----- packages/react-dev-utils/package.json | 1 + .../config/webpack.config.dev.js | 3 ++ .../config/webpack.config.prod.js | 7 ++- 4 files changed, 46 insertions(+), 12 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 6dc631aa7e9..d5042538449 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -1,7 +1,8 @@ var fs = require('fs'); var path = require('path'); -var flowBinPath = require('flow-bin'); var childProcess = require('child_process'); +var flowBinPath = require('flow-bin'); +const flowTypedPath = path.join(__dirname, 'node_modules', '.bin', 'flow-typed'); function FlowTypecheckPlugin(options) { this.options = options || {}; @@ -10,12 +11,14 @@ function FlowTypecheckPlugin(options) { // If flow should run in a current compilation this._flowShouldRun = false; // Stores the last flow output - this._flowOutput = ""; + this._flowOutput = ''; // Flow server process this._flowServer = null; + this._flowServerStderr = ''; } FlowTypecheckPlugin.prototype.apply = function(compiler) { + var version = this.options.flowVersion || 'latest'; compiler.plugin('compilation', function(compilation, params) { // Detect the presence of flow and initialize it compilation.plugin('normal-module-loader', function(loaderContext, module) { @@ -26,7 +29,7 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { loaderContext.fs.readFile(module.resource, function(err, data) { if (data && data.toString().indexOf('@flow') >= 0) { if (!this._flowInitialized) { - this._initializeFlow(compiler.options.context); + this._initializeFlow(compiler.options.context, version); this._flowInitialized = true; } this._flowShouldRun = true; @@ -41,7 +44,6 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { // Only if a file with @ flow has been changed if (this._flowShouldRun) { this._flowShouldRun = false; - var version = this.options.flowVersion || 'latest'; this._flowCheck(compiler.options.context, version, function(err, flowOutput) { if (err) { compilation.errors.push(err.message); @@ -65,19 +67,40 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { }; // This initializer will run once per webpack run (runs once across all compilations) -FlowTypecheckPlugin.prototype._initializeFlow = function(projectPath) { +FlowTypecheckPlugin.prototype._initializeFlow = function(projectPath, flowVersion) { const flowconfigPath = path.join(projectPath, '.flowconfig'); fs.exists(flowconfigPath, function(exists) { if (!exists) { fs.writeFile(flowconfigPath, (this.options.flowconfig || []).join('\n')); } }.bind(this)); - // TODO: run flow-typed - this._flowServer = childProcess.spawn( - flowBinPath, - ['server'], + childProcess.exec( + flowTypedPath + ' install --overwrite --flowVersion=' + flowVersion, { cwd: projectPath } ); + var otherTypeDefs = this.options.otherFlowTypedDefs; + Object.keys(otherTypeDefs || {}).forEach(function(packageName) { + childProcess.exec( + flowTypedPath + ' install ' + packageName + '@' + otherTypeDefs[packageName] + ' --overwrite --flowVersion=' + flowVersion, + { cwd: projectPath } + ); + }) + function spawnServer() { + this._flowServer = childProcess.spawn( + flowBinPath, + ['server'], + { cwd: projectPath } + ); + this._flowServer.stderr.on('data', function(chunk) { + this._flowServerStderr += chunk.toString(); + }.bind(this)); + this._flowServer.on('exit', function() { + if (this._flowServerStderr.indexOf('Lib files changed')) { + spawnServer(); + } + }.bind(this)); + }; + spawnServer.call(this); }; // This check will run each time a compilation sees a file with @ flow change @@ -99,7 +122,9 @@ FlowTypecheckPlugin.prototype._flowCheck = function(projectPath, flowVersion, cb if (flowErrOutput.length > 0) { if (flowErrOutput.indexOf("There is no Flow server running") >= 0) { return cb(new Error( - 'flow server: unexpectedly died.\n' + + 'flow server: unexpectedly died.\n\n' + + this._flowServerStderr + + '\n' + 'This is likely due to a version mismatch between a global flow ' + 'server (that you or your IDE may try to run) and react-script\'s ' + 'flow server.\n' + @@ -111,7 +136,7 @@ FlowTypecheckPlugin.prototype._flowCheck = function(projectPath, flowVersion, cb } } cb(null, flowOutput); - }); + }.bind(this)); }; module.exports = FlowTypecheckPlugin; diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index 063c87153d4..89776f48207 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -28,6 +28,7 @@ "chalk": "1.1.3", "escape-string-regexp": "1.0.5", "flow-bin": "^0.36.0", + "flow-typed": "^2.0.0", "html-entities": "1.2.0", "opn": "4.0.2", "sockjs-client": "1.0.3", diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index 20f46521887..d53fd975433 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -238,6 +238,9 @@ module.exports = { './flow-typed' ], flowVersion: "0.36.0", + otherFlowTypedDefs: { + jest: "17.0.0" + } }) ], // Some libraries import Node modules but don't use them in the browser. diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 82d6eb318f1..5413019d0a8 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -276,7 +276,12 @@ module.exports = { hashFuncNames: ['sha256', 'sha384'] }), // Run Flow only if we see some @ flow annotations, will error on CI - new FlowTypecheckPlugin({}) + new FlowTypecheckPlugin({ + flowVersion: "0.36.0", + otherFlowTypedDefs: { + jest: "17.0.0" + } + }) ], // Some libraries import Node modules but don't use them in the browser. // Tell Webpack to provide empty mocks for them so importing them works. From d4763a5301a35d36bf8bcd71896b816d8467a010 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Sun, 4 Dec 2016 23:12:36 +0100 Subject: [PATCH 06/74] Reset server stderr on restart --- packages/react-dev-utils/FlowTypecheckPlugin.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index d5042538449..5435607bd3e 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -96,6 +96,7 @@ FlowTypecheckPlugin.prototype._initializeFlow = function(projectPath, flowVersio }.bind(this)); this._flowServer.on('exit', function() { if (this._flowServerStderr.indexOf('Lib files changed')) { + this._flowServerStderr = ""; spawnServer(); } }.bind(this)); From 2f34a673795b742c341494592cb7837f3f5fb859 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Sun, 4 Dec 2016 23:35:05 +0100 Subject: [PATCH 07/74] Move options as explicit props of the plugin --- .../react-dev-utils/FlowTypecheckPlugin.js | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 5435607bd3e..fe5fe3db985 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -5,7 +5,15 @@ var flowBinPath = require('flow-bin'); const flowTypedPath = path.join(__dirname, 'node_modules', '.bin', 'flow-typed'); function FlowTypecheckPlugin(options) { - this.options = options || {}; + options = options || {}; + // The flow-bin version + this.flowVersion = options.flowVersion || 'latest'; + // Contents of the generated .flowconfig if it doesn't exist + this.flowconfig = options.flowconfig || []; + // Load up other flow-typed defs outside of the package.json (implicit packages behind react-scripts) + // Key is the package name, value is the version number + this.otherFlowTypedDefs = options.otherFlowTypedDefs || {}; + // If flow is globally present in the project, this will stay across compilations this._flowInitialized = false; // If flow should run in a current compilation @@ -18,7 +26,6 @@ function FlowTypecheckPlugin(options) { } FlowTypecheckPlugin.prototype.apply = function(compiler) { - var version = this.options.flowVersion || 'latest'; compiler.plugin('compilation', function(compilation, params) { // Detect the presence of flow and initialize it compilation.plugin('normal-module-loader', function(loaderContext, module) { @@ -29,7 +36,7 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { loaderContext.fs.readFile(module.resource, function(err, data) { if (data && data.toString().indexOf('@flow') >= 0) { if (!this._flowInitialized) { - this._initializeFlow(compiler.options.context, version); + this._initializeFlow(compiler.options.context, this.flowVersion); this._flowInitialized = true; } this._flowShouldRun = true; @@ -44,7 +51,7 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { // Only if a file with @ flow has been changed if (this._flowShouldRun) { this._flowShouldRun = false; - this._flowCheck(compiler.options.context, version, function(err, flowOutput) { + this._flowCheck(compiler.options.context, this.flowVersion, function(err, flowOutput) { if (err) { compilation.errors.push(err.message); return callback(); @@ -71,17 +78,16 @@ FlowTypecheckPlugin.prototype._initializeFlow = function(projectPath, flowVersio const flowconfigPath = path.join(projectPath, '.flowconfig'); fs.exists(flowconfigPath, function(exists) { if (!exists) { - fs.writeFile(flowconfigPath, (this.options.flowconfig || []).join('\n')); + fs.writeFile(flowconfigPath, this.flowconfig.join('\n')); } }.bind(this)); childProcess.exec( flowTypedPath + ' install --overwrite --flowVersion=' + flowVersion, { cwd: projectPath } ); - var otherTypeDefs = this.options.otherFlowTypedDefs; - Object.keys(otherTypeDefs || {}).forEach(function(packageName) { + Object.keys(this.otherFlowTypedDefs).forEach(function(packageName) { childProcess.exec( - flowTypedPath + ' install ' + packageName + '@' + otherTypeDefs[packageName] + ' --overwrite --flowVersion=' + flowVersion, + flowTypedPath + ' install ' + packageName + '@' + this.otherFlowTypedDefs[packageName] + ' --overwrite --flowVersion=' + flowVersion, { cwd: projectPath } ); }) From 79b3b8641a01e9200a28849a8419a427d6f52117 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Sun, 4 Dec 2016 23:40:34 +0100 Subject: [PATCH 08/74] Use arrow functions --- .../react-dev-utils/FlowTypecheckPlugin.js | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index fe5fe3db985..62f9110492c 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -26,14 +26,14 @@ function FlowTypecheckPlugin(options) { } FlowTypecheckPlugin.prototype.apply = function(compiler) { - compiler.plugin('compilation', function(compilation, params) { + compiler.plugin('compilation', (compilation, params) => { // Detect the presence of flow and initialize it - compilation.plugin('normal-module-loader', function(loaderContext, module) { + compilation.plugin('normal-module-loader', (loaderContext, module) => { // We're only checking the presence of flow in non-node_modules // (some dependencies may keep their flow comments, we don't want to match them) if (module.resource.indexOf("node_modules") < 0) { // We use webpack's cached FileSystem to avoid slowing down compilation - loaderContext.fs.readFile(module.resource, function(err, data) { + loaderContext.fs.readFile(module.resource, (err, data) => { if (data && data.toString().indexOf('@flow') >= 0) { if (!this._flowInitialized) { this._initializeFlow(compiler.options.context, this.flowVersion); @@ -41,17 +41,17 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { } this._flowShouldRun = true; } - }.bind(this)); + }); } - }.bind(this)) - }.bind(this)); + }) + }); // While emitting run a flow check if flow has been detected - compiler.plugin('emit', function(compilation, callback) { + compiler.plugin('emit', (compilation, callback) => { // Only if a file with @ flow has been changed if (this._flowShouldRun) { this._flowShouldRun = false; - this._flowCheck(compiler.options.context, this.flowVersion, function(err, flowOutput) { + this._flowCheck(compiler.options.context, this.flowVersion, (err, flowOutput) => { if (err) { compilation.errors.push(err.message); return callback(); @@ -62,7 +62,7 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { compilation.warnings.push(this._flowOutput); } callback(); - }.bind(this)); + }); } else { // Output a warning if flow failed in a previous run if (this._flowOutput.length > 0 && this._flowOutput.indexOf('No errors!') < 0) { @@ -70,22 +70,22 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { } callback(); } - }.bind(this)); + }); }; // This initializer will run once per webpack run (runs once across all compilations) FlowTypecheckPlugin.prototype._initializeFlow = function(projectPath, flowVersion) { const flowconfigPath = path.join(projectPath, '.flowconfig'); - fs.exists(flowconfigPath, function(exists) { + fs.exists(flowconfigPath, (exists) => { if (!exists) { fs.writeFile(flowconfigPath, this.flowconfig.join('\n')); } - }.bind(this)); + }); childProcess.exec( flowTypedPath + ' install --overwrite --flowVersion=' + flowVersion, { cwd: projectPath } ); - Object.keys(this.otherFlowTypedDefs).forEach(function(packageName) { + Object.keys(this.otherFlowTypedDefs).forEach((packageName) => { childProcess.exec( flowTypedPath + ' install ' + packageName + '@' + this.otherFlowTypedDefs[packageName] + ' --overwrite --flowVersion=' + flowVersion, { cwd: projectPath } @@ -97,15 +97,15 @@ FlowTypecheckPlugin.prototype._initializeFlow = function(projectPath, flowVersio ['server'], { cwd: projectPath } ); - this._flowServer.stderr.on('data', function(chunk) { + this._flowServer.stderr.on('data', (chunk) => { this._flowServerStderr += chunk.toString(); - }.bind(this)); - this._flowServer.on('exit', function() { + }); + this._flowServer.on('exit', () => { if (this._flowServerStderr.indexOf('Lib files changed')) { this._flowServerStderr = ""; spawnServer(); } - }.bind(this)); + }); }; spawnServer.call(this); }; @@ -119,13 +119,13 @@ FlowTypecheckPlugin.prototype._flowCheck = function(projectPath, flowVersion, cb ['status', '--no-auto-start', '--color=always'], { cwd: projectPath } ); - statusCheck.stdout.on('data', function(chunk) { + statusCheck.stdout.on('data', (chunk) => { flowOutput += chunk.toString(); }); - statusCheck.stderr.on('data', function(chunk) { + statusCheck.stderr.on('data', (chunk) => { flowErrOutput += chunk.toString(); }); - statusCheck.on('close', function() { + statusCheck.on('close', () => { if (flowErrOutput.length > 0) { if (flowErrOutput.indexOf("There is no Flow server running") >= 0) { return cb(new Error( @@ -143,7 +143,7 @@ FlowTypecheckPlugin.prototype._flowCheck = function(projectPath, flowVersion, cb } } cb(null, flowOutput); - }.bind(this)); + }); }; module.exports = FlowTypecheckPlugin; From d004f2d7cac77349e9e6efbc0da0bccd025eec3d Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 5 Dec 2016 00:42:11 +0100 Subject: [PATCH 09/74] Refactor using promises and no class attrs --- .../react-dev-utils/FlowTypecheckPlugin.js | 196 +++++++++--------- 1 file changed, 97 insertions(+), 99 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 62f9110492c..fa6ee8ec06f 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -4,6 +4,81 @@ var childProcess = require('child_process'); var flowBinPath = require('flow-bin'); const flowTypedPath = path.join(__dirname, 'node_modules', '.bin', 'flow-typed'); +function execOneTime(command, args, options) { + return new Promise((resolve) => { + childProcess.exec( + command + ' ' + (args || []).join(' '), + (options || {}), + (error, stdout, stderr) => { + resolve({ + error: error, + stdout: stdout, + stderr: stderr + }); + } + ) + }); +} + +function writeFileIfDoesNotExist(path, data) { + return new Promise((resolve, reject) => { + fs.exists(path, exists => { + if (!exists) { + fs.writeFile(path, data, err => { + if (err) { + reject(err); + } + resolve(data); + }); + } + }); + }); +} + +function initializeFlow(projectPath, flowVersion, flowconfig, otherFlowTypedDefs) { + const flowconfigPath = path.join(projectPath, '.flowconfig'); + Promise.all( + [ + writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n')), + execOneTime( + flowTypedPath, + ['install', '--overwrite', '--flowVersion=' + flowVersion], + { cwd: projectPath } + ) + ].concat( + Object.keys(otherFlowTypedDefs).map((packageName) => execOneTime( + flowTypedPath, + [ + 'install', + packageName + '@' + otherFlowTypedDefs[packageName], + '--overwrite', + '--flowVersion=' + flowVersion + ], + { cwd: projectPath } + )) + ) + ); +} + +function flowCheck(projectPath, flowVersion) { + return execOneTime( + flowBinPath, + ['status', '--color=always'], + { cwd: projectPath } + ) + .then(res => { + var flowOutput = res.stdout; + var flowErrOutput = res.stderr; + if (flowErrOutput.length > 0) { + if(flowErrOutput.indexOf("still initializing") < 0) { + return Promise.reject(new Error('flow server:\n' + flowErrOutput)); + } + } + return flowOutput; + }); +} + + function FlowTypecheckPlugin(options) { options = options || {}; // The flow-bin version @@ -13,19 +88,12 @@ function FlowTypecheckPlugin(options) { // Load up other flow-typed defs outside of the package.json (implicit packages behind react-scripts) // Key is the package name, value is the version number this.otherFlowTypedDefs = options.otherFlowTypedDefs || {}; - - // If flow is globally present in the project, this will stay across compilations - this._flowInitialized = false; - // If flow should run in a current compilation - this._flowShouldRun = false; - // Stores the last flow output - this._flowOutput = ''; - // Flow server process - this._flowServer = null; - this._flowServerStderr = ''; } FlowTypecheckPlugin.prototype.apply = function(compiler) { + var flowInitialized = false; + var flowShouldRun = false; + var flowOutput = ''; compiler.plugin('compilation', (compilation, params) => { // Detect the presence of flow and initialize it compilation.plugin('normal-module-loader', (loaderContext, module) => { @@ -35,11 +103,14 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { // We use webpack's cached FileSystem to avoid slowing down compilation loaderContext.fs.readFile(module.resource, (err, data) => { if (data && data.toString().indexOf('@flow') >= 0) { - if (!this._flowInitialized) { - this._initializeFlow(compiler.options.context, this.flowVersion); - this._flowInitialized = true; + if (!flowInitialized) { + initializeFlow( + compiler.options.context, this.flowVersion, + this.flowconfig, this.otherFlowTypedDefs + ); + flowInitialized = true; } - this._flowShouldRun = true; + flowShouldRun = true; } }); } @@ -49,101 +120,28 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { // While emitting run a flow check if flow has been detected compiler.plugin('emit', (compilation, callback) => { // Only if a file with @ flow has been changed - if (this._flowShouldRun) { - this._flowShouldRun = false; - this._flowCheck(compiler.options.context, this.flowVersion, (err, flowOutput) => { - if (err) { - compilation.errors.push(err.message); - return callback(); - } - this._flowOutput = flowOutput; + if (flowShouldRun) { + flowShouldRun = false; + flowCheck(compiler.options.context, this.flowVersion) + .then(newOutput => { + flowOutput = newOutput; // Output a warning if flow failed - if (this._flowOutput.indexOf('No errors!') < 0) { - compilation.warnings.push(this._flowOutput); + if (flowOutput.indexOf('No errors!') < 0) { + compilation.warnings.push(flowOutput); } callback(); + }, (err) => { + compilation.errors.push(err.message); + callback(); }); } else { // Output a warning if flow failed in a previous run - if (this._flowOutput.length > 0 && this._flowOutput.indexOf('No errors!') < 0) { - compilation.warnings.push(this._flowOutput); + if (flowOutput.length > 0 && flowOutput.indexOf('No errors!') < 0) { + compilation.warnings.push(flowOutput); } callback(); } }); }; -// This initializer will run once per webpack run (runs once across all compilations) -FlowTypecheckPlugin.prototype._initializeFlow = function(projectPath, flowVersion) { - const flowconfigPath = path.join(projectPath, '.flowconfig'); - fs.exists(flowconfigPath, (exists) => { - if (!exists) { - fs.writeFile(flowconfigPath, this.flowconfig.join('\n')); - } - }); - childProcess.exec( - flowTypedPath + ' install --overwrite --flowVersion=' + flowVersion, - { cwd: projectPath } - ); - Object.keys(this.otherFlowTypedDefs).forEach((packageName) => { - childProcess.exec( - flowTypedPath + ' install ' + packageName + '@' + this.otherFlowTypedDefs[packageName] + ' --overwrite --flowVersion=' + flowVersion, - { cwd: projectPath } - ); - }) - function spawnServer() { - this._flowServer = childProcess.spawn( - flowBinPath, - ['server'], - { cwd: projectPath } - ); - this._flowServer.stderr.on('data', (chunk) => { - this._flowServerStderr += chunk.toString(); - }); - this._flowServer.on('exit', () => { - if (this._flowServerStderr.indexOf('Lib files changed')) { - this._flowServerStderr = ""; - spawnServer(); - } - }); - }; - spawnServer.call(this); -}; - -// This check will run each time a compilation sees a file with @ flow change -FlowTypecheckPlugin.prototype._flowCheck = function(projectPath, flowVersion, cb) { - var flowOutput = ""; - var flowErrOutput = ""; - var statusCheck = childProcess.spawn( - flowBinPath, - ['status', '--no-auto-start', '--color=always'], - { cwd: projectPath } - ); - statusCheck.stdout.on('data', (chunk) => { - flowOutput += chunk.toString(); - }); - statusCheck.stderr.on('data', (chunk) => { - flowErrOutput += chunk.toString(); - }); - statusCheck.on('close', () => { - if (flowErrOutput.length > 0) { - if (flowErrOutput.indexOf("There is no Flow server running") >= 0) { - return cb(new Error( - 'flow server: unexpectedly died.\n\n' + - this._flowServerStderr + - '\n' + - 'This is likely due to a version mismatch between a global flow ' + - 'server (that you or your IDE may try to run) and react-script\'s ' + - 'flow server.\n' + - 'You should run: \n' + - 'npm install -g flow-bin@' + flowVersion - )); - } else if(flowErrOutput.indexOf("still initializing") < 0) { - return cb(new Error(flowErrOutput)); - } - } - cb(null, flowOutput); - }); -}; - module.exports = FlowTypecheckPlugin; From ec8d2303e0281496996a1d02bc758f17cdf7d1ee Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 5 Dec 2016 01:47:54 +0100 Subject: [PATCH 10/74] Add documentation for out-of-the-box flow --- packages/react-scripts/template/README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/react-scripts/template/README.md b/packages/react-scripts/template/README.md index 4445e9b903a..d10b1aff19c 100644 --- a/packages/react-scripts/template/README.md +++ b/packages/react-scripts/template/README.md @@ -479,15 +479,14 @@ Now you are ready to use the imported React Bootstrap components within your com Flow is a static type checker that helps you write code with fewer bugs. Check out this [introduction to using static types in JavaScript](https://medium.com/@preethikasireddy/why-use-static-types-in-javascript-part-1-8382da1e0adb) if you are new to this concept. -Recent versions of [Flow](http://flowtype.org/) work with Create React App projects out of the box. +Flow typing is now supported out of the box. All you have to do is add the `/* @flow */` comment on top of files you +want to typecheck. If no `.flowconfig` is present, one will be generated for you. The script will also download type +definitions from [flow-typed](https://github.com/flowtype/flow-typed) automatically. -To add Flow to a Create React App project, follow these steps: +Flow errors will show up alongside eslint errors as you work on your application. -1. Run `npm install --save-dev flow-bin`. -2. Add `"flow": "flow"` to the `scripts` section of your `package.json`. -3. Add `// @flow` to any files you want to type check (for example, to `src/App.js`). +>Note: If your global flow installation version differs from react-scripts's flow version, you may experience slower compilation times while going back and forth between your app and your IDE (that may use your global flow). Ensure you have the same `flow-bin` version [here](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-dev-utils/package.json). -Now you can run `npm run flow` to check the files for type errors. You can optionally use an IDE like [Nuclide](https://nuclide.io/docs/languages/flow/) for a better integrated experience. In the future we plan to integrate it into Create React App even more closely. From bb39b190ccd96c8bdb8028167f85588b1803ba2b Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 5 Dec 2016 16:26:12 +0100 Subject: [PATCH 11/74] Schedule project init to complete before 1st check --- .../react-dev-utils/FlowTypecheckPlugin.js | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index fa6ee8ec06f..c246d782919 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -30,6 +30,8 @@ function writeFileIfDoesNotExist(path, data) { } resolve(data); }); + } else { + resolve(data); } }); }); @@ -37,7 +39,7 @@ function writeFileIfDoesNotExist(path, data) { function initializeFlow(projectPath, flowVersion, flowconfig, otherFlowTypedDefs) { const flowconfigPath = path.join(projectPath, '.flowconfig'); - Promise.all( + return Promise.all( [ writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n')), execOneTime( @@ -91,7 +93,9 @@ function FlowTypecheckPlugin(options) { } FlowTypecheckPlugin.prototype.apply = function(compiler) { + var flowActiveOnProject = false; var flowInitialized = false; + var flowInitializationPromise; var flowShouldRun = false; var flowOutput = ''; compiler.plugin('compilation', (compilation, params) => { @@ -103,12 +107,20 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { // We use webpack's cached FileSystem to avoid slowing down compilation loaderContext.fs.readFile(module.resource, (err, data) => { if (data && data.toString().indexOf('@flow') >= 0) { - if (!flowInitialized) { - initializeFlow( + if (!flowActiveOnProject) { + flowInitializationPromise = initializeFlow( compiler.options.context, this.flowVersion, this.flowconfig, this.otherFlowTypedDefs - ); - flowInitialized = true; + ) + .then(() => { + flowInitialized = true; + }, (e) => { + loaderContext.emitWarning(new Error( + 'Flow project initialization warning:\n' + + e.message + )); + }); + flowActiveOnProject = true; } flowShouldRun = true; } @@ -122,7 +134,10 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { // Only if a file with @ flow has been changed if (flowShouldRun) { flowShouldRun = false; - flowCheck(compiler.options.context, this.flowVersion) + (flowInitialized ? + Promise.resolve() : + flowInitializationPromise) + .then(() => flowCheck(compiler.options.context, this.flowVersion)) .then(newOutput => { flowOutput = newOutput; // Output a warning if flow failed From 88785023e57fb9da031995bd747c9c365a63ac09 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 5 Dec 2016 16:59:17 +0100 Subject: [PATCH 12/74] Process code-based flow checks --- .../react-dev-utils/FlowTypecheckPlugin.js | 88 +++++++++++-------- 1 file changed, 50 insertions(+), 38 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index c246d782919..23211f19e11 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -4,19 +4,44 @@ var childProcess = require('child_process'); var flowBinPath = require('flow-bin'); const flowTypedPath = path.join(__dirname, 'node_modules', '.bin', 'flow-typed'); +function stripFlowLoadingIndicators(message) { + var newMessage = message; + var launchingIndex = newMessage.indexOf("Launching Flow server for"); + if (launchingIndex >= 0) { + newMessage = newMessage.slice(0, launchingIndex); + } + var stillIndex = newMessage.indexOf("flow is still initializing"); + if (stillIndex >= 0) { + newMessage = newMessage.slice(0, stillIndex); + } + return newMessage; +} + function execOneTime(command, args, options) { - return new Promise((resolve) => { - childProcess.exec( - command + ' ' + (args || []).join(' '), - (options || {}), - (error, stdout, stderr) => { - resolve({ - error: error, - stdout: stdout, - stderr: stderr - }); + return new Promise((resolve, reject) => { + var stdout = new Buffer(""); + var stderr = new Buffer(""); + var oneTimeProcess = childProcess.spawn( + command, + args, + options + ); + oneTimeProcess.stdout.on('data', chunk => { + stdout = Buffer.concat([stdout, chunk]); + }); + oneTimeProcess.stderr.on('data', chunk => { + stderr = Buffer.concat([stderr, chunk]); + }); + oneTimeProcess.on('exit', code => { + switch (code) { + case 0: + return resolve(stdout); + default: + return reject(new Error( + Buffer.concat([stdout, stderr]).toString() + )); } - ) + }); }); } @@ -67,17 +92,7 @@ function flowCheck(projectPath, flowVersion) { flowBinPath, ['status', '--color=always'], { cwd: projectPath } - ) - .then(res => { - var flowOutput = res.stdout; - var flowErrOutput = res.stderr; - if (flowErrOutput.length > 0) { - if(flowErrOutput.indexOf("still initializing") < 0) { - return Promise.reject(new Error('flow server:\n' + flowErrOutput)); - } - } - return flowOutput; - }); + ); } @@ -97,9 +112,10 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { var flowInitialized = false; var flowInitializationPromise; var flowShouldRun = false; - var flowOutput = ''; + var flowErrorOutput = null; + + // During module traversal, assert the presence of an @ flow in a module compiler.plugin('compilation', (compilation, params) => { - // Detect the presence of flow and initialize it compilation.plugin('normal-module-loader', (loaderContext, module) => { // We're only checking the presence of flow in non-node_modules // (some dependencies may keep their flow comments, we don't want to match them) @@ -129,7 +145,7 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { }) }); - // While emitting run a flow check if flow has been detected + // While emitting, run a flow check if flow has been detected compiler.plugin('emit', (compilation, callback) => { // Only if a file with @ flow has been changed if (flowShouldRun) { @@ -138,21 +154,17 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { Promise.resolve() : flowInitializationPromise) .then(() => flowCheck(compiler.options.context, this.flowVersion)) - .then(newOutput => { - flowOutput = newOutput; - // Output a warning if flow failed - if (flowOutput.indexOf('No errors!') < 0) { - compilation.warnings.push(flowOutput); - } - callback(); - }, (err) => { - compilation.errors.push(err.message); - callback(); - }); + .then(() => { + flowErrorOutput = null; + }, error => { + flowErrorOutput = stripFlowLoadingIndicators(error.message); + compilation.warnings.push(flowErrorOutput); + }) + .then(callback); } else { // Output a warning if flow failed in a previous run - if (flowOutput.length > 0 && flowOutput.indexOf('No errors!') < 0) { - compilation.warnings.push(flowOutput); + if (flowErrorOutput) { + compilation.warnings.push(flowErrorOutput); } callback(); } From 6a5cfc1c070959f53d0b70a9d1c2f754a62363a6 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 5 Dec 2016 17:21:39 +0100 Subject: [PATCH 13/74] Get flow version internally without config --- .../react-dev-utils/FlowTypecheckPlugin.js | 75 ++++++++++++------- .../config/webpack.config.dev.js | 1 - .../config/webpack.config.prod.js | 1 - 3 files changed, 48 insertions(+), 29 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 23211f19e11..d28fe5d6dcc 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -62,32 +62,57 @@ function writeFileIfDoesNotExist(path, data) { }); } -function initializeFlow(projectPath, flowVersion, flowconfig, otherFlowTypedDefs) { - const flowconfigPath = path.join(projectPath, '.flowconfig'); - return Promise.all( - [ - writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n')), +function getFlowPath(globalInstall) { + return ( + globalInstall ? execOneTime( - flowTypedPath, - ['install', '--overwrite', '--flowVersion=' + flowVersion], - { cwd: projectPath } + '/bin/sh', + ['-c', 'which flow'] ) - ].concat( - Object.keys(otherFlowTypedDefs).map((packageName) => execOneTime( - flowTypedPath, - [ - 'install', - packageName + '@' + otherFlowTypedDefs[packageName], - '--overwrite', - '--flowVersion=' + flowVersion - ], - { cwd: projectPath } - )) + .then(rawPath => { + var path = rawPath.toString().replace('\n', ''); + return path.indexOf("not found") >= 0 ? + flowBinPath : + path + }) : + Promise.resolve(flowBinPath) + ) +} + +function getFlowVersion(options) { + return getFlowPath((options || {}).globalInstall) + .then(flowPath => execOneTime( + flowPath, + ['version', '--json'] + )) + .then(rawData => JSON.parse(rawData)) + .then(versionData => versionData.semver); +} + +function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { + const flowconfigPath = path.join(projectPath, '.flowconfig'); + return getFlowVersion().then(localVersion => Promise.all([ + writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n')), + execOneTime( + flowTypedPath, + ['install', '--overwrite', '--flowVersion=' + localVersion], + { cwd: projectPath } ) - ); + ].concat( + Object.keys(otherFlowTypedDefs).map((packageName) => execOneTime( + flowTypedPath, + [ + 'install', + packageName + '@' + otherFlowTypedDefs[packageName], + '--overwrite', + '--flowVersion=' + localVersion + ], + { cwd: projectPath } + )) + ))); } -function flowCheck(projectPath, flowVersion) { +function flowCheck(projectPath) { return execOneTime( flowBinPath, ['status', '--color=always'], @@ -95,11 +120,8 @@ function flowCheck(projectPath, flowVersion) { ); } - function FlowTypecheckPlugin(options) { options = options || {}; - // The flow-bin version - this.flowVersion = options.flowVersion || 'latest'; // Contents of the generated .flowconfig if it doesn't exist this.flowconfig = options.flowconfig || []; // Load up other flow-typed defs outside of the package.json (implicit packages behind react-scripts) @@ -125,8 +147,7 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { if (data && data.toString().indexOf('@flow') >= 0) { if (!flowActiveOnProject) { flowInitializationPromise = initializeFlow( - compiler.options.context, this.flowVersion, - this.flowconfig, this.otherFlowTypedDefs + compiler.options.context, this.flowconfig, this.otherFlowTypedDefs ) .then(() => { flowInitialized = true; @@ -153,7 +174,7 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { (flowInitialized ? Promise.resolve() : flowInitializationPromise) - .then(() => flowCheck(compiler.options.context, this.flowVersion)) + .then(() => flowCheck(compiler.options.context)) .then(() => { flowErrorOutput = null; }, error => { diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index d53fd975433..db8e51f1027 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -237,7 +237,6 @@ module.exports = { '[libs]', './flow-typed' ], - flowVersion: "0.36.0", otherFlowTypedDefs: { jest: "17.0.0" } diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 5413019d0a8..14e7c9f1f76 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -277,7 +277,6 @@ module.exports = { }), // Run Flow only if we see some @ flow annotations, will error on CI new FlowTypecheckPlugin({ - flowVersion: "0.36.0", otherFlowTypedDefs: { jest: "17.0.0" } From 513c0a9cd4337d97309fac8c5c99a6f96432358a Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 5 Dec 2016 17:52:53 +0100 Subject: [PATCH 14/74] Check global flow version If a mismatch occurs, the DX will be bad, we should tell it to the user --- .../react-dev-utils/FlowTypecheckPlugin.js | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index d28fe5d6dcc..449ba717eab 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -14,6 +14,10 @@ function stripFlowLoadingIndicators(message) { if (stillIndex >= 0) { newMessage = newMessage.slice(0, stillIndex); } + var notRespIndex = newMessage.indexOf("The flow server is not responding"); + if (notRespIndex >= 0) { + newMessage = newMessage.slice(0, notRespIndex); + } return newMessage; } @@ -32,6 +36,7 @@ function execOneTime(command, args, options) { oneTimeProcess.stderr.on('data', chunk => { stderr = Buffer.concat([stderr, chunk]); }); + oneTimeProcess.on('error', error => reject(error)); oneTimeProcess.on('exit', code => { switch (code) { case 0: @@ -62,29 +67,11 @@ function writeFileIfDoesNotExist(path, data) { }); } -function getFlowPath(globalInstall) { - return ( - globalInstall ? - execOneTime( - '/bin/sh', - ['-c', 'which flow'] - ) - .then(rawPath => { - var path = rawPath.toString().replace('\n', ''); - return path.indexOf("not found") >= 0 ? - flowBinPath : - path - }) : - Promise.resolve(flowBinPath) - ) -} - function getFlowVersion(options) { - return getFlowPath((options || {}).globalInstall) - .then(flowPath => execOneTime( - flowPath, + return execOneTime( + (options || {}).global ? "flow" : flowBinPath, ['version', '--json'] - )) + ) .then(rawData => JSON.parse(rawData)) .then(versionData => versionData.semver); } @@ -92,6 +79,21 @@ function getFlowVersion(options) { function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { const flowconfigPath = path.join(projectPath, '.flowconfig'); return getFlowVersion().then(localVersion => Promise.all([ + getFlowVersion({global: true}).catch(() => localVersion) + .then(globalVersion => + globalVersion !== localVersion ? + Promise.reject(new Error( + 'Your global flow version does not match react-script\'s flow version.\n\n' + + 'Having two different versions is likely to restart your flow server ' + + 'regularly while you go back and forth between your App and your IDE ' + + '(that is likely to rely on your global flow installation). ' + + 'This will slow down significantly your development experience. ' + + 'In order to avoid this, ensure you have flow ' + localVersion + ' globally ' + + '(your current global version is ' + globalVersion + ').\n\n' + + 'Run `npm install -g flow-bin@' + localVersion + '` to fix this.' + )) : + true + ), writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n')), execOneTime( flowTypedPath, From 719ac8260a87667c75a95bd4f0df25d397303e70 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 5 Dec 2016 18:05:44 +0100 Subject: [PATCH 15/74] Add flow-typed to gitignore automatically --- .../react-dev-utils/FlowTypecheckPlugin.js | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 449ba717eab..8bbd67b34b2 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -56,7 +56,7 @@ function writeFileIfDoesNotExist(path, data) { if (!exists) { fs.writeFile(path, data, err => { if (err) { - reject(err); + return reject(err); } resolve(data); }); @@ -67,6 +67,37 @@ function writeFileIfDoesNotExist(path, data) { }); } +function writeInFileIfNotPresent(path, contentToAssert, contentToAppend) { + return new Promise((resolve, reject) => { + fs.exists(path, exists => { + if (!exists) { + fs.writeFile(path, contentToAppend, err => { + if (err) { + return reject(err); + } + resolve(contentToAppend); + }); + } else { + fs.readFile(path, (err, existingContent) => { + if (err) { + return reject(err); + } + if (existingContent.indexOf(contentToAssert) < 0) { + fs.appendFile(path, contentToAppend, err => { + if (err) { + return reject(err); + } + resolve(contentToAppend); + }); + } else { + resolve(contentToAppend); + } + }); + } + }); + }); +} + function getFlowVersion(options) { return execOneTime( (options || {}).global ? "flow" : flowBinPath, @@ -78,6 +109,7 @@ function getFlowVersion(options) { function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { const flowconfigPath = path.join(projectPath, '.flowconfig'); + const gitignorePath = path.join(projectPath, '.gitignore'); return getFlowVersion().then(localVersion => Promise.all([ getFlowVersion({global: true}).catch(() => localVersion) .then(globalVersion => @@ -95,6 +127,7 @@ function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { true ), writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n')), + writeInFileIfNotPresent(gitignorePath, 'flow-typed', 'flow-typed'), execOneTime( flowTypedPath, ['install', '--overwrite', '--flowVersion=' + localVersion], From c76f15e44175d477bf26735ccdc1799b2840ba8e Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 5 Dec 2016 18:56:27 +0100 Subject: [PATCH 16/74] Add optional flow to end to end testing --- tasks/e2e.sh | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tasks/e2e.sh b/tasks/e2e.sh index 5a2ecdf31ef..0c2e7bc2a27 100755 --- a/tasks/e2e.sh +++ b/tasks/e2e.sh @@ -153,6 +153,7 @@ test -e build/static/js/*.js test -e build/static/css/*.css test -e build/static/media/*.svg test -e build/favicon.ico +test ! -e .flowconfig # Run tests with CI flag CI=true npm test @@ -162,6 +163,23 @@ CI=true npm test # Test the server npm start -- --smoke-test +# Test optional flow enabling +cp src/App.js src/App.backup.js +cp .gitignore .gitignore.backup +echo " +/* @flow */ +var wrong: string = 0; +" >> src/App.js +npm start -- --smoke-test || true +test -e .flowconfig +test -d flow-typed +cat .gitignore | grep flow-typed +rm src/App.js .gitignore +cp src/App.backup.js src/App.js +cp .gitignore.backup .gitignore +rm src/App.backup.js .gitignore.backup .flowconfig +rm -rf flow-typed + # ****************************************************************************** # Finally, let's check that everything still works after ejecting. # ****************************************************************************** @@ -183,6 +201,7 @@ test -e build/static/js/*.js test -e build/static/css/*.css test -e build/static/media/*.svg test -e build/favicon.ico +test ! -e .flowconfig # Run tests, overring the watch option to disable it. # `CI=true npm test` won't work here because `npm test` becomes just `jest`. @@ -195,6 +214,23 @@ npm test -- --watch=no # Test the server npm start -- --smoke-test +# Test optional flow enabling +cp src/App.js src/App.backup.js +cp .gitignore .gitignore.backup +echo " +/* @flow */ +var wrong: string = 0; +" >> src/App.js +npm start -- --smoke-test || true +test -e .flowconfig +test -d flow-typed +cat .gitignore | grep flow-typed +rm src/App.js .gitignore +cp src/App.backup.js src/App.js +cp .gitignore.backup .gitignore +rm src/App.backup.js .gitignore.backup .flowconfig +rm -rf flow-typed + # ****************************************************************************** # Test --scripts-version with a version number # ****************************************************************************** From ba7a05abb603bba593a82d87648c2a4a7ff03616 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 5 Dec 2016 19:04:48 +0100 Subject: [PATCH 17/74] Run flow even if init failed --- packages/react-dev-utils/FlowTypecheckPlugin.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 8bbd67b34b2..ff324069a90 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -184,13 +184,14 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { flowInitializationPromise = initializeFlow( compiler.options.context, this.flowconfig, this.otherFlowTypedDefs ) - .then(() => { - flowInitialized = true; - }, (e) => { + .catch(e => { loaderContext.emitWarning(new Error( 'Flow project initialization warning:\n' + e.message )); + }) + .then(() => { + flowInitialized = true; }); flowActiveOnProject = true; } From 2362346ee183254156d94ad046f89e3852b8f99f Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 5 Dec 2016 21:38:02 +0100 Subject: [PATCH 18/74] Remove fbjs ignores now that it's fixed --- packages/react-scripts/config/webpack.config.dev.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index db8e51f1027..fef412cf5bd 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -231,9 +231,6 @@ module.exports = { // Trigger some typechecking if a file matches with an @ flow comment new FlowTypecheckPlugin({ flowconfig: [ - '[ignore]', - '/node_modules/fbjs/.*', - '', '[libs]', './flow-typed' ], From f83fa6a42be97aa7c0cc45b1c6c57aacea7839c0 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 5 Dec 2016 22:38:20 +0100 Subject: [PATCH 19/74] Remove flow-typed lib import (known by default!) --- packages/react-scripts/config/webpack.config.dev.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index fef412cf5bd..c276a60be70 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -230,10 +230,6 @@ module.exports = { new WatchMissingNodeModulesPlugin(paths.appNodeModules), // Trigger some typechecking if a file matches with an @ flow comment new FlowTypecheckPlugin({ - flowconfig: [ - '[libs]', - './flow-typed' - ], otherFlowTypedDefs: { jest: "17.0.0" } From f42c5cc673f850f2089bed71774fe6a67c07e773 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 5 Dec 2016 23:33:29 +0100 Subject: [PATCH 20/74] In e2e: assert flow is correctly showing errors --- tasks/e2e.sh | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/tasks/e2e.sh b/tasks/e2e.sh index 0c2e7bc2a27..2b447f429c5 100755 --- a/tasks/e2e.sh +++ b/tasks/e2e.sh @@ -169,12 +169,14 @@ cp .gitignore .gitignore.backup echo " /* @flow */ var wrong: string = 0; -" >> src/App.js -npm start -- --smoke-test || true +" > src/App.js +cat src/App.backup.js >> src/App.js +CI=true npm run build >> errors.log 2>> errors.log || true +cat errors.log | grep "This type is incompatible with" test -e .flowconfig test -d flow-typed cat .gitignore | grep flow-typed -rm src/App.js .gitignore +rm src/App.js .gitignore errors.log cp src/App.backup.js src/App.js cp .gitignore.backup .gitignore rm src/App.backup.js .gitignore.backup .flowconfig @@ -220,12 +222,13 @@ cp .gitignore .gitignore.backup echo " /* @flow */ var wrong: string = 0; -" >> src/App.js -npm start -- --smoke-test || true -test -e .flowconfig +" > src/App.js +cat src/App.backup.js >> src/App.js +CI=true npm run build >> errors.log 2>> errors.log || true +cat errors.log | grep "This type is incompatible with" test -d flow-typed cat .gitignore | grep flow-typed -rm src/App.js .gitignore +rm src/App.js .gitignore errors.log cp src/App.backup.js src/App.js cp .gitignore.backup .gitignore rm src/App.backup.js .gitignore.backup .flowconfig From f9ae1f072eaddc63c16a5669f7abb7af74669ee6 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Thu, 8 Dec 2016 17:07:04 +0100 Subject: [PATCH 21/74] Only change gitignore if we create a flowconfig --- packages/react-dev-utils/FlowTypecheckPlugin.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index ff324069a90..a9fc4672940 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -58,10 +58,10 @@ function writeFileIfDoesNotExist(path, data) { if (err) { return reject(err); } - resolve(data); + resolve(true); }); } else { - resolve(data); + resolve(false); } }); }); @@ -75,7 +75,7 @@ function writeInFileIfNotPresent(path, contentToAssert, contentToAppend) { if (err) { return reject(err); } - resolve(contentToAppend); + resolve(true); }); } else { fs.readFile(path, (err, existingContent) => { @@ -87,10 +87,10 @@ function writeInFileIfNotPresent(path, contentToAssert, contentToAppend) { if (err) { return reject(err); } - resolve(contentToAppend); + resolve(true); }); } else { - resolve(contentToAppend); + resolve(false); } }); } @@ -126,8 +126,11 @@ function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { )) : true ), - writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n')), - writeInFileIfNotPresent(gitignorePath, 'flow-typed', 'flow-typed'), + writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n')) + .then(wroteFlowconfig => wroteFlowconfig ? + writeInFileIfNotPresent(gitignorePath, 'flow-typed', 'flow-typed/npm') : + false + ), execOneTime( flowTypedPath, ['install', '--overwrite', '--flowVersion=' + localVersion], From c5e2102f4f7d15d3af290a0fe4950e2889bd3a6c Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Fri, 9 Dec 2016 14:22:14 +0100 Subject: [PATCH 22/74] Ensure the init promise is correctly triggered --- packages/react-dev-utils/FlowTypecheckPlugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index a9fc4672940..d053b2a8ff8 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -170,7 +170,7 @@ function FlowTypecheckPlugin(options) { FlowTypecheckPlugin.prototype.apply = function(compiler) { var flowActiveOnProject = false; var flowInitialized = false; - var flowInitializationPromise; + var flowInitializationPromise = Promise.reject(new Error('Init process did not start yet')); var flowShouldRun = false; var flowErrorOutput = null; From e4e21114909f9130c65a6601759c27db05c88f50 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Fri, 9 Dec 2016 15:06:34 +0100 Subject: [PATCH 23/74] Don't create an uncatched promise --- packages/react-dev-utils/FlowTypecheckPlugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index d053b2a8ff8..a9fc4672940 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -170,7 +170,7 @@ function FlowTypecheckPlugin(options) { FlowTypecheckPlugin.prototype.apply = function(compiler) { var flowActiveOnProject = false; var flowInitialized = false; - var flowInitializationPromise = Promise.reject(new Error('Init process did not start yet')); + var flowInitializationPromise; var flowShouldRun = false; var flowErrorOutput = null; From f9bf33d0d676679727929b426204a9cbc64ad16a Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Fri, 9 Dec 2016 20:29:13 +0100 Subject: [PATCH 24/74] Don't reinit flow in a child compilation --- packages/react-dev-utils/FlowTypecheckPlugin.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index a9fc4672940..f23e6b9e133 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -184,9 +184,11 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { loaderContext.fs.readFile(module.resource, (err, data) => { if (data && data.toString().indexOf('@flow') >= 0) { if (!flowActiveOnProject) { - flowInitializationPromise = initializeFlow( - compiler.options.context, this.flowconfig, this.otherFlowTypedDefs - ) + flowInitializationPromise = (!compiler.parentCompilation ? + initializeFlow( + compiler.options.context, this.flowconfig, this.otherFlowTypedDefs + ) : Promise.resolve() + ) .catch(e => { loaderContext.emitWarning(new Error( 'Flow project initialization warning:\n' + From 4448e98035ae71f01a70879ce65cfb02cd207020 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 12 Dec 2016 10:40:39 +0100 Subject: [PATCH 25/74] Remove flow-typed/ dir assertion Was failing intermittently --- tasks/e2e.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tasks/e2e.sh b/tasks/e2e.sh index 2b447f429c5..3b57c93d5a8 100755 --- a/tasks/e2e.sh +++ b/tasks/e2e.sh @@ -174,7 +174,6 @@ cat src/App.backup.js >> src/App.js CI=true npm run build >> errors.log 2>> errors.log || true cat errors.log | grep "This type is incompatible with" test -e .flowconfig -test -d flow-typed cat .gitignore | grep flow-typed rm src/App.js .gitignore errors.log cp src/App.backup.js src/App.js @@ -226,7 +225,7 @@ var wrong: string = 0; cat src/App.backup.js >> src/App.js CI=true npm run build >> errors.log 2>> errors.log || true cat errors.log | grep "This type is incompatible with" -test -d flow-typed +test -e .flowconfig cat .gitignore | grep flow-typed rm src/App.js .gitignore errors.log cp src/App.backup.js src/App.js From db929a1c09b9a4ec4a79cef9fa1c285864bff208 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 12 Dec 2016 16:43:12 +0100 Subject: [PATCH 26/74] Reapply a lost change from #1233 during rebase --- packages/react-scripts/config/webpack.config.prod.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index 14e7c9f1f76..d4deb6481af 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -15,7 +15,6 @@ var HtmlWebpackPlugin = require('html-webpack-plugin'); var ExtractTextPlugin = require('extract-text-webpack-plugin'); var ManifestPlugin = require('webpack-manifest-plugin'); var InterpolateHtmlPlugin = require('react-dev-utils/InterpolateHtmlPlugin'); -var SubresourceIntegrityPlugin = require('webpack-subresource-integrity'); var FlowTypecheckPlugin = require('react-dev-utils/FlowTypecheckPlugin'); var url = require('url'); var paths = require('./paths'); @@ -271,10 +270,6 @@ module.exports = { new ManifestPlugin({ fileName: 'asset-manifest.json' }), - // Generate and inject subresources hashes in the final `index.html`. - new SubresourceIntegrityPlugin({ - hashFuncNames: ['sha256', 'sha384'] - }), // Run Flow only if we see some @ flow annotations, will error on CI new FlowTypecheckPlugin({ otherFlowTypedDefs: { From eac318d3032e0bba743a27cb913ab1b1bff49064 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 12 Dec 2016 16:44:00 +0100 Subject: [PATCH 27/74] Consistent ESLint capitalization --- packages/react-scripts/template/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-scripts/template/README.md b/packages/react-scripts/template/README.md index d10b1aff19c..2c3a3338114 100644 --- a/packages/react-scripts/template/README.md +++ b/packages/react-scripts/template/README.md @@ -483,7 +483,7 @@ Flow typing is now supported out of the box. All you have to do is add the `/* @ want to typecheck. If no `.flowconfig` is present, one will be generated for you. The script will also download type definitions from [flow-typed](https://github.com/flowtype/flow-typed) automatically. -Flow errors will show up alongside eslint errors as you work on your application. +Flow errors will show up alongside ESLint errors as you work on your application. >Note: If your global flow installation version differs from react-scripts's flow version, you may experience slower compilation times while going back and forth between your app and your IDE (that may use your global flow). Ensure you have the same `flow-bin` version [here](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-dev-utils/package.json). From 054357fcb13f5b3928b64b348206c11dc45f2ea7 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 12 Dec 2016 19:06:37 +0100 Subject: [PATCH 28/74] Run flow version check before the rest --- .../react-dev-utils/FlowTypecheckPlugin.js | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index f23e6b9e133..93e6a0f80db 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -110,22 +110,15 @@ function getFlowVersion(options) { function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { const flowconfigPath = path.join(projectPath, '.flowconfig'); const gitignorePath = path.join(projectPath, '.gitignore'); - return getFlowVersion().then(localVersion => Promise.all([ + return getFlowVersion().then(localVersion => getFlowVersion({global: true}).catch(() => localVersion) .then(globalVersion => globalVersion !== localVersion ? - Promise.reject(new Error( - 'Your global flow version does not match react-script\'s flow version.\n\n' + - 'Having two different versions is likely to restart your flow server ' + - 'regularly while you go back and forth between your App and your IDE ' + - '(that is likely to rely on your global flow installation). ' + - 'This will slow down significantly your development experience. ' + - 'In order to avoid this, ensure you have flow ' + localVersion + ' globally ' + - '(your current global version is ' + globalVersion + ').\n\n' + - 'Run `npm install -g flow-bin@' + localVersion + '` to fix this.' - )) : - true - ), + Promise.reject(new Error('Please try to run `npm i -g flow-bin@' + localVersion + '`.')) : + localVersion + ) + ) + .then(localVersion => Promise.all([ writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n')) .then(wroteFlowconfig => wroteFlowconfig ? writeInFileIfNotPresent(gitignorePath, 'flow-typed', 'flow-typed/npm') : @@ -188,7 +181,7 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { initializeFlow( compiler.options.context, this.flowconfig, this.otherFlowTypedDefs ) : Promise.resolve() - ) + ) .catch(e => { loaderContext.emitWarning(new Error( 'Flow project initialization warning:\n' + From b3e560918b5f21ed690ab5d045dcc6a0ef65ab50 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 12 Dec 2016 19:34:38 +0100 Subject: [PATCH 29/74] Transmit via warnings why Flow was disabled --- .../react-dev-utils/FlowTypecheckPlugin.js | 23 +++++++++++-------- packages/react-scripts/scripts/start.js | 22 +++++++++++++++++- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 93e6a0f80db..411a6e225b0 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -114,7 +114,11 @@ function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { getFlowVersion({global: true}).catch(() => localVersion) .then(globalVersion => globalVersion !== localVersion ? - Promise.reject(new Error('Please try to run `npm i -g flow-bin@' + localVersion + '`.')) : + Promise.reject(new Error( + 'Flow integration was disabled because the global Flow version does not match.\n' + + 'You may either remove the global Flow installation or install a compatible version:\n' + + ' npm install -g flow-bin@' + localVersion + )) : localVersion ) ) @@ -163,6 +167,7 @@ function FlowTypecheckPlugin(options) { FlowTypecheckPlugin.prototype.apply = function(compiler) { var flowActiveOnProject = false; var flowInitialized = false; + var flowInitError = null; var flowInitializationPromise; var flowShouldRun = false; var flowErrorOutput = null; @@ -182,14 +187,11 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { compiler.options.context, this.flowconfig, this.otherFlowTypedDefs ) : Promise.resolve() ) - .catch(e => { - loaderContext.emitWarning(new Error( - 'Flow project initialization warning:\n' + - e.message - )); - }) .then(() => { flowInitialized = true; + }, e => { + flowInitError = e; + return Promise.reject(e); }); flowActiveOnProject = true; } @@ -206,9 +208,12 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { if (flowShouldRun) { flowShouldRun = false; (flowInitialized ? - Promise.resolve() : + (flowInitError ? Promise.reject(flowInitError) : Promise.resolve()) : flowInitializationPromise) - .then(() => flowCheck(compiler.options.context)) + .then( + () => flowCheck(compiler.options.context), + e => Promise.reject(e) // don't run a check if init errored, just carry the error + ) .then(() => { flowErrorOutput = null; }, error => { diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index 0e11ef0321f..c4f03dd8b06 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -60,6 +60,11 @@ if (isSmokeTest) { }; } +// Used to detect flow errors we want to swallow +function isFlowIntegrationErrorMessage(message) { + return /^flow integration was disabled/i.test(message); +} + function setupCompiler(host, port, protocol) { // "Compiler" is a low-level interface to Webpack. // It lets us listen to some events and provide our own custom messages. @@ -85,15 +90,25 @@ function setupCompiler(host, port, protocol) { clearConsole(); } + // We need to know if there was an error preventing flow from starting so + // we can swallow it and show it in a more discreet manner + var statsMessages = stats.toJson({}, true); + var flowDisabledMessage = statsMessages.warnings.find(isFlowIntegrationErrorMessage); + statsMessages.warnings = statsMessages.warnings.filter(m => !isFlowIntegrationErrorMessage(m)); + // We have switched off the default Webpack output in WebpackDevServer // options so we are going to "massage" the warnings and errors and present // them in a readable focused way. - var messages = formatWebpackMessages(stats.toJson({}, true)); + var messages = formatWebpackMessages(statsMessages); var isSuccessful = !messages.errors.length && !messages.warnings.length; var showInstructions = isSuccessful && (isInteractive || isFirstCompile); if (isSuccessful) { console.log(chalk.green('Compiled successfully!')); + // If flow was disabled, report it + if (flowDisabledMessage) { + console.log(chalk.yellow('Flow checks were skipped.')); + } } if (showInstructions) { @@ -133,6 +148,11 @@ function setupCompiler(host, port, protocol) { console.log('Use ' + chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.'); console.log('Use ' + chalk.yellow('// $FlowFixMe') + ' to ignore flow-related warnings on the next line.'); } + + // We print why flow was disabled + if (flowDisabledMessage) { + console.log(flowDisabledMessage); + } }); } From cabeadb25e7c5fd575f44addab9e1458ed40d0d8 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 12 Dec 2016 19:40:18 +0100 Subject: [PATCH 30/74] Don't fail on tarball react-scripts --- packages/react-dev-utils/FlowTypecheckPlugin.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 411a6e225b0..341effaf816 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -133,6 +133,9 @@ function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { ['install', '--overwrite', '--flowVersion=' + localVersion], { cwd: projectPath } ) + // This operation will fail if react-scripts is a path to a tarball in the + // package.json (like in End To End testing!). So we swallow this error. + .catch((e) => /invalid comparator/i.test(e.message) ? true : Promise.reject(e)) ].concat( Object.keys(otherFlowTypedDefs).map((packageName) => execOneTime( flowTypedPath, From fa6d3c8de66f5a338dad97ec1c9cab217e10e1b0 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 12 Dec 2016 19:44:50 +0100 Subject: [PATCH 31/74] Celebrate when the flow tests pass! --- packages/react-dev-utils/FlowTypecheckPlugin.js | 3 +++ packages/react-scripts/scripts/start.js | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 341effaf816..f077f6052c5 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -219,6 +219,7 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { ) .then(() => { flowErrorOutput = null; + compilation.flowPassed = true; }, error => { flowErrorOutput = stripFlowLoadingIndicators(error.message); compilation.warnings.push(flowErrorOutput); @@ -228,6 +229,8 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { // Output a warning if flow failed in a previous run if (flowErrorOutput) { compilation.warnings.push(flowErrorOutput); + } else { + compilation.flowPassed = true; } callback(); } diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index c4f03dd8b06..abd4cc34398 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -105,10 +105,12 @@ function setupCompiler(host, port, protocol) { if (isSuccessful) { console.log(chalk.green('Compiled successfully!')); - // If flow was disabled, report it if (flowDisabledMessage) { console.log(chalk.yellow('Flow checks were skipped.')); } + if (stats.compilation.flowPassed) { + console.log(chalk.green('Flow checks have passed.')); + } } if (showInstructions) { From 05ae46061b29bf9f29ec7feb9dab8b8d7fc479c8 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 12 Dec 2016 20:01:28 +0100 Subject: [PATCH 32/74] Show npm commands in cyan --- packages/react-dev-utils/FlowTypecheckPlugin.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index f077f6052c5..a95b0c232c9 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -1,5 +1,6 @@ var fs = require('fs'); var path = require('path'); +var chalk = require('chalk'); var childProcess = require('child_process'); var flowBinPath = require('flow-bin'); const flowTypedPath = path.join(__dirname, 'node_modules', '.bin', 'flow-typed'); @@ -117,7 +118,7 @@ function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { Promise.reject(new Error( 'Flow integration was disabled because the global Flow version does not match.\n' + 'You may either remove the global Flow installation or install a compatible version:\n' + - ' npm install -g flow-bin@' + localVersion + chalk.cyan(' npm install -g flow-bin@' + localVersion) )) : localVersion ) From e30a95920351ef5b7c82e2ee571b31088b0dd522 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 12 Dec 2016 20:09:02 +0100 Subject: [PATCH 33/74] "Hide" flow err suppression in dev utils --- .../react-dev-utils/formatWebpackMessages.js | 18 +++++++++++++++--- packages/react-scripts/scripts/start.js | 19 ++++--------------- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/packages/react-dev-utils/formatWebpackMessages.js b/packages/react-dev-utils/formatWebpackMessages.js index 88834a9fc62..07bced666f3 100644 --- a/packages/react-dev-utils/formatWebpackMessages.js +++ b/packages/react-dev-utils/formatWebpackMessages.js @@ -20,6 +20,11 @@ function isLikelyASyntaxError(message) { return message.indexOf(friendlySyntaxErrorLabel) !== -1; } +// Used to detect flow errors we want to swallow +function isFlowIntegrationErrorMessage(message) { + return /^flow integration was disabled/i.test(message); +} + // Cleans up webpack error messages. function formatMessage(message) { var lines = message.split('\n'); @@ -113,15 +118,22 @@ function formatMessage(message) { } function formatWebpackMessages(json) { - var formattedErrors = json.errors.map(function(message) { + var formattedErrors = json.errors + .filter(m => !isFlowIntegrationErrorMessage(m)) + .map(function(message) { return 'Error in ' + formatMessage(message) }); - var formattedWarnings = json.warnings.map(function(message) { + var formattedWarnings = json.warnings + .filter(m => !isFlowIntegrationErrorMessage(m)) + .map(function(message) { return 'Warning in ' + formatMessage(message) }); + var flowIntegrationError = [].concat(json.errors, json.warnings) + .find(isFlowIntegrationErrorMessage); var result = { errors: formattedErrors, - warnings: formattedWarnings + warnings: formattedWarnings, + flowIntegrationError: flowIntegrationError }; if (result.errors.some(isLikelyASyntaxError)) { // If there are any syntax errors, show just them. diff --git a/packages/react-scripts/scripts/start.js b/packages/react-scripts/scripts/start.js index abd4cc34398..0d53ce80495 100644 --- a/packages/react-scripts/scripts/start.js +++ b/packages/react-scripts/scripts/start.js @@ -60,11 +60,6 @@ if (isSmokeTest) { }; } -// Used to detect flow errors we want to swallow -function isFlowIntegrationErrorMessage(message) { - return /^flow integration was disabled/i.test(message); -} - function setupCompiler(host, port, protocol) { // "Compiler" is a low-level interface to Webpack. // It lets us listen to some events and provide our own custom messages. @@ -90,22 +85,16 @@ function setupCompiler(host, port, protocol) { clearConsole(); } - // We need to know if there was an error preventing flow from starting so - // we can swallow it and show it in a more discreet manner - var statsMessages = stats.toJson({}, true); - var flowDisabledMessage = statsMessages.warnings.find(isFlowIntegrationErrorMessage); - statsMessages.warnings = statsMessages.warnings.filter(m => !isFlowIntegrationErrorMessage(m)); - // We have switched off the default Webpack output in WebpackDevServer // options so we are going to "massage" the warnings and errors and present // them in a readable focused way. - var messages = formatWebpackMessages(statsMessages); + var messages = formatWebpackMessages(stats.toJson({}, true)); var isSuccessful = !messages.errors.length && !messages.warnings.length; var showInstructions = isSuccessful && (isInteractive || isFirstCompile); if (isSuccessful) { console.log(chalk.green('Compiled successfully!')); - if (flowDisabledMessage) { + if (messages.isFlowIntegrationErrorMessage) { console.log(chalk.yellow('Flow checks were skipped.')); } if (stats.compilation.flowPassed) { @@ -152,8 +141,8 @@ function setupCompiler(host, port, protocol) { } // We print why flow was disabled - if (flowDisabledMessage) { - console.log(flowDisabledMessage); + if (messages.isFlowIntegrationErrorMessage) { + console.log(messages.isFlowIntegrationErrorMessage); } }); } From 3ed5f2c5f21ba4955a90f238280eec838c9a341a Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Mon, 12 Dec 2016 20:09:42 +0100 Subject: [PATCH 34/74] Only highlight the npm word --- packages/react-dev-utils/FlowTypecheckPlugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index a95b0c232c9..11725549da8 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -118,7 +118,7 @@ function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { Promise.reject(new Error( 'Flow integration was disabled because the global Flow version does not match.\n' + 'You may either remove the global Flow installation or install a compatible version:\n' + - chalk.cyan(' npm install -g flow-bin@' + localVersion) + ' ' + chalk.cyan('npm') + ' install -g flow-bin@' + localVersion )) : localVersion ) From 026e5811b40e97821e5aaf1dc357c98e7b620e4b Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Tue, 13 Dec 2016 15:41:06 +0100 Subject: [PATCH 35/74] Make flow-typed provision sequential --- .../react-dev-utils/FlowTypecheckPlugin.js | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 11725549da8..6e4c557f9d7 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -136,19 +136,20 @@ function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { ) // This operation will fail if react-scripts is a path to a tarball in the // package.json (like in End To End testing!). So we swallow this error. - .catch((e) => /invalid comparator/i.test(e.message) ? true : Promise.reject(e)) - ].concat( - Object.keys(otherFlowTypedDefs).map((packageName) => execOneTime( - flowTypedPath, - [ - 'install', - packageName + '@' + otherFlowTypedDefs[packageName], - '--overwrite', - '--flowVersion=' + localVersion - ], - { cwd: projectPath } + .catch((e) => /invalid comparator/i.test(e.message) ? true : Promise.reject(e)) + .then(() => Promise.all( + Object.keys(otherFlowTypedDefs).map((packageName) => execOneTime( + flowTypedPath, + [ + 'install', + packageName + '@' + otherFlowTypedDefs[packageName], + '--overwrite', + '--flowVersion=' + localVersion + ], + { cwd: projectPath } + )) )) - ))); + ])); } function flowCheck(projectPath) { From 6e4412407629739ef503ce4d1b3db37717598e59 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Tue, 13 Dec 2016 15:44:29 +0100 Subject: [PATCH 36/74] Redisign & simplify flow e2e tests --- tasks/e2e.sh | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/tasks/e2e.sh b/tasks/e2e.sh index 3b57c93d5a8..7a31e12ec68 100755 --- a/tasks/e2e.sh +++ b/tasks/e2e.sh @@ -166,16 +166,14 @@ npm start -- --smoke-test # Test optional flow enabling cp src/App.js src/App.backup.js cp .gitignore .gitignore.backup -echo " -/* @flow */ -var wrong: string = 0; -" > src/App.js +echo "/* @flow */" > src/App.js cat src/App.backup.js >> src/App.js -CI=true npm run build >> errors.log 2>> errors.log || true -cat errors.log | grep "This type is incompatible with" +npm start -- --smoke-test +npm run build test -e .flowconfig +test -d flow-typed cat .gitignore | grep flow-typed -rm src/App.js .gitignore errors.log +rm src/App.js .gitignore cp src/App.backup.js src/App.js cp .gitignore.backup .gitignore rm src/App.backup.js .gitignore.backup .flowconfig @@ -218,16 +216,15 @@ npm start -- --smoke-test # Test optional flow enabling cp src/App.js src/App.backup.js cp .gitignore .gitignore.backup -echo " -/* @flow */ -var wrong: string = 0; -" > src/App.js +echo "/* @flow */" > src/App.js cat src/App.backup.js >> src/App.js -CI=true npm run build >> errors.log 2>> errors.log || true -cat errors.log | grep "This type is incompatible with" +cp src/App.js /Users/rricard/app.js +npm start -- --smoke-test +npm run build test -e .flowconfig +test -d flow-typed cat .gitignore | grep flow-typed -rm src/App.js .gitignore errors.log +rm src/App.js .gitignore cp src/App.backup.js src/App.js cp .gitignore.backup .gitignore rm src/App.backup.js .gitignore.backup .flowconfig From 3047218b10e8ca8662762c3ffc2f05d5adf8a072 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Tue, 13 Dec 2016 16:15:15 +0100 Subject: [PATCH 37/74] Correctly ignore invalid comp errors --- packages/react-dev-utils/FlowTypecheckPlugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 6e4c557f9d7..4aea3f17bcf 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -136,7 +136,7 @@ function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { ) // This operation will fail if react-scripts is a path to a tarball in the // package.json (like in End To End testing!). So we swallow this error. - .catch((e) => /invalid comparator/i.test(e.message) ? true : Promise.reject(e)) + .catch((e) => /(invalid comparator)|(unable to rebase the local cache repo)/i.test(e.message) ? true : Promise.reject(e)) .then(() => Promise.all( Object.keys(otherFlowTypedDefs).map((packageName) => execOneTime( flowTypedPath, From 09c2c13ac0da917634fd330dae8c552fac7ffcbe Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Tue, 13 Dec 2016 16:23:04 +0100 Subject: [PATCH 38/74] Add ref to an issue comment on the silencing --- packages/react-dev-utils/FlowTypecheckPlugin.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 4aea3f17bcf..fecdd33d56a 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -136,6 +136,7 @@ function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { ) // This operation will fail if react-scripts is a path to a tarball in the // package.json (like in End To End testing!). So we swallow this error. + // See https://github.com/flowtype/flow-typed/issues/399#issuecomment-266766678 .catch((e) => /(invalid comparator)|(unable to rebase the local cache repo)/i.test(e.message) ? true : Promise.reject(e)) .then(() => Promise.all( Object.keys(otherFlowTypedDefs).map((packageName) => execOneTime( From 3949fb3f771355171eea6f64a97278868d6332b4 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Tue, 13 Dec 2016 17:03:53 +0100 Subject: [PATCH 39/74] Remove debugging command in e2e.sh --- tasks/e2e.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tasks/e2e.sh b/tasks/e2e.sh index 7a31e12ec68..7143a944446 100755 --- a/tasks/e2e.sh +++ b/tasks/e2e.sh @@ -218,7 +218,6 @@ cp src/App.js src/App.backup.js cp .gitignore .gitignore.backup echo "/* @flow */" > src/App.js cat src/App.backup.js >> src/App.js -cp src/App.js /Users/rricard/app.js npm start -- --smoke-test npm run build test -e .flowconfig From 159638b98bdcfd2e138b039492526e0b9fd2f369 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Tue, 13 Dec 2016 17:14:40 +0100 Subject: [PATCH 40/74] Remove planning to add the feature in doc[ci skip] It's there now! --- packages/react-scripts/template/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/react-scripts/template/README.md b/packages/react-scripts/template/README.md index 2c3a3338114..c6fb7181509 100644 --- a/packages/react-scripts/template/README.md +++ b/packages/react-scripts/template/README.md @@ -488,7 +488,6 @@ Flow errors will show up alongside ESLint errors as you work on your application >Note: If your global flow installation version differs from react-scripts's flow version, you may experience slower compilation times while going back and forth between your app and your IDE (that may use your global flow). Ensure you have the same `flow-bin` version [here](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-dev-utils/package.json). You can optionally use an IDE like [Nuclide](https://nuclide.io/docs/languages/flow/) for a better integrated experience. -In the future we plan to integrate it into Create React App even more closely. To learn more about Flow, check out [its documentation](https://flowtype.org/). From 5105c29b5000850eb0b7eb6c78daadad0f3287e1 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Tue, 13 Dec 2016 17:36:07 +0100 Subject: [PATCH 41/74] No build test for flow (duplicate) --- tasks/e2e.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/tasks/e2e.sh b/tasks/e2e.sh index 7143a944446..a0a96833283 100755 --- a/tasks/e2e.sh +++ b/tasks/e2e.sh @@ -169,7 +169,6 @@ cp .gitignore .gitignore.backup echo "/* @flow */" > src/App.js cat src/App.backup.js >> src/App.js npm start -- --smoke-test -npm run build test -e .flowconfig test -d flow-typed cat .gitignore | grep flow-typed @@ -219,7 +218,6 @@ cp .gitignore .gitignore.backup echo "/* @flow */" > src/App.js cat src/App.backup.js >> src/App.js npm start -- --smoke-test -npm run build test -e .flowconfig test -d flow-typed cat .gitignore | grep flow-typed From 4fa4569db9668eaaf5d5f6a990b6f27a556d766c Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Tue, 13 Dec 2016 18:37:50 +0100 Subject: [PATCH 42/74] Simpler second flow test This also let it test the idempotence of the flow addition --- tasks/e2e.sh | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/tasks/e2e.sh b/tasks/e2e.sh index a0a96833283..5df6fb5a68a 100755 --- a/tasks/e2e.sh +++ b/tasks/e2e.sh @@ -165,18 +165,15 @@ npm start -- --smoke-test # Test optional flow enabling cp src/App.js src/App.backup.js -cp .gitignore .gitignore.backup echo "/* @flow */" > src/App.js cat src/App.backup.js >> src/App.js npm start -- --smoke-test test -e .flowconfig test -d flow-typed cat .gitignore | grep flow-typed -rm src/App.js .gitignore +rm src/App.js cp src/App.backup.js src/App.js -cp .gitignore.backup .gitignore -rm src/App.backup.js .gitignore.backup .flowconfig -rm -rf flow-typed +rm src/App.backup.js # ****************************************************************************** # Finally, let's check that everything still works after ejecting. @@ -218,14 +215,8 @@ cp .gitignore .gitignore.backup echo "/* @flow */" > src/App.js cat src/App.backup.js >> src/App.js npm start -- --smoke-test -test -e .flowconfig -test -d flow-typed -cat .gitignore | grep flow-typed -rm src/App.js .gitignore cp src/App.backup.js src/App.js -cp .gitignore.backup .gitignore -rm src/App.backup.js .gitignore.backup .flowconfig -rm -rf flow-typed +rm src/App.backup.js # ****************************************************************************** # Test --scripts-version with a version number From ba483c66571c2e58373b0237516eadd5e4ded4de Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Wed, 14 Dec 2016 12:16:02 +0100 Subject: [PATCH 43/74] After eject, the .flowconfig should stay --- tasks/e2e.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/tasks/e2e.sh b/tasks/e2e.sh index 5df6fb5a68a..ecdde8bbe22 100755 --- a/tasks/e2e.sh +++ b/tasks/e2e.sh @@ -196,7 +196,6 @@ test -e build/static/js/*.js test -e build/static/css/*.css test -e build/static/media/*.svg test -e build/favicon.ico -test ! -e .flowconfig # Run tests, overring the watch option to disable it. # `CI=true npm test` won't work here because `npm test` becomes just `jest`. From a48e8c3ca023f11e48fdc05b6c79012ade173009 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Wed, 14 Dec 2016 12:50:17 +0100 Subject: [PATCH 44/74] Use Flow 0.37.0 --- packages/react-dev-utils/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index 89776f48207..f075b0dac90 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -27,7 +27,7 @@ "ansi-html": "0.0.5", "chalk": "1.1.3", "escape-string-regexp": "1.0.5", - "flow-bin": "^0.36.0", + "flow-bin": "^0.37.0", "flow-typed": "^2.0.0", "html-entities": "1.2.0", "opn": "4.0.2", From 9e1957e141efc66f13150a4182a22dc41cf27feb Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Wed, 14 Dec 2016 17:56:56 +0100 Subject: [PATCH 45/74] Directly target the flow-typed executable Not the .bin version --- packages/react-dev-utils/FlowTypecheckPlugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index fecdd33d56a..9323683a01a 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -3,7 +3,7 @@ var path = require('path'); var chalk = require('chalk'); var childProcess = require('child_process'); var flowBinPath = require('flow-bin'); -const flowTypedPath = path.join(__dirname, 'node_modules', '.bin', 'flow-typed'); +const flowTypedPath = path.join(__dirname, 'node_modules', 'flow-typed', 'dist', 'cli.js'); function stripFlowLoadingIndicators(message) { var newMessage = message; From c8340958cd1418f4dc6a95f4f9d0484577c08f3b Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Wed, 14 Dec 2016 18:04:07 +0100 Subject: [PATCH 46/74] Revert "Directly target the flow-typed executable" This reverts commit 9e1957e141efc66f13150a4182a22dc41cf27feb. --- packages/react-dev-utils/FlowTypecheckPlugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 9323683a01a..fecdd33d56a 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -3,7 +3,7 @@ var path = require('path'); var chalk = require('chalk'); var childProcess = require('child_process'); var flowBinPath = require('flow-bin'); -const flowTypedPath = path.join(__dirname, 'node_modules', 'flow-typed', 'dist', 'cli.js'); +const flowTypedPath = path.join(__dirname, 'node_modules', '.bin', 'flow-typed'); function stripFlowLoadingIndicators(message) { var newMessage = message; From f5d39828ff32755e314c5aca72a70256313e02e0 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Thu, 15 Dec 2016 16:37:53 +0100 Subject: [PATCH 47/74] Find flow-typed in the npm flat struct --- packages/react-dev-utils/FlowTypecheckPlugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index fecdd33d56a..ea5b3584fc1 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -3,7 +3,7 @@ var path = require('path'); var chalk = require('chalk'); var childProcess = require('child_process'); var flowBinPath = require('flow-bin'); -const flowTypedPath = path.join(__dirname, 'node_modules', '.bin', 'flow-typed'); +const flowTypedPath = path.join(__dirname, '..' , '..', 'node_modules', '.bin', 'flow-typed'); function stripFlowLoadingIndicators(message) { var newMessage = message; From cfc1115e4a9a544ee5998702b4ef8ce78c45d68d Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Thu, 15 Dec 2016 16:49:43 +0100 Subject: [PATCH 48/74] Define flow-typed path with project root --- packages/react-dev-utils/FlowTypecheckPlugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index ea5b3584fc1..93b6fde05e2 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -3,7 +3,6 @@ var path = require('path'); var chalk = require('chalk'); var childProcess = require('child_process'); var flowBinPath = require('flow-bin'); -const flowTypedPath = path.join(__dirname, '..' , '..', 'node_modules', '.bin', 'flow-typed'); function stripFlowLoadingIndicators(message) { var newMessage = message; @@ -109,6 +108,7 @@ function getFlowVersion(options) { } function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { + const flowTypedPath = path.join(projectPath, 'node_modules', '.bin', 'flow-typed'); const flowconfigPath = path.join(projectPath, '.flowconfig'); const gitignorePath = path.join(projectPath, '.gitignore'); return getFlowVersion().then(localVersion => From 030f7c173f42543b476c2e7c75a451e4728fcd01 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Thu, 15 Dec 2016 17:15:42 +0100 Subject: [PATCH 49/74] Resolve flow-typed via module resolution And boot it like the CLI boots in flow-typed sources --- .../react-dev-utils/FlowTypecheckPlugin.js | 2 +- packages/react-dev-utils/package.json | 1 + packages/react-dev-utils/runFlowTyped.js | 26 +++++++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 packages/react-dev-utils/runFlowTyped.js diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 93b6fde05e2..597e33647ea 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -3,6 +3,7 @@ var path = require('path'); var chalk = require('chalk'); var childProcess = require('child_process'); var flowBinPath = require('flow-bin'); +const flowTypedPath = path.join(__dirname, 'runFlowTyped.js'); function stripFlowLoadingIndicators(message) { var newMessage = message; @@ -108,7 +109,6 @@ function getFlowVersion(options) { } function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { - const flowTypedPath = path.join(projectPath, 'node_modules', '.bin', 'flow-typed'); const flowconfigPath = path.join(projectPath, '.flowconfig'); const gitignorePath = path.join(projectPath, '.gitignore'); return getFlowVersion().then(localVersion => diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index f075b0dac90..5ed882dd27b 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -13,6 +13,7 @@ "files": [ "clearConsole.js", "FlowTypecheckPlugin.js", + "runFlowTyped.js", "checkRequiredFiles.js", "formatWebpackMessages.js", "getProcessForPort.js", diff --git a/packages/react-dev-utils/runFlowTyped.js b/packages/react-dev-utils/runFlowTyped.js new file mode 100644 index 00000000000..39364cc31c3 --- /dev/null +++ b/packages/react-dev-utils/runFlowTyped.js @@ -0,0 +1,26 @@ +#!/usr/bin/env node +var flowTyped = require('flow-typed'); +var fs = require('fs'); +var path = require('path'); + +// Extracted from flow-typed repository: https://github.com/flowtype/flow-typed/blob/master/cli/src/cli.js#L67 +/** + * Look to see if the CWD is within an npm project. If it is, and that project + * has a flow-typed CLI `npm install`ed, use that version instead of the global + * version of the CLI. + */ +const CWD = process.cwd(); +let currDir = CWD; +let lastDir = null; +while (currDir !== lastDir) { + const localCLIPath = path.join(currDir, 'node_modules', '.bin', 'flow-typed'); + try { + if (fs.statSync(localCLIPath).isFile()) { + flowTyped.runCLI = require.call(null, localCLIPath).runCLI; + break; + } + } catch (e) { /* File doesn't exist, move up a dir... */ } + lastDir = currDir; + currDir = path.resolve(currDir, '..'); +} +flowTyped.runCLI(); From 37444f2aec66bbc3279ed05618742b56a0924015 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Thu, 15 Dec 2016 17:19:46 +0100 Subject: [PATCH 50/74] Remove unneeded complexity --- packages/react-dev-utils/runFlowTyped.js | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/packages/react-dev-utils/runFlowTyped.js b/packages/react-dev-utils/runFlowTyped.js index 39364cc31c3..07f93e6d119 100644 --- a/packages/react-dev-utils/runFlowTyped.js +++ b/packages/react-dev-utils/runFlowTyped.js @@ -1,26 +1,4 @@ #!/usr/bin/env node var flowTyped = require('flow-typed'); -var fs = require('fs'); -var path = require('path'); -// Extracted from flow-typed repository: https://github.com/flowtype/flow-typed/blob/master/cli/src/cli.js#L67 -/** - * Look to see if the CWD is within an npm project. If it is, and that project - * has a flow-typed CLI `npm install`ed, use that version instead of the global - * version of the CLI. - */ -const CWD = process.cwd(); -let currDir = CWD; -let lastDir = null; -while (currDir !== lastDir) { - const localCLIPath = path.join(currDir, 'node_modules', '.bin', 'flow-typed'); - try { - if (fs.statSync(localCLIPath).isFile()) { - flowTyped.runCLI = require.call(null, localCLIPath).runCLI; - break; - } - } catch (e) { /* File doesn't exist, move up a dir... */ } - lastDir = currDir; - currDir = path.resolve(currDir, '..'); -} flowTyped.runCLI(); From 08ca7837c946c77278403c218a8f723dcf75a799 Mon Sep 17 00:00:00 2001 From: Robin Ricard Date: Thu, 15 Dec 2016 17:27:31 +0100 Subject: [PATCH 51/74] Make run flow-typed executable --- packages/react-dev-utils/runFlowTyped.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 packages/react-dev-utils/runFlowTyped.js diff --git a/packages/react-dev-utils/runFlowTyped.js b/packages/react-dev-utils/runFlowTyped.js old mode 100644 new mode 100755 From 51cca10431be2a3cf0b2cc0c5048fb08f1d1a15d Mon Sep 17 00:00:00 2001 From: Gregor Weber Date: Sat, 18 Mar 2017 21:06:31 +0100 Subject: [PATCH 52/74] Don't put flow-typed on gitignore --- packages/react-dev-utils/FlowTypecheckPlugin.js | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 597e33647ea..9f742b6ea25 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -110,8 +110,7 @@ function getFlowVersion(options) { function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { const flowconfigPath = path.join(projectPath, '.flowconfig'); - const gitignorePath = path.join(projectPath, '.gitignore'); - return getFlowVersion().then(localVersion => + return getFlowVersion().then(localVersion => getFlowVersion({global: true}).catch(() => localVersion) .then(globalVersion => globalVersion !== localVersion ? @@ -124,11 +123,7 @@ function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { ) ) .then(localVersion => Promise.all([ - writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n')) - .then(wroteFlowconfig => wroteFlowconfig ? - writeInFileIfNotPresent(gitignorePath, 'flow-typed', 'flow-typed/npm') : - false - ), + writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n')), execOneTime( flowTypedPath, ['install', '--overwrite', '--flowVersion=' + localVersion], From a3a78f77e93517a41f42ac7c001d7df93489318b Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 16:39:29 -0400 Subject: [PATCH 53/74] Appease linter --- .../react-dev-utils/FlowTypecheckPlugin.js | 235 +++++++++--------- packages/react-dev-utils/runFlowTyped.js | 12 + 2 files changed, 128 insertions(+), 119 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 9f742b6ea25..3fbf2059e8f 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -1,3 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + var fs = require('fs'); var path = require('path'); var chalk = require('chalk'); @@ -7,15 +18,15 @@ const flowTypedPath = path.join(__dirname, 'runFlowTyped.js'); function stripFlowLoadingIndicators(message) { var newMessage = message; - var launchingIndex = newMessage.indexOf("Launching Flow server for"); + var launchingIndex = newMessage.indexOf('Launching Flow server for'); if (launchingIndex >= 0) { newMessage = newMessage.slice(0, launchingIndex); } - var stillIndex = newMessage.indexOf("flow is still initializing"); + var stillIndex = newMessage.indexOf('flow is still initializing'); if (stillIndex >= 0) { newMessage = newMessage.slice(0, stillIndex); } - var notRespIndex = newMessage.indexOf("The flow server is not responding"); + var notRespIndex = newMessage.indexOf('The flow server is not responding'); if (notRespIndex >= 0) { newMessage = newMessage.slice(0, notRespIndex); } @@ -24,13 +35,9 @@ function stripFlowLoadingIndicators(message) { function execOneTime(command, args, options) { return new Promise((resolve, reject) => { - var stdout = new Buffer(""); - var stderr = new Buffer(""); - var oneTimeProcess = childProcess.spawn( - command, - args, - options - ); + var stdout = new Buffer(''); + var stderr = new Buffer(''); + var oneTimeProcess = childProcess.spawn(command, args, options); oneTimeProcess.stdout.on('data', chunk => { stdout = Buffer.concat([stdout, chunk]); }); @@ -43,9 +50,7 @@ function execOneTime(command, args, options) { case 0: return resolve(stdout); default: - return reject(new Error( - Buffer.concat([stdout, stderr]).toString() - )); + return reject(new Error(Buffer.concat([stdout, stderr]).toString())); } }); }); @@ -68,92 +73,77 @@ function writeFileIfDoesNotExist(path, data) { }); } -function writeInFileIfNotPresent(path, contentToAssert, contentToAppend) { - return new Promise((resolve, reject) => { - fs.exists(path, exists => { - if (!exists) { - fs.writeFile(path, contentToAppend, err => { - if (err) { - return reject(err); - } - resolve(true); - }); - } else { - fs.readFile(path, (err, existingContent) => { - if (err) { - return reject(err); - } - if (existingContent.indexOf(contentToAssert) < 0) { - fs.appendFile(path, contentToAppend, err => { - if (err) { - return reject(err); - } - resolve(true); - }); - } else { - resolve(false); - } - }); - } - }); - }); -} - function getFlowVersion(options) { - return execOneTime( - (options || {}).global ? "flow" : flowBinPath, - ['version', '--json'] - ) - .then(rawData => JSON.parse(rawData)) - .then(versionData => versionData.semver); + return execOneTime((options || {}).global ? 'flow' : flowBinPath, [ + 'version', + '--json', + ]) + .then(rawData => JSON.parse(rawData)) + .then(versionData => versionData.semver); } function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { const flowconfigPath = path.join(projectPath, '.flowconfig'); - return getFlowVersion().then(localVersion => - getFlowVersion({global: true}).catch(() => localVersion) - .then(globalVersion => - globalVersion !== localVersion ? - Promise.reject(new Error( - 'Flow integration was disabled because the global Flow version does not match.\n' + - 'You may either remove the global Flow installation or install a compatible version:\n' + - ' ' + chalk.cyan('npm') + ' install -g flow-bin@' + localVersion - )) : - localVersion - ) - ) - .then(localVersion => Promise.all([ - writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n')), - execOneTime( - flowTypedPath, - ['install', '--overwrite', '--flowVersion=' + localVersion], - { cwd: projectPath } - ) - // This operation will fail if react-scripts is a path to a tarball in the - // package.json (like in End To End testing!). So we swallow this error. - // See https://github.com/flowtype/flow-typed/issues/399#issuecomment-266766678 - .catch((e) => /(invalid comparator)|(unable to rebase the local cache repo)/i.test(e.message) ? true : Promise.reject(e)) - .then(() => Promise.all( - Object.keys(otherFlowTypedDefs).map((packageName) => execOneTime( + return getFlowVersion() + .then(localVersion => + getFlowVersion({ global: true }) + .catch(() => localVersion) + .then( + globalVersion => + globalVersion !== localVersion + ? Promise.reject( + new Error( + 'Flow integration was disabled because the global Flow version does not match.\n' + + 'You may either remove the global Flow installation or install a compatible version:\n' + + ' ' + + chalk.cyan('npm') + + ' install -g flow-bin@' + + localVersion + ) + ) + : localVersion + )) + .then(localVersion => Promise.all([ + writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n')), + execOneTime( flowTypedPath, - [ - 'install', - packageName + '@' + otherFlowTypedDefs[packageName], - '--overwrite', - '--flowVersion=' + localVersion - ], + ['install', '--overwrite', '--flowVersion=' + localVersion], { cwd: projectPath } - )) - )) - ])); + ) + // This operation will fail if react-scripts is a path to a tarball in the + // package.json (like in End To End testing!). So we swallow this error. + // See https://github.com/flowtype/flow-typed/issues/399#issuecomment-266766678 + .catch( + e => + /(invalid comparator)|(unable to rebase the local cache repo)/i.test( + e.message + ) + ? true + : Promise.reject(e) + ) + .then(() => + Promise.all( + Object.keys( + otherFlowTypedDefs + ).map(packageName => + execOneTime( + flowTypedPath, + [ + 'install', + packageName + '@' + otherFlowTypedDefs[packageName], + '--overwrite', + '--flowVersion=' + localVersion, + ], + { cwd: projectPath } + )) + )), + ])); } function flowCheck(projectPath) { - return execOneTime( - flowBinPath, - ['status', '--color=always'], - { cwd: projectPath } - ); + return execOneTime(flowBinPath, ['status', '--color=always'], { + cwd: projectPath, + }); } function FlowTypecheckPlugin(options) { @@ -173,34 +163,38 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { var flowShouldRun = false; var flowErrorOutput = null; - // During module traversal, assert the presence of an @ flow in a module - compiler.plugin('compilation', (compilation, params) => { + // During module traversal, assert the presence of an @flow in a module + compiler.plugin('compilation', compilation => { compilation.plugin('normal-module-loader', (loaderContext, module) => { // We're only checking the presence of flow in non-node_modules // (some dependencies may keep their flow comments, we don't want to match them) - if (module.resource.indexOf("node_modules") < 0) { + if (module.resource.indexOf('node_modules') < 0) { // We use webpack's cached FileSystem to avoid slowing down compilation loaderContext.fs.readFile(module.resource, (err, data) => { if (data && data.toString().indexOf('@flow') >= 0) { if (!flowActiveOnProject) { - flowInitializationPromise = (!compiler.parentCompilation ? - initializeFlow( - compiler.options.context, this.flowconfig, this.otherFlowTypedDefs - ) : Promise.resolve() - ) - .then(() => { - flowInitialized = true; - }, e => { - flowInitError = e; - return Promise.reject(e); - }); + flowInitializationPromise = (!compiler.parentCompilation + ? initializeFlow( + compiler.options.context, + this.flowconfig, + this.otherFlowTypedDefs + ) + : Promise.resolve()).then( + () => { + flowInitialized = true; + }, + e => { + flowInitError = e; + return Promise.reject(e); + } + ); flowActiveOnProject = true; } flowShouldRun = true; } }); } - }) + }); }); // While emitting, run a flow check if flow has been detected @@ -208,21 +202,24 @@ FlowTypecheckPlugin.prototype.apply = function(compiler) { // Only if a file with @ flow has been changed if (flowShouldRun) { flowShouldRun = false; - (flowInitialized ? - (flowInitError ? Promise.reject(flowInitError) : Promise.resolve()) : - flowInitializationPromise) - .then( - () => flowCheck(compiler.options.context), - e => Promise.reject(e) // don't run a check if init errored, just carry the error - ) - .then(() => { - flowErrorOutput = null; - compilation.flowPassed = true; - }, error => { - flowErrorOutput = stripFlowLoadingIndicators(error.message); - compilation.warnings.push(flowErrorOutput); - }) - .then(callback); + (flowInitialized + ? flowInitError ? Promise.reject(flowInitError) : Promise.resolve() + : flowInitializationPromise) + .then( + () => flowCheck(compiler.options.context), + e => Promise.reject(e) // don't run a check if init errored, just carry the error + ) + .then( + () => { + flowErrorOutput = null; + compilation.flowPassed = true; + }, + error => { + flowErrorOutput = stripFlowLoadingIndicators(error.message); + compilation.warnings.push(flowErrorOutput); + } + ) + .then(callback); } else { // Output a warning if flow failed in a previous run if (flowErrorOutput) { diff --git a/packages/react-dev-utils/runFlowTyped.js b/packages/react-dev-utils/runFlowTyped.js index 07f93e6d119..51dc717e625 100755 --- a/packages/react-dev-utils/runFlowTyped.js +++ b/packages/react-dev-utils/runFlowTyped.js @@ -1,4 +1,16 @@ #!/usr/bin/env node + +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + var flowTyped = require('flow-typed'); flowTyped.runCLI(); From eb9e341dc8fa79e4b632fad4d842ccca236eafb3 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 16:50:54 -0400 Subject: [PATCH 54/74] Update flow --- packages/react-dev-utils/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index 6a8f1c0676c..fe988a3029e 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -33,7 +33,7 @@ "chalk": "1.1.3", "escape-string-regexp": "1.0.5", "filesize": "3.3.0", - "flow-bin": "^0.37.0", + "flow-bin": "^0.44.2", "flow-typed": "^2.0.0", "gzip-size": "3.0.0", "html-entities": "1.2.0", From 137f59f2332f955bbff4a3af562f2cf8bcb3283f Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 18:32:05 -0400 Subject: [PATCH 55/74] Add basic flow type checking --- .../react-dev-utils/FlowTypecheckPlugin.js | 286 ++++++------------ 1 file changed, 93 insertions(+), 193 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 3fbf2059e8f..c035ad5c929 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -9,31 +9,13 @@ 'use strict'; -var fs = require('fs'); -var path = require('path'); -var chalk = require('chalk'); -var childProcess = require('child_process'); -var flowBinPath = require('flow-bin'); -const flowTypedPath = path.join(__dirname, 'runFlowTyped.js'); +const fs = require('fs'); +const path = require('path'); +const chalk = require('chalk'); +const childProcess = require('child_process'); +const flowBinPath = require('flow-bin'); -function stripFlowLoadingIndicators(message) { - var newMessage = message; - var launchingIndex = newMessage.indexOf('Launching Flow server for'); - if (launchingIndex >= 0) { - newMessage = newMessage.slice(0, launchingIndex); - } - var stillIndex = newMessage.indexOf('flow is still initializing'); - if (stillIndex >= 0) { - newMessage = newMessage.slice(0, stillIndex); - } - var notRespIndex = newMessage.indexOf('The flow server is not responding'); - if (notRespIndex >= 0) { - newMessage = newMessage.slice(0, notRespIndex); - } - return newMessage; -} - -function execOneTime(command, args, options) { +function exec(command, args, options) { return new Promise((resolve, reject) => { var stdout = new Buffer(''); var stderr = new Buffer(''); @@ -47,189 +29,107 @@ function execOneTime(command, args, options) { oneTimeProcess.on('error', error => reject(error)); oneTimeProcess.on('exit', code => { switch (code) { - case 0: + case 0: { return resolve(stdout); - default: + } + default: { return reject(new Error(Buffer.concat([stdout, stderr]).toString())); + } } }); }); } -function writeFileIfDoesNotExist(path, data) { - return new Promise((resolve, reject) => { - fs.exists(path, exists => { - if (!exists) { - fs.writeFile(path, data, err => { - if (err) { - return reject(err); - } - resolve(true); - }); - } else { - resolve(false); - } - }); - }); -} - -function getFlowVersion(options) { - return execOneTime((options || {}).global ? 'flow' : flowBinPath, [ - 'version', - '--json', - ]) - .then(rawData => JSON.parse(rawData)) - .then(versionData => versionData.semver); +function filterFlowErrors(error) { + return error + .toString() + .split('\n') + .filter(line => { + return !(/flow is still initializing/.test(line) || + /Found \d+ error/.test(line) || + line.trim().length === 0); + }) + .join('\n'); } -function initializeFlow(projectPath, flowconfig, otherFlowTypedDefs) { - const flowconfigPath = path.join(projectPath, '.flowconfig'); - return getFlowVersion() - .then(localVersion => - getFlowVersion({ global: true }) - .catch(() => localVersion) - .then( - globalVersion => - globalVersion !== localVersion - ? Promise.reject( - new Error( - 'Flow integration was disabled because the global Flow version does not match.\n' + - 'You may either remove the global Flow installation or install a compatible version:\n' + - ' ' + - chalk.cyan('npm') + - ' install -g flow-bin@' + - localVersion - ) - ) - : localVersion - )) - .then(localVersion => Promise.all([ - writeFileIfDoesNotExist(flowconfigPath, flowconfig.join('\n')), - execOneTime( - flowTypedPath, - ['install', '--overwrite', '--flowVersion=' + localVersion], - { cwd: projectPath } - ) - // This operation will fail if react-scripts is a path to a tarball in the - // package.json (like in End To End testing!). So we swallow this error. - // See https://github.com/flowtype/flow-typed/issues/399#issuecomment-266766678 - .catch( - e => - /(invalid comparator)|(unable to rebase the local cache repo)/i.test( - e.message - ) - ? true - : Promise.reject(e) - ) - .then(() => - Promise.all( - Object.keys( - otherFlowTypedDefs - ).map(packageName => - execOneTime( - flowTypedPath, - [ - 'install', - packageName + '@' + otherFlowTypedDefs[packageName], - '--overwrite', - '--flowVersion=' + localVersion, - ], - { cwd: projectPath } - )) - )), - ])); -} - -function flowCheck(projectPath) { - return execOneTime(flowBinPath, ['status', '--color=always'], { - cwd: projectPath, - }); -} +class FlowTypecheckPlugin { + constructor(options) { + this.shouldRun = false; + this.flowStarted = false; + } -function FlowTypecheckPlugin(options) { - options = options || {}; - // Contents of the generated .flowconfig if it doesn't exist - this.flowconfig = options.flowconfig || []; - // Load up other flow-typed defs outside of the package.json (implicit packages behind react-scripts) - // Key is the package name, value is the version number - this.otherFlowTypedDefs = options.otherFlowTypedDefs || {}; -} + startFlow(cwd) { + if (this.flowStarted) { + return Promise.resolve(); + } + const flowConfigPath = path.join(cwd, '.flowconfig'); + return new Promise((resolve, reject) => { + fs.stat(flowConfigPath, (err, stats) => { + if (err) { + resolve(exec(flowBinPath, ['init'], { cwd })); + } else { + resolve(); + } + }); + }) + .then(() => + exec(flowBinPath, ['stop'], { cwd }).then(() => + exec(flowBinPath, ['start'], { cwd }))) + .then(() => { + this.flowStarted = true; + }); + } -FlowTypecheckPlugin.prototype.apply = function(compiler) { - var flowActiveOnProject = false; - var flowInitialized = false; - var flowInitError = null; - var flowInitializationPromise; - var flowShouldRun = false; - var flowErrorOutput = null; + apply(compiler) { + compiler.plugin('compile', () => { + this.shouldRun = false; + }); - // During module traversal, assert the presence of an @flow in a module - compiler.plugin('compilation', compilation => { - compilation.plugin('normal-module-loader', (loaderContext, module) => { - // We're only checking the presence of flow in non-node_modules - // (some dependencies may keep their flow comments, we don't want to match them) - if (module.resource.indexOf('node_modules') < 0) { - // We use webpack's cached FileSystem to avoid slowing down compilation - loaderContext.fs.readFile(module.resource, (err, data) => { - if (data && data.toString().indexOf('@flow') >= 0) { - if (!flowActiveOnProject) { - flowInitializationPromise = (!compiler.parentCompilation - ? initializeFlow( - compiler.options.context, - this.flowconfig, - this.otherFlowTypedDefs - ) - : Promise.resolve()).then( - () => { - flowInitialized = true; - }, - e => { - flowInitError = e; - return Promise.reject(e); - } - ); - flowActiveOnProject = true; - } - flowShouldRun = true; - } - }); - } + compiler.plugin('compilation', compilation => { + compilation.plugin('normal-module-loader', (loaderContext, module) => { + if ( + this.shouldRun || + module.resource.indexOf('node_modules') !== -1 || + !/[.]js(x)?$/.test(module.resource) + ) { + return; + } + if ( + loaderContext.fs + .readFileSync(module.resource, 'utf8') + .indexOf('@flow') !== -1 + ) { + this.shouldRun = true; + } + }); }); - }); - // While emitting, run a flow check if flow has been detected - compiler.plugin('emit', (compilation, callback) => { - // Only if a file with @ flow has been changed - if (flowShouldRun) { - flowShouldRun = false; - (flowInitialized - ? flowInitError ? Promise.reject(flowInitError) : Promise.resolve() - : flowInitializationPromise) - .then( - () => flowCheck(compiler.options.context), - e => Promise.reject(e) // don't run a check if init errored, just carry the error - ) - .then( - () => { - flowErrorOutput = null; - compilation.flowPassed = true; - }, - error => { - flowErrorOutput = stripFlowLoadingIndicators(error.message); - compilation.warnings.push(flowErrorOutput); - } - ) - .then(callback); - } else { - // Output a warning if flow failed in a previous run - if (flowErrorOutput) { - compilation.warnings.push(flowErrorOutput); - } else { - compilation.flowPassed = true; + // Run lint checks + compiler.plugin('emit', (compilation, callback) => { + if (!this.shouldRun) { + //TODO: flow did not run + callback(); + return; } - callback(); - } - }); -}; + const cwd = compiler.options.context; + this.startFlow(cwd) + .then(() => { + exec(flowBinPath, ['status', '--color=always'], { cwd }) + .then(() => { + callback(); + //TODO: flow ran, and there was no errors + }) + .catch(e => { + compilation.warnings.push(filterFlowErrors(e)); + callback(); + }); + }) + .catch(e => { + //TODO: flow failed, set a warning + callback(); + }); + }); + } +} module.exports = FlowTypecheckPlugin; From 2fd2dbcb4716f3a69023294fbac078f66759e666 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 18:58:31 -0400 Subject: [PATCH 56/74] Adjust format function --- packages/react-dev-utils/FlowTypecheckPlugin.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index c035ad5c929..280886ba4c9 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -40,15 +40,15 @@ function exec(command, args, options) { }); } -function filterFlowErrors(error) { +function formatFlowErrors(error) { return error .toString() .split('\n') .filter(line => { return !(/flow is still initializing/.test(line) || - /Found \d+ error/.test(line) || - line.trim().length === 0); + /Found \d+ error/.test(line)); }) + .map(line => line.replace(/^Error:\s*/, '')) .join('\n'); } @@ -94,10 +94,9 @@ class FlowTypecheckPlugin { ) { return; } + const contents = loaderContext.fs.readFileSync(module.resource, 'utf8'); if ( - loaderContext.fs - .readFileSync(module.resource, 'utf8') - .indexOf('@flow') !== -1 + /^\s*\/\/.*@flow/.test(contents) || /^\s*\/\*.*@flow/.test(contents) ) { this.shouldRun = true; } @@ -120,7 +119,7 @@ class FlowTypecheckPlugin { //TODO: flow ran, and there was no errors }) .catch(e => { - compilation.warnings.push(filterFlowErrors(e)); + compilation.warnings.push(formatFlowErrors(e)); callback(); }); }) From 7c5749d4b63a888e599529654a0b6a4225fa715f Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 19:59:15 -0400 Subject: [PATCH 57/74] Remove flow-typed stub --- packages/react-dev-utils/FlowTypecheckPlugin.js | 1 - packages/react-dev-utils/package.json | 1 - packages/react-dev-utils/runFlowTyped.js | 16 ---------------- .../react-scripts/config/webpack.config.dev.js | 8 ++------ .../react-scripts/config/webpack.config.prod.js | 8 ++------ 5 files changed, 4 insertions(+), 30 deletions(-) delete mode 100755 packages/react-dev-utils/runFlowTyped.js diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 280886ba4c9..b77265560e1 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -106,7 +106,6 @@ class FlowTypecheckPlugin { // Run lint checks compiler.plugin('emit', (compilation, callback) => { if (!this.shouldRun) { - //TODO: flow did not run callback(); return; } diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index fe988a3029e..b810e80b3b6 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -23,7 +23,6 @@ "openBrowser.js", "openChrome.applescript", "prompt.js", - "runFlowTyped.js", "WatchMissingNodeModulesPlugin.js", "webpackHotDevClient.js" ], diff --git a/packages/react-dev-utils/runFlowTyped.js b/packages/react-dev-utils/runFlowTyped.js deleted file mode 100755 index 51dc717e625..00000000000 --- a/packages/react-dev-utils/runFlowTyped.js +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env node - -/** - * Copyright (c) 2015-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -'use strict'; - -var flowTyped = require('flow-typed'); - -flowTyped.runCLI(); diff --git a/packages/react-scripts/config/webpack.config.dev.js b/packages/react-scripts/config/webpack.config.dev.js index 7f6bbbf20f7..1840751d8ce 100644 --- a/packages/react-scripts/config/webpack.config.dev.js +++ b/packages/react-scripts/config/webpack.config.dev.js @@ -242,12 +242,8 @@ module.exports = { // makes the discovery automatic so you don't have to restart. // See https://github.com/facebookincubator/create-react-app/issues/186 new WatchMissingNodeModulesPlugin(paths.appNodeModules), - // Trigger some typechecking if a file matches with an @ flow comment - new FlowTypecheckPlugin({ - otherFlowTypedDefs: { - jest: '18.0.0', - }, - }), + // Run Flow on files with the @flow header + new FlowTypecheckPlugin(), ], // Some libraries import Node modules but don't use them in the browser. // Tell Webpack to provide empty mocks for them so importing them works. diff --git a/packages/react-scripts/config/webpack.config.prod.js b/packages/react-scripts/config/webpack.config.prod.js index f4cf92466ab..89af42f3bde 100644 --- a/packages/react-scripts/config/webpack.config.prod.js +++ b/packages/react-scripts/config/webpack.config.prod.js @@ -278,12 +278,8 @@ module.exports = { new ManifestPlugin({ fileName: 'asset-manifest.json', }), - // Run Flow only if we see some @ flow annotations, will error on CI - new FlowTypecheckPlugin({ - otherFlowTypedDefs: { - jest: '18.0.0', - }, - }), + // Run Flow on files with the @flow header + new FlowTypecheckPlugin(), ], // Some libraries import Node modules but don't use them in the browser. // Tell Webpack to provide empty mocks for them so importing them works. From f4299ef824ce2e251b21ef8d348af802ad2b42ed Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 20:01:21 -0400 Subject: [PATCH 58/74] Use access instead of stat to prevent dual-compile --- packages/react-dev-utils/FlowTypecheckPlugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index b77265560e1..b0f409c8d78 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -64,7 +64,7 @@ class FlowTypecheckPlugin { } const flowConfigPath = path.join(cwd, '.flowconfig'); return new Promise((resolve, reject) => { - fs.stat(flowConfigPath, (err, stats) => { + fs.access(flowConfigPath, fs.constants.R_OK | fs.constants.W_OK, err => { if (err) { resolve(exec(flowBinPath, ['init'], { cwd })); } else { From a3fb2dff58075a83d62b7e1a7a036b55e9163aea Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 20:07:03 -0400 Subject: [PATCH 59/74] Add some friendly comments --- packages/react-dev-utils/FlowTypecheckPlugin.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index b0f409c8d78..3547db35bbe 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -62,6 +62,7 @@ class FlowTypecheckPlugin { if (this.flowStarted) { return Promise.resolve(); } + console.log(chalk.cyan('Starting the flow server ...')); const flowConfigPath = path.join(cwd, '.flowconfig'); return new Promise((resolve, reject) => { fs.access(flowConfigPath, fs.constants.R_OK | fs.constants.W_OK, err => { @@ -77,6 +78,11 @@ class FlowTypecheckPlugin { exec(flowBinPath, ['start'], { cwd }))) .then(() => { this.flowStarted = true; + console.log( + chalk.yellow( + 'Flow is initializing, ' + chalk.bold('this might take a while ...') + ) + ); }); } From de99610715e66ad3f19d879b6d8948d95a9ba032 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 20:26:58 -0400 Subject: [PATCH 60/74] Lock down flow version --- .../react-dev-utils/FlowTypecheckPlugin.js | 20 +++++++++++++------ packages/react-dev-utils/package.json | 3 +-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 3547db35bbe..b0e9fb7a415 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -78,11 +78,6 @@ class FlowTypecheckPlugin { exec(flowBinPath, ['start'], { cwd }))) .then(() => { this.flowStarted = true; - console.log( - chalk.yellow( - 'Flow is initializing, ' + chalk.bold('this might take a while ...') - ) - ); }); } @@ -116,8 +111,19 @@ class FlowTypecheckPlugin { return; } const cwd = compiler.options.context; + const first = !this.flowStarted; this.startFlow(cwd) .then(() => { + if (first) { + console.log( + chalk.yellow( + 'Flow is initializing, ' + + chalk.bold('this might take a while...') + ) + ); + } else { + console.log('Running flow...'); + } exec(flowBinPath, ['status', '--color=always'], { cwd }) .then(() => { callback(); @@ -129,7 +135,9 @@ class FlowTypecheckPlugin { }); }) .catch(e => { - //TODO: flow failed, set a warning + compilation.warnings.push( + 'Flow checking has been disabled due to an error in Flow.' + ); callback(); }); }); diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index b810e80b3b6..b35129fbbc5 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -32,8 +32,7 @@ "chalk": "1.1.3", "escape-string-regexp": "1.0.5", "filesize": "3.3.0", - "flow-bin": "^0.44.2", - "flow-typed": "^2.0.0", + "flow-bin": "0.44.2", "gzip-size": "3.0.0", "html-entities": "1.2.0", "opn": "4.0.2", From 29628efd12154f281e4de6892d0e5553a4f51e6b Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 20:38:54 -0400 Subject: [PATCH 61/74] Update comment logs --- packages/react-dev-utils/FlowTypecheckPlugin.js | 1 - .../react-scripts/scripts/utils/createWebpackCompiler.js | 8 +------- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index b0e9fb7a415..016a9b5abce 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -127,7 +127,6 @@ class FlowTypecheckPlugin { exec(flowBinPath, ['status', '--color=always'], { cwd }) .then(() => { callback(); - //TODO: flow ran, and there was no errors }) .catch(e => { compilation.warnings.push(formatFlowErrors(e)); diff --git a/packages/react-scripts/scripts/utils/createWebpackCompiler.js b/packages/react-scripts/scripts/utils/createWebpackCompiler.js index f2d6880d2b7..2f6f0cac2b3 100644 --- a/packages/react-scripts/scripts/utils/createWebpackCompiler.js +++ b/packages/react-scripts/scripts/utils/createWebpackCompiler.js @@ -74,9 +74,6 @@ module.exports = function createWebpackCompiler(config, onReadyCallback) { if (isSuccessful) { console.log(chalk.green('Compiled successfully!')); - if (messages.isFlowIntegrationErrorMessage) { - console.log(chalk.yellow('Flow checks were skipped.')); - } } if (typeof onReadyCallback === 'function') { @@ -115,16 +112,13 @@ module.exports = function createWebpackCompiler(config, onReadyCallback) { chalk.yellow('/* eslint-disable */') + ' to ignore all warnings in a file.' ); + // Teach some Flow tricks. console.log( 'Use ' + chalk.yellow('// $FlowFixMe') + ' to ignore flow-related warnings on the next line.' ); } - - if (messages.isFlowIntegrationErrorMessage) { - console.log(messages.isFlowIntegrationErrorMessage); - } }); return compiler; From 30494dcee3f7bc80543a80bd0a52a0a6d7c59013 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 20:41:57 -0400 Subject: [PATCH 62/74] Remove old method --- .../react-dev-utils/formatWebpackMessages.js | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/packages/react-dev-utils/formatWebpackMessages.js b/packages/react-dev-utils/formatWebpackMessages.js index f1cee7cc04c..098c48657e2 100644 --- a/packages/react-dev-utils/formatWebpackMessages.js +++ b/packages/react-dev-utils/formatWebpackMessages.js @@ -22,11 +22,6 @@ function isLikelyASyntaxError(message) { return message.indexOf(friendlySyntaxErrorLabel) !== -1; } -// Used to detect flow errors we want to swallow -function isFlowIntegrationErrorMessage(message) { - return /^flow integration was disabled/i.test(message); -} - // Cleans up webpack error messages. function formatMessage(message) { var lines = message.split('\n'); @@ -118,23 +113,15 @@ function formatMessage(message) { } function formatWebpackMessages(json) { - var formattedErrors = json.errors - .filter(m => !isFlowIntegrationErrorMessage(m)) - .map(function(message) { - return 'Error in ' + formatMessage(message); - }); - var formattedWarnings = json.warnings - .filter(m => !isFlowIntegrationErrorMessage(m)) - .map(function(message) { - return 'Warning in ' + formatMessage(message); - }); - var flowIntegrationError = [] - .concat(json.errors, json.warnings) - .find(isFlowIntegrationErrorMessage); + var formattedErrors = json.errors.map(function(message) { + return 'Error in ' + formatMessage(message); + }); + var formattedWarnings = json.warnings.map(function(message) { + return 'Warning in ' + formatMessage(message); + }); var result = { errors: formattedErrors, warnings: formattedWarnings, - flowIntegrationError: flowIntegrationError, }; if (result.errors.some(isLikelyASyntaxError)) { // If there are any syntax errors, show just them. From f850266fe07df22c8f93873bc3d556f519d75921 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 20:44:01 -0400 Subject: [PATCH 63/74] Remove from e2e --- tasks/e2e-simple.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/tasks/e2e-simple.sh b/tasks/e2e-simple.sh index 9e9b32657f0..8859b6f3502 100755 --- a/tasks/e2e-simple.sh +++ b/tasks/e2e-simple.sh @@ -246,8 +246,6 @@ echo "/* @flow */" > src/App.js cat src/App.backup.js >> src/App.js npm start -- --smoke-test test -e .flowconfig -test -d flow-typed -cat .gitignore | grep flow-typed rm src/App.js cp src/App.backup.js src/App.js rm src/App.backup.js From 8d1f51535eef59cbf8c37901fc448ddf21e3672a Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 21:32:56 -0400 Subject: [PATCH 64/74] Check flow version for performance --- .../react-dev-utils/FlowTypecheckPlugin.js | 76 +++++++++++++++---- 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 016a9b5abce..1ed363d5312 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -40,22 +40,49 @@ function exec(command, args, options) { }); } -function formatFlowErrors(error) { +function createVersionWarning(flowVersion) { + return 'Flow: ' + + chalk.red( + chalk.bold( + `Your global flow version is incompatible with this tool. +To fix warning, uninstall it or run \`npm install -g flow-bin@${flowVersion}\`.` + ) + ); +} + +function formatFlowErrors(error, flowVersion) { return error .toString() .split('\n') .filter(line => { return !(/flow is still initializing/.test(line) || - /Found \d+ error/.test(line)); + /Found \d+ error/.test(line) || + /The flow server is not responding/.test(line) || + /Going to launch a new one/.test(line) || + /The flow server is not responding/.test(line) || + /Spawned flow server/.test(line) || + /Logs will go to/.test(line) || + /version didn't match the client's/.test(line)); }) .map(line => line.replace(/^Error:\s*/, '')) .join('\n'); } +function getFlowVersion(global) { + return exec(global ? 'flow' : flowBinPath, ['version', '--json']) + .then(data => JSON.parse(data.toString('utf8')).semver || '0.0.0') + .catch(e => null); +} + class FlowTypecheckPlugin { constructor(options) { this.shouldRun = false; this.flowStarted = false; + + this.flowVersion = require(path.join( + __dirname, + 'package.json' + )).dependencies['flow-bin']; } startFlow(cwd) { @@ -64,15 +91,30 @@ class FlowTypecheckPlugin { } console.log(chalk.cyan('Starting the flow server ...')); const flowConfigPath = path.join(cwd, '.flowconfig'); - return new Promise((resolve, reject) => { - fs.access(flowConfigPath, fs.constants.R_OK | fs.constants.W_OK, err => { - if (err) { - resolve(exec(flowBinPath, ['init'], { cwd })); - } else { - resolve(); - } - }); - }) + return getFlowVersion(true) + .then(globalVersion => { + if (globalVersion === null) return; + return getFlowVersion(false).then(ourVersion => { + if (globalVersion !== ourVersion) { + return Promise.reject('__FLOW_VERSION_MISMATCH__'); + } + }); + }) + .then( + () => new Promise((resolve, reject) => { + fs.access( + flowConfigPath, + fs.constants.R_OK | fs.constants.W_OK, + err => { + if (err) { + resolve(exec(flowBinPath, ['init'], { cwd })); + } else { + resolve(); + } + } + ); + }) + ) .then(() => exec(flowBinPath, ['stop'], { cwd }).then(() => exec(flowBinPath, ['start'], { cwd }))) @@ -129,14 +171,18 @@ class FlowTypecheckPlugin { callback(); }) .catch(e => { - compilation.warnings.push(formatFlowErrors(e)); + compilation.warnings.push(formatFlowErrors(e, this.flowVersion)); callback(); }); }) .catch(e => { - compilation.warnings.push( - 'Flow checking has been disabled due to an error in Flow.' - ); + if (e === '__FLOW_VERSION_MISMATCH__') { + compilation.warnings.push(createVersionWarning(this.flowVersion)); + } else { + compilation.warnings.push( + 'Flow: Type checking has been disabled due to an error in Flow.' + ); + } callback(); }); }); From 1df2cb575d434c715d03008daab50f43deb06cf6 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 21:33:09 -0400 Subject: [PATCH 65/74] Remove warning prefix for flow messages --- packages/react-dev-utils/formatWebpackMessages.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-dev-utils/formatWebpackMessages.js b/packages/react-dev-utils/formatWebpackMessages.js index 098c48657e2..509d13d90ec 100644 --- a/packages/react-dev-utils/formatWebpackMessages.js +++ b/packages/react-dev-utils/formatWebpackMessages.js @@ -117,6 +117,8 @@ function formatWebpackMessages(json) { return 'Error in ' + formatMessage(message); }); var formattedWarnings = json.warnings.map(function(message) { + var formattedMessage = formatMessage(message); + if (/^Flow: /.test(message)) return formattedMessage; return 'Warning in ' + formatMessage(message); }); var result = { From 70ad8dd285abcc7ee43adb7afeda2555e810ec6a Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 21:33:19 -0400 Subject: [PATCH 66/74] Update README --- packages/react-scripts/template/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-scripts/template/README.md b/packages/react-scripts/template/README.md index e8d57fd45e2..50a6a9b0426 100644 --- a/packages/react-scripts/template/README.md +++ b/packages/react-scripts/template/README.md @@ -646,13 +646,12 @@ We suggest the following approach: Here is an example of adding a [customized Bootstrap](https://medium.com/@tacomanator/customizing-create-react-app-aa9ffb88165) that follows these steps. -## Adding Flow +## Using Flow Flow is a static type checker that helps you write code with fewer bugs. Check out this [introduction to using static types in JavaScript](https://medium.com/@preethikasireddy/why-use-static-types-in-javascript-part-1-8382da1e0adb) if you are new to this concept. -Flow typing is now supported out of the box. All you have to do is add the `/* @flow */` comment on top of files you -want to typecheck. If no `.flowconfig` is present, one will be generated for you. The script will also download type -definitions from [flow-typed](https://github.com/flowtype/flow-typed) automatically. +Flow typing is supported out of the box. All you have to do is add the `/* @flow */` comment on top of files you +want to typecheck. If no `.flowconfig` is present, one will be generated for you. Flow errors will show up alongside ESLint errors as you work on your application. @@ -661,6 +660,7 @@ Flow errors will show up alongside ESLint errors as you work on your application You can optionally use an IDE like [Nuclide](https://nuclide.io/docs/languages/flow/) for a better integrated experience. To learn more about Flow, check out [its documentation](https://flowtype.org/). +You may also want to learn how to use definitions from [flow-typed](https://github.com/flowtype/flow-typed). ## Adding Custom Environment Variables From 45841cd27eb9eb680940a25c78bb5f8d65a8db90 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 21:34:11 -0400 Subject: [PATCH 67/74] Don't double compute --- packages/react-dev-utils/formatWebpackMessages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/formatWebpackMessages.js b/packages/react-dev-utils/formatWebpackMessages.js index 509d13d90ec..a2ecdbe3cc1 100644 --- a/packages/react-dev-utils/formatWebpackMessages.js +++ b/packages/react-dev-utils/formatWebpackMessages.js @@ -119,7 +119,7 @@ function formatWebpackMessages(json) { var formattedWarnings = json.warnings.map(function(message) { var formattedMessage = formatMessage(message); if (/^Flow: /.test(message)) return formattedMessage; - return 'Warning in ' + formatMessage(message); + return 'Warning in ' + formattedMessage; }); var result = { errors: formattedErrors, From dab6f9d73a2ce23288dc345f564a6e0289f0c49a Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 21:44:27 -0400 Subject: [PATCH 68/74] Fix linter errors --- packages/react-dev-utils/FlowTypecheckPlugin.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 1ed363d5312..b9e52a546f4 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -50,7 +50,7 @@ To fix warning, uninstall it or run \`npm install -g flow-bin@${flowVersion}\`.` ); } -function formatFlowErrors(error, flowVersion) { +function formatFlowErrors(error) { return error .toString() .split('\n') @@ -71,11 +71,11 @@ function formatFlowErrors(error, flowVersion) { function getFlowVersion(global) { return exec(global ? 'flow' : flowBinPath, ['version', '--json']) .then(data => JSON.parse(data.toString('utf8')).semver || '0.0.0') - .catch(e => null); + .catch(() => null); } class FlowTypecheckPlugin { - constructor(options) { + constructor() { this.shouldRun = false; this.flowStarted = false; @@ -101,7 +101,7 @@ class FlowTypecheckPlugin { }); }) .then( - () => new Promise((resolve, reject) => { + () => new Promise(resolve => { fs.access( flowConfigPath, fs.constants.R_OK | fs.constants.W_OK, @@ -171,7 +171,7 @@ class FlowTypecheckPlugin { callback(); }) .catch(e => { - compilation.warnings.push(formatFlowErrors(e, this.flowVersion)); + compilation.warnings.push(formatFlowErrors(e)); callback(); }); }) From 1cb808eb4a00d49910e5b6cd0c8684162ef96b43 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Sat, 22 Apr 2017 22:08:57 -0400 Subject: [PATCH 69/74] Use default perm check --- packages/react-dev-utils/FlowTypecheckPlugin.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index b9e52a546f4..7ec08238bde 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -102,17 +102,13 @@ class FlowTypecheckPlugin { }) .then( () => new Promise(resolve => { - fs.access( - flowConfigPath, - fs.constants.R_OK | fs.constants.W_OK, - err => { - if (err) { - resolve(exec(flowBinPath, ['init'], { cwd })); - } else { - resolve(); - } + fs.access(flowConfigPath, err => { + if (err) { + resolve(exec(flowBinPath, ['init'], { cwd })); + } else { + resolve(); } - ); + }); }) ) .then(() => From e94387ca8de1b4ed68dda9ee7328c97e06c0a890 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Mon, 24 Apr 2017 16:31:30 -0400 Subject: [PATCH 70/74] Handle already running server since it exits with a non-zero --- packages/react-dev-utils/FlowTypecheckPlugin.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 7ec08238bde..390d1b9802a 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -111,9 +111,18 @@ class FlowTypecheckPlugin { }); }) ) - .then(() => - exec(flowBinPath, ['stop'], { cwd }).then(() => - exec(flowBinPath, ['start'], { cwd }))) + .then(() => exec(flowBinPath, ['stop'], { + cwd, + }).then(() => exec(flowBinPath, ['start'], { cwd }).catch(err => { + if ( + typeof err.message === 'string' && + err.message.indexOf('There is already a server running') !== -1 + ) { + return true; + } else { + throw err; + } + }))) .then(() => { this.flowStarted = true; }); From 45e6c29a5cdfb27e8cfac7948cdea2ff4f90ef3a Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Mon, 24 Apr 2017 16:32:28 -0400 Subject: [PATCH 71/74] Reduce nesting --- packages/react-dev-utils/FlowTypecheckPlugin.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 390d1b9802a..e0134bc9ce9 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -113,7 +113,8 @@ class FlowTypecheckPlugin { ) .then(() => exec(flowBinPath, ['stop'], { cwd, - }).then(() => exec(flowBinPath, ['start'], { cwd }).catch(err => { + })) + .then(() => exec(flowBinPath, ['start'], { cwd }).catch(err => { if ( typeof err.message === 'string' && err.message.indexOf('There is already a server running') !== -1 @@ -122,7 +123,7 @@ class FlowTypecheckPlugin { } else { throw err; } - }))) + })) .then(() => { this.flowStarted = true; }); From fb4735c106b7f17a11b76b9f0c9d3bb89e011918 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Mon, 24 Apr 2017 17:19:41 -0400 Subject: [PATCH 72/74] Simulate a mutex for startFlow --- .../react-dev-utils/FlowTypecheckPlugin.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index e0134bc9ce9..4ee38d97c36 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -78,6 +78,7 @@ class FlowTypecheckPlugin { constructor() { this.shouldRun = false; this.flowStarted = false; + this.flowStarting = null; this.flowVersion = require(path.join( __dirname, @@ -89,8 +90,16 @@ class FlowTypecheckPlugin { if (this.flowStarted) { return Promise.resolve(); } + if (this.flowStarting != null) { + return this.flowStarting; + } console.log(chalk.cyan('Starting the flow server ...')); const flowConfigPath = path.join(cwd, '.flowconfig'); + let success, fail; + this.flowStarting = new Promise((resolve, reject) => { + success = resolve; + fail = reject; + }); return getFlowVersion(true) .then(globalVersion => { if (globalVersion === null) return; @@ -126,6 +135,13 @@ class FlowTypecheckPlugin { })) .then(() => { this.flowStarted = true; + success(); + this.flowStarting = null; + }) + .catch(err => { + fail(); + this.flowStarting = null; + throw err; }); } @@ -159,7 +175,7 @@ class FlowTypecheckPlugin { return; } const cwd = compiler.options.context; - const first = !this.flowStarted; + const first = this.flowStarting == null && !this.flowStarted; this.startFlow(cwd) .then(() => { if (first) { From 800af94575280a4e024983bb34980200c9a4b7f9 Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 3 May 2017 23:32:54 -0400 Subject: [PATCH 73/74] Fix unhandled rejection --- .../react-dev-utils/FlowTypecheckPlugin.js | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/react-dev-utils/FlowTypecheckPlugin.js b/packages/react-dev-utils/FlowTypecheckPlugin.js index 4ee38d97c36..7062a350174 100644 --- a/packages/react-dev-utils/FlowTypecheckPlugin.js +++ b/packages/react-dev-utils/FlowTypecheckPlugin.js @@ -91,14 +91,20 @@ class FlowTypecheckPlugin { return Promise.resolve(); } if (this.flowStarting != null) { - return this.flowStarting; + return this.flowStarting.then(err => { + // We need to do it like this because of unhandled rejections + // ... basically, we can't actually reject a promise unless someone + // has it handled -- which is only the case when we're returned from here + if (err != null) { + throw err; + } + }); } console.log(chalk.cyan('Starting the flow server ...')); const flowConfigPath = path.join(cwd, '.flowconfig'); - let success, fail; - this.flowStarting = new Promise((resolve, reject) => { - success = resolve; - fail = reject; + let delegate; + this.flowStarting = new Promise(resolve => { + delegate = resolve; }); return getFlowVersion(true) .then(globalVersion => { @@ -135,11 +141,11 @@ class FlowTypecheckPlugin { })) .then(() => { this.flowStarted = true; - success(); + delegate(); this.flowStarting = null; }) .catch(err => { - fail(); + delegate(err); this.flowStarting = null; throw err; }); From ad625f16c792e2ea7a0b571c021bcc990167610a Mon Sep 17 00:00:00 2001 From: Joe Haddad Date: Wed, 3 May 2017 23:34:19 -0400 Subject: [PATCH 74/74] Upgrade flow --- packages/react-dev-utils/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json index b35129fbbc5..7ca9d4ccb7f 100644 --- a/packages/react-dev-utils/package.json +++ b/packages/react-dev-utils/package.json @@ -32,7 +32,7 @@ "chalk": "1.1.3", "escape-string-regexp": "1.0.5", "filesize": "3.3.0", - "flow-bin": "0.44.2", + "flow-bin": "0.45.0", "gzip-size": "3.0.0", "html-entities": "1.2.0", "opn": "4.0.2",