Publish check (#3556)

diff --git a/script/check_publish.sh b/script/check_publish.sh
index 5584fc6..c92de4b 100755
--- a/script/check_publish.sh
+++ b/script/check_publish.sh
@@ -10,33 +10,9 @@
 
 source "$SCRIPT_DIR/common.sh"
 
-function check_publish() {
-  local failures=()
-  for dir in $(plugin_tools list --plugins="$1"); do
-    local package_name=$(basename "$dir")
-
-    echo "Checking that $package_name can be published."
-    if [[ $(cd "$dir" && cat pubspec.yaml | grep -E "^publish_to: none") ]]; then
-      echo "Package $package_name is marked as unpublishable. Skipping."
-    elif (cd "$dir" && flutter pub publish -- --dry-run > /dev/null); then
-      echo "Package $package_name is able to be published."
-    else
-      error "Unable to publish $package_name"
-      failures=("${failures[@]}" "$package_name")
-    fi
-  done
-  if [[ "${#failures[@]}" != 0 ]]; then
-    error "FAIL: The following ${#failures[@]} package(s) failed the publishing check:"
-    for failure in "${failures[@]}"; do
-      error "$failure"
-    done
-  fi
-  return "${#failures[@]}"
-}
-
 # Sets CHANGED_PACKAGE_LIST and CHANGED_PACKAGES
 check_changed_packages
 
 if [[ "${#CHANGED_PACKAGE_LIST[@]}" != 0 ]]; then
-  check_publish "${CHANGED_PACKAGES}"
+  plugin_tools publish-check --plugins="${CHANGED_PACKAGES}"
 fi
diff --git a/script/tool/lib/src/main.dart b/script/tool/lib/src/main.dart
index bb3f67c..fa81597 100644
--- a/script/tool/lib/src/main.dart
+++ b/script/tool/lib/src/main.dart
@@ -7,6 +7,7 @@
 import 'package:args/command_runner.dart';
 import 'package:file/file.dart';
 import 'package:file/local.dart';
+import 'package:flutter_plugin_tools/src/publish_check_command.dart';
 import 'package:flutter_plugin_tools/src/publish_plugin_command.dart';
 import 'package:path/path.dart' as p;
 
@@ -51,6 +52,7 @@
     ..addCommand(JavaTestCommand(packagesDir, fileSystem))
     ..addCommand(LintPodspecsCommand(packagesDir, fileSystem))
     ..addCommand(ListCommand(packagesDir, fileSystem))
+    ..addCommand(PublishCheckCommand(packagesDir, fileSystem))
     ..addCommand(PublishPluginCommand(packagesDir, fileSystem))
     ..addCommand(TestCommand(packagesDir, fileSystem))
     ..addCommand(VersionCheckCommand(packagesDir, fileSystem))
diff --git a/script/tool/lib/src/publish_check_command.dart b/script/tool/lib/src/publish_check_command.dart
new file mode 100644
index 0000000..8d6f6bb
--- /dev/null
+++ b/script/tool/lib/src/publish_check_command.dart
@@ -0,0 +1,92 @@
+// Copyright 2017 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+
+import 'package:colorize/colorize.dart';
+import 'package:file/file.dart';
+import 'package:pubspec_parse/pubspec_parse.dart';
+
+import 'common.dart';
+
+class PublishCheckCommand extends PluginCommand {
+  PublishCheckCommand(
+    Directory packagesDir,
+    FileSystem fileSystem, {
+    ProcessRunner processRunner = const ProcessRunner(),
+  }) : super(packagesDir, fileSystem, processRunner: processRunner);
+
+  @override
+  final String name = 'publish-check';
+
+  @override
+  final String description =
+      'Checks to make sure that a plugin *could* be published.';
+
+  @override
+  Future<Null> run() async {
+    checkSharding();
+    final List<Directory> failedPackages = <Directory>[];
+
+    await for (Directory plugin in getPlugins()) {
+      if (!(await passesPublishCheck(plugin))) failedPackages.add(plugin);
+    }
+
+    if (failedPackages.isNotEmpty) {
+      final String error =
+          'FAIL: The following ${failedPackages.length} package(s) failed the '
+          'publishing check:';
+      final String joinedFailedPackages = failedPackages.join('\n');
+
+      final Colorize colorizedError = Colorize('$error\n$joinedFailedPackages')
+        ..red();
+      print(colorizedError);
+      throw ToolExit(1);
+    }
+
+    final Colorize passedMessage =
+        Colorize('All packages passed publish check!')..green();
+    print(passedMessage);
+  }
+
+  Pubspec tryParsePubspec(Directory package) {
+    final File pubspecFile = package.childFile('pubspec.yaml');
+
+    try {
+      return Pubspec.parse(pubspecFile.readAsStringSync());
+    } on Exception catch (exception) {
+      print(
+        'Failed to parse `pubspec.yaml` at ${pubspecFile.path}: $exception}',
+      );
+      return null;
+    }
+  }
+
+  Future<bool> passesPublishCheck(Directory package) async {
+    final String packageName = package.basename;
+    print('Checking that $packageName can be published.');
+
+    final Pubspec pubspec = tryParsePubspec(package);
+    if (pubspec == null) {
+      return false;
+    } else if (pubspec.publishTo == 'none') {
+      print('Package $packageName is marked as unpublishable. Skipping.');
+      return true;
+    }
+
+    final int exitCode = await processRunner.runAndStream(
+      'flutter',
+      <String>['pub', 'publish', '--', '--dry-run'],
+      workingDir: package,
+    );
+
+    if (exitCode == 0) {
+      print("Package $packageName is able to be published.");
+      return true;
+    } else {
+      print('Unable to publish $packageName');
+      return false;
+    }
+  }
+}