diff --git a/packages/react-dev-utils/README.md b/packages/react-dev-utils/README.md
index 11f3f8eaa8a62e30c35f8ec48ec70e5913bbf230..d65c43ec667967db505bfea1472ef556af8b408f 100644
--- a/packages/react-dev-utils/README.md
+++ b/packages/react-dev-utils/README.md
@@ -252,6 +252,19 @@ if (openBrowser('http://localhost:3000')) {
 }
 ```
 
+#### `printHostingInstructions(appPackage: Object, publicUrl: string, publicPath: string, buildFolder: string, useYarn: boolean): void`
+
+Prints hosting instructions after the project is built.
+
+Pass your parsed `package.json` object as `appPackage`, your the URL where you plan to host the app as `publicUrl`, `output.publicPath` from your Webpack configuration as `publicPath`, the `buildFolder` name, and whether to `useYarn` in instructions.
+
+```js
+const appPackage = require(paths.appPackageJson);
+const publicUrl = paths.publicUrl;
+const publicPath = config.output.publicPath;
+printHostingInstructions(appPackage, publicUrl, publicPath, 'build', true);
+```
+
 #### `webpackHotDevClient.js`
 
 This is an alternative client for [WebpackDevServer](https://github.com/webpack/webpack-dev-server) that shows a syntax error overlay.
diff --git a/packages/react-dev-utils/eslintFormatter.js b/packages/react-dev-utils/eslintFormatter.js
index 674f2f06decb943bf34d339deb7ff36a17720047..c087734eadd0fafcae7d9d169ab442f22a9034eb 100644
--- a/packages/react-dev-utils/eslintFormatter.js
+++ b/packages/react-dev-utils/eslintFormatter.js
@@ -13,6 +13,7 @@ function isError(message) {
 function formatter(results) {
   let output = '\n';
   let hasErrors = false;
+  let reportContainsErrorRuleIDs = false;
 
   results.forEach(result => {
     let messages = result.messages;
@@ -25,6 +26,9 @@ function formatter(results) {
       if (isError(message)) {
         messageType = 'error';
         hasErrors = true;
+        if (message.ruleId) {
+          reportContainsErrorRuleIDs = true;
+        }
       } else {
         messageType = 'warn';
       }
@@ -61,7 +65,7 @@ function formatter(results) {
     output += `${outputTable}\n\n`;
   });
 
-  if (hasErrors) {
+  if (reportContainsErrorRuleIDs) {
     // Unlike with warnings, we have to do it here.
     // We have similar code in react-scripts for warnings,
     // but warnings can appear in multiple files so we only
diff --git a/packages/react-dev-utils/package.json b/packages/react-dev-utils/package.json
index 0a22499ca015bbd6540330ad85a77b5f507e9f0d..c63502cd8b00177062e58355dac7fa938dcb21bc 100644
--- a/packages/react-dev-utils/package.json
+++ b/packages/react-dev-utils/package.json
@@ -25,6 +25,7 @@
     "openBrowser.js",
     "openChrome.applescript",
     "prepareProxy.js",
+    "printHostingInstructions.js",
     "WatchMissingNodeModulesPlugin.js",
     "webpackHotDevClient.js"
   ],
diff --git a/packages/react-dev-utils/printHostingInstructions.js b/packages/react-dev-utils/printHostingInstructions.js
new file mode 100644
index 0000000000000000000000000000000000000000..4b31cbc449988b51219501a6b22c62b11446ae15
--- /dev/null
+++ b/packages/react-dev-utils/printHostingInstructions.js
@@ -0,0 +1,114 @@
+/**
+ * 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';
+
+const chalk = require('chalk');
+const url = require('url');
+
+function printHostingInstructions(
+  appPackage,
+  publicUrl,
+  publicPath,
+  buildFolder,
+  useYarn
+) {
+  const publicPathname = url.parse(publicPath).pathname;
+  if (publicUrl && publicUrl.indexOf('.github.io/') !== -1) {
+    // "homepage": "http://user.github.io/project"
+    console.log(
+      `The project was built assuming it is hosted at ${chalk.green(publicPathname)}.`
+    );
+    console.log(
+      `You can control this with the ${chalk.green('homepage')} field in your ${chalk.cyan('package.json')}.`
+    );
+    console.log();
+    console.log(`The ${chalk.cyan('build')} folder is ready to be deployed.`);
+    console.log(`To publish it at ${chalk.green(publicUrl)}, run:`);
+    // If script deploy has been added to package.json, skip the instructions
+    if (typeof appPackage.scripts.deploy === 'undefined') {
+      console.log();
+      if (useYarn) {
+        console.log(`  ${chalk.cyan('yarn')} add --dev gh-pages`);
+      } else {
+        console.log(`  ${chalk.cyan('npm')} install --save-dev gh-pages`);
+      }
+      console.log();
+      console.log(
+        `Add the following script in your ${chalk.cyan('package.json')}.`
+      );
+      console.log();
+      console.log(`    ${chalk.dim('// ...')}`);
+      console.log(`    ${chalk.yellow('"scripts"')}: {`);
+      console.log(`      ${chalk.dim('// ...')}`);
+      console.log(
+        `      ${chalk.yellow('"predeploy"')}: ${chalk.yellow('"npm run build",')}`
+      );
+      console.log(
+        `      ${chalk.yellow('"deploy"')}: ${chalk.yellow('"gh-pages -d build"')}`
+      );
+      console.log('    }');
+      console.log();
+      console.log('Then run:');
+    }
+    console.log();
+    console.log(`  ${chalk.cyan(useYarn ? 'yarn' : 'npm')} run deploy`);
+    console.log();
+  } else if (publicPath !== '/') {
+    // "homepage": "http://mywebsite.com/project"
+    console.log(
+      `The project was built assuming it is hosted at ${chalk.green(publicPath)}.`
+    );
+    console.log(
+      `You can control this with the ${chalk.green('homepage')} field in your ${chalk.cyan('package.json')}.`
+    );
+    console.log();
+    console.log(`The ${chalk.cyan('build')} folder is ready to be deployed.`);
+    console.log();
+  } else {
+    if (publicUrl) {
+      // "homepage": "http://mywebsite.com"
+      console.log(
+        `The project was built assuming it is hosted at ${chalk.green(publicUrl)}.`
+      );
+      console.log(
+        `You can control this with the ${chalk.green('homepage')} field in your ${chalk.cyan('package.json')}.`
+      );
+      console.log();
+    } else {
+      // no homepage
+      console.log(
+        'The project was built assuming it is hosted at the server root.'
+      );
+      console.log(
+        `To override this, specify the ${chalk.green('homepage')} in your ${chalk.cyan('package.json')}.`
+      );
+      console.log('For example, add this to build it for GitHub Pages:');
+      console.log();
+      console.log(
+        `  ${chalk.green('"homepage"')} ${chalk.cyan(':')} ${chalk.green('"http://myname.github.io/myapp"')}${chalk.cyan(',')}`
+      );
+      console.log();
+    }
+    console.log(
+      `The ${chalk.cyan(buildFolder)} folder is ready to be deployed.`
+    );
+    console.log('You may serve it with a static server:');
+    console.log();
+    if (useYarn) {
+      console.log(`  ${chalk.cyan('yarn')} global add serve`);
+    } else {
+      console.log(`  ${chalk.cyan('npm')} install -g serve`);
+    }
+    console.log(`  ${chalk.cyan('serve')} -s build`);
+    console.log();
+  }
+}
+
+module.exports = printHostingInstructions;
diff --git a/packages/react-scripts/scripts/build.js b/packages/react-scripts/scripts/build.js
index da9004f9ee66155e1275fddc51f78ca40f40d366..8a4149370d2eaf63482078a53130fcf615e76d07 100644
--- a/packages/react-scripts/scripts/build.js
+++ b/packages/react-scripts/scripts/build.js
@@ -23,14 +23,15 @@ process.on('unhandledRejection', err => {
 // Ensure environment variables are read.
 require('../config/env');
 
+const path = require('path');
 const chalk = require('chalk');
 const fs = require('fs-extra');
-const path = require('path');
-const url = require('url');
 const webpack = require('webpack');
 const config = require('../config/webpack.config.prod');
 const paths = require('../config/paths');
 const checkRequiredFiles = require('react-dev-utils/checkRequiredFiles');
+const formatWebpackMessages = require('react-dev-utils/formatWebpackMessages');
+const printHostingInstructions = require('react-dev-utils/printHostingInstructions');
 const FileSizeReporter = require('react-dev-utils/FileSizeReporter');
 
 const measureFileSizesBeforeBuild = FileSizeReporter.measureFileSizesBeforeBuild;
@@ -44,159 +45,74 @@ if (!checkRequiredFiles([paths.appHtml, paths.appIndexJs])) {
 
 // First, read the current file sizes in build directory.
 // This lets us display how much they changed later.
-measureFileSizesBeforeBuild(paths.appBuild).then(previousFileSizes => {
-  // Remove all content but keep the directory so that
-  // if you're in it, you don't end up in Trash
-  fs.emptyDirSync(paths.appBuild);
-
-  // Start the webpack build
-  build(previousFileSizes);
-
-  // Merge with the public folder
-  copyPublicFolder();
-});
-
-// Print out errors
-function printErrors(summary, errors) {
-  console.log(chalk.red(summary));
-  console.log();
-  errors.forEach(err => {
-    console.log(err.message || err);
-    console.log();
-  });
-}
-
-// Create the production build and print the deployment instructions.
-function build(previousFileSizes) {
-  console.log('Creating an optimized production build...');
-
-  let compiler;
-  try {
-    compiler = webpack(config);
-  } catch (err) {
-    printErrors('Failed to compile.', [err]);
-    process.exit(1);
-  }
-
-  compiler.run((err, stats) => {
-    if (err) {
-      printErrors('Failed to compile.', [err]);
-      process.exit(1);
-    }
+measureFileSizesBeforeBuild(paths.appBuild)
+  .then(previousFileSizes => {
+    // Remove all content but keep the directory so that
+    // if you're in it, you don't end up in Trash
+    fs.emptyDirSync(paths.appBuild);
+    // Merge with the public folder
+    copyPublicFolder();
+    // Start the webpack build
+    return build(previousFileSizes);
+  })
+  .then(
+    ({ stats, previousFileSizes }) => {
+      console.log(chalk.green('Compiled successfully.'));
+      console.log();
 
-    if (stats.compilation.errors.length) {
-      printErrors('Failed to compile.', stats.compilation.errors);
-      process.exit(1);
-    }
+      console.log('File sizes after gzip:');
+      console.log();
+      printFileSizesAfterBuild(stats, previousFileSizes);
+      console.log();
 
-    if (process.env.CI && stats.compilation.warnings.length) {
-      printErrors(
-        'Failed to compile. When process.env.CI = true, warnings are treated as failures. Most CI servers set this automatically.',
-        stats.compilation.warnings
+      const appPackage = require(paths.appPackageJson);
+      const publicUrl = paths.publicUrl;
+      const publicPath = config.output.publicPath;
+      const buildFolder = path.relative(process.cwd(), paths.appBuild);
+      printHostingInstructions(
+        appPackage,
+        publicUrl,
+        publicPath,
+        buildFolder,
+        useYarn
       );
+    },
+    err => {
+      console.log(chalk.red('Failed to compile.'));
+      console.log();
+      console.log(err.message || err);
+      console.log();
       process.exit(1);
     }
+  );
 
-    console.log(chalk.green('Compiled successfully.'));
-    console.log();
-
-    console.log('File sizes after gzip:');
-    console.log();
-    printFileSizesAfterBuild(stats, previousFileSizes);
-    console.log();
+// Create the production build and print the deployment instructions.
+function build(previousFileSizes) {
+  console.log('Creating an optimized production build...');
 
-    const appPackage = require(paths.appPackageJson);
-    const publicUrl = paths.publicUrl;
-    const publicPath = config.output.publicPath;
-    const publicPathname = url.parse(publicPath).pathname;
-    if (publicUrl && publicUrl.indexOf('.github.io/') !== -1) {
-      // "homepage": "http://user.github.io/project"
-      console.log(
-        `The project was built assuming it is hosted at ${chalk.green(publicPathname)}.`
-      );
-      console.log(
-        `You can control this with the ${chalk.green('homepage')} field in your ${chalk.cyan('package.json')}.`
-      );
-      console.log();
-      console.log(`The ${chalk.cyan('build')} folder is ready to be deployed.`);
-      console.log(`To publish it at ${chalk.green(publicUrl)}, run:`);
-      // If script deploy has been added to package.json, skip the instructions
-      if (typeof appPackage.scripts.deploy === 'undefined') {
-        console.log();
-        if (useYarn) {
-          console.log(`  ${chalk.cyan('yarn')} add --dev gh-pages`);
-        } else {
-          console.log(`  ${chalk.cyan('npm')} install --save-dev gh-pages`);
-        }
-        console.log();
-        console.log(
-          `Add the following script in your ${chalk.cyan('package.json')}.`
-        );
-        console.log();
-        console.log(`    ${chalk.dim('// ...')}`);
-        console.log(`    ${chalk.yellow('"scripts"')}: {`);
-        console.log(`      ${chalk.dim('// ...')}`);
-        console.log(
-          `      ${chalk.yellow('"predeploy"')}: ${chalk.yellow('"npm run build",')}`
-        );
-        console.log(
-          `      ${chalk.yellow('"deploy"')}: ${chalk.yellow('"gh-pages -d build"')}`
-        );
-        console.log('    }');
-        console.log();
-        console.log('Then run:');
+  let compiler = webpack(config);
+  return new Promise((resolve, reject) => {
+    compiler.run((err, stats) => {
+      if (err) {
+        return reject(err);
       }
-      console.log();
-      console.log(`  ${chalk.cyan(useYarn ? 'yarn' : 'npm')} run deploy`);
-      console.log();
-    } else if (publicPath !== '/') {
-      // "homepage": "http://mywebsite.com/project"
-      console.log(
-        `The project was built assuming it is hosted at ${chalk.green(publicPath)}.`
-      );
-      console.log(
-        `You can control this with the ${chalk.green('homepage')} field in your ${chalk.cyan('package.json')}.`
-      );
-      console.log();
-      console.log(`The ${chalk.cyan('build')} folder is ready to be deployed.`);
-      console.log();
-    } else {
-      if (publicUrl) {
-        // "homepage": "http://mywebsite.com"
-        console.log(
-          `The project was built assuming it is hosted at ${chalk.green(publicUrl)}.`
-        );
-        console.log(
-          `You can control this with the ${chalk.green('homepage')} field in your ${chalk.cyan('package.json')}.`
-        );
-        console.log();
-      } else {
-        // no homepage
-        console.log(
-          'The project was built assuming it is hosted at the server root.'
-        );
-        console.log(
-          `To override this, specify the ${chalk.green('homepage')} in your ${chalk.cyan('package.json')}.`
-        );
-        console.log('For example, add this to build it for GitHub Pages:');
+      const messages = formatWebpackMessages(stats.toJson({}, true));
+      if (messages.errors.length) {
+        return reject(new Error(messages.errors.join('\n\n')));
+      }
+      if (process.env.CI && messages.warnings.length) {
         console.log();
         console.log(
-          `  ${chalk.green('"homepage"')} ${chalk.cyan(':')} ${chalk.green('"http://myname.github.io/myapp"')}${chalk.cyan(',')}`
+          chalk.yellow(
+            'Treating warnings as errors because process.env.CI = true.\n' +
+              'Most CI servers set it automatically.'
+          )
         );
         console.log();
+        return reject(new Error(messages.warnings.join('\n\n')));
       }
-      const build = path.relative(process.cwd(), paths.appBuild);
-      console.log(`The ${chalk.cyan(build)} folder is ready to be deployed.`);
-      console.log('You may serve it with a static server:');
-      console.log();
-      if (useYarn) {
-        console.log(`  ${chalk.cyan('yarn')} global add serve`);
-      } else {
-        console.log(`  ${chalk.cyan('npm')} install -g serve`);
-      }
-      console.log(`  ${chalk.cyan('serve')} -s build`);
-      console.log();
-    }
+      return resolve({ stats, previousFileSizes });
+    });
   });
 }