Deprecate --server argument to `pub publish` and `pub uploader`. (#2697)

This argument does not really work optimally.

This also fixes the error message given when
PUB_HOSTED_URL has a bad scheme.
diff --git a/lib/src/command/lish.dart b/lib/src/command/lish.dart
index 2dbd6dc..f33a529 100644
--- a/lib/src/command/lish.dart
+++ b/lib/src/command/lish.dart
@@ -62,7 +62,8 @@
         negatable: false,
         help: 'Publish without confirmation if there are no errors.');
     argParser.addOption('server',
-        help: 'The package server to which to upload this package.');
+        help: 'The package server to which to upload this package.',
+        hide: true);
   }
 
   Future<void> _publish(List<int> packageBytes) async {
@@ -116,6 +117,16 @@
 
   @override
   Future 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 (force && dryRun) {
       usageException('Cannot use both --force and --dry-run.');
     }
diff --git a/lib/src/command/uploader.dart b/lib/src/command/uploader.dart
index dc28eca..f80419c 100644
--- a/lib/src/command/uploader.dart
+++ b/lib/src/command/uploader.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:io';
 
 import '../command.dart';
 import '../exceptions.dart';
@@ -28,8 +29,10 @@
 
   UploaderCommand() {
     argParser.addOption('server',
-        defaultsTo: cache.sources.hosted.defaultUrl,
-        help: 'The package server on which the package is hosted.');
+        defaultsTo: Platform.environment['PUB_HOSTED_URL'] ??
+            'https://pub.dartlang.org',
+        help: 'The package server on which the package is hosted.\n',
+        hide: true);
     argParser.addOption('package',
         help: 'The package whose uploaders will be modified.\n'
             '(defaults to the current package)');
@@ -37,6 +40,15 @@
 
   @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();
diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart
index d8c3ef7..84810a2 100644
--- a/lib/src/source/hosted.dart
+++ b/lib/src/source/hosted.dart
@@ -62,10 +62,10 @@
     var url = io.Platform.environment['PUB_HOSTED_URL'];
     if (url == null) return null;
     var uri = Uri.parse(url);
-    if (uri.scheme?.isEmpty ?? true) {
+    if (!uri.isScheme('http') && !uri.isScheme('https')) {
       throw ConfigException(
-          '`PUB_HOSTED_URL` must include a scheme such as "https://". '
-          '$url is invalid');
+          '`PUB_HOSTED_URL` must have either the scheme "https://" or "http://". '
+          '"$url" is invalid.');
     }
     return url;
   }
diff --git a/test/get/hosted/explain_bad_hosted_url_test.dart b/test/get/hosted/explain_bad_hosted_url_test.dart
new file mode 100644
index 0000000..c4b2e77
--- /dev/null
+++ b/test/get/hosted/explain_bad_hosted_url_test.dart
@@ -0,0 +1,27 @@
+// Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
+// 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 'package:test/test.dart';
+
+import '../../descriptor.dart' as d;
+import '../../test_pub.dart';
+
+void main() {
+  test('Complains nicely about invalid PUB_HOSTED_URL', () async {
+    await d.appDir({'foo': 'any'}).create();
+
+    // Get once so it gets cached.
+    await pubGet(
+        environment: {'PUB_HOSTED_URL': 'abc://bad_scheme.com'},
+        error: contains(
+            'PUB_HOSTED_URL` must have either the scheme "https://" or "http://".'),
+        exitCode: 78);
+
+    await pubGet(
+        environment: {'PUB_HOSTED_URL': ''},
+        error: contains(
+            'PUB_HOSTED_URL` must have either the scheme "https://" or "http://".'),
+        exitCode: 78);
+  });
+}
diff --git a/test/lish/does_not_publish_if_private_with_server_arg_test.dart b/test/lish/does_not_publish_if_private_with_server_arg_test.dart
index 96cc1f5..d446ec5 100644
--- a/test/lish/does_not_publish_if_private_with_server_arg_test.dart
+++ b/test/lish/does_not_publish_if_private_with_server_arg_test.dart
@@ -18,8 +18,10 @@
     await d.dir(appPath, [d.pubspec(pkg)]).create();
 
     await runPub(
-        args: ['lish', '--server', 'http://example.com'],
-        error: startsWith('A private package cannot be published.'),
-        exitCode: exit_codes.DATA);
+      args: ['lish'],
+      error: startsWith('A private package cannot be published.'),
+      environment: {'PUB_HOSTED_URL': 'http://example.com'},
+      exitCode: exit_codes.DATA,
+    );
   });
 }
