Remove `uploader` command (#3335)

We now have a web-interface for managing the uploaders. That should be the preferred way.

Leaving a (hidden) stub telling the user to use the web interface instead.

See also breaking change request: https://github.com/dart-lang/sdk#48526
diff --git a/lib/src/command/uploader.dart b/lib/src/command/uploader.dart
index 99a6a57..cae4963 100644
--- a/lib/src/command/uploader.dart
+++ b/lib/src/command/uploader.dart
@@ -6,10 +6,7 @@
 import 'dart:io';
 
 import '../command.dart';
-import '../exit_codes.dart' as exit_codes;
-import '../http.dart';
-import '../log.dart' as log;
-import '../oauth2.dart' as oauth2;
+import '../utils.dart';
 
 /// Handles the `uploader` pub command.
 class UploaderCommand extends PubCommand {
@@ -23,6 +20,9 @@
   @override
   String get docUrl => 'https://dart.dev/tools/pub/cmd/pub-uploader';
 
+  @override
+  bool get hidden => true;
+
   /// The URL of the package hosting server.
   Uri get server => Uri.parse(argResults['server']);
 
@@ -41,58 +41,18 @@
 
   @override
   Future<void> runProtected() async {
-    if (argResults.wasParsed('server')) {
-      await log.warningsOnlyUnlessTerminal(() {
-        log.message(
-          '''
-The --server option is deprecated. Use `publish_to` in your pubspec.yaml or set
-the \$PUB_HOSTED_URL environment variable.''',
-        );
-      });
-    }
-    if (argResults.rest.isEmpty) {
-      log.error('No uploader command given.');
-      printUsage();
-      overrideExitCode(exit_codes.USAGE);
-      return;
-    }
-
-    var rest = argResults.rest.toList();
-
-    // TODO(rnystrom): Use subcommands for these.
-    var command = rest.removeAt(0);
-    if (!['add', 'remove'].contains(command)) {
-      log.error('Unknown uploader command "$command".');
-      printUsage();
-      overrideExitCode(exit_codes.USAGE);
-      return;
-    } else if (rest.isEmpty) {
-      log.error('No uploader given for "pub uploader $command".');
-      printUsage();
-      overrideExitCode(exit_codes.USAGE);
-      return;
-    }
-
-    final package = argResults['package'] ?? entrypoint.root.name;
-    final uploader = rest[0];
+    String packageName = '<packageName>';
     try {
-      final response = await oauth2.withClient(cache, (client) {
-        if (command == 'add') {
-          var url = server.resolve('/api/packages/'
-              '${Uri.encodeComponent(package)}/uploaders');
-          return client
-              .post(url, headers: pubApiHeaders, body: {'email': uploader});
-        } else {
-          // command == 'remove'
-          var url = server.resolve('/api/packages/'
-              '${Uri.encodeComponent(package)}/uploaders/'
-              '${Uri.encodeComponent(uploader)}');
-          return client.delete(url, headers: pubApiHeaders);
-        }
-      });
-      handleJsonSuccess(response);
-    } on PubHttpException catch (error) {
-      handleJsonError(error.response);
+      packageName = entrypoint.root.name;
+    } on Exception catch (_) {
+      // Probably run without a pubspec.
+      // Just print error below without a specific package name.
     }
+    fail('''
+Package uploaders are no longer managed from the command line.
+Manage uploaders from:
+
+https://pub.dev/packages/$packageName/admin
+''');
   }
 }
diff --git a/test/pub_uploader_test.dart b/test/pub_uploader_test.dart
index 47d50d3..643671a 100644
--- a/test/pub_uploader_test.dart
+++ b/test/pub_uploader_test.dart
@@ -2,168 +2,34 @@
 // for details. 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 'dart:convert';
-
-import 'package:pub/src/exit_codes.dart' as exit_codes;
-import 'package:shelf/shelf.dart' as shelf;
 import 'package:test/test.dart';
-import 'package:test_process/test_process.dart';
 
 import 'descriptor.dart' as d;
 import 'test_pub.dart';
 
-Future<TestProcess> startPubUploader(PackageServer server, List<String> args) {
-  var tokenEndpoint = Uri.parse(server.url).resolve('/token').toString();
-  var allArgs = ['uploader', ...args];
-  return startPub(
-      args: allArgs,
-      tokenEndpoint: tokenEndpoint,
-      environment: {'PUB_HOSTED_URL': tokenEndpoint});
-}
-
 void main() {
-  group('displays usage', () {
-    test('when run with no arguments', () {
-      return runPub(args: ['uploader'], exitCode: exit_codes.USAGE);
-    });
+  test('displays deprecation notice', () async {
+    await runPub(
+      args: ['uploader', 'add'],
+      error: '''
+Package uploaders are no longer managed from the command line.
+Manage uploaders from:
 
-    test('when run with only a command', () {
-      return runPub(args: ['uploader', 'add'], exitCode: exit_codes.USAGE);
-    });
+https://pub.dev/packages/<packageName>/admin
+''',
+      exitCode: 1,
+    );
 
-    test('when run with an invalid command', () {
-      return runPub(
-          args: ['uploader', 'foo', 'email'], exitCode: exit_codes.USAGE);
-    });
-  });
+    await d.appDir().create();
+    await runPub(
+      args: ['uploader', 'add'],
+      error: '''
+Package uploaders are no longer managed from the command line.
+Manage uploaders from:
 
-  test('adds an uploader', () async {
-    await servePackages();
-    await d.credentialsFile(globalServer, 'access token').create();
-    var pub = await startPubUploader(
-        globalServer, ['--package', 'pkg', 'add', 'email']);
-
-    globalServer.expect('POST', '/api/packages/pkg/uploaders', (request) {
-      return request.readAsString().then((body) {
-        expect(body, equals('email=email'));
-
-        return shelf.Response.ok(
-            jsonEncode({
-              'success': {'message': 'Good job!'}
-            }),
-            headers: {'content-type': 'application/json'});
-      });
-    });
-
-    expect(pub.stdout, emits('Good job!'));
-    await pub.shouldExit(exit_codes.SUCCESS);
-  });
-
-  test('removes an uploader', () async {
-    await servePackages();
-    await d.credentialsFile(globalServer, 'access token').create();
-    var pub = await startPubUploader(
-        globalServer, ['--package', 'pkg', 'remove', 'email']);
-
-    globalServer.expect('DELETE', '/api/packages/pkg/uploaders/email',
-        (request) {
-      return shelf.Response.ok(
-          jsonEncode({
-            'success': {'message': 'Good job!'}
-          }),
-          headers: {'content-type': 'application/json'});
-    });
-
-    expect(pub.stdout, emits('Good job!'));
-    await pub.shouldExit(exit_codes.SUCCESS);
-  });
-
-  test('defaults to the current package', () async {
-    await d.validPackage.create();
-
-    await servePackages();
-    await d.credentialsFile(globalServer, 'access token').create();
-    var pub = await startPubUploader(globalServer, ['add', 'email']);
-
-    globalServer.expect('POST', '/api/packages/test_pkg/uploaders', (request) {
-      return shelf.Response.ok(
-          jsonEncode({
-            'success': {'message': 'Good job!'}
-          }),
-          headers: {'content-type': 'application/json'});
-    });
-
-    expect(pub.stdout, emits('Good job!'));
-    await pub.shouldExit(exit_codes.SUCCESS);
-  });
-
-  test('add provides an error', () async {
-    await servePackages();
-    await d.credentialsFile(globalServer, 'access token').create();
-    var pub = await startPubUploader(
-        globalServer, ['--package', 'pkg', 'add', 'email']);
-
-    globalServer.expect('POST', '/api/packages/pkg/uploaders', (request) {
-      return shelf.Response(400,
-          body: jsonEncode({
-            'error': {'message': 'Bad job!'}
-          }),
-          headers: {'content-type': 'application/json'});
-    });
-
-    expect(pub.stderr, emits('Bad job!'));
-    await pub.shouldExit(1);
-  });
-
-  test('remove provides an error', () async {
-    await servePackages();
-    await d.credentialsFile(globalServer, 'access token').create();
-    var pub = await startPubUploader(
-        globalServer, ['--package', 'pkg', 'remove', 'e/mail']);
-
-    globalServer.expect('DELETE', '/api/packages/pkg/uploaders/e%2Fmail',
-        (request) {
-      return shelf.Response(400,
-          body: jsonEncode({
-            'error': {'message': 'Bad job!'}
-          }),
-          headers: {'content-type': 'application/json'});
-    });
-
-    expect(pub.stderr, emits('Bad job!'));
-    await pub.shouldExit(1);
-  });
-
-  test('add provides invalid JSON', () async {
-    await servePackages();
-    await d.credentialsFile(globalServer, 'access token').create();
-    var pub = await startPubUploader(
-        globalServer, ['--package', 'pkg', 'add', 'email']);
-
-    globalServer.expect('POST', '/api/packages/pkg/uploaders',
-        (request) => shelf.Response.ok('{not json'));
-
-    expect(
-        pub.stderr,
-        emitsLines('Invalid server response:\n'
-            '{not json'));
-    await pub.shouldExit(1);
-  });
-
-  test('remove provides invalid JSON', () async {
-    await servePackages();
-    await d.credentialsFile(globalServer, 'access token').create();
-    var pub = await startPubUploader(
-        globalServer, ['--package', 'pkg', 'remove', 'email']);
-
-    globalServer.expect('DELETE', '/api/packages/pkg/uploaders/email',
-        (request) => shelf.Response.ok('{not json'));
-
-    expect(
-        pub.stderr,
-        emitsLines('Invalid server response:\n'
-            '{not json'));
-    await pub.shouldExit(1);
+https://pub.dev/packages/myapp/admin
+''',
+      exitCode: 1,
+    );
   });
 }
diff --git a/test/testdata/goldens/directory_option_test/commands taking a --directory~-C parameter work.txt b/test/testdata/goldens/directory_option_test/commands taking a --directory~-C parameter work.txt
index d80d984..9585341 100644
--- a/test/testdata/goldens/directory_option_test/commands taking a --directory~-C parameter work.txt
+++ b/test/testdata/goldens/directory_option_test/commands taking a --directory~-C parameter work.txt
@@ -114,7 +114,11 @@
 
 ## Section 12
 $ pub uploader -C myapp add sigurdm@google.com
-Good job!
+[STDERR] Package uploaders are no longer managed from the command line.
+[STDERR] Manage uploaders from:
+[STDERR] 
+[STDERR] https://pub.dev/packages/test_pkg/admin
+[EXIT CODE] 1
 
 -------------------------------- END OF OUTPUT ---------------------------------
 
diff --git a/test/testdata/goldens/help_test/pub uploader --help.txt b/test/testdata/goldens/help_test/pub uploader --help.txt
deleted file mode 100644
index 10c9d9d..0000000
--- a/test/testdata/goldens/help_test/pub uploader --help.txt
+++ /dev/null
@@ -1,15 +0,0 @@
-# GENERATED BY: test/help_test.dart
-
-## Section 0
-$ pub uploader --help
-Manage uploaders for a package on pub.dartlang.org.
-
-Usage: pub uploader [options] {add/remove} <email>
--h, --help               Print this usage information.
-    --package            The package whose uploaders will be modified.
-                         (defaults to the current package)
--C, --directory=<dir>    Run this in the directory<dir>.
-
-Run "pub help" to see global options.
-See https://dart.dev/tools/pub/cmd/pub-uploader for detailed documentation.
-