diff --git a/test/lish/force_cannot_be_combined_with_dry_run_test.dart b/test/lish/force_cannot_be_combined_with_dry_run_test.dart
index aabdcfb..ea46ead 100644
--- a/test/lish/force_cannot_be_combined_with_dry_run_test.dart
+++ b/test/lish/force_cannot_be_combined_with_dry_run_test.dart
@@ -2,9 +2,8 @@
 // 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 'package:test/test.dart';
-
 import 'package:pub/src/exit_codes.dart' as exit_codes;
+import 'package:test/test.dart';
 
 import '../descriptor.dart' as d;
 import '../test_pub.dart';
@@ -14,16 +13,15 @@
 
   test('--force cannot be combined with --dry-run', () async {
     await runPub(args: ['lish', '--force', '--dry-run'], error: '''
-          Cannot use both --force and --dry-run.
-          
-          Usage: pub publish [options]
-          -h, --help       Print this usage information.
-          -n, --dry-run    Validate but do not publish the package.
-          -f, --force      Publish without confirmation if there are no errors.
-              --server     The package server to which to upload this package.
+Cannot use both --force and --dry-run.
 
-          Run "pub help" to see global options.
-          See https://dart.dev/tools/pub/cmd/pub-lish for detailed documentation.
-          ''', exitCode: exit_codes.USAGE);
+Usage: pub publish [options]
+-h, --help       Print this usage information.
+-n, --dry-run    Validate but do not publish the package.
+-f, --force      Publish without confirmation if there are no errors.
+
+Run "pub help" to see global options.
+See https://dart.dev/tools/pub/cmd/pub-lish for detailed documentation.
+''', exitCode: exit_codes.USAGE);
   });
 }
diff --git a/test/pub_uploader_test.dart b/test/pub_uploader_test.dart
index 1de8708..f1967e0 100644
--- a/test/pub_uploader_test.dart
+++ b/test/pub_uploader_test.dart
@@ -5,12 +5,11 @@
 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 'package:pub/src/exit_codes.dart' as exit_codes;
-
 import 'descriptor.dart' as d;
 import 'test_pub.dart';
 
@@ -19,8 +18,6 @@
 
 Usage: pub uploader [options] {add/remove} <email>
 -h, --help       Print this usage information.
-    --server     The package server on which the package is hosted.
-                 (defaults to "https://pub.dartlang.org")
     --package    The package whose uploaders will be modified.
                  (defaults to the current package)
 
@@ -30,8 +27,11 @@
 
 Future<TestProcess> startPubUploader(PackageServer server, List<String> args) {
   var tokenEndpoint = Uri.parse(server.url).resolve('/token').toString();
-  var allArgs = ['uploader', '--server', tokenEndpoint, ...args];
-  return startPub(args: allArgs, tokenEndpoint: tokenEndpoint);
+  var allArgs = ['uploader', ...args];
+  return startPub(
+      args: allArgs,
+      tokenEndpoint: tokenEndpoint,
+      environment: {'PUB_HOSTED_URL': tokenEndpoint});
 }
 
 void main() {
diff --git a/test/test_pub.dart b/test/test_pub.dart
index 45d47c2..c98dcb8 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -336,8 +336,11 @@
 Future<PubProcess> startPublish(PackageServer server,
     {List<String> args}) async {
   var tokenEndpoint = Uri.parse(server.url).resolve('/token').toString();
-  args = ['lish', '--server', server.url, ...?args];
-  return await startPub(args: args, tokenEndpoint: tokenEndpoint);
+  args = ['lish', ...?args];
+  return await startPub(
+      args: args,
+      tokenEndpoint: tokenEndpoint,
+      environment: {'PUB_HOSTED_URL': server.url});
 }
 
 /// Handles the beginning confirmation process for uploading a packages.